Re: Add ARM64 Windows port
Masatoshi SANO <[email protected]> Fri, 13 Feb 2026 00:41:25 +0900
| Newsgroups | gmane.lisp.steel-bank.devel |
|---|---|
| Message-ID | <CAH6JMpgoZqntQ8P5sArLxFfmpdDRwk=4Hofkz2iEnOSxrGW_Gw@mail.gmail.com> |
Thank you for the review, Stas. All points are valid and have been addressed. In particular, the csel removal was a real bug — you were right that multiple value returns do not restore CSP. Here is what changed (incremental patch attached): > Allocations functions can't trigger a GC, a GC is requested by > checking pseudo-atomic-interrupted. Agreed. The storew saving CSP in the alloc trampoline has been removed. > Why is the change to newspace_full_scavenge needed, it's not hidden > behind safepoint-specific ifdefs. Which test fails without that > change? It is now guarded by: #if defined(LISP_FEATURE_SB_SAFEPOINT) && !defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK) This is needed for gethash-concurrency.pure.lisp on ARM64 with safepoints. Pinned from_space objects have their slots scavenged by neither scavenge_root_gens (requires gen >= from) nor the newspace scan (requires gen == new_space). On conservative platforms this is masked because stack scanning pins transitively reachable objects, but on precise platforms (ARM64) pinned objects can reference non-pinned from_space objects that must be explicitly transported. This is in patch 4/5, which can be dropped entirely if a different approach is preferred. > Then this should be conditioned at compile time. Fixed. Now uses #ifndef LISP_FEATURE_ARM64 instead of a runtime check. > #+sb-thread #-win32 is not a good way to combine conditionals. Fixed to #+(and sb-thread (not win32)). > This change is suspicious. Why is reg_CSP suddenly growing downward? The call_into_c changes are now confined behind #ifdef LISP_FEATURE_WIN32, with the original code path completely untouched for other platforms. On Windows ARM64 with safepoints, thread->control_stack_pointer cannot be zeroed to signal "in Lisp" state (it always holds the actual stack extent for GC scanning). So call_into_c needs to save/restore the previous thread CFP/CSP around the C call. The current implementation uses pre-decrement stores for this. > And multiple value returns certainly do not restore CSP. You are right. The csel removal in call_into_lisp was a bug in our patch. It has been restored unconditionally — the line is now identical to master. > Basically, don't make any changes to the existing code without tests, > confine them behind #ifdefs / #+. Agreed. All changes to existing code are now behind platform-specific guards (#ifdef LISP_FEATURE_WIN32, #-win32, or the safepoint guard). The non-Windows code paths are unchanged from master. Test results on Windows 11 ARM (Snapdragon) with this patch applied: Success: 4036, Expected-failure: 20, Unexpected-failure: 5 All 5 unexpected failures are pre-existing (FP denormals, weak hash table, sleep/safepoint), no new regressions. On Thu, Feb 12, 2026 at 3:38 PM Stas Boukarev <[email protected]> wrote: > > ;; Update thread->control_stack_pointer to include > the saved > > ;; Lisp registers so that scavenge_control_stack > scans them > > ;; if GC is triggered by the C allocation function. > > Allocations functions can't trigger a GC, a GC is requested by > checking pseudo-atomic-interrupted. > > Why is the change to newspace_full_scavenge needed, it's not hidden > behind safepoint-specific ifdefs. Which test fails without that > change? > > > // Unprotect the in-use ranges. Any page could be written during > scavenge > > // On some platforms (e.g., ARM64), fixedobj space may be disabled > > // (FIXEDOBJ_SPACE_START=0, size=0), so skip the protection change. > > Then this should be conditioned at compile time. > > #+sb-thread #-win32 is not a good way to combine conditionals. > > > // Build a Lisp stack frame. > - // Can store two values above the stack pointer, interrupts > ignore them. > - stp reg_CFP, reg_LR, [reg_CSP] > - add reg_R10, reg_CSP, #2*8 > + // Save the current thread structure CFP/CSP first, then our > own CFP/LR. > +#ifdef LISP_FEATURE_SB_THREAD > + ldp x3, x4, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] > // Load thread CFP/CSP > + stp x3, x4, [reg_CSP, #-16]! // Push thread CFP/CSP onto stack, CSP > -= 16 > +#endif > + stp reg_CFP, reg_LR, [reg_CSP, #-16]! // Push our > CFP/LR, CSP -= 16 > + add reg_R10, reg_CSP, #32 // R10 = original CSP (before > both pushes) > mov reg_LEXENV, reg_LR > > This change is suspicious. Why is reg_CSP suddenly growing downward? > And multiple value returns certainly do not restore CSP. > > Basically, don't make any changes to the existing code without tests, > confine them behind #ifdefs / #+. > > On Thu, Feb 12, 2026 at 8:33 AM Masatoshi SANO <[email protected]> wrote: > > > > This patch series adds ARM64 Windows (AArch64 WoA) support to SBCL. > > Built and tested on Windows 11 ARM using MSYS2 clangarm64 toolchain, > > cross-compiled from x86-64 Windows SBCL. > > > > Most tests pass. Known failures: > > > > - gethash-concurrency.pure.lisp: ~80% pass rate. Patch 4/5 adds > > defensive workarounds but the root cause is likely ARM64 weak > > memory ordering interacting with the hash table high-water-mark. > > Patch 4/5 can be dropped if a different approach is preferred. > > > > - Floating-point denormal tests (pre-existing on ARM64) > > > > - sleepytests.pure.lisp hangs (safepoint interrupt delivery during > > sleep, pre-existing on Windows safepoint builds) > > > > The patches are structured as follows: > > > > 1/5 align.h LLP64 truncation fix (Windows 64-bit general) > > 2/5 LLP64 type fixes and general bugs found during porting > > 3/5 GC fix for ARM64 safepoint builds with precise stack scanning > > 4/5 Defensive GC validation for concurrent hash table operations > > (optional -- see above) > > 5/5 ARM64 Windows platform support > > > > Patches 1-2 fix bugs that affect existing platforms (Windows x86-64, > > or all platforms). Patch 3 fixes a GC crash specific to ARM64 > > safepoint builds with precise scanning (!C_STACK_IS_CONTROL_STACK). > > Patch 5 is the main platform port. > > > > _______________________________________________ > > Sbcl-devel mailing list > > [email protected] > > https://lists.sourceforge.net/lists/listinfo/sbcl-devel > _______________________________________________ Sbcl-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/sbcl-devel
0001.patch
(application/octet-stream, 8.5 KB)
From a6309208d0babad9abc9f836976c45c84c96d35b Mon Sep 17 00:00:00 2001 From: "SANO,Masatoshi" <[email protected]> Date: Thu, 12 Feb 2026 15:56:08 +0900 Subject: [PATCH] Address maintainer feedback on ARM64 Windows patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes based on review by Stas Boukarev: 1. Remove alloc tramp CSP save in tramps.lisp - alloc/alloc_list don't trigger GC, so saving CSP for scavenge_control_stack is unnecessary. 2. Fix reader macro style: #+sb-thread #-win32 → #+(and sb-thread (not win32)) and #-win32 → #+(not win32) in tramps.lisp and c-call.lisp. 3. Use compile-time #ifndef LISP_FEATURE_ARM64 guard for fixedobj_size instead of runtime check in gencgc.c. 4. Add #if defined(LISP_FEATURE_SB_SAFEPOINT) && !defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK) guard around pinned objects scavenge in newspace_full_scavenge. 5. Confine call_into_c frame save/restore changes behind #ifdef LISP_FEATURE_WIN32 in arm64-assem.S, keeping original code path untouched for other platforms. 6. Restore csel instruction in call_into_lisp for non-Windows platforms, guarded by #ifdef LISP_FEATURE_WIN32. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- src/assembly/arm64/tramps.lisp | 9 +------ src/compiler/arm64/c-call.lisp | 1 - src/runtime/arm64-assem.S | 45 +++++++++++++++++----------------- src/runtime/gencgc.c | 16 ++++++------ 4 files changed, 32 insertions(+), 39 deletions(-) diff --git a/src/assembly/arm64/tramps.lisp b/src/assembly/arm64/tramps.lisp index 367612be8..d548943c1 100644 --- a/src/assembly/arm64/tramps.lisp +++ b/src/assembly/arm64/tramps.lisp @@ -80,11 +80,6 @@ (inst stp cfp-tn lr-tn (@ csp-tn -112)) (map-pairs stp csp-tn -80 lisp-registers) - ;; Update thread->control_stack_pointer to include the saved - ;; Lisp registers so that scavenge_control_stack scans them - ;; if GC is triggered by the C allocation function. - #+sb-thread - (storew csp-tn thread-tn thread-control-stack-pointer-slot) (map-pairs stp nsp-tn 0 float-registers :pre-index -512 :delta 32) (invoke-foreign-routine ,c-name nl3) @@ -95,9 +90,7 @@ (inst ldr lr-tn (@ csp-tn -104)) (inst sub csp-tn csp-tn (+ 32 80)) ;; deallocate the frame - ;; Windows uses control-stack-pointer as actual SP, not FFCA flag - #+sb-thread - #-win32 + #+(and sb-thread (not win32)) (inst str zr-tn (@ thread-tn (* thread-control-stack-pointer-slot n-word-bytes))) #-sb-thread (progn diff --git a/src/compiler/arm64/c-call.lisp b/src/compiler/arm64/c-call.lisp index 18b0f3b11..43b98d227 100644 --- a/src/compiler/arm64/c-call.lisp +++ b/src/compiler/arm64/c-call.lisp @@ -481,7 +481,6 @@ ;; No longer OK to run GC except at safepoints. #+(or sb-safepoint nonstop-foreign-call) (storew zr-tn thread-tn thread-saved-csp-slot)) - ;; Windows uses control-stack-pointer as actual SP, not FFCA flag #-win32 (storew zr-tn thread-tn thread-control-stack-pointer-slot) return diff --git a/src/runtime/arm64-assem.S b/src/runtime/arm64-assem.S index 9181e90f0..34fd25543 100644 --- a/src/runtime/arm64-assem.S +++ b/src/runtime/arm64-assem.S @@ -280,9 +280,8 @@ Lno_args: ldr reg_LR, [reg_LEXENV, #CLOSURE_FUN_OFFSET] blr reg_LR - // NOTE: We do NOT adjust CSP here. The Lisp calling convention ensures that - // the callee restores CSP to its pre-call value before returning. - // The original code had a csel instruction here that was buggy. + // Correct stack pointer for return processing. + csel reg_CSP, reg_OCFP, reg_CSP, eq // Return value mov x0, reg_R0 @@ -341,17 +340,22 @@ GNAME(call_into_c): // All other C arguments are already stashed on the C stack. // Build a Lisp stack frame. - // Save the current thread structure CFP/CSP first, then our own CFP/LR. -#ifdef LISP_FEATURE_SB_THREAD - ldp x3, x4, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] // Load thread CFP/CSP - stp x3, x4, [reg_CSP, #-16]! // Push thread CFP/CSP onto stack, CSP -= 16 +#ifdef LISP_FEATURE_WIN32 + // On Windows ARM64, save thread CFP/CSP before overwriting. + // CSP cannot be zeroed to indicate "in Lisp" state on safepoint builds. + ldp x3, x4, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] + stp x3, x4, [reg_CSP, #-16]! + stp reg_CFP, reg_LR, [reg_CSP, #-16]! + add reg_R10, reg_CSP, #32 +#else + // Can store two values above the stack pointer, interrupts ignore them. + stp reg_CFP, reg_LR, [reg_CSP] + add reg_R10, reg_CSP, #2*8 #endif - stp reg_CFP, reg_LR, [reg_CSP, #-16]! // Push our CFP/LR, CSP -= 16 - add reg_R10, reg_CSP, #32 // R10 = original CSP (before both pushes) mov reg_LEXENV, reg_LR + // Save the lisp stack and frame pointers. #ifdef LISP_FEATURE_SB_THREAD - // Now save our own stack pointers to thread structure. stp reg_CSP, reg_R10, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] #else ENTER_PA @@ -406,24 +410,21 @@ GNAME(call_into_c): #endif - // Restore the Lisp stack and frame pointers from the stack - // Stack layout: [CSP] = CFP, [CSP+8] = LR, [CSP+16] = thread CFP, [CSP+24] = thread CSP - ldp reg_CFP, reg_LR, [reg_CSP], #16 // Restore CFP/LR, CSP += 16 - + // Restore the Lisp stack and frame pointers +#ifdef LISP_FEATURE_WIN32 + // Restore CFP/LR from stack, then restore saved thread CFP/CSP. + ldp reg_CFP, reg_LR, [reg_CSP], #16 + ldp x3, x4, [reg_CSP], #16 + stp x3, x4, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] +#else #ifdef LISP_FEATURE_SB_THREAD - // Now restore the thread structure CFP/CSP that we saved at entry. - ldp x3, x4, [reg_CSP], #16 // Load saved thread CFP/CSP, CSP += 16 - stp x3, x4, [reg_THREAD, THREAD_CONTROL_FRAME_POINTER_OFFSET] // Restore to thread structure - -// Windows uses control_stack_pointer as actual SP, not FFCA flag -#ifndef LISP_FEATURE_WIN32 str xzr, [reg_THREAD, THREAD_CONTROL_STACK_POINTER_OFFSET] -#endif #else // Clear FFCA, so the runtime knows that we're "in lisp". str xzr, [reg_OCFP] #endif - mov reg_LEXENV, reg_LR + mov reg_LR, reg_LEXENV +#endif ret SIZE(call_into_c) diff --git a/src/runtime/gencgc.c b/src/runtime/gencgc.c index 6d9c66a20..e8499d454 100644 --- a/src/runtime/gencgc.c +++ b/src/runtime/gencgc.c @@ -2665,6 +2665,7 @@ static void newspace_full_scavenge(generation_index_t generation) /* Enable recording of all new allocation regions */ record_new_regions_below = 1 + page_table_pages; +#if defined(LISP_FEATURE_SB_SAFEPOINT) && !defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK) /* Scavenge pinned from_space objects. These objects reside on pages with * gen=from_space, so they are NOT processed by scavenge_root_gens (which * requires gen >= from) or the newspace scan above (which requires @@ -2694,6 +2695,7 @@ static void newspace_full_scavenge(generation_index_t generation) } } } +#endif } void gc_close_collector_regions(int flag) @@ -3949,14 +3951,12 @@ collect_garbage(generation_index_t last_gen) #ifdef LISP_FEATURE_IMMOBILE_SPACE if (ENABLE_PAGE_PROTECTION) { // Unprotect the in-use ranges. Any page could be written during scavenge - // On some platforms (e.g., ARM64), fixedobj space may be disabled - // (FIXEDOBJ_SPACE_START=0, size=0), so skip the protection change. - uword_t fixedobj_size = (lispobj)fixedobj_free_pointer - FIXEDOBJ_SPACE_START; - if (FIXEDOBJ_SPACE_START != 0 && fixedobj_size > 0) { - os_protect((os_vm_address_t)FIXEDOBJ_SPACE_START, - fixedobj_size, - OS_VM_PROT_ALL); - } +#ifndef LISP_FEATURE_ARM64 + // ARM64 does not use fixedobj space (FIXEDOBJ_SPACE_START=0). + os_protect((os_vm_address_t)FIXEDOBJ_SPACE_START, + (lispobj)fixedobj_free_pointer - FIXEDOBJ_SPACE_START, + OS_VM_PROT_ALL); +#endif } #endif -- 2.43.0