From bd8f26b7488adfc4a1cad877886350059230491c Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 4 Jun 2026 21:59:16 +0200 Subject: [PATCH 01/10] [clr-ios] Fix interpreter NullReferenceException with Vector on no-JIT x64 The interpreter only supports 128-bit Vector, but on Mac Catalyst and iOS simulator x64 it was ending up with 256-bit Vector, which caused the NullReferenceException in Vector.get_IsHardwareAccelerated. The runtime already limits Vector to 128-bit for the full interpreter mode, but it wasn't doing so on no-JIT builds where everything is interpreted anyway, so AVX2 on x64 silently bumped it up to 256-bit. Apply the same 128-bit limit whenever the interpreter is the only thing running code. arm64 was never affected since Vector is always 128-bit there. Related #128898 Fixes #128901 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/codeman.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 0ae31afb30ebef..89c1ba67b992f7 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1331,9 +1331,12 @@ void EEJitManager::SetCpuInfo() uint32_t maxVectorTBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_MaxVectorTBitWidth) / 128) * 128; #if defined(FEATURE_INTERPRETER) - if (maxVectorTBitWidth != 128 && CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3) + bool interpreterOnly = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3; +#if !defined(FEATURE_DYNAMIC_CODE_COMPILED) + interpreterOnly = true; +#endif + if (maxVectorTBitWidth != 128 && interpreterOnly) { - // Disable larger Vector sizes when interpreter is enabled maxVectorTBitWidth = 128; } #endif @@ -1702,9 +1705,12 @@ void EEJitManager::SetCpuInfo() uint32_t preferredVectorBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PreferredVectorBitWidth) / 128) * 128; #ifdef FEATURE_INTERPRETER - if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3) + bool interpreterOnlyPreferred = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3; +#if !defined(FEATURE_DYNAMIC_CODE_COMPILED) + interpreterOnlyPreferred = true; +#endif + if (interpreterOnlyPreferred) { - // Disable larger Vector sizes when interpreter is enabled, and not compiling S.P.Corelib preferredVectorBitWidth = 128; } #endif From 33fb93c1237d3ef328959742f4c394d84a8e9475 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 4 Jun 2026 22:01:38 +0200 Subject: [PATCH 02/10] Keep existing Vector cap comments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/codeman.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 89c1ba67b992f7..21fb0bee4acf74 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1337,6 +1337,7 @@ void EEJitManager::SetCpuInfo() #endif if (maxVectorTBitWidth != 128 && interpreterOnly) { + // Disable larger Vector sizes when interpreter is enabled maxVectorTBitWidth = 128; } #endif @@ -1711,6 +1712,7 @@ void EEJitManager::SetCpuInfo() #endif if (interpreterOnlyPreferred) { + // Disable larger Vector sizes when interpreter is enabled, and not compiling S.P.Corelib preferredVectorBitWidth = 128; } #endif From f75f6b7eeb6dac1f61d80bbf5d6dccb953a70442 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 10:49:51 +0200 Subject: [PATCH 03/10] Use 128-bit Vector baseline for crossgen2 on Apple x64 targets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/tools/Common/InstructionSetHelpers.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/InstructionSetHelpers.cs b/src/coreclr/tools/Common/InstructionSetHelpers.cs index 7dcf031a9e0131..ffefed9dacd40e 100644 --- a/src/coreclr/tools/Common/InstructionSetHelpers.cs +++ b/src/coreclr/tools/Common/InstructionSetHelpers.cs @@ -31,7 +31,11 @@ public static InstructionSetSupport ConfigureInstructionSetSupport(string instru if ((targetArchitecture == TargetArchitecture.X86) || (targetArchitecture == TargetArchitecture.X64)) { - if (isReadyToRun && targetOS != TargetOS.OSX && targetOS != TargetOS.MacCatalyst) + bool isAppleOS = targetOS is TargetOS.OSX or TargetOS.MacCatalyst + or TargetOS.iOS or TargetOS.iOSSimulator + or TargetOS.tvOS or TargetOS.tvOSSimulator; + + if (isReadyToRun && !isAppleOS) { // ReadyToRun can presume AVX2, BMI1, BMI2, F16C, FMA, LZCNT, and MOVBE instructionSetSupportBuilder.AddSupportedInstructionSet("x86-64-v3"); From f3ad77610d881593d19d2a709b665604e94156b8 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 11:28:10 +0200 Subject: [PATCH 04/10] Fail fast when stripped-IL R2R is disabled on a no-JIT runtime On interpreter-only runtimes (no JIT), R2R native code is the only executable form of a method. When the image was compiled with --strip-il-bodies and an eager fixup fails (e.g. an instruction-set check), R2R is disabled and the interpreter runs the throw stub left in place of the IL, which surfaces as an opaque self-recursing stack overflow. Fail fast with a clear message instead. Runtimes with a JIT already reject stripped images at load, so this path is only reachable when there is no recovery option. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/ceeload.cpp | 8 ++++++++ src/coreclr/vm/readytoruninfo.h | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 620ccc1844f1aa..800d1bf44e44ad 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3583,6 +3583,14 @@ void Module::RunEagerFixupsUnlocked() { _ASSERTE(IsReadyToRun()); GetReadyToRunInfo()->DisableAllR2RCode(); + +#ifndef FEATURE_DYNAMIC_CODE_COMPILED + if (GetReadyToRunInfo()->IsStrippedILBodies()) + { + EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, + W("ReadyToRun code was disabled by a failed eager fixup, but the image has stripped IL bodies and this runtime has no JIT fallback.")); + } +#endif // !FEATURE_DYNAMIC_CODE_COMPILED } else { diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index e31a36b1234d35..e63acd5cbe4e7d 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -249,6 +249,12 @@ class ReadyToRunInfo return m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_PARTIAL; } + BOOL IsStrippedILBodies() + { + LIMITED_METHOD_CONTRACT; + return m_pCompositeInfo->m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_STRIPPED_IL_BODIES; + } + void DisableAllR2RCode() { LIMITED_METHOD_CONTRACT; From 83b41efea7e93094496ae0b5678e856b25d034bb Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 12:07:45 +0200 Subject: [PATCH 05/10] Drop iOS/tvOS device targets from x64 baseline exclusion iOS and tvOS devices are ARM-only, so the x86/x64 branch is never reached for TargetOS.iOS/tvOS; only the simulator targets run x64 (Intel/Rosetta) and need the x86-64-v2 baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/tools/Common/InstructionSetHelpers.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/InstructionSetHelpers.cs b/src/coreclr/tools/Common/InstructionSetHelpers.cs index ffefed9dacd40e..b1ff24e8b804f7 100644 --- a/src/coreclr/tools/Common/InstructionSetHelpers.cs +++ b/src/coreclr/tools/Common/InstructionSetHelpers.cs @@ -32,8 +32,7 @@ public static InstructionSetSupport ConfigureInstructionSetSupport(string instru if ((targetArchitecture == TargetArchitecture.X86) || (targetArchitecture == TargetArchitecture.X64)) { bool isAppleOS = targetOS is TargetOS.OSX or TargetOS.MacCatalyst - or TargetOS.iOS or TargetOS.iOSSimulator - or TargetOS.tvOS or TargetOS.tvOSSimulator; + or TargetOS.iOSSimulator or TargetOS.tvOSSimulator; if (isReadyToRun && !isAppleOS) { From 6d35b329297e50c5578eab1c0a59a4bdec17e041 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 12:22:48 +0200 Subject: [PATCH 06/10] Read stripped-IL flag from the component header The READYTORUN_FLAG_STRIPPED_IL_BODIES flag is emitted on each component assembly's own R2R header (RewriteComponentFile), not on the composite global header, so read it from m_pHeader like IsPartial() and the existing check in ReadyToRunInfo::InitializeForModule. Reading the composite header would miss stripped IL bodies for composite components and skip the fatal check on no-JIT builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/readytoruninfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index e63acd5cbe4e7d..3003799491f41e 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -252,7 +252,7 @@ class ReadyToRunInfo BOOL IsStrippedILBodies() { LIMITED_METHOD_CONTRACT; - return m_pCompositeInfo->m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_STRIPPED_IL_BODIES; + return m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_STRIPPED_IL_BODIES; } void DisableAllR2RCode() From 0cb7e20562e12e98dcbdc0568302cb35661c63fa Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 12:25:30 +0200 Subject: [PATCH 07/10] Fix stale Vector comment to match the actual condition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/codeman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 21fb0bee4acf74..4afeda438a6892 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1712,7 +1712,7 @@ void EEJitManager::SetCpuInfo() #endif if (interpreterOnlyPreferred) { - // Disable larger Vector sizes when interpreter is enabled, and not compiling S.P.Corelib + // Disable larger Vector sizes when only the interpreter runs code preferredVectorBitWidth = 128; } #endif From 3912fb3b9d93d998e7fc627b75eba7550eace981 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 17:35:23 +0200 Subject: [PATCH 08/10] Remove redundant interpreter cap on preferred vector width The first maxVectorTBitWidth check already pins Vector to 128-bit, so VectorT256/VectorT512 are never set on interpreter-only runtimes. The preferredVectorBitWidth block only controls the JIT's internal preferred vector width, which is irrelevant when the interpreter is the only engine. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/codeman.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 4afeda438a6892..f52f4f2e874c83 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1705,18 +1705,6 @@ void EEJitManager::SetCpuInfo() uint32_t preferredVectorBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PreferredVectorBitWidth) / 128) * 128; -#ifdef FEATURE_INTERPRETER - bool interpreterOnlyPreferred = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3; -#if !defined(FEATURE_DYNAMIC_CODE_COMPILED) - interpreterOnlyPreferred = true; -#endif - if (interpreterOnlyPreferred) - { - // Disable larger Vector sizes when only the interpreter runs code - preferredVectorBitWidth = 128; - } -#endif - if ((preferredVectorBitWidth == 0) && throttleVector512) { preferredVectorBitWidth = 256; From 41dd8e6422a11e1d7dc5fc8bd9e4623ad60ecc6b Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Fri, 5 Jun 2026 17:49:18 +0200 Subject: [PATCH 09/10] Avoid reading InterpMode config when overridden on no-JIT runtimes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/codeman.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index f52f4f2e874c83..eda07ee2a9cbb8 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1331,9 +1331,10 @@ void EEJitManager::SetCpuInfo() uint32_t maxVectorTBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_MaxVectorTBitWidth) / 128) * 128; #if defined(FEATURE_INTERPRETER) +#if defined(FEATURE_DYNAMIC_CODE_COMPILED) bool interpreterOnly = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3; -#if !defined(FEATURE_DYNAMIC_CODE_COMPILED) - interpreterOnly = true; +#else + bool interpreterOnly = true; #endif if (maxVectorTBitWidth != 128 && interpreterOnly) { From a7fb6b92096d754192f2b573bbcea6de824f77ec Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Mon, 8 Jun 2026 16:36:23 +0200 Subject: [PATCH 10/10] Rename IsStrippedILBodies to HasStrippedILBodies Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/ceeload.cpp | 2 +- src/coreclr/vm/readytoruninfo.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 800d1bf44e44ad..51e7e674c0eb93 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3585,7 +3585,7 @@ void Module::RunEagerFixupsUnlocked() GetReadyToRunInfo()->DisableAllR2RCode(); #ifndef FEATURE_DYNAMIC_CODE_COMPILED - if (GetReadyToRunInfo()->IsStrippedILBodies()) + if (GetReadyToRunInfo()->HasStrippedILBodies()) { EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("ReadyToRun code was disabled by a failed eager fixup, but the image has stripped IL bodies and this runtime has no JIT fallback.")); diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 3003799491f41e..8127e05f6385fd 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -249,7 +249,7 @@ class ReadyToRunInfo return m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_PARTIAL; } - BOOL IsStrippedILBodies() + BOOL HasStrippedILBodies() { LIMITED_METHOD_CONTRACT; return m_pHeader->CoreHeader.Flags & READYTORUN_FLAG_STRIPPED_IL_BODIES;