diff --git a/.gitignore b/.gitignore index cc0d2274..af441a42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ +# garbage /target -/bytecode/src/instrs.rs -/.rgignore rustc-ice-* + +# sqlite db.sqlite +db.sqlite-journal + +# assets /depell/src/*.gz +/depell/src/*.wasm +/bytecode/src/instrs.rs diff --git a/Cargo.lock b/Cargo.lock index 14c0d16a..d3faf192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,15 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -417,6 +426,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -649,6 +659,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "smallvec" version = "1.13.2" diff --git a/depell/Cargo.toml b/depell/Cargo.toml index cc377b00..6fd1ad9c 100644 --- a/depell/Cargo.toml +++ b/depell/Cargo.toml @@ -8,10 +8,7 @@ axum = "0.7.7" getrandom = "0.2.15" htmlm = "0.5.0" log = "0.4.22" -rusqlite = "0.32.1" +rusqlite = { version = "0.32.1", features = ["bundled"] } serde = { version = "1.0.210", features = ["derive"] } time = "0.3.36" tokio = { version = "1.40.0", features = ["rt"] } - -[features] -gzip = [] diff --git a/depell/README.md b/depell/README.md new file mode 100644 index 00000000..a5ccaa7c --- /dev/null +++ b/depell/README.md @@ -0,0 +1,10 @@ +# Depell + +Depell is a website that allows users to import/post/run hblang code and create huge dependency graphs + +## Local Development + +```bash +cargo xtask watch-depell +# browser http://localhost:8080 +``` diff --git a/depell/src/index.css b/depell/src/index.css index a5c20bd4..94a75211 100644 --- a/depell/src/index.css +++ b/depell/src/index.css @@ -70,6 +70,7 @@ textarea { padding-top: calc(var(--small-gap) * 1.5); font-family: var(--monospace); resize: none; + tab-size: 4; } pre { diff --git a/depell/src/index.js b/depell/src/index.js index fd976835..431de932 100644 --- a/depell/src/index.js +++ b/depell/src/index.js @@ -5,28 +5,21 @@ function never() { throw new Error() } /**@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); - } - +async function getHbcInstance() { + hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {}); + return hbcInstance ??= (await hbcInstaceFuture).instance; } -/** @param {WebAssembly.Instance} instance @param {Uint8Array} code @param {number} fuel @returns {string | undefined} */ -function compileCodeSync(instance, code, fuel) { +const stack_pointer_offset = 1 << 20; + +/** @param {WebAssembly.Instance} instance @param {Uint8Array} code @param {number} fuel + * @returns {string} */ +function compileCode(instance, code, fuel) { let { INPUT, INPUT_LEN, LOG_MESSAGES, LOG_MESSAGES_LEN, PANIC_MESSAGE, PANIC_MESSAGE_LEN, - memory, compile_and_run + memory, compile_and_run, } = instance.exports; if (!(true @@ -48,48 +41,57 @@ function compileCodeSync(instance, code, fuel) { } catch (e) { if (PANIC_MESSAGE instanceof WebAssembly.Global && PANIC_MESSAGE_LEN instanceof WebAssembly.Global) { - console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN)); + console.error(e, bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN)); } - let log = bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN); - console.error(log, e); - return undefined; + return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN); } } +/** @typedef {Object} Post + * @property {string} path + * @property {string} code */ + +/** @param {Post[]} posts @returns {Uint8Array} */ +function packPosts(posts) { + let len = 0; for (const post of posts) len += 2 + post.path.length + 2 + post.code.length; + + const buf = new Uint8Array(len), view = new DataView(buf.buffer), enc = new TextEncoder(); + len = 0; for (const post of posts) { + view.setUint16(len, post.path.length, true); len += 2; + buf.set(enc.encode(post.path), len); len += post.path.length; + view.setUint16(len, post.code.length, true); len += 2; + buf.set(enc.encode(post.code), len); len += post.code.length; + } + + return buf; +} + /** @param {WebAssembly.Memory} mem * @param {WebAssembly.Global} ptr * @param {WebAssembly.Global} len * @return {string} */ function bufToString(mem, ptr, len) { - return new TextDecoder() + const res = new TextDecoder() .decode(new Uint8Array(mem.buffer, ptr.value, new DataView(mem.buffer).getUint32(len.value, true))); + new DataView(mem.buffer).setUint32(len.value, 0, true); + return res; } /**@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 (!fmtInstance) { - fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {}); - return (async () => { - fmtInstance = (await fmtInstaceFuture).instance; - return modifyCodeSync(fmtInstance, code, action); - })(); - } else { - return modifyCodeSync(fmtInstance, code, action); - } -} + * @returns {Promise} */ +async function modifyCode(code, action) { + fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {}); + fmtInstance ??= (await fmtInstaceFuture).instance; -/** @param {WebAssembly.Instance} instance @param {string} code @param {"fmt" | "minify"} action @returns {string | undefined} */ -function modifyCodeSync(instance, code, action) { let { INPUT, INPUT_LEN, OUTPUT, OUTPUT_LEN, PANIC_MESSAGE, PANIC_MESSAGE_LEN, memory, fmt, minify - } = instance.exports; + } = fmtInstance.exports; if (!(true && INPUT instanceof WebAssembly.Global @@ -136,17 +138,36 @@ function wireUp(target) { execApply(target); cacheInputs(target); bindTextareaAutoResize(target); + bindCodeEdit(target); } /** @type {{ [key: string]: (content: string) => Promise | string }} */ const applyFns = { timestamp: (content) => new Date(parseInt(content) * 1000).toLocaleString(), - fmt: (content) => { - let res = modifyCode(content, "fmt"); - return res instanceof Promise ? res.then(c => c ?? content) : res ?? content; - }, + fmt: (content) => modifyCode(content, "fmt").then(c => c ?? "post has invalid code"), }; +/** @param {HTMLElement} target */ +async function bindCodeEdit(target) { + const edit = target.querySelector("#code-edit"); + if (!(edit instanceof HTMLTextAreaElement)) return; + const errors = target.querySelector("#compiler-output"); + if (!(errors instanceof HTMLPreElement)) never(); + + const hbc = await getHbcInstance(); + + const debounce = 0; + let timeout = 0; + edit.addEventListener("input", () => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + const buf = packPosts([{ path: "local.hb", code: edit.value }]); + errors.textContent = compileCode(hbc, buf, 1); + timeout = 0; + }, debounce); + }); +} + /** @param {HTMLElement} target */ function execApply(target) { for (const elem of target.querySelectorAll('[apply]')) { @@ -175,23 +196,9 @@ function bindTextareaAutoResize(target) { }); textarea.onkeydown = (ev) => { - const selecting = textarea.selectionStart !== textarea.selectionEnd; - if (ev.key === "Tab") { ev.preventDefault(); - document.execCommand('insertText', false, " "); - } - - if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) { - let i = textarea.selectionStart, looped = false; - while (textarea.value.charCodeAt(--i) === ' '.charCodeAt(0)) looped = true; - if (textarea.value.charCodeAt(i) === '\n'.charCodeAt(0) && looped) { - ev.preventDefault(); - let toDelete = (textarea.selectionStart - (i + 1)) % 4; - if (toDelete === 0) toDelete = 4; - textarea.selectionStart -= toDelete; - document.execCommand('delete', false); - } + document.execCommand('insertText', false, "\t"); } } } @@ -235,17 +242,13 @@ if (window.location.hostname === 'localhost') { 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 = ""; + const posts = [{ + path: "foo.hb", + code: "main:=fn():int{return 42}", + }]; + const buf = packPosts(posts); + const res = compileCode(await getHbcInstance(), buf, 1) ?? never(); + const expected = "exit code: 42\n"; if (expected != res) console.error(expected, res); } })() diff --git a/depell/src/main.rs b/depell/src/main.rs index 1c5b1989..3d085592 100644 --- a/depell/src/main.rs +++ b/depell/src/main.rs @@ -153,8 +153,9 @@ impl Page for Post { - + +

             
             !{include_str!("post-page.html")}
         }
diff --git a/depell/wasm-hbc/src/lib.rs b/depell/wasm-hbc/src/lib.rs
index aa932bbe..98efa5a6 100644
--- a/depell/wasm-hbc/src/lib.rs
+++ b/depell/wasm-hbc/src/lib.rs
@@ -18,7 +18,7 @@ wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
 unsafe fn compile_and_run(mut fuel: usize) {
     ALLOCATOR.reset();
 
-    log::set_logger(&wasm_rt::Logger).unwrap();
+    _ = log::set_logger(&wasm_rt::Logger);
     log::set_max_level(log::LevelFilter::Error);
 
     struct File<'a> {
@@ -81,7 +81,12 @@ unsafe fn compile_and_run(mut fuel: usize) {
                 let unknown = ct.vm.read_reg(2).0;
                 log::error!("unknown ecall: {unknown}")
             }
-            Ok(hbvm::VmRunOk::Timer) => fuel -= 1,
+            Ok(hbvm::VmRunOk::Timer) => {
+                fuel -= 1;
+                if fuel == 0 {
+                    log::error!("program timed out");
+                }
+            }
             Ok(hbvm::VmRunOk::Breakpoint) => todo!(),
             Err(e) => {
                 log::error!("vm error: {e}");
diff --git a/depell/wasm-rt/src/lib.rs b/depell/wasm-rt/src/lib.rs
index b698e73e..c4afaa64 100644
--- a/depell/wasm-rt/src/lib.rs
+++ b/depell/wasm-rt/src/lib.rs
@@ -25,16 +25,18 @@ macro_rules! decl_buffer {
 #[macro_export]
 macro_rules! decl_runtime {
     ($memory_size:expr, $max_panic_size:expr) => {
+        #[cfg(debug_assertions)]
+        #[no_mangle]
+        static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
+        #[cfg(debug_assertions)]
+        #[no_mangle]
+        static mut PANIC_MESSAGE_LEN: usize = 0;
+
         #[cfg(target_arch = "wasm32")]
         #[panic_handler]
         pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
             #[cfg(debug_assertions)]
             {
-                #[no_mangle]
-                static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
-                #[no_mangle]
-                static mut PANIC_MESSAGE_LEN: usize = 0;
-
                 unsafe {
                     use core::fmt::Write;
                     let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
@@ -52,6 +54,16 @@ macro_rules! decl_runtime {
         #[cfg(target_arch = "wasm32")]
         #[alloc_error_handler]
         fn alloc_error(_: core::alloc::Layout) -> ! {
+            #[cfg(debug_assertions)]
+            {
+                unsafe {
+                    use core::fmt::Write;
+                    let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
+                    _ = writeln!(f, "out of memory");
+                    PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
+                }
+            }
+
             core::arch::wasm32::unreachable()
         }
     };
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index ea925f46..7c683d71 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -52,7 +52,6 @@ fn main() -> io::Result<()> {
             build_wasm_blob("hbc", true)?;
             exec(build_cmd("gzip -k -f depell/src/index.js"))?;
             exec(build_cmd("gzip -k -f depell/src/index.css"))?;
-            exec(build_cmd("cargo run -p depell --features gzip"))?;
             Ok(())
         }
         "build-depell" => {
@@ -60,7 +59,12 @@ fn main() -> io::Result<()> {
             build_wasm_blob("hbc", false)?;
             exec(build_cmd("gzip -k -f depell/src/index.js"))?;
             exec(build_cmd("gzip -k -f depell/src/index.css"))?;
-            exec(build_cmd("cargo run -p depell --features gzip --release"))?;
+            Ok(())
+        }
+        "watch-depell" => {
+            let mut c = build_cmd("cargo watch --why");
+            c.arg("--exec=xtask build-depell-debug").arg("--exec=run -p depell");
+            exec(c)?;
             Ok(())
         }
         _ => Ok(()),