diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 22efe4e9aa..bc34757575 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -2,7 +2,7 @@ BenchmarkDotNet - netstandard2.0;net6.0;net8.0 + netstandard2.0;net6.0;net8.0;net9.0 true $(NoWarn);1701;1702;1705;1591;3005;NU1702;CS3001;CS3003 BenchmarkDotNet diff --git a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs index 1ba6e6a038..c88d86565b 100644 --- a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs @@ -211,9 +211,7 @@ internal static bool IsStackOnlyWithImplicitCast(this Type argumentType, object? if (argumentInstance == null) return false; - // IsByRefLikeAttribute is not exposed for older runtimes, so we need to check it in an ugly way ;) - bool isByRefLike = argumentType.GetCustomAttributes().Any(attribute => attribute.ToString()?.Contains("IsByRefLike") ?? false); - if (!isByRefLike) + if (!argumentType.IsByRefLike()) return false; var instanceType = argumentInstance.GetType(); @@ -235,5 +233,13 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo) && typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0; + + internal static bool IsByRefLike(this Type type) +#if NETSTANDARD2_0 + // Type.IsByRefLike is not available in netstandard2.0. + => type.IsValueType && type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute"); +#else + => type.IsByRefLike; +#endif } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index 0afa8b5092..fd0640a9d0 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -150,6 +150,12 @@ protected void GatherReferences(string projectFilePath, BuildPartition buildPart // TODO: Add Aliases here for extern alias #2289 } + // Mono80IsSupported test fails when BenchmarkDotNet is restored for net9.0 if we don't remove the ProjectReference. + if (XUnitHelper.IsIntegrationTest.Value) + { + projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/ProjectReference").ParentNode); + } + xmlDoc.Save(artifactsPaths.ProjectFilePath); BuildResult BuildProject(string tfm) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs index 5f4d6db36a..27e20c1867 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs @@ -1,12 +1,9 @@ using System; -using JetBrains.Annotations; - namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { /// Common API to run the Setup/Clean/Idle/Run methods - [PublicAPI] - public abstract class BenchmarkAction + internal abstract class BenchmarkAction { /// Gets or sets invoke single callback. /// Invoke single callback. @@ -16,9 +13,5 @@ public abstract class BenchmarkAction /// Invoke multiple times callback. public Action InvokeUnroll { get; protected set; } public Action InvokeNoUnroll{ get; protected set; } - - /// Gets the last run result. - /// The last run result. - public virtual object LastRunResult => null; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs index aa9781f847..4473a909eb 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -9,27 +10,47 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { /// Helper class that creates instances. - public static partial class BenchmarkActionFactory + internal static partial class BenchmarkActionFactory { /// /// Dispatch method that creates using /// or to find correct implementation. /// Either or should be not null. /// - private static BenchmarkAction CreateCore( - object instance, - MethodInfo? targetMethod, - MethodInfo? fallbackIdleSignature, - int unrollFactor) + private static BenchmarkAction CreateCore(object instance, MethodInfo? targetMethod, MethodInfo? fallbackIdleSignature, int unrollFactor) { PrepareInstanceAndResultType(instance, targetMethod, fallbackIdleSignature, out var resultInstance, out var resultType); if (resultType == typeof(void)) return new BenchmarkActionVoid(resultInstance, targetMethod, unrollFactor); + if (resultType == typeof(void*)) + return new BenchmarkActionVoidPointer(resultInstance, targetMethod, unrollFactor); + + if (resultType.IsByRef) + { + var returnParameter = targetMethod?.ReturnParameter ?? fallbackIdleSignature.ReturnParameter; + // IsReadOnlyAttribute is not part of netstandard2.0, so we need to check the attribute name as usual. + if (returnParameter.GetCustomAttributes().Any(attribute => attribute.GetType().FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute")) + return Create( + typeof(BenchmarkActionByRefReadonly<>).MakeGenericType(resultType.GetElementType()), + resultInstance, + targetMethod, + unrollFactor); + + return Create( + typeof(BenchmarkActionByRef<>).MakeGenericType(resultType.GetElementType()), + resultInstance, + targetMethod, + unrollFactor); + } + if (resultType == typeof(Task)) return new BenchmarkActionTask(resultInstance, targetMethod, unrollFactor); + if (resultType == typeof(ValueTask)) + return new BenchmarkActionValueTask(resultInstance, targetMethod, unrollFactor); + if (resultType.GetTypeInfo().IsGenericType) { var genericType = resultType.GetGenericTypeDefinition(); @@ -49,10 +70,6 @@ private static BenchmarkAction CreateCore( unrollFactor); } - if (targetMethod == null && resultType.GetTypeInfo().IsValueType) - // for Idle: we return int because creating bigger ValueType could take longer than benchmarked method itself. - resultType = typeof(int); - return Create( typeof(BenchmarkAction<>).MakeGenericType(resultType), resultInstance, @@ -86,6 +103,10 @@ private static void PrepareInstanceAndResultType( if (isUsingAsyncKeyword) throw new NotSupportedException("Async void is not supported by design."); } + else if (resultType.IsPointer && resultType != typeof(void*)) + { + throw new NotSupportedException("InProcessNoEmitToolchain only supports void* return, not T*"); + } } /// Helper to enforce .ctor signature. @@ -109,7 +130,7 @@ public static BenchmarkAction CreateWorkload(Descriptor descriptor, object insta /// Unroll factor. /// Idle benchmark action. public static BenchmarkAction CreateOverhead(Descriptor descriptor, object instance, int unrollFactor) => - CreateCore(instance, null, descriptor.WorkloadMethod, unrollFactor); + CreateCore(instance, null, FallbackSignature, unrollFactor); /// Creates global setup benchmark action. /// Descriptor info. diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs index ee4ca65362..542e11fa95 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs @@ -20,9 +20,7 @@ 5. Implementation should match to the code in BenchmarkProgram.txt. // DONTTOUCH: Be VERY CAREFUL when changing the code. // Please, ensure that the implementation is in sync with content of BenchmarkProgram.txt - - /// Helper class that creates instances. - public static partial class BenchmarkActionFactory + internal static partial class BenchmarkActionFactory { /// Base class that provides reusable API for final implementations. internal abstract class BenchmarkActionBase : BenchmarkAction @@ -35,19 +33,15 @@ protected static TDelegate CreateWorkload(object? targetInstance, Met return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance); } - protected static TDelegate CreateWorkloadOrOverhead( - object? targetInstance, - MethodInfo? workloadMethod, - TDelegate overheadStaticCallback, - TDelegate overheadInstanceCallback) where TDelegate : notnull + protected Action CreateWorkloadOrOverhead(object? instance, MethodInfo? method) { - if (workloadMethod == null) - return targetInstance == null ? overheadStaticCallback : overheadInstanceCallback; - - if (workloadMethod.IsStatic) - return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate)); - - return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance); + if (method == null) + { + return instance == null ? OverheadStatic : OverheadInstance; + } + return method.IsStatic + ? (Action) method.CreateDelegate(typeof(Action)) + : (Action) method.CreateDelegate(typeof(Action), instance); } protected static TDelegate Unroll(TDelegate callback, int unrollFactor) @@ -61,6 +55,11 @@ protected static TDelegate Unroll(TDelegate callback, int unrollFacto return (TDelegate)(object)Delegate.Combine( Enumerable.Repeat((Delegate)(object)callback, unrollFactor).ToArray()); } + + // must be kept in sync with VoidDeclarationsProvider.IdleImplementation + private static void OverheadStatic() { } + + private void OverheadInstance() { } } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs index aee0a8f998..21911c6af9 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs @@ -12,79 +12,186 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit // DONTTOUCH: Be VERY CAREFUL when changing the code. // Please, ensure that the implementation is in sync with content of BenchmarkProgram.txt - - /// Helper class that creates instances. - public static partial class BenchmarkActionFactory + internal static partial class BenchmarkActionFactory { - internal class BenchmarkActionVoid : BenchmarkActionBase + internal sealed class BenchmarkActionVoid : BenchmarkActionBase { private readonly Action callback; private readonly Action unrolledCallback; - public BenchmarkActionVoid(object instance, MethodInfo method, int unrollFactor) + public BenchmarkActionVoid(object? instance, MethodInfo? method, int unrollFactor) { - callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); + callback = CreateWorkloadOrOverhead(instance, method); + unrolledCallback = Unroll(callback, unrollFactor); InvokeSingle = callback; + InvokeUnroll = WorkloadActionUnroll; + InvokeNoUnroll = WorkloadActionNoUnroll; + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + unrolledCallback(); + } + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionNoUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + callback(); + } + } + } + internal unsafe class BenchmarkActionVoidPointer : BenchmarkActionBase + { + private delegate void* PointerFunc(); + + private readonly PointerFunc callback; + private readonly PointerFunc unrolledCallback; + + public BenchmarkActionVoidPointer(object? instance, MethodInfo? method, int unrollFactor) + { + callback = CreateWorkload(instance, method); unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = () => callback(); InvokeUnroll = WorkloadActionUnroll; InvokeNoUnroll = WorkloadActionNoUnroll; } - private static void OverheadStatic() { } - private void OverheadInstance() { } + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + unrolledCallback(); + } + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionNoUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + callback(); + } + } + } + + internal unsafe class BenchmarkActionByRef : BenchmarkActionBase +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif + { + private delegate ref T ByRefFunc(); + + private readonly ByRefFunc callback; + private readonly ByRefFunc unrolledCallback; + + public BenchmarkActionByRef(object? instance, MethodInfo? method, int unrollFactor) + { + callback = CreateWorkload(instance, method); + unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = () => callback(); + InvokeUnroll = WorkloadActionUnroll; + InvokeNoUnroll = WorkloadActionNoUnroll; + } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { unrolledCallback(); + } } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { callback(); + } + } + } + + internal unsafe class BenchmarkActionByRefReadonly : BenchmarkActionBase +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif + { + private delegate ref readonly T ByRefReadonlyFunc(); + + private readonly ByRefReadonlyFunc callback; + private readonly ByRefReadonlyFunc unrolledCallback; + + public BenchmarkActionByRefReadonly(object? instance, MethodInfo? method, int unrollFactor) + { + callback = CreateWorkload(instance, method); + unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = () => callback(); + InvokeUnroll = WorkloadActionUnroll; + InvokeNoUnroll = WorkloadActionNoUnroll; + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + unrolledCallback(); + } + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionNoUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + callback(); + } } } internal class BenchmarkAction : BenchmarkActionBase +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif { private readonly Func callback; private readonly Func unrolledCallback; - private T result; - public BenchmarkAction(object instance, MethodInfo method, int unrollFactor) + public BenchmarkAction(object? instance, MethodInfo? method, int unrollFactor) { - callback = CreateWorkloadOrOverhead>(instance, method, OverheadStatic, OverheadInstance); - InvokeSingle = InvokeSingleHardcoded; - + callback = CreateWorkload>(instance, method); unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = () => callback(); InvokeUnroll = WorkloadActionUnroll; InvokeNoUnroll = WorkloadActionNoUnroll; } - private static T OverheadStatic() => default; - private T OverheadInstance() => default; - - private void InvokeSingleHardcoded() => result = callback(); - [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + unrolledCallback(); + } } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = callback(); + { + callback(); + } } - - public override object LastRunResult => result; } internal class BenchmarkActionTask : BenchmarkActionBase @@ -93,30 +200,23 @@ internal class BenchmarkActionTask : BenchmarkActionBase private readonly Action callback; private readonly Action unrolledCallback; - public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) + public BenchmarkActionTask(object? instance, MethodInfo? method, int unrollFactor) { - bool isIdle = method == null; - if (!isIdle) + if (method == null) { - startTaskCallback = CreateWorkload>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkloadOrOverhead(instance, method); } else { - callback = Overhead; + startTaskCallback = CreateWorkload>(instance, method); + callback = ExecuteBlocking; } - - InvokeSingle = callback; - unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = callback; InvokeUnroll = WorkloadActionUnroll; InvokeNoUnroll = WorkloadActionNoUnroll; - } - // must be kept in sync with VoidDeclarationsProvider.IdleImplementation - private void Overhead() { } - // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); @@ -124,118 +224,154 @@ private void Overhead() { } private void WorkloadActionUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { unrolledCallback(); + } } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { callback(); + } } } internal class BenchmarkActionTask : BenchmarkActionBase { private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; - private T result; + private readonly Action callback; + private readonly Action unrolledCallback; - public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) + public BenchmarkActionTask(object? instance, MethodInfo? method, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + if (method == null) + { + callback = CreateWorkloadOrOverhead(instance, method); + } + else { startTaskCallback = CreateWorkload>>(instance, method); callback = ExecuteBlocking; } - else + unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = callback; + InvokeUnroll = WorkloadActionUnroll; + InvokeNoUnroll = WorkloadActionNoUnroll; + } + + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) { - callback = Overhead; + unrolledCallback(); + } + } + + [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] + private void WorkloadActionNoUnroll(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + callback(); } + } + } - InvokeSingle = InvokeSingleHardcoded; + internal class BenchmarkActionValueTask : BenchmarkActionBase + { + private readonly Func startTaskCallback; + private readonly Action callback; + private readonly Action unrolledCallback; + public BenchmarkActionValueTask(object? instance, MethodInfo? method, int unrollFactor) + { + if (method == null) + { + callback = CreateWorkloadOrOverhead(instance, method); + } + else + { + startTaskCallback = CreateWorkload>(instance, method); + callback = ExecuteBlocking; + } unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = callback; InvokeUnroll = WorkloadActionUnroll; InvokeNoUnroll = WorkloadActionNoUnroll; } - private T Overhead() => default; - - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); - - private void InvokeSingleHardcoded() => result = callback(); + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + unrolledCallback(); + } } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = callback(); + { + callback(); + } } - - public override object LastRunResult => result; } internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; - private T result; + private readonly Action callback; + private readonly Action unrolledCallback; - public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) + public BenchmarkActionValueTask(object? instance, MethodInfo? method, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + if (method == null) { - startTaskCallback = CreateWorkload>>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkloadOrOverhead(instance, method); } else { - callback = Overhead; + startTaskCallback = CreateWorkload>>(instance, method); + callback = ExecuteBlocking; } - - InvokeSingle = InvokeSingleHardcoded; - - unrolledCallback = Unroll(callback, unrollFactor); + InvokeSingle = callback; InvokeUnroll = WorkloadActionUnroll; InvokeNoUnroll = WorkloadActionNoUnroll; } - private T Overhead() => default; - - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); - - private void InvokeSingleHardcoded() => result = callback(); + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke()); [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + unrolledCallback(); + } } [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = callback(); + { + callback(); + } } - - public override object LastRunResult => result; } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/InProcessNoEmitRunner.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/InProcessNoEmitRunner.cs index 00aa407778..2d113a4e5a 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/InProcessNoEmitRunner.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/InProcessNoEmitRunner.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using BenchmarkDotNet.Toolchains.Parameters; +using BenchmarkDotNet.Validators; using JetBrains.Annotations; namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit @@ -127,6 +128,10 @@ public static void RunCore(IHost host, ExecuteParameters parameters) host.WriteLine("// Job: {0}", job.DisplayInfo); host.WriteLine(); + var errors = BenchmarkProcessValidator.Validate(job, instance); + if (ValidationErrorReporter.ReportIfAny(errors, host)) + return; + var compositeInProcessDiagnoserHandler = new Diagnosers.CompositeInProcessDiagnoserHandler( parameters.CompositeInProcessDiagnoser.InProcessDiagnosers .Select((d, i) => Diagnosers.InProcessDiagnoserRouter.Create(d, benchmarkCase, i)) diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs index f2e56edd38..1e8ab24722 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -42,19 +43,32 @@ public InProcessTest(ITestOutputHelper output) : base(output) public void BenchmarkActionVoidSupported() => TestInvoke(x => x.InvokeOnceVoid(), UnrollFactor); [Fact] - public void BenchmarkActionTaskSupported() => TestInvoke(x => x.InvokeOnceTaskAsync(), UnrollFactor, null); + public void BenchmarkActionTaskSupported() => TestInvoke(x => x.InvokeOnceTaskAsync(), UnrollFactor); [Fact] - public void BenchmarkActionRefTypeSupported() => TestInvoke(x => x.InvokeOnceRefType(), UnrollFactor, StringResult); + public void BenchmarkActionValueTaskSupported() => TestInvoke(x => x.InvokeOnceValueTaskAsync(), UnrollFactor); [Fact] - public void BenchmarkActionValueTypeSupported() => TestInvoke(x => x.InvokeOnceValueType(), UnrollFactor, DecimalResult); + public void BenchmarkActionRefTypeSupported() => TestInvoke(x => x.InvokeOnceRefType(), UnrollFactor); [Fact] - public void BenchmarkActionTaskOfTSupported() => TestInvoke(x => x.InvokeOnceTaskOfTAsync(), UnrollFactor, StringResult); + public void BenchmarkActionValueTypeSupported() => TestInvoke(x => x.InvokeOnceValueType(), UnrollFactor); [Fact] - public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor, DecimalResult); + public void BenchmarkActionTaskOfTSupported() => TestInvoke(x => x.InvokeOnceTaskOfTAsync(), UnrollFactor); + + [Fact] + public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor); + + [Fact] + public unsafe void BenchmarkActionVoidPointerSupported() => TestInvoke(x => x.InvokeOnceVoidPointerType(), UnrollFactor); + + // Can't use ref returns in expression, so pass the MethodInfo directly instead. + [Fact] + public void BenchmarkActionByRefTypeSupported() => TestInvoke(typeof(BenchmarkAllCases).GetMethod(nameof(BenchmarkAllCases.InvokeOnceByRefType)), UnrollFactor); + + [Fact] + public void BenchmarkActionByRefReadonlyValueTypeSupported() => TestInvoke(typeof(BenchmarkAllCases).GetMethod(nameof(BenchmarkAllCases.InvokeOnceByRefReadonlyType)), UnrollFactor); [Fact] public void BenchmarkDifferentPlatformReturnsValidationError() @@ -82,65 +96,49 @@ private void TestInvoke(Expression> methodCall, int un // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, false, null); + TestInvoke(action, unrollFactor, false); // Idle mode action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); // GlobalSetup/GlobalCleanup action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false); // GlobalSetup/GlobalCleanup (empty) descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); } [AssertionMethod] - private void TestInvoke(Expression> methodCall, int unrollFactor, object expectedResult) + private void TestInvoke(Expression> methodCall, int unrollFactor) { var targetMethod = ((MethodCallExpression)methodCall.Body).Method; + TestInvoke(targetMethod, unrollFactor); + } + + [AssertionMethod] + private void TestInvoke(MethodInfo targetMethod, int unrollFactor) + { var descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, false, expectedResult); + TestInvoke(action, unrollFactor, false); // Idle mode - - bool isValueTask = typeof(T).IsConstructedGenericType && typeof(T).GetGenericTypeDefinition() == typeof(ValueTask<>); - - object? idleExpected; - if (isValueTask) - idleExpected = GetDefault(typeof(T).GetGenericArguments()[0]); - else if (typeof(T).GetTypeInfo().IsValueType) - idleExpected = 0; - else if (expectedResult == null || typeof(T) == typeof(Task)) - idleExpected = null; - else - idleExpected = GetDefault(expectedResult.GetType()); - action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, true, idleExpected); - } - - private static object GetDefault(Type type) - { - if (type.GetTypeInfo().IsValueType) - { - return Activator.CreateInstance(type); - } - return null; + TestInvoke(action, unrollFactor, true); } [AssertionMethod] - private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle, object expectedResult) + private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle) { try { @@ -164,8 +162,6 @@ private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool benchmarkAction.InvokeUnroll(11); Assert.Equal(BenchmarkAllCases.Counter, 1 + unrollFactor * 11); } - - Assert.Equal(benchmarkAction.LastRunResult, expectedResult); } finally { @@ -237,6 +233,13 @@ public async Task InvokeOnceTaskAsync() Interlocked.Increment(ref Counter); } + [Benchmark] + public async ValueTask InvokeOnceValueTaskAsync() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + [Benchmark] public string InvokeOnceRefType() { @@ -265,6 +268,51 @@ public ValueTask InvokeOnceValueTaskOfT() Interlocked.Increment(ref Counter); return new ValueTask(DecimalResult); } + + [Benchmark] + public ref int InvokeOnceByRefType() + { + Interlocked.Increment(ref Counter); + return ref Counter; + } + + [Benchmark] + public ref readonly int InvokeOnceByRefReadonlyType() + { + Interlocked.Increment(ref Counter); + return ref Counter; + } + + [Benchmark] + public unsafe void* InvokeOnceVoidPointerType() + { + Interlocked.Increment(ref Counter); + return default; + } + +#if NET9_0_OR_GREATER + [Benchmark] + public Span InvokeOnceRefStruct() + { + Interlocked.Increment(ref Counter); + return default; + } + + // This doesn't make much sense in practice, but the type system allows it, so we test it. + [Benchmark] + public ref Span InvokeOnceByRefRefStruct() + { + Interlocked.Increment(ref Counter); + return ref Unsafe.NullRef>(); + } + + [Benchmark] + public ref readonly Span InvokeOnceByRefReadonlyRefStruct() + { + Interlocked.Increment(ref Counter); + return ref Unsafe.NullRef>(); + } +#endif }