From d14c0eb5e423d54838a615b913c5fcce3d96bf4f Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 14 Jun 2026 20:35:13 -0400 Subject: [PATCH] wasm asm: stream output line-by-line via new emit_chunk env import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the WAT tier to parity with c-emcc and pyodide for streaming output during evalLisp. Previously the asm tier buffered everything in the 0x10000 output region and the JS loader only read the bytes AFTER lumbda_eval returned — fox saw the bend demo's pre-call displays sit invisible for 18 s and then appear all at once. WAT-side changes: - New env.emit_chunk(ptr, len) import. Host function forwards the slice to the current onChunk callback so the worker can postMessage a chunk to the playground panel as work happens. - New $flush_start global tracks the offset (relative to 0x10000) where the next emit_chunk slice begins. Reset to 0 at the top of lumbda_eval alongside $output_len so successive evals don't re-emit stale bytes. - $out_char now checks for newline (i32.const 10) after the store. A newline emits the slice [flush_start, output_len) and advances flush_start to the end. Every display call ends up flushing on its trailing newline; per-char displays without a newline get buffered until the next newline or the eval-end trailing flush. - $lumbda_eval ends with a trailing-flush guard so any non-newline- terminated content (e.g. print_value's final repr) reaches the stream instead of only landing through the final lumbda_output_* read. JS loader: - importObj.env.emit_chunk decodes the slice from wasm memory and forwards to refs.currentOnChunk. - evalLisp(src, onChunk) parameter; sets/clears currentOnChunk around the lumbda_eval call. Same shape as c-emcc + pyodide. Tests: every node test that instantiates the asm wasm directly now declares a stub emit_chunk() {} alongside its bend_call stub — unit, integration, functional-cross, parity-cross-tier. Node test suite passes 23/23 unit; parity probe times out in its full sweep under our 30s ceiling so it gets run separately. Quick smoke: (display "line 1") (newline) (display "line 2") (newline) (display "line 3") emits three chunks via onChunk — "line 1\n", "line 2\n", "line 3" — and lumbda_output_* still has the full "line 1\nline 2\nline 3" as before. --- wasm/asm/lumbda-asm.loader.js | 17 +++++++++-- wasm/asm/lumbda.wat | 37 +++++++++++++++++++++++- wasm/dist-repl/asm/lumbda-asm.loader.js | 17 +++++++++-- wasm/dist-repl/asm/lumbda-asm.wasm | Bin 21530 -> 21610 bytes wasm/tests/functional-cross.mjs | 2 +- wasm/tests/integration.mjs | 2 +- wasm/tests/parity-cross-tier.mjs | 2 +- wasm/tests/unit.mjs | 35 +++++++++++++++++++++- www/playground/asm/lumbda-asm.loader.js | 17 +++++++++-- www/playground/asm/lumbda-asm.wasm | Bin 21530 -> 21610 bytes www/repl/asm/lumbda-asm.loader.js | 17 +++++++++-- www/repl/asm/lumbda-asm.wasm | Bin 21530 -> 21610 bytes 12 files changed, 133 insertions(+), 13 deletions(-) diff --git a/wasm/asm/lumbda-asm.loader.js b/wasm/asm/lumbda-asm.loader.js index e724244..852e411 100644 --- a/wasm/asm/lumbda-asm.loader.js +++ b/wasm/asm/lumbda-asm.loader.js @@ -13,7 +13,7 @@ async function _bootstrap() { // Closure-captured holder so the imported function can see the // instance's memory after instantiation completes. - const refs = { instance: null, bendUrl: null }; + const refs = { instance: null, bendUrl: null, currentOnChunk: null }; const importObj = { env: { @@ -45,6 +45,16 @@ async function _bootstrap() { return err.length; } }, + // emit_chunk(ptr, len) — WAT's out_char calls this on every + // newline (and lumbda_eval calls it once more at the end for + // trailing non-newline content) so the worker can postMessage + // a chunk to the playground panel as work happens, matching + // the C-tier Module.print + pyodide _StreamingStdout streams. + emit_chunk(ptr, len) { + if (!refs.currentOnChunk || len <= 0) return; + const mem = new Uint8Array(refs.instance.exports.memory.buffer, ptr, len); + refs.currentOnChunk(new TextDecoder().decode(mem)); + }, }, }; @@ -57,16 +67,19 @@ async function _bootstrap() { const exp = instance.exports; return { - async evalLisp(src) { + async evalLisp(src, onChunk) { const srcBytes = enc.encode(src); const srcPtr = exp.lumbda_source_ptr(); const mem = new Uint8Array(exp.memory.buffer); mem.set(srcBytes, srcPtr); + refs.currentOnChunk = onChunk || null; try { exp.lumbda_eval(srcBytes.length); } catch (e) { + refs.currentOnChunk = null; return `error: ${e.message}`; } + refs.currentOnChunk = null; const outPtr = exp.lumbda_output_ptr(); const outLen = exp.lumbda_output_len(); return dec.decode(new Uint8Array(exp.memory.buffer, outPtr, outLen)); diff --git a/wasm/asm/lumbda.wat b/wasm/asm/lumbda.wat index 5cd0827..61aa4a3 100644 --- a/wasm/asm/lumbda.wat +++ b/wasm/asm/lumbda.wat @@ -46,11 +46,26 @@ (import "env" "bend_call" (func $js_bend_call (param i32 i32 i32) (result i32))) + ;; emit_chunk(ptr, len) — flush a slice of the output buffer to JS + ;; for streaming. Called from out_char whenever a newline is emitted + ;; (and from lumbda_eval at the very end for any trailing content + ;; without a newline). Loader's host function forwards the bytes to + ;; the current onChunk callback so the worker can postMessage the + ;; chunk to the playground panel as work happens. + (import "env" "emit_chunk" + (func $js_emit_chunk (param i32 i32))) + ;; ─── Memory & exports ────────────────────────────────────────── (memory (export "memory") 32 4096) ;; 32 pages = 2 MB initial, grow to 256 MB (global $heap_ptr (mut i32) (i32.const 0x30000)) (global $output_len (mut i32) (i32.const 0)) + ;; flush_start tracks the offset in the output buffer (relative to + ;; 0x10000) where the next emit_chunk should begin. Updated each + ;; time we flush; reset to 0 at the top of lumbda_eval alongside + ;; output_len. Lets streaming carry just the most-recent line, not + ;; the entire eval-so-far buffer. + (global $flush_start (mut i32) (i32.const 0)) (global $source_ptr (mut i32) (i32.const 0x20000)) (global $source_end (mut i32) (i32.const 0x20000)) (global $intern_list (mut i32) (i32.const 4)) ;; NIL initially @@ -1278,7 +1293,16 @@ (i32.store8 (i32.add (i32.const 0x10000) (global.get $output_len)) (local.get $c)) - (global.set $output_len (i32.add (global.get $output_len) (i32.const 1)))) + (global.set $output_len (i32.add (global.get $output_len) (i32.const 1))) + ;; Newline flushes the slice [flush_start, output_len) to JS so + ;; the playground panel can render line-by-line during eval + ;; instead of waiting for the full evalLisp to return. + (if (i32.eq (local.get $c) (i32.const 10)) + (then + (call $js_emit_chunk + (i32.add (i32.const 0x10000) (global.get $flush_start)) + (i32.sub (global.get $output_len) (global.get $flush_start))) + (global.set $flush_start (global.get $output_len))))) (func $out_str (param $ptr i32) (param $len i32) (local $i i32) @@ -4201,6 +4225,7 @@ (local $val i32) (call $lumbda_init) (global.set $output_len (i32.const 0)) + (global.set $flush_start (i32.const 0)) (global.set $source_ptr (i32.const 0x20000)) (global.set $source_end (i32.add (i32.const 0x20000) (local.get $src_len))) (local.set $val (global.get $VOID)) @@ -4228,6 +4253,16 @@ (i32.const 10)) (then (call $out_char (i32.const 10)))))) (call $print_value (local.get $val)))) + ;; Trailing-content flush — print_value's repr doesn't end with a + ;; newline, so any bytes past $flush_start would otherwise miss + ;; the streaming path and only reach the UI through the final + ;; lumbda_output_ptr / lumbda_output_len read. + (if (i32.gt_u (global.get $output_len) (global.get $flush_start)) + (then + (call $js_emit_chunk + (i32.add (i32.const 0x10000) (global.get $flush_start)) + (i32.sub (global.get $output_len) (global.get $flush_start))) + (global.set $flush_start (global.get $output_len)))) ;; Trigger GC when the heap exceeds 60% of available memory. This is ;; the only safe collection point — the eval call stack has unwound, ;; so the only roots are the globals the collector knows about. diff --git a/wasm/dist-repl/asm/lumbda-asm.loader.js b/wasm/dist-repl/asm/lumbda-asm.loader.js index e724244..852e411 100644 --- a/wasm/dist-repl/asm/lumbda-asm.loader.js +++ b/wasm/dist-repl/asm/lumbda-asm.loader.js @@ -13,7 +13,7 @@ async function _bootstrap() { // Closure-captured holder so the imported function can see the // instance's memory after instantiation completes. - const refs = { instance: null, bendUrl: null }; + const refs = { instance: null, bendUrl: null, currentOnChunk: null }; const importObj = { env: { @@ -45,6 +45,16 @@ async function _bootstrap() { return err.length; } }, + // emit_chunk(ptr, len) — WAT's out_char calls this on every + // newline (and lumbda_eval calls it once more at the end for + // trailing non-newline content) so the worker can postMessage + // a chunk to the playground panel as work happens, matching + // the C-tier Module.print + pyodide _StreamingStdout streams. + emit_chunk(ptr, len) { + if (!refs.currentOnChunk || len <= 0) return; + const mem = new Uint8Array(refs.instance.exports.memory.buffer, ptr, len); + refs.currentOnChunk(new TextDecoder().decode(mem)); + }, }, }; @@ -57,16 +67,19 @@ async function _bootstrap() { const exp = instance.exports; return { - async evalLisp(src) { + async evalLisp(src, onChunk) { const srcBytes = enc.encode(src); const srcPtr = exp.lumbda_source_ptr(); const mem = new Uint8Array(exp.memory.buffer); mem.set(srcBytes, srcPtr); + refs.currentOnChunk = onChunk || null; try { exp.lumbda_eval(srcBytes.length); } catch (e) { + refs.currentOnChunk = null; return `error: ${e.message}`; } + refs.currentOnChunk = null; const outPtr = exp.lumbda_output_ptr(); const outLen = exp.lumbda_output_len(); return dec.decode(new Uint8Array(exp.memory.buffer, outPtr, outLen)); diff --git a/wasm/dist-repl/asm/lumbda-asm.wasm b/wasm/dist-repl/asm/lumbda-asm.wasm index 2b82bac4c66dadc70c789a08635c7d5acfd2a563..356398162381f7c77607fdc4c62b597db50f3e16 100644 GIT binary patch literal 21610 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 diff --git a/wasm/tests/functional-cross.mjs b/wasm/tests/functional-cross.mjs index e6fea2f..56f9ab8 100644 --- a/wasm/tests/functional-cross.mjs +++ b/wasm/tests/functional-cross.mjs @@ -19,7 +19,7 @@ function countPassFail(out) { async function runAsm() { const wasmBytes = fs.readFileSync(path.join(dist, "asm", "lumbda-asm.wasm")); - const importObj = { env: { bend_call(_p, _l, _r) { return 0; } } }; + const importObj = { env: { bend_call(_p, _l, _r) { return 0; }, emit_chunk() {} } }; const { instance } = await WebAssembly.instantiate(wasmBytes, importObj); const exp = instance.exports; exp.lumbda_init(); diff --git a/wasm/tests/integration.mjs b/wasm/tests/integration.mjs index 1b25147..21748b6 100644 --- a/wasm/tests/integration.mjs +++ b/wasm/tests/integration.mjs @@ -31,7 +31,7 @@ function nativePython(demoPath) { async function runAsm(src) { const wasmBytes = fs.readFileSync(path.join(dist, "asm", "lumbda-asm.wasm")); - const importObj = { env: { bend_call(_p, _l, _r) { return 0; } } }; + const importObj = { env: { bend_call(_p, _l, _r) { return 0; }, emit_chunk() {} } }; const { instance } = await WebAssembly.instantiate(wasmBytes, importObj); const exp = instance.exports; exp.lumbda_init(); diff --git a/wasm/tests/parity-cross-tier.mjs b/wasm/tests/parity-cross-tier.mjs index 5210118..6800d17 100644 --- a/wasm/tests/parity-cross-tier.mjs +++ b/wasm/tests/parity-cross-tier.mjs @@ -62,7 +62,7 @@ function nativePython(src) { async function withAsmTier() { const wasmBytes = fs.readFileSync(path.join(dist, "asm", "lumbda-asm.wasm")); - const importObj = { env: { bend_call() { return 0; } } }; + const importObj = { env: { bend_call() { return 0; }, emit_chunk() {} } }; const { instance } = await WebAssembly.instantiate(wasmBytes, importObj); const exp = instance.exports; exp.lumbda_init(); diff --git a/wasm/tests/unit.mjs b/wasm/tests/unit.mjs index fd0daff..fb6cc65 100644 --- a/wasm/tests/unit.mjs +++ b/wasm/tests/unit.mjs @@ -23,7 +23,7 @@ function check(name, cond, detail) { async function testAsm() { console.log("── asm tier ──"); const wasmBytes = fs.readFileSync(path.join(dist, "asm", "lumbda-asm.wasm")); - const importObj = { env: { bend_call(_p, _l, _r) { return 0; } } }; + const importObj = { env: { bend_call(_p, _l, _r) { return 0; }, emit_chunk() {} } }; const { instance } = await WebAssembly.instantiate(wasmBytes, importObj); const exp = instance.exports; exp.lumbda_init(); @@ -90,6 +90,39 @@ async function testC() { // Skip error-path test on C tier: undefined-symbol triggers a long // setjmp/longjmp chain that the Emscripten runtime executes in finite // time but our test runner times the whole suite — keep it lean. + + // Portal save / resume — REPL backbone. portal-snapshot! writes JSON + // into MEMFS at /tmp/.portal. We round-trip a binding through + // a fresh tier instance to prove the blob is self-contained. + try { m.FS.mkdir("/tmp"); } catch (e) { /* exists */ } + evalLisp("(define round-trip-val 12345)"); + evalLisp('(portal-snapshot! "rt")'); + const blob = new TextDecoder().decode(m.FS.readFile("/tmp/rt.portal")); + check("portal-snapshot! produces JSON blob", + blob.includes('"lumbda-portal-v1"') && blob.includes('round-trip-val')); + + const m2 = await createLumbdaC({ + locateFile: (p) => path.join(dist, "c", p), + print: (line) => out.push(line), + printErr: (line) => out.push("ERR: " + line), + }); + m2.cwrap("lumbda_wasm_init", null, [])(); + const _eval2 = m2.cwrap("lumbda_wasm_eval", "number", ["string"]); + const _free2 = m2.cwrap("lumbda_wasm_free_result", null, ["number"]); + function evalLisp2(src) { + out = []; + const r = _eval2(src); + let errMsg = ""; + if (r) { errMsg = m2.UTF8ToString(r); _free2(r); } + return out.join("\n") + (errMsg ? "\n" + errMsg : ""); + } + check("fresh tier has no round-trip-val", + evalLisp2("round-trip-val").includes("error")); + try { m2.FS.mkdir("/tmp"); } catch (e) { /* exists */ } + m2.FS.writeFile("/tmp/rt.portal", blob); + evalLisp2('(portal-load! "rt")'); + check("portal-load! restores round-trip-val", + evalLisp2("round-trip-val").trim() === "12345"); } (async () => { diff --git a/www/playground/asm/lumbda-asm.loader.js b/www/playground/asm/lumbda-asm.loader.js index e724244..852e411 100644 --- a/www/playground/asm/lumbda-asm.loader.js +++ b/www/playground/asm/lumbda-asm.loader.js @@ -13,7 +13,7 @@ async function _bootstrap() { // Closure-captured holder so the imported function can see the // instance's memory after instantiation completes. - const refs = { instance: null, bendUrl: null }; + const refs = { instance: null, bendUrl: null, currentOnChunk: null }; const importObj = { env: { @@ -45,6 +45,16 @@ async function _bootstrap() { return err.length; } }, + // emit_chunk(ptr, len) — WAT's out_char calls this on every + // newline (and lumbda_eval calls it once more at the end for + // trailing non-newline content) so the worker can postMessage + // a chunk to the playground panel as work happens, matching + // the C-tier Module.print + pyodide _StreamingStdout streams. + emit_chunk(ptr, len) { + if (!refs.currentOnChunk || len <= 0) return; + const mem = new Uint8Array(refs.instance.exports.memory.buffer, ptr, len); + refs.currentOnChunk(new TextDecoder().decode(mem)); + }, }, }; @@ -57,16 +67,19 @@ async function _bootstrap() { const exp = instance.exports; return { - async evalLisp(src) { + async evalLisp(src, onChunk) { const srcBytes = enc.encode(src); const srcPtr = exp.lumbda_source_ptr(); const mem = new Uint8Array(exp.memory.buffer); mem.set(srcBytes, srcPtr); + refs.currentOnChunk = onChunk || null; try { exp.lumbda_eval(srcBytes.length); } catch (e) { + refs.currentOnChunk = null; return `error: ${e.message}`; } + refs.currentOnChunk = null; const outPtr = exp.lumbda_output_ptr(); const outLen = exp.lumbda_output_len(); return dec.decode(new Uint8Array(exp.memory.buffer, outPtr, outLen)); diff --git a/www/playground/asm/lumbda-asm.wasm b/www/playground/asm/lumbda-asm.wasm index 2b82bac4c66dadc70c789a08635c7d5acfd2a563..356398162381f7c77607fdc4c62b597db50f3e16 100644 GIT binary patch literal 21610 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 diff --git a/www/repl/asm/lumbda-asm.loader.js b/www/repl/asm/lumbda-asm.loader.js index e724244..852e411 100644 --- a/www/repl/asm/lumbda-asm.loader.js +++ b/www/repl/asm/lumbda-asm.loader.js @@ -13,7 +13,7 @@ async function _bootstrap() { // Closure-captured holder so the imported function can see the // instance's memory after instantiation completes. - const refs = { instance: null, bendUrl: null }; + const refs = { instance: null, bendUrl: null, currentOnChunk: null }; const importObj = { env: { @@ -45,6 +45,16 @@ async function _bootstrap() { return err.length; } }, + // emit_chunk(ptr, len) — WAT's out_char calls this on every + // newline (and lumbda_eval calls it once more at the end for + // trailing non-newline content) so the worker can postMessage + // a chunk to the playground panel as work happens, matching + // the C-tier Module.print + pyodide _StreamingStdout streams. + emit_chunk(ptr, len) { + if (!refs.currentOnChunk || len <= 0) return; + const mem = new Uint8Array(refs.instance.exports.memory.buffer, ptr, len); + refs.currentOnChunk(new TextDecoder().decode(mem)); + }, }, }; @@ -57,16 +67,19 @@ async function _bootstrap() { const exp = instance.exports; return { - async evalLisp(src) { + async evalLisp(src, onChunk) { const srcBytes = enc.encode(src); const srcPtr = exp.lumbda_source_ptr(); const mem = new Uint8Array(exp.memory.buffer); mem.set(srcBytes, srcPtr); + refs.currentOnChunk = onChunk || null; try { exp.lumbda_eval(srcBytes.length); } catch (e) { + refs.currentOnChunk = null; return `error: ${e.message}`; } + refs.currentOnChunk = null; const outPtr = exp.lumbda_output_ptr(); const outLen = exp.lumbda_output_len(); return dec.decode(new Uint8Array(exp.memory.buffer, outPtr, outLen)); diff --git a/www/repl/asm/lumbda-asm.wasm b/www/repl/asm/lumbda-asm.wasm index 2b82bac4c66dadc70c789a08635c7d5acfd2a563..356398162381f7c77607fdc4c62b597db50f3e16 100644 GIT binary patch literal 21610 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