[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