From 3aecec6f2424634fa42a9ff4f92ea2eab411a897 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 18 Jan 2024 14:54:02 +0100 Subject: [PATCH] Add --collect-spmi-to to collect SPMI collections When used, it should point to an existing directory in which SPMI collections of all of the host invocations will be created. It requires the SPMI shim to exist next to the host. --- Fuzzlyn/ExecutionServerPool.cs | 8 ++-- Fuzzlyn/FuzzlynOptions.cs | 1 + Fuzzlyn/Helpers.cs | 7 ++++ Fuzzlyn/Program.cs | 18 ++++++++- Fuzzlyn/Reduction/Reducer.cs | 20 +++++++++- Fuzzlyn/RunningExecutionServer.cs | 8 +++- Fuzzlyn/SpmiSetupOptions.cs | 63 +++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 Fuzzlyn/SpmiSetupOptions.cs diff --git a/Fuzzlyn/ExecutionServerPool.cs b/Fuzzlyn/ExecutionServerPool.cs index 459421e9..483fcd21 100644 --- a/Fuzzlyn/ExecutionServerPool.cs +++ b/Fuzzlyn/ExecutionServerPool.cs @@ -10,12 +10,14 @@ internal class ExecutionServerPool // Stop a server once it has not been used for this duration private static readonly TimeSpan s_inactivityPeriod = TimeSpan.FromMinutes(3); - public ExecutionServerPool(string host) + public ExecutionServerPool(string host, SpmiSetupOptions spmiOptions) { Host = host; + SpmiOptions = spmiOptions; } public string Host { get; } + public SpmiSetupOptions SpmiOptions { get; } private List _pool = new(); @@ -46,7 +48,7 @@ private RunningExecutionServer Get(bool keepNonEmptyEagerly) if (startNew) { - RunningExecutionServer created = RunningExecutionServer.Create(Host); + RunningExecutionServer created = RunningExecutionServer.Create(Host, SpmiOptions); lock (_pool) { _pool.Add(created); @@ -56,7 +58,7 @@ private RunningExecutionServer Get(bool keepNonEmptyEagerly) if (bestServer != null) return bestServer; - return RunningExecutionServer.Create(Host); + return RunningExecutionServer.Create(Host, SpmiOptions); } private void Return(RunningExecutionServer server) diff --git a/Fuzzlyn/FuzzlynOptions.cs b/Fuzzlyn/FuzzlynOptions.cs index cb8fe128..de1029c9 100644 --- a/Fuzzlyn/FuzzlynOptions.cs +++ b/Fuzzlyn/FuzzlynOptions.cs @@ -17,6 +17,7 @@ internal class FuzzlynOptions public bool Output { get; set; } = false; public bool EnableChecksumming { get; set; } = true; public bool Reduce { get; set; } = false; + public string CollectSpmiTo { get; set; } public bool Execute { get; set; } = true; public bool Stats { get; set; } = false; public KnownErrors KnownErrors { get; set; } diff --git a/Fuzzlyn/Helpers.cs b/Fuzzlyn/Helpers.cs index 3cf4e7ea..21680c66 100644 --- a/Fuzzlyn/Helpers.cs +++ b/Fuzzlyn/Helpers.cs @@ -73,4 +73,11 @@ public static void SetExecutionEnvironmentVariables(StringDictionary envVars) envVars["DOTNET_TieredCompilation"] = "0"; envVars["DOTNET_JitThrowOnAssertionFailure"] = "1"; } + + public static void SetSpmiCollectionEnvironmentVariables(StringDictionary envVars, SpmiSetupOptions options) + { + envVars["DOTNET_JitName"] = options.ShimName; + envVars["SuperPMIShimLogPath"] = options.CollectionDirectory; + envVars["SuperPMIShimPath"] = options.JitPath; + } } diff --git a/Fuzzlyn/Program.cs b/Fuzzlyn/Program.cs index 7e85b2c8..3b424704 100644 --- a/Fuzzlyn/Program.cs +++ b/Fuzzlyn/Program.cs @@ -34,6 +34,7 @@ private static void Main(string[] args) bool? enableChecksumming = null; bool? reduce = null; string reduceDebugGitDir = null; + string collectSpmiTo = null; string outputPath = null; bool? stats = null; bool? execute = null; @@ -60,6 +61,7 @@ private static void Main(string[] args) { "reduce", "Reduce program to a minimal example", v => reduce = v != null }, { "output=", "Output program source to this path. Also enables writing updates in the console during reduction.", v => outputPath = v }, { "reduce-debug-git-dir=", "Create reduce path in specified dir (must not exists beforehand)", v => reduceDebugGitDir = v }, + { "collect-spmi-to=", "Collect SPMI collections to specified directory", v => collectSpmiTo = v }, { "stats", "Generate a bunch of programs and record their sizes", v => stats = v != null }, { "execute", "Whether or not to execute the generated and compiled programs (enabled by default, disable with --execute-) ", v => execute = v != null }, { "known-errors=", "A JSON file of known error strings that will be ignored, or the string \"dotnet/runtime\" to use a built-in list for the tip of dotnet/runtime.", v => knownErrors = v }, @@ -117,6 +119,8 @@ private static void Main(string[] args) options.EnableChecksumming = enableChecksumming.Value; if (reduce.HasValue) options.Reduce = reduce.Value; + if (collectSpmiTo != null) + options.CollectSpmiTo = collectSpmiTo; if (stats.HasValue) options.Stats = stats.Value; if (execute.HasValue) @@ -176,11 +180,21 @@ private static bool CreateExecutionServerPool(FuzzlynOptions options) { if (!File.Exists(options.Host)) { - Console.WriteLine("Error: invalid host specified"); + Console.WriteLine("Error: invalid host specified ({0})", options.Host); return false; } - s_executionServerPool = new ExecutionServerPool(options.Host); + SpmiSetupOptions spmiOptions = null; + if (options.CollectSpmiTo != null) + { + spmiOptions = SpmiSetupOptions.Create(options.CollectSpmiTo, options.Host, out string error); + if (spmiOptions == null) + { + Console.WriteLine("Error: {0}", error); + return false; + } + } + s_executionServerPool = new ExecutionServerPool(options.Host, spmiOptions); return true; } diff --git a/Fuzzlyn/Reduction/Reducer.cs b/Fuzzlyn/Reduction/Reducer.cs index 9742fc6e..80d8fa2b 100644 --- a/Fuzzlyn/Reduction/Reducer.cs +++ b/Fuzzlyn/Reduction/Reducer.cs @@ -651,10 +651,16 @@ private bool IsStandaloneProgramInteresting(CompilationUnitSyntax prog) (string debugStdout, string debugStderr) = ExecuteInSubProcess(prog, Compiler.DebugOptions.WithOutputKind(OutputKind.ConsoleApplication), tempAsmPath); (string releaseStdout, string releaseStderr) = ExecuteInSubProcess(prog, Compiler.ReleaseOptions.WithOutputKind(OutputKind.ConsoleApplication), tempAsmPath); if (debugStderr.Contains("Assert failure") || debugStderr.Contains("JIT assert failed")) + { + Console.WriteLine("Standalone is interesting due to assert failure in debug:{0}{1}", Environment.NewLine, debugStderr); return true; + } if (releaseStderr.Contains("Assert failure") || releaseStderr.Contains("JIT assert failed")) + { + Console.WriteLine("Standalone is interesting due to assert failure in release:{0}{1}", Environment.NewLine, releaseStderr); return true; + } string[] debugStderrLines = debugStderr.ReplaceLineEndings().Split(Environment.NewLine); string[] releaseStderrLines = releaseStderr.ReplaceLineEndings().Split(Environment.NewLine); @@ -663,9 +669,18 @@ private bool IsStandaloneProgramInteresting(CompilationUnitSyntax prog) string debugExceptionLine = debugStderrLines.FirstOrDefault(l => l.Contains("Unhandled exception.")); string releaseExceptionLine = releaseStderrLines.FirstOrDefault(l => l.Contains("Unhandled exception.")); if (debugExceptionLine != releaseExceptionLine) + { + Console.WriteLine("Standalone is interesting due to different unhandled exceptions:{0}{1}{0}VS{0}{2}", Environment.NewLine, debugExceptionLine, releaseExceptionLine); + return true; + } + + if (debugStdout != releaseStdout) + { + Console.WriteLine("Standalone is interesting due to different stdout:{0}{1}{0}VS{0}{2}", Environment.NewLine, debugStdout, releaseStdout); return true; + } - return debugStdout != releaseStdout; + return false; } finally { @@ -705,6 +720,9 @@ private bool IsStandaloneProgramInteresting(CompilationUnitSyntax prog) Helpers.SetExecutionEnvironmentVariables(info.EnvironmentVariables); + if (_pool.SpmiOptions != null) + Helpers.SetSpmiCollectionEnvironmentVariables(info.EnvironmentVariables, _pool.SpmiOptions); + // WER mode is inherited by child, so if this is a crash we can // make sure no WER dialog opens by disabling it for our own // process. diff --git a/Fuzzlyn/RunningExecutionServer.cs b/Fuzzlyn/RunningExecutionServer.cs index b7dbab94..ccdde65a 100644 --- a/Fuzzlyn/RunningExecutionServer.cs +++ b/Fuzzlyn/RunningExecutionServer.cs @@ -124,7 +124,7 @@ public void Kill() } } - public static RunningExecutionServer Create(string host) + public static RunningExecutionServer Create(string host, SpmiSetupOptions spmiOptions) { string executorPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Fuzzlyn.ExecutionServer.dll"); ProcessStartInfo info = new() @@ -140,6 +140,12 @@ public static RunningExecutionServer Create(string host) info.ArgumentList.Add(executorPath); Helpers.SetExecutionEnvironmentVariables(info.EnvironmentVariables); + + if (spmiOptions != null) + { + Helpers.SetSpmiCollectionEnvironmentVariables(info.EnvironmentVariables, spmiOptions); + } + Process proc = Process.Start(info); return new RunningExecutionServer(proc); } diff --git a/Fuzzlyn/SpmiSetupOptions.cs b/Fuzzlyn/SpmiSetupOptions.cs new file mode 100644 index 00000000..f2fe9c8c --- /dev/null +++ b/Fuzzlyn/SpmiSetupOptions.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fuzzlyn; + +internal class SpmiSetupOptions +{ + public string CollectionDirectory { get; private set; } + public string ShimPath { get; private set; } + public string ShimName { get; private set; } + public string JitPath { get; private set; } + + public static SpmiSetupOptions Create(string collectionDir, string host, out string error) + { + if (!Directory.Exists(collectionDir)) + { + error = $"SPMI collection directory {collectionDir} does not exist"; + return null; + } + + SpmiSetupOptions options = new() + { + CollectionDirectory = collectionDir + }; + + string hostDir = Path.GetDirectoryName(host); + (string shimName, string jitName)[] setups = + { + ("superpmi-shim-collector.dll", "clrjit.dll"), + ("libsuperpmi-shim-collector.so", "libclrjit.so"), + ("libsuperpmi-shim-collector.dylib", "libclrjit.dylib") + }; + + foreach ((string shimName, string jitName) in setups) + { + string shimPath = Path.Combine(hostDir, shimName); + if (!File.Exists(shimPath)) + { + continue; + } + + string jitPath = Path.Combine(hostDir, jitName); + if (!File.Exists(jitPath)) + { + error = $"Expected JIT to exist next to SPMI shim (at {jitPath})"; + return null; + } + + options.ShimPath = shimPath; + options.ShimName = shimName; + options.JitPath = jitPath; + error = null; + return options; + } + + error = $"Could not find an SPMI shim in host directory {hostDir}"; + return null; + } +}