Skip to content

Commit 61c6342

Browse files
committed
Account for struct padding.
1 parent 878842f commit 61c6342

File tree

1 file changed

+42
-3
lines changed

1 file changed

+42
-3
lines changed

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

+42-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Runtime.InteropServices;
56
using BenchmarkDotNet.Attributes;
67

78
namespace 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

Comments
 (0)