From ee50cb81a13da92182f89235dad87527e94febc6 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 14 May 2026 15:57:14 -0700 Subject: [PATCH 1/6] Proof of concept for including thumb bit in R2R symbols --- src/coreclr/jit/emit.cpp | 34 ++----------------- src/coreclr/jit/emitarm.cpp | 7 ++-- .../Compiler/ObjectWriter/ObjectWriter.cs | 6 +--- .../ReadyToRun/DelayLoadHelperImport.cs | 12 +++---- .../ExceptionInfoLookupTableNode.cs | 2 +- .../ReadyToRun/MethodGCInfoNode.cs | 8 ++--- .../ResumptionStubEntryPointSignature.cs | 2 +- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 2 +- 8 files changed, 20 insertions(+), 53 deletions(-) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index d3e723b5df1d18..5ea0dc49de9f26 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -8525,40 +8525,10 @@ void emitter::emitOutputDataSec(dataSecDsc* sec, AllocMemChunk* chunks) if (m_compiler->opts.compReloc) { -#ifdef TARGET_ARM - // The runtime and ILC will handle setting the thumb bit on the async resumption stub entrypoint, - // either directly in the emitAsyncResumeStubEntryPoint value (runtime) or will add the thumb bit - // to the symbol definition (ilc). ReadyToRun is different here: it emits method symbols without the - // thumb bit, then during fixups, the runtime adds the thumb bit. This works for all cases where - // the method entrypoint is fixed up at runtime, but doesn't hold for the resumption stub, which is - // emitted as a direct call without the typical indirection cell + fixup. This is okay in this case - // (while regular method calls could not do this) because the async method and its resumption stub - // are tightly coupled and effectively funclets of the same method. However, this means that - // crossgen needs the reloc for the resumption stubs entrypoint to include the thumb bit. Until we - // unify the behavior of crossgen with the runtime and ilc, we will work around this by emitting the - // reloc with the addend for the thumb bit. - if (m_compiler->IsReadyToRun()) - { - emitRecordRelocationWithAddlDelta(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint, - CorInfoReloc::DIRECT, 1); - } - else -#endif - { - emitRecordRelocation(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint, CorInfoReloc::DIRECT); - } + emitRecordRelocation(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint, CorInfoReloc::DIRECT); if (target != nullptr) { -#ifdef TARGET_ARM - if (m_compiler->IsReadyToRun()) - { - emitRecordRelocationWithAddlDelta(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT, 1); - } - else -#endif - { - emitRecordRelocation(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT); - } + emitRecordRelocation(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT); } } diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index cfb5de23666cac..f38e906ec01740 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -5336,9 +5336,10 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i) assert(ins == INS_movw || ins == INS_movt); distVal = (ssize_t)emitOffsetToPtr(dstOffs); - // ILC defines method symbols with the thumb bit already set, so don't add it here. - // For ReadyToRun and non-relocatable code (runtime JIT), we set it ourselves. - if (!m_compiler->IsNativeAot()) + // ILC and crossgen2 defines method symbols with the thumb bit already set, so don't add it here. + // Assume compilations with relocs will put the thumb bit in the symbol. + // For non-relocatable code (runtime JIT), we set it ourselves. + if (!(m_compiler->compReloc)) { distVal += 1; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 5eb2018c7cf947..8d7a8826dc4f75 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -409,12 +409,8 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection _helper; public DelayLoadHelperImport( - NodeFactory factory, - ImportSectionNode importSectionNode, - ReadyToRunHelper helper, - Signature instanceSignature, - bool useVirtualCall = false, + NodeFactory factory, + ImportSectionNode importSectionNode, + ReadyToRunHelper helper, + Signature instanceSignature, + bool useVirtualCall = false, bool useJumpableStub = false, MethodDesc callingMethod = null) : base(importSectionNode, instanceSignature, callingMethod) @@ -87,7 +87,7 @@ public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory f // This needs to be an empty target pointer since it will be filled in with Module* // when loaded by CoreCLR dataBuilder.EmitReloc(_delayLoadHelper, - factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64, factory.Target.CodeDelta); + factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs index f9fe3657851a16..b5ec213d93d428 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs @@ -121,7 +121,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // First, emit the actual EH records in sequence and store map from methods to the EH record symbols for (int index = 0; index < _methodNodes.Count; index++) { - exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB); + exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB, -factory.Target.CodeDelta); exceptionInfoLookupBuilder.EmitReloc(_ehInfoNode, RelocType.IMAGE_REL_BASED_ADDR32NB, _ehInfoOffsets[index]); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodGCInfoNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodGCInfoNode.cs index 6d85b90d6131f5..05a2cfb9a23b2c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodGCInfoNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodGCInfoNode.cs @@ -145,7 +145,7 @@ public bool Equals(GCInfoComponent other) { if (other.Bytes == null) return false; - + return Bytes.SequenceEqual(other.Bytes); } else @@ -170,7 +170,7 @@ private IEnumerable EncodeDataCore(NodeFactory factory) const byte UNW_FLAG_UHANDLER = 2; const byte UNW_FLAG_CHAININFO = 4; const byte FlagsShift = 3; - + for (int frameInfoIndex = 0; frameInfoIndex < numFrameInfos; frameInfoIndex++) { FrameInfo frameInfo = (frameInfoIndex >= numHotFrameInfos) ? @@ -189,7 +189,7 @@ private IEnumerable EncodeDataCore(NodeFactory factory) yield return new GCInfoComponent(header); yield return new GCInfoComponent(_methodNode, 0); yield return new GCInfoComponent(_methodNode, _methodNode.Size); - // TODO: Is this correct? + // TODO: Is this correct? yield return new GCInfoComponent(factory.RuntimeFunctionsGCInfo, this.OffsetFromBeginningOfArray); } else @@ -212,7 +212,7 @@ private IEnumerable EncodeDataCore(NodeFactory factory) { bool isFilterFunclet = (frameInfo.Flags & FrameInfoFlags.Filter) != 0; ISymbolNode personalityRoutine = (isFilterFunclet ? factory.FilterFuncletPersonalityRoutine : factory.PersonalityRoutine); - yield return new GCInfoComponent(personalityRoutine, factory.Target.CodeDelta); + yield return new GCInfoComponent(personalityRoutine, 0); } if (frameInfoIndex == 0 && _methodNode.GCInfo != null) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ResumptionStubEntryPointSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ResumptionStubEntryPointSignature.cs index a071ed723afc8f..9d9b4655370832 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ResumptionStubEntryPointSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ResumptionStubEntryPointSignature.cs @@ -20,7 +20,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) builder.AddSymbol(this); builder.EmitByte((byte)ReadyToRunFixupKind.ResumptionStubEntryPoint); // Emit a relocation to the resumption stub code; at link time this becomes the RVA. - builder.EmitReloc(_resumptionStub, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: factory.Target.CodeDelta); + builder.EmitReloc(_resumptionStub, RelocType.IMAGE_REL_BASED_ADDR32NB); return builder.ToObjectData(); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs index 063caae405075c..9edf662963015e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs @@ -130,7 +130,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } else { - runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset + _nodeFactory.Target.CodeDelta); + runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset); if (!relocsOnly && _nodeFactory.Target.Architecture == TargetArchitecture.X64) { // On Amd64, the 2nd word contains the EndOffset of the runtime function From a78cfed1340b689e10fc91f2d4fa92930111f065 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 14 May 2026 17:23:54 -0700 Subject: [PATCH 2/6] Fix compReloc syntax, add subtraction to DelayLoadHelperImport --- src/coreclr/jit/emitarm.cpp | 2 +- .../DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index f38e906ec01740..319b89dc08dc7f 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -5339,7 +5339,7 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i) // ILC and crossgen2 defines method symbols with the thumb bit already set, so don't add it here. // Assume compilations with relocs will put the thumb bit in the symbol. // For non-relocatable code (runtime JIT), we set it ourselves. - if (!(m_compiler->compReloc)) + if (!(m_compiler->opts.compReloc)) { distVal += 1; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs index 668af18398026c..d522572b1b3170 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs @@ -87,7 +87,7 @@ public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory f // This needs to be an empty target pointer since it will be filled in with Module* // when loaded by CoreCLR dataBuilder.EmitReloc(_delayLoadHelper, - factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64); + factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64, -factory.Target.CodeDelta); } else { From a731125d0b5ee38b3657bc56009cec7a11dbb6e0 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 18 May 2026 14:26:11 -0700 Subject: [PATCH 3/6] Add ARM Thumb bit ReadyToRun validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ILCompiler.ReadyToRun.Tests.csproj | 1 + .../CrossModuleInlining/ExceptionHandling.cs | 21 +++ .../TestCases/R2RTestSuites.cs | 36 ++++ .../TestCasesRunner/R2RDriver.cs | 6 +- .../TestCasesRunner/R2RResultChecker.cs | 171 ++++++++++++++++++ .../ReadyToRun/DelayLoadHelperImport.cs | 2 +- .../ReadyToRun/ReadyToRunHeaderNode.cs | 5 +- 7 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/CrossModuleInlining/ExceptionHandling.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 1ec092009d6f88..c034a01e270db6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -21,6 +21,7 @@ +