Skip to content

Fix async GlobalSetup/Cleanup with InProcessEmit toolchain #2109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ private static void EmitNoArgsMethodCallPopReturn(
private TypeBuilder runnableBuilder;
private ConsumableTypeInfo consumableInfo;
private ConsumeEmitter consumeEmitter;
private ConsumableTypeInfo globalSetupReturnInfo;
private ConsumableTypeInfo globalCleanupReturnInfo;
private ConsumableTypeInfo iterationSetupReturnInfo;
private ConsumableTypeInfo iterationCleanupReturnInfo;

private FieldBuilder globalSetupActionField;
private FieldBuilder globalCleanupActionField;
Expand Down Expand Up @@ -358,13 +362,22 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark)

consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType);
consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo);
globalSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalSetupMethod?.ReturnType);
globalCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalCleanupMethod?.ReturnType);
iterationSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationSetupMethod?.ReturnType);
iterationCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationCleanupMethod?.ReturnType);

// Init types
runnableBuilder = DefineRunnableTypeBuilder(benchmark, moduleBuilder);
overheadDelegateType = EmitOverheadDelegateType();
workloadDelegateType = EmitWorkloadDelegateType();
}

private static ConsumableTypeInfo GetConsumableTypeInfo(Type methodReturnType)
{
return methodReturnType == null ? null : new ConsumableTypeInfo(methodReturnType);
}

private Type EmitOverheadDelegateType()
{
// .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0OverheadDelegate
Expand Down Expand Up @@ -890,34 +903,84 @@ private void EmitSetupCleanupMethods()
{
// Emit Setup/Cleanup methods
// We emit empty method instead of EmptyAction = "() => { }"
globalSetupMethod = EmitWrapperMethod(
GlobalSetupMethodName,
Descriptor.GlobalSetupMethod);
globalCleanupMethod = EmitWrapperMethod(
GlobalCleanupMethodName,
Descriptor.GlobalCleanupMethod);
iterationSetupMethod = EmitWrapperMethod(
IterationSetupMethodName,
Descriptor.IterationSetupMethod);
iterationCleanupMethod = EmitWrapperMethod(
IterationCleanupMethodName,
Descriptor.IterationCleanupMethod);
globalSetupMethod = EmitWrapperMethod(GlobalSetupMethodName, Descriptor.GlobalSetupMethod, globalSetupReturnInfo);
globalCleanupMethod = EmitWrapperMethod(GlobalCleanupMethodName, Descriptor.GlobalCleanupMethod, globalCleanupReturnInfo);
iterationSetupMethod = EmitWrapperMethod(IterationSetupMethodName, Descriptor.IterationSetupMethod, iterationSetupReturnInfo);
iterationCleanupMethod = EmitWrapperMethod(IterationCleanupMethodName, Descriptor.IterationCleanupMethod, iterationCleanupReturnInfo);
}

private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod)
private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod, ConsumableTypeInfo returnTypeInfo)
{
var methodBuilder = runnableBuilder.DefinePrivateVoidInstanceMethod(methodName);

var ilBuilder = methodBuilder.GetILGenerator();

if (optionalTargetMethod != null)
EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true);
{
if (returnTypeInfo?.IsAwaitable == true)
{
EmitAwaitableSetupTeardown(methodBuilder, optionalTargetMethod, ilBuilder, returnTypeInfo);
}
else
{
EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true);
}
}

ilBuilder.EmitVoidReturn(methodBuilder);

return methodBuilder;
}

private void EmitAwaitableSetupTeardown(
MethodBuilder methodBuilder,
MethodInfo targetMethod,
ILGenerator ilBuilder,
ConsumableTypeInfo returnTypeInfo)
{
if (targetMethod == null)
throw new ArgumentNullException(nameof(targetMethod));

if (returnTypeInfo.WorkloadMethodReturnType == typeof(void))
{
ilBuilder.Emit(OpCodes.Ldarg_0);
}
/*
// call for instance
// GlobalSetup();
IL_0006: ldarg.0
IL_0007: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup()
*/
/*
// call for static
// GlobalSetup();
IL_0006: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup()
*/
if (targetMethod.IsStatic)
{
ilBuilder.Emit(OpCodes.Call, targetMethod);

}
else if (methodBuilder.IsStatic)
{
throw new InvalidOperationException(
$"[BUG] Static method {methodBuilder.Name} tries to call instance member {targetMethod.Name}");
}
else
{
ilBuilder.Emit(OpCodes.Ldarg_0);
ilBuilder.Emit(OpCodes.Call, targetMethod);
}

/*
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
*/

ilBuilder.Emit(OpCodes.Call, returnTypeInfo.GetResultMethod);
ilBuilder.Emit(OpCodes.Pop);
}

private void EmitCtorBody()
{
var ilBuilder = ctorMethod.GetILGenerator();
Expand Down
116 changes: 103 additions & 13 deletions tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,35 +239,125 @@ public static ValueTask<decimal> InvokeOnceStaticValueTaskOfT()
}
}

[Fact]
public void InProcessEmitToolchainSupportsIterationSetupAndCleanup()
[Theory]
[InlineData(typeof(IterationSetupCleanup))]
[InlineData(typeof(GlobalSetupCleanupTask))]
[InlineData(typeof(GlobalSetupCleanupValueTask))]
[InlineData(typeof(GlobalSetupCleanupValueTaskSource))]
public void InProcessEmitToolchainSupportsSetupAndCleanup(Type benchmarkType)
{
var logger = new OutputLogger(Output);
var config = CreateInProcessConfig(logger);

WithIterationSetupAndCleanup.SetupCounter = 0;
WithIterationSetupAndCleanup.BenchmarkCounter = 0;
WithIterationSetupAndCleanup.CleanupCounter = 0;
Counters.SetupCounter = 0;
Counters.BenchmarkCounter = 0;
Counters.CleanupCounter = 0;

var summary = CanExecute<WithIterationSetupAndCleanup>(config);
var summary = CanExecute(benchmarkType, config);

Assert.Equal(1, WithIterationSetupAndCleanup.SetupCounter);
Assert.Equal(16, WithIterationSetupAndCleanup.BenchmarkCounter);
Assert.Equal(1, WithIterationSetupAndCleanup.CleanupCounter);
Assert.Equal(1, Counters.SetupCounter);
Assert.Equal(16, Counters.BenchmarkCounter);
Assert.Equal(1, Counters.CleanupCounter);
}

public class WithIterationSetupAndCleanup
private static class Counters
{
public static int SetupCounter, BenchmarkCounter, CleanupCounter;
}

public class IterationSetupCleanup
{
[IterationSetup]
public void Setup() => Interlocked.Increment(ref SetupCounter);
public void Setup() => Interlocked.Increment(ref Counters.SetupCounter);

[Benchmark]
public void Benchmark() => Interlocked.Increment(ref BenchmarkCounter);
public void Benchmark() => Interlocked.Increment(ref Counters.BenchmarkCounter);

[IterationCleanup]
public void Cleanup() => Interlocked.Increment(ref CleanupCounter);
public void Cleanup() => Interlocked.Increment(ref Counters.CleanupCounter);
}

public class GlobalSetupCleanupTask
{
[GlobalSetup]
public static async Task GlobalSetup()
{
await Task.Yield();
Interlocked.Increment(ref Counters.SetupCounter);
}

[GlobalCleanup]
public async Task<int> GlobalCleanup()
{
await Task.Yield();
Interlocked.Increment(ref Counters.CleanupCounter);
return 42;
}

[Benchmark]
public void InvokeOnceVoid()
{
Interlocked.Increment(ref Counters.BenchmarkCounter);
}
}

public class GlobalSetupCleanupValueTask
{
[GlobalSetup]
public static async ValueTask GlobalSetup()
{
await Task.Yield();
Interlocked.Increment(ref Counters.SetupCounter);
}

[GlobalCleanup]
public async ValueTask<int> GlobalCleanup()
{
await Task.Yield();
Interlocked.Increment(ref Counters.CleanupCounter);
return 42;
}

[Benchmark]
public void InvokeOnceVoid()
{
Interlocked.Increment(ref Counters.BenchmarkCounter);
}
}

public class GlobalSetupCleanupValueTaskSource
{
private readonly static ValueTaskSource<int> valueTaskSource = new ();

[GlobalSetup]
public static ValueTask GlobalSetup()
{
valueTaskSource.Reset();
Task.Delay(1).ContinueWith(_ =>
{
Interlocked.Increment(ref Counters.SetupCounter);
valueTaskSource.SetResult(42);
});
return new ValueTask(valueTaskSource, valueTaskSource.Token);
}

[GlobalCleanup]
public ValueTask<int> GlobalCleanup()
{
valueTaskSource.Reset();
Task.Delay(1).ContinueWith(_ =>
{
Interlocked.Increment(ref Counters.CleanupCounter);
valueTaskSource.SetResult(42);
});
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
}

[Benchmark]
public void InvokeOnceVoid()
{
Interlocked.Increment(ref Counters.BenchmarkCounter);
}
}
}
}
Loading