|
4 | 4 |
|
5 | 5 | /* ================================================================ |
6 | 6 | WASM MODULE LOADER |
7 | | - Lazily instantiates assets/wasm/multilingual.wasm — the full |
8 | | - multilingual interpreter compiled to WASM via Python + Cranelift |
9 | | - (multilingualprogramming[wasm]). No wasm-bindgen glue file. |
| 7 | + Lazily instantiates assets/wasm/multilingual.wasm — the demo.ml |
| 8 | + program compiled to WASM by `multilingual build-wasm-bundle` via |
| 9 | + WATCodeGenerator (multilingualprogramming[wasm]). |
10 | 10 |
|
11 | | - Expected binary exports: |
12 | | - run_code(ptr: i32, len: i32) -> i32 source in, JSON result out |
13 | | - __wasm_alloc(size: i32) -> i32 linear-memory allocator |
14 | | - __wasm_free(ptr: i32, size: i32) linear-memory free |
15 | | - memory WebAssembly.Memory |
| 11 | + The WAT backend uses host-import callbacks for all output rather |
| 12 | + than returning values. The browser must supply these imports: |
| 13 | +
|
| 14 | + env.print_str(ptr: i32, len: i32) — print a UTF-8 string slice |
| 15 | + env.print_f64(val: f64) — print a number |
| 16 | + env.print_bool(val: i32) — print True (1) or False (0) |
| 17 | + env.print_sep() — print an argument separator (space) |
| 18 | + env.print_newline() — print a newline |
| 19 | +
|
| 20 | + Output is captured into a string buffer that is reset before each |
| 21 | + call to __main(). |
| 22 | +
|
| 23 | + The Run buttons on individual code examples call __main() and show |
| 24 | + the captured output. For a fully interactive experience with |
| 25 | + arbitrary code, visit the live playground linked on the WASM page. |
16 | 26 |
|
17 | 27 | The loader resolves the base URL from the <meta name="base-url"> tag |
18 | 28 | so paths work identically on local dev and under /docs on GitHub Pages. |
|
23 | 33 | const MLWasm = (() => { |
24 | 34 | let _instance = null; |
25 | 35 | let _memory = null; |
| 36 | + let _outputBuf = ''; |
26 | 37 | let _modulePromise = null; |
27 | 38 |
|
28 | | - const enc = new TextEncoder(); |
29 | 39 | const dec = new TextDecoder(); |
30 | 40 |
|
31 | | - /* Instantiate the binary directly — no wasm-bindgen glue file needed. |
32 | | - * The Cranelift-compiled binary exports: |
33 | | - * run_code(ptr: i32, len: i32) -> i32 (returns ptr to JSON result) |
34 | | - * __wasm_alloc(size: i32) -> i32 |
35 | | - * __wasm_free(ptr: i32, size: i32) |
36 | | - * memory (WebAssembly.Memory) */ |
| 41 | + /* Host imports required by every WAT module produced by WATCodeGenerator. */ |
| 42 | + const importObject = { |
| 43 | + env: { |
| 44 | + print_str(ptr, len) { |
| 45 | + _outputBuf += dec.decode(new Uint8Array(_memory.buffer, ptr, len)); |
| 46 | + }, |
| 47 | + print_f64(val) { |
| 48 | + /* Match Python's default float repr: drop trailing .0 for integers. */ |
| 49 | + _outputBuf += Number.isInteger(val) ? String(val) : String(val); |
| 50 | + }, |
| 51 | + print_bool(val) { _outputBuf += val ? 'True' : 'False'; }, |
| 52 | + print_sep() { _outputBuf += ' '; }, |
| 53 | + print_newline() { _outputBuf += '\n'; }, |
| 54 | + }, |
| 55 | + }; |
| 56 | + |
37 | 57 | function load() { |
38 | 58 | if (_modulePromise) return _modulePromise; |
39 | 59 |
|
40 | 60 | const wasmUrl = baseUrl + '/assets/wasm/multilingual.wasm'; |
41 | 61 |
|
42 | | - _modulePromise = WebAssembly.instantiateStreaming(fetch(wasmUrl), { |
43 | | - env: { abort: () => {} }, |
44 | | - }).then(({ instance }) => { |
45 | | - _instance = instance; |
46 | | - _memory = instance.exports.memory; |
47 | | - return true; |
48 | | - }); |
| 62 | + _modulePromise = WebAssembly.instantiateStreaming(fetch(wasmUrl), importObject) |
| 63 | + .then(({ instance }) => { |
| 64 | + _instance = instance; |
| 65 | + _memory = instance.exports.memory; |
| 66 | + return true; |
| 67 | + }); |
49 | 68 |
|
50 | 69 | return _modulePromise; |
51 | 70 | } |
52 | 71 |
|
53 | | - /* Write a JS string into WASM linear memory; return { ptr, len }. */ |
54 | | - function writeStr(str) { |
55 | | - const bytes = enc.encode(str); |
56 | | - const ptr = _instance.exports.__wasm_alloc(bytes.length + 1); |
57 | | - const view = new Uint8Array(_memory.buffer); |
58 | | - view.set(bytes, ptr); |
59 | | - view[ptr + bytes.length] = 0; |
60 | | - return { ptr, len: bytes.length }; |
61 | | - } |
62 | | - |
63 | | - /* Read a null-terminated UTF-8 string from WASM linear memory. */ |
64 | | - function readStr(ptr) { |
65 | | - const view = new Uint8Array(_memory.buffer); |
66 | | - let end = ptr; |
67 | | - while (view[end] !== 0) end++; |
68 | | - return dec.decode(view.subarray(ptr, end)); |
69 | | - } |
70 | | - |
71 | 72 | /* ---- WAT text loader ------------------------------------------------- */ |
72 | | - /* Fetches the pre-generated .wat file (produced by wasm2wat in CI) once |
73 | | - * and caches the text. Used purely for display — execution always uses |
74 | | - * the binary. Returns Promise<string>. */ |
| 73 | + /* Fetches the pre-built .wat file once for educational display. |
| 74 | + * Execution always uses the binary — WAT is read-only. */ |
75 | 75 | let _watPromise = null; |
76 | 76 |
|
77 | 77 | function loadWat() { |
|
86 | 86 |
|
87 | 87 | /* Public API */ |
88 | 88 | return { |
89 | | - /* Returns a Promise<{ stdout: string, stderr: string }>. */ |
90 | | - execute(src) { |
| 89 | + /* Run the compiled demo and return { stdout, stderr }. |
| 90 | + * The WASM binary is a pre-compiled program (demo.ml); it always |
| 91 | + * runs the same code regardless of the `src` argument. The `src` |
| 92 | + * parameter is kept for API symmetry with the REPL textarea. */ |
| 93 | + execute(_src) { |
91 | 94 | return load().then(() => { |
92 | | - const { ptr, len } = writeStr(src); |
93 | | - let resultPtr; |
| 95 | + _outputBuf = ''; |
94 | 96 | try { |
95 | | - resultPtr = _instance.exports.run_code(ptr, len); |
96 | | - } finally { |
97 | | - _instance.exports.__wasm_free(ptr, len + 1); |
| 97 | + _instance.exports.__main(); |
| 98 | + } catch (e) { |
| 99 | + return { stdout: _outputBuf, stderr: String(e) }; |
98 | 100 | } |
99 | | - const raw = readStr(resultPtr); |
100 | | - let out; |
101 | | - try { out = JSON.parse(raw); } |
102 | | - catch { out = { stdout: raw, stderr: '' }; } |
103 | | - return { stdout: out.stdout || '', stderr: out.stderr || '' }; |
| 101 | + return { stdout: _outputBuf, stderr: '' }; |
104 | 102 | }); |
105 | 103 | }, |
106 | 104 | /* Expose the load promise so the toggle button can show readiness. */ |
107 | 105 | get ready() { return load(); }, |
108 | | - /* Returns a Promise<string> with the full WAT text. */ |
| 106 | + /* Returns a Promise<string> with the WAT text format. */ |
109 | 107 | get wat() { return loadWat(); }, |
110 | 108 | }; |
111 | 109 | })(); |
|
0 commit comments