Expose execution-stack headroom for cooperative guest error recovery (PostgreSQL embedding)
Use case: PostgreSQL needs an early execution-stack check, before a terminal trap
We embed PostgreSQL 18.4 in Oliphaunt using Rust Wasmer/WASIX and serialized LLVM AOT. Deep JSON/expression/PL/pgSQL recursion exposed a mismatch: PostgreSQL checks its C/shadow stack in Wasm linear memory, but the engine has a separate native execution stack. The latter can run out first.
Our consumed runtime is Wasmer 7.2.1 / wasmer-wasix 0.702.1; the experiments below are Linux x86_64 with the unchanged 1 MiB execution-stack default. They are not a new reproduction of every ASan failure reported against 7.2.0-alpha.1 in #6793 / #6795 / #6774, nor qualification of current upstream main.
The hard exception-entry guard in #6913 is useful independently. Our additional requirement is an earlier, cooperative check: PostgreSQL must still have enough execution stack to raise its ordinary SQLSTATE 54001, unwind through its guest-local error boundary, roll back and keep the connection usable. We are not asking to catch a terminal Wasmer stack-overflow trap and pretend PostgreSQL recovered.
What an embedding-facing solution needs
- Measure the active Wasm execution stack, excluding guard pages/platform reserves. An ordinary Rust host callback runs on the parent stack, so sampling its local address measures the wrong stack. Use the real SP, including under sanitizers; distinguish unsupported/unavailable accounting from a known budget.
- Stable accounting across nested calls, pooled stacks and traps. Restore previous bounds on the parent stack so a guest's non-local exit cannot skip restoration. For a configured budget, do not silently expose extra capacity merely because a larger cached stack was reused.
- Separate application recovery policy from the engine hard floor. The engine protects its own helpers/unwinder; PostgreSQL selects an earlier threshold covering unchecked descent, error reporting and cleanup. Neither threshold guarantees arbitrary unchecked code can recover.
- A cheap, narrowly scoped query. We prototyped a fixed scalar runtime operation using Wasmer's existing function registration/trampoline machinery. Its successful path has no allocation, lock, user callback or host-stack switch. Ordinary host calls keep their isolation and require no suspended-stack snapshots. We do not need a general way for arbitrary callbacks to bypass that isolation, or a PostgreSQL-specific ABI in upstream Wasmer.
- An explicit backend contract. Our prototype rejects constructing the binding where accounting is unsupported, and terminates on missing accounting during a supported invocation. Browser/V8 and Windows need their own supported contract; a fake unlimited budget is not a substitute.
Evidence and reusable code
Pinned candidate patches, regression probes, measurements and artifact hashes.
Our candidate passes 62 VM unit tests in both debug and release, a 1,000-cycle scalar/nested-call probe, and serialized-AOT throw/catch/rethrow checks. Real PostgreSQL tests pass in memory and directory mode at max_stack_depth=100kB and 2MB, including repeated depth errors followed by valid queries, PL exception catches, savepoint rollback and directory reopen. Its 512 KiB application reserve and 64 KiB engine floor are experimental, not proven cross-platform constants. The candidate is not enabled in released consumer packages.
We ran 144 successful benchmark children against our previous guarded direct-binding candidate, with identical PostgreSQL guest/AOT and SDK dependency versions. RTT improved about 2.6% in memory and 3.6% in a directory follow-up; bulk-query results were mixed. This is not an unguarded-runtime comparison or a blanket non-regression claim. The linked report retains the initial directory INSERT warning as well as its smaller follow-up result.
PostgreSQL's historical independent IA64 register-stack check is relevant precedent for checking a second constrained stack at the existing application recursion boundary.
Would maintainers prefer a shared VM headroom primitive with a narrowly scoped callable binding, or another existing engine mechanism for this use case? We would like to build on #6913's accounting rather than maintain a parallel implementation. This embedding API can be a follow-up and should not hold up the independent EH hardening. An upstream-published contract also matters for ordinary Rust SDK consumers, whose dependency builds cannot inherit our repository's root Cargo patches.
Source: wasmerio/wasmer