Skip to content

Commit cec7f9b

Browse files
committed
Match overhead return type to workload return type.
Match overhead action implementation to workload action implementation. Support more consume types. Cleaned up byref returns.
1 parent f32a2e7 commit cec7f9b

File tree

18 files changed

+254
-555
lines changed

18 files changed

+254
-555
lines changed

build/common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</PropertyGroup>
3434

3535
<PropertyGroup Condition=" '$(IsVisualBasic)' != 'true' AND '$(IsFsharp)' != 'true' ">
36-
<LangVersion>9.0</LangVersion>
36+
<LangVersion>11</LangVersion>
3737

3838
<Major>0</Major>
3939
<Minor>13</Minor>

src/BenchmarkDotNet/Code/CodeGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ internal static string Generate(BuildPartition buildPartition)
4848
.Replace("$WorkloadMethodDelegate$", provider.WorkloadMethodDelegate(passArguments))
4949
.Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName)
5050
.Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers)
51-
.Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName)
5251
.Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName)
5352
.Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName)
5453
.Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName)

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ internal abstract class DeclarationsProvider
4444

4545
public virtual string ConsumeField => null;
4646

47-
protected abstract Type OverheadMethodReturnType { get; }
48-
49-
public string OverheadMethodReturnTypeName => OverheadMethodReturnType.GetCorrectCSharpTypeName();
5047

5148
public abstract string OverheadImplementation { get; }
5249

@@ -76,8 +73,6 @@ public VoidDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
7673

7774
public override string ReturnsDefinition => "RETURNS_VOID";
7875

79-
protected override Type OverheadMethodReturnType => typeof(void);
80-
8176
public override string OverheadImplementation => string.Empty;
8277
}
8378

@@ -90,26 +85,19 @@ public override string ConsumeField
9085
? $".{field.Name}"
9186
: null;
9287

93-
protected override Type OverheadMethodReturnType
94-
=> Consumer.IsConsumable(WorkloadMethodReturnType)
95-
? WorkloadMethodReturnType
96-
: (Consumer.HasConsumableField(WorkloadMethodReturnType, out var field)
97-
? field.FieldType
98-
: typeof(int)); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself
99-
10088
public override string OverheadImplementation
10189
{
10290
get
10391
{
104-
string value;
105-
var type = OverheadMethodReturnType;
106-
if (type.GetTypeInfo().IsPrimitive)
107-
value = $"default({type.GetCorrectCSharpTypeName()})";
108-
else if (type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface)
109-
value = "null";
110-
else
111-
value = SourceCodeHelper.ToSourceCode(Activator.CreateInstance(type)) + ";";
112-
return $"return {value};";
92+
var type = WorkloadMethodReturnType;
93+
if (type.IsByRefLike())
94+
{
95+
return $"return default({type.GetCorrectCSharpTypeName()});";
96+
}
97+
return $"""
98+
System.Runtime.CompilerServices.Unsafe.SkipInit(out {type.GetCorrectCSharpTypeName()} value);
99+
return value;
100+
""";
113101
}
114102
}
115103

@@ -123,13 +111,11 @@ internal class ByRefDeclarationsProvider : NonVoidDeclarationsProvider
123111
{
124112
public ByRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
125113

126-
protected override Type OverheadMethodReturnType => typeof(IntPtr);
127-
128114
public override string WorkloadMethodReturnTypeName => base.WorkloadMethodReturnTypeName.Replace("&", string.Empty);
129115

130116
public override string ConsumeField => null;
131117

132-
public override string OverheadImplementation => $"return default(System.{nameof(IntPtr)});";
118+
public override string OverheadImplementation => $"return ref overheadDefaultValueHolder;";
133119

134120
public override string ReturnsDefinition => "RETURNS_BYREF";
135121

@@ -140,8 +126,6 @@ internal class ByReadOnlyRefDeclarationsProvider : ByRefDeclarationsProvider
140126
{
141127
public ByReadOnlyRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
142128

143-
public override string ReturnsDefinition => "RETURNS_BYREF_READONLY";
144-
145129
public override string WorkloadMethodReturnTypeModifiers => "ref readonly";
146130
}
147131

src/BenchmarkDotNet/Engines/Consumer.cs

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@
44
using System.Reflection;
55
using System.Runtime.CompilerServices;
66
using System.Threading;
7+
using BenchmarkDotNet.Extensions;
78
using JetBrains.Annotations;
89

910
// ReSharper disable NotAccessedField.Local
1011
namespace BenchmarkDotNet.Engines
1112
{
1213
public class Consumer
1314
{
14-
private static readonly HashSet<Type> SupportedTypes
15-
= new HashSet<Type>(
16-
typeof(Consumer).GetTypeInfo()
17-
.DeclaredFields
18-
.Where(field => !field.IsStatic) // exclude this HashSet itself
19-
.Select(field => field.FieldType));
20-
2115
#pragma warning disable IDE0052 // Remove unread private members
2216
private volatile byte byteHolder;
2317
private volatile sbyte sbyteHolder;
@@ -123,39 +117,13 @@ public void Consume<T>(T objectValue) where T : class // class constraint preven
123117

124118
[MethodImpl(MethodImplOptions.AggressiveInlining)]
125119
public void Consume<T>(in T value)
126-
{
127-
if (typeof(T) == typeof(byte))
128-
byteHolder = (byte)(object)value;
129-
else if (typeof(T) == typeof(sbyte))
130-
sbyteHolder = (sbyte)(object)value;
131-
else if (typeof(T) == typeof(short))
132-
shortHolder = (short)(object)value;
133-
else if (typeof(T) == typeof(ushort))
134-
ushortHolder = (ushort)(object)value;
135-
else if (typeof(T) == typeof(int))
136-
intHolder = (int)(object)value;
137-
else if (typeof(T) == typeof(uint))
138-
uintHolder = (uint)(object)value;
139-
else if (typeof(T) == typeof(bool))
140-
boolHolder = (bool)(object)value;
141-
else if (typeof(T) == typeof(char))
142-
charHolder = (char)(object)value;
143-
else if (typeof(T) == typeof(float))
144-
floatHolder = (float)(object)value;
145-
else if (typeof(T) == typeof(double))
146-
Volatile.Write(ref doubleHolder, (double)(object)value);
147-
else if (typeof(T) == typeof(long))
148-
Volatile.Write(ref longHolder, (long)(object)value);
149-
else if (typeof(T) == typeof(ulong))
150-
Volatile.Write(ref ulongHolder, (ulong)(object)value);
151-
else if (default(T) == null && !typeof(T).IsValueType)
152-
Consume((object) value);
153-
else
154-
DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(value); // non-primitive and nullable value types
155-
}
120+
// Read the value as a byte and write it to a volatile field.
121+
// This prevents copying large structs, and prevents dead code elimination and out-of-order execution.
122+
// (reading as a type larger than byte could possibly read past the memory bounds, causing the application to crash)
123+
// This also works for empty structs, because the runtime enforces a minimum size of 1 byte.
124+
=> byteHolder = Unsafe.As<T, byte>(ref Unsafe.AsRef(in value));
156125

157-
internal static bool IsConsumable(Type type)
158-
=> SupportedTypes.Contains(type) || type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface;
126+
internal static bool IsConsumable(Type type) => !type.IsByRefLike();
159127

160128
internal static bool HasConsumableField(Type type, out FieldInfo consumableField)
161129
{

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,7 @@ internal static bool IsStackOnlyWithImplicitCast(this Type argumentType, object?
185185
if (argumentInstance == null)
186186
return false;
187187

188-
// IsByRefLikeAttribute is not exposed for older runtimes, so we need to check it in an ugly way ;)
189-
bool isByRefLike = argumentType.GetCustomAttributes().Any(attribute => attribute.ToString()?.Contains("IsByRefLike") ?? false);
190-
if (!isByRefLike)
188+
if (!argumentType.IsByRefLike())
191189
return false;
192190

193191
var instanceType = argumentInstance.GetType();
@@ -209,5 +207,9 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo)
209207
&& typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it
210208

211209
internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0;
210+
211+
internal static bool IsByRefLike(this Type type)
212+
// Type.IsByRefLike is not available in netstandard2.0.
213+
=> type.IsValueType && type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute");
212214
}
213215
}

src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorEmitOpExtensions.cs

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -115,129 +115,5 @@ public static void EmitLdarg(this ILGenerator ilBuilder, ParameterInfo argument)
115115
break;
116116
}
117117
}
118-
119-
public static void EmitLdindStind(this ILGenerator ilBuilder, Type resultType)
120-
{
121-
if (!resultType.IsByRef)
122-
throw new NotSupportedException($"Cannot emit indirect op for non-reference {resultType}.");
123-
124-
// The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single
125-
var valueType = resultType.GetElementType();
126-
if (valueType?.IsEnum ?? false)
127-
valueType = valueType.GetEnumUnderlyingType();
128-
129-
switch (valueType)
130-
{
131-
case Type t when t == typeof(bool):
132-
/*
133-
IL_0018: ldind.u1
134-
IL_0019: stind.i1
135-
*/
136-
ilBuilder.Emit(OpCodes.Ldind_U1);
137-
ilBuilder.Emit(OpCodes.Stind_I1);
138-
break;
139-
case Type t when t == typeof(byte):
140-
/*
141-
IL_0018: ldind.u1
142-
IL_0019: stind.i1
143-
*/
144-
ilBuilder.Emit(OpCodes.Ldind_U1);
145-
ilBuilder.Emit(OpCodes.Stind_I1);
146-
break;
147-
case Type t when t == typeof(sbyte):
148-
/*
149-
IL_0018: ldind.i1
150-
IL_0019: stind.i1
151-
*/
152-
ilBuilder.Emit(OpCodes.Ldind_I1);
153-
ilBuilder.Emit(OpCodes.Stind_I1);
154-
break;
155-
case Type t when t == typeof(short):
156-
/*
157-
IL_0018: ldind.i2
158-
IL_0019: stind.i2
159-
*/
160-
ilBuilder.Emit(OpCodes.Ldind_I2);
161-
ilBuilder.Emit(OpCodes.Stind_I2);
162-
break;
163-
case Type t1 when t1 == typeof(ushort):
164-
case Type t2 when t2 == typeof(char):
165-
/*
166-
IL_0018: ldind.u2
167-
IL_0019: stind.i2
168-
*/
169-
ilBuilder.Emit(OpCodes.Ldind_U2);
170-
ilBuilder.Emit(OpCodes.Stind_I2);
171-
break;
172-
case Type t when t == typeof(int):
173-
/*
174-
IL_0018: ldind.i4
175-
IL_0019: stind.i4
176-
*/
177-
ilBuilder.Emit(OpCodes.Ldind_I4);
178-
ilBuilder.Emit(OpCodes.Stind_I4);
179-
break;
180-
case Type t when t == typeof(uint):
181-
/*
182-
IL_0018: ldind.i4
183-
IL_0019: stind.i4
184-
*/
185-
ilBuilder.Emit(OpCodes.Ldind_U4);
186-
ilBuilder.Emit(OpCodes.Stind_I4);
187-
break;
188-
case Type t1 when t1 == typeof(ulong):
189-
case Type t2 when t2 == typeof(long):
190-
/*
191-
IL_0018: ldind.i8
192-
IL_0019: stind.i8
193-
*/
194-
ilBuilder.Emit(OpCodes.Ldind_I8);
195-
ilBuilder.Emit(OpCodes.Stind_I8);
196-
break;
197-
case Type t1 when t1 == typeof(IntPtr):
198-
case Type t2 when t2 == typeof(UIntPtr):
199-
/*
200-
IL_0018: ldind.i
201-
IL_0019: stind.i
202-
*/
203-
ilBuilder.Emit(OpCodes.Ldind_I);
204-
ilBuilder.Emit(OpCodes.Stind_I);
205-
break;
206-
case Type t when t == typeof(double):
207-
/*
208-
IL_0018: ldind.r8
209-
IL_0019: stind.i8
210-
*/
211-
ilBuilder.Emit(OpCodes.Ldind_R8);
212-
ilBuilder.Emit(OpCodes.Stind_R8);
213-
break;
214-
case Type t when t == typeof(float):
215-
/*
216-
IL_0018: ldind.r4
217-
IL_0019: stind.i4
218-
*/
219-
ilBuilder.Emit(OpCodes.Ldind_R4);
220-
ilBuilder.Emit(OpCodes.Stind_R4);
221-
break;
222-
case Type t when t.IsClass || t.IsInterface:
223-
/*
224-
IL_0018: ldind.ref
225-
IL_0019: stind.ref
226-
*/
227-
ilBuilder.Emit(OpCodes.Ldind_Ref);
228-
ilBuilder.Emit(OpCodes.Stind_Ref);
229-
break;
230-
case Type t when t.IsEnum || t.IsValueType:
231-
/*
232-
IL_0018: ldobj valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.TimeSpan>
233-
IL_0019: stobj valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.TimeSpan>
234-
*/
235-
ilBuilder.Emit(OpCodes.Ldobj, valueType);
236-
ilBuilder.Emit(OpCodes.Stobj, valueType);
237-
break;
238-
default:
239-
throw new NotSupportedException($"Cannot emit indirect store for {resultType}.");
240-
}
241-
}
242118
}
243119
}

0 commit comments

Comments
 (0)