Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
eacd9d2
Initial plan
Copilot Jul 13, 2026
f5f5cce
Preserve IL for CoreCLR interpreter fallback
Copilot Jul 13, 2026
7462331
Complete IL stripping validation
Copilot Jul 13, 2026
bbff0da
Fix fixed ISA handling for stripped R2R images
kotlarmilos Jul 14, 2026
7276e8a
Merge main into IL stripping fixes
kotlarmilos Jul 14, 2026
6f8d7a0
Potential fix for pull request finding
kotlarmilos Jul 14, 2026
12d0b93
Potential fix for pull request finding
kotlarmilos Jul 14, 2026
3b82391
Address fixed instruction set review feedback
kotlarmilos Jul 15, 2026
a4ce2cf
Keep fixed ISA handling in crossgen2
kotlarmilos Jul 16, 2026
7729441
Restore existing CorInfo formatting
kotlarmilos Jul 16, 2026
ab2af53
Remove unrelated formatting changes
kotlarmilos Jul 16, 2026
027c501
Dedup fixed-instruction-set target check and relocate crossgen2 ISA h…
kotlarmilos Jul 17, 2026
c099887
Drop doc comment on IsFixedInstructionSetTarget for parity with surro…
kotlarmilos Jul 17, 2026
8b8c810
Drop comment on GetFixedInstructionSetSupport for parity with surroun…
kotlarmilos Jul 17, 2026
3309739
Merge branch 'main' into copilot/clr-ios-track-il-stripping-issues
kotlarmilos Jul 17, 2026
7d4fd8b
Refactor crossgen2 fixed-instruction-set handling per review feedback
kotlarmilos Jul 20, 2026
5c33a99
Fix Apple mobile ReadyToRun startup crash from baseline instruction-s…
kotlarmilos Jul 20, 2026
4537bbc
Drop explanatory comment on baseline signature unsupported-entry guard
kotlarmilos Jul 20, 2026
9c231ea
Move GetTargetAllowsRuntimeCodeGeneration into crossgen2 Program.cs
kotlarmilos Jul 21, 2026
c86b9e1
Merge branch 'main' into copilot/clr-ios-track-il-stripping-issues
kotlarmilos Jul 21, 2026
fae3aeb
Remove host-specific FEATURE_DYNAMIC_CODE_COMPILED gating from crossgen2
kotlarmilos Jul 22, 2026
7e1cc8d
Remove dead FEATURE_DYNAMIC_CODE_COMPILED define from ILCompiler.Read…
kotlarmilos Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/coreclr/tools/Common/InstructionSetHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Internal.TypeSystem;

using InstructionSet = Internal.JitInterface.InstructionSet;
using InstructionSetFlags = Internal.JitInterface.InstructionSetFlags;

namespace System.CommandLine
{
Expand Down Expand Up @@ -353,5 +354,35 @@ public static InstructionSetSupport ConfigureInstructionSetSupport(string instru
InstructionSetSupportBuilder.GetNonSpecifiableInstructionSetsForArch(targetArchitecture),
targetArchitecture);
}

// Produces an InstructionSetSupport where the instruction sets are fixed at compile time: every
// specifiable instruction set that is not already supported is marked explicitly unsupported, and the
// supported sets are also treated as optimistic. This is used for targets without runtime code generation
// (for example Apple mobile and WASM), where the pre-compiled code must hard code its ISA usage because
// there is no JIT to recover from an instruction set mismatch.
public static InstructionSetSupport GetFixedInstructionSetSupport(InstructionSetSupport instructionSetSupport)
Comment thread
kotlarmilos marked this conversation as resolved.
{
InstructionSetFlags unsupportedInstructionSets = instructionSetSupport.ExplicitlyUnsupportedFlags;
foreach (var instructionSetInfo in InstructionSetFlags.ArchitectureToValidInstructionSets(instructionSetSupport.Architecture))
{
if (instructionSetInfo.Specifiable &&
!instructionSetSupport.IsInstructionSetSupported(instructionSetInfo.InstructionSet))
{
unsupportedInstructionSets.AddInstructionSet(instructionSetInfo.InstructionSet);
}
}
unsupportedInstructionSets.ExpandInstructionSetByReverseImplication(instructionSetSupport.Architecture);
Comment thread
kotlarmilos marked this conversation as resolved.
unsupportedInstructionSets.Set64BitInstructionSetVariants(instructionSetSupport.Architecture);

if (instructionSetSupport.Architecture is TargetArchitecture.X86 or TargetArchitecture.ARM)
unsupportedInstructionSets.Set64BitInstructionSetVariantsUnconditionally(instructionSetSupport.Architecture);

return new InstructionSetSupport(
instructionSetSupport.SupportedFlags,
unsupportedInstructionSets,
instructionSetSupport.SupportedFlags,
instructionSetSupport.NonSpecifiableFlags,
instructionSetSupport.Architecture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ public void RuntimeAsyncStripILBodiesPreservesTaskReturningIL()
new(nameof(RuntimeAsyncStripILBodiesPreservesTaskReturningIL), [new CrossgenAssembly(stripILBodies)])
{
Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize, Crossgen2Option.StripILBodies],
AdditionalArgs = ["--targetarch:x64"],
Validate = Validate,
},
]));
Expand All @@ -593,6 +594,7 @@ static void Validate(ReadyToRunReader reader)
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "PlainStrippableMethod", out diag), diag);
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "ComputeTag", out diag), diag);
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "Root", out diag), diag);
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "UsesRuntimeCheckedInstructionSet", out diag), diag);
Comment thread
kotlarmilos marked this conversation as resolved.

Comment thread
kotlarmilos marked this conversation as resolved.
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "AsyncTaskMethod", out diag), diag);
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "AsyncValueTaskMethod", out diag), diag);
Expand All @@ -604,6 +606,50 @@ static void Validate(ReadyToRunReader reader)
}
}

[Fact]
public void AppleMobileStripILBodiesUsesFixedInstructionSet()
{
var stripILBodies = new CompiledAssembly
{
AssemblyName = nameof(AppleMobileStripILBodiesUsesFixedInstructionSet),
SourceResourceNames =
[
"RuntimeAsync/StripILBodies.cs",
"RuntimeAsync/RuntimeAsyncMethodGenerationAttribute.cs",
],
Features = { RuntimeAsyncFeature },
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(AppleMobileStripILBodiesUsesFixedInstructionSet),
[
new(nameof(AppleMobileStripILBodiesUsesFixedInstructionSet), [new CrossgenAssembly(stripILBodies)])
{
Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize, Crossgen2Option.StripILBodies],
AdditionalArgs = ["--targetos:ios", "--targetarch:arm64"],
Validate = Validate,
},
]));

static void Validate(ReadyToRunReader reader)
{
string componentFile = Path.Combine(
Path.GetDirectoryName(reader.Filename)!,
nameof(AppleMobileStripILBodiesUsesFixedInstructionSet) + ".dll");

Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "PlainStrippableMethod", out string diag), diag);
Assert.True(R2RAssert.MethodILIsStripped(componentFile, "StripILBodies", "UsesRuntimeCheckedInstructionSet", out diag), diag);
Comment thread
kotlarmilos marked this conversation as resolved.
Assert.False(
R2RAssert.HasFixupKindOnMethod(
reader,
ReadyToRunFixupKind.Check_InstructionSetSupport,
".UsesRuntimeCheckedInstructionSet(",
out diag),
diag);
Assert.True(R2RAssert.EagerInstructionSetSupportHasNoUnsupportedEntries(reader, out diag), diag);
}
}

/// <summary>
/// PR #123643: Async methods capturing GC refs across await points
/// produce ContinuationLayout fixups encoding the GC ref map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using ArmAes = System.Runtime.Intrinsics.Arm.Aes;
using X86Aes = System.Runtime.Intrinsics.X86.Aes;

public static class StripILBodies
{
[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down Expand Up @@ -82,6 +85,12 @@ public static int PlainStrippableMethod(int a, int b)
return a + b + ComputeTag();
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool UsesRuntimeCheckedInstructionSet()
{
return X86Aes.IsSupported || ArmAes.IsSupported || ArmAes.Arm64.IsSupported;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int ComputeTag()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,47 @@ public static bool HasFixupKindCountOnMethod(ReadyToRunReader reader, ReadyToRun
return true;
}

/// <summary>
/// Returns true if the global eager baseline <see cref="ReadyToRunFixupKind.Check_InstructionSetSupport"/>
/// fixup does not assert that any instruction set must be absent at runtime ("must be absent" entries render
/// with a <c>-</c> suffix; supported entries use <c>+</c>). Targets that cannot generate code at runtime must
/// not encode these assertions, as a failing eager fixup fatally disables all ReadyToRun code with no JIT fallback.
/// </summary>
Comment thread
kotlarmilos marked this conversation as resolved.
public static bool EagerInstructionSetSupportHasNoUnsupportedEntries(ReadyToRunReader reader, out string diagnostic)
{
var options = new SignatureFormattingOptions();
var signatures = new List<string>();
foreach (ReadyToRunImportSection section in reader.ImportSections)
{
if (section.Entries is null)
continue;

Comment thread
kotlarmilos marked this conversation as resolved.
foreach (ReadyToRunImportSection.ImportSectionEntry entry in section.Entries)
{
if (entry.Signature is not null && entry.Signature.FixupKind == ReadyToRunFixupKind.Check_InstructionSetSupport)
signatures.Add(entry.Signature.ToString(options));
}
}

if (signatures.Count == 0)
{
diagnostic = "Expected a global Check_InstructionSetSupport eager fixup, but none was found.";
return false;
}

var withUnsupported = signatures.Where(s => s.Contains('-')).ToList();
if (withUnsupported.Count > 0)
{
diagnostic =
"Global Check_InstructionSetSupport fixup must not assert any instruction set is absent " +
$"on no-JIT targets, but found: [{string.Join(", ", withUnsupported)}]";
return false;
}

diagnostic = $"Global Check_InstructionSetSupport fixup asserts only supported instruction sets: [{string.Join(", ", signatures)}]";
return true;
}

/// <summary>
/// Returns true if the R2R image contains at least one fixup of the given kind.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ public class ReadyToRunInstructionSetSupportSignature : Signature
{
string _instructionSetsSupport;

public static string ToInstructionSetSupportString(InstructionSetSupport instructionSetSupport)
public static string ToInstructionSetSupportString(InstructionSetSupport instructionSetSupport, bool emitExplicitlyUnsupported = true)
{
StringBuilder builder = new StringBuilder();
InstructionSet[] supportedInstructionSets = instructionSetSupport.SupportedFlags.ToArray();
Array.Sort(supportedInstructionSets);
InstructionSet[] explicitlyUnsupportedInstructionSets = instructionSetSupport.ExplicitlyUnsupportedFlags.ToArray();
Array.Sort(explicitlyUnsupportedInstructionSets);

bool addDelimiter = false;
var r2rAlreadyEmitted = new HashSet<ReadyToRunInstructionSet>();
Expand All @@ -43,19 +41,25 @@ public static string ToInstructionSetSupportString(InstructionSetSupport instruc
builder.Append(',');
r2rAlreadyEmitted.Clear();

addDelimiter = false;
foreach (var instructionSetUnsupported in explicitlyUnsupportedInstructionSets)
if (emitExplicitlyUnsupported)
{
var r2rInstructionSet = instructionSetUnsupported.R2RInstructionSet(instructionSetSupport.Architecture);
if (r2rInstructionSet == null)
continue;
InstructionSet[] explicitlyUnsupportedInstructionSets = instructionSetSupport.ExplicitlyUnsupportedFlags.ToArray();
Array.Sort(explicitlyUnsupportedInstructionSets);

if (r2rAlreadyEmitted.Add(r2rInstructionSet.Value))
addDelimiter = false;
foreach (var instructionSetUnsupported in explicitlyUnsupportedInstructionSets)
{
if (addDelimiter)
builder.Append('-');
addDelimiter = true;
builder.Append(r2rInstructionSet.Value.ToString());
var r2rInstructionSet = instructionSetUnsupported.R2RInstructionSet(instructionSetSupport.Architecture);
if (r2rInstructionSet == null)
continue;

if (r2rAlreadyEmitted.Add(r2rInstructionSet.Value))
{
if (addDelimiter)
builder.Append('-');
addDelimiter = true;
builder.Append(r2rInstructionSet.Value.ToString());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,11 @@ internal ReadyToRunCodegenCompilation(
CompilationModuleGroup = (ReadyToRunCompilationModuleGroupBase)nodeFactory.CompilationModuleGroup;

// Generate baseline support specification for InstructionSetSupport. This will prevent usage of the generated
// code if the runtime environment doesn't support the specified instruction set
string instructionSetSupportString = ReadyToRunInstructionSetSupportSignature.ToInstructionSetSupportString(instructionSetSupport);
// code if the runtime environment doesn't support the specified instruction set. Targets that cannot generate
// code at runtime must not encode "must be absent" assertions, since a failing eager fixup is a fatal startup
// error with no JIT fallback (see ToInstructionSetSupportString).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since a failing eager fixup is a fatal startup error with no JIT fallback (see ToInstructionSetSupportString

If we have no JIT fallback, we want to make sure that everything both the AOT compiler and the runtime are configured to use the same fixed instruction set so that the instruction support eager fixup would never fail.

It is fine to delete the instruction support eager fixup as an optimization.

'Targets that cannot generate code at runtime must not encode "must be absent" assertions' reasoning looks suspect to me. It says "oh, this was failing at runtime so have stopped generating to make the failure go away". Is this just hiding a bug? Is this change still necessary to fix the issues that this PR is fixing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Apple arm64 the baseline signature emits "must be absent" assertions for the opportunistic instruction sets that fall outside the fixed baseline. GetFixedInstructionSetSupport widens that absent set to the full complement of the fixed baseline, and the device hardware actually provides many of those sets, so the eager Check_InstructionSetSupport fixup fails at load and disables all ReadyToRun code. With IL stripped and no JIT there is no fallback, so startup crashes.

Crossgen2 already fixes the required set through Helpers.GetFixedInstructionSetSupport, and the device has all of it, so the required half already agrees and the check passes. The excluded set is the half that pinning cannot fix. It says the CPU must not have the optional features the code skipped. Apple hardware does have those features, so this check always fails on that hardware. A failed fixup is fatal without a JIT. That is why aligning the fixed set does not prevent the failure.

On a JIT target a mismatch rejects the baseline method and the JIT recompiles for the richer hardware. Without a JIT that path does not exist, so their only effect is to reject valid fixed-baseline code and crash.

I kept the "must be present" half as an invariant check, since those sets are guaranteed present on these targets. I can drop the whole eager fixup for no-JIT targets instead if you prefer that.

bool targetAllowsRuntimeCodeGeneration = ((ReadyToRunCompilerContext)nodeFactory.TypeSystemContext).TargetAllowsRuntimeCodeGeneration;
string instructionSetSupportString = ReadyToRunInstructionSetSupportSignature.ToInstructionSetSupportString(instructionSetSupport, emitExplicitlyUnsupported: targetAllowsRuntimeCodeGeneration);
ReadyToRunInstructionSetSupportSignature instructionSetSupportSig = new ReadyToRunInstructionSetSupportSignature(instructionSetSupportString);
_dependencyGraph.AddRoot(new Import(NodeFactory.EagerImports, instructionSetSupportSig), "Baseline instruction set support");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ public ReadyToRunCompilerContext(
TargetDetails details,
SharedGenericsMode genericsMode,
bool bubbleIncludesCoreModule,
bool targetAllowsRuntimeCodeGeneration,
InstructionSetSupport instructionSetSupport,
CompilerTypeSystemContext oldTypeSystemContext)
: base(details, genericsMode)
{
BubbleIncludesCoreModule = bubbleIncludesCoreModule;
TargetAllowsRuntimeCodeGeneration = targetAllowsRuntimeCodeGeneration;
InstructionSetSupport = instructionSetSupport;
_r2rFieldLayoutAlgorithm = new ReadyToRunMetadataFieldLayoutAlgorithm();
_systemObjectFieldLayoutAlgorithm = new SystemObjectFieldLayoutAlgorithm(_r2rFieldLayoutAlgorithm);
Expand Down Expand Up @@ -99,27 +101,7 @@ public ReadyToRunCompilerContext(

public InstructionSetSupport InstructionSetSupport { get; }

public bool TargetAllowsRuntimeCodeGeneration
{
get
{
#if FEATURE_DYNAMIC_CODE_COMPILED
if (Target.OperatingSystem is TargetOS.iOS or TargetOS.iOSSimulator or TargetOS.MacCatalyst or TargetOS.tvOS or TargetOS.tvOSSimulator)
{
return false;
}

if (Target.Architecture is TargetArchitecture.Wasm32)
{
return false;
}

return true;
#else
return false;
#endif
}
}
public bool TargetAllowsRuntimeCodeGeneration { get; }

public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>READYTORUN;$(DefineConstants)</DefineConstants>
<DefineConstants Condition="'$(FeatureDynamicCodeCompiled)' == 'true'">$(DefineConstants);FEATURE_DYNAMIC_CODE_COMPILED</DefineConstants>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<Platforms>x64;x86;arm;arm64</Platforms>
<PlatformTarget>AnyCPU</PlatformTarget>
Expand Down
35 changes: 24 additions & 11 deletions src/coreclr/tools/aot/crossgen2/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,34 @@ public int Run()

var logger = new Logger(Console.Out, Get(_command.IsVerbose));

// Crossgen2 is partial AOT and its pre-compiled methods can be
// thrown away at runtime if they mismatch in required ISAs or
// computed layouts of structs. Thus we want to ensure that usage
// of Vector<T> is only optimistic and doesn't hard code a dependency
// that would cause the entire image to be invalidated.
bool isVectorTOptimistic = true;

(TargetArchitecture targetArchitecture, TargetOS targetOS, TargetAbi targetAbi) =
Helpers.GetTargetSpec(Get(_command.TargetArchitecture), Get(_command.TargetOS));
bool targetAllowsRuntimeCodeGeneration = GetTargetAllowsRuntimeCodeGeneration(targetOS, targetArchitecture);

// Crossgen2 is partial AOT and its pre-compiled methods can be thrown away at runtime if
// they mismatch in required ISAs or computed layouts of structs. On targets that allow
// runtime code generation we keep Vector<T> usage optimistic so we never hard code a
// dependency that could invalidate the entire image. On targets that do not allow
// runtime code generation, we do not want an ISA mismatch to invalidate any code
// in the image. There we make Vector<T> non-optimistic and hard code the ISA support
// to avoid falling back to the interpreter.
bool isVectorTOptimistic = targetAllowsRuntimeCodeGeneration;
bool allowOptimistic = _command.OptimizationMode != OptimizationMode.PreferSize;

if (targetOS is TargetOS.iOS or TargetOS.tvOS or TargetOS.iOSSimulator or TargetOS.tvOSSimulator or TargetOS.MacCatalyst or TargetOS.Browser or TargetOS.Wasi)
if (!targetAllowsRuntimeCodeGeneration)
{
// These platforms do not support jitted code, so we want to ensure that we don't
// need to fall back to the interpreter for any hardware-intrinsic optimizations.
// Disable optimistic instruction sets by default.
allowOptimistic = false;
}

InstructionSetSupport instructionSetSupport = Helpers.ConfigureInstructionSetSupport(Get(_command.InstructionSet), Get(_command.MaxVectorTBitWidth), isVectorTOptimistic, targetArchitecture, targetOS,
SR.InstructionSetMustNotBe, SR.InstructionSetInvalidImplication, logger,
allowOptimistic: allowOptimistic,
isReadyToRun: true);
if (!targetAllowsRuntimeCodeGeneration)
{
instructionSetSupport = Helpers.GetFixedInstructionSetSupport(instructionSetSupport);
}

SharedGenericsMode genericsMode = SharedGenericsMode.CanonicalReferenceTypes;
var targetDetails = new TargetDetails(targetArchitecture, targetOS, targetAbi, instructionSetSupport.GetVectorTSimdVector());

Expand Down Expand Up @@ -142,6 +147,7 @@ public int Run()
// Initialize type system context
//
_typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, versionBubbleIncludesCoreLib,
targetAllowsRuntimeCodeGeneration,
instructionSetSupport,
oldTypeSystemContext: null);

Expand Down Expand Up @@ -286,6 +292,7 @@ public int Run()
bool singleCompilationVersionBubbleIncludesCoreLib = versionBubbleIncludesCoreLib || (String.Compare(inputFile.Key, "System.Private.CoreLib", StringComparison.OrdinalIgnoreCase) == 0);

typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, singleCompilationVersionBubbleIncludesCoreLib,
targetAllowsRuntimeCodeGeneration,
_typeSystemContext.InstructionSetSupport,
_typeSystemContext);
typeSystemContext.InputFilePaths = singleCompilationInputFilePaths;
Expand Down Expand Up @@ -700,6 +707,12 @@ private void RunSingleCompilation(Dictionary<string, string> inFilePaths, Instru
}
}

private static bool GetTargetAllowsRuntimeCodeGeneration(TargetOS operatingSystem, TargetArchitecture architecture)
{
return operatingSystem is not (TargetOS.iOS or TargetOS.iOSSimulator or TargetOS.MacCatalyst or TargetOS.tvOS or TargetOS.tvOSSimulator or TargetOS.Browser or TargetOS.Wasi)
&& architecture is not TargetArchitecture.Wasm32;
}

private void CheckManagedCppInputFiles(IEnumerable<string> inputPaths)
{
foreach (string inputFilePath in inputPaths)
Expand Down
Loading
Loading