Raise wasm C stack size so deep recursion no longer traps (fixes #47)#48
Open
paulmanoni wants to merge 1 commit into
Open
Raise wasm C stack size so deep recursion no longer traps (fixes #47)#48paulmanoni wants to merge 1 commit into
paulmanoni wants to merge 1 commit into
Conversation
QuickJS's parser/evaluator recurse in C. The qjswasm target linked with wasm-ld's small default stack, so deeply (but ordinarily) nested source overflowed it and surfaced as "wasm error: out of bounds memory access" — e.g. ~1000 nested parens via plain Eval, and as few as ~15-20 levels of source nesting when running @vue/compiler-sfc (which adds its own recursion on top). Fixes fastschema#47. Root cause: the `--stack-first` / `--initial-memory` add_link_options sit AFTER add_executable(qjswasm), so they never applied to that target. Put the stack sizing in the target_link_options(qjswasm ...) block that actually takes effect: -z stack-size=16777216 (16 MiB C stack; was wasm-ld's small default) --initial-memory=20971520 (20 MiB; with --stack-first the stack lives at the bottom of linear memory, so initial memory must exceed stack-size + data) Verified: nested-paren Eval now succeeds to depth 20000 (was trapping at 1000), and a real 128-component Vue app that previously failed on 10 files now compiles all 128. qjs.wasm regenerated via `make` + wasm-opt -O3.
paulmanoni
pushed a commit
to paulmanoni/nexus
that referenced
this pull request
Jun 1, 2026
Point qjs at paulmanoni/qjs (fix/wasm-stack-size-deep-recursion) via a replace directive. The fork raises the wasm C stack so QuickJS's recursive parser no longer traps on deep expressions — upstream qjs v0.0.6 traps with "out of bounds memory access" while compiling ordinary SFC <script setup> code. With this pin the CGo-free vue_qjs backend compiles a real 128-SFC app 128/128 (was 10 failures). Pure Go, no MaxStackSize needed. TEMPORARY: drop this replace and bump to a released version once the upstream fix lands (fastschema/qjs#48). Does not affect default builds — the qjs backend only compiles under -tags vue_qjs.
paulmanoni
pushed a commit
to paulmanoni/nexus
that referenced
this pull request
Jun 1, 2026
Plain `nexus build` / `nexus dev` (no cgo, no build tags) now compile .vue via the QuickJS-NG-over-WASM backend, so Vue support works in the standard pure-Go install. The native CGo binding stays available as an opt-in via `-tags vue` (CGO_ENABLED=1), which is faster per SFC. Backend plumbing: - Untag bootstrap.go / plugin.go (they were pure Go, only cgo-tagged because the package was) and compile_qjs.go (drop the vue_qjs tag) so the WASM backend + bundle bootstrap + esbuild plugin compile without cgo. - Generalize Pool to a factory (NewPool(func() (SFCCompiler, error), size)) so either backend gets the same concurrency; add Close() to the SFCCompiler interface so the pool can tear members down. CLI wiring (cmd/nexus): - frontend_vue_common.go: shared backend-agnostic bootstrap+pool+plugin helper and vuePoolSize(). - frontend_build_vue_qjs.go (//go:build !vue): default hook → WASM. - frontend_build_vue.go (//go:build cgo && vue): opt-in hook → native. - Update the "no SFC compiler" message: it now only triggers when you pass -tags vue without cgo. Verified: a pure-Go (CGO_ENABLED=0) nexus binary bundles a real 128-SFC app — 261 output files with code splitting in ~4s, all SFCs compiled. Requires the qjs WASM stack fix (currently via the fork-pin in go.mod; upstream fastschema/qjs#48).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Evaluating deeply‑nested expressions traps with
wasm error: out of bounds memory accessinstead of completing (or returning a catchable error). It's a C call‑stack overflow: QuickJS's parser/evaluator recurse in C, and theqjswasmtarget linked with wasm‑ld's small default stack.The ceiling is low enough to hit real code:
Evalof ~1000 nested parens traps;@vue/compiler-sfc(which adds its own recursion on top of the source AST) it traps at only ~15–20 levels of ordinary source nesting — array/object config literals with arrow callbacks, ternaries, optional chaining, etc.Fixes #47.
Root cause
--stack-firstand--initial-memoryare added viaadd_link_options(...)afteradd_executable(qjswasm)inqjswasm.cmake, so they never apply to that target (add_link_optionsonly affects targets created after it). The effective link therefore used wasm‑ld's default stack size.Fix
Move the stack sizing into the
target_link_options(qjswasm PRIVATE ...)block that actually applies to the target:qjs.wasmregenerated viamake+wasm-opt -O3(WASI SDK 33).Verification
Evalnow succeeds to depth 20000 (previously trapped at 1000).@vue/compiler-sfcon qjs: 10/128 files failed before, 0/128 fail now.Notes / follow‑ups (not in this PR)
(*Runtime).callreturned anerrorinstead ofpanic-ing (today a single deepEvalcan crash the host and poison the runtime), and if QuickJS's ownJS_SetMaxStackSizeguard were coupled just under the linker stack so true runaway recursion throws a catchableRangeError. Glad to do those in a follow‑up if you're interested.