cgen: emit GC_allow_register_threads() constructor for shared libs#27200
cgen: emit GC_allow_register_threads() constructor for shared libs#27200eptx wants to merge 1 commit into
Conversation
…lang#27178) When a V `-shared` library is dlopen'd by a host runtime that spawns its own OS threads (Rust cargo workers, C# tasks, Java JNI threads, etc.), the host threads that enter V code must be registered with libgc — otherwise libgc aborts with "Collecting from unknown thread" on the first collection triggered from an unregistered thread. Per-thread registration is inherently caller-side (the binding owns when it spawns), but the process-level enable (`GC_allow_register_threads()`) can be done by V itself at library load time. This commit emits a separate `__attribute__((constructor))` that calls it, so the binding no longer has to remember. The constructor also calls `GC_set_pages_executable(0)` before `GC_INIT()` to disable libgc's trampoline allocator (which requests rwx pages via mprotect — denied under macOS hardened runtime). The explicit `GC_INIT()` makes the timing deterministic so subsequent allocations don't lazy-init under different settings. Both calls are gated on `gc_mode in [boehm_full, boehm_incr, boehm_full_opt, boehm_incr_opt, boehm_leak]` and skipped on Windows (which uses a different shared-lib init path via gen_windows_shared_library_boehm_init). Closes vlang#27178.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1f65906ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| g.writeln('__attribute__ ((constructor))') | ||
| g.writeln('static void _v_shared_lib_gc_init(void) {') |
There was a problem hiding this comment.
Assign constructor priority for GC init
The new _v_shared_lib_gc_init and existing _vinit_caller are both emitted as plain __attribute__((constructor)) functions, but no priority is specified, so their relative execution order is not guaranteed by the toolchain. If _vinit_caller runs first, _vinit() can touch GC before GC_set_pages_executable(0) / GC_allow_register_threads() / GC_INIT() run, which defeats the fix and can still surface hardened-runtime or "unknown thread" failures in shared-library hosts. Use explicit constructor priorities (or call GC init from _vinit_caller before _vinit) to enforce ordering.
Useful? React with 👍 / 👎.
|
It already is emitted but into the main function. It would make more sense to just move it into the |
Closes #27178.
When a V
-sharedlibrary is dlopen'd by a host runtime that spawnsits own OS threads (Rust cargo workers, C# tasks, Java JNI threads,
etc.), the host threads that enter V code must be registered with
libgc — otherwise libgc aborts with
"Collecting from unknown thread"on the first collection triggered from an unregistered thread.
Per-thread registration via
GC_register_my_thread()is inherentlycaller-side, but the process-level enable
GC_allow_register_threads()can be done by V itself at library load time. This PR emits a
__attribute__((constructor))that calls it, so bindings no longerhave to remember.
The constructor also calls
GC_set_pages_executable(0)beforeGC_INIT()to disable libgc's trampoline allocator (which requestsrwx pages via mprotect — denied under macOS hardened runtime). The
explicit
GC_INIT()makes the timing deterministic so subsequentallocations don't lazy-init under different settings.
All three calls are:
gc_mode in [boehm_full, boehm_incr, boehm_full_opt, boehm_incr_opt, boehm_leak]#if defined(GC_THREADS)for the thread-registration callTested locally on macOS 26.4 ARM64 against a real-world V
-sharedconsumer (the cx-home/cx project, a structured-data library with 5
host-language bindings). Before this PR, every Rust/Go/C#/Java/TS
binding had to hand-roll a
cx_initwrapper that calledGC_allow_register_threads()at module load. After: bindings canjust dlopen the library and start using it.
V's own
make test vlib/builtinpasses 35/35 with this patch.