Skip to content

Commit 545942a

Browse files
authored
Updated InProcessNoEmitToolchain to match behavior of other toolchains. (dotnet#2315)
Added support for pointer, ByRef, ValueTask, and ref struct returns. Added benchmark validation.
1 parent 80e68c1 commit 545942a

File tree

9 files changed

+368
-154
lines changed

9 files changed

+368
-154
lines changed

src/BenchmarkDotNet/BenchmarkDotNet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Import Project="..\..\build\common.props" />
33
<PropertyGroup>
44
<AssemblyTitle>BenchmarkDotNet</AssemblyTitle>
5-
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
5+
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0</TargetFrameworks>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
<NoWarn>$(NoWarn);1701;1702;1705;1591;3005;NU1702;CS3001;CS3003</NoWarn>
88
<AssemblyName>BenchmarkDotNet</AssemblyName>

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,7 @@ internal static bool IsStackOnlyWithImplicitCast(this Type argumentType, object?
211211
if (argumentInstance == null)
212212
return false;
213213

214-
// IsByRefLikeAttribute is not exposed for older runtimes, so we need to check it in an ugly way ;)
215-
bool isByRefLike = argumentType.GetCustomAttributes().Any(attribute => attribute.ToString()?.Contains("IsByRefLike") ?? false);
216-
if (!isByRefLike)
214+
if (!argumentType.IsByRefLike())
217215
return false;
218216

219217
var instanceType = argumentInstance.GetType();
@@ -235,5 +233,13 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo)
235233
&& typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it
236234

237235
internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0;
236+
237+
internal static bool IsByRefLike(this Type type)
238+
#if NETSTANDARD2_0
239+
// Type.IsByRefLike is not available in netstandard2.0.
240+
=> type.IsValueType && type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute");
241+
#else
242+
=> type.IsByRefLike;
243+
#endif
238244
}
239245
}

src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ protected void GatherReferences(string projectFilePath, BuildPartition buildPart
150150
// TODO: Add Aliases here for extern alias #2289
151151
}
152152

153+
// Mono80IsSupported test fails when BenchmarkDotNet is restored for net9.0 if we don't remove the ProjectReference.
154+
if (XUnitHelper.IsIntegrationTest.Value)
155+
{
156+
projectElement.RemoveChild(projectElement.SelectSingleNode("ItemGroup/ProjectReference").ParentNode);
157+
}
158+
153159
xmlDoc.Save(artifactsPaths.ProjectFilePath);
154160

155161
BuildResult BuildProject(string tfm)
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
using System;
22

3-
using JetBrains.Annotations;
4-
53
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
64
{
75
/// <summary>Common API to run the Setup/Clean/Idle/Run methods</summary>
8-
[PublicAPI]
9-
public abstract class BenchmarkAction
6+
internal abstract class BenchmarkAction
107
{
118
/// <summary>Gets or sets invoke single callback.</summary>
129
/// <value>Invoke single callback.</value>
@@ -16,9 +13,5 @@ public abstract class BenchmarkAction
1613
/// <value>Invoke multiple times callback.</value>
1714
public Action<long> InvokeUnroll { get; protected set; }
1815
public Action<long> InvokeNoUnroll{ get; protected set; }
19-
20-
/// <summary>Gets the last run result.</summary>
21-
/// <value>The last run result.</value>
22-
public virtual object LastRunResult => null;
2316
}
2417
}

src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Reflection;
34
using System.Runtime.CompilerServices;
45
using System.Threading.Tasks;
@@ -9,27 +10,47 @@
910
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
1011
{
1112
/// <summary>Helper class that creates <see cref="BenchmarkAction"/> instances. </summary>
12-
public static partial class BenchmarkActionFactory
13+
internal static partial class BenchmarkActionFactory
1314
{
1415
/// <summary>
1516
/// Dispatch method that creates <see cref="BenchmarkAction"/> using
1617
/// <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> to find correct implementation.
1718
/// Either <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> should be not <c>null</c>.
1819
/// </summary>
19-
private static BenchmarkAction CreateCore(
20-
object instance,
21-
MethodInfo? targetMethod,
22-
MethodInfo? fallbackIdleSignature,
23-
int unrollFactor)
20+
private static BenchmarkAction CreateCore(object instance, MethodInfo? targetMethod, MethodInfo? fallbackIdleSignature, int unrollFactor)
2421
{
2522
PrepareInstanceAndResultType(instance, targetMethod, fallbackIdleSignature, out var resultInstance, out var resultType);
2623

2724
if (resultType == typeof(void))
2825
return new BenchmarkActionVoid(resultInstance, targetMethod, unrollFactor);
2926

27+
if (resultType == typeof(void*))
28+
return new BenchmarkActionVoidPointer(resultInstance, targetMethod, unrollFactor);
29+
30+
if (resultType.IsByRef)
31+
{
32+
var returnParameter = targetMethod?.ReturnParameter ?? fallbackIdleSignature.ReturnParameter;
33+
// IsReadOnlyAttribute is not part of netstandard2.0, so we need to check the attribute name as usual.
34+
if (returnParameter.GetCustomAttributes().Any(attribute => attribute.GetType().FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute"))
35+
return Create(
36+
typeof(BenchmarkActionByRefReadonly<>).MakeGenericType(resultType.GetElementType()),
37+
resultInstance,
38+
targetMethod,
39+
unrollFactor);
40+
41+
return Create(
42+
typeof(BenchmarkActionByRef<>).MakeGenericType(resultType.GetElementType()),
43+
resultInstance,
44+
targetMethod,
45+
unrollFactor);
46+
}
47+
3048
if (resultType == typeof(Task))
3149
return new BenchmarkActionTask(resultInstance, targetMethod, unrollFactor);
3250

51+
if (resultType == typeof(ValueTask))
52+
return new BenchmarkActionValueTask(resultInstance, targetMethod, unrollFactor);
53+
3354
if (resultType.GetTypeInfo().IsGenericType)
3455
{
3556
var genericType = resultType.GetGenericTypeDefinition();
@@ -49,10 +70,6 @@ private static BenchmarkAction CreateCore(
4970
unrollFactor);
5071
}
5172

52-
if (targetMethod == null && resultType.GetTypeInfo().IsValueType)
53-
// for Idle: we return int because creating bigger ValueType could take longer than benchmarked method itself.
54-
resultType = typeof(int);
55-
5673
return Create(
5774
typeof(BenchmarkAction<>).MakeGenericType(resultType),
5875
resultInstance,
@@ -86,6 +103,10 @@ private static void PrepareInstanceAndResultType(
86103
if (isUsingAsyncKeyword)
87104
throw new NotSupportedException("Async void is not supported by design.");
88105
}
106+
else if (resultType.IsPointer && resultType != typeof(void*))
107+
{
108+
throw new NotSupportedException("InProcessNoEmitToolchain only supports void* return, not T*");
109+
}
89110
}
90111

91112
/// <summary>Helper to enforce .ctor signature.</summary>
@@ -109,7 +130,7 @@ public static BenchmarkAction CreateWorkload(Descriptor descriptor, object insta
109130
/// <param name="unrollFactor">Unroll factor.</param>
110131
/// <returns>Idle benchmark action.</returns>
111132
public static BenchmarkAction CreateOverhead(Descriptor descriptor, object instance, int unrollFactor) =>
112-
CreateCore(instance, null, descriptor.WorkloadMethod, unrollFactor);
133+
CreateCore(instance, null, FallbackSignature, unrollFactor);
113134

114135
/// <summary>Creates global setup benchmark action.</summary>
115136
/// <param name="descriptor">Descriptor info.</param>

src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ 5. Implementation should match to the code in BenchmarkProgram.txt.
2020

2121
// DONTTOUCH: Be VERY CAREFUL when changing the code.
2222
// Please, ensure that the implementation is in sync with content of BenchmarkProgram.txt
23-
24-
/// <summary>Helper class that creates <see cref="BenchmarkAction"/> instances. </summary>
25-
public static partial class BenchmarkActionFactory
23+
internal static partial class BenchmarkActionFactory
2624
{
2725
/// <summary>Base class that provides reusable API for final implementations.</summary>
2826
internal abstract class BenchmarkActionBase : BenchmarkAction
@@ -35,19 +33,15 @@ protected static TDelegate CreateWorkload<TDelegate>(object? targetInstance, Met
3533
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance);
3634
}
3735

38-
protected static TDelegate CreateWorkloadOrOverhead<TDelegate>(
39-
object? targetInstance,
40-
MethodInfo? workloadMethod,
41-
TDelegate overheadStaticCallback,
42-
TDelegate overheadInstanceCallback) where TDelegate : notnull
36+
protected Action CreateWorkloadOrOverhead(object? instance, MethodInfo? method)
4337
{
44-
if (workloadMethod == null)
45-
return targetInstance == null ? overheadStaticCallback : overheadInstanceCallback;
46-
47-
if (workloadMethod.IsStatic)
48-
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate));
49-
50-
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance);
38+
if (method == null)
39+
{
40+
return instance == null ? OverheadStatic : OverheadInstance;
41+
}
42+
return method.IsStatic
43+
? (Action) method.CreateDelegate(typeof(Action))
44+
: (Action) method.CreateDelegate(typeof(Action), instance);
5145
}
5246

5347
protected static TDelegate Unroll<TDelegate>(TDelegate callback, int unrollFactor)
@@ -61,6 +55,11 @@ protected static TDelegate Unroll<TDelegate>(TDelegate callback, int unrollFacto
6155
return (TDelegate)(object)Delegate.Combine(
6256
Enumerable.Repeat((Delegate)(object)callback, unrollFactor).ToArray());
6357
}
58+
59+
// must be kept in sync with VoidDeclarationsProvider.IdleImplementation
60+
private static void OverheadStatic() { }
61+
62+
private void OverheadInstance() { }
6463
}
6564
}
6665
}

0 commit comments

Comments
 (0)