Skip to content

fix(runtime): #4876 keep js_promise_report_unhandled_rejections alive through auto-optimize LTO#4883

Merged
proggeramlug merged 1 commit into
mainfrom
fix-4876-unhandled-rejection-symbol
Jun 10, 2026
Merged

fix(runtime): #4876 keep js_promise_report_unhandled_rejections alive through auto-optimize LTO#4883
proggeramlug merged 1 commit into
mainfrom
fix-4876-unhandled-rejection-symbol

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #4876.

Problem

Starting with v0.5.1151, every native link fails with:

undefined symbol: _js_promise_report_unhandled_rejections
  >>> referenced by _main
Error: Linking failed

Codegen emits an unconditional call to js_promise_report_unhandled_rejections in the generated _main (the program-end unhandled-rejection hook added by the #4838/#4852 readable-unhandled-rejection work). The runtime defines the symbol (#[no_mangle] pub extern "C" in promise/then.rs), but the auto-optimize whole-program-bitcode internalize + dead-strip pass drops it: the function is reachable only from generated .o, and without a #[used] anchor LTO internalizes then strips it.

Affected: aarch64-apple-{darwin,ios,tvos}, x86_64-unknown-linux-gnu. Android linked because it doesn't go through the same auto-optimize bitcode path.

Fix

Add a #[used] static KEEP_PROMISE_REPORT_UNHANDLED_REJECTIONS anchor in crates/perry-runtime/src/promise/then.rs, matching the established keepalive pattern for codegen-only FFIs in error.rs and promise/combinators.rs.

Validation

Built perry + runtime/stdlib, cleared .perry-cache, compiled a minimal program with an unhandled Promise.reject. The auto-optimize path (rebuilds runtime+stdlib with LTO) now links cleanly:

$ perry t.ts -o t && ./t
hello
Uncaught (in promise) Error: boom   # exit 1

Before this fix the same compile failed at link with the undefined symbol.

… through auto-optimize LTO

Codegen emits an unconditional call to js_promise_report_unhandled_rejections
in the generated _main (the program-end unhandled-rejection hook from the
#4838/#4852 work), but the runtime symbol was being dropped by the
auto-optimize whole-program-bitcode internalize+dead-strip pass. The fn is
#[no_mangle] pub extern "C" and reachable only from generated .o, so without a
#[used] anchor LTO internalized and stripped it — every native link
(aarch64-apple-{darwin,ios,tvos}, x86_64-unknown-linux-gnu) failed with
'undefined symbol: js_promise_report_unhandled_rejections'. Android linked
because it doesn't go through the same auto-optimize bitcode path.

Fix: add a #[used] KEEP anchor in promise/then.rs, matching the existing
keepalive pattern in error.rs and promise/combinators.rs.
@proggeramlug proggeramlug merged commit 7a49319 into main Jun 10, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix-4876-unhandled-rejection-symbol branch June 10, 2026 07:34
proggeramlug added a commit that referenced this pull request Jun 11, 2026
)

The auto-optimize runtime/stdlib rebuild caches its output in a
`target/perry-auto-{hash}` dir keyed only on (features, panic_mode,
target, wasm-host) — not on the compiler version. The object cache is
already version-invalidated (build_cache.rs misses on
`perry_version != CARGO_PKG_VERSION`), so the two caches disagree across
a compiler upgrade on a persistent build host:

- object cache rebuilds program objects with the new codegen, which emits
  calls to newly-added runtime entrypoints; while
- the version-blind perry-auto dir hands back a stale `libperry_runtime.a`
  from the previous compiler that predates those symbols.

Result: the final link fails with "undefined symbol" for exactly the
entrypoints added since the cached runtime was built (and only those —
older entrypoints resolve fine), e.g. `js_promise_run_promise_jobs`,
`js_register_closure_strict_function`, `js_iterator_result_validate`,
`js_promise_report_unhandled_rejections`, `js_mark_entry_module_esm`.

This is why #4883's per-symbol `#[used]` anchor didn't help: the symbol
isn't being stripped from a fresh build, it's absent from a stale cached
one. Add `CARGO_PKG_VERSION` to the cache key so a compiler upgrade
forces a matching runtime rebuild, mirroring the object cache.

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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.

v0.5.1151 regression: codegen emits js_promise_report_unhandled_rejections but runtime doesn't export it — all native links fail

1 participant