Skip to content

Commit

Permalink
Match overhead return type to workload return type.
Browse files Browse the repository at this point in the history
Match overhead action implementation to workload action implementation.
Support more consume types.
Cleaned up byref returns.
  • Loading branch information
timcassell committed May 17, 2023
1 parent f32a2e7 commit b44d380
Show file tree
Hide file tree
Showing 17 changed files with 252 additions and 543 deletions.
2 changes: 1 addition & 1 deletion build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(IsVisualBasic)' != 'true' AND '$(IsFsharp)' != 'true' ">
<LangVersion>9.0</LangVersion>
<LangVersion>11</LangVersion>

<Major>0</Major>
<Minor>13</Minor>
Expand Down
1 change: 0 additions & 1 deletion src/BenchmarkDotNet/Code/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ internal static string Generate(BuildPartition buildPartition)
.Replace("$WorkloadMethodDelegate$", provider.WorkloadMethodDelegate(passArguments))
.Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName)
.Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers)
.Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName)
.Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName)
.Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName)
.Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName)
Expand Down
37 changes: 11 additions & 26 deletions src/BenchmarkDotNet/Code/DeclarationsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ internal abstract class DeclarationsProvider

public virtual string ConsumeField => null;

protected abstract Type OverheadMethodReturnType { get; }

public string OverheadMethodReturnTypeName => OverheadMethodReturnType.GetCorrectCSharpTypeName();

public abstract string OverheadImplementation { get; }

Expand Down Expand Up @@ -76,8 +73,6 @@ internal class VoidDeclarationsProvider : DeclarationsProvider

public override string ReturnsDefinition => "RETURNS_VOID";

protected override Type OverheadMethodReturnType => typeof(void);

public override string OverheadImplementation => string.Empty;
}

Expand All @@ -90,26 +85,20 @@ public override string ConsumeField
? $".{field.Name}"
: null;

protected override Type OverheadMethodReturnType
=> Consumer.IsConsumable(WorkloadMethodReturnType)
? WorkloadMethodReturnType
: (Consumer.HasConsumableField(WorkloadMethodReturnType, out var field)
? field.FieldType
: typeof(int)); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself

public override string OverheadImplementation
{
get
{
string value;
var type = OverheadMethodReturnType;
if (type.GetTypeInfo().IsPrimitive)
value = $"default({type.GetCorrectCSharpTypeName()})";
else if (type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface)
value = "null";
else
value = SourceCodeHelper.ToSourceCode(Activator.CreateInstance(type)) + ";";
return $"return {value};";
var type = WorkloadMethodReturnType;
bool isByRefLike = type.IsByRefLike();
if (isByRefLike || (Consumer.IsConsumable(type) && !isByRefLike))
{
return $"return default({type.GetCorrectCSharpTypeName()});";
}
return $"""
System.Runtime.CompilerServices.Unsafe.SkipInit(out {type.GetCorrectCSharpTypeName()} value);
return value;
""";
}
}

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

protected override Type OverheadMethodReturnType => typeof(IntPtr);

public override string WorkloadMethodReturnTypeName => base.WorkloadMethodReturnTypeName.Replace("&", string.Empty);

public override string ConsumeField => null;

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

public override string ReturnsDefinition => "RETURNS_BYREF";

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

public override string ReturnsDefinition => "RETURNS_BYREF_READONLY";

public override string WorkloadMethodReturnTypeModifiers => "ref readonly";
}

Expand Down
46 changes: 7 additions & 39 deletions src/BenchmarkDotNet/Engines/Consumer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using BenchmarkDotNet.Extensions;
using JetBrains.Annotations;

// ReSharper disable NotAccessedField.Local
namespace BenchmarkDotNet.Engines
{
public class Consumer
{
private static readonly HashSet<Type> SupportedTypes
= new HashSet<Type>(
typeof(Consumer).GetTypeInfo()
.DeclaredFields
.Where(field => !field.IsStatic) // exclude this HashSet itself
.Select(field => field.FieldType));

#pragma warning disable IDE0052 // Remove unread private members
private volatile byte byteHolder;
private volatile sbyte sbyteHolder;
Expand Down Expand Up @@ -123,39 +117,13 @@ public void Consume(object objectValue)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Consume<T>(in T value)
{
if (typeof(T) == typeof(byte))
byteHolder = (byte)(object)value;
else if (typeof(T) == typeof(sbyte))
sbyteHolder = (sbyte)(object)value;
else if (typeof(T) == typeof(short))
shortHolder = (short)(object)value;
else if (typeof(T) == typeof(ushort))
ushortHolder = (ushort)(object)value;
else if (typeof(T) == typeof(int))
intHolder = (int)(object)value;
else if (typeof(T) == typeof(uint))
uintHolder = (uint)(object)value;
else if (typeof(T) == typeof(bool))
boolHolder = (bool)(object)value;
else if (typeof(T) == typeof(char))
charHolder = (char)(object)value;
else if (typeof(T) == typeof(float))
floatHolder = (float)(object)value;
else if (typeof(T) == typeof(double))
Volatile.Write(ref doubleHolder, (double)(object)value);
else if (typeof(T) == typeof(long))
Volatile.Write(ref longHolder, (long)(object)value);
else if (typeof(T) == typeof(ulong))
Volatile.Write(ref ulongHolder, (ulong)(object)value);
else if (default(T) == null && !typeof(T).IsValueType)
Consume((object) value);
else
DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(value); // non-primitive and nullable value types
}
// Read the value as a byte and write it to a volatile field.
// This prevents copying large structs, and prevents dead code elimination and out-of-order execution.
// (reading as a type larger than byte could possibly read past the memory bounds, causing the application to crash)
// This also works for empty structs, because the runtime enforces a minimum size of 1 byte.
=> byteHolder = Unsafe.As<T, byte>(ref Unsafe.AsRef(in value));

internal static bool IsConsumable(Type type)
=> SupportedTypes.Contains(type) || type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface;
internal static bool IsConsumable(Type type) => !type.IsByRefLike();

internal static bool HasConsumableField(Type type, out FieldInfo consumableField)
{
Expand Down
4 changes: 4 additions & 0 deletions src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,9 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo)
&& typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it

internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0;

internal static bool IsByRefLike(this Type type)
// Type.IsByRefLike is not available in netstandard2.0.
=> type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,129 +115,5 @@ public static void EmitLdarg(this ILGenerator ilBuilder, ParameterInfo argument)
break;
}
}

public static void EmitLdindStind(this ILGenerator ilBuilder, Type resultType)
{
if (!resultType.IsByRef)
throw new NotSupportedException($"Cannot emit indirect op for non-reference {resultType}.");

// The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single
var valueType = resultType.GetElementType();
if (valueType?.IsEnum ?? false)
valueType = valueType.GetEnumUnderlyingType();

switch (valueType)
{
case Type t when t == typeof(bool):
/*
IL_0018: ldind.u1
IL_0019: stind.i1
*/
ilBuilder.Emit(OpCodes.Ldind_U1);
ilBuilder.Emit(OpCodes.Stind_I1);
break;
case Type t when t == typeof(byte):
/*
IL_0018: ldind.u1
IL_0019: stind.i1
*/
ilBuilder.Emit(OpCodes.Ldind_U1);
ilBuilder.Emit(OpCodes.Stind_I1);
break;
case Type t when t == typeof(sbyte):
/*
IL_0018: ldind.i1
IL_0019: stind.i1
*/
ilBuilder.Emit(OpCodes.Ldind_I1);
ilBuilder.Emit(OpCodes.Stind_I1);
break;
case Type t when t == typeof(short):
/*
IL_0018: ldind.i2
IL_0019: stind.i2
*/
ilBuilder.Emit(OpCodes.Ldind_I2);
ilBuilder.Emit(OpCodes.Stind_I2);
break;
case Type t1 when t1 == typeof(ushort):
case Type t2 when t2 == typeof(char):
/*
IL_0018: ldind.u2
IL_0019: stind.i2
*/
ilBuilder.Emit(OpCodes.Ldind_U2);
ilBuilder.Emit(OpCodes.Stind_I2);
break;
case Type t when t == typeof(int):
/*
IL_0018: ldind.i4
IL_0019: stind.i4
*/
ilBuilder.Emit(OpCodes.Ldind_I4);
ilBuilder.Emit(OpCodes.Stind_I4);
break;
case Type t when t == typeof(uint):
/*
IL_0018: ldind.i4
IL_0019: stind.i4
*/
ilBuilder.Emit(OpCodes.Ldind_U4);
ilBuilder.Emit(OpCodes.Stind_I4);
break;
case Type t1 when t1 == typeof(ulong):
case Type t2 when t2 == typeof(long):
/*
IL_0018: ldind.i8
IL_0019: stind.i8
*/
ilBuilder.Emit(OpCodes.Ldind_I8);
ilBuilder.Emit(OpCodes.Stind_I8);
break;
case Type t1 when t1 == typeof(IntPtr):
case Type t2 when t2 == typeof(UIntPtr):
/*
IL_0018: ldind.i
IL_0019: stind.i
*/
ilBuilder.Emit(OpCodes.Ldind_I);
ilBuilder.Emit(OpCodes.Stind_I);
break;
case Type t when t == typeof(double):
/*
IL_0018: ldind.r8
IL_0019: stind.i8
*/
ilBuilder.Emit(OpCodes.Ldind_R8);
ilBuilder.Emit(OpCodes.Stind_R8);
break;
case Type t when t == typeof(float):
/*
IL_0018: ldind.r4
IL_0019: stind.i4
*/
ilBuilder.Emit(OpCodes.Ldind_R4);
ilBuilder.Emit(OpCodes.Stind_R4);
break;
case Type t when t.IsClass || t.IsInterface:
/*
IL_0018: ldind.ref
IL_0019: stind.ref
*/
ilBuilder.Emit(OpCodes.Ldind_Ref);
ilBuilder.Emit(OpCodes.Stind_Ref);
break;
case Type t when t.IsEnum || t.IsValueType:
/*
IL_0018: ldobj valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.TimeSpan>
IL_0019: stobj valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.TimeSpan>
*/
ilBuilder.Emit(OpCodes.Ldobj, valueType);
ilBuilder.Emit(OpCodes.Stobj, valueType);
break;
default:
throw new NotSupportedException($"Cannot emit indirect store for {resultType}.");
}
}
}
}
Loading

0 comments on commit b44d380

Please sign in to comment.