//! Generate JavaScript and HTML loaders for running exported WASM in the browser. use crate::export::ExportMetadata; /// Generate a JavaScript loader that instantiates a WAFER `.wasm` module. /// /// The loader provides the six required imports (emit, memory, dsp, rsp, fsp, /// table) and host-function stubs, then calls `_start`. pub fn generate_js_loader(wasm_filename: &str, metadata: &ExportMetadata) -> String { let (dsp, rsp, fsp) = (metadata.dsp_init, metadata.rsp_init, metadata.fsp_init); let memory_pages = metadata.memory_size.div_ceil(65536).max(16); // Build the host function registration code. let mut host_registrations = String::new(); for (idx, name) in &metadata.host_functions { let js_impl = js_host_function(name); host_registrations.push_str(&format!(" table.set({idx}, {js_impl});\n")); } format!( r#"// WAFER JS Loader - generated by wafer build --js // Loads and runs {wasm_filename} in the browser. const WAFER = (() => {{ const CELL_SIZE = 4; const DATA_STACK_TOP = 0x1540; const SYSVAR_BASE = 0x0004; let outputCallback = (s) => {{ const el = document.getElementById('output'); if (el) el.textContent += s; else console.log(s); }}; async function run(opts) {{ if (opts && opts.output) outputCallback = opts.output; const memory = new WebAssembly.Memory({{ initial: {memory_pages} }}); const dsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {dsp}); const rsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {rsp}); const fsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {fsp}); const table = new WebAssembly.Table({{ element: 'anyfunc', initial: 256 }}); function emit(code) {{ outputCallback(String.fromCharCode(code)); }} const importObject = {{ env: {{ emit, memory, dsp, rsp, fsp, table }} }}; // Register host functions const view = () => new DataView(memory.buffer); const pop = () => {{ const sp = dsp.value; const v = view().getInt32(sp, true); dsp.value = sp + CELL_SIZE; return v; }}; const push = (v) => {{ const sp = dsp.value - CELL_SIZE; view().setInt32(sp, v, true); dsp.value = sp; }}; {host_registrations} const response = await fetch('{wasm_filename}'); const bytes = await response.arrayBuffer(); const {{ instance }} = await WebAssembly.instantiate(bytes, importObject); if (instance.exports._start) {{ instance.exports._start(); }} return instance; }} return {{ run }}; }})(); "# ) } /// Generate a minimal HTML page that loads the JS loader. pub fn generate_html_page(wasm_filename: &str, js_filename: &str) -> String { format!( r#"