diff --git a/src/Antigen/Antigen/Antigen.cs b/src/Antigen/Antigen/Antigen.cs index e45bdc72..725cbe71 100644 --- a/src/Antigen/Antigen/Antigen.cs +++ b/src/Antigen/Antigen/Antigen.cs @@ -9,6 +9,7 @@ using Utils; using System.Linq; using System.Runtime.CompilerServices; +using Antigen.Compilation; using Antigen.Execution; using System.Reflection; using System.Runtime.InteropServices; @@ -89,8 +90,15 @@ internal static int Run(AntigenRootCommand command) TestCase.s_Driver = EEDriver.GetInstance(s_runOptions.CoreRun, Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ExecutionEngine.dll"), () => EnvVarOptions.TestVars(includeOsrSwitches: PRNG.Decide(0.3), false)); TestCase.s_TestRunner = TestRunner.GetInstance(TestCase.s_Driver, s_runOptions.CoreRun); - // Generate vector methods - VectorHelpers.RecordVectorMethods(); + // Generate vector methods. The flag must be set before recording, because the + // method pool is built once and reused for every test case. The pool and the + // compiler both read CORE_ROOT so that the API surface Antigen generates against + // is the same one the tests will execute against. + string coreRootDirectory = Path.GetDirectoryName(Path.GetFullPath(s_runOptions.CoreRun)); + Compiler.SetReferenceDirectory(coreRootDirectory); + VectorHelpers.AllowFloatToIntegralReinterpret = + result.GetValue(command.AllowFloatToIntegralReinterpret); + VectorHelpers.RecordVectorMethods(coreRootDirectory); Parallel.For(0, 4, (p) => RunTest()); Console.WriteLine($"Executed {s_testId} test cases."); diff --git a/src/Antigen/Antigen/Antigen.csproj b/src/Antigen/Antigen/Antigen.csproj index c72d3a78..854e8caa 100644 --- a/src/Antigen/Antigen/Antigen.csproj +++ b/src/Antigen/Antigen/Antigen.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Antigen/Antigen/AntigenRootCommand.cs b/src/Antigen/Antigen/AntigenRootCommand.cs index 74b0b285..7deb6009 100644 --- a/src/Antigen/Antigen/AntigenRootCommand.cs +++ b/src/Antigen/Antigen/AntigenRootCommand.cs @@ -17,6 +17,8 @@ internal sealed class AntigenRootCommand : RootCommand new("--NumTestCases", "-n") { Description = "Number of test cases to execute. By default, 1000." }; public Option RunDuration { get; } = new("--RunDuration", "-d") { Description = "Duration in minutes to run. By default until NumTestCases, but if Duration is given, will override the NumTestCases." }; + public Option AllowFloatToIntegralReinterpret { get; } = + new("--AllowFloatToIntegralReinterpret") { Description = "Allow reinterpret-casting floating point vectors to integral types (for example, Vector128.AsInt32()). Can cause false positives due to differing NaN representations." }; public ParseResult Result { get; private set; } @@ -26,6 +28,7 @@ public AntigenRootCommand(string[] args) : base("Antigen JIT fuzzer") Options.Add(IssuesFolder); Options.Add(NumTestCases); Options.Add(RunDuration); + Options.Add(AllowFloatToIntegralReinterpret); SetAction(result => { diff --git a/src/Antigen/Antigen/Expressions/ConstantValue.cs b/src/Antigen/Antigen/Expressions/ConstantValue.cs index ddb88b19..c2fb4f85 100644 --- a/src/Antigen/Antigen/Expressions/ConstantValue.cs +++ b/src/Antigen/Antigen/Expressions/ConstantValue.cs @@ -20,6 +20,13 @@ public class ConstantValue : Expression { "Vector4", new List() { "One", "Zero", "UnitW", "UnitX", "UnitY", "UnitZ" } }, }; + // Reduce the prevelence of vector-of-floats.AllBitsSet(), as NaN's tend to pollute + // otherwise interesting computations + private const double FloatingPointAllBitsSetProbability = 0.05; + private const double IntegralAllBitsSetProbability = 0.5; + + private static readonly List s_nonNaNVectorConstants = new List() { "Zero", "One", "Indices" }; + protected ConstantValue(Tree.ValueType valueType, string value) : base(null) { if (valueType.PrimitiveType == Primitive.Char) @@ -87,7 +94,13 @@ public static ConstantValue GetConstantValue(Tree.ValueType literalType, IList> 8); h *= 1099511628211UL;"); + staticMethodBuilder.AppendLine("}"); + staticMethodBuilder.AppendLine("return (int)(h ^ (h >> 32));"); staticMethodBuilder.AppendLine("}"); // Log method diff --git a/src/Antigen/Antigen/Helpers/VectorHelpers.cs b/src/Antigen/Antigen/Helpers/VectorHelpers.cs index 8f021866..b1a6b686 100644 --- a/src/Antigen/Antigen/Helpers/VectorHelpers.cs +++ b/src/Antigen/Antigen/Helpers/VectorHelpers.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Numerics; using System.Reflection; @@ -19,11 +20,24 @@ namespace Antigen { public class VectorHelpers { - private static readonly List s_vectorGenericArgs = new() { typeof(byte), typeof(sbyte), - typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double) }; + private static readonly string[] s_vectorGenericArgNames = new[] + { + "System.Byte", "System.SByte", "System.Int16", "System.UInt16", "System.Int32", + "System.UInt32", "System.Int64", "System.UInt64", "System.Single", "System.Double" + }; + + private static List s_vectorGenericArgs = null; private static List s_allVectorMethods = null; private static List s_allVectorTypes = null; + // When set, build the method pool by reading CORE_ROOT rather than by reflecting over + // Antigen's own runtime. + private static MetadataLoadContext s_metadataContext = null; + private static Assembly s_coreLib = null; + + // Allow float-to-int reinterpret casts (Vector.As) in the method pool + public static bool AllowFloatToIntegralReinterpret = false; + public static List GetAllVectorMethods() { Debug.Assert(s_allVectorMethods != null); @@ -138,7 +152,13 @@ private static void RecordVectorTypes() ]; } - public static void RecordVectorMethods() + /// + /// Build the method pool. When is supplied, the + /// pool is read from CORE_ROOT's assemblies (the framework the generated tests will + /// actually execute against). When null, falls back to reflecting over Antigen's own + /// runtime, which is the historical behavior. + /// + public static void RecordVectorMethods(string coreRootDirectory = null) { Debug.Assert(s_allVectorTypes == null); @@ -147,63 +167,286 @@ public static void RecordVectorMethods() return; } + InitializeMetadataContext(coreRootDirectory); + RecordVectorTypes(); s_allVectorMethods = new List(); - RecordIntrinsicMethods(typeof(Vector)); - RecordIntrinsicMethods(typeof(Vector2)); - RecordIntrinsicMethods(typeof(Vector3)); - RecordIntrinsicMethods(typeof(Vector4)); - RecordVectorCtors(typeof(Vector2)); - RecordVectorCtors(typeof(Vector3)); - RecordVectorCtors(typeof(Vector4)); - RecordIntrinsicMethods(typeof(Vector64)); - RecordIntrinsicMethods(typeof(Vector128)); - RecordIntrinsicMethods(typeof(Vector256)); - RecordIntrinsicMethods(typeof(Vector512)); - RecordIntrinsicMethods(typeof(AdvSimd)); - RecordIntrinsicMethods(typeof(AdvSimd.Arm64), "AdvSimd.Arm64"); - RecordIntrinsicMethods(typeof(Sve)); - RecordIntrinsicMethods(typeof(System.Runtime.Intrinsics.X86.Aes)); - RecordIntrinsicMethods(typeof(Bmi1)); - RecordIntrinsicMethods(typeof(Bmi1.X64), "Bmi1.X64"); - RecordIntrinsicMethods(typeof(Bmi2)); - RecordIntrinsicMethods(typeof(Bmi2.X64), "Bmi2.X64"); - RecordIntrinsicMethods(typeof(Fma)); - RecordIntrinsicMethods(typeof(Lzcnt)); - RecordIntrinsicMethods(typeof(Lzcnt.X64), "Lzcnt.X64"); - RecordIntrinsicMethods(typeof(Pclmulqdq)); - RecordIntrinsicMethods(typeof(Popcnt)); - RecordIntrinsicMethods(typeof(Popcnt.X64), "Popcnt.X64"); - RecordIntrinsicMethods(typeof(Avx)); - RecordIntrinsicMethods(typeof(Avx2)); - RecordIntrinsicMethods(typeof(Avx512BW)); - RecordIntrinsicMethods(typeof(Avx512CD)); - RecordIntrinsicMethods(typeof(Avx512DQ)); - RecordIntrinsicMethods(typeof(Avx512F)); - RecordIntrinsicMethods(typeof(Avx512Vbmi)); - RecordIntrinsicMethods(typeof(Sse)); - RecordIntrinsicMethods(typeof(Sse2)); - RecordIntrinsicMethods(typeof(Sse3)); - RecordIntrinsicMethods(typeof(Sse41)); - RecordIntrinsicMethods(typeof(Sse42)); - RecordIntrinsicMethods(typeof(Sse)); + s_vectorGenericArgs = s_vectorGenericArgNames.Select(ResolveType).Where(t => t != null).ToList(); + if (s_vectorGenericArgs.Count != s_vectorGenericArgNames.Length) + { + throw new InvalidOperationException( + $"Could not resolve the primitive types used to instantiate generic vector methods. " + + $"Resolved {s_vectorGenericArgs.Count} of {s_vectorGenericArgNames.Length}."); + } + + RecordIntrinsicMethods("System.Numerics.Vector", "Vector"); + RecordIntrinsicMethods("System.Numerics.Vector2", "Vector2"); + RecordIntrinsicMethods("System.Numerics.Vector3", "Vector3"); + RecordIntrinsicMethods("System.Numerics.Vector4", "Vector4"); + RecordVectorCtors("System.Numerics.Vector2"); + RecordVectorCtors("System.Numerics.Vector3"); + RecordVectorCtors("System.Numerics.Vector4"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Vector64", "Vector64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Vector128", "Vector128"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Vector256", "Vector256"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Vector512", "Vector512"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Arm.AdvSimd", "AdvSimd"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Arm.AdvSimd+Arm64", "AdvSimd.Arm64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.Arm.Sve", "Sve"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Aes", "Aes"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Bmi1", "Bmi1"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Bmi1+X64", "Bmi1.X64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Bmi2", "Bmi2"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Bmi2+X64", "Bmi2.X64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Fma", "Fma"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Lzcnt", "Lzcnt"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Lzcnt+X64", "Lzcnt.X64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Pclmulqdq", "Pclmulqdq"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Popcnt", "Popcnt"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Popcnt+X64", "Popcnt.X64"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx", "Avx"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx2", "Avx2"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx512BW", "Avx512BW"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx512CD", "Avx512CD"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx512DQ", "Avx512DQ"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx512F", "Avx512F"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Avx512Vbmi", "Avx512Vbmi"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse", "Sse"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse2", "Sse2"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse3", "Sse3"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse41", "Sse41"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse42", "Sse42"); + RecordIntrinsicMethods("System.Runtime.Intrinsics.X86.Sse", "Sse"); } - private static bool ShouldSkipVectorMethod(string fullMethodName) + /// + /// Open CORE_ROOT for metadata-only inspection. Nothing here is ever executed, so the + /// assemblies do not need to match the runtime Antigen is running on. + /// + private static void InitializeMetadataContext(string coreRootDirectory) { + if (string.IsNullOrEmpty(coreRootDirectory)) + { + return; + } + + string coreLibPath = Path.Combine(coreRootDirectory, "System.Private.CoreLib.dll"); + if (!File.Exists(coreLibPath)) + { + Console.WriteLine($"WARNING: {coreLibPath} not found; falling back to Antigen's own framework " + + $"for the method pool. Generated tests may use APIs that do not exist in CORE_ROOT."); + return; + } + + s_metadataContext = new MetadataLoadContext( + new PathAssemblyResolver(Directory.GetFiles(coreRootDirectory, "*.dll"))); + s_coreLib = s_metadataContext.LoadFromAssemblyPath(coreLibPath); + } + + /// + /// Resolve a type by full metadata name from CORE_ROOT when available, otherwise from + /// Antigen's own runtime. + /// + private static Type ResolveType(string fullName) + { + if (s_coreLib != null) + { + Type fromCoreLib = s_coreLib.GetType(fullName); + if (fromCoreLib != null) + { + return fromCoreLib; + } + + foreach (var assembly in s_metadataContext.GetAssemblies()) + { + Type candidate = assembly.GetType(fullName); + if (candidate != null) + { + return candidate; + } + } + + Console.WriteLine($"WARNING: '{fullName}' was not found in CORE_ROOT's " + + $"System.Private.CoreLib (searched {s_metadataContext.GetAssemblies().Count()} " + + $"loaded assemblies). Any methods on it will be missing from the pool."); + return null; + } + + Type fromOwnRuntime = Type.GetType(fullName); + if (fromOwnRuntime == null) + { + Console.WriteLine($"WARNING: '{fullName}' was not found in Antigen's own framework. " + + $"Any methods on it will be missing from the pool."); + } + + return fromOwnRuntime; + } + + /// + /// True if every generic argument of appears somewhere in its + /// parameter list, and so can be inferred by the C# compiler at a call site that does not + /// spell out type arguments. + /// + private static bool CanInferGenericArguments(MethodInfo method) + { + var genericArguments = method.GetGenericArguments(); + var parameters = method.GetParameters(); + + foreach (var genericArgument in genericArguments) + { + bool found = false; + foreach (var parameter in parameters) + { + if (MentionsType(parameter.ParameterType, genericArgument)) + { + found = true; + break; + } + } + + if (!found) + { + return false; + } + } + return true; + } + + private static bool MentionsType(Type type, Type sought) + { + if (type.IsGenericParameter) + { + return type.Name == sought.Name; + } + + if (type.HasElementType) + { + return MentionsType(type.GetElementType(), sought); + } + + if (type.IsGenericType) + { + foreach (var argument in type.GetGenericArguments()) + { + if (MentionsType(argument, sought)) + { + return true; + } + } + } + + return false; + } + + private static bool ShouldSkipVectorMethod(string fullMethodName) { // We do not support these types, so ignore these methods. + // Need both "Byref" and "&" as MethodInfo.ToString() can render these differently return fullMethodName.Contains("IntPtr") || fullMethodName.Contains("ValueTuple") || fullMethodName.Contains("Matrix") || fullMethodName.Contains("Span") || fullMethodName.Contains("Quaternion") || fullMethodName.Contains("[]") || fullMethodName.Contains("*") || fullMethodName.Contains("ByRef") || + fullMethodName.Contains("&") || fullMethodName.Contains("Numerics.Plane") || fullMethodName.Contains("Divide") || /*fullMethodName.Contains("SveMaskPattern") ||*/ fullMethodName.Contains("SvePrefetchType") || fullMethodName.Contains("FloatComparisonMode") || fullMethodName.Contains("FloatRoundingMode") || fullMethodName.Contains("MidpointRounding") || fullMethodName.Contains("Unsafe"); } + // Look for reinterpret casts away from a floating point element type using "Vector.As", + // and for the "*WhereAllBitsSet" bit tests applied to a floating point vector. + // A pretty common source of false positives is + // some_fp_vector -> Vector.As -> some_other_vector -> print_bits + // The JIT is permitted to choose different bitwise representations for NaN + private static bool IsFloatToIntegralReinterpretation(MethodInfo method) + { + if (IsAllBitsSetTestOnFloat(method)) + { + return true; + } + + if (!method.Name.StartsWith("As", StringComparison.Ordinal) || + method.Name.Equals("Asin", StringComparison.Ordinal)) + { + return false; + } + + var parameters = method.GetParameters(); + if (parameters.Length != 1) + { + return false; + } + + return IsFloatingPointElementVector(parameters[0].ParameterType); + } + + // Same issue with "*WhereAllBitsSet" family (CountWhereAllBitsSet, AnyWhereAllBitsSet, ...) + // As for Vector.As* + private static bool IsAllBitsSetTestOnFloat(MethodInfo method) + { + if (!method.Name.EndsWith("WhereAllBitsSet", StringComparison.Ordinal)) + { + return false; + } + + foreach (var parameter in method.GetParameters()) + { + if (IsFloatingPointElementVector(parameter.ParameterType)) + { + return true; + } + } + + return false; + } + + /// + /// Returns the element type of a closed generic vector type, or null if the type is not + /// one (Vector2/Vector3/Vector4 and open generics both return null). + /// + private static Type VectorElementType(Type type) + { + if (!type.IsGenericType || type.ContainsGenericParameters) + { + return null; + } + + var genericArgs = type.GetGenericArguments(); + return genericArgs.Length == 1 ? genericArgs[0] : null; + } + + private static bool IsFloatingPointElementVector(Type type) + { + var elementType = VectorElementType(type); + // Compared by name, not by typeof(): when the pool is built from CORE_ROOT metadata + // these Types come from a MetadataLoadContext, so reference equality against the + // runtime's typeof(float) is always false and this filter would silently stop working. + return elementType != null && + (elementType.FullName == "System.Single" || elementType.FullName == "System.Double"); + } + + private static void RecordIntrinsicMethods(string typeFullName, string vectorTypeName) + { + Type resolved = ResolveType(typeFullName); + if (resolved == null) + { + // Expected for intrinsic classes absent from this framework version. + return; + } + + RecordIntrinsicMethods(resolved, vectorTypeName); + } + + private static void RecordVectorCtors(string typeFullName) + { + Type resolved = ResolveType(typeFullName); + if (resolved != null) + { + RecordVectorCtors(resolved); + } + } + /// /// Record the vector methods as well as the ones that creates the Vector. /// Applicable for Vector64, Vector128, Vector256, Vector512. @@ -234,6 +477,11 @@ private static void RecordIntrinsicMethods(Type vectorType, string vectorTypeNam continue; } + if (!AllowFloatToIntegralReinterpret && IsFloatToIntegralReinterpretation(method)) + { + continue; + } + s_allVectorMethods.Add(CreateMethodSignature(vectorTypeName, method)); nonGenericAdded.Add(method.Name); } @@ -261,12 +509,25 @@ private static void RecordIntrinsicMethods(Type vectorType, string vectorTypeNam continue; } + if (!CanInferGenericArguments(method)) + { + // Something like "Vector128.Pi()" with no args breaks our type inference, + // leave these out + continue; + } + if (method.GetGenericArguments().Count() == 1) { // Only instantiate generic single argument methods foreach (var genericArgument in s_vectorGenericArgs) { var genericInitVectorMethod = method.MakeGenericMethod(genericArgument); + + if (!AllowFloatToIntegralReinterpret && IsFloatToIntegralReinterpretation(genericInitVectorMethod)) + { + continue; + } + s_allVectorMethods.Add(CreateMethodSignature(vectorTypeName, genericInitVectorMethod)); } } diff --git a/src/Antigen/Antigen/TestCase.cs b/src/Antigen/Antigen/TestCase.cs index e333fb36..a8abdb71 100644 --- a/src/Antigen/Antigen/TestCase.cs +++ b/src/Antigen/Antigen/TestCase.cs @@ -38,7 +38,6 @@ public enum CompilationType "Attempted to divide by zero.", "Arithmetic operation resulted in an overflow.", "isCandidateVar(fieldVarDsc) == isMultiReg", // https://github.com/dotnet/runtime/issues/85628 - "curSize < maxSplitSize", // https://github.com/dotnet/runtime/issues/91251 }; private SyntaxNode testCaseRoot; @@ -177,8 +176,12 @@ public TestResult Verify() { return TestResult.Overflow; } + + // Known error that we have no specific bucket for (e.g. the JIT + // assert in _knownDiffs). Report it, but do not save a repro: + // it is an already-filed issue. + return TestResult.OtherError; } - return TestResult.OtherError; } var parsedError = RslnUtilities.ParseAssertionError(errorMessage); parsedError = parsedError ?? errorMessage; diff --git a/src/Antigen/Antigen/TestMethod.cs b/src/Antigen/Antigen/TestMethod.cs index fc41d567..f2d2237e 100644 --- a/src/Antigen/Antigen/TestMethod.cs +++ b/src/Antigen/Antigen/TestMethod.cs @@ -925,12 +925,12 @@ private Expression MethodCallHelper(MethodSignature methodSig, int depth) { if ((methodSig.MethodName.Contains("GetElement") || methodSig.MethodName.Contains("WithElement")) && (parameter.ParamName == "index")) { - // For GetElement/WithElement, the index should not exceed the element count. So perform modulo operation for (argExpr % ElementCount) + // For GetElement/WithElement, the index must stay below the element count. VectorType targetVectorType = methodSig.Parameters[0].ParamType.VectorType; ConstantValue elementCountExpr; if (Tree.ValueType.GetElementCount(targetVectorType) == 1) { - elementCountExpr = ConstantValue.GetConstantValue(1); + elementCountExpr = ConstantValue.GetConstantValue(0); } else { diff --git a/src/Antigen/Antigen/Tree/Types.cs b/src/Antigen/Antigen/Tree/Types.cs index 0fbef450..13bf6281 100644 --- a/src/Antigen/Antigen/Tree/Types.cs +++ b/src/Antigen/Antigen/Tree/Types.cs @@ -118,6 +118,34 @@ public bool IsVectorNumerics() return false; } + public bool HasFloatingPointElement() + { + if (!IsVectorType) + { + return false; + } + + switch (VectorType) + { + case VectorType.Vector64_Float: + case VectorType.Vector64_Double: + case VectorType.Vector128_Float: + case VectorType.Vector128_Double: + case VectorType.Vector256_Float: + case VectorType.Vector256_Double: + case VectorType.Vector512_Float: + case VectorType.Vector512_Double: + case VectorType.Vector_Float: + case VectorType.Vector_Double: + case VectorType.Vector2: + case VectorType.Vector3: + case VectorType.Vector4: + return true; + default: + return false; + } + } + public bool IsVectorTIntrinsics() { if (IsVectorType) diff --git a/src/Antigen/ExecutionEngine/Program.cs b/src/Antigen/ExecutionEngine/Program.cs index 76e15dc9..01175e0b 100644 --- a/src/Antigen/ExecutionEngine/Program.cs +++ b/src/Antigen/ExecutionEngine/Program.cs @@ -81,7 +81,20 @@ static async Task Main(string[] args) /// /// /// + // As in Fuzzlyn, run generated code on a dedicated large stack thread. + // Generated code has deep call trees and frequently stack overlfows, otherwise + private const int GeneratedCodeStackSizeBytes = 64 * 1024 * 1024; + private static RunResult Run(byte[] assemblyBytes) + { + RunResult result = default; + Thread runnerThread = new(() => result = RunOnCurrentThread(assemblyBytes), GeneratedCodeStackSizeBytes); + runnerThread.Start(); + runnerThread.Join(); + return result; + } + + private static RunResult RunOnCurrentThread(byte[] assemblyBytes) { int hashCode; var assembly = s_loader.LoadFromBytes(assemblyBytes); diff --git a/src/Antigen/Trimmer/TestTrimmer.cs b/src/Antigen/Trimmer/TestTrimmer.cs index 2d863409..9f1acae0 100644 --- a/src/Antigen/Trimmer/TestTrimmer.cs +++ b/src/Antigen/Trimmer/TestTrimmer.cs @@ -70,6 +70,11 @@ internal static int Run(TrimmerRootCommand command) AltJitMethodName = result.GetValue(command.AltJitMethodName), }; + // Trimmer runs in a separate process, so it does not inherit Antigen's + // process-local compiler configuration. + string coreRootDirectory = Path.GetDirectoryName(Path.GetFullPath(opts.CoreRunPath)); + Compiler.SetReferenceDirectory(coreRootDirectory); + int.TryParse(opts.ParentPid, out s_parentProcessId); Task monitorTask = Task.Run(() => MonitorParentProcess()); diff --git a/src/Antigen/Utilities/Compilation/Compiler.cs b/src/Antigen/Utilities/Compilation/Compiler.cs index ded1ce22..411803ef 100644 --- a/src/Antigen/Utilities/Compilation/Compiler.cs +++ b/src/Antigen/Utilities/Compilation/Compiler.cs @@ -35,15 +35,32 @@ public class Compiler { "SYSLIB5003", ReportDiagnostic.Suppress } }); - private static readonly string s_corelibPath = typeof(object).Assembly.Location; - private static readonly MetadataReference[] s_references = -{ - MetadataReference.CreateFromFile(s_corelibPath), - MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(s_corelibPath)!, "System.Console.dll")), - MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(s_corelibPath)!, "System.Runtime.dll")), - MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location), - MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location), - }; + // Generated tests must compile against CORE_ROOT, not Antigen's own framework, because + // they execute under CORE_ROOT's corerun. Initialize to Antigen's framework to preserve + // the previous behavior for callers that do not configure a reference directory. + private static MetadataReference[] s_references = + CreateReferences(Path.GetDirectoryName(typeof(object).Assembly.Location) ?? + throw new InvalidOperationException("Could not locate Antigen's framework directory.")); + + /// + /// Point compilation at CORE_ROOT. Must be called before the first Compile(). + /// + public static void SetReferenceDirectory(string referenceDirectory) + { + s_references = CreateReferences(referenceDirectory); + } + + private static MetadataReference[] CreateReferences(string referenceDirectory) + { + return new MetadataReference[] + { + MetadataReference.CreateFromFile(Path.Combine(referenceDirectory, "System.Private.CoreLib.dll")), + MetadataReference.CreateFromFile(Path.Combine(referenceDirectory, "System.Console.dll")), + MetadataReference.CreateFromFile(Path.Combine(referenceDirectory, "System.Runtime.dll")), + MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location), + }; + } private readonly string m_outputDirectory;