From ce16f37a7719437214ca43731e8060db2e0bc222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 5 Sep 2026 06:40:36 +0200 Subject: [PATCH 1/2] fix(runtime): unify the three pthread stack-bounds extern declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `perry-runtime` declared `pthread_getattr_np`, `pthread_attr_getstack` and `pthread_attr_destroy` in three separate `extern "C"` blocks using two different argument spellings. Declaring one symbol twice in a crate with different signatures is `clashing_extern_declarations`, which `-D warnings` denies, so the `warnings` job could not compile the crate at all: error: `pthread_getattr_np` redeclared with a different signature --> crates/perry-runtime/src/gc/roots.rs:761:9 ::: crates/perry-runtime/src/error_stack_frames.rs:200:13 = note: expected `unsafe extern "C" fn(usize, *mut u8) -> i32` found `unsafe extern "C" fn(usize, *mut [u64; 8]) -> i32` = note: `-D clashing-extern-declarations` implied by `-D warnings` error: could not compile `perry-runtime` (lib) due to 3 previous errors `gc::roots::get_stack_bottom` has spelled the attr buffer `[u64; 8]` since long before the break. The competing `*mut u8` spelling also predates it, in `gc::roots::stack_maps::fp_chain`, but that module is gated to `target_arch = "aarch64"`, so on CI's linux-x86_64 host the two never met. 1ebc65e87 (#9486/#9521) added a third copy in `error_stack_frames.rs` gated only on `target_os = "linux"`; with no arch gate it collides with `gc/roots.rs` on x86_64, and the crate stopped compiling under `-D warnings`. All three blocks now use the `[u64; 8]` spelling, which additionally clears the latent aarch64-linux collision between `fp_chain` and `gc/roots.rs`. `[u64; 8]` is 64 bytes — at least the size of `pthread_attr_t` on every supported glibc/musl target, and correctly aligned for it, which the `[u8; 128]` buffer it replaces was not. Unbreaks the `warnings` job on https://github.com/PerryTS/perry/actions/runs/33926006467 Claude-Session: https://claude.ai/code/session_01YPfnmWZmSpSWpmnoXvH8z2 --- .../perry-runtime/src/error_stack_frames.rs | 26 ++++++++++++------- .../perry-runtime/src/gc/roots/stack_maps.rs | 24 ++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/crates/perry-runtime/src/error_stack_frames.rs b/crates/perry-runtime/src/error_stack_frames.rs index 5f7c03693e..49e8162756 100644 --- a/crates/perry-runtime/src/error_stack_frames.rs +++ b/crates/perry-runtime/src/error_stack_frames.rs @@ -195,25 +195,33 @@ mod walk { #[cfg(all(target_os = "linux", not(target_vendor = "apple")))] fn stack_top_uncached() -> usize { + // These signatures must stay identical to the ones in + // `gc::roots::get_stack_bottom` and `gc::roots::stack_maps::fp_chain`. + // Two `extern "C"` blocks in one crate that declare the same symbol + // with different argument types are a hard error under `-D warnings` + // (`clashing_extern_declarations`), and `[u64; 8]` — 64 bytes, which + // covers `pthread_attr_t` on every supported glibc/musl target — is + // the spelling the collector has always used. Unlike a `[u8; N]` + // buffer it is also correctly aligned for the attr union. unsafe extern "C" { fn pthread_self() -> usize; - fn pthread_getattr_np(thread: usize, attr: *mut u8) -> i32; + fn pthread_getattr_np(thread: usize, attr: *mut [u64; 8]) -> i32; fn pthread_attr_getstack( - attr: *const u8, - stackaddr: *mut *mut core::ffi::c_void, + attr: *const [u64; 8], + stackaddr: *mut *mut u8, stacksize: *mut usize, ) -> i32; - fn pthread_attr_destroy(attr: *mut u8) -> i32; + fn pthread_attr_destroy(attr: *mut [u64; 8]) -> i32; } - let mut attr = [0u8; 128]; - let mut addr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut attr = [0u64; 8]; + let mut addr: *mut u8 = core::ptr::null_mut(); let mut size: usize = 0; unsafe { - if pthread_getattr_np(pthread_self(), attr.as_mut_ptr()) != 0 { + if pthread_getattr_np(pthread_self(), &mut attr) != 0 { return 0; } - let ok = pthread_attr_getstack(attr.as_ptr(), &mut addr, &mut size) == 0; - pthread_attr_destroy(attr.as_mut_ptr()); + let ok = pthread_attr_getstack(&attr, &mut addr, &mut size) == 0; + pthread_attr_destroy(&mut attr); if !ok { return 0; } diff --git a/crates/perry-runtime/src/gc/roots/stack_maps.rs b/crates/perry-runtime/src/gc/roots/stack_maps.rs index 4e52ed3915..b1162161e7 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps.rs @@ -1786,27 +1786,31 @@ mod fp_chain { /// back to the platform unwinder (fail-closed like every other anomaly). #[cfg(target_os = "linux")] fn stack_top() -> usize { + // Identical to `gc::roots::get_stack_bottom` and the Error-stack + // capture in `error_stack_frames.rs`: one crate may not declare the + // same `extern "C"` symbol with two different signatures + // (`clashing_extern_declarations`, denied via `-D warnings`). unsafe extern "C" { fn pthread_self() -> usize; - fn pthread_getattr_np(thread: usize, attr: *mut u8) -> i32; + fn pthread_getattr_np(thread: usize, attr: *mut [u64; 8]) -> i32; fn pthread_attr_getstack( - attr: *const u8, - stackaddr: *mut *mut c_void, + attr: *const [u64; 8], + stackaddr: *mut *mut u8, stacksize: *mut usize, ) -> i32; - fn pthread_attr_destroy(attr: *mut u8) -> i32; + fn pthread_attr_destroy(attr: *mut [u64; 8]) -> i32; } // pthread_attr_t is at most 64 bytes on glibc/musl for the supported - // targets; over-allocate defensively. - let mut attr = [0u8; 128]; - let mut addr: *mut c_void = std::ptr::null_mut(); + // targets, and `[u64; 8]` is both that size and correctly aligned. + let mut attr = [0u64; 8]; + let mut addr: *mut u8 = std::ptr::null_mut(); let mut size: usize = 0; unsafe { - if pthread_getattr_np(pthread_self(), attr.as_mut_ptr()) != 0 { + if pthread_getattr_np(pthread_self(), &mut attr) != 0 { return 0; } - let ok = pthread_attr_getstack(attr.as_ptr(), &mut addr, &mut size) == 0; - pthread_attr_destroy(attr.as_mut_ptr()); + let ok = pthread_attr_getstack(&attr, &mut addr, &mut size) == 0; + pthread_attr_destroy(&mut attr); if !ok { return 0; } From 7ce6e9f447d3a11748bd8910624add81263106b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 5 Sep 2026 06:42:15 +0200 Subject: [PATCH 2/2] docs(changelog): add PR 9776 fragment Claude-Session: https://claude.ai/code/session_01YPfnmWZmSpSWpmnoXvH8z2 --- .../9776-pthread-extern-signature-clash.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 changelog.d/9776-pthread-extern-signature-clash.md diff --git a/changelog.d/9776-pthread-extern-signature-clash.md b/changelog.d/9776-pthread-extern-signature-clash.md new file mode 100644 index 0000000000..523fe121e2 --- /dev/null +++ b/changelog.d/9776-pthread-extern-signature-clash.md @@ -0,0 +1,18 @@ +**`perry-runtime` compiles again under `-D warnings` on linux-x86_64.** The crate +declared `pthread_getattr_np`, `pthread_attr_getstack` and `pthread_attr_destroy` +in three separate `extern "C"` blocks using two different spellings of the +`pthread_attr_t` buffer — `[u64; 8]` in `gc::roots::get_stack_bottom`, `*mut u8` +in `gc::roots::stack_maps::fp_chain` and in the Error-stack frame walk. Declaring +one symbol twice in a crate with different signatures is +`clashing_extern_declarations`, which `-D warnings` denies, and the crate stopped +building outright (#9486's frame walk supplied the third copy). + +The `*mut u8` spelling was not itself new, but its only previous home was +`fp_chain`, which is gated to `target_arch = "aarch64"`; on an x86_64 host that +module is compiled out and the two spellings never met. + +All three blocks now use `[u64; 8]`, the spelling the collector has always used. +That also clears the latent aarch64-linux collision between `fp_chain` and +`gc::roots`, and replaces a `[u8; 128]` buffer — 128 bytes but aligned to 1 — +with one that is both large enough for `pthread_attr_t` on every supported +glibc/musl target (56 bytes on both) and correctly aligned for it.