22using System . Collections . Generic ;
33using System . Linq ;
44using System . Reflection ;
5+ using System . Runtime . InteropServices ;
56using BenchmarkDotNet . Attributes ;
67
78namespace BenchmarkDotNet . Extensions
@@ -242,12 +243,50 @@ internal static bool IsDefaultFasterThanField(this Type type, bool isClassicMono
242243 _ when type . IsEnum
243244 => Enum . GetUnderlyingType ( type ) . SizeOfDefault ( ) ,
244245
245- // Note: the runtime pads structs for alignment purposes, and it enforces a minimum of 1 byte, even for empty structs,
246- // but we don't need to worry about either of those cases for the purpose this serves (calculating whether to use `default` or read a field in Mono for the overhead method).
247246 _ when type . IsValueType
248- => type . GetFields ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) . Aggregate ( 0 , ( count , field ) => field . FieldType . SizeOfDefault ( ) + count ) ,
247+ => type . SizeOfDefaultStruct ( ) ,
249248
250249 _ => throw new Exception ( "Unknown type size: " + type . FullName )
251250 } ;
251+
252+ private static int SizeOfDefaultStruct ( this Type structType )
253+ {
254+ // Find the offset of the highest field, so we only have to calculate the size of it + its offset.
255+ int fieldOffset = int . MinValue ;
256+ FieldInfo maxField = null ;
257+ bool containsReference = false ;
258+ foreach ( var field in structType . GetFields ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) )
259+ {
260+ var fieldType = field . FieldType ;
261+ containsReference |= fieldType . IsPointer || fieldType . IsClass || fieldType . IsInterface ;
262+ int offset = field . GetFieldOffset ( ) ;
263+ if ( offset > fieldOffset )
264+ {
265+ fieldOffset = offset ;
266+ maxField = field ;
267+ }
268+ }
269+ if ( maxField == null )
270+ {
271+ // Runtime enforces minimum struct size as 1 byte.
272+ return 1 ;
273+ }
274+ // Runtime pads struct for alignment purposes if it contains a reference.
275+ int structSize = maxField . FieldType . SizeOfDefault ( ) + fieldOffset ;
276+ return containsReference
277+ ? GetPaddedStructSize ( structSize )
278+ : structSize ;
279+ }
280+
281+ // Code obtained from https://stackoverflow.com/a/56512720
282+ private static int GetFieldOffset ( this FieldInfo fi ) => Marshal . ReadInt32 ( fi . FieldHandle . Value + ( 4 + IntPtr . Size ) ) & 0xFFFFFF ;
283+
284+ private static int GetPaddedStructSize ( int fieldsSize )
285+ {
286+ Math . DivRem ( fieldsSize , IntPtr . Size , out int padding ) ;
287+ return padding == 0
288+ ? fieldsSize
289+ : fieldsSize - padding + IntPtr . Size ;
290+ }
252291 }
253292}
0 commit comments