diff --git a/BenchmarkDotNet.sln b/BenchmarkDotNet.sln index 1df6c0aabd..186fecb04b 100644 --- a/BenchmarkDotNet.sln +++ b/BenchmarkDotNet.sln @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.Plotting.Tests", "tests\BenchmarkDotNet.Exporters.Plotting.Tests\BenchmarkDotNet.Exporters.Plotting.Tests.csproj", "{199AC83E-30BD-40CD-87CE-0C838AC0320D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Weaver", "src\BenchmarkDotNet.Weaver\BenchmarkDotNet.Weaver.csproj", "{5731DE42-16FE-430E-BA90-0EBE714CB221}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -161,6 +163,10 @@ Global {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Debug|Any CPU.Build.0 = Debug|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.ActiveCfg = Release|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.Build.0 = Release|Any CPU + {5731DE42-16FE-430E-BA90-0EBE714CB221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5731DE42-16FE-430E-BA90-0EBE714CB221}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5731DE42-16FE-430E-BA90-0EBE714CB221}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5731DE42-16FE-430E-BA90-0EBE714CB221}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -190,6 +196,7 @@ Global {2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {B92ECCEF-7C27-4012-9E19-679F3C40A6A6} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {199AC83E-30BD-40CD-87CE-0C838AC0320D} = {14195214-591A-45B7-851A-19D3BA2413F9} + {5731DE42-16FE-430E-BA90-0EBE714CB221} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F} diff --git a/NuGet.Config b/NuGet.Config index 7507704b8b..ca58921f13 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -8,11 +8,13 @@ - + + + diff --git a/build/BenchmarkDotNet.Build/Program.cs b/build/BenchmarkDotNet.Build/Program.cs index 51dd875fbd..a0886b6ba6 100644 --- a/build/BenchmarkDotNet.Build/Program.cs +++ b/build/BenchmarkDotNet.Build/Program.cs @@ -16,8 +16,29 @@ public static int Main(string[] args) } } +[TaskName(Name)] +[TaskDescription("Pack Weaver")] +public class PackWeaverTask : FrostingTask, IHelpProvider +{ + private const string Name = "pack-weaver"; + + public override void Run(BuildContext context) => context.BuildRunner.PackWeaver(); + + public HelpInfo GetHelp() + { + return new HelpInfo + { + Examples = new[] + { + new Example(Name) + } + }; + } +} + [TaskName(Name)] [TaskDescription("Restore NuGet packages")] +[IsDependentOn(typeof(PackWeaverTask))] public class RestoreTask : FrostingTask, IHelpProvider { private const string Name = "restore"; diff --git a/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs b/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs index a38ce7e79d..4569110a2c 100644 --- a/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs +++ b/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs @@ -8,6 +8,7 @@ using Cake.Common.Tools.DotNet.Workload.Install; using Cake.Core; using Cake.Core.IO; +using System.Linq; namespace BenchmarkDotNet.Build.Runners; @@ -20,6 +21,34 @@ public BuildRunner(BuildContext context) this.context = context; } + public void PackWeaver() + { + var weaverPath = context.AllPackableSrcProjects.Single(p => p.GetFilename() == "BenchmarkDotNet.Weaver.csproj"); + + context.DotNetRestore(weaverPath.GetDirectory().FullPath, + new DotNetRestoreSettings + { + MSBuildSettings = context.MsBuildSettingsRestore + }); + + context.Information("BuildSystemProvider: " + context.BuildSystem().Provider); + context.DotNetBuild(weaverPath.FullPath, new DotNetBuildSettings + { + NoRestore = true, + DiagnosticOutput = true, + MSBuildSettings = context.MsBuildSettingsBuild, + Configuration = context.BuildConfiguration, + Verbosity = context.BuildVerbosity + }); + + context.DotNetPack(weaverPath.FullPath, new DotNetPackSettings + { + OutputDirectory = weaverPath.GetDirectory().Combine("packages"), + MSBuildSettings = context.MsBuildSettingsPack, + Configuration = context.BuildConfiguration + }); + } + public void Restore() { context.DotNetRestore(context.SolutionFile.FullPath, @@ -71,7 +100,7 @@ public void Pack() var settingsSrc = new DotNetPackSettings { OutputDirectory = context.ArtifactsDirectory, - ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg"), + ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg").Append("-p:IsFullPack=true"), MSBuildSettings = context.MsBuildSettingsPack, Configuration = context.BuildConfiguration }; diff --git a/build/common.props b/build/common.props index 972f3ea9f6..c1498bc380 100644 --- a/build/common.props +++ b/build/common.props @@ -90,4 +90,9 @@ + + + + -1 + diff --git a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj index 457b660115..368a9f6162 100644 --- a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj +++ b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj @@ -3,6 +3,7 @@ true + Exe net462;net8.0 diff --git a/samples/BenchmarkDotNet.Samples/IntroSmokeStringBuilder.cs b/samples/BenchmarkDotNet.Samples/IntroSmokeStringBuilder.cs new file mode 100644 index 0000000000..65b9355095 --- /dev/null +++ b/samples/BenchmarkDotNet.Samples/IntroSmokeStringBuilder.cs @@ -0,0 +1,34 @@ +using BenchmarkDotNet.Attributes; +using System.Text; + +namespace BenchmarkDotNet.Samples +{ + [MemoryDiagnoser(false)] + public class IntroSmokeStringBuilder + { + [Benchmark] + [Arguments(1)] + [Arguments(1_000)] + public StringBuilder Append_Strings(int repeat) + { + StringBuilder builder = new StringBuilder(); + + // strings are not sorted by length to mimic real input + for (int i = 0; i < repeat; i++) + { + builder.Append("12345"); + builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"); + builder.Append("1234567890abcdefghijklmnopqrstuvwxy"); + builder.Append("1234567890"); + builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHI"); + builder.Append("1234567890abcde"); + builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCD"); + builder.Append("1234567890abcdefghijklmnopqrst"); + builder.Append("1234567890abcdefghij"); + builder.Append("1234567890abcdefghijklmno"); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index ccc51bcd9a..c28950ab9a 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -14,4 +14,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Weaver/.gitignore b/src/BenchmarkDotNet.Weaver/.gitignore new file mode 100644 index 0000000000..ddb9a15db5 --- /dev/null +++ b/src/BenchmarkDotNet.Weaver/.gitignore @@ -0,0 +1 @@ +!packages/ \ No newline at end of file diff --git a/src/BenchmarkDotNet.Weaver/BenchmarkDotNet.Weaver.csproj b/src/BenchmarkDotNet.Weaver/BenchmarkDotNet.Weaver.csproj new file mode 100644 index 0000000000..00c5f2f1c5 --- /dev/null +++ b/src/BenchmarkDotNet.Weaver/BenchmarkDotNet.Weaver.csproj @@ -0,0 +1,27 @@ + + + + + + netstandard2.0 + $(VersionSuffix)$(WeaverVersionSuffix) + $(MSBuildThisFileDirectory)bin\$(Configuration) + true + false + $(NoWarn);NU5100;NU5128 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Weaver/buildTransitive/netstandard2.0/BenchmarkDotNet.Weaver.targets b/src/BenchmarkDotNet.Weaver/buildTransitive/netstandard2.0/BenchmarkDotNet.Weaver.targets new file mode 100644 index 0000000000..79c1aaa2a5 --- /dev/null +++ b/src/BenchmarkDotNet.Weaver/buildTransitive/netstandard2.0/BenchmarkDotNet.Weaver.targets @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Weaver/packages/BenchmarkDotNet.Weaver.0.14.1-develop-1.nupkg b/src/BenchmarkDotNet.Weaver/packages/BenchmarkDotNet.Weaver.0.14.1-develop-1.nupkg new file mode 100644 index 0000000000..f652b4bca6 Binary files /dev/null and b/src/BenchmarkDotNet.Weaver/packages/BenchmarkDotNet.Weaver.0.14.1-develop-1.nupkg differ diff --git a/src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs b/src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs new file mode 100644 index 0000000000..765faff0f1 --- /dev/null +++ b/src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.Cecil; + +namespace BenchmarkDotNet.Weaver; + +internal class CustomAssemblyResolver : DefaultAssemblyResolver +{ + public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + // NetStandard causes StackOverflow. https://github.com/jbevain/cecil/issues/573 + // Mscorlib fails to resolve in Visual Studio. https://github.com/jbevain/cecil/issues/966 + // We don't care about any types from runtime assemblies anyway, so just skip resolving them. + => name.Name is "netstandard" or "mscorlib" or "System.Runtime" or "System.Private.CoreLib" + ? null + : base.Resolve(name, parameters); +} + +/// +/// The Task used by MSBuild to weave the assembly. +/// +public sealed class WeaveAssemblyTask : Task +{ + /// + /// The directory of the output. + /// + [Required] + public string TargetDir { get; set; } + + /// + /// The path of the target assembly. + /// + [Required] + public string TargetAssembly { get; set; } + + /// + /// Runs the weave assembly task. + /// + /// if successful; otherwise. + public override bool Execute() + { + if (!File.Exists(TargetAssembly)) + { + Log.LogError($"Assembly not found: {TargetAssembly}"); + return false; + } + + var resolver = new CustomAssemblyResolver(); + resolver.AddSearchDirectory(TargetDir); + + // ReaderParameters { ReadWrite = true } is necessary to later write the file. + // https://stackoverflow.com/questions/41840455/locked-target-assembly-with-mono-cecil-and-pcl-code-injection + var readerParameters = new ReaderParameters + { + ReadWrite = true, + AssemblyResolver = resolver + }; + + bool benchmarkMethodsImplAdjusted = false; + try + { + using var module = ModuleDefinition.ReadModule(TargetAssembly, readerParameters); + + foreach (var type in module.Types) + { + ProcessType(type, ref benchmarkMethodsImplAdjusted); + } + + // Write the modified assembly to file. + module.Write(); + } + catch (Exception e) + { + if (benchmarkMethodsImplAdjusted) + { + Log.LogWarning($"Benchmark methods were found that require NoInlining, and assembly weaving failed.{Environment.NewLine}{e}"); + } + } + return true; + } + + private static void ProcessType(TypeDefinition type, ref bool benchmarkMethodsImplAdjusted) + { + // We can skip non-public types as they are not valid for benchmarks. + if (type.IsNotPublic) + { + return; + } + + // Remove AggressiveInlining and add NoInlining to all [Benchmark] methods. + foreach (var method in type.Methods) + { + if (method.CustomAttributes.Any(IsBenchmarkAttribute)) + { + var oldImpl = method.ImplAttributes; + method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining; + benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0; + } + } + + // Recursively process nested types + foreach (var nestedType in type.NestedTypes) + { + ProcessType(nestedType, ref benchmarkMethodsImplAdjusted); + } + } + + private static bool IsBenchmarkAttribute(CustomAttribute attribute) + { + // BenchmarkAttribute is unsealed, so we need to walk its hierarchy. + for (var attr = attribute.AttributeType; attr != null; attr = attr.Resolve()?.BaseType) + { + if (attr.FullName == "BenchmarkDotNet.Attributes.BenchmarkAttribute") + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs index 005564b77c..bd5705c6c3 100644 --- a/src/BenchmarkDotNet/Code/CodeGenerator.cs +++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs @@ -37,24 +37,16 @@ internal static string Generate(BuildPartition buildPartition) string passArguments = GetPassArguments(benchmark); - string compilationId = $"{provider.ReturnsDefinition}_{buildInfo.Id}"; - AddNonEmptyUnique(additionalLogic, benchmark.Descriptor.AdditionalLogic); string benchmarkTypeCode = new SmartStringBuilder(ResourceHelper.LoadTemplate("BenchmarkType.txt")) .Replace("$ID$", buildInfo.Id.ToString()) .Replace("$OperationsPerInvoke$", provider.OperationsPerInvoke) .Replace("$WorkloadTypeName$", provider.WorkloadTypeName) - .Replace("$WorkloadMethodDelegate$", provider.WorkloadMethodDelegate(passArguments)) - .Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName) - .Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers) - .Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName) .Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName) .Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName) .Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName) .Replace("$IterationCleanupMethodName$", provider.IterationCleanupMethodName) - .Replace("$OverheadImplementation$", provider.OverheadImplementation) - .Replace("$ConsumeField$", provider.ConsumeField) .Replace("$JobSetDefinition$", GetJobsSetDefinition(benchmark)) .Replace("$ParamsContent$", GetParamsContent(benchmark)) .Replace("$ArgumentsDefinition$", GetArgumentsDefinition(benchmark)) @@ -65,7 +57,7 @@ internal static string Generate(BuildPartition buildPartition) .Replace("$MeasureExtraStats$", buildInfo.Config.HasExtraStatsDiagnoser() ? "true" : "false") .Replace("$DisassemblerEntryMethodName$", DisassemblerConstants.DisassemblerEntryMethodName) .Replace("$WorkloadMethodCall$", provider.GetWorkloadMethodCall(passArguments)) - .RemoveRedundantIfDefines(compilationId); + .ToString(); benchmarkTypeCode = Unroll(benchmarkTypeCode, benchmark.Job.ResolveValue(RunMode.UnrollFactorCharacteristic, EnvironmentResolver.Instance)); @@ -154,36 +146,21 @@ private static DeclarationsProvider GetDeclarationsProvider(Descriptor descripto if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(ValueTask)) { - return new TaskDeclarationsProvider(descriptor); + return new AsyncDeclarationsProvider(descriptor); } if (method.ReturnType.GetTypeInfo().IsGenericType && (method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) || method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))) { - return new GenericTaskDeclarationsProvider(descriptor); - } - - if (method.ReturnType == typeof(void)) - { - bool isUsingAsyncKeyword = method.HasAttribute(); - if (isUsingAsyncKeyword) - { - throw new NotSupportedException("async void is not supported by design"); - } - - return new VoidDeclarationsProvider(descriptor); + return new AsyncDeclarationsProvider(descriptor); } - if (method.ReturnType.IsByRef) + if (method.ReturnType == typeof(void) && method.HasAttribute()) { - // System.Runtime.CompilerServices.IsReadOnlyAttribute is part of .NET Standard 2.1, we can't use it here.. - if (method.ReturnParameter.GetCustomAttributes().Any(attribute => attribute.GetType().Name == "IsReadOnlyAttribute")) - return new ByReadOnlyRefDeclarationsProvider(descriptor); - else - return new ByRefDeclarationsProvider(descriptor); + throw new NotSupportedException("async void is not supported by design"); } - return new NonVoidDeclarationsProvider(descriptor); + return new SyncDeclarationsProvider(descriptor); } // internal for tests @@ -305,31 +282,6 @@ public SmartStringBuilder Replace(string oldValue, string? newValue) return this; } - public string RemoveRedundantIfDefines(string id) - { - var oldLines = builder.ToString().Split('\n'); - var newLines = new List(); - bool keepAdding = true; - - foreach (string line in oldLines) - { - if (line.StartsWith("#if RETURNS") || line.StartsWith("#elif RETURNS")) - { - keepAdding = line.Contains(id); - } - else if (line.StartsWith("#endif // RETURNS")) - { - keepAdding = true; - } - else if (keepAdding) - { - newLines.Add(line); - } - } - - return string.Join("\n", newLines); - } - public override string ToString() => builder.ToString(); } } diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index 7528e8ed62..0c9a8dce46 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -1,10 +1,6 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; using System.Threading.Tasks; -using BenchmarkDotNet.Engines; using BenchmarkDotNet.Extensions; -using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Running; namespace BenchmarkDotNet.Code @@ -30,26 +26,8 @@ internal abstract class DeclarationsProvider public string IterationCleanupMethodName => Descriptor.IterationCleanupMethod?.Name ?? EmptyAction; - public abstract string ReturnsDefinition { get; } - - protected virtual Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType; - - public virtual string WorkloadMethodReturnTypeName => WorkloadMethodReturnType.GetCorrectCSharpTypeName(); - - public virtual string WorkloadMethodDelegate(string passArguments) => Descriptor.WorkloadMethod.Name; - - public virtual string WorkloadMethodReturnTypeModifiers => null; - public virtual string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments})"; - public virtual string ConsumeField => null; - - protected abstract Type OverheadMethodReturnType { get; } - - public string OverheadMethodReturnTypeName => OverheadMethodReturnType.GetCorrectCSharpTypeName(); - - public abstract string OverheadImplementation { get; } - private string GetMethodName(MethodInfo method) { if (method == null) @@ -70,104 +48,14 @@ private string GetMethodName(MethodInfo method) } } - internal class VoidDeclarationsProvider : DeclarationsProvider - { - public VoidDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - public override string ReturnsDefinition => "RETURNS_VOID"; - - protected override Type OverheadMethodReturnType => typeof(void); - - public override string OverheadImplementation => string.Empty; - } - - internal class NonVoidDeclarationsProvider : DeclarationsProvider - { - public NonVoidDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - public override string ConsumeField - => !Consumer.IsConsumable(WorkloadMethodReturnType) && Consumer.HasConsumableField(WorkloadMethodReturnType, out var field) - ? $".{field.Name}" - : null; - - protected override Type OverheadMethodReturnType - => Consumer.IsConsumable(WorkloadMethodReturnType) - ? WorkloadMethodReturnType - : (Consumer.HasConsumableField(WorkloadMethodReturnType, out var field) - ? field.FieldType - : typeof(int)); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself - - public override string OverheadImplementation - { - get - { - string value; - var type = OverheadMethodReturnType; - if (type.GetTypeInfo().IsPrimitive) - value = $"default({type.GetCorrectCSharpTypeName()})"; - else if (type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface) - value = "null"; - else - value = SourceCodeHelper.ToSourceCode(Activator.CreateInstance(type)) + ";"; - return $"return {value};"; - } - } - - public override string ReturnsDefinition - => Consumer.IsConsumable(WorkloadMethodReturnType) || Consumer.HasConsumableField(WorkloadMethodReturnType, out _) - ? "RETURNS_CONSUMABLE" - : "RETURNS_NON_CONSUMABLE_STRUCT"; - } - - internal class ByRefDeclarationsProvider : NonVoidDeclarationsProvider + internal class SyncDeclarationsProvider : DeclarationsProvider { - public ByRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - protected override Type OverheadMethodReturnType => typeof(IntPtr); - - public override string WorkloadMethodReturnTypeName => base.WorkloadMethodReturnTypeName.Replace("&", string.Empty); - - public override string ConsumeField => null; - - public override string OverheadImplementation => $"return default(System.{nameof(IntPtr)});"; - - public override string ReturnsDefinition => "RETURNS_BYREF"; - - public override string WorkloadMethodReturnTypeModifiers => "ref"; + public SyncDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } } - internal class ByReadOnlyRefDeclarationsProvider : ByRefDeclarationsProvider + internal class AsyncDeclarationsProvider : DeclarationsProvider { - public ByReadOnlyRefDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - public override string ReturnsDefinition => "RETURNS_BYREF_READONLY"; - - public override string WorkloadMethodReturnTypeModifiers => "ref readonly"; - } - - internal class TaskDeclarationsProvider : VoidDeclarationsProvider - { - public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; - - public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; - - protected override Type WorkloadMethodReturnType => typeof(void); - } - - /// - /// declarations provider for and - /// - internal class GenericTaskDeclarationsProvider : NonVoidDeclarationsProvider - { - public GenericTaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - - protected override Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType.GetTypeInfo().GetGenericArguments().Single(); - - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ return BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; + public AsyncDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; } diff --git a/src/BenchmarkDotNet/Engines/Consumer.cs b/src/BenchmarkDotNet/Engines/Consumer.cs index 55f8e3b040..5c687f20e6 100644 --- a/src/BenchmarkDotNet/Engines/Consumer.cs +++ b/src/BenchmarkDotNet/Engines/Consumer.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using JetBrains.Annotations; @@ -11,13 +8,6 @@ namespace BenchmarkDotNet.Engines { public class Consumer { - private static readonly HashSet SupportedTypes - = new HashSet( - typeof(Consumer).GetTypeInfo() - .DeclaredFields - .Where(field => !field.IsStatic) // exclude this HashSet itself - .Select(field => field.FieldType)); - #pragma warning disable IDE0052 // Remove unread private members private volatile byte byteHolder; private volatile sbyte sbyteHolder; @@ -153,28 +143,5 @@ public void Consume(in T value) else DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(value); // non-primitive and nullable value types } - - internal static bool IsConsumable(Type type) - => SupportedTypes.Contains(type) || type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface; - - internal static bool HasConsumableField(Type type, out FieldInfo? consumableField) - { - var typeInfo = type.GetTypeInfo(); - - if (typeInfo.IsEnum) - { - // Enums are tricky bastards which report "value__" field, which is public for reflection, but inaccessible via C# - consumableField = null; - return false; - } - - var publicInstanceFields = typeInfo.DeclaredFields - .Where(field => field.IsPublic && !field.IsStatic) - .ToArray(); - - consumableField = publicInstanceFields.FirstOrDefault(field => IsConsumable(field.FieldType)); - - return consumableField != null; - } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorCallExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorCallExtensions.cs index 6f9a794e01..214963dd78 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorCallExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorCallExtensions.cs @@ -7,25 +7,6 @@ namespace BenchmarkDotNet.Helpers.Reflection.Emit { internal static class IlGeneratorCallExtensions { - public static LocalBuilder DeclareOptionalLocalForInstanceCall( - this ILGenerator ilBuilder, - Type localType, - MethodInfo methodToCall) - { - if (methodToCall.DeclaringType == null) - throw new ArgumentException($"The {nameof(methodToCall)} should have non-null {nameof(methodToCall.DeclaringType)}."); - - if (methodToCall.IsStatic) - return null; - - if (!methodToCall.DeclaringType.IsAssignableFrom(localType)) - throw new ArgumentException($"{methodToCall.DeclaringType} is not assignable from {localType}."); - - return localType.IsValueType && localType != typeof(void) - ? ilBuilder.DeclareLocal(localType) - : null; - } - public static void EmitStaticCall( this ILGenerator ilBuilder, MethodInfo methodToCall, diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs deleted file mode 100644 index 4b23db0141..0000000000 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorDefaultValueExtensions.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Reflection.Emit; - -namespace BenchmarkDotNet.Helpers.Reflection.Emit -{ - internal static class IlGeneratorDefaultValueExtensions - { - public static LocalBuilder DeclareOptionalLocalForReturnDefault(this ILGenerator ilBuilder, Type resultType) - { - return resultType.UseInitObjForReturnDefault() - ? ilBuilder.DeclareLocal(resultType) - : null; - } - - public static void EmitSetLocalToDefault(this ILGenerator ilBuilder, LocalBuilder local) - { - var resultType = local.LocalType; - switch (resultType) - { - case Type t when t == typeof(void): - break; - case Type t when t.IsClass || t.IsInterface: - ilBuilder.Emit(OpCodes.Ldnull); - ilBuilder.EmitStloc(local); - break; - case Type t when t.UseInitObjForInitLocal(): - EmitInitObj(ilBuilder, resultType, local); - break; - default: - EmitLoadDefaultPrimitive(ilBuilder, resultType); - ilBuilder.EmitStloc(local); - break; - } - } - - public static void EmitReturnDefault(this ILGenerator ilBuilder, Type resultType, LocalBuilder optionalLocalForInitobj) - { - switch (resultType) - { - case Type t when t == typeof(void): - break; - case Type t when t.IsClass || t.IsInterface: - ilBuilder.Emit(OpCodes.Ldnull); - break; - case Type t when t.UseInitObjForReturnDefault(): - EmitInitObj(ilBuilder, resultType, optionalLocalForInitobj); - ilBuilder.EmitLdloc(optionalLocalForInitobj); - break; - default: - EmitLoadDefaultPrimitive(ilBuilder, resultType); - break; - } - // IL_0000: ret - ilBuilder.Emit(OpCodes.Ret); - } - - private static bool IsInitLocalPrimitive(this Type t) - { - // var x = default(T): - // C# compiler uses special logic for enum defaults and primitive defaults - // On init local case this logic does not apply for IntPtr & UIntPtr. - - if (t == typeof(void)) - return true; - - if (t.IsEnum) - return true; - - return t.IsPrimitive - && t != typeof(IntPtr) - && t != typeof(UIntPtr); - } - - private static bool IsReturnDefaultPrimitive(this Type t) - { - // return default(T): - // C# compiler uses special logic for enum defaults and primitive defaults - // On return default special logic is applied for decimals too. - - - if (t == typeof(void)) - return true; - - if (t.IsEnum) - return true; - - return t.IsPrimitive - || t == typeof(decimal); - } - - private static bool UseInitObjForInitLocal(this Type resultType) - { - return resultType.IsValueType && !resultType.IsInitLocalPrimitive(); - } - - private static bool UseInitObjForReturnDefault(this Type resultType) - { - return resultType.IsValueType && !resultType.IsReturnDefaultPrimitive(); - } - - private static void EmitInitObj(ILGenerator ilBuilder, Type resultType, LocalBuilder optionalLocalForInitobj) - { - if (optionalLocalForInitobj == null) - throw new ArgumentNullException(nameof(optionalLocalForInitobj)); - - /* - IL_0000: ldloca.s 0 - IL_0002: initobj [mscorlib]System.DateTime - */ - ilBuilder.EmitLdloca(optionalLocalForInitobj); - ilBuilder.Emit(OpCodes.Initobj, resultType); - } - - private static void EmitLoadDefaultPrimitive(this ILGenerator ilBuilder, Type resultType) - { - var valueType = resultType; - if (valueType.IsEnum) - valueType = resultType.GetEnumUnderlyingType(); - - // The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single - // + custom logic for decimal - switch (valueType) - { - case Type t0 when t0 == typeof(bool): - case Type t1 when t1 == typeof(byte): - case Type t2 when t2 == typeof(sbyte): - case Type t3 when t3 == typeof(short): - case Type t4 when t4 == typeof(ushort): - case Type t5 when t5 == typeof(int): - case Type t6 when t6 == typeof(uint): - case Type t7 when t7 == typeof(char): - ilBuilder.Emit(OpCodes.Ldc_I4_0); - break; - case Type t1 when t1 == typeof(ulong): - case Type t2 when t2 == typeof(long): - /* - // return 0L; - IL_0000: ldc.i4.0 - IL_0001: conv.i8 - // return 0uL; - IL_0000: ldc.i4.0 - IL_0001: conv.i8 - */ - ilBuilder.Emit(OpCodes.Ldc_I4_0); - ilBuilder.Emit(OpCodes.Conv_I8); - break; - case Type t when t == typeof(IntPtr): - /* - IL_0000: ldc.i4.0 - IL_0001: conv.i - */ - ilBuilder.Emit(OpCodes.Ldc_I4_0); - ilBuilder.Emit(OpCodes.Conv_I); - break; - case Type t when t == typeof(UIntPtr): - /* - IL_0000: ldc.i4.0 - IL_0001: conv.u - */ - ilBuilder.Emit(OpCodes.Ldc_I4_0); - ilBuilder.Emit(OpCodes.Conv_U); - break; - case Type t when t == typeof(double): - ilBuilder.Emit(OpCodes.Ldc_R8, 0.0d); - break; - case Type t when t == typeof(float): - ilBuilder.Emit(OpCodes.Ldc_R4, 0.0f); - break; - case Type t when t == typeof(decimal): - /* - // return decimal.Zero; - IL_0011: ldsfld valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::Zero - */ - var zeroField = typeof(decimal).GetField(nameof(decimal.Zero)); - ilBuilder.Emit(OpCodes.Ldsfld, zeroField); - break; - default: - throw new NotSupportedException($"Cannot emit default for {resultType}."); - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorEmitOpExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorEmitOpExtensions.cs index 50a3392f44..7763d59e91 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorEmitOpExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorEmitOpExtensions.cs @@ -116,127 +116,19 @@ public static void EmitLdarg(this ILGenerator ilBuilder, ParameterInfo argument) } } - public static void EmitLdindStind(this ILGenerator ilBuilder, Type resultType) + public static void EmitStarg(this ILGenerator ilBuilder, ParameterInfo argument) { - if (!resultType.IsByRef) - throw new NotSupportedException($"Cannot emit indirect op for non-reference {resultType}."); - - // The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single - var valueType = resultType.GetElementType(); - if (valueType?.IsEnum ?? false) - valueType = valueType.GetEnumUnderlyingType(); + var position = argument.Position; + if (!((MethodBase) argument.Member).IsStatic) + position++; - switch (valueType) + if (position < 255) { - case Type t when t == typeof(bool): - /* - IL_0018: ldind.u1 - IL_0019: stind.i1 - */ - ilBuilder.Emit(OpCodes.Ldind_U1); - ilBuilder.Emit(OpCodes.Stind_I1); - break; - case Type t when t == typeof(byte): - /* - IL_0018: ldind.u1 - IL_0019: stind.i1 - */ - ilBuilder.Emit(OpCodes.Ldind_U1); - ilBuilder.Emit(OpCodes.Stind_I1); - break; - case Type t when t == typeof(sbyte): - /* - IL_0018: ldind.i1 - IL_0019: stind.i1 - */ - ilBuilder.Emit(OpCodes.Ldind_I1); - ilBuilder.Emit(OpCodes.Stind_I1); - break; - case Type t when t == typeof(short): - /* - IL_0018: ldind.i2 - IL_0019: stind.i2 - */ - ilBuilder.Emit(OpCodes.Ldind_I2); - ilBuilder.Emit(OpCodes.Stind_I2); - break; - case Type t1 when t1 == typeof(ushort): - case Type t2 when t2 == typeof(char): - /* - IL_0018: ldind.u2 - IL_0019: stind.i2 - */ - ilBuilder.Emit(OpCodes.Ldind_U2); - ilBuilder.Emit(OpCodes.Stind_I2); - break; - case Type t when t == typeof(int): - /* - IL_0018: ldind.i4 - IL_0019: stind.i4 - */ - ilBuilder.Emit(OpCodes.Ldind_I4); - ilBuilder.Emit(OpCodes.Stind_I4); - break; - case Type t when t == typeof(uint): - /* - IL_0018: ldind.i4 - IL_0019: stind.i4 - */ - ilBuilder.Emit(OpCodes.Ldind_U4); - ilBuilder.Emit(OpCodes.Stind_I4); - break; - case Type t1 when t1 == typeof(ulong): - case Type t2 when t2 == typeof(long): - /* - IL_0018: ldind.i8 - IL_0019: stind.i8 - */ - ilBuilder.Emit(OpCodes.Ldind_I8); - ilBuilder.Emit(OpCodes.Stind_I8); - break; - case Type t1 when t1 == typeof(IntPtr): - case Type t2 when t2 == typeof(UIntPtr): - /* - IL_0018: ldind.i - IL_0019: stind.i - */ - ilBuilder.Emit(OpCodes.Ldind_I); - ilBuilder.Emit(OpCodes.Stind_I); - break; - case Type t when t == typeof(double): - /* - IL_0018: ldind.r8 - IL_0019: stind.i8 - */ - ilBuilder.Emit(OpCodes.Ldind_R8); - ilBuilder.Emit(OpCodes.Stind_R8); - break; - case Type t when t == typeof(float): - /* - IL_0018: ldind.r4 - IL_0019: stind.i4 - */ - ilBuilder.Emit(OpCodes.Ldind_R4); - ilBuilder.Emit(OpCodes.Stind_R4); - break; - case Type t when t.IsClass || t.IsInterface: - /* - IL_0018: ldind.ref - IL_0019: stind.ref - */ - ilBuilder.Emit(OpCodes.Ldind_Ref); - ilBuilder.Emit(OpCodes.Stind_Ref); - break; - case Type t when t.IsEnum || t.IsValueType: - /* - IL_0018: ldobj valuetype [mscorlib]System.Nullable`1 - IL_0019: stobj valuetype [mscorlib]System.Nullable`1 - */ - ilBuilder.Emit(OpCodes.Ldobj, valueType); - ilBuilder.Emit(OpCodes.Stobj, valueType); - break; - default: - throw new NotSupportedException($"Cannot emit indirect store for {resultType}."); + ilBuilder.Emit(OpCodes.Starg_S, (byte) position); + } + else + { + ilBuilder.Emit(OpCodes.Starg, checked((short) position)); } } } diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs index 6d601e6495..4fd23e3ab8 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs @@ -84,64 +84,57 @@ public static void EmitSetDelegateToThisField( } } - public static void EmitLoopBeginFromLocToArg( + public static void EmitLoopBeginFromArgToZero( this ILGenerator ilBuilder, - Label loopStartLabel, - Label loopHeadLabel, - LocalBuilder indexLocal, - ParameterInfo toArg) + out Label loopStartLabel, + out Label loopHeadLabel) { - // loop counter stored as loc0, loop max passed as arg1 + loopStartLabel = ilBuilder.DefineLabel(); + loopHeadLabel = ilBuilder.DefineLabel(); + // invokeCount passed as arg /* - // for (long i = 0L; i < invokeCount; i++) - IL_0000: ldc.i4.0 - IL_0001: conv.i8 - IL_0002: stloc.0 + // while (--invokeCount >= 0) */ - ilBuilder.Emit(OpCodes.Ldc_I4_0); - ilBuilder.Emit(OpCodes.Conv_I8); - ilBuilder.EmitStloc(indexLocal); - // IL_0003: br.s IL_0036 // loop head: IL_0036 // we use long jump + // IL_0000: br.s IL_000e // loop head: IL_000e // we use long jump ilBuilder.Emit(OpCodes.Br, loopHeadLabel); - // loop start (head: IL_0036) + // loop start (head: IL_000e) ilBuilder.MarkLabel(loopStartLabel); } - public static void EmitLoopEndFromLocToArg( + public static void EmitLoopEndFromArgToZero( this ILGenerator ilBuilder, Label loopStartLabel, Label loopHeadLabel, - LocalBuilder indexLocal, - ParameterInfo toArg) + ParameterInfo arg) { - // loop counter stored as loc0, loop max passed as arg1 - /* - // for (long i = 0L; i < invokeCount; i++) - IL_0031: ldloc.0 - IL_0032: ldc.i4.1 - IL_0033: conv.i8 - IL_0034: add - IL_0035: stloc.0 - */ - ilBuilder.EmitLdloc(indexLocal); - ilBuilder.Emit(OpCodes.Ldc_I4_1); - ilBuilder.Emit(OpCodes.Conv_I8); - ilBuilder.Emit(OpCodes.Add); - ilBuilder.EmitStloc(indexLocal); - + // invokeCount passed as arg /* - // for (long i = 0L; i < invokeCount; i++) - IL_0036: ldloc.0 // loop head: IL_0036 - IL_0037: ldarg.1 - IL_0038: blt.s IL_0005 // we use long jump + // while (--invokeCount >= 0) + IL_0008: ldarg.1 + IL_0009: ldc.i4.1 + IL_000a: conv.i8 + IL_000b: sub + IL_000c: dup + IL_000d: starg.s invokeCount + IL_000f: ldc.i4.0 + IL_0010: conv.i8 + IL_0011: bge.s IL_0002 // end loop */ + + // loop head ilBuilder.MarkLabel(loopHeadLabel); - ilBuilder.EmitLdloc(indexLocal); - ilBuilder.EmitLdarg(toArg); - ilBuilder.Emit(OpCodes.Blt, loopStartLabel); + ilBuilder.EmitLdarg(arg); + ilBuilder.Emit(OpCodes.Ldc_I4_1); + ilBuilder.Emit(OpCodes.Conv_I8); + ilBuilder.Emit(OpCodes.Sub); + ilBuilder.Emit(OpCodes.Dup); + ilBuilder.EmitStarg(arg); + ilBuilder.Emit(OpCodes.Ldc_I4_0); + ilBuilder.Emit(OpCodes.Conv_I8); + ilBuilder.Emit(OpCodes.Bge, loopStartLabel); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/MethodBuilderExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/MethodBuilderExtensions.cs index c09a7d19c2..ae3890141b 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/MethodBuilderExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/MethodBuilderExtensions.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Portability; -using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -9,9 +8,6 @@ namespace BenchmarkDotNet.Helpers.Reflection.Emit { internal static class MethodBuilderExtensions { - public static Type[] GetParameterTypes(this MethodBase method) => - method.GetParameters().Select(p => p.ParameterType).ToArray(); - public static ParameterInfo[] GetEmitParameters(this MethodBuilder method, IEnumerable signatureParameters) => signatureParameters .Select(p => diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/ModuleBuilderExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/ModuleBuilderExtensions.cs deleted file mode 100644 index c879bc1f46..0000000000 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/ModuleBuilderExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace BenchmarkDotNet.Helpers.Reflection.Emit -{ - internal static class ModuleBuilderExtensions - { - public static Type EmitCustomDelegate( - this ModuleBuilder moduleBuilder, - string delegateTypeName, - ParameterInfo returnType, - ParameterInfo[] parameters) - { - // TODO: begin/end invoke ? - var delegatePatternType = typeof(Action); - - var typeBuilder = moduleBuilder.DefineType( - delegateTypeName, - delegatePatternType.Attributes, - delegatePatternType.BaseType); - - var ctorPattern = delegatePatternType.GetConstructors().Single(); - var ctorBuilder = typeBuilder.DefineConstructor( - ctorPattern.Attributes, - ctorPattern.CallingConvention, - ctorPattern.GetParameterTypes()); - - foreach (var parameterInfo in ctorPattern.GetParameters()) - { - ctorBuilder.DefineParameter(parameterInfo.Position + 1, parameterInfo.Attributes, parameterInfo.Name); - } - - ctorBuilder.SetImplementationFlags(ctorPattern.GetMethodImplementationFlags()); - - var invokePatternMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(delegatePatternType); - - var invokeBuilder = typeBuilder.DefineMethod( - invokePatternMethod.Name, - invokePatternMethod.Attributes, - invokePatternMethod.CallingConvention, - returnType.ParameterType, - parameters.Select(p => p.ParameterType).ToArray()); - foreach (var parameterInfo in parameters) - { - invokeBuilder.DefineParameter(parameterInfo.Position + 1, parameterInfo.Attributes, parameterInfo.Name); - } - invokeBuilder.DefineParameter(0, returnType.Attributes, ""); - - invokeBuilder.SetImplementationFlags(invokePatternMethod.GetMethodImplementationFlags()); - -#if NETFRAMEWORK - return typeBuilder.CreateType(); -#else - return typeBuilder.CreateTypeInfo(); -#endif - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/TypeBuilderExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/TypeBuilderExtensions.cs index c71c19cd63..2e290be9ba 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/TypeBuilderExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/TypeBuilderExtensions.cs @@ -25,15 +25,6 @@ private static void DefineParameters(this MethodBuilder methodBuilder, Parameter methodBuilder.DefineParameter(0, returnType.Attributes, ""); } - public static MethodInfo GetDelegateInvokeMethod(Type delegateType) - { - var result = delegateType.GetMethod(nameof(Action.Invoke)); - if (result == null) - throw new ArgumentException($"The type {delegateType} nas no Invoke method.", nameof(delegateType)); - - return result; - } - public static ConstructorBuilder DefinePublicInstanceCtor(this TypeBuilder typeBuilder, params ParameterInfo[] parameters) { // .method public hidebysig specialname rtspecialname diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index f17737d646..eeeb08f3e0 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -1,5 +1,5 @@ // the type name must be in sync with WindowsDisassembler.BuildArguments - public unsafe class Runnable_$ID$ : global::$WorkloadTypeName$ + public unsafe sealed class Runnable_$ID$ : global::$WorkloadTypeName$ { public static void Run(BenchmarkDotNet.Engines.IHost host, System.String benchmarkName) { @@ -51,18 +51,12 @@ } } - public delegate $OverheadMethodReturnTypeName$ OverheadDelegate($ArgumentsDefinition$); - - public delegate $WorkloadMethodReturnTypeModifiers$ $WorkloadMethodReturnType$ WorkloadDelegate($ArgumentsDefinition$); - public Runnable_$ID$() { globalSetupAction = $GlobalSetupMethodName$; globalCleanupAction = $GlobalCleanupMethodName$; iterationSetupAction = $IterationSetupMethodName$; iterationCleanupAction = $IterationCleanupMethodName$; - overheadDelegate = __Overhead; - workloadDelegate = $WorkloadMethodDelegate$; $InitializeArgumentFields$ } @@ -70,8 +64,6 @@ private System.Action globalCleanupAction; private System.Action iterationSetupAction; private System.Action iterationCleanupAction; - private BenchmarkDotNet.Autogenerated.Runnable_$ID$.OverheadDelegate overheadDelegate; - private BenchmarkDotNet.Autogenerated.Runnable_$ID$.WorkloadDelegate workloadDelegate; $DeclareArgumentFields$ // this method is used only for the disassembly diagnoser purposes @@ -84,6 +76,16 @@ $DisassemblerEntryMethodName$(); } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + public void $DisassemblerEntryMethodName$() + { + if (NotEleven == 11) + { + $LoadArguments$ + $WorkloadMethodCall$; + } + } + private System.Int32 dummyVar; [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -104,310 +106,48 @@ @DummyUnroll@ } - private $OverheadMethodReturnTypeName$ __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict - { - $OverheadImplementation$ - } - -#if RETURNS_CONSUMABLE_$ID$ - - private BenchmarkDotNet.Engines.Consumer consumer = new BenchmarkDotNet.Engines.Consumer(); - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - consumer.Consume(overheadDelegate($PassArguments$));@Unroll@ - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - consumer.Consume(overheadDelegate($PassArguments$)); - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$);@Unroll@ - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$); - } - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ - return $WorkloadMethodCall$; - } - - return default($WorkloadMethodReturnType$); - } - -#elif RETURNS_NON_CONSUMABLE_STRUCT_$ID$ - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = overheadDelegate($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = overheadDelegate($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = workloadDelegate($PassArguments$);@Unroll@ - } - NonGenericKeepAliveWithoutBoxing(result); - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - result = workloadDelegate($PassArguments$); - } - NonGenericKeepAliveWithoutBoxing(result); - } - - // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method - // and stack-only types like Span can not be generic type arguments http://adamsitnik.com/Span/#span-must-not-be-a-generic-type-argument [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private void NonGenericKeepAliveWithoutBoxing($WorkloadMethodReturnType$ _) { } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() + private void __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict { - if (NotEleven == 11) - { - $LoadArguments$ - return $WorkloadMethodCall$; - } - - return default($WorkloadMethodReturnType$); } -#elif RETURNS_BYREF_$ID$ - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] private void OverheadActionUnroll(System.Int64 invokeCount) { $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) + while (--invokeCount >= 0) { - value = overheadDelegate($PassArguments$);@Unroll@ + __Overhead($PassArguments$);@Unroll@ } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] private void OverheadActionNoUnroll(System.Int64 invokeCount) { $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) + while (--invokeCount >= 0) { - value = overheadDelegate($PassArguments$); + __Overhead($PassArguments$); } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); } - private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionUnroll(System.Int64 invokeCount) { $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) + while (--invokeCount >= 0) { - alias = workloadDelegate($PassArguments$);@Unroll@ + $WorkloadMethodCall$;@Unroll@ } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); } [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] private void WorkloadActionNoUnroll(System.Int64 invokeCount) { $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = workloadDelegate($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public ref $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ - return ref $WorkloadMethodCall$; - } - - return ref workloadDefaultValueHolder; - } -#elif RETURNS_BYREF_READONLY_$ID$ - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) + while (--invokeCount >= 0) { - value = overheadDelegate($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); - for (System.Int64 i = 0; i < invokeCount; i++) - { - value = overheadDelegate($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - } - - private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = workloadDelegate($PassArguments$);@Unroll@ - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; - for (System.Int64 i = 0; i < invokeCount; i++) - { - alias = workloadDelegate($PassArguments$); - } - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public ref readonly $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ - return ref $WorkloadMethodCall$; - } - - return ref workloadDefaultValueHolder; - } -#elif RETURNS_VOID_$ID$ - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - overheadDelegate($PassArguments$);@Unroll@ - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void OverheadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - overheadDelegate($PassArguments$); - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - workloadDelegate($PassArguments$);@Unroll@ - } - } - - [System.Runtime.CompilerServices.MethodImpl(BenchmarkDotNet.Portability.CodeGenHelper.AggressiveOptimizationOption)] - private void WorkloadActionNoUnroll(System.Int64 invokeCount) - { - $LoadArguments$ - for (System.Int64 i = 0; i < invokeCount; i++) - { - workloadDelegate($PassArguments$); - } - } - - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - public void $DisassemblerEntryMethodName$() - { - if (NotEleven == 11) - { - $LoadArguments$ $WorkloadMethodCall$; } } -#endif // RETURNS } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs index 147a514058..123f07bf7a 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/ConsumableTypeInfo.cs @@ -1,7 +1,4 @@ -using BenchmarkDotNet.Engines; -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -41,43 +38,23 @@ public ConsumableTypeInfo(Type methodReturnType) if (WorkloadMethodReturnType == null) throw new InvalidOperationException("Bug: (WorkloadMethodReturnType == null"); - var consumableField = default(FieldInfo); if (WorkloadMethodReturnType == typeof(void)) { IsVoid = true; - OverheadMethodReturnType = WorkloadMethodReturnType; } else if (WorkloadMethodReturnType.IsByRef) { IsByRef = true; - OverheadMethodReturnType = typeof(IntPtr); } - else if (Consumer.IsConsumable(WorkloadMethodReturnType) - || Consumer.HasConsumableField(WorkloadMethodReturnType, out consumableField)) - { - IsConsumable = true; - WorkloadConsumableField = consumableField; - OverheadMethodReturnType = consumableField?.FieldType ?? WorkloadMethodReturnType; - } - else - { - OverheadMethodReturnType = typeof(int); // we return this simple type because creating bigger ValueType could take longer than benchmarked method itself - } - - if (OverheadMethodReturnType == null) - throw new InvalidOperationException("Bug: (OverheadResultType == null"); } public Type OriginMethodReturnType { get; } public Type WorkloadMethodReturnType { get; } - public Type OverheadMethodReturnType { get; } public MethodInfo? GetResultMethod { get; } public bool IsVoid { get; } public bool IsByRef { get; } - public bool IsConsumable { get; } - public FieldInfo? WorkloadConsumableField { get; } public bool IsAwaitable { get; } } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs deleted file mode 100644 index 96ed6d9578..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ByRefConsumeEmitter.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; -using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal class ByRefConsumeEmitter : ConsumeEmitter - { - private FieldBuilder workloadDefaultValueHolderField; - private MethodInfo overheadKeepAliveWithoutBoxingMethod; - private MethodInfo workloadKeepAliveWithoutBoxingMethod; - private LocalBuilder resultLocal; - - public ByRefConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) { } - - protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) - { - var nonRefType = ConsumableInfo.WorkloadMethodReturnType.GetElementType(); - if (nonRefType == null) - throw new InvalidOperationException($"Bug: type {ConsumableInfo.WorkloadMethodReturnType} is non-ref type."); - - workloadDefaultValueHolderField = runnableBuilder.DefineField( - WorkloadDefaultValueHolderFieldName, - nonRefType, FieldAttributes.Private); - } - - protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - /* - // return ref workloadDefaultValueHolder; - IL_0031: ldarg.0 - IL_0032: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder - IL_0037: ret - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField); - ilBuilder.Emit(OpCodes.Ret); - } - - protected override void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && m.GetParameterTypes().First().IsByRef == false) - .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); - - workloadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && m.GetParameterTypes().First().IsByRef) - .MakeGenericMethod(ConsumableInfo.WorkloadMethodReturnType.GetElementType()); - } - - protected override void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - /* - .locals init ( - [4] native int, - ) - -or- - .locals init ( - [4] int32&, - ) - */ - if (ActionKind == RunnableActionKind.Overhead) - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); - else - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.WorkloadMethodReturnType); - } - - /// Emits the action before loop override. - /// The il builder. - /// EmitActionKind - null - protected override void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - /* - // IntPtr value = default(IntPtr); - IL_001c: ldloca.s 4 - IL_001e: initobj [mscorlib]System.IntPtr - -or- - // ref int reference = ref workloadDefaultValueHolder; - IL_001c: ldarg.0 - IL_001d: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder - IL_0022: stloc.s 4 - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitLdloca(resultLocal); - ilBuilder.Emit(OpCodes.Initobj, ConsumableInfo.OverheadMethodReturnType); - } - else - { - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField); - ilBuilder.EmitStloc(resultLocal); - } - } - - protected override void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - /* - - -or- - // reference = ... - IL_002a: ldloc.s 4 - */ - if (ActionKind != RunnableActionKind.Overhead) - { - ilBuilder.EmitLdloc(resultLocal); - } - } - - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - /* - IL_0039: stloc.s 4 - -or- - // reference = ... - IL_003b: ldind.i4 - IL_003c: stind.i4 - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitStloc(resultLocal); - } - else - { - ilBuilder.EmitLdindStind(resultLocal.LocalType); - } - } - - protected override void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - /* - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - IL_007a: ldloc.s 4 - IL_007c: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0) - -or- - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref reference); - IL_0082: ldloc.s 4 - IL_0084: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0&) - */ - if (ActionKind == RunnableActionKind.Overhead) - ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, resultLocal); - else - ilBuilder.EmitStaticCall(workloadKeepAliveWithoutBoxingMethod, resultLocal); - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs deleted file mode 100644 index 76a2a5f505..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumableConsumeEmitter.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal class ConsumableConsumeEmitter : ConsumeEmitter - { - private static MethodInfo GetConsumeMethod(Type consumableType) - { - var consumeMethod = typeof(Consumer).GetMethod(nameof(Consumer.Consume), new[] { consumableType }); - - // Use generic method for ref types - if (consumeMethod == null || consumeMethod.GetParameterTypes().FirstOrDefault() == typeof(object)) - { - if (consumableType.IsClass || consumableType.IsInterface) - { - consumeMethod = typeof(Consumer) - .GetMethods() - .Single(m => - { - Type argType = m.GetParameterTypes().FirstOrDefault(); - - return m.Name == nameof(Consumer.Consume) && m.IsGenericMethodDefinition - && !argType.IsByRef // we are not interested in "Consume(in T value)" - && argType.IsPointer == consumableType.IsPointer; // use "Consume(T objectValue) where T : class" or "Consume(T* ptrValue) where T: unmanaged" - }); - - consumeMethod = consumableType.IsPointer - ? consumeMethod.MakeGenericMethod(consumableType.GetElementType()) // consumableType is T*, we need T for Consume(T* ptrValue) - : consumeMethod.MakeGenericMethod(consumableType); - } - else - { - consumeMethod = null; - } - } - - if (consumeMethod == null) - { - throw new InvalidOperationException($"Cannot consume result of {consumableType}."); - } - - return consumeMethod; - } - - private FieldBuilder consumerField; - private LocalBuilder disassemblyDiagnoserLocal; - - public ConsumableConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) - { - } - - protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) - { - consumerField = runnableBuilder.DefineField(RunnableConstants.ConsumerFieldName, typeof(Consumer), FieldAttributes.Private); - } - - protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) - { - // optional local if default(T) uses .initobj - disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); - } - - protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); - } - - protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - var ctor = typeof(Consumer).GetConstructor(Array.Empty()); - if (ctor == null) - throw new InvalidOperationException($"Cannot get default .ctor for {typeof(Consumer)}"); - - /* - // consumer = new Consumer(); - IL_0000: ldarg.0 - IL_0001: newobj instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::.ctor() - IL_0006: stfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Newobj, ctor); - ilBuilder.Emit(OpCodes.Stfld, consumerField); - } - - protected override void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - /* - // consumer. ...; - IL_000c: ldarg.0 - IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, consumerField); - } - - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - /* - // ... .Consume( ... ) - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(string) - -or- - // ... .Consume( ... .ConsumableField); - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - // -or- .Consume( ... ); - IL_001e: ldfld int32 BenchmarkDotNet.Samples.CustomWithConsumable::ConsumableField - IL_0023: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - */ - if (ActionKind == RunnableActionKind.Overhead) - { - var overheadConsumeMethod = GetConsumeMethod(ConsumableInfo.OverheadMethodReturnType); - ilBuilder.Emit(OpCodes.Callvirt, overheadConsumeMethod); - } - else - { - var consumeField = ConsumableInfo.WorkloadConsumableField; - if (consumeField == null) - { - var consumeMethod = GetConsumeMethod(ConsumableInfo.WorkloadMethodReturnType); - ilBuilder.Emit(OpCodes.Callvirt, consumeMethod); - } - else - { - var consumeMethod = GetConsumeMethod(consumeField.FieldType); - ilBuilder.Emit(OpCodes.Ldfld, consumeField); - ilBuilder.Emit(OpCodes.Callvirt, consumeMethod); - } - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs deleted file mode 100644 index 9767cb8263..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/ConsumeEmitter.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; -using JetBrains.Annotations; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal abstract class ConsumeEmitter - { - public static ConsumeEmitter GetConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) - { - if (consumableTypeInfo == null) - throw new ArgumentNullException(nameof(consumableTypeInfo)); - - if (consumableTypeInfo.IsVoid) - return new VoidConsumeEmitter(consumableTypeInfo); - if (consumableTypeInfo.IsByRef) - return new ByRefConsumeEmitter(consumableTypeInfo); - if (consumableTypeInfo.IsConsumable) - return new ConsumableConsumeEmitter(consumableTypeInfo); - return new NonConsumableConsumeEmitter(consumableTypeInfo); - } - - protected ConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) - { - if (consumableTypeInfo == null) - throw new ArgumentNullException(nameof(consumableTypeInfo)); - - ConsumableInfo = consumableTypeInfo; - } - - protected ConsumableTypeInfo ConsumableInfo { get; } - - protected ILGenerator? IlBuilder { get; private set; } - protected MethodBuilder? ActionMethodBuilder { get; private set; } - protected MethodInfo? ActionInvokeMethod { get; private set; } - protected RunnableActionKind? ActionKind { get; private set; } - - [AssertionMethod] - private void AssertNoBuilder() - { - if (IlBuilder != null) - throw new InvalidOperationException("Bug: emit action logic is broken. Expects that IlBuilder != null"); - - if (ActionMethodBuilder != null) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. {nameof(ActionMethodBuilder)} is not null."); - - if (ActionInvokeMethod != null) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. {nameof(ActionInvokeMethod)} is not null."); - - if (ActionKind != null) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. {nameof(ActionKind)} is not null."); - } - - [AssertionMethod] - private void AssertHasBuilder(ILGenerator ilBuilder) - { - if (IlBuilder != ilBuilder) - throw new InvalidOperationException( - "Bug: emit action logic is broken. Expects that IlBuilder is same as passed one."); - - if (ActionMethodBuilder == null) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. {nameof(ActionMethodBuilder)} is null."); - - if (ActionInvokeMethod == null) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. {nameof(ActionInvokeMethod)} is null."); - - if (ActionKind != RunnableActionKind.Overhead && ActionKind != RunnableActionKind.Workload) - throw new InvalidOperationException( - $"Bug: emit action logic is broken. Unknown {nameof(ActionKind)} value: {ActionKind}."); - } - - public void OnDefineFields(TypeBuilder runnableBuilder) - { - AssertNoBuilder(); - - OnDefineFieldsOverride(runnableBuilder); - } - - protected virtual void OnDefineFieldsOverride(TypeBuilder runnableBuilder) - { - } - - public void OnEmitMembers(TypeBuilder runnableBuilder) - { - AssertNoBuilder(); - - OnEmitMembersOverride(runnableBuilder); - } - - protected virtual void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - } - - public void OnEmitCtorBody(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - AssertNoBuilder(); - - OnEmitCtorBodyOverride(constructorBuilder, ilBuilder); - } - - protected virtual void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) - { - } - - public void DeclareDisassemblyDiagnoserLocals(ILGenerator ilBuilder) - { - AssertNoBuilder(); - - DeclareDisassemblyDiagnoserLocalsOverride(ilBuilder); - } - - protected virtual void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) - { - } - - public void EmitDisassemblyDiagnoserReturnDefault(ILGenerator ilBuilder) - { - AssertNoBuilder(); - - EmitDisassemblyDiagnoserReturnDefaultOverride(ilBuilder); - } - - protected virtual void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - } - - public void BeginEmitAction( - MethodBuilder actionMethodBuilder, - ILGenerator ilBuilder, - MethodInfo actionInvokeMethod, - RunnableActionKind actionKind) - { - if (actionMethodBuilder.IsStatic) - throw new NotSupportedException($"The {actionMethodBuilder} method should be instance method."); - - AssertNoBuilder(); - - IlBuilder = ilBuilder; - ActionMethodBuilder = actionMethodBuilder; - ActionInvokeMethod = actionInvokeMethod; - ActionKind = actionKind; - - BeginEmitActionOverride(IlBuilder); - } - - protected virtual void BeginEmitActionOverride(ILGenerator ilBuilder) - { - } - - public void CompleteEmitAction(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - CompleteEmitActionOverride(ilBuilder); - - IlBuilder = null; - ActionMethodBuilder = null; - ActionInvokeMethod = null; - ActionKind = null; - } - - protected virtual void CompleteEmitActionOverride(ILGenerator ilBuilder) - { - } - - public void DeclareActionLocals(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - DeclareActionLocalsOverride(ilBuilder); - } - - protected virtual void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionBeforeLoop(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionBeforeLoopOverride(ilBuilder); - } - - protected virtual void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionAfterLoop(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionAfterLoopOverride(ilBuilder); - } - - protected virtual void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionBeforeCall(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionBeforeCallOverride(ilBuilder); - } - - protected virtual void EmitActionBeforeCallOverride(ILGenerator ilBuilder) - { - } - - public void EmitActionAfterCall(ILGenerator ilBuilder) - { - AssertHasBuilder(ilBuilder); - - EmitActionAfterCallOverride(ilBuilder); - } - - protected virtual void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs deleted file mode 100644 index 4087165f3b..0000000000 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/NonConsumableConsumeEmitter.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Helpers.Reflection.Emit; -using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; - -namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation -{ - internal class NonConsumableConsumeEmitter : ConsumeEmitter - { - private MethodInfo overheadKeepAliveWithoutBoxingMethod; - private MethodInfo nonGenericKeepAliveWithoutBoxingMethod; - private LocalBuilder resultLocal; - private LocalBuilder disassemblyDiagnoserLocal; - - public NonConsumableConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) - { - } - - protected override void OnEmitMembersOverride(TypeBuilder runnableBuilder) - { - overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() - .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) - && !m.GetParameterTypes().First().IsByRef) - .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); - - // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method - // and stack-only types like Span can not be generic type arguments http://adamsitnik.com/Span/#span-must-not-be-a-generic-type-argument - nonGenericKeepAliveWithoutBoxingMethod = EmitNonGenericKeepAliveWithoutBoxing( - NonGenericKeepAliveWithoutBoxingMethodName, - runnableBuilder); - } - - protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) - { - // optional local if default(T) uses .initobj - disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); - } - - protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) - { - ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); - } - - private MethodBuilder EmitNonGenericKeepAliveWithoutBoxing(string methodName, TypeBuilder runnableBuilder) - { - /* - method private hidebysig - instance void NonGenericKeepAliveWithoutBoxing( - valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable _ - ) cil managed noinlining - */ - var valueArg = new EmitParameterInfo( - 0, - DummyParamName, - ConsumableInfo.WorkloadMethodReturnType); - var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( - methodName, - MethodAttributes.Private, - EmitParameterInfo.CreateReturnVoidParameter(), - valueArg) - .SetNoInliningImplementationFlag(); - valueArg.SetMember(methodBuilder); - - var ilBuilder = methodBuilder.GetILGenerator(); - - /* - IL_0001: ret - */ - ilBuilder.EmitVoidReturn(methodBuilder); - - return methodBuilder; - } - - - protected override void DeclareActionLocalsOverride(ILGenerator ilBuilder) - { - /* - .locals init ( - [2] int32 - ) - -or- - .locals init ( - [2] valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable, - ) - */ - if (ActionKind == RunnableActionKind.Overhead) - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); - else - resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.WorkloadMethodReturnType); - } - - /// Emits the action before loop override. - /// The il builder. - /// EmitActionKind - null - protected override void EmitActionBeforeLoopOverride(ILGenerator ilBuilder) - { - /* - // int value = 0; - IL_000e: ldc.i4.0 - IL_000f: stloc.2 - -or- - // CustomStructNonConsumable _ = default(CustomStructNonConsumable); - IL_000e: ldloca.s 2 - IL_0010: initobj BenchmarkDotNet.Samples.CustomStructNonConsumable - */ - ilBuilder.EmitSetLocalToDefault(resultLocal); - } - - protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder) - { - // IL_0022: stloc.2 - ilBuilder.EmitStloc(resultLocal); - } - - protected override void EmitActionAfterLoopOverride(ILGenerator ilBuilder) - { - /* - // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - IL_002c: ldloc.2 - IL_002d: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing(!!0) - -or- - // NonGenericKeepAliveWithoutBoxing(_); - IL_0032: ldarg.0 - IL_0033: ldloc.2 - IL_0034: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::NonGenericKeepAliveWithoutBoxing(valuetype BenchmarkDotNet.Samples.CustomStructNonConsumable) - */ - if (ActionKind == RunnableActionKind.Overhead) - { - ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, resultLocal); - } - else - { - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.EmitInstanceCallThisValueOnStack( - null, - nonGenericKeepAliveWithoutBoxingMethod, - new[] { resultLocal }, - forceDirectCall: true); - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs index 0e9d386a24..a87d4256e2 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs @@ -170,7 +170,7 @@ private static TypeBuilder DefineRunnableTypeBuilder( BenchmarkBuildInfo benchmark, ModuleBuilder moduleBuilder) { - // .class public auto ansi beforefieldinit BenchmarkDotNet.Autogenerated.Runnable_0 + // .class public auto ansi sealed beforefieldinit BenchmarkDotNet.Autogenerated.Runnable_0 // extends [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark var benchmarkDescriptor = benchmark.BenchmarkCase.Descriptor; @@ -184,7 +184,7 @@ private static TypeBuilder DefineRunnableTypeBuilder( } var result = moduleBuilder.DefineType( GetRunnableTypeName(benchmark), - workloadTypeAttributes, + workloadTypeAttributes | TypeAttributes.Sealed, workloadType); return result; @@ -242,11 +242,8 @@ private static void EmitNoArgsMethodCallPopReturn( private int jobUnrollFactor; private int dummyUnrollFactor; - private Type overheadDelegateType; - private Type workloadDelegateType; private TypeBuilder runnableBuilder; private ConsumableTypeInfo consumableInfo; - private ConsumeEmitter consumeEmitter; private ConsumableTypeInfo globalSetupReturnInfo; private ConsumableTypeInfo globalCleanupReturnInfo; private ConsumableTypeInfo iterationSetupReturnInfo; @@ -256,8 +253,6 @@ private static void EmitNoArgsMethodCallPopReturn( private FieldBuilder globalCleanupActionField; private FieldBuilder iterationSetupActionField; private FieldBuilder iterationCleanupActionField; - private FieldBuilder overheadDelegateField; - private FieldBuilder workloadDelegateField; private FieldBuilder notElevenField; private FieldBuilder dummyVarField; @@ -267,7 +262,6 @@ private static void EmitNoArgsMethodCallPopReturn( private MethodBuilder dummy1Method; private MethodBuilder dummy2Method; private MethodBuilder dummy3Method; - private MethodInfo workloadImplementationMethod; private MethodBuilder overheadImplementationMethod; private MethodBuilder overheadActionUnrollMethod; private MethodBuilder overheadActionNoUnrollMethod; @@ -316,16 +310,12 @@ private Type EmitRunnableCore(BenchmarkBuildInfo newBenchmark) dummy2Method = EmitDummyMethod(Dummy2MethodName, dummyUnrollFactor); dummy3Method = EmitDummyMethod(Dummy3MethodName, dummyUnrollFactor); - // 3. Emit impl - consumeEmitter.OnEmitMembers(runnableBuilder); - // Overhead impl overheadImplementationMethod = EmitOverheadImplementation(OverheadImplementationMethodName); overheadActionUnrollMethod = EmitOverheadAction(OverheadActionUnrollMethodName, jobUnrollFactor); overheadActionNoUnrollMethod = EmitOverheadAction(OverheadActionNoUnrollMethodName, 1); // Workload impl - workloadImplementationMethod = EmitWorkloadImplementation(WorkloadImplementationMethodName); workloadActionUnrollMethod = EmitWorkloadAction(WorkloadActionUnrollMethodName, jobUnrollFactor); workloadActionNoUnrollMethod = EmitWorkloadAction(WorkloadActionNoUnrollMethodName, 1); @@ -361,16 +351,13 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark) dummyUnrollFactor = DummyUnrollFactor; consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType); - consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo); globalSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalSetupMethod?.ReturnType); globalCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalCleanupMethod?.ReturnType); iterationSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationSetupMethod?.ReturnType); iterationCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationCleanupMethod?.ReturnType); - // Init types + // Init type runnableBuilder = DefineRunnableTypeBuilder(benchmark, moduleBuilder); - overheadDelegateType = EmitOverheadDelegateType(); - workloadDelegateType = EmitWorkloadDelegateType(); } private static ConsumableTypeInfo GetConsumableTypeInfo(Type methodReturnType) @@ -378,52 +365,6 @@ private static ConsumableTypeInfo GetConsumableTypeInfo(Type methodReturnType) return methodReturnType == null ? null : new ConsumableTypeInfo(methodReturnType); } - private Type EmitOverheadDelegateType() - { - // .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0OverheadDelegate - // extends[mscorlib]System.MulticastDelegate; - var overheadReturnType = EmitParameterInfo.CreateReturnParameter(consumableInfo.OverheadMethodReturnType); - - // replace arg names - var overheadParameters = Descriptor.WorkloadMethod.GetParameters() - .Select(p => - (ParameterInfo)new EmitParameterInfo( - p.Position, - ArgParamPrefix + p.Position, - p.ParameterType, - p.Attributes, - null)) - .ToArray(); - - return moduleBuilder.EmitCustomDelegate( - GetRunnableTypeName(benchmark) + OverheadDelegateTypeSuffix, - overheadReturnType, - overheadParameters); - } - - private Type EmitWorkloadDelegateType() - { - // .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0WorkloadDelegate - // extends [mscorlib]System.MulticastDelegate - var workloadReturnType = EmitParameterInfo.CreateReturnParameter(consumableInfo.WorkloadMethodReturnType); - - // Replace arg names - var workloadParameters = Descriptor.WorkloadMethod.GetParameters() - .Select(p => - (ParameterInfo)new EmitParameterInfo( - p.Position, - ArgParamPrefix + p.Position, - p.ParameterType, - p.Attributes, - null)) - .ToArray(); - - return moduleBuilder.EmitCustomDelegate( - GetRunnableTypeName(benchmark) + WorkloadDelegateTypeSuffix, - workloadReturnType, - workloadParameters); - } - private void DefineFields() { globalSetupActionField = @@ -434,10 +375,6 @@ private void DefineFields() runnableBuilder.DefineField(IterationSetupActionFieldName, typeof(Action), FieldAttributes.Private); iterationCleanupActionField = runnableBuilder.DefineField(IterationCleanupActionFieldName, typeof(Action), FieldAttributes.Private); - overheadDelegateField = - runnableBuilder.DefineField(OverheadDelegateFieldName, overheadDelegateType, FieldAttributes.Private); - workloadDelegateField = - runnableBuilder.DefineField(WorkloadDelegateFieldName, workloadDelegateType, FieldAttributes.Private); // Define arg fields foreach (var parameter in Descriptor.WorkloadMethod.GetParameters()) @@ -485,7 +422,6 @@ private void DefineFields() notElevenField = runnableBuilder.DefineField(NotElevenFieldName, typeof(int), FieldAttributes.Public); dummyVarField = runnableBuilder.DefineField(DummyVarFieldName, typeof(int), FieldAttributes.Private); - consumeEmitter.OnDefineFields(runnableBuilder); } private ConstructorBuilder DefineCtor() @@ -553,166 +489,117 @@ private MethodBuilder EmitDummyMethod(string methodName, int unrollFactor) private MethodBuilder EmitOverheadImplementation(string methodName) { - var overheadInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(overheadDelegateType); - //.method private hidebysig - // instance int32 __Overhead(int64 arg0) cil managed + // instance void __Overhead(int64 arg0) cil managed + + // Replace arg names + var parameters = Descriptor.WorkloadMethod.GetParameters() + .Select(p => + (ParameterInfo) new EmitParameterInfo( + p.Position, + ArgParamPrefix + p.Position, + p.ParameterType, + p.Attributes, + null)) + .ToArray(); + var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, - overheadInvokeMethod.ReturnParameter, - overheadInvokeMethod.GetParameters()); + EmitParameterInfo.CreateReturnVoidParameter(), + parameters) + .SetNoInliningImplementationFlag(); var ilBuilder = methodBuilder.GetILGenerator(); - var returnType = methodBuilder.ReturnType; - /* - // return default; - IL_0000: ldc.i4.0 + // return; IL_0001: ret */ - // optional local if default(T) uses .initobj - var optionalLocalForInitobj = ilBuilder.DeclareOptionalLocalForReturnDefault(returnType); - ilBuilder.EmitReturnDefault(returnType, optionalLocalForInitobj); + ilBuilder.EmitVoidReturn(methodBuilder); return methodBuilder; } - private MethodInfo EmitWorkloadImplementation(string methodName) - { - // Shortcut: DO NOT emit method if the result type is not awaitable - if (!consumableInfo.IsAwaitable) - return Descriptor.WorkloadMethod; - - var workloadInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); - - //.method private hidebysig - // instance int32 __Workload(int64 arg0) cil managed - var args = workloadInvokeMethod.GetParameters(); - var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( - methodName, - MethodAttributes.Private, - workloadInvokeMethod.ReturnParameter, - args); - args = methodBuilder.GetEmitParameters(args); - - var ilBuilder = methodBuilder.GetILGenerator(); - - /* - IL_0026: ldarg.0 - IL_0027: ldloc.0 - IL_0028: ldloc.1 - IL_0029: ldloc.2 - IL_002a: ldloc.3 - IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1 BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string) - */ - if (!Descriptor.WorkloadMethod.IsStatic) - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.EmitLdargs(args); - ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod); - - /* - // BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...); - IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1) - */ + private MethodBuilder EmitOverheadAction(string methodName, int unrollFactor) + => EmitActionImpl(methodName, overheadImplementationMethod, EmitCallOverhead, unrollFactor); - ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod); + private MethodBuilder EmitWorkloadAction(string methodName, int unrollFactor) + => EmitActionImpl(methodName, Descriptor.WorkloadMethod, EmitCallWorkload, unrollFactor); + private void EmitCallOverhead(ILGenerator ilBuilder, IReadOnlyList argLocals) + { /* - IL_0014: ret + // __Overhead(); + IL_0008: ldarg.0 + IL_0009: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__Overhead() */ - ilBuilder.Emit(OpCodes.Ret); - - return methodBuilder; + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdLocals(argLocals); + ilBuilder.Emit(OpCodes.Call, overheadImplementationMethod); } - private MethodBuilder EmitOverheadAction(string methodName, int unrollFactor) + private void EmitCallWorkload(ILGenerator ilBuilder, IReadOnlyList argLocals) { - return EmitActionImpl(methodName, RunnableActionKind.Overhead, unrollFactor); - } + /* + // InvokeOnceVoid(); + IL_0008: ldarg.0 + IL_0009: call instance void [BenchmarkDotNet.IntegrationTests]BenchmarkDotNet.IntegrationTests.InProcessEmitTest/BenchmarkAllCases::InvokeOnceVoid() + */ + MethodInfo invokeMethod = Descriptor.WorkloadMethod; + if (!invokeMethod.IsStatic) + { + ilBuilder.Emit(OpCodes.Ldarg_0); + } + ilBuilder.EmitLdLocals(argLocals); + ilBuilder.Emit(OpCodes.Call, invokeMethod); - private MethodBuilder EmitWorkloadAction(string methodName, int unrollFactor) - { - return EmitActionImpl(methodName, RunnableActionKind.Workload, unrollFactor); - } + if (consumableInfo.IsAwaitable) + { + /* + // BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...); + IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1) + */ + ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod); + } - private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actionKind, int unrollFactor) - { - FieldInfo actionDelegateField; - MethodInfo actionInvokeMethod; - switch (actionKind) + if (consumableInfo.WorkloadMethodReturnType != typeof(void)) { - case RunnableActionKind.Overhead: - actionDelegateField = overheadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(overheadDelegateType); - break; - case RunnableActionKind.Workload: - actionDelegateField = workloadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); - break; - default: - throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null); + // IL_000b: pop + ilBuilder.Emit(OpCodes.Pop); } + } + private MethodBuilder EmitActionImpl(string methodName, MethodInfo invokeMethod, Action> callMethodEmitter, int unrollFactor) + { // .method private hidebysig // instance void OverheadActionUnroll(int64 invokeCount) cil managed aggressiveoptimization - var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); + var invokeCountArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); var actionMethodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( methodName, MethodAttributes.Private, EmitParameterInfo.CreateReturnVoidParameter(), - toArg) + invokeCountArg) .SetAggressiveOptimizationImplementationFlag(); - toArg.SetMember(actionMethodBuilder); + invokeCountArg.SetMember(actionMethodBuilder); // Emit impl var ilBuilder = actionMethodBuilder.GetILGenerator(); - consumeEmitter.BeginEmitAction(actionMethodBuilder, ilBuilder, actionInvokeMethod, actionKind); // init locals var argLocals = EmitDeclareArgLocals(ilBuilder); - consumeEmitter.DeclareActionLocals(ilBuilder); - var indexLocal = ilBuilder.DeclareLocal(typeof(long)); // load fields EmitLoadArgFieldsToLocals(ilBuilder, argLocals); - consumeEmitter.EmitActionBeforeLoop(ilBuilder); // loop - var loopStartLabel = ilBuilder.DefineLabel(); - var loopHeadLabel = ilBuilder.DefineLabel(); - ilBuilder.EmitLoopBeginFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); + ilBuilder.EmitLoopBeginFromArgToZero(out var loopStartLabel, out var loopHeadLabel); { - /* - // overheadDelegate(); - IL_0005: ldarg.0 - IL_0006: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_000b: callvirt instance void BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke() - // -or- - // consumer.Consume(overheadDelegate(_argField)); - IL_000c: ldarg.0 - IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - IL_0012: ldarg.0 - IL_0013: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_0018: ldloc.0 - IL_0019: callvirt instance int32 BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke(int64) - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - */ for (int u = 0; u < unrollFactor; u++) { - consumeEmitter.EmitActionBeforeCall(ilBuilder); - - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); - ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); - - consumeEmitter.EmitActionAfterCall(ilBuilder); + callMethodEmitter(ilBuilder, argLocals); } } - ilBuilder.EmitLoopEndFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); - - consumeEmitter.EmitActionAfterLoop(ilBuilder); - consumeEmitter.CompleteEmitAction(ilBuilder); + ilBuilder.EmitLoopEndFromArgToZero(loopStartLabel, loopHeadLabel, invokeCountArg); // IL_003a: ret ilBuilder.EmitVoidReturn(actionMethodBuilder); @@ -810,9 +697,9 @@ private void EmitLoadArgFieldsToLocals(ILGenerator ilBuilder, IReadOnlyList Validate(ValidationParameters validationPara .Union(ValidateClassModifiers((validationParameters.Benchmarks)) .Union(ValidateAccessModifiers(validationParameters.Benchmarks)) .Union(ValidateBindingModifiers(validationParameters.Benchmarks)) + .Union(ValidateMethodImpl(validationParameters.Benchmarks)) ); private static IEnumerable ValidateClassModifiers(IEnumerable benchmarks) @@ -95,6 +96,17 @@ private static IEnumerable ValidateBindingModifiers(IEnumerable benchmark )); + private static IEnumerable ValidateMethodImpl(IEnumerable benchmarks) + => benchmarks.Where(x => !x.Descriptor.WorkloadMethod.MethodImplementationFlags.HasFlag(MethodImplAttributes.NoInlining)) + .Distinct(BenchmarkMethodEqualityComparer.Instance) + .Select(benchmark + => new ValidationError( + true, + $"Benchmarked method `{benchmark.Descriptor.WorkloadMethod.Name}` does not have MethodImplOptions.NoInlining flag set." + + $" You may need to rebuild your project, or apply it manually if you are not using MSBuild to build your project.", + benchmark + )); + private static bool IsValidCSharpIdentifier(string identifier) // F# allows to use whitespaces as names #479 => !string.IsNullOrEmpty(identifier) && (char.IsLetter(identifier[0]) || identifier[0] == Underscore) // An identifier must start with a letter or an underscore diff --git a/tests/BenchmarkDotNet.IntegrationTests.FSharp/Program.fs b/tests/BenchmarkDotNet.IntegrationTests.FSharp/Program.fs index e8192e4c62..5b44c26231 100644 --- a/tests/BenchmarkDotNet.IntegrationTests.FSharp/Program.fs +++ b/tests/BenchmarkDotNet.IntegrationTests.FSharp/Program.fs @@ -34,3 +34,10 @@ type EnumParamsTest() = [] member this.Benchmark() = if not (this.EnumParamValue = TestEnum.B) then failwith "Invalid Params value assigned" + +type AnonymousRecordTest() = + + [] + member _.AnonymousRecord() = + let array = Array.init 5 id + array |> Array.countBy (fun n -> {| Field = n |}) \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs index 29bf8275dd..1857d3dd25 100644 --- a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs @@ -11,27 +11,40 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Portability; using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Tests.XUnit; +using BenchmarkDotNet.Toolchains; using BenchmarkDotNet.Toolchains.InProcess.Emit; +using Perfolizer; using Perfolizer.Horology; +using Perfolizer.Mathematics.Common; +using Perfolizer.Mathematics.SignificanceTesting; +using Perfolizer.Mathematics.SignificanceTesting.MannWhitney; using Perfolizer.Metrology; using Xunit; using Xunit.Abstractions; namespace BenchmarkDotNet.IntegrationTests.ManualRunning { - public class ExpectedBenchmarkResultsTests : BenchmarkTestExecutor + public class ExpectedBenchmarkResultsTests(ITestOutputHelper output) : BenchmarkTestExecutor(output) { - // NativeAot takes a long time to build, so not including it in these tests. - // We also don't test InProcessNoEmitToolchain because it is known to be less accurate than code-gen toolchains. - private static readonly TimeInterval FallbackCpuResolutionValue = TimeInterval.FromNanoseconds(0.2d); - public ExpectedBenchmarkResultsTests(ITestOutputHelper output) : base(output) { } + // Visual Studio Test Explorer doesn't like to display IToolchain params separately, so use an enum instead. + public enum ToolchainType + { + Default, + InProcess + } - private static IEnumerable EmptyBenchmarkTypes() => - new[] + private IToolchain GetToolchain(ToolchainType toolchain) + => toolchain switch { + ToolchainType.Default => Job.Default.GetToolchain(), + ToolchainType.InProcess => InProcessEmitToolchain.Instance, + _ => throw new ArgumentOutOfRangeException() + }; + + private static IEnumerable EmptyBenchmarkTypes() => + [ typeof(EmptyVoid), typeof(EmptyByte), typeof(EmptySByte), @@ -46,70 +59,31 @@ private static IEnumerable EmptyBenchmarkTypes() => typeof(EmptyUIntPtr), typeof(EmptyVoidPointer), typeof(EmptyClass) - }; + ]; - public static IEnumerable InProcessData() + public static IEnumerable GetEmptyArgs() { foreach (var type in EmptyBenchmarkTypes()) { - yield return new object[] { type }; - } - } - - public static IEnumerable CoreData() - { - foreach (var type in EmptyBenchmarkTypes()) - { - yield return new object[] { type, RuntimeMoniker.Net70 }; - yield return new object[] { type, RuntimeMoniker.Mono70 }; - } - } - - public static IEnumerable FrameworkData() - { - foreach (var type in EmptyBenchmarkTypes()) - { - yield return new object[] { type, RuntimeMoniker.Net462 }; - yield return new object[] { type, RuntimeMoniker.Mono }; + yield return new object[] { ToolchainType.Default, type }; + // InProcess overhead measurements are incorrect in Core. https://github.com/dotnet/runtime/issues/89685 + if (!RuntimeInformation.IsNetCore) + { + yield return new object[] { ToolchainType.InProcess, type }; + } } } [Theory] - [MemberData(nameof(InProcessData))] - public void EmptyBenchmarksReportZeroTimeAndAllocated_InProcess(Type benchmarkType) - { - AssertZeroResults(benchmarkType, ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithToolchain(InProcessEmitToolchain.Instance) - )); - } - - [TheoryEnvSpecific("To not repeat tests in both Full .NET Framework and Core", EnvRequirement.DotNetCoreOnly)] - [MemberData(nameof(CoreData))] - public void EmptyBenchmarksReportZeroTimeAndAllocated_Core(Type benchmarkType, RuntimeMoniker runtimeMoniker) - { - AssertZeroResults(benchmarkType, ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithRuntime(runtimeMoniker.GetRuntime()) - )); - } - - [TheoryEnvSpecific("Can only run Full .NET Framework and Mono tests from Framework host", EnvRequirement.FullFrameworkOnly)] - [MemberData(nameof(FrameworkData))] - public void EmptyBenchmarksReportZeroTimeAndAllocated_Framework(Type benchmarkType, RuntimeMoniker runtimeMoniker) - { - AssertZeroResults(benchmarkType, ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithRuntime(runtimeMoniker.GetRuntime()) - )); - } - - private void AssertZeroResults(Type benchmarkType, IConfig config) + [MemberData(nameof(GetEmptyArgs))] + public void EmptyBenchmarkReportsZeroTimeAndAllocated(ToolchainType toolchain, Type benchmarkType) { - var summary = CanExecute(benchmarkType, config + var config = ManualConfig.CreateEmpty() + .AddJob(Job.Default.WithToolchain(GetToolchain(toolchain))) .WithSummaryStyle(SummaryStyle.Default.WithTimeUnit(TimeUnit.Nanosecond)) - .AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(false))) - ); + .AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(false))); + + var summary = CanExecute(benchmarkType, config); var cpuResolution = CpuDetector.Cpu?.MaxFrequency()?.ToResolution() ?? FallbackCpuResolutionValue; var threshold = new NumberValue(cpuResolution.Nanoseconds).ToThreshold(); @@ -126,87 +100,98 @@ private void AssertZeroResults(Type benchmarkType, IConfig config) } } - [Fact] - public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_InProcess() - { - AssertDifferentSizedStructsResults(ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithToolchain(InProcessEmitToolchain.Instance) - )); - } + private static IEnumerable NonEmptyBenchmarkTypes() => + [ + // Structs even as large as Struct128 results in zero measurements on Zen 5, so the test will only pass on older or different CPU architectures. + //typeof(DifferentSizedStructs), + typeof(ActualWork) + ]; - [TheoryEnvSpecific("To not repeat tests in both Full .NET Framework and Core", EnvRequirement.DotNetCoreOnly)] - [InlineData(RuntimeMoniker.Net70)] - [InlineData(RuntimeMoniker.Mono70)] - public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_Core(RuntimeMoniker runtimeMoniker) + public static IEnumerable GetNonEmptyArgs() { - AssertDifferentSizedStructsResults(ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithRuntime(runtimeMoniker.GetRuntime()) - )); - } - - [TheoryEnvSpecific("Can only run Full .NET Framework and Mono tests from Framework host", EnvRequirement.FullFrameworkOnly)] - [InlineData(RuntimeMoniker.Net462)] - [InlineData(RuntimeMoniker.Mono)] - public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_Framework(RuntimeMoniker runtimeMoniker) - { - AssertDifferentSizedStructsResults(ManualConfig.CreateEmpty() - .AddJob(Job.Default - .WithRuntime(runtimeMoniker.GetRuntime()) - )); + foreach (var type in NonEmptyBenchmarkTypes()) + { + // Framework is slightly less accurate than Core. + yield return new object[] { ToolchainType.Default, type, RuntimeInformation.IsNetCore ? 0 : 1 }; + // InProcess overhead measurements are incorrect in Core. https://github.com/dotnet/runtime/issues/89685 + if (!RuntimeInformation.IsNetCore) + { + yield return new object[] { ToolchainType.InProcess, type, 1 }; + } + } } - private void AssertDifferentSizedStructsResults(IConfig config) + [Theory] + [MemberData(nameof(GetNonEmptyArgs))] + public void NonEmptyBenchmarkReportsNonZeroTimeAndZeroAllocated(ToolchainType toolchain, Type benchmarkType, int subtractOverheadByClocks) { - var summary = CanExecute(config + var config = ManualConfig.CreateEmpty() + .AddJob(Job.Default.WithToolchain(GetToolchain(toolchain))) .WithSummaryStyle(SummaryStyle.Default.WithTimeUnit(TimeUnit.Nanosecond)) - .AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(false))) - ); + .AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(false))); + + var summary = CanExecute(benchmarkType, config); var cpuResolution = CpuDetector.Cpu?.MaxFrequency()?.ToResolution() ?? FallbackCpuResolutionValue; - var threshold = (cpuResolution / 2).ToThreshold(); + // Modern cpus can execute multiple instructions per clock cycle, + // resulting in measurements greater than 0 but less than 1 clock cycle. + // (example: Intel Core i9-9880H CPU 2.30GHz reports 0.2852 ns for `_field++;`) + var threshold = Threshold.Zero; + var overheadSubtraction = cpuResolution.Nanoseconds * subtractOverheadByClocks; foreach (var report in summary.Reports) { var workloadMeasurements = report.AllMeasurements.Where(m => m.Is(IterationMode.Workload, IterationStage.Actual)).GetStatistics().Sample; - var overheadMeasurements = report.AllMeasurements.Where(m => m.Is(IterationMode.Overhead, IterationStage.Actual)).GetStatistics().Sample; + var overheadMeasurements = new Sample(report.AllMeasurements + .Where(m => m.Is(IterationMode.Overhead, IterationStage.Actual)) + .GetStatistics().OriginalValues + .Select(x => x - overheadSubtraction).ToArray()); - bool isZero = ZeroMeasurementHelper.AreIndistinguishable(workloadMeasurements, overheadMeasurements, threshold); - Assert.False(isZero, $"Actual time was 0."); + var comparisonResult = new SimpleEquivalenceTest(MannWhitneyTest.Instance).Perform(workloadMeasurements, overheadMeasurements, threshold, SignificanceLevel.P1E5); + Assert.True(comparisonResult == ComparisonResult.Greater, "Workload measurements are not greater than overhead."); Assert.True((report.GcStats.GetBytesAllocatedPerOperation(report.BenchmarkCase) ?? 0L) == 0L, "Memory allocations measured above 0."); } } } +} - public struct Struct16 - { - public long l1, l2; - } +// Types outside of namespace so it's easier to read in the test explorer. +#pragma warning disable CA1050 // Declare types in namespaces +public struct Struct16 +{ + public long l1, l2; +} - public struct Struct32 - { - public long l1, l2, l3, l4; - } +public struct Struct32 +{ + public long l1, l2, l3, l4; +} - public struct Struct64 - { - public long l1, l2, l3, l4, l5, l6, l7, l8; - } +public struct Struct64 +{ + public long l1, l2, l3, l4, l5, l6, l7, l8; +} - public struct Struct128 - { - public long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16; - } +public struct Struct128 +{ + public long l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16; +} - public class DifferentSizedStructs - { - [Benchmark] public Struct16 Struct16() => default; - [Benchmark] public Struct32 Struct32() => default; - [Benchmark] public Struct64 Struct64() => default; - [Benchmark] public Struct128 Struct128() => default; - } +public class DifferentSizedStructs +{ + [Benchmark] public Struct16 Struct16() => default; + [Benchmark] public Struct32 Struct32() => default; + [Benchmark] public Struct64 Struct64() => default; + [Benchmark] public Struct128 Struct128() => default; +} + +public class ActualWork +{ + public int _field; + + [Benchmark] + public void IncrementField() => _field++; } public class EmptyVoid diff --git a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj index 16f12c41b4..e99703148c 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj +++ b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj @@ -40,10 +40,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/tests/BenchmarkDotNet.IntegrationTests/FSharpTests.cs b/tests/BenchmarkDotNet.IntegrationTests/FSharpTests.cs index 2b0928e9b4..73a8dc3c36 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/FSharpTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/FSharpTests.cs @@ -10,5 +10,9 @@ public FSharpTests(ITestOutputHelper output) : base(output) { } [Fact] public void ParamsSupportFSharpEnums() => CanExecute(); + + // #2530 + [Fact] + public void FSharpAnonymousRecordIsSupported() => CanExecute(); } } diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.cs index 1a2feea77b..9229359c4b 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.cs @@ -23,6 +23,9 @@ public class RunnableStructCaseBenchmark private bool _refResultHolder1; + [Benchmark] + public ref readonly bool RefReadonlyReturnStructCase1() => ref _refResultHolder1; + [Benchmark] public ref bool RefReturnStructCase1() => ref _refResultHolder1; @@ -33,6 +36,9 @@ public class RunnableStructCaseBenchmark private byte _refResultHolder2; + [Benchmark] + public ref readonly byte RefReadonlyReturnStructCase2() => ref _refResultHolder2; + [Benchmark] public ref byte RefReturnStructCase2() => ref _refResultHolder2; @@ -43,6 +49,9 @@ public class RunnableStructCaseBenchmark private sbyte _refResultHolder3; + [Benchmark] + public ref readonly sbyte RefReadonlyReturnStructCase3() => ref _refResultHolder3; + [Benchmark] public ref sbyte RefReturnStructCase3() => ref _refResultHolder3; @@ -53,6 +62,9 @@ public class RunnableStructCaseBenchmark private short _refResultHolder4; + [Benchmark] + public ref readonly short RefReadonlyReturnStructCase4() => ref _refResultHolder4; + [Benchmark] public ref short RefReturnStructCase4() => ref _refResultHolder4; @@ -63,6 +75,9 @@ public class RunnableStructCaseBenchmark private ushort _refResultHolder5; + [Benchmark] + public ref readonly ushort RefReadonlyReturnStructCase5() => ref _refResultHolder5; + [Benchmark] public ref ushort RefReturnStructCase5() => ref _refResultHolder5; @@ -73,6 +88,9 @@ public class RunnableStructCaseBenchmark private int _refResultHolder6; + [Benchmark] + public ref readonly int RefReadonlyReturnStructCase6() => ref _refResultHolder6; + [Benchmark] public ref int RefReturnStructCase6() => ref _refResultHolder6; @@ -83,6 +101,9 @@ public class RunnableStructCaseBenchmark private uint _refResultHolder7; + [Benchmark] + public ref readonly uint RefReadonlyReturnStructCase7() => ref _refResultHolder7; + [Benchmark] public ref uint RefReturnStructCase7() => ref _refResultHolder7; @@ -93,6 +114,9 @@ public class RunnableStructCaseBenchmark private long _refResultHolder8; + [Benchmark] + public ref readonly long RefReadonlyReturnStructCase8() => ref _refResultHolder8; + [Benchmark] public ref long RefReturnStructCase8() => ref _refResultHolder8; @@ -103,6 +127,9 @@ public class RunnableStructCaseBenchmark private ulong _refResultHolder9; + [Benchmark] + public ref readonly ulong RefReadonlyReturnStructCase9() => ref _refResultHolder9; + [Benchmark] public ref ulong RefReturnStructCase9() => ref _refResultHolder9; @@ -113,6 +140,9 @@ public class RunnableStructCaseBenchmark private IntPtr _refResultHolder10; + [Benchmark] + public ref readonly IntPtr RefReadonlyReturnStructCase10() => ref _refResultHolder10; + [Benchmark] public ref IntPtr RefReturnStructCase10() => ref _refResultHolder10; @@ -123,6 +153,9 @@ public class RunnableStructCaseBenchmark private UIntPtr _refResultHolder11; + [Benchmark] + public ref readonly UIntPtr RefReadonlyReturnStructCase11() => ref _refResultHolder11; + [Benchmark] public ref UIntPtr RefReturnStructCase11() => ref _refResultHolder11; @@ -133,6 +166,9 @@ public class RunnableStructCaseBenchmark private char _refResultHolder12; + [Benchmark] + public ref readonly char RefReadonlyReturnStructCase12() => ref _refResultHolder12; + [Benchmark] public ref char RefReturnStructCase12() => ref _refResultHolder12; @@ -143,6 +179,9 @@ public class RunnableStructCaseBenchmark private double _refResultHolder13; + [Benchmark] + public ref readonly double RefReadonlyReturnStructCase13() => ref _refResultHolder13; + [Benchmark] public ref double RefReturnStructCase13() => ref _refResultHolder13; @@ -153,6 +192,9 @@ public class RunnableStructCaseBenchmark private float _refResultHolder14; + [Benchmark] + public ref readonly float RefReadonlyReturnStructCase14() => ref _refResultHolder14; + [Benchmark] public ref float RefReturnStructCase14() => ref _refResultHolder14; @@ -163,6 +205,9 @@ public class RunnableStructCaseBenchmark private decimal _refResultHolder15; + [Benchmark] + public ref readonly decimal RefReadonlyReturnStructCase15() => ref _refResultHolder15; + [Benchmark] public ref decimal RefReturnStructCase15() => ref _refResultHolder15; @@ -173,6 +218,9 @@ public class RunnableStructCaseBenchmark private int? _refResultHolder16; + [Benchmark] + public ref readonly int? RefReadonlyReturnStructCase16() => ref _refResultHolder16; + [Benchmark] public ref int? RefReturnStructCase16() => ref _refResultHolder16; @@ -183,6 +231,9 @@ public class RunnableStructCaseBenchmark private (int, int) _refResultHolder17; + [Benchmark] + public ref readonly (int, int) RefReadonlyReturnStructCase17() => ref _refResultHolder17; + [Benchmark] public ref (int, int) RefReturnStructCase17() => ref _refResultHolder17; @@ -193,6 +244,9 @@ public class RunnableStructCaseBenchmark private DateTime _refResultHolder18; + [Benchmark] + public ref readonly DateTime RefReadonlyReturnStructCase18() => ref _refResultHolder18; + [Benchmark] public ref DateTime RefReturnStructCase18() => ref _refResultHolder18; @@ -203,6 +257,9 @@ public class RunnableStructCaseBenchmark private TimeSpan? _refResultHolder19; + [Benchmark] + public ref readonly TimeSpan? RefReadonlyReturnStructCase19() => ref _refResultHolder19; + [Benchmark] public ref TimeSpan? RefReturnStructCase19() => ref _refResultHolder19; @@ -213,6 +270,9 @@ public class RunnableStructCaseBenchmark private CustomEnumNonConsumable _refResultHolder20; + [Benchmark] + public ref readonly CustomEnumNonConsumable RefReadonlyReturnStructCase20() => ref _refResultHolder20; + [Benchmark] public ref CustomEnumNonConsumable RefReturnStructCase20() => ref _refResultHolder20; @@ -223,6 +283,9 @@ public class RunnableStructCaseBenchmark private CustomEnumConsumable _refResultHolder21; + [Benchmark] + public ref readonly CustomEnumConsumable RefReadonlyReturnStructCase21() => ref _refResultHolder21; + [Benchmark] public ref CustomEnumConsumable RefReturnStructCase21() => ref _refResultHolder21; @@ -233,6 +296,9 @@ public class RunnableStructCaseBenchmark private CustomStructNonConsumable _refResultHolder22; + [Benchmark] + public ref readonly CustomStructNonConsumable RefReadonlyReturnStructCase22() => ref _refResultHolder22; + [Benchmark] public ref CustomStructNonConsumable RefReturnStructCase22() => ref _refResultHolder22; @@ -243,6 +309,9 @@ public class RunnableStructCaseBenchmark private CustomStructConsumable _refResultHolder23; + [Benchmark] + public ref readonly CustomStructConsumable RefReadonlyReturnStructCase23() => ref _refResultHolder23; + [Benchmark] public ref CustomStructConsumable RefReturnStructCase23() => ref _refResultHolder23; @@ -253,6 +322,9 @@ public class RunnableStructCaseBenchmark private CustomStructConsumable _refResultHolder24; + [Benchmark] + public ref readonly CustomStructConsumable RefReadonlyReturnStructCase24() => ref _refResultHolder24; + [Benchmark] public ref CustomStructConsumable RefReturnStructCase24() => ref _refResultHolder24; @@ -263,6 +335,9 @@ public class RunnableStructCaseBenchmark private CustomStructConsumable _refResultHolder25; + [Benchmark] + public ref readonly CustomStructConsumable RefReadonlyReturnStructCase25() => ref _refResultHolder25; + [Benchmark] public ref CustomStructConsumable RefReturnStructCase25() => ref _refResultHolder25; diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.tt b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.tt index a0fe0c5282..8564d018a4 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.tt +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableStructCaseBenchmark.tt @@ -61,6 +61,9 @@ namespace BenchmarkDotNet.IntegrationTests.InProcess.EmitTests private <#=type#> _refResultHolder<#=counter#>; + [Benchmark] + public ref readonly <#=type#> RefReadonlyReturnStructCase<#=counter#>() => ref _refResultHolder<#=counter#>; + [Benchmark] public ref <#=type#> RefReturnStructCase<#=counter#>() => ref _refResultHolder<#=counter#>; diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.cs index 530daa56e3..67d128185c 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.cs @@ -34,13 +34,21 @@ public class RunnableTaskCaseBenchmark [Benchmark, Arguments(2, "2", 0.2)] public Task TaskCase2(int x, string y, double? z) => Task.FromResult(123); - // ---- Begin TaskCase(ValueTask) ---- + // ---- Begin TaskCase(ValueTask) ---- [Benchmark] - public ValueTask TaskCase3() => new ValueTask(""); + public ValueTask TaskCase3() => new ValueTask(); [Benchmark, Arguments(3, "3", 0.3)] - public ValueTask TaskCase3(int x, string y, double? z) => new ValueTask(""); + public ValueTask TaskCase3(int x, string y, double? z) => new ValueTask(); + + // ---- Begin TaskCase(ValueTask) ---- + + [Benchmark] + public ValueTask TaskCase4() => new ValueTask(""); + + [Benchmark, Arguments(4, "4", 0.4)] + public ValueTask TaskCase4(int x, string y, double? z) => new ValueTask(""); } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.tt b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.tt index 8b48133003..a29fae1b5f 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.tt +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests.T4/RunnableTaskCaseBenchmark.tt @@ -25,6 +25,7 @@ namespace BenchmarkDotNet.IntegrationTests.InProcess.EmitTests int counter = 1; EmitTaskCaseBenchmark(ref counter, "Task", "Task.CompletedTask"); EmitTaskCaseBenchmark(ref counter, "Task", "Task.FromResult(123)"); + EmitTaskCaseBenchmark(ref counter, "ValueTask", "new ValueTask()"); EmitTaskCaseBenchmark(ref counter, "ValueTask", "new ValueTask(\"\")"); // BDN do not support custom awaitables diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs index 65cd9c752e..75438fc0ce 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs @@ -31,7 +31,8 @@ public class NaiveRunnableEmitDiff { { OpCodes.Br_S, OpCodes.Br }, { OpCodes.Blt_S, OpCodes.Blt }, - { OpCodes.Bne_Un_S, OpCodes.Bne_Un } + { OpCodes.Bne_Un_S, OpCodes.Bne_Un }, + { OpCodes.Bge_S, OpCodes.Bge } }; public static void RunDiff(string roslynAssemblyPath, string emittedAssemblyPath, ILogger logger) diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs index 31fc7a9283..1049bcbe87 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/SampleBenchmark.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -43,6 +44,27 @@ public ref int RefReturnManyArgsCase(ref double i, int j, string k, object l) return ref refValueHolder; } + [Benchmark] + public unsafe int* ReturnsIntPointer() + { + Thread.Sleep(100); + return default; + } + + [Benchmark] + public unsafe void* ReturnsVoidPointer() + { + Thread.Sleep(100); + return default; + } + + [Benchmark] + public unsafe CustomStructNonConsumable* ReturnsStructPointer() + { + Thread.Sleep(100); + return default; + } + [Benchmark, Arguments(12)] public Task TaskSample(long arg) {