Skip to content

Commit b44d380

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 b44d380

File tree

17 files changed

+252
-543
lines changed

17 files changed

+252
-543
lines changed

build/common.props

+1-1
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

-1
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

+11-26
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,20 @@ 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+
bool isByRefLike = type.IsByRefLike();
94+
if (isByRefLike || (Consumer.IsConsumable(type) && !isByRefLike))
95+
{
96+
return $"return default({type.GetCorrectCSharpTypeName()});";
97+
}
98+
return $"""
99+
System.Runtime.CompilerServices.Unsafe.SkipInit(out {type.GetCorrectCSharpTypeName()} value);
100+
return value;
101+
""";
113102
}
114103
}
115104

@@ -123,13 +112,11 @@ internal class ByRefDeclarationsProvider : NonVoidDeclarationsProvider
123112
{
124113
public ByRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
125114

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

130117
public override string ConsumeField => null;
131118

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

134121
public override string ReturnsDefinition => "RETURNS_BYREF";
135122

@@ -140,8 +127,6 @@ internal class ByReadOnlyRefDeclarationsProvider : ByRefDeclarationsProvider
140127
{
141128
public ByReadOnlyRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
142129

143-
public override string ReturnsDefinition => "RETURNS_BYREF_READONLY";
144-
145130
public override string WorkloadMethodReturnTypeModifiers => "ref readonly";
146131
}
147132

src/BenchmarkDotNet/Engines/Consumer.cs

+7-39
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

+4
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,9 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo)
209209
&& typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it
210210

211211
internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0;
212+
213+
internal static bool IsByRefLike(this Type type)
214+
// Type.IsByRefLike is not available in netstandard2.0.
215+
=> type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute");
212216
}
213217
}

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

-124
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)