From f742e124577d2255b6d646b158a13753c4daab87 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Wed, 7 Oct 2026 16:39:30 -0700 Subject: [PATCH 1/2] Backport ReadyToRun module activation fix to .NET 10 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4258af2-2f1f-4af6-b46e-0f6a1fae207f --- .../ReadyToRun/ModuleTokenResolver.cs | 5 +-- .../ReadyToRun/SignatureBuilder.cs | 4 +-- .../ReadyToRun/TypeFixupSignature.cs | 4 ++- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 14 ++++++++ src/coreclr/vm/jitinterface.cpp | 5 +++ .../ModuleInitializer/FieldLibrary.cs | 11 ++++++ .../ModuleInitializer/FieldLibrary.csproj | 8 +++++ .../ModuleInitializer/InitializerLibrary.cs | 24 +++++++++++++ .../InitializerLibrary.csproj | 11 ++++++ .../ModuleInitializer/RvaFieldLibrary.il | 22 ++++++++++++ .../ModuleInitializer/RvaFieldLibrary.ilproj | 8 +++++ .../UnsafeAccessorModuleInitializer.cs | 36 +++++++++++++++++++ .../UnsafeAccessorModuleInitializer.csproj | 17 +++++++++ .../ModuleInitializer/ValueTypeLibrary.cs | 19 ++++++++++ .../ModuleInitializer/ValueTypeLibrary.csproj | 8 +++++ 15 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 src/tests/readytorun/ModuleInitializer/FieldLibrary.cs create mode 100644 src/tests/readytorun/ModuleInitializer/FieldLibrary.csproj create mode 100644 src/tests/readytorun/ModuleInitializer/InitializerLibrary.cs create mode 100644 src/tests/readytorun/ModuleInitializer/InitializerLibrary.csproj create mode 100644 src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.il create mode 100644 src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.ilproj create mode 100644 src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.cs create mode 100644 src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.csproj create mode 100644 src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.cs create mode 100644 src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.csproj diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs index 391d5328a4be96..4a29340a420c46 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs @@ -54,7 +54,9 @@ public void InitManifestMutableModule(MutableModule mutableModule) public ModuleToken GetModuleTokenForType(EcmaType type, bool allowDynamicallyCreatedReference, bool throwIfNotFound = true) { - if (_compilationModuleGroup.VersionsWithType(type)) + // The global module type always has TypeDef token 0x02000001 (ECMA-335 II.22.37), + // so its token remains valid even outside the version bubble. + if (_compilationModuleGroup.VersionsWithType(type) || type.IsModuleType) { return new ModuleToken(type.EcmaModule, (mdToken)MetadataTokens.GetToken(type.Handle)); } @@ -438,4 +440,3 @@ public DummyTypeInfo GetTypeFromSpecification(MetadataReader reader, ModuleToken } } } - diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs index 1fb549a6f1afa0..b38c3bb5140fad 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs @@ -592,7 +592,7 @@ public ObjectNode.ObjectData ToObjectData() return _builder.ToObjectData(); } - public SignatureContext EmitFixup(NodeFactory factory, ReadyToRunFixupKind fixupKind, IEcmaModule targetModule, SignatureContext outerContext) + public SignatureContext EmitFixup(NodeFactory factory, ReadyToRunFixupKind fixupKind, IEcmaModule targetModule, SignatureContext outerContext, bool hasStableToken = false) { if (targetModule == outerContext.LocalContext) { @@ -602,7 +602,7 @@ public SignatureContext EmitFixup(NodeFactory factory, ReadyToRunFixupKind fixup else { EmitByte((byte)(fixupKind | ReadyToRunFixupKind.ModuleOverride)); - if (!(targetModule is Internal.TypeSystem.Ecma.MutableModule) && !factory.CompilationModuleGroup.VersionsWithModule((ModuleDesc)targetModule)) + if (!hasStableToken && !(targetModule is Internal.TypeSystem.Ecma.MutableModule) && !factory.CompilationModuleGroup.VersionsWithModule((ModuleDesc)targetModule)) { throw new InternalCompilerErrorException("Attempt to use token from a module not within the version bubble"); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs index a32a1a11f5c5f9..23f42a2331e3c3 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs @@ -45,7 +45,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } IEcmaModule targetModule = factory.SignatureContext.GetTargetModule(_typeDesc); - SignatureContext innerContext = dataBuilder.EmitFixup(factory, fixupKind, targetModule, factory.SignatureContext); + // The global module type has a fixed token even outside the version bubble. + bool hasStableToken = fixupKind == ReadyToRunFixupKind.TypeHandle && _typeDesc is EcmaType { IsModuleType: true }; + SignatureContext innerContext = dataBuilder.EmitFixup(factory, fixupKind, targetModule, factory.SignatureContext, hasStableToken); dataBuilder.EmitTypeSignature(_typeDesc, innerContext); if ((fixupKind == ReadyToRunFixupKind.Check_TypeLayout) || diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index a1dc79692155f5..044fdac7c701df 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -3182,6 +3182,20 @@ private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_ } _precodeFixups = previouslyStashedFixups; + // Static methods, constructors, and instance methods on default-initialized value types + // can be the first use of a module. + // Preserve activation if the module has, or outside the version bubble can gain, an initializer. + EcmaModule inlineeModule = (inlinee.OwningType as MetadataType)?.Module as EcmaModule; + if ((inlinee.Signature.IsStatic || inlinee.IsConstructor || inlinee.OwningType.IsValueType) && + inlineeModule is not null && + inlineeModule != _compilation.TypeSystemContext.SystemModule && + inlineeModule != (MethodBeingCompiled.OwningType as MetadataType)?.Module && + (!_compilation.CompilationModuleGroup.VersionsWithModule(inlineeModule) || + inlineeModule.GetGlobalModuleType().HasStaticConstructor)) + { + classMustBeLoadedBeforeCodeIsRun(inlineeModule.GetGlobalModuleType()); + } + // If during inlining we found new inlinees, then if the inline was successful, add them to the set of fixups // for the entire method. HashSet previouslyStashedInlinees = _stashedInlinedMethods.Pop(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 74bab557b4416d..56826c54c38732 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -13848,6 +13848,11 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, // We can take address of RVA field only since ngened code is domain neutral _ASSERTE(pField->IsRVA()); + if (g_fEEStarted) + { + pField->GetModule()->EnsureActive(); + } + result = (size_t)pField->GetStaticAddressHandle(NULL); } break; diff --git a/src/tests/readytorun/ModuleInitializer/FieldLibrary.cs b/src/tests/readytorun/ModuleInitializer/FieldLibrary.cs new file mode 100644 index 00000000000000..30c648675a51b9 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/FieldLibrary.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ModuleInitializerTest; + +public sealed class FieldHolder +{ + private static bool s_isSet; + + public static bool IsSet => s_isSet; +} diff --git a/src/tests/readytorun/ModuleInitializer/FieldLibrary.csproj b/src/tests/readytorun/ModuleInitializer/FieldLibrary.csproj new file mode 100644 index 00000000000000..e5bb75a3c7cdb3 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/FieldLibrary.csproj @@ -0,0 +1,8 @@ + + + Library + + + + + diff --git a/src/tests/readytorun/ModuleInitializer/InitializerLibrary.cs b/src/tests/readytorun/ModuleInitializer/InitializerLibrary.cs new file mode 100644 index 00000000000000..3d15be238d809f --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/InitializerLibrary.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace ModuleInitializerTest; + +public static class InitializerLibrary +{ + [ModuleInitializer] + public static void Initialize() + { + GetIsSet(null) = true; + } + + [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "s_isSet")] + private static extern ref bool GetIsSet(FieldHolder holder); + + // Inlining this empty call must not remove the module's initialization dependency. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Touch() + { + } +} diff --git a/src/tests/readytorun/ModuleInitializer/InitializerLibrary.csproj b/src/tests/readytorun/ModuleInitializer/InitializerLibrary.csproj new file mode 100644 index 00000000000000..b6f6b391e868a7 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/InitializerLibrary.csproj @@ -0,0 +1,11 @@ + + + Library + + + + + + + + diff --git a/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.il b/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.il new file mode 100644 index 00000000000000..4632f0cca3e538 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.il @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern mscorlib {} +.assembly RvaFieldLibrary {} +.module RvaFieldLibrary.dll + +.method private specialname rtspecialname static void .cctor() cil managed +{ + .maxstack 2 + ldstr "RvaFieldLibrary.Initialized" + ldc.i4.1 + call void [mscorlib]System.AppContext::SetSwitch(string, bool) + ret +} + +.class public auto ansi beforefieldinit RvaFieldHolder extends [mscorlib]System.Object +{ + .field public static int32 Value at ValueData +} + +.data ValueData = int32(42) diff --git a/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.ilproj b/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.ilproj new file mode 100644 index 00000000000000..37df3cbf212132 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/RvaFieldLibrary.ilproj @@ -0,0 +1,8 @@ + + + Library + + + + + diff --git a/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.cs b/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.cs new file mode 100644 index 00000000000000..68cd6d33c3120a --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +namespace ModuleInitializerTest; + +public static class UnsafeAccessorModuleInitializer +{ + [Fact] + public static void TestEntryPoint() + { + InitializerLibrary.Touch(); + Assert.True(FieldHolder.IsSet, "The module initializer did not update the private static field."); + } + + [Fact] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void RvaFieldAccess() + { + Assert.Equal(42, RvaFieldHolder.Value); + Assert.True(AppContext.TryGetSwitch("RvaFieldLibrary.Initialized", out bool initialized) && initialized, + "Accessing the RVA field did not run its module initializer."); + } + + [Fact] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ValueTypeInstanceMethodAccess() + { + Assert.Equal(42, default(ValueTypeLibrary).GetValue()); + Assert.True(AppContext.TryGetSwitch("ValueTypeLibrary.Initialized", out bool initialized) && initialized, + "Calling the value-type instance method did not run its module initializer."); + } +} diff --git a/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.csproj b/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.csproj new file mode 100644 index 00000000000000..27b396232f63b5 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/UnsafeAccessorModuleInitializer.csproj @@ -0,0 +1,17 @@ + + + + true + true + 1 + + + + + + + + + + + diff --git a/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.cs b/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.cs new file mode 100644 index 00000000000000..0b7f3788112e8c --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +namespace ModuleInitializerTest; + +public struct ValueTypeLibrary +{ + [ModuleInitializer] + public static void Initialize() + { + AppContext.SetSwitch("ValueTypeLibrary.Initialized", true); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetValue() => 42; +} diff --git a/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.csproj b/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.csproj new file mode 100644 index 00000000000000..e8127811af2068 --- /dev/null +++ b/src/tests/readytorun/ModuleInitializer/ValueTypeLibrary.csproj @@ -0,0 +1,8 @@ + + + Library + + + + + From 502ea7cbe8a1f92ad472d563090725ebff781c4e Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Wed, 7 Oct 2026 17:09:43 -0700 Subject: [PATCH 2/2] Guard duplicate unwind context macros Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4258af2-2f1f-4af6-b46e-0f6a1fae207f --- src/coreclr/inc/crosscomp.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/inc/crosscomp.h b/src/coreclr/inc/crosscomp.h index 4c30eb01ba8ae9..5473076a59398a 100644 --- a/src/coreclr/inc/crosscomp.h +++ b/src/coreclr/inc/crosscomp.h @@ -210,7 +210,9 @@ typedef struct _T_DISPATCHER_CONTEXT { #define ARM64_MAX_BREAKPOINTS 8 #define ARM64_MAX_WATCHPOINTS 2 +#ifndef CONTEXT_UNWOUND_TO_CALL #define CONTEXT_UNWOUND_TO_CALL 0x20000000 +#endif typedef union _NEON128 { struct { @@ -413,7 +415,9 @@ enum #define LOONGARCH64_MAX_BREAKPOINTS 8 #define LOONGARCH64_MAX_WATCHPOINTS 2 +#ifndef CONTEXT_UNWOUND_TO_CALL #define CONTEXT_UNWOUND_TO_CALL 0x20000000 +#endif typedef struct DECLSPEC_ALIGN(16) _T_CONTEXT { @@ -546,7 +550,9 @@ typedef struct _T_KNONVOLATILE_CONTEXT_POINTERS { #define RISCV64_MAX_BREAKPOINTS 8 #define RISCV64_MAX_WATCHPOINTS 2 +#ifndef CONTEXT_UNWOUND_TO_CALL #define CONTEXT_UNWOUND_TO_CALL 0x20000000 +#endif typedef struct DECLSPEC_ALIGN(16) _T_CONTEXT {