Skip to content

Commit 8a73c91

Browse files
authored
Revert "Use DynamicDependency (#2941)" (#2948)
This reverts commit 3cebcb9.
1 parent cb5d392 commit 8a73c91

File tree

7 files changed

+73
-105
lines changed

7 files changed

+73
-105
lines changed

src/BenchmarkDotNet/Code/CodeGenerator.cs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ internal static string Generate(BuildPartition buildPartition)
2727

2828
var benchmarksCode = new List<string>(buildPartition.Benchmarks.Length);
2929

30+
var extraDefines = new List<string>();
31+
3032
foreach (var buildInfo in buildPartition.Benchmarks)
3133
{
3234
var benchmark = buildInfo.BenchmarkCase;
@@ -62,11 +64,19 @@ internal static string Generate(BuildPartition buildPartition)
6264
benchmarksCode.Add(benchmarkTypeCode);
6365
}
6466

67+
if (buildPartition.IsNativeAot)
68+
extraDefines.Add("#define NATIVEAOT");
69+
else if (buildPartition.IsNetFramework)
70+
extraDefines.Add("#define NETFRAMEWORK");
71+
else if (buildPartition.IsWasm)
72+
extraDefines.Add("#define WASM");
73+
6574
string benchmarkProgramContent = new SmartStringBuilder(ResourceHelper.LoadTemplate("BenchmarkProgram.txt"))
6675
.Replace("$ShadowCopyDefines$", useShadowCopy ? "#define SHADOWCOPY" : null).Replace("$ShadowCopyFolderPath$", shadowCopyFolderPath)
67-
.Replace("$ExtraDefines$", buildPartition.IsNetFramework ? "#define NETFRAMEWORK" : string.Empty)
76+
.Replace("$ExtraDefines$", string.Join(Environment.NewLine, extraDefines))
6877
.Replace("$DerivedTypes$", string.Join(Environment.NewLine, benchmarksCode))
69-
.Replace("$ExtraAttribute$", GetExtraAttributes(buildPartition))
78+
.Replace("$ExtraAttribute$", GetExtraAttributes(buildPartition.RepresentativeBenchmarkCase.Descriptor))
79+
.Replace("$NativeAotSwitch$", GetNativeAotSwitch(buildPartition))
7080
.ToString();
7181

7282
return benchmarkProgramContent;
@@ -184,19 +194,8 @@ private static string GetPassArguments(BenchmarkCase benchmarkCase)
184194
benchmarkCase.Descriptor.WorkloadMethod.GetParameters()
185195
.Select((parameter, index) => $"{GetParameterModifier(parameter)} arg{index}"));
186196

187-
private static string GetExtraAttributes(BuildPartition buildPartition)
188-
{
189-
var attrs = new List<string>(buildPartition.Benchmarks.Length + 1);
190-
if (buildPartition.RepresentativeBenchmarkCase.Descriptor.WorkloadMethod.GetCustomAttributes(false).OfType<STAThreadAttribute>().Any())
191-
{
192-
attrs.Add("[global::System.STAThread]");
193-
}
194-
foreach (var buildInfo in buildPartition.Benchmarks)
195-
{
196-
attrs.Add($"[global::System.Diagnostics.CodeAnalysis.DynamicDependency(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, typeof(global::BenchmarkDotNet.Autogenerated.Runnable_{buildInfo.Id.Value}))]");
197-
}
198-
return string.Join(Environment.NewLine, attrs);
199-
}
197+
private static string GetExtraAttributes(Descriptor descriptor)
198+
=> descriptor.WorkloadMethod.GetCustomAttributes(false).OfType<STAThreadAttribute>().Any() ? "[System.STAThreadAttribute]" : string.Empty;
200199

201200
private static string GetEngineFactoryTypeName(BenchmarkCase benchmarkCase)
202201
{
@@ -252,6 +251,26 @@ private static string GetParameterModifier(ParameterInfo parameterInfo)
252251
return "ref";
253252
}
254253

254+
/// <summary>
255+
/// for NativeAOT we can't use reflection to load type and run a method, so we simply generate a switch for all types..
256+
/// </summary>
257+
private static string GetNativeAotSwitch(BuildPartition buildPartition)
258+
{
259+
if (!buildPartition.IsNativeAot)
260+
return default;
261+
262+
var @switch = new StringBuilder(buildPartition.Benchmarks.Length * 30);
263+
@switch.AppendLine("switch (id) {");
264+
265+
foreach (var buildInfo in buildPartition.Benchmarks)
266+
@switch.AppendLine($"case {buildInfo.Id.Value}: BenchmarkDotNet.Autogenerated.Runnable_{buildInfo.Id.Value}.Run(host, benchmarkName, diagnoserRunMode); break;");
267+
268+
@switch.AppendLine("default: throw new System.NotSupportedException(\"invalid benchmark id\");");
269+
@switch.AppendLine("}");
270+
271+
return @switch.ToString();
272+
}
273+
255274
private static Type GetFieldType(Type argumentType, ParameterInstance argument)
256275
{
257276
// #774 we can't store Span in a field, so we store an array (which is later casted to Span when we load the arguments)

src/BenchmarkDotNet/Running/BuildPartition.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using BenchmarkDotNet.Toolchains;
1313
using BenchmarkDotNet.Toolchains.CsProj;
1414
using BenchmarkDotNet.Toolchains.DotNetCli;
15+
using BenchmarkDotNet.Toolchains.MonoWasm;
1516
using BenchmarkDotNet.Toolchains.Roslyn;
1617
using JetBrains.Annotations;
1718

@@ -56,6 +57,9 @@ public BuildPartition(BenchmarkBuildInfo[] benchmarks, IResolver resolver)
5657

5758
public bool IsNativeAot => RepresentativeBenchmarkCase.Job.IsNativeAOT();
5859

60+
public bool IsWasm => Runtime is WasmRuntime // given job can have Wasm toolchain set, but Runtime == default ;)
61+
|| (RepresentativeBenchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) && toolchain is WasmToolchain);
62+
5963
public bool IsNetFramework => Runtime is ClrRuntime
6064
|| (RepresentativeBenchmarkCase.Job.Infrastructure.TryGetToolchain(out var toolchain) && (toolchain is RoslynToolchain || toolchain is CsProjClassicNetToolchain));
6165

src/BenchmarkDotNet/Templates/BenchmarkProgram.txt

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,20 @@ $ExtraDefines$
44
// this file must not be importing any namespaces
55
// we should use full names everywhere to avoid any potential naming conflicts, example: #1007, #778
66

7-
#pragma warning disable CS0436 // Type conflicts with imported type
87
#if !NET7_0_OR_GREATER
98
namespace System.Diagnostics.CodeAnalysis
109
{
10+
/// <summary>
11+
/// Specifies that this constructor sets all required members for the current type,
12+
/// and callers do not need to set any required members themselves.
13+
/// </summary>
1114
[global::System.AttributeUsage(global::System.AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
1215
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1316
internal sealed class SetsRequiredMembersAttribute : global::System.Attribute
1417
{
1518
}
1619
}
1720
#endif
18-
#if !NET5_0_OR_GREATER
19-
namespace System.Diagnostics.CodeAnalysis
20-
{
21-
[global::System.Flags]
22-
internal enum DynamicallyAccessedMemberTypes
23-
{
24-
None = 0,
25-
PublicParameterlessConstructor = 0x0001,
26-
PublicConstructors = 0x0002 | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor,
27-
NonPublicConstructors = 0x0004,
28-
PublicMethods = 0x0008,
29-
NonPublicMethods = 0x0010,
30-
PublicFields = 0x0020,
31-
NonPublicFields = 0x0040,
32-
PublicNestedTypes = 0x0080,
33-
NonPublicNestedTypes = 0x0100,
34-
PublicProperties = 0x0200,
35-
NonPublicProperties = 0x0400,
36-
PublicEvents = 0x0800,
37-
NonPublicEvents = 0x1000,
38-
Interfaces = 0x2000,
39-
All = ~global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None
40-
}
41-
[global::System.AttributeUsage(global::System.AttributeTargets.Constructor | global::System.AttributeTargets.Field | global::System.AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
42-
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
43-
[global::System.Diagnostics.Conditional("MULTI_TARGETING_SUPPORT_ATTRIBUTES")]
44-
internal sealed class DynamicDependencyAttribute : global::System.Attribute
45-
{
46-
public DynamicDependencyAttribute(string memberSignature)
47-
{
48-
MemberSignature = memberSignature;
49-
}
50-
public DynamicDependencyAttribute(string memberSignature, global::System.Type type)
51-
{
52-
MemberSignature = memberSignature;
53-
Type = type;
54-
}
55-
public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName)
56-
{
57-
MemberSignature = memberSignature;
58-
TypeName = typeName;
59-
AssemblyName = assemblyName;
60-
}
61-
public DynamicDependencyAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes memberTypes, global::System.Type type)
62-
{
63-
MemberTypes = memberTypes;
64-
Type = type;
65-
}
66-
public DynamicDependencyAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName)
67-
{
68-
MemberTypes = memberTypes;
69-
TypeName = typeName;
70-
AssemblyName = assemblyName;
71-
}
72-
public string MemberSignature { get; }
73-
public global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes MemberTypes { get; }
74-
public global::System.Type Type { get; }
75-
public string TypeName { get; }
76-
public string AssemblyName { get; }
77-
public string Condition { get; set; }
78-
}
79-
}
80-
#endif
81-
#pragma warning restore CS0436 // Type conflicts with imported type
8221

8322
// the namespace name must be in sync with WindowsDisassembler.BuildArguments
8423
namespace BenchmarkDotNet.Autogenerated
@@ -99,7 +38,7 @@ namespace BenchmarkDotNet.Autogenerated
9938

10039
private static global::System.Int32 AfterAssemblyLoadingAttached(global::System.String[] args)
10140
{
102-
global::BenchmarkDotNet.Engines.IHost host;
41+
global::BenchmarkDotNet.Engines.IHost host; // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
10342
if (global::BenchmarkDotNet.Engines.AnonymousPipesHost.TryGetFileHandles(args, out global::System.String writeHandle, out global::System.String readHandle))
10443
host = new global::BenchmarkDotNet.Engines.AnonymousPipesHost(writeHandle, readHandle);
10544
else
@@ -115,18 +54,23 @@ namespace BenchmarkDotNet.Autogenerated
11554
// which could cause the jitting/assembly loading to happen before we do anything
11655
// we have some jitting diagnosers and we want them to catch all the informations!!
11756

57+
// this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
11858
global::System.String benchmarkName = global::System.Linq.Enumerable.FirstOrDefault(global::System.Linq.Enumerable.Skip(global::System.Linq.Enumerable.SkipWhile(args, arg => arg != "--benchmarkName"), 1)) ?? "not provided";
11959
global::BenchmarkDotNet.Diagnosers.RunMode diagnoserRunMode = (global::BenchmarkDotNet.Diagnosers.RunMode) global::System.Int32.Parse(global::System.Linq.Enumerable.FirstOrDefault(global::System.Linq.Enumerable.Skip(global::System.Linq.Enumerable.SkipWhile(args, arg => arg != "--diagnoserRunMode"), 1)) ?? "0");
12060
global::System.Int32 id = args.Length > 0
121-
? global::System.Int32.Parse(args[args.Length - 1])
61+
? global::System.Int32.Parse(args[args.Length - 1]) // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
12262
: 0; // used when re-using generated exe without BDN (typically to repro a bug)
12363

12464
if (args.Length == 0)
12565
{
12666
host.WriteLine("You have not specified benchmark id (an integer) so the first benchmark will be executed.");
12767
}
68+
#if NATIVEAOT
69+
$NativeAotSwitch$
70+
#else
12871
global::System.Type type = typeof(global::BenchmarkDotNet.Autogenerated.UniqueProgramName).Assembly.GetType($"BenchmarkDotNet.Autogenerated.Runnable_{id}");
12972
type.GetMethod("Run", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Static).Invoke(null, new global::System.Object[] { host, benchmarkName, diagnoserRunMode });
73+
#endif
13074
return 0;
13175
}
13276
catch (global::System.Exception oom) when (oom is global::System.OutOfMemoryException || oom is global::System.Reflection.TargetInvocationException reflection && reflection.InnerException is global::System.OutOfMemoryException)

src/BenchmarkDotNet/Templates/WasmLinkerDescription.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
<assembly fullname="System.Private.CoreLib">
33
<type fullname="System.IntPtr" />
44
</assembly>
5+
<assembly fullname="$PROGRAMNAME$" preserve="all" />
56
</linker>

src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
2929
if (((WasmRuntime)buildPartition.Runtime).Aot)
3030
{
3131
GenerateProjectFile(buildPartition, artifactsPaths, aot: true, logger);
32-
33-
var linkDescriptionFileName = "WasmLinkerDescription.xml";
34-
File.WriteAllText(Path.Combine(Path.GetDirectoryName(artifactsPaths.ProjectFilePath), linkDescriptionFileName), ResourceHelper.LoadTemplate(linkDescriptionFileName));
35-
} else
32+
GenerateLinkerDescriptionFile(artifactsPaths);
33+
}
34+
else
3635
{
3736
GenerateProjectFile(buildPartition, artifactsPaths, aot: false, logger);
3837
}
@@ -67,6 +66,16 @@ protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths
6766
GatherReferences(buildPartition, artifactsPaths, logger);
6867
}
6968

69+
protected void GenerateLinkerDescriptionFile(ArtifactsPaths artifactsPaths)
70+
{
71+
const string linkDescriptionFileName = "WasmLinkerDescription.xml";
72+
73+
var content = ResourceHelper.LoadTemplate(linkDescriptionFileName)
74+
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName);
75+
76+
File.WriteAllText(Path.Combine(Path.GetDirectoryName(artifactsPaths.ProjectFilePath)!, linkDescriptionFileName), content);
77+
}
78+
7079
protected override string GetExecutablePath(string binariesDirectoryPath, string programName) => Path.Combine(binariesDirectoryPath, "AppBundle", MainJS);
7180

7281
protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration)

tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,8 @@ public class NaiveRunnableEmitDiff
1313
private static readonly HashSet<string> IgnoredTypeNames = new HashSet<string>()
1414
{
1515
"BenchmarkDotNet.Autogenerated.UniqueProgramName",
16-
// not required to be used in the InProcess toolchains (it's already used in the host process)
17-
"BenchmarkDotNet.Autogenerated.DirtyAssemblyResolveHelper",
18-
// Polyfill types added for older runtimes.
19-
"System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute",
20-
"System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes",
21-
"System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute",
16+
"BenchmarkDotNet.Autogenerated.DirtyAssemblyResolveHelper", // not required to be used in the InProcess toolchains (it's already used in the host process)
17+
"System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", // Conditionally added in runtimes older than .Net 7.
2218
};
2319

2420
private static readonly HashSet<string> IgnoredAttributeTypeNames = new HashSet<string>()

tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using BenchmarkDotNet.Tests.Loggers;
1212
using BenchmarkDotNet.Tests.XUnit;
1313
using BenchmarkDotNet.Toolchains.DotNetCli;
14-
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
1514
using BenchmarkDotNet.Toolchains.MonoWasm;
1615
using Xunit;
1716
using Xunit.Abstractions;
@@ -28,11 +27,11 @@ namespace BenchmarkDotNet.IntegrationTests
2827
/// </summary>
2928
public class WasmTests(ITestOutputHelper output) : BenchmarkTestExecutor(output)
3029
{
31-
private ManualConfig GetConfig(MonoAotCompilerMode aotCompilerMode)
30+
private ManualConfig GetConfig()
3231
{
3332
var dotnetVersion = "net8.0";
3433
var logger = new OutputLogger(Output);
35-
var netCoreAppSettings = new NetCoreAppSettings(dotnetVersion, null, "Wasm", aotCompilerMode: aotCompilerMode);
34+
var netCoreAppSettings = new NetCoreAppSettings(dotnetVersion, null, "Wasm");
3635
var mainJsPath = Path.Combine(AppContext.BaseDirectory, "AppBundle", "test-main.js");
3736

3837
return ManualConfig.CreateEmpty()
@@ -45,24 +44,20 @@ private ManualConfig GetConfig(MonoAotCompilerMode aotCompilerMode)
4544
.WithOption(ConfigOptions.GenerateMSBuildBinLog, true);
4645
}
4746

48-
[TheoryEnvSpecific("WASM is only supported on Unix", EnvRequirement.NonWindows)]
49-
[InlineData(MonoAotCompilerMode.mini)]
50-
[InlineData(MonoAotCompilerMode.wasm)]
51-
public void WasmIsSupported(MonoAotCompilerMode aotCompilerMode)
47+
[FactEnvSpecific("WASM is only supported on Unix", EnvRequirement.NonWindows)]
48+
public void WasmIsSupported()
5249
{
5350
// Test fails on Linux non-x64.
5451
if (OsDetector.IsLinux() && RuntimeInformation.GetCurrentPlatform() != Platform.X64)
5552
{
5653
return;
5754
}
5855

59-
CanExecute<WasmBenchmark>(GetConfig(aotCompilerMode));
56+
CanExecute<WasmBenchmark>(GetConfig());
6057
}
6158

62-
[TheoryEnvSpecific("WASM is only supported on Unix", EnvRequirement.NonWindows)]
63-
[InlineData(MonoAotCompilerMode.mini)]
64-
[InlineData(MonoAotCompilerMode.wasm)]
65-
public void WasmSupportsInProcessDiagnosers(MonoAotCompilerMode aotCompilerMode)
59+
[FactEnvSpecific("WASM is only supported on Unix", EnvRequirement.NonWindows)]
60+
public void WasmSupportsInProcessDiagnosers()
6661
{
6762
// Test fails on Linux non-x64.
6863
if (OsDetector.IsLinux() && RuntimeInformation.GetCurrentPlatform() != Platform.X64)
@@ -73,7 +68,7 @@ public void WasmSupportsInProcessDiagnosers(MonoAotCompilerMode aotCompilerMode)
7368
try
7469
{
7570
var diagnoser = new MockInProcessDiagnoser1(BenchmarkDotNet.Diagnosers.RunMode.NoOverhead);
76-
var config = GetConfig(aotCompilerMode).AddDiagnoser(diagnoser);
71+
var config = GetConfig().AddDiagnoser(diagnoser);
7772

7873
CanExecute<WasmBenchmark>(config);
7974

0 commit comments

Comments
 (0)