Skip to content

Commit 36cd088

Browse files
committed
fix(runtime): route macOS ARM64 TLS through pthread_self()
The Layer-1 and Layer-2 TLS helpers both grouped Linux aarch64 and Darwin aarch64 under a single `mrs TPIDR_EL0` branch. That works for glibc/musl — offset 0 of TPIDR_EL0 is the TCB self-pointer by convention — but Apple reserves TPIDR_EL0 on Apple Silicon and the userland mrs read returns a value whose offset-0 dereference lands in unmapped or privileged memory. Apple-clang CI segfaulted on TlsIntrinsic.{Read64ViaIntrinsic, Read32ViaIntrinsic, ResolutionWithNonZeroDelta} the first time the VM touched `vmpilot_tls_read64(0)`. Split the arm64 arms: - Linux aarch64 keeps the mrs fast path. - Darwin aarch64 joins Darwin x86_64 in the pthread_self() branch, which returns a real user-readable `_opaque_pthread_t*`; both the runtime and the test compute the expected value through the same path so the assertion still holds. - Darwin aarch64 no longer matches any Layer-1 arm and transparently falls through to Layer-2's fallback_read64/32 via get_segment_base_fallback(). No Windows / Linux path is touched. Ships alongside the Stage 11/12 work; was flagged during CI review and kept out of those commits because it's an unrelated pre-existing macOS bug.
1 parent 7744092 commit 36cd088

1 file changed

Lines changed: 18 additions & 9 deletions

File tree

runtime/src/tls_helpers.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,22 @@ inline uint8_t* get_segment_base_fallback() noexcept {
6767
return reinterpret_cast<uint8_t*>(static_cast<uintptr_t>(base));
6868
}
6969

70-
#elif (defined(VMPILOT_OS_LINUX) || defined(VMPILOT_OS_DARWIN)) && defined(__aarch64__)
71-
// ARM64: TPIDR_EL0 is the thread pointer — readable via mrs instruction.
72-
// The "fallback" is the same instruction since there's no alternative.
70+
#elif defined(VMPILOT_OS_LINUX) && defined(__aarch64__)
71+
// Linux ARM64: TPIDR_EL0 is the thread pointer and offset 0 is the
72+
// TCB self-pointer (glibc/musl convention) so the mrs instruction
73+
// returns a user-readable base.
7374
inline uint8_t* get_segment_base_fallback() noexcept {
7475
uint64_t base;
7576
asm volatile("mrs %0, TPIDR_EL0" : "=r"(base));
7677
return reinterpret_cast<uint8_t*>(base);
7778
}
7879

79-
#elif defined(VMPILOT_OS_DARWIN) && defined(__x86_64__)
80-
// macOS x86_64 (Intel, discontinued but may still run in CI).
81-
// macOS doesn't expose fs:/gs: for user TLS. Use pthread_self()
82-
// which returns the thread struct base — TLS lives at negative offsets.
80+
#elif defined(VMPILOT_OS_DARWIN) && (defined(__aarch64__) || defined(__x86_64__))
81+
// macOS (Apple Silicon + Intel): TPIDR_EL0 on arm64 does NOT point at
82+
// user-accessible memory — Apple reserves it for kernel use and
83+
// exposes per-thread state via pthread_self() instead. pthread_self()
84+
// returns the `_opaque_pthread_t*` which IS user-readable, so we use
85+
// the same pointer for both arches.
8386
#include <pthread.h>
8487
inline uint8_t* get_segment_base_fallback() noexcept {
8588
return reinterpret_cast<uint8_t*>(pthread_self());
@@ -193,9 +196,15 @@ extern "C" void vmpilot_tls_write32(uint64_t offset, uint64_t value) noexcept {
193196
}
194197

195198
// =========================================================================
196-
// Layer 1: Linux/macOS ARM64 — TLS via TPIDR_EL0
199+
// Layer 1: Linux ARM64 — TLS via TPIDR_EL0
200+
//
201+
// macOS ARM64 deliberately opts out: Apple reserves TPIDR_EL0 for
202+
// kernel use and a userland `mrs` read returns a value whose offset-0
203+
// dereference is not guaranteed to touch user-readable memory.
204+
// macOS ARM64 therefore falls through to the Layer-2 pthread_self()
205+
// fallback, which returns a real user-space `_opaque_pthread_t*`.
197206
// =========================================================================
198-
#elif (defined(VMPILOT_OS_LINUX) || defined(VMPILOT_OS_DARWIN)) && defined(__aarch64__) && !defined(VMPILOT_TLS_FORCE_FALLBACK)
207+
#elif defined(VMPILOT_OS_LINUX) && defined(__aarch64__) && !defined(VMPILOT_TLS_FORCE_FALLBACK)
199208

200209
extern "C" uint64_t vmpilot_tls_read64(uint64_t offset) noexcept {
201210
uint64_t base;

0 commit comments

Comments
 (0)