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
}