From dc3114f7fc40d08d564c1fc628e8e0ad5e4b6675 Mon Sep 17 00:00:00 2001 From: Sebastian Nickolls Date: Tue, 25 Aug 2026 13:26:14 +0000 Subject: [PATCH 1/2] Arm64-SVE: Add scheme for scalable SVE state in the CONTEXT record Replaces the fixed-length SVE state from the CONTEXT record with dynamically allocated space, similar to XSTATE, for saving and restoring vector/predicate register state, and the first-faulting register. This reduces the size of the CONTEXT structure, as the SVE state is now optional. Subsystems that want to consume SVE state should set XSTATE_MASK_ARM64_SVE in CONTEXT::XStateFeaturesMask and allocate sufficient storage space for the trailer. --- .../Runtime/ExceptionServices/AsmOffsets.cs | 14 +- src/coreclr/pal/inc/pal.h | 39 ++-- src/coreclr/pal/src/arch/arm64/asmconstants.h | 32 +--- src/coreclr/pal/src/arch/arm64/context2.S | 168 ++++++++++++------ src/coreclr/pal/src/exception/signal.cpp | 4 +- src/coreclr/pal/src/thread/context.cpp | 125 ++++++++----- src/coreclr/vm/arm64/asmconstants.h | 2 +- .../GC/Scenarios/SVE/SveRegisterState.cs | 120 +++++++++++++ .../GC/Scenarios/SVE/SveRegisterState.csproj | 11 ++ 9 files changed, 372 insertions(+), 143 deletions(-) create mode 100644 src/tests/GC/Scenarios/SVE/SveRegisterState.cs create mode 100644 src/tests/GC/Scenarios/SVE/SveRegisterState.csproj diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs index 9b263295106d86..d24c1495ed319a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs @@ -26,9 +26,9 @@ class AsmOffsets #endif // TARGET_UNIX #elif TARGET_ARM64 #if TARGET_UNIX - public const int SIZEOF__REGDISPLAY = 0x9f0; - public const int OFFSETOF__REGDISPLAY__SP = 0x938; - public const int OFFSETOF__REGDISPLAY__ControlPC = 0x940; + public const int SIZEOF__REGDISPLAY = 0x970; + public const int OFFSETOF__REGDISPLAY__SP = 0x8b8; + public const int OFFSETOF__REGDISPLAY__ControlPC = 0x8c0; #else // TARGET_UNIX public const int SIZEOF__REGDISPLAY = 0x950; public const int OFFSETOF__REGDISPLAY__SP = 0x898; @@ -87,9 +87,9 @@ class AsmOffsets #endif // TARGET_UNIX #elif TARGET_ARM64 #if TARGET_UNIX - public const int SIZEOF__REGDISPLAY = 0x9e0; - public const int OFFSETOF__REGDISPLAY__SP = 0x930; - public const int OFFSETOF__REGDISPLAY__ControlPC = 0x938; + public const int SIZEOF__REGDISPLAY = 0x960; + public const int OFFSETOF__REGDISPLAY__SP = 0x8b0; + public const int OFFSETOF__REGDISPLAY__ControlPC = 0x8b8; #else // TARGET_UNIX public const int SIZEOF__REGDISPLAY = 0x940; public const int OFFSETOF__REGDISPLAY__SP = 0x890; @@ -144,7 +144,7 @@ class AsmOffsets #endif // TARGET_UNIX #elif TARGET_ARM64 #if TARGET_UNIX - public const int SIZEOF__PAL_LIMITED_CONTEXT = 0x3e0; + public const int SIZEOF__PAL_LIMITED_CONTEXT = 0x3a0; #else // TARGET_UNIX public const int SIZEOF__PAL_LIMITED_CONTEXT = 0x390; #endif // TARGET_UNIX diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 0ff5b7435ecab0..0d7bac7cfb48a9 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -1580,19 +1580,38 @@ typedef struct DECLSPEC_ALIGN(16) _CONTEXT { /* +0x380 */ DWORD64 Wvr[ARM64_MAX_WATCHPOINTS]; /* +0x390 */ DWORD64 XStateFeaturesMask; - - // - // Sve Registers - // - // TODO-SVE: Support Vector register sizes >128bit - // For 128bit, Z and V registers fully overlap, so there is no need to load/store both. - /* +0x398 */ DWORD Vl; - /* +0x39c */ DWORD Ffr; - /* +0x3a0 */ DWORD P[16]; - /* +0x3e0 */ + /* +0x3a0 */ } CONTEXT, *PCONTEXT, *LPCONTEXT; +// The SVE header immediately follows CONTEXT, when CONTEXT_XSTATE is set and +// XStateFeaturesMask contains XSTATE_MASK_ARM64_SVE. +// If CONTEXT_SVE_STATE_HEADER.VectorLengthInBytes != 0, it is immediately +// followed by the serialized state of the vector/predicate register files, +// and the first-faulting register, padded to 16-byte alignment. The bottom +// 128 bits of each Z register are duplicated in CONTEXT::V in this design. +// +// +------------------------------------------+ +// | CONTEXT | +// +------------------------------------------+ +// | CONTEXT_SVE_STATE_HEADER (16 bytes) | +// +------------------------------------------+ +// | Z0..Z31 (32 * VL bytes) | +// +------------------------------------------+ +// | P0..P15 (16 * VL/8 bytes) | +// +------------------------------------------+ +// | FFR (VL/8 bytes) | +// +------------------------------------------+ +// | padding to 16-byte alignment | +// +------------------------------------------+ +// +// TODO-SVE: We should update this layout to conform with Windows XSTATE. +typedef struct DECLSPEC_ALIGN(16) _CONTEXT_SVE_STATE_HEADER +{ + DWORD VectorLengthInBytes; + DWORD Reserved[3]; +} CONTEXT_SVE_STATE_HEADER, *PCONTEXT_SVE_STATE_HEADER; + // // Nonvolatile context pointer record. // diff --git a/src/coreclr/pal/src/arch/arm64/asmconstants.h b/src/coreclr/pal/src/arch/arm64/asmconstants.h index 63b316f4558598..002edd3e8cd9fc 100644 --- a/src/coreclr/pal/src/arch/arm64/asmconstants.h +++ b/src/coreclr/pal/src/arch/arm64/asmconstants.h @@ -105,35 +105,9 @@ #define CONTEXT_DEBUG_SIZE 120 // (8*4)+(8*8)+(2*4)+(2*8) #define CONTEXT_XSTATEFEATURESMASK_OFFSET CONTEXT_DEBUG_OFFSET+CONTEXT_DEBUG_SIZE +#define CONTEXT_Size 0x3a0 -// TODO-SVE: Support Vector register sizes >128bit - -#define CONTEXT_SVE_OFFSET CONTEXT_XSTATEFEATURESMASK_OFFSET+8 -#define CONTEXT_VL_OFFSET 0 - -// SVE register offsets are multiples of the vector length -#define CONTEXT_SVE_REGS_OFFSET CONTEXT_VL_OFFSET+4 -#define CONTEXT_FFR_VL 0 -#define CONTEXT_P0_VL CONTEXT_FFR_VL+1 -#define CONTEXT_P1_VL CONTEXT_P0_VL+1 -#define CONTEXT_P2_VL CONTEXT_P1_VL+1 -#define CONTEXT_P3_VL CONTEXT_P2_VL+1 -#define CONTEXT_P4_VL CONTEXT_P3_VL+1 -#define CONTEXT_P5_VL CONTEXT_P4_VL+1 -#define CONTEXT_P6_VL CONTEXT_P5_VL+1 -#define CONTEXT_P7_VL CONTEXT_P6_VL+1 -#define CONTEXT_P8_VL CONTEXT_P7_VL+1 -#define CONTEXT_P9_VL CONTEXT_P8_VL+1 -#define CONTEXT_P10_VL CONTEXT_P9_VL+1 -#define CONTEXT_P11_VL CONTEXT_P10_VL+1 -#define CONTEXT_P12_VL CONTEXT_P11_VL+1 -#define CONTEXT_P13_VL CONTEXT_P12_VL+1 -#define CONTEXT_P14_VL CONTEXT_P13_VL+1 -#define CONTEXT_P15_VL CONTEXT_P14_VL+1 - -#define CONTEXT_SVE_REGS_SIZE ((CONTEXT_P15_VL+1) * 4) -#define CONTEXT_SVE_SIZE CONTEXT_SVE_REGS_SIZE + 8 - -#define CONTEXT_Size CONTEXT_SVE_OFFSET + CONTEXT_SVE_SIZE +#define CONTEXT_SVE_STATE_HEADER_VectorLengthInBytes 0 +#define CONTEXT_SVE_STATE_HEADER_Size 16 #endif diff --git a/src/coreclr/pal/src/arch/arm64/context2.S b/src/coreclr/pal/src/arch/arm64/context2.S index 40f46957025dc6..520f7455c4b34b 100644 --- a/src/coreclr/pal/src/arch/arm64/context2.S +++ b/src/coreclr/pal/src/arch/arm64/context2.S @@ -116,27 +116,61 @@ LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT): ldr x1, [x0, CONTEXT_XSTATEFEATURESMASK_OFFSET] tbz x1, #XSTATE_ARM64_SVE_BIT, LOCAL_LABEL(Done_CONTEXT_SVE) - add x0, x0, CONTEXT_SVE_OFFSET - str p0, [x0, CONTEXT_P0_VL, MUL VL] - str p1, [x0, CONTEXT_P1_VL, MUL VL] - str p2, [x0, CONTEXT_P2_VL, MUL VL] - str p3, [x0, CONTEXT_P3_VL, MUL VL] - str p4, [x0, CONTEXT_P4_VL, MUL VL] - str p5, [x0, CONTEXT_P5_VL, MUL VL] - str p6, [x0, CONTEXT_P6_VL, MUL VL] - str p7, [x0, CONTEXT_P7_VL, MUL VL] - str p8, [x0, CONTEXT_P8_VL, MUL VL] - str p9, [x0, CONTEXT_P9_VL, MUL VL] - str p10, [x0, CONTEXT_P10_VL, MUL VL] - str p11, [x0, CONTEXT_P11_VL, MUL VL] - str p12, [x0, CONTEXT_P12_VL, MUL VL] - str p13, [x0, CONTEXT_P13_VL, MUL VL] - str p14, [x0, CONTEXT_P14_VL, MUL VL] - str p15, [x0, CONTEXT_P15_VL, MUL VL] - rdffr p0.b - str p0, [x0, CONTEXT_FFR_VL, MUL VL] - ldr p0, [x0, 0, MUL VL] - sub x0, x0, CONTEXT_SVE_OFFSET + add x2, x0, CONTEXT_Size + CONTEXT_SVE_STATE_HEADER_Size + str z0, [x2, 0, MUL VL] + str z1, [x2, 1, MUL VL] + str z2, [x2, 2, MUL VL] + str z3, [x2, 3, MUL VL] + str z4, [x2, 4, MUL VL] + str z5, [x2, 5, MUL VL] + str z6, [x2, 6, MUL VL] + str z7, [x2, 7, MUL VL] + str z8, [x2, 8, MUL VL] + str z9, [x2, 9, MUL VL] + str z10, [x2, 10, MUL VL] + str z11, [x2, 11, MUL VL] + str z12, [x2, 12, MUL VL] + str z13, [x2, 13, MUL VL] + str z14, [x2, 14, MUL VL] + str z15, [x2, 15, MUL VL] + str z16, [x2, 16, MUL VL] + str z17, [x2, 17, MUL VL] + str z18, [x2, 18, MUL VL] + str z19, [x2, 19, MUL VL] + str z20, [x2, 20, MUL VL] + str z21, [x2, 21, MUL VL] + str z22, [x2, 22, MUL VL] + str z23, [x2, 23, MUL VL] + str z24, [x2, 24, MUL VL] + str z25, [x2, 25, MUL VL] + str z26, [x2, 26, MUL VL] + str z27, [x2, 27, MUL VL] + str z28, [x2, 28, MUL VL] + str z29, [x2, 29, MUL VL] + str z30, [x2, 30, MUL VL] + str z31, [x2, 31, MUL VL] + rdvl x3, #1 + add x2, x2, x3, lsl #5 + + str p0, [x2, 0, MUL VL] + str p1, [x2, 1, MUL VL] + str p2, [x2, 2, MUL VL] + str p3, [x2, 3, MUL VL] + str p4, [x2, 4, MUL VL] + str p5, [x2, 5, MUL VL] + str p6, [x2, 6, MUL VL] + str p7, [x2, 7, MUL VL] + str p8, [x2, 8, MUL VL] + str p9, [x2, 9, MUL VL] + str p10, [x2, 10, MUL VL] + str p11, [x2, 11, MUL VL] + str p12, [x2, 12, MUL VL] + str p13, [x2, 13, MUL VL] + str p14, [x2, 14, MUL VL] + str p15, [x2, 15, MUL VL] + rdffr p15.b + str p15, [x2, 16, MUL VL] + ldr p15, [x2, 15, MUL VL] LOCAL_LABEL(Done_CONTEXT_SVE): @@ -191,34 +225,6 @@ LOCAL_LABEL(Restore_CONTEXT_FLOATING_POINT): // since we potentially clobber x0 below, we'll bank it in x16 mov x16, x0 - ldr w17, [x16, CONTEXT_ContextFlags] - tbz w17, #CONTEXT_XSTATE_BIT, LOCAL_LABEL(No_Restore_CONTEXT_SVE) - - ldr w17, [x16, CONTEXT_XSTATEFEATURESMASK_OFFSET] - tbz w17, #XSTATE_ARM64_SVE_BIT, LOCAL_LABEL(No_Restore_CONTEXT_SVE) - - add x16, x16, CONTEXT_SVE_OFFSET - ldr p0, [x16, CONTEXT_FFR_VL, MUL VL] - wrffr p0.b - ldr p0, [x16, CONTEXT_P0_VL, MUL VL] - ldr p1, [x16, CONTEXT_P1_VL, MUL VL] - ldr p2, [x16, CONTEXT_P2_VL, MUL VL] - ldr p3, [x16, CONTEXT_P3_VL, MUL VL] - ldr p4, [x16, CONTEXT_P4_VL, MUL VL] - ldr p5, [x16, CONTEXT_P5_VL, MUL VL] - ldr p6, [x16, CONTEXT_P6_VL, MUL VL] - ldr p7, [x16, CONTEXT_P7_VL, MUL VL] - ldr p8, [x16, CONTEXT_P8_VL, MUL VL] - ldr p9, [x16, CONTEXT_P9_VL, MUL VL] - ldr p10, [x16, CONTEXT_P10_VL, MUL VL] - ldr p11, [x16, CONTEXT_P11_VL, MUL VL] - ldr p12, [x16, CONTEXT_P12_VL, MUL VL] - ldr p13, [x16, CONTEXT_P13_VL, MUL VL] - ldr p14, [x16, CONTEXT_P14_VL, MUL VL] - ldr p15, [x16, CONTEXT_P15_VL, MUL VL] - sub x16, x16, CONTEXT_SVE_OFFSET - -LOCAL_LABEL(No_Restore_CONTEXT_SVE): ldr w17, [x16, CONTEXT_ContextFlags] tbz w17, #CONTEXT_FLOATING_POINT_BIT, LOCAL_LABEL(No_Restore_CONTEXT_FLOATING_POINT) @@ -246,6 +252,69 @@ LOCAL_LABEL(No_Restore_CONTEXT_SVE): sub x16, x16, CONTEXT_FLOAT_CONTROL_OFFSET + CONTEXT_NEON_OFFSET LOCAL_LABEL(No_Restore_CONTEXT_FLOATING_POINT): + ldr w17, [x16, CONTEXT_ContextFlags] + tbz w17, #CONTEXT_XSTATE_BIT, LOCAL_LABEL(No_Restore_CONTEXT_SVE) + + ldr x0, [x16, CONTEXT_XSTATEFEATURESMASK_OFFSET] + tbz x0, #XSTATE_ARM64_SVE_BIT, LOCAL_LABEL(No_Restore_CONTEXT_SVE) + + add x1, x16, CONTEXT_Size + CONTEXT_SVE_STATE_HEADER_Size + ldr z0, [x1, 0, MUL VL] + ldr z1, [x1, 1, MUL VL] + ldr z2, [x1, 2, MUL VL] + ldr z3, [x1, 3, MUL VL] + ldr z4, [x1, 4, MUL VL] + ldr z5, [x1, 5, MUL VL] + ldr z6, [x1, 6, MUL VL] + ldr z7, [x1, 7, MUL VL] + ldr z8, [x1, 8, MUL VL] + ldr z9, [x1, 9, MUL VL] + ldr z10, [x1, 10, MUL VL] + ldr z11, [x1, 11, MUL VL] + ldr z12, [x1, 12, MUL VL] + ldr z13, [x1, 13, MUL VL] + ldr z14, [x1, 14, MUL VL] + ldr z15, [x1, 15, MUL VL] + ldr z16, [x1, 16, MUL VL] + ldr z17, [x1, 17, MUL VL] + ldr z18, [x1, 18, MUL VL] + ldr z19, [x1, 19, MUL VL] + ldr z20, [x1, 20, MUL VL] + ldr z21, [x1, 21, MUL VL] + ldr z22, [x1, 22, MUL VL] + ldr z23, [x1, 23, MUL VL] + ldr z24, [x1, 24, MUL VL] + ldr z25, [x1, 25, MUL VL] + ldr z26, [x1, 26, MUL VL] + ldr z27, [x1, 27, MUL VL] + ldr z28, [x1, 28, MUL VL] + ldr z29, [x1, 29, MUL VL] + ldr z30, [x1, 30, MUL VL] + ldr z31, [x1, 31, MUL VL] + rdvl x2, #1 + add x1, x1, x2, lsl #5 + + ldr p0, [x1, 16, MUL VL] + wrffr p0.b + ldr p0, [x1, 0, MUL VL] + ldr p1, [x1, 1, MUL VL] + ldr p2, [x1, 2, MUL VL] + ldr p3, [x1, 3, MUL VL] + ldr p4, [x1, 4, MUL VL] + ldr p5, [x1, 5, MUL VL] + ldr p6, [x1, 6, MUL VL] + ldr p7, [x1, 7, MUL VL] + ldr p8, [x1, 8, MUL VL] + ldr p9, [x1, 9, MUL VL] + ldr p10, [x1, 10, MUL VL] + ldr p11, [x1, 11, MUL VL] + ldr p12, [x1, 12, MUL VL] + ldr p13, [x1, 13, MUL VL] + ldr p14, [x1, 14, MUL VL] + ldr p15, [x1, 15, MUL VL] + +LOCAL_LABEL(No_Restore_CONTEXT_SVE): + ldr w17, [x16, CONTEXT_ContextFlags] tbz w17, #CONTEXT_INTEGER_BIT, LOCAL_LABEL(No_Restore_CONTEXT_INTEGER) ldp x0, x1, [x16, CONTEXT_X0] @@ -295,4 +364,3 @@ LEAF_ENTRY RestoreCompleteContext, _TEXT LEAF_END RestoreCompleteContext, _TEXT #endif // __APPLE__ - diff --git a/src/coreclr/pal/src/exception/signal.cpp b/src/coreclr/pal/src/exception/signal.cpp index 74fa88db04e409..9ab8e0dc0e5248 100644 --- a/src/coreclr/pal/src/exception/signal.cpp +++ b/src/coreclr/pal/src/exception/signal.cpp @@ -952,7 +952,7 @@ static void inject_activation_handler(int code, siginfo_t *siginfo, void *contex ULONG contextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT; -#if defined(HOST_AMD64) || defined(HOST_ARM64) +#if defined(HOST_AMD64) contextFlags |= CONTEXT_XSTATE; #endif @@ -1143,7 +1143,7 @@ static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext ULONG contextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT; -#if defined(HOST_AMD64) || defined(HOST_ARM64) +#if defined(HOST_AMD64) contextFlags |= CONTEXT_XSTATE; #endif diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index 9c60bb9373d730..e59980e36e0566 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -42,6 +42,16 @@ extern PGET_GCMARKER_EXCEPTION_CODE g_getGcMarkerExceptionCode; #define CONTEXT_ALL_FLOATING CONTEXT_FLOATING_POINT #endif +#if defined(XSTATE_SUPPORTED) && defined(HOST_ARM64) && !defined(TARGET_FREEBSD) && !defined(__APPLE__) +static inline bool IsValidSveVectorLengthInBytes(DWORD vectorLengthInBytes) +{ + return (vectorLengthInBytes >= 16) && (vectorLengthInBytes <= 256) && + ((vectorLengthInBytes & (vectorLengthInBytes - 1)) == 0); +} + +static const CONTEXT_SVE_STATE_HEADER *GetArm64SveStateHeader(const CONTEXT *context); +#endif + #if !HAVE_MACH_EXCEPTIONS #ifndef __GLIBC__ @@ -896,36 +906,41 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) #endif // !TARGET_OSX } #elif defined(HOST_ARM64) - if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) + if((lpContext->XStateFeaturesMask & XSTATE_MASK_ARM64_SVE) == XSTATE_MASK_ARM64_SVE) { - //TODO-SVE: This only handles vector lengths of 128bits. - // Use sve->vl from the signal frame to avoid SIGILL on platforms that - // provide an SVE context record without supporting SVE instructions - // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). - if (sve->vl == 16) - { - _ASSERT((lpContext->XStateFeaturesMask & XSTATE_MASK_ARM64_SVE) == XSTATE_MASK_ARM64_SVE); + const CONTEXT_SVE_STATE_HEADER *sveStateHeader = GetArm64SveStateHeader(lpContext); - // Derive vq from the signal frame's vl (the authoritative layout) - // rather than lpContext->Vl, to ensure offset calculations always - // match the actual frame even in non-debug builds. - uint16_t vq = sve_vq_from_vl(sve->vl); + // The native signal frame should have SVE state space. + _ASSERTE(sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))); + // The VL should have been initialized in the SVE header matching the signal frame. + _ASSERTE((sveStateHeader != nullptr) && (sveStateHeader->VectorLengthInBytes == sve->vl)); - // Vector length should not have changed. - _ASSERTE(lpContext->Vl == sve->vl); + const uint16_t vectorQuadwordCount = sve_vq_from_vl(sve->vl); + uint8_t *nativeSveBytes = reinterpret_cast(sve); - //Note: Size of ffr register is SVE_SIG_FFR_SIZE(vq) bytes. - *(WORD*) (((uint8_t*)sve) + SVE_SIG_FFR_OFFSET(vq)) = lpContext->Ffr; - - //TODO-SVE: Copy SVE registers once they are >128bits - //Note: Size of a Z register is SVE_SIG_ZREGS_SIZE(vq) bytes. + const uint8_t *contextZRegisters = reinterpret_cast(sveStateHeader + 1); + for (int i = 0; i < 32; i++) + { + uint8_t *nativeZRegister = + nativeSveBytes + SVE_SIG_ZREG_OFFSET(vectorQuadwordCount, i); + memcpy(nativeZRegister, contextZRegisters + (i * sve->vl), sve->vl); + _ASSERTE(memcmp(nativeZRegister, &lpContext->V[i], sizeof(NEON128)) == 0); + } - for (int i = 0; i < 16; i++) - { - //Note: Size of a P register is SVE_SIG_PREGS_SIZE(vq) bytes. - *(WORD*) (((uint8_t*)sve) + SVE_SIG_PREG_OFFSET(vq, i)) = lpContext->P[i]; - } + const SIZE_T predicateRegisterSize = SVE_SIG_PREG_SIZE(vectorQuadwordCount); + const uint8_t *contextPredicateRegisters = contextZRegisters + (32 * sve->vl); + for (int i = 0; i < 16; i++) + { + memcpy(nativeSveBytes + SVE_SIG_PREG_OFFSET(vectorQuadwordCount, i), + contextPredicateRegisters + (i * predicateRegisterSize), + predicateRegisterSize); } + + const uint8_t *contextFirstFaultRegister = + contextPredicateRegisters + (16 * predicateRegisterSize); + memcpy(nativeSveBytes + SVE_SIG_FFR_OFFSET(vectorQuadwordCount), + contextFirstFaultRegister, + predicateRegisterSize); } #endif //HOST_AMD64 } @@ -1023,6 +1038,23 @@ void _GetNativeSigSimdContext(uint8_t *data, uint32_t size, fpsimd_context **fp_ *sve_ptr = sve; } } + +#if defined(XSTATE_SUPPORTED) +static const CONTEXT_SVE_STATE_HEADER *GetArm64SveStateHeader(const CONTEXT *context) +{ + if (((context->ContextFlags & CONTEXT_XSTATE) != CONTEXT_XSTATE) || + ((context->XStateFeaturesMask & XSTATE_MASK_ARM64_SVE) == 0)) + { + return nullptr; + } + + const CONTEXT_SVE_STATE_HEADER *sveStateHeader = + reinterpret_cast(context + 1); + _ASSERTE(IsValidSveVectorLengthInBytes(sveStateHeader->VectorLengthInBytes)); + + return sveStateHeader; +} +#endif // XSTATE_SUPPORTED #endif // HOST_64BIT && HOST_ARM64 && !TARGET_FREEBSD && !__APPLE__ /*++ @@ -1263,31 +1295,36 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex #elif defined(HOST_ARM64) if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) { - //TODO-SVE: This only handles vector lengths of 128bits. - // Use sve->vl from the signal frame to avoid SIGILL on platforms that - // provide an SVE context record without supporting SVE instructions - // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). - if (sve->vl == 16) - { - _ASSERTE(sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(16))); - lpContext->Vl = sve->vl; + const CONTEXT_SVE_STATE_HEADER *sveStateHeader = GetArm64SveStateHeader(lpContext); - uint16_t vq = sve_vq_from_vl(sve->vl); + // The VL should have been initialized in the SVE header matching the signal frame. + _ASSERTE((sveStateHeader != nullptr) && (sveStateHeader->VectorLengthInBytes == sve->vl)); - lpContext->XStateFeaturesMask |= XSTATE_MASK_ARM64_SVE; + const uint16_t vectorQuadwordCount = sve_vq_from_vl(sve->vl); + const uint8_t *nativeSveBytes = reinterpret_cast(sve); + uint8_t *contextZRegisters = reinterpret_cast(lpContext + 1) + sizeof(*sveStateHeader); - //Note: Size of ffr register is SVE_SIG_FFR_SIZE(vq) bytes. - lpContext->Ffr = *(WORD*) (((uint8_t*)sve) + SVE_SIG_FFR_OFFSET(vq)); - - //TODO-SVE: Copy SVE registers once they are >128bits - //Note: Size of a Z register is SVE_SIG_ZREGS_SIZE(vq) bytes. + for (int i = 0; i < 32; i++) + { + const uint8_t *nativeZRegister = + nativeSveBytes + SVE_SIG_ZREG_OFFSET(vectorQuadwordCount, i); + memcpy(contextZRegisters + (i * sve->vl), nativeZRegister, sve->vl); + _ASSERTE(memcmp(&lpContext->V[i], nativeZRegister, sizeof(NEON128)) == 0); + } - for (int i = 0; i < 16; i++) - { - //Note: Size of a P register is SVE_SIG_PREGS_SIZE(vq) bytes. - lpContext->P[i] = *(WORD*) (((uint8_t*)sve) + SVE_SIG_PREG_OFFSET(vq, i)); - } + const SIZE_T predicateRegisterSize = sve->vl / 8; + uint8_t *contextPredicateRegisters = contextZRegisters + (32 * sve->vl); + for (int i = 0; i < 16; i++) + { + memcpy(contextPredicateRegisters + (i * predicateRegisterSize), + nativeSveBytes + SVE_SIG_PREG_OFFSET(vectorQuadwordCount, i), + predicateRegisterSize); } + + uint8_t *contextFirstFaultRegister = contextPredicateRegisters + (16 * predicateRegisterSize); + memcpy(contextFirstFaultRegister, + nativeSveBytes + SVE_SIG_FFR_OFFSET(vectorQuadwordCount), + predicateRegisterSize); } #endif // HOST_AMD64 else diff --git a/src/coreclr/vm/arm64/asmconstants.h b/src/coreclr/vm/arm64/asmconstants.h index 1ce793fc64ad24..dde70e220d0551 100644 --- a/src/coreclr/vm/arm64/asmconstants.h +++ b/src/coreclr/vm/arm64/asmconstants.h @@ -97,7 +97,7 @@ ASMCONSTANTS_C_ASSERT(VASigCookie__pPInvokeILStub == offsetof(VASigCookie, pPInv ASMCONSTANTS_C_ASSERT(SIZEOF__Frame == sizeof(Frame)); #if !defined(HOST_WINDOWS) -#define SIZEOF__CONTEXT 0x3e0 +#define SIZEOF__CONTEXT 0x3a0 #else #define SIZEOF__CONTEXT 0x390 #endif diff --git a/src/tests/GC/Scenarios/SVE/SveRegisterState.cs b/src/tests/GC/Scenarios/SVE/SveRegisterState.cs new file mode 100644 index 00000000000000..93b1c8eb343e07 --- /dev/null +++ b/src/tests/GC/Scenarios/SVE/SveRegisterState.cs @@ -0,0 +1,120 @@ +// 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.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using Xunit; + +// This test exercises SVE intrinsics in a hot loop, with code that encourages +// lots of values to be allocated to the predicate register file. It also +// force triggers GC to encourage suspension, ensuring predicate values are +// saved and restored correctly. + +public class SveRegisterState +{ + private const int IterationCount = 1_000_000; + + private static bool s_collectorReady; + private static bool s_exercisingRegisters; + private static bool s_stopCollecting; + private static int s_collectionCount; + + [Fact] + public static void TestEntryPoint() + { + Console.WriteLine($"Vector.Count: {Vector.Count}"); + if (!Sve.IsSupported) + { + return; + } + + Volatile.Write(ref s_collectorReady, false); + Volatile.Write(ref s_exercisingRegisters, false); + Volatile.Write(ref s_collectionCount, 0); + Volatile.Write(ref s_stopCollecting, false); + Thread collector = new Thread(CollectUntilStopped); + collector.Start(); + SpinWait.SpinUntil(() => Volatile.Read(ref s_collectorReady)); + + Vector result; + int iterations; + try + { + result = ExerciseRegisters(out iterations); + } + finally + { + Volatile.Write(ref s_stopCollecting, true); + collector.Join(); + } + + Assert.True(Volatile.Read(ref s_collectionCount) > 0); + Console.WriteLine($"Iterations: {iterations}; collections: {Volatile.Read(ref s_collectionCount)}"); + + int[] resultValues = new int[Vector.Count]; + result.CopyTo(resultValues); + for (int i = 0; i < resultValues.Length; i++) + { + int expectedDelta = i switch + { + 0 => 31, + 1 => 251, + 2 => -77, + _ => -221, + }; + Assert.Equal(iterations * expectedDelta, resultValues[i]); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector ExerciseRegisters(out int iterations) + { + Vector mask0 = Sve.CreateTrueMaskInt32(); + Vector mask1 = Sve.CreateTrueMaskInt32(SveMaskPattern.VectorCount1); + Vector mask2 = Sve.CreateTrueMaskInt32(SveMaskPattern.VectorCount2); + Vector mask3 = Sve.CreateTrueMaskInt32(SveMaskPattern.VectorCount3); + Vector mask4 = Sve.BitwiseClear(mask0, mask1); + Vector mask5 = Sve.Xor(mask2, mask1); + Vector mask6 = Sve.And(mask3, mask4); + Vector mask7 = Sve.Or(mask1, mask5); + Vector result = Vector.Zero; + + Volatile.Write(ref s_exercisingRegisters, true); + iterations = 0; + do + { + result = Sve.ConditionalSelect(mask0, result + Vector.Create(1), result - Vector.Create(1)); + result = Sve.ConditionalSelect(mask1, result + Vector.Create(2), result - Vector.Create(2)); + result = Sve.ConditionalSelect(mask2, result + Vector.Create(4), result - Vector.Create(4)); + result = Sve.ConditionalSelect(mask3, result + Vector.Create(8), result - Vector.Create(8)); + result = Sve.ConditionalSelect(mask4, result + Vector.Create(16), result - Vector.Create(16)); + result = Sve.ConditionalSelect(mask5, result + Vector.Create(32), result - Vector.Create(32)); + result = Sve.ConditionalSelect(mask6, result + Vector.Create(64), result - Vector.Create(64)); + result = Sve.ConditionalSelect(mask7, result + Vector.Create(128), result - Vector.Create(128)); + iterations++; + } + while ((iterations < IterationCount) || (Volatile.Read(ref s_collectionCount) == 0)); + Volatile.Write(ref s_exercisingRegisters, false); + + return result; + } + + private static void CollectUntilStopped() + { + Volatile.Write(ref s_collectorReady, true); + while (!Volatile.Read(ref s_stopCollecting)) + { + if (!Volatile.Read(ref s_exercisingRegisters)) + { + Thread.Yield(); + continue; + } + + GC.Collect(); + Interlocked.Increment(ref s_collectionCount); + } + } +} diff --git a/src/tests/GC/Scenarios/SVE/SveRegisterState.csproj b/src/tests/GC/Scenarios/SVE/SveRegisterState.csproj new file mode 100644 index 00000000000000..9a6c885637d96b --- /dev/null +++ b/src/tests/GC/Scenarios/SVE/SveRegisterState.csproj @@ -0,0 +1,11 @@ + + + true + True + $(NoWarn);SYSLIB5003 + + + + + + From cd065347333e701f93bf8405acbc8e4eef9e439f Mon Sep 17 00:00:00 2001 From: Sebastian Nickolls Date: Tue, 20 Oct 2026 11:54:51 +0000 Subject: [PATCH 2/2] Address review comments * Add documentation on native_context SVE space guarantees * Clear up bit tests in context2.S --- src/coreclr/pal/src/arch/arm64/context2.S | 34 +++-------------------- src/coreclr/pal/src/thread/context.cpp | 7 ++++- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/coreclr/pal/src/arch/arm64/context2.S b/src/coreclr/pal/src/arch/arm64/context2.S index 520f7455c4b34b..65ace16e47a98b 100644 --- a/src/coreclr/pal/src/arch/arm64/context2.S +++ b/src/coreclr/pal/src/arch/arm64/context2.S @@ -25,13 +25,7 @@ LEAF_ENTRY CONTEXT_CaptureContext, _TEXT str x1, [sp, 24] ldr w1, [x0, CONTEXT_ContextFlags] - // clangs assembler doesn't seem to support the mov Wx, imm32 yet - movz w2, #0x40, lsl #16 - movk w2, #0x1 - mov w3, w2 - and w2, w1, w2 - cmp w2, w3 - b.ne LOCAL_LABEL(Done_CONTEXT_CONTROL) + tbz w1, #CONTEXT_CONTROL_BIT, LOCAL_LABEL(Done_CONTEXT_CONTROL) // save the cpsr ldr x2, [sp, 24] @@ -41,15 +35,7 @@ LEAF_ENTRY CONTEXT_CaptureContext, _TEXT stp x2, lr, [x0, CONTEXT_Sp] LOCAL_LABEL(Done_CONTEXT_CONTROL): - // we dont clobber x1 in the CONTEXT_CONTROL case - // ldr w1, [x0, CONTEXT_ContextFlags] - // clangs assembler doesn't seem to support the mov Wx, imm32 yet - movz w2, #0x40, lsl #16 - movk w2, #0x2 - mov w3, w2 - and w2, w1, w2 - cmp w2, w3 - b.ne LOCAL_LABEL(Done_CONTEXT_INTEGER) + tbz w1, #CONTEXT_INTEGER_BIT, LOCAL_LABEL(Done_CONTEXT_INTEGER) ldp x1, x2, [sp] ldr x3, [sp, 16] @@ -72,13 +58,7 @@ LOCAL_LABEL(Done_CONTEXT_CONTROL): LOCAL_LABEL(Done_CONTEXT_INTEGER): ldr w1, [x0, CONTEXT_ContextFlags] - // clangs assembler doesn't seem to support the mov Wx, imm32 yet - movz w2, #0x40, lsl #16 - movk w2, #0x4 - mov w3, w2 - and w2, w1, w2 - cmp w2, w3 - b.ne LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT) + tbz w1, #CONTEXT_FLOATING_POINT_BIT, LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT) add x0, x0, CONTEXT_NEON_OFFSET stp q0, q1, [x0, CONTEXT_V0] @@ -105,13 +85,7 @@ LOCAL_LABEL(Done_CONTEXT_INTEGER): LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT): ldr w1, [x0, CONTEXT_ContextFlags] - // clangs assembler doesn't seem to support the mov Wx, imm32 yet - movz w2, #0x40, lsl #16 - movk w2, #0x40 - mov w3, w2 - and w2, w1, w2 - cmp w2, w3 - b.ne LOCAL_LABEL(Done_CONTEXT_SVE) + tbz w1, #CONTEXT_XSTATE_BIT, LOCAL_LABEL(Done_CONTEXT_SVE) ldr x1, [x0, CONTEXT_XSTATEFEATURESMASK_OFFSET] tbz x1, #XSTATE_ARM64_SVE_BIT, LOCAL_LABEL(Done_CONTEXT_SVE) diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index e59980e36e0566..a2a020a399b8a6 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -712,6 +712,11 @@ Parameters : Return value : None +Notes: + On ARM64, if lpContext->ContextFlags has CONTEXT_XSTATE, and + lpContext->XStateFeaturesMask has XSTATE_MASK_ARM64_SVE, the caller + is responsible for ensuring the native_context_t has enough space + allocated to store the relevant SVE header and register data. --*/ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) { @@ -910,7 +915,7 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) { const CONTEXT_SVE_STATE_HEADER *sveStateHeader = GetArm64SveStateHeader(lpContext); - // The native signal frame should have SVE state space. + // The native signal frame should have space allocated for SVE state, provided by the caller. _ASSERTE(sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))); // The VL should have been initialized in the SVE header matching the signal frame. _ASSERTE((sveStateHeader != nullptr) && (sveStateHeader->VectorLengthInBytes == sve->vl));