diff --git a/.cargo/config.toml b/.cargo/config.toml index 2279e15c..c2eb6c59 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,4 @@ [alias] xtask = "r -p xtask --" +wasm-build = "b --target wasm32-unknown-unknown --profile=small -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size,panic_immediate_abort -p" +wasm-build-debug = "b --target wasm32-unknown-unknown -p" diff --git a/.gitignore b/.gitignore index 401d09f0..cbe94b17 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.rgignore rustc-ice-* db.sqlite +/depell/src/*.wasm diff --git a/Cargo.lock b/Cargo.lock index fff9be83..8b98e79d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,6 +810,9 @@ name = "wasm-hbc" version = "0.1.0" dependencies = [ "hblang", + "hbvm", + "log", + "wasm-rt", ] [[package]] @@ -817,6 +820,14 @@ name = "wasm-hbfmt" version = "0.1.0" dependencies = [ "hblang", + "wasm-rt", +] + +[[package]] +name = "wasm-rt" +version = "0.1.0" +dependencies = [ + "log", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c02bdeab..9280b961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["profile-rustflags"] + [workspace] resolver = "2" members = [ @@ -9,11 +11,12 @@ members = [ "depell", "depell/wasm-fmt", "depell/wasm-hbc", + "depell/wasm-rt", ] [workspace.dependencies] hbbytecode = { path = "bytecode", default-features = false } -hbvm = { path = "vm" } +hbvm = { path = "vm", default-features = false } hbxrt = { path = "xrt" } hblang = { path = "lang", default-features = false } hbjit = { path = "jit" } @@ -26,9 +29,13 @@ codegen-units = 1 panic = "abort" [profile.small] +#rustflags = ["-Zfmt-debug=none", "-Zlocation-detail=none"] inherits = "release" opt-level = "z" -strip = true +strip = "debuginfo" +#strip = true lto = true codegen-units = 1 panic = "abort" + + diff --git a/depell/src/index.css b/depell/src/index.css index 80bcc1e5..a5c20bd4 100644 --- a/depell/src/index.css +++ b/depell/src/index.css @@ -54,9 +54,6 @@ form { flex-direction: column; gap: var(--small-gap); - ::placeholder { - color: var(--placeholder); - } .error { color: var(--error); diff --git a/depell/src/index.js b/depell/src/index.js index 2ac665a9..fd976835 100644 --- a/depell/src/index.js +++ b/depell/src/index.js @@ -3,19 +3,82 @@ /** @return {never} */ function never() { throw new Error() } -/**@type{WebAssembly.Instance}*/ let instance; -/**@type{Promise}*/ let instaceFuture; +/**@type{WebAssembly.Instance}*/ let hbcInstance; +/**@type{Promise}*/ let hbcInstaceFuture; +/** @param {Uint8Array} code @param {number} fuel + * @returns {Promise | string | undefined} */ +function compileCode(code, fuel) { + if (!hbcInstance) { + hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {}); + return (async () => { + hbcInstance = (await hbcInstaceFuture).instance; + return compileCodeSync(hbcInstance, code, fuel); + })(); + } else { + return compileCodeSync(hbcInstance, code, fuel); + } + +} + +/** @param {WebAssembly.Instance} instance @param {Uint8Array} code @param {number} fuel @returns {string | undefined} */ +function compileCodeSync(instance, code, fuel) { + let { + INPUT, INPUT_LEN, + LOG_MESSAGES, LOG_MESSAGES_LEN, + PANIC_MESSAGE, PANIC_MESSAGE_LEN, + memory, compile_and_run + } = instance.exports; + + if (!(true + && INPUT instanceof WebAssembly.Global + && INPUT_LEN instanceof WebAssembly.Global + && LOG_MESSAGES instanceof WebAssembly.Global + && LOG_MESSAGES_LEN instanceof WebAssembly.Global + && memory instanceof WebAssembly.Memory + && typeof compile_and_run === "function" + )) console.log(instance.exports), never(); + + const dw = new DataView(memory.buffer); + dw.setUint32(INPUT_LEN.value, code.length, true); + new Uint8Array(memory.buffer, INPUT.value).set(code); + + try { + compile_and_run(fuel); + return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN); + } catch (e) { + if (PANIC_MESSAGE instanceof WebAssembly.Global + && PANIC_MESSAGE_LEN instanceof WebAssembly.Global) { + console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN)); + } + let log = bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN); + console.error(log, e); + return undefined; + } +} + +/** @param {WebAssembly.Memory} mem + * @param {WebAssembly.Global} ptr + * @param {WebAssembly.Global} len + * @return {string} */ +function bufToString(mem, ptr, len) { + return new TextDecoder() + .decode(new Uint8Array(mem.buffer, ptr.value, + new DataView(mem.buffer).getUint32(len.value, true))); +} + +/**@type{WebAssembly.Instance}*/ let fmtInstance; +/**@type{Promise}*/ let fmtInstaceFuture; /** @param {string} code @param {"fmt" | "minify"} action * @returns {Promise | string | undefined} */ function modifyCode(code, action) { - if (!instance) { - instaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {}); + if (!fmtInstance) { + fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {}); return (async () => { - instance = (await instaceFuture).instance; - return modifyCodeSync(instance, code, action); + fmtInstance = (await fmtInstaceFuture).instance; + return modifyCodeSync(fmtInstance, code, action); })(); } else { - return modifyCodeSync(instance, code, action); + return modifyCodeSync(fmtInstance, code, action); } } @@ -100,11 +163,15 @@ function bindTextareaAutoResize(target) { for (const textarea of target.querySelectorAll("textarea")) { if (!(textarea instanceof HTMLTextAreaElement)) never(); - textarea.style.height = textarea.scrollHeight + "px"; + const taCssMap = window.getComputedStyle(textarea); + const padding = parseInt(taCssMap.getPropertyValue('padding-top') ?? "0") + + parseInt(taCssMap.getPropertyValue('padding-top') ?? "0"); + textarea.style.height = "auto"; + textarea.style.height = (textarea.scrollHeight - padding) + "px"; textarea.style.overflowY = "hidden"; textarea.addEventListener("input", function() { textarea.style.height = "auto"; - textarea.style.height = textarea.scrollHeight + "px"; + textarea.style.height = (textarea.scrollHeight - padding) + "px"; }); textarea.onkeydown = (ev) => { @@ -112,10 +179,7 @@ function bindTextareaAutoResize(target) { if (ev.key === "Tab") { ev.preventDefault(); - const prevPos = textarea.selectionStart; - textarea.value = textarea.value.slice(0, textarea.selectionStart) + - ' ' + textarea.value.slice(textarea.selectionEnd); - textarea.selectionStart = textarea.selectionEnd = prevPos + 4; + document.execCommand('insertText', false, " "); } if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) { @@ -125,10 +189,8 @@ function bindTextareaAutoResize(target) { ev.preventDefault(); let toDelete = (textarea.selectionStart - (i + 1)) % 4; if (toDelete === 0) toDelete = 4; - const prevPos = textarea.selectionStart; - textarea.value = textarea.value.slice(0, textarea.selectionStart - toDelete) + - textarea.value.slice(textarea.selectionEnd); - textarea.selectionStart = textarea.selectionEnd = prevPos - toDelete; + textarea.selectionStart -= toDelete; + document.execCommand('delete', false); } } } @@ -165,11 +227,27 @@ if (window.location.hostname === 'localhost') { if (id !== new_id) window.location.reload(); }, 300); - (async function testCodeChange() { - const code = "main:=fn():void{return}"; - const fmtd = await modifyCode(code, "fmt") ?? never(); - const prev = await modifyCode(fmtd, "minify") ?? never(); - if (code != prev) console.error(code, prev); + (async function test() { + { + const code = "main:=fn():void{return}"; + const fmtd = await modifyCode(code, "fmt") ?? never(); + const prev = await modifyCode(fmtd, "minify") ?? never(); + if (code != prev) console.error(code, prev); + } + { + + const name = "foo.hb"; + const code = "main:=fn():void{return 42}"; + const buf = new Uint8Array(2 + name.length + 2 + code.length); + const view = new DataView(buf.buffer); + view.setUint16(0, name.length, true); + buf.set(new TextEncoder().encode(name), 2); + view.setUint16(2 + name.length, code.length, true); + buf.set(new TextEncoder().encode(code), 2 + name.length + 2); + const res = await compileCode(buf, 1) ?? never(); + const expected = ""; + if (expected != res) console.error(expected, res); + } })() } diff --git a/depell/src/main.rs b/depell/src/main.rs index 6592caac..2527438e 100644 --- a/depell/src/main.rs +++ b/depell/src/main.rs @@ -40,6 +40,8 @@ async fn amain() { let router = axum::Router::new() .route("/", get(Index::page)) + .route("/index.css", static_asset!("text/css", "index.css")) + .route("/index.js", static_asset!("text/javascript", "index.js")) .route( "/hbfmt.wasm", static_asset!( @@ -47,6 +49,13 @@ async fn amain() { "../../target/wasm32-unknown-unknown/small/wasm_hbfmt.wasm" ), ) + .route( + "/hbc.wasm", + static_asset!( + "application/wasm", + "../../target/wasm32-unknown-unknown/small/wasm_hbc.wasm" + ), + ) .route("/index-view", get(Index::get)) .route("/feed", get(Index::page)) .route("/profile", get(Profile::page)) @@ -377,7 +386,7 @@ async fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html "" - +