Skip to content

wasm: fix pointer builtins, missing -sASYNCIFY, and add main_loop() - #632

Open
nbeerbower wants to merge 1 commit into
mainfrom
claude/wasm-browser-perf-fixes-2939ki
Open

wasm: fix pointer builtins, missing -sASYNCIFY, and add main_loop()#632
nbeerbower wants to merge 1 commit into
mainfrom
claude/wasm-browser-perf-fixes-2939ki

Conversation

@nbeerbower

Copy link
Copy Markdown
Collaborator

Three independent fixes to the browser story, all verified against
Emscripten 4.0.7 + Node.

  1. Raw pointer builtins no longer panic (or fail to link) under WASM.

    builtins_ffi.c was wrapped entirely in #ifndef EMSCRIPTEN, which
    swept up the ptr_offset / ptr_read_* / ptr_write_* / ptr_deref_*
    family living at the bottom of the file. Those need neither dlopen
    nor libffi -- they are address arithmetic and loads/stores over
    linear memory -- so the guard now closes before them.

    Four of them (ptr_offset, ptr_read_i32, ptr_write_i32,
    ptr_deref_i32) had panicking stubs in wasm_shim.c; those are gone.
    The other ten (ptr_read_i8/i16/u8/u16/u32/i64/u64/ptr/f32/f64) had
    no stub at all, so a program touching them failed at wasm-ld with
    "undefined symbol". Both cases now work.

  2. --target wasm links with -sASYNCIFY.

    hml_sleep() compiles to emscripten_sleep(), which only exists when
    Asyncify is on. Without the flag Emscripten links a throwing stub,
    so the program dies the first time it sleeps:

    Please compile your program with async support in order to use
    asynchronous operations like emscripten_sleep

    The threaded branch already documented the flag in a comment but
    never passed it. Both branches pass it now.

  3. New main_loop(callback, fps) / main_loop_stop() in @stdlib/time.

    Asyncify is not free: it instruments the module so it can unwind and
    rewind the WASM stack at any suspend point. Measured here at ~1.3-1.4x
    on CPU-bound code and +31% .wasm size.

    main_loop() hands the frame callback to the host's own scheduler
    (emscripten_set_main_loop_arg) instead, so a frame loop needs no
    sleep() and the program can be linked with the new --no-asyncify.
    It also gets the timing right: fps 0 means requestAnimationFrame,
    i.e. vsync-locked and throttled in background tabs, where a sleep()
    loop is not frame-locked at all.

    Natively it is a paced loop that returns once stopped. Under
    --target wasm it does not return, since the host owns the event loop
    from that point on -- documented in stdlib/docs/time.md.

Tests: tests/wasm/wasm_pointers.hml and tests/wasm/wasm_main_loop.hml
(both run through emcc + node in the WASM suite), plus a
tests/parity/builtins/main_loop.hml parity test covering frames,
restart, fps pacing, closures and the error paths.

make test 715 passed / 2 failed (both pre-existing on main: no
libwebsockets and no IPv6 in this container), parity 321/321,
test-compiler 54/54, test-contracts 23/23, wasm-test 19 passed.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FsuZNWvEWeYRDTk3jzNNPL

Three independent fixes to the browser story, all verified against
Emscripten 4.0.7 + Node.

1. Raw pointer builtins no longer panic (or fail to link) under WASM.

   builtins_ffi.c was wrapped entirely in #ifndef __EMSCRIPTEN__, which
   swept up the ptr_offset / ptr_read_* / ptr_write_* / ptr_deref_*
   family living at the bottom of the file. Those need neither dlopen
   nor libffi -- they are address arithmetic and loads/stores over
   linear memory -- so the guard now closes before them.

   Four of them (ptr_offset, ptr_read_i32, ptr_write_i32,
   ptr_deref_i32) had panicking stubs in wasm_shim.c; those are gone.
   The other ten (ptr_read_i8/i16/u8/u16/u32/i64/u64/ptr/f32/f64) had
   no stub at all, so a program touching them failed at wasm-ld with
   "undefined symbol". Both cases now work.

2. --target wasm links with -sASYNCIFY.

   hml_sleep() compiles to emscripten_sleep(), which only exists when
   Asyncify is on. Without the flag Emscripten links a throwing stub,
   so the program dies the first time it sleeps:

     Please compile your program with async support in order to use
     asynchronous operations like emscripten_sleep

   The threaded branch already documented the flag in a comment but
   never passed it. Both branches pass it now.

3. New main_loop(callback, fps) / main_loop_stop() in @stdlib/time.

   Asyncify is not free: it instruments the module so it can unwind and
   rewind the WASM stack at any suspend point. Measured here at ~1.3-1.4x
   on CPU-bound code and +31% .wasm size.

   main_loop() hands the frame callback to the host's own scheduler
   (emscripten_set_main_loop_arg) instead, so a frame loop needs no
   sleep() and the program can be linked with the new --no-asyncify.
   It also gets the timing right: fps 0 means requestAnimationFrame,
   i.e. vsync-locked and throttled in background tabs, where a sleep()
   loop is not frame-locked at all.

   Natively it is a paced loop that returns once stopped. Under
   --target wasm it does not return, since the host owns the event loop
   from that point on -- documented in stdlib/docs/time.md.

Tests: tests/wasm/wasm_pointers.hml and tests/wasm/wasm_main_loop.hml
(both run through emcc + node in the WASM suite), plus a
tests/parity/builtins/main_loop.hml parity test covering frames,
restart, fps pacing, closures and the error paths.

make test 715 passed / 2 failed (both pre-existing on main: no
libwebsockets and no IPv6 in this container), parity 321/321,
test-compiler 54/54, test-contracts 23/23, wasm-test 19 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsuZNWvEWeYRDTk3jzNNPL
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.

2 participants