From ac236600f1b802e163da242fdd1eb563057bc611 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Wed, 19 Aug 2026 14:49:31 -0700 Subject: [PATCH 1/2] spawn antigen tests in new thread with higher stack limit add a method StableHash and call that instead of GethashCode to make the repros easier to work with fix test bucketing float to int reinterpret off by default. Reduce float allbitsset probability reflect over core_root so we get up to date intrinsics have trimmer use up to date corerun remove lazy initialization of metadata paths fix indexing into single element vectors clean up comments reword comments. add logging for unresolved types. remove HasByRefParameter clean up some comments fixup another comment --- src/Antigen/Antigen/Antigen.cs | 12 +- src/Antigen/Antigen/Antigen.csproj | 1 + src/Antigen/Antigen/AntigenRootCommand.cs | 3 + .../Antigen/Expressions/ConstantValue.cs | 15 +- src/Antigen/Antigen/Helpers/PreGenerated.cs | 15 +- src/Antigen/Antigen/Helpers/VectorHelpers.cs | 332 +++++++++++++++--- src/Antigen/Antigen/TestCase.cs | 7 +- src/Antigen/Antigen/TestMethod.cs | 4 +- src/Antigen/Antigen/Tree/Types.cs | 28 ++ src/Antigen/ExecutionEngine/Program.cs | 13 + src/Antigen/Trimmer/TestTrimmer.cs | 5 + src/Antigen/Utilities/Compilation/Compiler.cs | 35 +- 12 files changed, 411 insertions(+), 59 deletions(-) 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..e1addb25 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,273 @@ 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 float->integral reinterpret casts using "Vector.As". + // A pretty common source of false positives is + // some_fp_vector -> Vector.As -> some_int_vector -> print_bits + // The JIT is permitted to choose different bitwise representations for NaN + private static bool IsFloatToIntegralReinterpretation(MethodInfo method) + { + if (!method.Name.StartsWith("As", StringComparison.Ordinal)) + { + return false; + } + + var parameters = method.GetParameters(); + if (parameters.Length != 1) + { + return false; + } + + return IsFloatingPointElementVector(parameters[0].ParameterType) && + IsIntegralElementVector(method.ReturnType); + } + + /// + /// 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 readonly HashSet s_integralElementTypeNames = new HashSet() + { + "System.Byte", "System.SByte", "System.Int16", "System.UInt16", + "System.Int32", "System.UInt32", "System.Int64", "System.UInt64", + "System.IntPtr", "System.UIntPtr" + }; + + private static bool IsIntegralElementVector(Type type) + { + var elementType = VectorElementType(type); + return elementType != null && s_integralElementTypeNames.Contains(elementType.FullName); + } + + 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 +464,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 +496,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; From dcfcc3c57d356b834a768503271e03641d8cf0a9 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Thu, 10 Sep 2026 14:32:50 -0700 Subject: [PATCH 2/2] need to also look for *WhereAllBitsSet family --- src/Antigen/Antigen/Helpers/VectorHelpers.cs | 49 +++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/Antigen/Antigen/Helpers/VectorHelpers.cs b/src/Antigen/Antigen/Helpers/VectorHelpers.cs index e1addb25..b1a6b686 100644 --- a/src/Antigen/Antigen/Helpers/VectorHelpers.cs +++ b/src/Antigen/Antigen/Helpers/VectorHelpers.cs @@ -354,13 +354,20 @@ private static bool ShouldSkipVectorMethod(string fullMethodName) { fullMethodName.Contains("MidpointRounding") || fullMethodName.Contains("Unsafe"); } - // Look for float->integral reinterpret casts using "Vector.As". + // 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_int_vector -> print_bits + // 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 (!method.Name.StartsWith("As", StringComparison.Ordinal)) + if (IsAllBitsSetTestOnFloat(method)) + { + return true; + } + + if (!method.Name.StartsWith("As", StringComparison.Ordinal) || + method.Name.Equals("Asin", StringComparison.Ordinal)) { return false; } @@ -371,8 +378,27 @@ private static bool IsFloatToIntegralReinterpretation(MethodInfo method) return false; } - return IsFloatingPointElementVector(parameters[0].ParameterType) && - IsIntegralElementVector(method.ReturnType); + 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; } /// @@ -400,19 +426,6 @@ private static bool IsFloatingPointElementVector(Type type) (elementType.FullName == "System.Single" || elementType.FullName == "System.Double"); } - private static readonly HashSet s_integralElementTypeNames = new HashSet() - { - "System.Byte", "System.SByte", "System.Int16", "System.UInt16", - "System.Int32", "System.UInt32", "System.Int64", "System.UInt64", - "System.IntPtr", "System.UIntPtr" - }; - - private static bool IsIntegralElementVector(Type type) - { - var elementType = VectorElementType(type); - return elementType != null && s_integralElementTypeNames.Contains(elementType.FullName); - } - private static void RecordIntrinsicMethods(string typeFullName, string vectorTypeName) { Type resolved = ResolveType(typeFullName);