diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs index 52ae0e5478..1749c9d05c 100644 --- a/src/BenchmarkDotNet/Code/CodeGenerator.cs +++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs @@ -45,7 +45,6 @@ internal static string Generate(BuildPartition buildPartition) .Replace("$ID$", buildInfo.Id.ToString()) .Replace("$OperationsPerInvoke$", provider.OperationsPerInvoke) .Replace("$WorkloadTypeName$", provider.WorkloadTypeName) - .Replace("$WorkloadMethodDelegate$", provider.WorkloadMethodDelegate(passArguments)) .Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName) .Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers) .Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName) diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index ddf78eb572..d885812c03 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -36,8 +36,6 @@ internal abstract class DeclarationsProvider public virtual string WorkloadMethodReturnTypeName => WorkloadMethodReturnType.GetCorrectCSharpTypeName(); - public virtual string WorkloadMethodDelegate(string passArguments) => Descriptor.WorkloadMethod.Name; - public virtual string WorkloadMethodReturnTypeModifiers => null; public virtual string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments})"; @@ -151,9 +149,6 @@ internal class TaskDeclarationsProvider : VoidDeclarationsProvider // we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way, // and will eventually throw actual exception, not aggregated one - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}"; - public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()"; protected override Type WorkloadMethodReturnType => typeof(void); @@ -170,9 +165,6 @@ internal class GenericTaskDeclarationsProvider : NonVoidDeclarationsProvider // we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way, // and will eventually throw actual exception, not aggregated one - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ return {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}"; - public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()"; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index d8f15f9138..d3b406bb33 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -1,5 +1,5 @@ // the type name must be in sync with WindowsDisassembler.BuildArguments - public unsafe class Runnable_$ID$ : global::$WorkloadTypeName$ + public unsafe sealed class Runnable_$ID$ : global::$WorkloadTypeName$ { public static void Run(BenchmarkDotNet.Engines.IHost host, System.String benchmarkName) { @@ -51,18 +51,12 @@ } } - public delegate $OverheadMethodReturnTypeName$ OverheadDelegate($ArgumentsDefinition$); - - public delegate $WorkloadMethodReturnTypeModifiers$ $WorkloadMethodReturnType$ WorkloadDelegate($ArgumentsDefinition$); - public Runnable_$ID$() { globalSetupAction = $GlobalSetupMethodName$; globalCleanupAction = $GlobalCleanupMethodName$; iterationSetupAction = $IterationSetupMethodName$; iterationCleanupAction = $IterationCleanupMethodName$; - overheadDelegate = __Overhead; - workloadDelegate = $WorkloadMethodDelegate$; $InitializeArgumentFields$ } @@ -70,8 +64,6 @@ private System.Action globalCleanupAction; private System.Action iterationSetupAction; private System.Action iterationCleanupAction; - private BenchmarkDotNet.Autogenerated.Runnable_$ID$.OverheadDelegate overheadDelegate; - private BenchmarkDotNet.Autogenerated.Runnable_$ID$.WorkloadDelegate workloadDelegate; $DeclareArgumentFields$ // this method is used only for the disassembly diagnoser purposes @@ -111,6 +103,19 @@ #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$) + { + return $WorkloadMethodCall$; + } + private BenchmarkDotNet.Engines.Consumer consumer = new BenchmarkDotNet.Engines.Consumer(); #if NETCOREAPP3_0_OR_GREATER @@ -121,7 +126,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(overheadDelegate($PassArguments$));@Unroll@ + consumer.Consume(__OverheadWrapper($PassArguments$));@Unroll@ } } @@ -133,7 +138,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(overheadDelegate($PassArguments$)); + consumer.Consume(__OverheadWrapper($PassArguments$)); } } @@ -145,7 +150,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$);@Unroll@ + consumer.Consume(__WorkloadWrapper($PassArguments$)$ConsumeField$);@Unroll@ } } @@ -157,7 +162,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$); + consumer.Consume(__WorkloadWrapper($PassArguments$)$ConsumeField$); } } @@ -175,6 +180,19 @@ #elif RETURNS_NON_CONSUMABLE_STRUCT_$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 @@ -184,7 +202,7 @@ $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - result = overheadDelegate($PassArguments$);@Unroll@ + result = __OverheadWrapper($PassArguments$);@Unroll@ } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); } @@ -198,7 +216,7 @@ $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - result = overheadDelegate($PassArguments$); + result = __OverheadWrapper($PassArguments$); } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); } @@ -212,7 +230,7 @@ $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); for (System.Int64 i = 0; i < invokeCount; i++) { - result = workloadDelegate($PassArguments$);@Unroll@ + result = __WorkloadWrapper($PassArguments$);@Unroll@ } NonGenericKeepAliveWithoutBoxing(result); } @@ -226,7 +244,7 @@ $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); for (System.Int64 i = 0; i < invokeCount; i++) { - result = workloadDelegate($PassArguments$); + result = __WorkloadWrapper($PassArguments$); } NonGenericKeepAliveWithoutBoxing(result); } @@ -250,6 +268,19 @@ #elif RETURNS_BYREF_$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 $WorkloadMethodReturnType$ __WorkloadWrapper($ArgumentsDefinition$) + { + return ref $WorkloadMethodCall$; + } + #if NETCOREAPP3_0_OR_GREATER [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] #endif @@ -259,7 +290,7 @@ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - value = overheadDelegate($PassArguments$);@Unroll@ + value = __OverheadWrapper($PassArguments$);@Unroll@ } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } @@ -273,7 +304,7 @@ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - value = overheadDelegate($PassArguments$); + value = __OverheadWrapper($PassArguments$); } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } @@ -289,7 +320,7 @@ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; for (System.Int64 i = 0; i < invokeCount; i++) { - alias = workloadDelegate($PassArguments$);@Unroll@ + alias = __WorkloadWrapper($PassArguments$);@Unroll@ } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); } @@ -303,7 +334,7 @@ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; for (System.Int64 i = 0; i < invokeCount; i++) { - alias = workloadDelegate($PassArguments$); + alias = __WorkloadWrapper($PassArguments$); } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); } @@ -321,6 +352,19 @@ } #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$; + } + #if NETCOREAPP3_0_OR_GREATER [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] #endif @@ -330,7 +374,7 @@ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - value = overheadDelegate($PassArguments$);@Unroll@ + value = __OverheadWrapper($PassArguments$);@Unroll@ } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } @@ -344,7 +388,7 @@ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); for (System.Int64 i = 0; i < invokeCount; i++) { - value = overheadDelegate($PassArguments$); + value = __OverheadWrapper($PassArguments$); } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } @@ -360,7 +404,7 @@ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; for (System.Int64 i = 0; i < invokeCount; i++) { - alias = workloadDelegate($PassArguments$);@Unroll@ + alias = __WorkloadWrapper($PassArguments$);@Unroll@ } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); } @@ -374,7 +418,7 @@ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; for (System.Int64 i = 0; i < invokeCount; i++) { - alias = workloadDelegate($PassArguments$); + alias = __WorkloadWrapper($PassArguments$); } BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); } @@ -392,6 +436,19 @@ } #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 @@ -400,7 +457,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - overheadDelegate($PassArguments$);@Unroll@ + __OverheadWrapper($PassArguments$);@Unroll@ } } @@ -412,7 +469,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - overheadDelegate($PassArguments$); + __OverheadWrapper($PassArguments$); } } @@ -424,7 +481,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - workloadDelegate($PassArguments$);@Unroll@ + __WorkloadWrapper($PassArguments$);@Unroll@ } } @@ -436,7 +493,7 @@ $LoadArguments$ for (System.Int64 i = 0; i < invokeCount; i++) { - workloadDelegate($PassArguments$); + __WorkloadWrapper($PassArguments$); } } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs index 7f9d47c62f..4844086a3c 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs @@ -168,7 +168,7 @@ private static ModuleBuilder DefineModuleBuilder(AssemblyBuilder assemblyBuilder BenchmarkBuildInfo benchmark, ModuleBuilder moduleBuilder) { - // .class public auto ansi beforefieldinit BenchmarkDotNet.Autogenerated.Runnable_0 + // .class public auto ansi sealed beforefieldinit BenchmarkDotNet.Autogenerated.Runnable_0 // extends [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark var benchmarkDescriptor = benchmark.BenchmarkCase.Descriptor; @@ -182,7 +182,7 @@ private static ModuleBuilder DefineModuleBuilder(AssemblyBuilder assemblyBuilder } var result = moduleBuilder.DefineType( GetRunnableTypeName(benchmark), - workloadTypeAttributes, + workloadTypeAttributes | TypeAttributes.Sealed, workloadType); return result; @@ -240,8 +240,6 @@ private static ModuleBuilder DefineModuleBuilder(AssemblyBuilder assemblyBuilder private int jobUnrollFactor; private int dummyUnrollFactor; - private Type overheadDelegateType; - private Type workloadDelegateType; private TypeBuilder runnableBuilder; private ConsumableTypeInfo consumableInfo; private ConsumeEmitter consumeEmitter; @@ -250,8 +248,6 @@ private static ModuleBuilder DefineModuleBuilder(AssemblyBuilder assemblyBuilder private FieldBuilder globalCleanupActionField; private FieldBuilder iterationSetupActionField; private FieldBuilder iterationCleanupActionField; - private FieldBuilder overheadDelegateField; - private FieldBuilder workloadDelegateField; private FieldBuilder notElevenField; private FieldBuilder dummyVarField; @@ -261,8 +257,9 @@ private static ModuleBuilder DefineModuleBuilder(AssemblyBuilder assemblyBuilder private MethodBuilder dummy1Method; private MethodBuilder dummy2Method; private MethodBuilder dummy3Method; - private MethodInfo workloadImplementationMethod; + private MethodBuilder workloadWrapperMethod; private MethodBuilder overheadImplementationMethod; + private MethodBuilder overheadWrapperMethod; private MethodBuilder overheadActionUnrollMethod; private MethodBuilder overheadActionNoUnrollMethod; private MethodBuilder workloadActionUnrollMethod; @@ -315,11 +312,12 @@ private Type EmitRunnableCore(BenchmarkBuildInfo newBenchmark) // Overhead impl overheadImplementationMethod = EmitOverheadImplementation(OverheadImplementationMethodName); + overheadWrapperMethod = EmitOverheadWrapperImplementation(OverheadWrapperImplementationMethodName); overheadActionUnrollMethod = EmitOverheadAction(OverheadActionUnrollMethodName, jobUnrollFactor); overheadActionNoUnrollMethod = EmitOverheadAction(OverheadActionNoUnrollMethodName, 1); // Workload impl - workloadImplementationMethod = EmitWorkloadImplementation(WorkloadImplementationMethodName); + workloadWrapperMethod = EmitWorkloadWrapperImplementation(WorkloadWrapperImplementationMethodName); workloadActionUnrollMethod = EmitWorkloadAction(WorkloadActionUnrollMethodName, jobUnrollFactor); workloadActionNoUnrollMethod = EmitWorkloadAction(WorkloadActionNoUnrollMethodName, 1); @@ -357,56 +355,8 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark) consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType); consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo); - // Init types + // Init type runnableBuilder = DefineRunnableTypeBuilder(benchmark, moduleBuilder); - overheadDelegateType = EmitOverheadDelegateType(); - workloadDelegateType = EmitWorkloadDelegateType(); - } - - private Type EmitOverheadDelegateType() - { - // .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0OverheadDelegate - // extends[mscorlib]System.MulticastDelegate; - var overheadReturnType = EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType); - - // replace arg names - var overheadParameters = Descriptor.WorkloadMethod.GetParameters() - .Select(p => - (ParameterInfo)new EmitParameterInfo( - p.Position, - ArgParamPrefix + p.Position, - p.ParameterType, - p.Attributes, - null)) - .ToArray(); - - return moduleBuilder.EmitCustomDelegate( - GetRunnableTypeName(benchmark) + OverheadDelegateTypeSuffix, - overheadReturnType, - overheadParameters); - } - - private Type EmitWorkloadDelegateType() - { - // .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0WorkloadDelegate - // extends [mscorlib]System.MulticastDelegate - var workloadReturnType = EmitParameterInfo.CreateReturnParameter(consumableInfo.WorkloadMethodReturnType); - - // Replace arg names - var workloadParameters = Descriptor.WorkloadMethod.GetParameters() - .Select(p => - (ParameterInfo)new EmitParameterInfo( - p.Position, - ArgParamPrefix + p.Position, - p.ParameterType, - p.Attributes, - null)) - .ToArray(); - - return moduleBuilder.EmitCustomDelegate( - GetRunnableTypeName(benchmark) + WorkloadDelegateTypeSuffix, - workloadReturnType, - workloadParameters); } private void DefineFields() @@ -419,10 +369,6 @@ private void DefineFields() runnableBuilder.DefineField(IterationSetupActionFieldName, typeof(Action), FieldAttributes.Private); iterationCleanupActionField = runnableBuilder.DefineField(IterationCleanupActionFieldName, typeof(Action), FieldAttributes.Private); - overheadDelegateField = - runnableBuilder.DefineField(OverheadDelegateFieldName, overheadDelegateType, FieldAttributes.Private); - workloadDelegateField = - runnableBuilder.DefineField(WorkloadDelegateFieldName, workloadDelegateType, FieldAttributes.Private); // Define arg fields foreach (var parameter in Descriptor.WorkloadMethod.GetParameters()) @@ -538,15 +484,25 @@ private MethodBuilder EmitDummyMethod(string methodName, int unrollFactor) private MethodBuilder EmitOverheadImplementation(string methodName) { - var overheadInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(overheadDelegateType); - //.method private hidebysig // instance int32 __Overhead(int64 arg0) cil managed + + // Replace arg names + var parameters = Descriptor.WorkloadMethod.GetParameters() + .Select(p => + (ParameterInfo) new EmitParameterInfo( + p.Position, + ArgParamPrefix + p.Position, + p.ParameterType, + p.Attributes, + null)) + .ToArray(); + var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, - overheadInvokeMethod.ReturnParameter, - overheadInvokeMethod.GetParameters()); + EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType), + parameters); var ilBuilder = methodBuilder.GetILGenerator(); var returnType = methodBuilder.ReturnType; @@ -563,59 +519,113 @@ private MethodBuilder EmitOverheadImplementation(string methodName) return methodBuilder; } - private MethodInfo EmitWorkloadImplementation(string methodName) + private MethodBuilder EmitOverheadWrapperImplementation(string methodName) { - // Shortcut: DO NOT emit method if the result type is not awaitable - if (!consumableInfo.IsAwaitable) - return Descriptor.WorkloadMethod; + //.method private hidebysig + // instance int32 __OverheadWrapper(int64 arg0) cil managed noinlining nooptimization - var workloadInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); + // Replace arg names + var parameters = Descriptor.WorkloadMethod.GetParameters() + .Select(p => + (ParameterInfo) new EmitParameterInfo( + p.Position, + ArgParamPrefix + p.Position, + p.ParameterType, + p.Attributes, + null)) + .ToArray(); - //.method private hidebysig - // instance int32 __Workload(int64 arg0) cil managed - var args = workloadInvokeMethod.GetParameters(); var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, - workloadInvokeMethod.ReturnParameter, - args); - args = methodBuilder.GetEmitParameters(args); - var callResultType = consumableInfo.OriginMethodReturnType; - var awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType - ?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null"); + EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType), + parameters) + .SetNoInliningImplementationFlag() + .SetNoOptimizationImplementationFlag(); var ilBuilder = methodBuilder.GetILGenerator(); - /* - .locals init ( - [0] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 - ) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call instance int32 [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::__Overhead(int64) + IL_0007: ret */ - var callResultLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(callResultType, consumableInfo.GetAwaiterMethod); - var awaiterLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod); + if (!overheadImplementationMethod.IsStatic) + ilBuilder.Emit(OpCodes.Ldarg_0); + parameters = methodBuilder.GetEmitParameters(parameters); + ilBuilder.EmitLdargs(parameters); + ilBuilder.Emit(OpCodes.Call, overheadImplementationMethod); + ilBuilder.Emit(OpCodes.Ret); + return methodBuilder; + } + + private MethodBuilder EmitWorkloadWrapperImplementation(string methodName) + { + //.method private hidebysig + // instance string __WorkloadWrapper(int32 arg0) cil managed noinlining nooptimization + + // Replace arg names + var parameters = Descriptor.WorkloadMethod.GetParameters() + .Select(p => + (ParameterInfo) new EmitParameterInfo( + p.Position, + ArgParamPrefix + p.Position, + p.ParameterType, + p.Attributes, + null)) + .ToArray(); + + var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( + methodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(consumableInfo.WorkloadMethodReturnType), + parameters) + .SetNoInliningImplementationFlag() + .SetNoOptimizationImplementationFlag(); + + var ilBuilder = methodBuilder.GetILGenerator(); + + Type awaiterType = null; + LocalBuilder callResultLocal = null; + LocalBuilder awaiterLocal = null; + if (consumableInfo.IsAwaitable) + { + awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType + ?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null"); + /* + .locals init ( + [0] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 + ) + */ + callResultLocal = ilBuilder.DeclareOptionalLocalForInstanceCall(consumableInfo.OriginMethodReturnType, consumableInfo.GetAwaiterMethod); + awaiterLocal = ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod); + } /* - // return TaskSample(arg0). ... ; IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: call instance class [mscorlib]System.Threading.Tasks.Task`1 [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::TaskSample(int64) + IL_0002: call instance string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::ReturnSingleArgCase(int32) */ if (!Descriptor.WorkloadMethod.IsStatic) + { ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.EmitLdargs(args); + } + parameters = methodBuilder.GetEmitParameters(parameters); + ilBuilder.EmitLdargs(parameters); ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod); - /* - // ... .GetAwaiter().GetResult(); - IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 class [mscorlib]System.Threading.Tasks.Task`1::GetAwaiter() - IL_000c: stloc.0 - IL_000d: ldloca.s 0 - IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() - */ - ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod); - ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod); + if (consumableInfo.IsAwaitable) + { + /* + // ... .GetAwaiter().GetResult(); + IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 class [mscorlib]System.Threading.Tasks.Task`1::GetAwaiter() + IL_000c: stloc.0 + IL_000d: ldloca.s 0 + IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() + */ + ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod); + ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod); + } /* IL_0014: ret @@ -637,17 +647,14 @@ private MethodBuilder EmitWorkloadAction(string methodName, int unrollFactor) private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actionKind, int unrollFactor) { - FieldInfo actionDelegateField; - MethodInfo actionInvokeMethod; + MethodInfo invokeMethod; switch (actionKind) { case RunnableActionKind.Overhead: - actionDelegateField = overheadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(overheadDelegateType); + invokeMethod = overheadWrapperMethod; break; case RunnableActionKind.Workload: - actionDelegateField = workloadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); + invokeMethod = workloadWrapperMethod; break; default: throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null); @@ -665,7 +672,7 @@ private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actio // Emit impl var ilBuilder = actionMethodBuilder.GetILGenerator(); - consumeEmitter.BeginEmitAction(actionMethodBuilder, ilBuilder, actionInvokeMethod, actionKind); + consumeEmitter.BeginEmitAction(actionMethodBuilder, ilBuilder, invokeMethod, actionKind); // init locals var argLocals = EmitDeclareArgLocals(ilBuilder); @@ -701,8 +708,7 @@ private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actio consumeEmitter.EmitActionBeforeCall(ilBuilder); ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); - ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); + ilBuilder.EmitInstanceCallThisValueOnStack(null, invokeMethod, argLocals, forceDirectCall: true); consumeEmitter.EmitActionAfterCall(ilBuilder); } @@ -956,12 +962,6 @@ private void EmitCtorBody() ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod); ilBuilder.EmitSetDelegateToThisField(iterationSetupActionField, iterationSetupMethod); ilBuilder.EmitSetDelegateToThisField(iterationCleanupActionField, iterationCleanupMethod); - ilBuilder.EmitSetDelegateToThisField(overheadDelegateField, overheadImplementationMethod); - - if (workloadImplementationMethod == null) - ilBuilder.EmitSetDelegateToThisField(workloadDelegateField, Descriptor.WorkloadMethod); - else - ilBuilder.EmitSetDelegateToThisField(workloadDelegateField, workloadImplementationMethod); ilBuilder.EmitCtorReturn(ctorMethod); } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs index c6e8cd8ae1..349b7aa8ca 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Runnable/RunnableConstants.cs @@ -20,8 +20,6 @@ public class RunnableConstants public const string GlobalCleanupActionFieldName = "globalCleanupAction"; public const string IterationSetupActionFieldName = "iterationSetupAction"; public const string IterationCleanupActionFieldName = "iterationCleanupAction"; - public const string WorkloadDelegateFieldName = "workloadDelegate"; - public const string OverheadDelegateFieldName = "overheadDelegate"; public const string NotElevenFieldName = "NotEleven"; public const string DummyVarFieldName = "dummyVar"; @@ -30,7 +28,9 @@ public class RunnableConstants public const string Dummy2MethodName = "Dummy2"; public const string Dummy3MethodName = "Dummy3"; public const string WorkloadImplementationMethodName = "__Workload"; + public const string WorkloadWrapperImplementationMethodName = "__WorkloadWrapper"; public const string OverheadImplementationMethodName = "__Overhead"; + public const string OverheadWrapperImplementationMethodName = "__OverheadWrapper"; public const string OverheadActionUnrollMethodName = "OverheadActionUnroll"; public const string OverheadActionNoUnrollMethodName = "OverheadActionNoUnroll"; public const string WorkloadActionUnrollMethodName = "WorkloadActionUnroll";