Repository navigation
Conversation
isoshigi
marked this pull request as draft
October 25, 2026 16:37
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
October 25, 2026 16:47
67b7abb to
00e8d73
Compare
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
October 26, 2026 12:18
00e8d73 to
37ce3d0
Compare
isoshigi
changed the base branch from
feat/exported-functions-and-entries
to
fix/wasm-memory-exhaustion
October 26, 2026 12:25
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
October 26, 2026 12:27
37ce3d0 to
7e70190
Compare
Collaborator
Author
|
rebase 後の CI で、HTTP service の service 経路だけ |
isoshigi
force-pushed
the
fix/wasm-memory-exhaustion
branch
from
October 26, 2026 14:01
404343c to
a28a328
Compare
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
October 26, 2026 14:45
7694971 to
73e67cb
Compare
isoshigi
marked this pull request as ready for review
October 27, 2026 13:13
isoshigi
force-pushed
the
fix/wasm-memory-exhaustion
branch
from
October 30, 2026 12:46
a28a328 to
7cd8457
Compare
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
October 30, 2026 12:46
73e67cb to
eeae7c8
Compare
Port the fiber programs of the C target to the WASM test suite: scope and join, nested scopes, Deferred waiters and complete races, interruption with a slow release, child failure notes, use after scope close, attempt and interruption, determinism, and defects that must not trap. The tests fail until the scheduler runs fibers.
First cut of the fiber runtime: counted Fiber/Scope/Deferred/wait records, a job queue, the root fiber, scope close, fork/join/await/interrupt and the Deferred operations, with the perform records reworked to wake a caller through a wait. Simple scopes run and the module validates, but the scope close path after a fork hangs and the existing suspend tests regress (caller waits are not woken), so this is not ready: see handoff/2a-fibers.md.
Port the C runtime's per-operation boundary: perform_end attaches the implementation's captured frames as its own strand and parks the caller on a separate caller wait; a later resume wakes that wait, so the caller runs inside the resume call and resume returns when it next suspends or ends. A defect or failure before the resume goes to the caller; after it ends the run. Waiting now resumes the same fiber inside the wake, the event loop queues it, the every-16th fairness yield goes through the later list, and an abandoned caller unwinds its implementation first. The run's result is kept while the strands still finishing return, and the TestClock wakes the earliest sleeper.
The corpus now matches C/JS for fiber_failures, fiber_unwinding, suspend_resume, fiber_fairness, fiber_deferred and fiber_main_fails, so their skip groups are removed. The emitted-byte pins move with the runtime change.
The run's list, the queued begin job and the handle each count a forked fiber. `fb_finish` drops the run's list reference, but the begin path handed the job's reference to it and never released it, so every child fiber record (and the scope it referenced) stayed alive for the rest of the run. Release the job's reference after the strand ends, in both the normal and the interrupted-before-start paths. A wait's live flag is a value, not a reference: the waiter descriptor marked it as one, and `wait_new` did not initialize it, so dropping a wait that was never suspended could free whatever stale pointer the reused block held. Mark `W_ONDROP` and `W_IMPL` instead (the fields that are references) and zero the live flag when the wait is created. A 400 round, 20 children per round run now fits the same six pages as the 50 round one.
`fb_idle` passed the address of the waitable-set cell to `waitable-set.wait` and `waitable-join` instead of the handle stored there, so wasmtime rejected the address as an unknown handle index as soon as a host timer started a subtask. Load the handle first. Finding the event's sleeper branched to the loop instead of out of the block enclosing it, so a returned event spun forever instead of waking the sleeper. Branch to the block. `SystemClock.sleep` now waits on the host clock on wasmtime 49.0.3 and 50.0.0, and so does an implementation that resumes more than once on the real clock.
…kips `[target.wasm.unsupported]` lists no construct. The driver tests still asserted that a fiber program is rejected on the WASM target: turn the diagnostic test into a run of the fiber program, and remove the fiber entries from the example and parity skip lists so the fibers example is compared with JS like the others.
`fb_finish_run` dropped every wait left in the run's list without taking it out of its target's list or out of the host and TestClock sleeper lists, so those heads and tails kept pointing at released walks. A later run's `fb_test_sleep` appended through a stale tail and its `fb_wake_test` woke an abandoned implementation continuation from the previous run. Unlink each wait before dropping it, and reset the host, TestClock and sequence cells when the next run starts. A two-test regression leaves an implementation sleeping after it resumed its caller (spec 14.5 does not wait for it) and then sleeps with TestClock in the next run.
A child that suspends as its first act attaches its continuation to the suspension and returns from `fb_begin`'s suspension branch without dropping the begin job's reference to the fiber, so the fiber, its scope and its value stayed alive. Drop that reference after `fb_suspended`. `fb_scope_finish` released the body's input only for a reference value with no child failure. For a failure or defect/interruption (modes 1 and 2) it emitted the wasm stack `drop`, which discards the value without releasing it, so a failed scope kept its error alive. Release the input through the runtime `drop` for those modes too. Regressions: a 400-round scope of twenty sleeping children stays in the six-page limit, and 3000 failing scopes with a ten-field error do too.
Failure notes were keyed by the error pointer, so two scopes that fail the same static error shared one note entry and the second display also showed the first scope's note. Copy a static error when it first gets a note and store the copy in the cause's error slot, as the C runtime's `em_notes_add` does. Also release the note string that an append replaces, and release the shown error and the intermediate string in `show_failure` (the appends borrow their arguments). A regression runs the same scope twice in one run and checks that each cause carries exactly its own note.
…ases The branch left two unused helpers (`dupv`, `ret_i32`), an unused `unwinding_cell` and an unused status constant, and two locals named like constants. Remove them and name the node and word locals in snake case so `cargo clippy --workspace --all-targets --locked -- -D warnings` passes.
An implementation continuation that suspended again kept two references: `fb_advance` handed the perform it owned to `fb_suspended`, which takes its own reference, and never released the incoming one; and `wait_wake` cleared the wait's `W_IMPL` before unlinking it, so `fb_unlink_wait` could not clear the perform's back-pointer or release the wait reference the perform held. The wait, its frames and the perform stayed alive per sleep. Release the perform in both of `fb_advance`'s exit paths, and unlink the wait while it still names its perform. A 4000-round operation that sleeps twice per call now fits the same six pages as the one-sleep test.
`fb_drop_wait` left the implementation wait it recursed into alive, and `fb_wait_wake` returned without consuming a wait that had already been woken or unwound even though every caller owns the reference it passes. Release the recursive wait after queueing its orphan continuation, and drop the dead wait on the early wake.
`fb_root_begin` reset the orphan cell for the next run without releasing a defect that the previous run's result did not use (it only replaces an interrupted result). Drop it before the reset, like the notes of the previous run.
The scope's `S_CLOSER` is a raw slot, but it was filled with `st_dup`, adding a reference that the finalizer-less scope record never released; every scope that closed with a sleeping child kept the closer wait, the child fiber and their records. Store the wait as the raw pointer it is: the suspension owns the one reference and waking it consumes that. Also release a reference wake value with the runtime `drop` (not the wasm stack drop) in the dead-wait path, consume the wake in the defensive `W_FIBER == 0` path, and note at the post-stall turn why the backend ends when it does. Regression: 3000 scopes whose body returns while a TestClock sleeper child runs, under the six-page limit.
`show_failure` emitted the notes query whenever a fiber plan existed, which is true for any suspending program, but the notes cell is 0 when the fiber runtime is not used. `fb_notes_of` then read address 0 (the scratch area, where a host subtask handle can be left) as the notes list. Guard the query on a real notes cell, and make the notes reader and cleaner emit no read at all when the cell is 0. A display whose `show` raises a defect no longer appends notes to the showing result: it returns null and leaves the defect as the outcome, so the test protocol cannot report a note as an error message while a defect is pending. Regressions: a suspending non-fiber program that sleeps then fails (its show path must not read address 0; the component scan checks the call), and a `@test` whose error's `show` raises a defect while notes exist (no note in the test message).
The service path kept calling `root_run` after the fiber scheduler replaced it with `fb_root_run`, so building a service failed with "missing runtime function". Each request is now a run of its own: `fb_root_begin` before the launch and `fb_root_run` after it, as for `main` and the tests.
…ions Resume a fairness yield from its wait like the C runtime's EM_JOB_GO: the wait keeps the continuation's owner (the fiber's main continuation or an operation implementation) and the operation call it settles. A pause inside an implementation no longer resumes as main and ends the run in place of the caller, and an interruption can still find and unwind the wait. Deliver a pending interruption to a queued resume instead of the value it carries, and release the replaced value. Interrupting a child now uses the recursive wait drop, so every implementation it is nested in unwinds innermost first and none resumes after the scope ends. Release the interrupted value when a defect replaces it, and the run result before a service request clears it. Make show_failure's borrowing contract explicit and release its argument at every owning caller. Remove the dead fb_step helper, document the scope close frame's shared word and non-reference slot, and note the non-terminal waitable status. Add C/JS/WASM parity regressions for the three blockers, and update the pinned component bytes.
isoshigi
force-pushed
the
fix/wasm-memory-exhaustion
branch
from
November 4, 2026 15:20
7cd8457 to
8a5c8fa
Compare
isoshigi
force-pushed
the
feat/wasm-fibers
branch
from
November 4, 2026 15:20
eeae7c8 to
ed0861c
Compare
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.
PR 5
fix/wasm-memory-exhaustion(fix(wasm): import only the std hosts reached from the entry #442〜fix(wasm): trap when the heap cannot grow #444 の上)feat/wasm-fibers概要
WASM target で
Fiber/Scope/Deferred/ interruption / release / scheduler を動かします。C backend の per-operation caller boundary を移植し、[dyn.suspend]の「caller はresumeの呼び出しの内側で再開する」を満たします。レビューで見つかった blocker の修正
EM_JOB_GOと同じ形で job に載せ、end/implを保持したまま再開する。implementation の継続が main として再開され、本当の main continuation が捨てられる問題を修正。JK_RESUMEでfb_interruptingを確認し、値ではなく interruption を届ける(置き換える値は参照なら解放)。scope body の return 後に child が走らない。interrupt_fiberをfb_drop_waitの再帰に置き換え、内側からすべて unwind する。scope 終了後に内側の実装が動かない。回帰テストを
crates/emela-driver/tests/suspend_parity.rsに追加(C/JS/WASM パリティ)。修正前のソースで WASM だけが期待と異なることも確認済み。レビューで見つかった major の修正
defect.rsの raise / defect_resume)。C と同じく解放。Run::ServiceのリクエストごとにG_DEFECT/G_FAILUREの run result を drop せず消していた。clear 前に解放。show_failureの borrow 契約を明示し、所有している呼び出し側(fb_cause_string、settled_impl、test_result、finish、cause_to_string)で error を解放。fb_step、空のifを削除。scope_close_descの slot 6/7 とidleの非終端 status の前提をコメント化。fb_drainの run-over 分岐のbr(0)をbr(1)に修正。変更点
perform_endが implementation の captured frames を strand として保持し、caller をWK_CALLERwait で park する。resumeが caller を wake し、同じ fiber ならその場で走らせる。fb_drop_waitで abandoned caller の implementation を innermost-first に unwind する。Fiber.attemptは自分の interruption を捕まえない。run_mode/run_wordの終了処理。SystemClock.sleepの host wait(monotonic-clock.wait-for)とwaitable-set-*/subtask-drop。[target.wasm.unsupported]の「fibers は unsupported」を前提にした stale な skip を削除。#442〜#444 / main との統合
reach(fix(wasm): import only the std hosts reached from the entry #442)を前提に rebase した。httpruntime とRun::Serviceを残し、fiber runtime をそのあとに登録する。check/export.rsとuser_exportsは使わない(ユーザーの@export(wasm)は driver が拒否する)。test_resultの show-defect 修正に追随し、notes のテストを「show の defect はそのテストのメッセージとして報告され、note にはならない」に更新。テスト
EMELA_WASMTIME=... cargo test -p emela-codegen-wasm --no-fail-fastcargo test -p emela-driver --test wasm_corpus --test suspend_parity --test wasm --test parity --test wasm_examples --test wasm_paritycargo fmt --all --check,cargo clippy --workspace --all-targets --locked -- -D warnings,scripts/spec.py check2E hardening
defect:+ exit 101 になり、wasm trap にならない。fiber_*.emelcorpus と、今回の blocker 3 件の回帰。既知の制約 / follow-up
subtask.cancelは、terminal event 待ちで trap するため保留。event は次のfb_idleが消費し、残りは instance teardown で回収される(bounded)。later/ event loop を再確認する。仕様判断として記録。State[Instant]を更新するタイミング)は spec の記述と実装の差があり、著者判断が必要。spec/targets/wasm.mdはまだSystemClock.sleepを「利用不可」とし、import 一覧と中断時 timer の記述も実装と一致しない。仕様変更は別 proposal / spec 変更で扱う(この PR では触っていない)。EMELA_LEAK_CHECK相当がなく、Service の複数リクエスト harness も別途必要)。