diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs index 1749c9d05c..7db937a3a3 100644 --- a/src/BenchmarkDotNet/Code/CodeGenerator.cs +++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs @@ -47,13 +47,10 @@ internal static string Generate(BuildPartition buildPartition) .Replace("$WorkloadTypeName$", provider.WorkloadTypeName) .Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName) .Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers) - .Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName) .Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName) .Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName) .Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName) .Replace("$IterationCleanupMethodName$", provider.IterationCleanupMethodName) - .Replace("$OverheadImplementation$", provider.OverheadImplementation) - .Replace("$ConsumeField$", provider.ConsumeField) .Replace("$JobSetDefinition$", GetJobsSetDefinition(benchmark)) .Replace("$ParamsContent$", GetParamsContent(benchmark)) .Replace("$ArgumentsDefinition$", GetArgumentsDefinition(benchmark)) diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index d885812c03..db31702e03 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -40,14 +40,6 @@ internal abstract class DeclarationsProvider public virtual string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments})"; - public virtual string ConsumeField => null; - - protected abstract Type OverheadMethodReturnType { get; } - - public string OverheadMethodReturnTypeName => OverheadMethodReturnType.GetCorrectCSharpTypeName(); - - public abstract string OverheadImplementation { get; } - private string GetMethodName(MethodInfo method) { if (method == null) @@ -73,62 +65,21 @@ internal class VoidDeclarationsProvider : DeclarationsProvider public VoidDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } public override string ReturnsDefinition => "RETURNS_VOID"; - - protected override Type OverheadMethodReturnType => typeof(void); - - public override string OverheadImplementation => string.Empty; } internal class NonVoidDeclarationsProvider : DeclarationsProvider { public NonVoidDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - public override string ConsumeField - => !Consumer.IsConsumable(WorkloadMethodReturnType) && Consumer.HasConsumableField(WorkloadMethodReturnType, out var field) - ? $".{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};"; - } - } - - public override string ReturnsDefinition - => Consumer.IsConsumable(WorkloadMethodReturnType) || Consumer.HasConsumableField(WorkloadMethodReturnType, out _) - ? "RETURNS_CONSUMABLE" - : "RETURNS_NON_CONSUMABLE_STRUCT"; + public override string ReturnsDefinition => "RETURNS_NON_VOID"; } 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 ReturnsDefinition => "RETURNS_BYREF"; public override string WorkloadMethodReturnTypeModifiers => "ref"; @@ -138,8 +89,6 @@ internal class ByReadOnlyRefDeclarationsProvider : ByRefDeclarationsProvider { public ByReadOnlyRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - public override string ReturnsDefinition => "RETURNS_BYREF_READONLY"; - public override string WorkloadMethodReturnTypeModifiers => "ref readonly"; } diff --git a/src/BenchmarkDotNet/Engines/Consumer.cs b/src/BenchmarkDotNet/Engines/Consumer.cs index 1abedec0d6..b17c40cd2a 100644 --- a/src/BenchmarkDotNet/Engines/Consumer.cs +++ b/src/BenchmarkDotNet/Engines/Consumer.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using JetBrains.Annotations; @@ -11,13 +8,6 @@ namespace BenchmarkDotNet.Engines { public class Consumer { - private static readonly HashSet SupportedTypes - = new HashSet( - 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; @@ -153,28 +143,5 @@ public void Consume(in T value) else DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(value); // non-primitive and nullable value types } - - internal static bool IsConsumable(Type type) - => SupportedTypes.Contains(type) || type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface; - - internal static bool HasConsumableField(Type type, out FieldInfo consumableField) - { - var typeInfo = type.GetTypeInfo(); - - if (typeInfo.IsEnum) - { - // Enums are tricky bastards which report "value__" field, which is public for reflection, but inaccessible via C# - consumableField = null; - return false; - } - - var publicInstanceFields = typeInfo.DeclaredFields - .Where(field => field.IsPublic && !field.IsStatic) - .ToArray(); - - consumableField = publicInstanceFields.FirstOrDefault(field => IsConsumable(field.FieldType)); - - return consumableField != null; - } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs index 4b23db0141..d9901120d3 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs @@ -12,33 +12,20 @@ public static LocalBuilder DeclareOptionalLocalForReturnDefault(this ILGenerator : null; } - public static void EmitSetLocalToDefault(this ILGenerator ilBuilder, LocalBuilder local) - { - var resultType = local.LocalType; - switch (resultType) - { - case Type t when t == typeof(void): - break; - case Type t when t.IsClass || t.IsInterface: - ilBuilder.Emit(OpCodes.Ldnull); - ilBuilder.EmitStloc(local); - break; - case Type t when t.UseInitObjForInitLocal(): - EmitInitObj(ilBuilder, resultType, local); - break; - default: - EmitLoadDefaultPrimitive(ilBuilder, resultType); - ilBuilder.EmitStloc(local); - break; - } - } - public static void EmitReturnDefault(this ILGenerator ilBuilder, Type resultType, LocalBuilder optionalLocalForInitobj) { switch (resultType) { case Type t when t == typeof(void): break; + case Type t when t.IsPointer: // Type.IsClass returns true for pointers, so we have to check for pointer type first. + /* + IL_0000: ldc.i4.0 + IL_0001: conv.u + */ + ilBuilder.Emit(OpCodes.Ldc_I4_0); + ilBuilder.Emit(OpCodes.Conv_U); + break; case Type t when t.IsClass || t.IsInterface: ilBuilder.Emit(OpCodes.Ldnull); break; diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index d3b406bb33..4c196e78a1 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -96,28 +96,17 @@ @DummyUnroll@ } - private $OverheadMethodReturnTypeName$ __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict + private void __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict { - $OverheadImplementation$ } -#if RETURNS_CONSUMABLE_$ID$ - // Prevent inlining the method invoke. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private $OverheadMethodReturnTypeName$ __OverheadWrapper($ArgumentsDefinition$) - { - return __Overhead($PassArguments$); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) + private void __OverheadWrapper($ArgumentsDefinition$) { - return $WorkloadMethodCall$; + __Overhead($PassArguments$); } - private BenchmarkDotNet.Engines.Consumer consumer = new BenchmarkDotNet.Engines.Consumer(); - #if NETCOREAPP3_0_OR_GREATER [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] #endif @@ -126,7 +115,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(__OverheadWrapper($PassArguments$));@Unroll@ + __OverheadWrapper($PassArguments$);@Unroll@ } } @@ -138,7 +127,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(__OverheadWrapper($PassArguments$)); + __OverheadWrapper($PassArguments$); } } @@ -148,9 +137,9 @@ private void WorkloadActionUnroll(System.Int64 invokeCount) { $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) + for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(__WorkloadWrapper($PassArguments$)$ConsumeField$);@Unroll@ + __WorkloadWrapper($PassArguments$);@Unroll@ } } @@ -162,98 +151,19 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(__WorkloadWrapper($PassArguments$)$ConsumeField$); - } - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ - return $WorkloadMethodCall$; + __WorkloadWrapper($PassArguments$); } - - return default($WorkloadMethodReturnType$); } -#elif RETURNS_NON_CONSUMABLE_STRUCT_$ID$ +#if RETURNS_NON_VOID_$ID$ // Prevent inlining the method invoke. - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private $OverheadMethodReturnTypeName$ __OverheadWrapper($ArgumentsDefinition$) - { - return __Overhead($PassArguments$); - } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] private $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) { return $WorkloadMethodCall$; } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = __OverheadWrapper($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = __OverheadWrapper($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = __WorkloadWrapper($PassArguments$);@Unroll@ - } - NonGenericKeepAliveWithoutBoxing(result); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = __WorkloadWrapper($PassArguments$); - } - NonGenericKeepAliveWithoutBoxing(result); - } - - // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method - // and stack-only types like Span can not be generic type arguments http://adamsitnik.com/Span/#span-must-not-be-a-generic-type-argument - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private void NonGenericKeepAliveWithoutBoxing($WorkloadMethodReturnType$ _) { } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() { @@ -270,77 +180,15 @@ // Prevent inlining the method invoke. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private $OverheadMethodReturnTypeName$ __OverheadWrapper($ArgumentsDefinition$) - { - return __Overhead($PassArguments$); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private ref $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) + private $WorkloadMethodReturnTypeModifiers$ $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) { return ref $WorkloadMethodCall$; } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - value = __OverheadWrapper($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - value = __OverheadWrapper($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - - private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = __WorkloadWrapper($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = __WorkloadWrapper($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); - } - + private $WorkloadMethodReturnType$ __disassemblerDefaultValueHolder = default($WorkloadMethodReturnType$); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public ref $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() + public $WorkloadMethodReturnTypeModifiers$ $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() { if (NotEleven == 11) { @@ -348,155 +196,18 @@ return ref $WorkloadMethodCall$; } - return ref workloadDefaultValueHolder; - } -#elif RETURNS_BYREF_READONLY_$ID$ - - // Prevent inlining the method invoke. - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private $OverheadMethodReturnTypeName$ __OverheadWrapper($ArgumentsDefinition$) - { - return __Overhead($PassArguments$); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private ref readonly $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) - { - return ref $WorkloadMethodCall$; + return ref __disassemblerDefaultValueHolder; } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - value = __OverheadWrapper($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - value = __OverheadWrapper($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - - private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = __WorkloadWrapper($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = __WorkloadWrapper($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public ref readonly $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ - return ref $WorkloadMethodCall$; - } - - return ref workloadDefaultValueHolder; - } #elif RETURNS_VOID_$ID$ // Prevent inlining the method invoke. - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] - private void __OverheadWrapper($ArgumentsDefinition$) - { - __Overhead($PassArguments$); - } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] private void __WorkloadWrapper($ArgumentsDefinition$) { $WorkloadMethodCall$; } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - __OverheadWrapper($PassArguments$);@Unroll@ - } - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - __OverheadWrapper($PassArguments$); - } - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - __WorkloadWrapper($PassArguments$);@Unroll@ - } - } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - __WorkloadWrapper($PassArguments$); - } - } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] public void $DisassemblerEntryMethodName$() { diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs index 0fde3ac1ee..e89960c59d 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs @@ -44,44 +44,24 @@ public ConsumableTypeInfo(Type methodReturnType) if (WorkloadMethodReturnType == null) throw new InvalidOperationException("Bug: (WorkloadMethodReturnType == null"); - var consumableField = default(FieldInfo); if (WorkloadMethodReturnType == typeof(void)) { IsVoid = true; - OverheadMethodReturnType = WorkloadMethodReturnType; } else if (WorkloadMethodReturnType.IsByRef) { IsByRef = true; - OverheadMethodReturnType = typeof(IntPtr); } - else if (Consumer.IsConsumable(WorkloadMethodReturnType) - || Consumer.HasConsumableField(WorkloadMethodReturnType, out consumableField)) - { - IsConsumable = true; - WorkloadConsumableField = consumableField; - OverheadMethodReturnType = consumableField?.FieldType ?? WorkloadMethodReturnType; - } - else - { - OverheadMethodReturnType = typeof(int); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself - } - - if (OverheadMethodReturnType == null) - throw new InvalidOperationException("Bug: (OverheadResultType == null"); } public Type OriginMethodReturnType { get; } public Type WorkloadMethodReturnType { get; } - public Type OverheadMethodReturnType { get; } public MethodInfo? GetAwaiterMethod { get; } public MethodInfo? GetResultMethod { get; } public bool IsVoid { get; } public bool IsByRef { get; } - public bool IsConsumable { get; } - public FieldInfo? WorkloadConsumableField { get; } public bool IsAwaitable => GetAwaiterMethod != null && GetResultMethod != null; } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs index 96ed6d9578..345686ac20 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs @@ -1,152 +1,43 @@ using System; -using System.Linq; using System.Reflection; using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation { internal class ByRefConsumeEmitter : ConsumeEmitter { - private FieldBuilder workloadDefaultValueHolderField; - private MethodInfo overheadKeepAliveWithoutBoxingMethod; - private MethodInfo workloadKeepAliveWithoutBoxingMethod; - private LocalBuilder resultLocal; + private FieldBuilder disassemblerDefaultValueHolderField; public ByRefConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) { } protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) { - var nonRefType = ConsumableInfo.WorkloadMethodReturnType.GetElementType(); - if (nonRefType == null) - throw new InvalidOperationException($"Bug: type {ConsumableInfo.WorkloadMethodReturnType} is non-ref type."); + var nonRefType = ConsumableInfo.WorkloadMethodReturnType.GetElementType() + ?? throw new InvalidOperationException($"Bug: type {ConsumableInfo.WorkloadMethodReturnType} is non-ref type."); - workloadDefaultValueHolderField = runnableBuilder.DefineField( - WorkloadDefaultValueHolderFieldName, + disassemblerDefaultValueHolderField = runnableBuilder.DefineField( + DisassemblerDefaultValueHolderName, nonRefType, FieldAttributes.Private); } protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) { /* - // return ref workloadDefaultValueHolder; + // return ref __disassemblerDefaultValueHolder IL_0031: ldarg.0 IL_0032: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder IL_0037: ret */ ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField); + ilBuilder.Emit(OpCodes.Ldflda, disassemblerDefaultValueHolderField); ilBuilder.Emit(OpCodes.Ret); } - protected override void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && m.GetParameterTypes().First().IsByRef == false) - .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); - - workloadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && m.GetParameterTypes().First().IsByRef) - .MakeGenericMethod(ConsumableInfo.WorkloadMethodReturnType.GetElementType()); - } - - protected override void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - /* - .locals init ( - [4] native int, - ) - -or- - .locals init ( - [4] int32&, - ) - */ - if (ActionKind == RunnableActionKind.Overhead) - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); - else - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.WorkloadMethodReturnType); - } - - /// Emits the action before loop override. - /// The il builder. - /// EmitActionKind - null - protected override void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - /* - // IntPtr value = default(IntPtr); - IL_001c: ldloca.s 4 - IL_001e: initobj [mscorlib]System.IntPtr - -or- - // ref int reference = ref workloadDefaultValueHolder; - IL_001c: ldarg.0 - IL_001d: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder - IL_0022: stloc.s 4 - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitLdloca(resultLocal); - ilBuilder.Emit(OpCodes.Initobj, ConsumableInfo.OverheadMethodReturnType); - } - else - { - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField); - ilBuilder.EmitStloc(resultLocal); - } - } - - protected override void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - /* - - -or- - // reference = ... - IL_002a: ldloc.s 4 - */ - if (ActionKind != RunnableActionKind.Overhead) - { - ilBuilder.EmitLdloc(resultLocal); - } - } - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) { - /* - IL_0039: stloc.s 4 - -or- - // reference = ... - IL_003b: ldind.i4 - IL_003c: stind.i4 - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitStloc(resultLocal); - } - else - { - ilBuilder.EmitLdindStind(resultLocal.LocalType); - } - } - - protected override void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - /* - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - IL_007a: ldloc.s 4 - IL_007c: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0) - -or- - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref reference); - IL_0082: ldloc.s 4 - IL_0084: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0&) - */ - if (ActionKind == RunnableActionKind.Overhead) - ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, resultLocal); - else - ilBuilder.EmitStaticCall(workloadKeepAliveWithoutBoxingMethod, resultLocal); + // IL_000a: pop + ilBuilder.Emit(OpCodes.Pop); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs deleted file mode 100644 index 76a2a5f505..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal class ConsumableConsumeEmitter : ConsumeEmitter - { - private static MethodInfo GetConsumeMethod(Type consumableType) - { - var consumeMethod = typeof(Consumer).GetMethod(nameof(Consumer.Consume), new[] { consumableType }); - - // Use generic method for ref types - if (consumeMethod == null || consumeMethod.GetParameterTypes().FirstOrDefault() == typeof(object)) - { - if (consumableType.IsClass || consumableType.IsInterface) - { - consumeMethod = typeof(Consumer) - .GetMethods() - .Single(m => - { - Type argType = m.GetParameterTypes().FirstOrDefault(); - - return m.Name == nameof(Consumer.Consume) && m.IsGenericMethodDefinition - && !argType.IsByRef // we are not interested in "Consume(in T value)" - && argType.IsPointer == consumableType.IsPointer; // use "Consume(T objectValue) where T : class" or "Consume(T* ptrValue) where T: unmanaged" - }); - - consumeMethod = consumableType.IsPointer - ? consumeMethod.MakeGenericMethod(consumableType.GetElementType()) // consumableType is T*, we need T for Consume(T* ptrValue) - : consumeMethod.MakeGenericMethod(consumableType); - } - else - { - consumeMethod = null; - } - } - - if (consumeMethod == null) - { - throw new InvalidOperationException($"Cannot consume result of {consumableType}."); - } - - return consumeMethod; - } - - private FieldBuilder consumerField; - private LocalBuilder disassemblyDiagnoserLocal; - - public ConsumableConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) - { - } - - protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) - { - consumerField = runnableBuilder.DefineField(RunnableConstants.ConsumerFieldName, typeof(Consumer), FieldAttributes.Private); - } - - protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) - { - // optional local if default(T) uses .initobj - disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); - } - - protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); - } - - protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - var ctor = typeof(Consumer).GetConstructor(Array.Empty()); - if (ctor == null) - throw new InvalidOperationException($"Cannot get default .ctor for {typeof(Consumer)}"); - - /* - // consumer = new Consumer(); - IL_0000: ldarg.0 - IL_0001: newobj instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::.ctor() - IL_0006: stfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Newobj, ctor); - ilBuilder.Emit(OpCodes.Stfld, consumerField); - } - - protected override void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - /* - // consumer. ...; - IL_000c: ldarg.0 - IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, consumerField); - } - - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - /* - // ... .Consume( ... ) - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(string) - -or- - // ... .Consume( ... .ConsumableField); - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - // -or- .Consume( ... ); - IL_001e: ldfld int32 BenchmarkDotNet.Samples.CustomWithConsumable::ConsumableField - IL_0023: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - */ - if (ActionKind == RunnableActionKind.Overhead) - { - var overheadConsumeMethod = GetConsumeMethod(ConsumableInfo.OverheadMethodReturnType); - ilBuilder.Emit(OpCodes.Callvirt, overheadConsumeMethod); - } - else - { - var consumeField = ConsumableInfo.WorkloadConsumableField; - if (consumeField == null) - { - var consumeMethod = GetConsumeMethod(ConsumableInfo.WorkloadMethodReturnType); - ilBuilder.Emit(OpCodes.Callvirt, consumeMethod); - } - else - { - var consumeMethod = GetConsumeMethod(consumeField.FieldType); - ilBuilder.Emit(OpCodes.Ldfld, consumeField); - ilBuilder.Emit(OpCodes.Callvirt, consumeMethod); - } - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs index 62fe06c649..57d63a4122 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs @@ -16,9 +16,7 @@ public static ConsumeEmitter GetConsumeEmitter(ConsumableTypeInfo consumableType return new VoidConsumeEmitter(consumableTypeInfo); if (consumableTypeInfo.IsByRef) return new ByRefConsumeEmitter(consumableTypeInfo); - if (consumableTypeInfo.IsConsumable) - return new ConsumableConsumeEmitter(consumableTypeInfo); - return new NonConsumableConsumeEmitter(consumableTypeInfo); + return new NonVoidConsumeEmitter(consumableTypeInfo); } protected ConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) @@ -86,28 +84,6 @@ protected virtual void OnDefineFieldsOverride(TypeBuilder runnableBuilder) { } - public void OnEmitMembers(TypeBuilder runnableBuilder) - { - AssertNoBuilder(); - - OnEmitMembersOverride(runnableBuilder); - } - - protected virtual void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - } - - public void OnEmitCtorBody(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - AssertNoBuilder(); - - OnEmitCtorBodyOverride(constructorBuilder, ilBuilder); - } - - protected virtual void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - } - public void DeclareDisassemblyDiagnoserLocals(ILGenerator ilBuilder) { AssertNoBuilder(); @@ -145,74 +121,18 @@ protected virtual void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ActionMethodBuilder = actionMethodBuilder; ActionInvokeMethod = actionInvokeMethod; ActionKind = actionKind; - - BeginEmitActionOverride(IlBuilder); - } - - protected virtual void BeginEmitActionOverride(ILGenerator ilBuilder) - { } public void CompleteEmitAction(ILGenerator ilBuilder) { AssertHasBuilder(ilBuilder); - CompleteEmitActionOverride(ilBuilder); - IlBuilder = null; ActionMethodBuilder = null; ActionInvokeMethod = null; ActionKind = null; } - protected virtual void CompleteEmitActionOverride(ILGenerator ilBuilder) - { - } - - public void DeclareActionLocals(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - DeclareActionLocalsOverride(ilBuilder); - } - - protected virtual void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionBeforeLoop(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionBeforeLoopOverride(ilBuilder); - } - - protected virtual void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionAfterLoop(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionAfterLoopOverride(ilBuilder); - } - - protected virtual void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionBeforeCall(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionBeforeCallOverride(ilBuilder); - } - - protected virtual void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - } - public void EmitActionAfterCall(ILGenerator ilBuilder) { AssertHasBuilder(ilBuilder); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs deleted file mode 100644 index 4087165f3b..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; -using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal class NonConsumableConsumeEmitter : ConsumeEmitter - { - private MethodInfo overheadKeepAliveWithoutBoxingMethod; - private MethodInfo nonGenericKeepAliveWithoutBoxingMethod; - private LocalBuilder resultLocal; - private LocalBuilder disassemblyDiagnoserLocal; - - public NonConsumableConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) - { - } - - protected override void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && !m.GetParameterTypes().First().IsByRef) - .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); - - // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method - // and stack-only types like Span can not be generic type arguments http://adamsitnik.com/Span/#span-must-not-be-a-generic-type-argument - nonGenericKeepAliveWithoutBoxingMethod = EmitNonGenericKeepAliveWithoutBoxing( - NonGenericKeepAliveWithoutBoxingMethodName, - runnableBuilder); - } - - protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) - { - // optional local if default(T) uses .initobj - disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); - } - - protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); - } - - private MethodBuilder EmitNonGenericKeepAliveWithoutBoxing(string methodName, TypeBuilder runnableBuilder) - { - /* - method private hidebysig - instance void NonGenericKeepAliveWithoutBoxing( - valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable _ - ) cil managed noinlining - */ - var valueArg = new EmitParameterInfo( - 0, - DummyParamName, - ConsumableInfo.WorkloadMethodReturnType); - var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( - methodName, - MethodAttributes.Private, - EmitParameterInfo.CreateReturnVoidParameter(), - valueArg) - .SetNoInliningImplementationFlag(); - valueArg.SetMember(methodBuilder); - - var ilBuilder = methodBuilder.GetILGenerator(); - - /* - IL_0001: ret - */ - ilBuilder.EmitVoidReturn(methodBuilder); - - return methodBuilder; - } - - - protected override void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - /* - .locals init ( - [2] int32 - ) - -or- - .locals init ( - [2] valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable, - ) - */ - if (ActionKind == RunnableActionKind.Overhead) - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); - else - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.WorkloadMethodReturnType); - } - - /// Emits the action before loop override. - /// The il builder. - /// EmitActionKind - null - protected override void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - /* - // int value = 0; - IL_000e: ldc.i4.0 - IL_000f: stloc.2 - -or- - // CustomStructNonConsumable _ = default(CustomStructNonConsumable); - IL_000e: ldloca.s 2 - IL_0010: initobj BenchmarkDotNet.Samples.CustomStructNonConsumable - */ - ilBuilder.EmitSetLocalToDefault(resultLocal); - } - - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - // IL_0022: stloc.2 - ilBuilder.EmitStloc(resultLocal); - } - - protected override void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - /* - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - IL_002c: ldloc.2 - IL_002d: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0) - -or- - // NonGenericKeepAliveWithoutBoxing(_); - IL_0032: ldarg.0 - IL_0033: ldloc.2 - IL_0034: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::NonGenericKeepAliveWithoutBoxing(valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable) - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, resultLocal); - } - else - { - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.EmitInstanceCallThisValueOnStack( - null, - nonGenericKeepAliveWithoutBoxingMethod, - new[] { resultLocal }, - forceDirectCall: true); - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonVoidConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonVoidConsumeEmitter.cs new file mode 100644 index 0000000000..185c2b7a2f --- /dev/null +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonVoidConsumeEmitter.cs @@ -0,0 +1,31 @@ +using System.Reflection.Emit; +using BenchmarkDotNet.Helpers.Reflection.Emit; + +namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation +{ + internal class NonVoidConsumeEmitter : ConsumeEmitter + { + private LocalBuilder disassemblyDiagnoserLocal; + + public NonVoidConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) + { + } + + protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) + { + // optional local if default(T) uses .initobj + disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); + } + + protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) + { + ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); + } + + protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) + { + // IL_000a: pop + ilBuilder.Emit(OpCodes.Pop); + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs index 4844086a3c..063af2cbf5 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs @@ -307,9 +307,6 @@ private Type EmitRunnableCore(BenchmarkBuildInfo newBenchmark) dummy2Method = EmitDummyMethod(Dummy2MethodName, dummyUnrollFactor); dummy3Method = EmitDummyMethod(Dummy3MethodName, dummyUnrollFactor); - // 3. Emit impl - consumeEmitter.OnEmitMembers(runnableBuilder); - // Overhead impl overheadImplementationMethod = EmitOverheadImplementation(OverheadImplementationMethodName); overheadWrapperMethod = EmitOverheadWrapperImplementation(OverheadWrapperImplementationMethodName); @@ -485,7 +482,7 @@ private MethodBuilder EmitDummyMethod(string methodName, int unrollFactor) private MethodBuilder EmitOverheadImplementation(string methodName) { //.method private hidebysig - // instance int32 __Overhead(int64 arg0) cil managed + // instance void __Overhead(int64 arg0) cil managed // Replace arg names var parameters = Descriptor.WorkloadMethod.GetParameters() @@ -501,20 +498,15 @@ private MethodBuilder EmitOverheadImplementation(string methodName) var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, - EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType), + EmitParameterInfo.CreateReturnVoidParameter(), parameters); var ilBuilder = methodBuilder.GetILGenerator(); - var returnType = methodBuilder.ReturnType; - /* - // return default; - IL_0000: ldc.i4.0 + // return; IL_0001: ret */ - // optional local if default(T) uses .initobj - var optionalLocalForInitobj = ilBuilder.DeclareOptionalLocalForReturnDefault(returnType); - ilBuilder.EmitReturnDefault(returnType, optionalLocalForInitobj); + ilBuilder.EmitVoidReturn(methodBuilder); return methodBuilder; } @@ -522,7 +514,7 @@ private MethodBuilder EmitOverheadImplementation(string methodName) private MethodBuilder EmitOverheadWrapperImplementation(string methodName) { //.method private hidebysig - // instance int32 __OverheadWrapper(int64 arg0) cil managed noinlining nooptimization + // instance void __OverheadWrapper(int64 arg0) cil managed noinlining nooptimization // Replace arg names var parameters = Descriptor.WorkloadMethod.GetParameters() @@ -538,7 +530,7 @@ private MethodBuilder EmitOverheadWrapperImplementation(string methodName) var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, - EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType), + EmitParameterInfo.CreateReturnVoidParameter(), parameters) .SetNoInliningImplementationFlag() .SetNoOptimizationImplementationFlag(); @@ -547,7 +539,7 @@ private MethodBuilder EmitOverheadWrapperImplementation(string methodName) /* IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: call instance int32 [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::__Overhead(int64) + IL_0002: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::__Overhead(int64) IL_0007: ret */ if (!overheadImplementationMethod.IsStatic) @@ -676,12 +668,10 @@ private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actio // init locals var argLocals = EmitDeclareArgLocals(ilBuilder); - consumeEmitter.DeclareActionLocals(ilBuilder); var indexLocal = ilBuilder.DeclareLocal(typeof(long)); // load fields EmitLoadArgFieldsToLocals(ilBuilder, argLocals); - consumeEmitter.EmitActionBeforeLoop(ilBuilder); // loop var loopStartLabel = ilBuilder.DefineLabel(); @@ -689,33 +679,23 @@ private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actio ilBuilder.EmitLoopBeginFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); { /* - // overheadDelegate(); + // __OverheadWrapper(); IL_0005: ldarg.0 - IL_0006: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_000b: callvirt instance void BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke() - // -or- - // consumer.Consume(overheadDelegate(_argField)); - IL_000c: ldarg.0 - IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - IL_0012: ldarg.0 - IL_0013: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_0018: ldloc.0 - IL_0019: callvirt instance int32 BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke(int64) - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) + IL_000b: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__OverheadWrapper() */ for (int u = 0; u < unrollFactor; u++) { - consumeEmitter.EmitActionBeforeCall(ilBuilder); - ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitInstanceCallThisValueOnStack(null, invokeMethod, argLocals, forceDirectCall: true); - consumeEmitter.EmitActionAfterCall(ilBuilder); + if (actionKind == RunnableActionKind.Workload) + { + consumeEmitter.EmitActionAfterCall(ilBuilder); + } } } ilBuilder.EmitLoopEndFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); - consumeEmitter.EmitActionAfterLoop(ilBuilder); consumeEmitter.CompleteEmitAction(ilBuilder); // IL_003a: ret @@ -956,8 +936,6 @@ private void EmitCtorBody() ilBuilder.EmitCallBaseParameterlessCtor(ctorMethod); - consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder); - ilBuilder.EmitSetDelegateToThisField(globalSetupActionField, globalSetupMethod); ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod); ilBuilder.EmitSetDelegateToThisField(iterationSetupActionField, iterationSetupMethod); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs index 349b7aa8ca..b75796b37f 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs @@ -11,8 +11,6 @@ public class RunnableConstants public const string DynamicAssemblySuffix = "Emitted"; public const string EmittedTypePrefix = "BenchmarkDotNet.Autogenerated.Runnable_"; - public const string WorkloadDelegateTypeSuffix = "WorkloadDelegate"; - public const string OverheadDelegateTypeSuffix = "OverheadDelegate"; public const string ArgFieldPrefix = "__argField"; public const string ArgParamPrefix = "arg"; @@ -38,10 +36,8 @@ public class RunnableConstants public const string ForDisassemblyDiagnoserMethodName = "__ForDisassemblyDiagnoser__"; public const string InvokeCountParamName = "invokeCount"; - public const string ConsumerFieldName = "consumer"; - public const string NonGenericKeepAliveWithoutBoxingMethodName = "NonGenericKeepAliveWithoutBoxing"; public const string DummyParamName = "_"; - public const string WorkloadDefaultValueHolderFieldName = "workloadDefaultValueHolder"; + public const string DisassemblerDefaultValueHolderName = "__disassemblerDefaultValueHolder"; public const string GlobalSetupMethodName = "GlobalSetup"; public const string GlobalCleanupMethodName = "GlobalCleanup"; diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs index 31fc7a9283..c2431229bf 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs @@ -43,6 +43,27 @@ public CustomStructNonConsumable ReturnManyArgsCase(ref double i, int j, string return ref refValueHolder; } + [Benchmark] + public unsafe int* ReturnsIntPointer() + { + Thread.Sleep(100); + return default; + } + + [Benchmark] + public unsafe void* ReturnsVoidPointer() + { + Thread.Sleep(100); + return default; + } + + [Benchmark] + public unsafe CustomStructNonConsumable* ReturnsStructPointer() + { + Thread.Sleep(100); + return default; + } + [Benchmark, Arguments(12)] public Task TaskSample(long arg) {