Skip to content

Make platform.h work on any libc with the standard rseq ABI - #141

Open
thevar1able wants to merge 2 commits into
mainfrom
libc-agnostic-rseq
Open

Make platform.h work on any libc with the standard rseq ABI#141
thevar1able wants to merge 2 commits into
mainfrom
libc-agnostic-rseq

Conversation

@thevar1able

Copy link
Copy Markdown
Member

Needed for ClickHouse/ClickHouse#109239, which links ClickHouse statically against a different libc and enables silk in that build. Silk does not target any specific libc. It only requires a libc that registers rseq and exports __rseq_offset / __rseq_size / __rseq_flags (glibc >= 2.35 does). Both commits change nothing on glibc >= 2.35.

Commit 1: dispatch strerror_r on its actual return type. The old code picked the GNU char * variant of strerror_r when _GNU_SOURCE is defined. Some libcs accept _GNU_SOURCE but only provide the POSIX int variant, so the build fails (18 TUs in the ClickHouse build). Two strerrorResult overloads now handle whichever variant the libc provides.

Commit 2: read the rseq area through librseq's globals in getCurrentProcessor. Two problems with the old code:

  1. It picked the offset symbol at build time with __has_include(<sys/rseq.h>), so behavior depended on the sysroot headers, not on the libc the binary runs against.
  2. It read rseq->cpu_id with no check. On a thread whose rseq area is not registered yet, cpu_id is -1, which cast to uint16_t becomes an out-of-bounds index into per-CPU state.

Now the offset always comes from librseq's rseq_offset, which rseq_init copies from the libc. The read is guarded by rseq_size cast to signed (librseq sets it to -1U before init and to 0 when libc registration failed). If the rseq read is not usable, we fall back to sched_getcpu, clamping errors to CPU 0 - the value is a sharding hint, so a wrong shard only costs contention. On glibc the fast path gains two predictable branches; checked in the disassembly.

Tested: full suite passes on glibc (one pre-existing flaky test, StealTest.computeFibersNeverSharePhysicalCore, fails the same way on main under parallel ctest load). In the ClickHouse static build, _silk_fibers and _silk_util now compile cleanly.

Selecting the GNU char *-returning strerror_r by feature-test macros misfires on libcs that accept _GNU_SOURCE but only ship the POSIX int-returning variant, breaking the build. Two strerrorResult overloads dispatch on the real return type instead, so silk::strerror works with either variant without preprocessor guessing.
getCurrentProcessor selected the rseq offset symbol at build time via __has_include(<sys/rseq.h>) and dereferenced cpu_id unconditionally, so behavior depended on the sysroot headers rather than the runtime libc, and an unregistered thread's cpu_id of -1 turned into an out-of-bounds shard index. The offset now always comes from librseq's rseq_offset, guarded by rseq_size cast to a signed value (librseq initializes it to -1U and sets it to 0 when the libc registration failed), and a negative cpu_id falls back to sched_getcpu clamped to shard 0. On glibc 2.35+ with rseq registered the fast path is unchanged apart from two predictable branches.
@praktika-gh

praktika-gh Bot commented Aug 24, 2026

Copy link
Copy Markdown

Workflow [PR], commit [ef15d39]

Summary:

job_name test_name status info comment
Test ARM (tsan) FAIL
Bench FAIL

Code Review

Result: ✅ No issues found

What changed: Updates platform utilities to select the libc's actual strerror_r return type and to obtain rseq ABI metadata from librseq at runtime. getCurrentProcessor() now validates rseq availability and CPU IDs, falling back to sched_getcpu() when needed.

No correctness, concurrency, or performance issues found in the changed code.

Investigation: 7/13 rounds, 44 tool calls.

thevar1able added a commit to ClickHouse/ClickHouse that referenced this pull request Aug 24, 2026
Silk's stack switching is boost fcontext (libc-agnostic); its libc
coupling was confined to `platform.h` and is removed on the silk side by
ClickHouse/silk#141. The `_silk_util` target
force-includes the `__GNUC_PREREQ` shim because silk includes librseq
headers. NOTE: musl builds of silk require the silk submodule to carry
PR 141; the pointer bump follows once it merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

// glibc's sched_getcpu fast path is THREAD_GETMEM_VOLATILE(THREAD_SELF, rseq_area.cpu_id),
// which is the same rseq read we do here. We skip the function call and the cpu_id >= 0
// fallback to vDSO/syscall -- safe on Linux 4.18+ / glibc 2.35+ where rseq is always registered.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole idea is to keep this function as tiny as possible. No, I do NOT want to have a callback here.

@vadimskipin

Copy link
Copy Markdown
Collaborator

"On a thread whose rseq area is not registered yet" - how is this possible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants