[PATCH] use memcmp in page_extensible_p for well-defined type+gen comparison
Andreas Franke via Sbcl-devel <[email protected]> Fri, 27 Feb 2026 02:32:35 +0000
| Newsgroups | gmane.lisp.steel-bank.devel |
|---|---|
| Message-ID | <trinity-5354d6e9-7272-4b01-a537-2e421cc88ba0-1772159555689@trinity-msg-rest-gmx-gmx-live-8d9bc96fb-zqkv2> |
Works for me on linux x86-64, but needs testing on other platforms.
> On this platform (x86-64 Linux, GCC -O3):
> - Compiles with zero warnings
> - Generates exactly 3 cmpw %reg, 6(%reg) instructions at the 3 call sites — one 16-bit compare per call, no extra branches
> - This is identical codegen to the original *(int16_t*) cast, but without undefined behavior
>
> Cross-platform correctness:
> - type and gen are adjacent single-byte fields (offsets 6 and 7 in the 8-byte struct). No padding is possible between char-aligned fields — this is guaranteed by the C standard.
> - memcmp compares bytes in declaration order regardless of endianness, and the anonymous
> struct { unsigned char type; generation_index_t gen; }
> has the same layout. This resolves the original
> TODO: implement this as single comparison
> that was guarded behind #ifdef LISP_FEATURE_BIG_ENDIAN.
> - Clang also inlines memcmp of 2 bytes at -O2 and above. MSVC inlines it as an intrinsic.
> - memcmp has no alignment requirements.
>
> The only theoretical risk would be if some exotic compiler didn't inline memcmp of 2 bytes — but at -O3 both GCC and Clang do, and SBCL doesn't build at anything lower.
_______________________________________________
Sbcl-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sbcl-devel
0001-use-memcmp-in-page_extensible_p-for-well-defined-typ.patch
(text/x-patch, 1.8 KB)
From 103cddcaeb88a5d0388dd8edb2677b97a0c536ef Mon Sep 17 00:00:00 2001 From: Andreas Franke <[email protected]> Date: Thu, 26 Feb 2026 21:06:37 +0000 Subject: [PATCH] use memcmp in page_extensible_p for well-defined type+gen comparison memcmp of 2 bytes compiles to the same single compare instruction. Resolves the strict-aliasing FIXME and the big-endian TODO. --- src/runtime/gencgc.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/runtime/gencgc.c b/src/runtime/gencgc.c index 9f0d278ee..aff359f83 100644 --- a/src/runtime/gencgc.c +++ b/src/runtime/gencgc.c @@ -382,20 +382,12 @@ set_alloc_start_page(unsigned int page_type, page_index_t page) #endif /* Test whether page 'index' can continue a non-large-object region - * having specified 'gen' and 'type' values. It must not be pinned - * and must be marked but not referenced from the stack */ + * having specified 'gen' and 'type' values. It must be marked but not + * referenced from the stack. gc_page_pins are necessarily zero here. */ static inline bool page_extensible_p(page_index_t index, generation_index_t gen, int type) { -#ifdef LISP_FEATURE_BIG_ENDIAN /* TODO: implement this as single comparison */ - int attributes_match = - page_table[index].type == type - && page_table[index].gen == gen - && !gc_page_pins[index]; -#else - // FIXME: "warning: dereferencing type-punned pointer will break strict-aliasing rules" - int attributes_match = - *(int16_t*)&page_table[index].type == ((gen<<8)|type); -#endif + struct { unsigned char type; generation_index_t gen; } expected = { type, gen }; + int attributes_match = !memcmp(&page_table[index].type, &expected, 2); #ifdef LISP_FEATURE_SOFT_CARD_MARKS return attributes_match && page_cards_all_marked_nonsticky(index); #else -- 2.43.0