Skip to content

Commit 5f0c47b

Browse files
authored
Fix async GlobalSetup/GlobalCleanup not being awaited with InProcessEmit toolchain. (#2109)
1 parent c7ed714 commit 5f0c47b

File tree

2 files changed

+180
-27
lines changed

2 files changed

+180
-27
lines changed

src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ private static void EmitNoArgsMethodCallPopReturn(
247247
private TypeBuilder runnableBuilder;
248248
private ConsumableTypeInfo consumableInfo;
249249
private ConsumeEmitter consumeEmitter;
250+
private ConsumableTypeInfo globalSetupReturnInfo;
251+
private ConsumableTypeInfo globalCleanupReturnInfo;
252+
private ConsumableTypeInfo iterationSetupReturnInfo;
253+
private ConsumableTypeInfo iterationCleanupReturnInfo;
250254

251255
private FieldBuilder globalSetupActionField;
252256
private FieldBuilder globalCleanupActionField;
@@ -358,13 +362,22 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark)
358362

359363
consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType);
360364
consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo);
365+
globalSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalSetupMethod?.ReturnType);
366+
globalCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalCleanupMethod?.ReturnType);
367+
iterationSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationSetupMethod?.ReturnType);
368+
iterationCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationCleanupMethod?.ReturnType);
361369

362370
// Init types
363371
runnableBuilder = DefineRunnableTypeBuilder(benchmark, moduleBuilder);
364372
overheadDelegateType = EmitOverheadDelegateType();
365373
workloadDelegateType = EmitWorkloadDelegateType();
366374
}
367375

376+
private static ConsumableTypeInfo GetConsumableTypeInfo(Type methodReturnType)
377+
{
378+
return methodReturnType == null ? null : new ConsumableTypeInfo(methodReturnType);
379+
}
380+
368381
private Type EmitOverheadDelegateType()
369382
{
370383
// .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0OverheadDelegate
@@ -890,34 +903,84 @@ private void EmitSetupCleanupMethods()
890903
{
891904
// Emit Setup/Cleanup methods
892905
// We emit empty method instead of EmptyAction = "() => { }"
893-
globalSetupMethod = EmitWrapperMethod(
894-
GlobalSetupMethodName,
895-
Descriptor.GlobalSetupMethod);
896-
globalCleanupMethod = EmitWrapperMethod(
897-
GlobalCleanupMethodName,
898-
Descriptor.GlobalCleanupMethod);
899-
iterationSetupMethod = EmitWrapperMethod(
900-
IterationSetupMethodName,
901-
Descriptor.IterationSetupMethod);
902-
iterationCleanupMethod = EmitWrapperMethod(
903-
IterationCleanupMethodName,
904-
Descriptor.IterationCleanupMethod);
906+
globalSetupMethod = EmitWrapperMethod(GlobalSetupMethodName, Descriptor.GlobalSetupMethod, globalSetupReturnInfo);
907+
globalCleanupMethod = EmitWrapperMethod(GlobalCleanupMethodName, Descriptor.GlobalCleanupMethod, globalCleanupReturnInfo);
908+
iterationSetupMethod = EmitWrapperMethod(IterationSetupMethodName, Descriptor.IterationSetupMethod, iterationSetupReturnInfo);
909+
iterationCleanupMethod = EmitWrapperMethod(IterationCleanupMethodName, Descriptor.IterationCleanupMethod, iterationCleanupReturnInfo);
905910
}
906911

907-
private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod)
912+
private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod, ConsumableTypeInfo returnTypeInfo)
908913
{
909914
var methodBuilder = runnableBuilder.DefinePrivateVoidInstanceMethod(methodName);
910915

911916
var ilBuilder = methodBuilder.GetILGenerator();
912917

913918
if (optionalTargetMethod != null)
914-
EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true);
919+
{
920+
if (returnTypeInfo?.IsAwaitable == true)
921+
{
922+
EmitAwaitableSetupTeardown(methodBuilder, optionalTargetMethod, ilBuilder, returnTypeInfo);
923+
}
924+
else
925+
{
926+
EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true);
927+
}
928+
}
915929

916930
ilBuilder.EmitVoidReturn(methodBuilder);
917931

918932
return methodBuilder;
919933
}
920934

935+
private void EmitAwaitableSetupTeardown(
936+
MethodBuilder methodBuilder,
937+
MethodInfo targetMethod,
938+
ILGenerator ilBuilder,
939+
ConsumableTypeInfo returnTypeInfo)
940+
{
941+
if (targetMethod == null)
942+
throw new ArgumentNullException(nameof(targetMethod));
943+
944+
if (returnTypeInfo.WorkloadMethodReturnType == typeof(void))
945+
{
946+
ilBuilder.Emit(OpCodes.Ldarg_0);
947+
}
948+
/*
949+
// call for instance
950+
// GlobalSetup();
951+
IL_0006: ldarg.0
952+
IL_0007: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup()
953+
*/
954+
/*
955+
// call for static
956+
// GlobalSetup();
957+
IL_0006: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup()
958+
*/
959+
if (targetMethod.IsStatic)
960+
{
961+
ilBuilder.Emit(OpCodes.Call, targetMethod);
962+
963+
}
964+
else if (methodBuilder.IsStatic)
965+
{
966+
throw new InvalidOperationException(
967+
$"[BUG] Static method {methodBuilder.Name} tries to call instance member {targetMethod.Name}");
968+
}
969+
else
970+
{
971+
ilBuilder.Emit(OpCodes.Ldarg_0);
972+
ilBuilder.Emit(OpCodes.Call, targetMethod);
973+
}
974+
975+
/*
976+
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
977+
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
978+
*/
979+
980+
ilBuilder.Emit(OpCodes.Call, returnTypeInfo.GetResultMethod);
981+
ilBuilder.Emit(OpCodes.Pop);
982+
}
983+
921984
private void EmitCtorBody()
922985
{
923986
var ilBuilder = ctorMethod.GetILGenerator();

tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -239,35 +239,125 @@ public static ValueTask<decimal> InvokeOnceStaticValueTaskOfT()
239239
}
240240
}
241241

242-
[Fact]
243-
public void InProcessEmitToolchainSupportsIterationSetupAndCleanup()
242+
[Theory]
243+
[InlineData(typeof(IterationSetupCleanup))]
244+
[InlineData(typeof(GlobalSetupCleanupTask))]
245+
[InlineData(typeof(GlobalSetupCleanupValueTask))]
246+
[InlineData(typeof(GlobalSetupCleanupValueTaskSource))]
247+
public void InProcessEmitToolchainSupportsSetupAndCleanup(Type benchmarkType)
244248
{
245249
var logger = new OutputLogger(Output);
246250
var config = CreateInProcessConfig(logger);
247251

248-
WithIterationSetupAndCleanup.SetupCounter = 0;
249-
WithIterationSetupAndCleanup.BenchmarkCounter = 0;
250-
WithIterationSetupAndCleanup.CleanupCounter = 0;
252+
Counters.SetupCounter = 0;
253+
Counters.BenchmarkCounter = 0;
254+
Counters.CleanupCounter = 0;
251255

252-
var summary = CanExecute<WithIterationSetupAndCleanup>(config);
256+
var summary = CanExecute(benchmarkType, config);
253257

254-
Assert.Equal(1, WithIterationSetupAndCleanup.SetupCounter);
255-
Assert.Equal(16, WithIterationSetupAndCleanup.BenchmarkCounter);
256-
Assert.Equal(1, WithIterationSetupAndCleanup.CleanupCounter);
258+
Assert.Equal(1, Counters.SetupCounter);
259+
Assert.Equal(16, Counters.BenchmarkCounter);
260+
Assert.Equal(1, Counters.CleanupCounter);
257261
}
258262

259-
public class WithIterationSetupAndCleanup
263+
private static class Counters
260264
{
261265
public static int SetupCounter, BenchmarkCounter, CleanupCounter;
266+
}
262267

268+
public class IterationSetupCleanup
269+
{
263270
[IterationSetup]
264-
public void Setup() => Interlocked.Increment(ref SetupCounter);
271+
public void Setup() => Interlocked.Increment(ref Counters.SetupCounter);
265272

266273
[Benchmark]
267-
public void Benchmark() => Interlocked.Increment(ref BenchmarkCounter);
274+
public void Benchmark() => Interlocked.Increment(ref Counters.BenchmarkCounter);
268275

269276
[IterationCleanup]
270-
public void Cleanup() => Interlocked.Increment(ref CleanupCounter);
277+
public void Cleanup() => Interlocked.Increment(ref Counters.CleanupCounter);
278+
}
279+
280+
public class GlobalSetupCleanupTask
281+
{
282+
[GlobalSetup]
283+
public static async Task GlobalSetup()
284+
{
285+
await Task.Yield();
286+
Interlocked.Increment(ref Counters.SetupCounter);
287+
}
288+
289+
[GlobalCleanup]
290+
public async Task<int> GlobalCleanup()
291+
{
292+
await Task.Yield();
293+
Interlocked.Increment(ref Counters.CleanupCounter);
294+
return 42;
295+
}
296+
297+
[Benchmark]
298+
public void InvokeOnceVoid()
299+
{
300+
Interlocked.Increment(ref Counters.BenchmarkCounter);
301+
}
302+
}
303+
304+
public class GlobalSetupCleanupValueTask
305+
{
306+
[GlobalSetup]
307+
public static async ValueTask GlobalSetup()
308+
{
309+
await Task.Yield();
310+
Interlocked.Increment(ref Counters.SetupCounter);
311+
}
312+
313+
[GlobalCleanup]
314+
public async ValueTask<int> GlobalCleanup()
315+
{
316+
await Task.Yield();
317+
Interlocked.Increment(ref Counters.CleanupCounter);
318+
return 42;
319+
}
320+
321+
[Benchmark]
322+
public void InvokeOnceVoid()
323+
{
324+
Interlocked.Increment(ref Counters.BenchmarkCounter);
325+
}
326+
}
327+
328+
public class GlobalSetupCleanupValueTaskSource
329+
{
330+
private readonly static ValueTaskSource<int> valueTaskSource = new ();
331+
332+
[GlobalSetup]
333+
public static ValueTask GlobalSetup()
334+
{
335+
valueTaskSource.Reset();
336+
Task.Delay(1).ContinueWith(_ =>
337+
{
338+
Interlocked.Increment(ref Counters.SetupCounter);
339+
valueTaskSource.SetResult(42);
340+
});
341+
return new ValueTask(valueTaskSource, valueTaskSource.Token);
342+
}
343+
344+
[GlobalCleanup]
345+
public ValueTask<int> GlobalCleanup()
346+
{
347+
valueTaskSource.Reset();
348+
Task.Delay(1).ContinueWith(_ =>
349+
{
350+
Interlocked.Increment(ref Counters.CleanupCounter);
351+
valueTaskSource.SetResult(42);
352+
});
353+
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
354+
}
355+
356+
[Benchmark]
357+
public void InvokeOnceVoid()
358+
{
359+
Interlocked.Increment(ref Counters.BenchmarkCounter);
360+
}
271361
}
272362
}
273363
}

0 commit comments

Comments
 (0)