This commit is contained in:
Jakub Doka 2024-10-12 21:25:37 +02:00
parent 5364b66629
commit 69b58c2b36
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
10 changed files with 137 additions and 82 deletions

10
.gitignore vendored
View file

@ -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

16
Cargo.lock generated
View file

@ -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"

View file

@ -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 = []

10
depell/README.md Normal file
View file

@ -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
```

View file

@ -70,6 +70,7 @@ textarea {
padding-top: calc(var(--small-gap) * 1.5);
font-family: var(--monospace);
resize: none;
tab-size: 4;
}
pre {

View file

@ -5,28 +5,21 @@ function never() { throw new Error() }
/**@type{WebAssembly.Instance}*/ let hbcInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let hbcInstaceFuture;
/** @param {Uint8Array} code @param {number} fuel
* @returns {Promise<string | undefined> | 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<WebAssembly.WebAssemblyInstantiatedSource>}*/ let fmtInstaceFuture;
/** @param {string} code @param {"fmt" | "minify"} action
* @returns {Promise<string | undefined> | 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<string | undefined>} */
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> | 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);
}
})()

View file

@ -153,8 +153,9 @@ impl Page for Post {
<input name="author" type="text" value={session.name} hidden>
<input name="name" type="text" placeholder="name" value=name
required maxlength=MAX_POSTNAME_LENGTH>
<textarea name="code" placeholder="code" rows=1 required>code</textarea>
<textarea id="code-edit" name="code" placeholder="code" rows=1 required>code</textarea>
<input type="submit" value="submit">
<pre id="compiler-output"></pre>
</form>
!{include_str!("post-page.html")}
}

View file

@ -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}");

View file

@ -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()
}
};

View file

@ -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(()),