diff --git a/__wasm/js-wasm/README.md b/__wasm/js-wasm/README.md new file mode 100644 index 0000000..2a373f8 --- /dev/null +++ b/__wasm/js-wasm/README.md @@ -0,0 +1,5 @@ + + +Reference: +https://github.com/richardanaya/js-wasm + diff --git a/__wasm/js-wasm/helloworld/Cargo.lock b/__wasm/js-wasm/helloworld/Cargo.lock new file mode 100644 index 0000000..4de72af --- /dev/null +++ b/__wasm/js-wasm/helloworld/Cargo.lock @@ -0,0 +1,56 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "callback" +version = "0.5.4" +dependencies = [ + "spin", +] + +[[package]] +name = "helloworld" +version = "0.1.0" +dependencies = [ + "js", +] + +[[package]] +name = "js" +version = "0.4.3" +dependencies = [ + "callback", + "spin", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] diff --git a/__wasm/js-wasm/helloworld/Cargo.toml b/__wasm/js-wasm/helloworld/Cargo.toml new file mode 100644 index 0000000..fc46643 --- /dev/null +++ b/__wasm/js-wasm/helloworld/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "helloworld" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ['cdylib'] + +[profile.release] +lto = true + +[dependencies] +js = "0.4.3" + +[patch.crates-io] +callback = { path = "./js-wasm/crates/callback" } +js = { path = "./js-wasm/crates/js" } + diff --git a/__wasm/js-wasm/helloworld/index.html b/__wasm/js-wasm/helloworld/index.html new file mode 100644 index 0000000..c741529 --- /dev/null +++ b/__wasm/js-wasm/helloworld/index.html @@ -0,0 +1,13 @@ + + + + + + + + + Open my console. +


+
https://github.com/richardanaya/js-wasm
+ + diff --git a/__wasm/js-wasm/helloworld/js-wasm b/__wasm/js-wasm/helloworld/js-wasm new file mode 160000 index 0000000..56faaf5 --- /dev/null +++ b/__wasm/js-wasm/helloworld/js-wasm @@ -0,0 +1 @@ +Subproject commit 56faaf58b4e118f57b46ceeebcd7887a337e1db9 diff --git a/__wasm/js-wasm/helloworld/js-wasm.js b/__wasm/js-wasm/helloworld/js-wasm.js new file mode 100644 index 0000000..5ab7765 --- /dev/null +++ b/__wasm/js-wasm/helloworld/js-wasm.js @@ -0,0 +1,244 @@ +"use strict"; +class Index { + constructor(index, generation) { + this.index = index; + this.generation = generation; + } + toNum() { + return Number((BigInt(this.generation) << BigInt(32)) | BigInt(this.index)); + } + static fromNum(n) { + const i = Number(((BigInt(n) & BigInt(0xffffffff00000000)) >> BigInt(32)) & + BigInt(0xffffffff)); + const g = n & 0xffffffff; + return new Index(g, i); + } +} +class GenerationalArena { + constructor() { + this.items = []; + this.generation = 0; + this.free_list_head = undefined; + this.length = 0; + } + insert(v) { + // lets use the first free entry if we have one + if (this.free_list_head !== undefined) { + let i = this.free_list_head; + this.free_list_head = this.items[i].nextFree; + this.items[i] = { + generation: this.generation, + value: v, + }; + this.length += 1; + return new Index(i, this.generation); + } + this.items.push({ + generation: this.generation, + value: v, + }); + const idx = new Index(this.items.length - 1, this.generation); + this.length += 1; + return idx; + } + contains(idx) { + return this.get(idx) !== undefined; + } + get(i) { + let e = this.items[i.index]; + if (e && e.generation === i.generation) { + return e.value; + } + return undefined; + } + remove(idx) { + if (idx.index >= this.items.length) { + return undefined; + } + let e = this.items[idx.index]; + if (e.generation !== undefined && e.generation == idx.generation) { + this.generation += 1; + this.items[idx.index] = { + nextFree: this.free_list_head, + }; + this.free_list_head = idx.index; + this.length -= 1; + return e.value; + } + return undefined; + } + *[Symbol.iterator]() { + for (let i = 0; i < this.items.length; i++) { + const x = this.items[i]; + if (x.generation !== undefined) { + yield { index: new Index(i, x.generation), value: x.value }; + } + } + } + indices() { + return { + items: this.items, + [Symbol.iterator]: function* iter() { + for (let i = 0; i < this.items.length; i++) { + const x = this.items[i]; + if (x.generation !== undefined) { + yield new Index(i, x.generation); + } + } + }, + }; + } + values() { + return { + items: this.items, + [Symbol.iterator]: function* iter() { + for (let i = 0; i < this.items.length; i++) { + const x = this.items[i]; + if (x.generation !== undefined) { + yield x.value; + } + } + }, + }; + } +} +const JsWasm = { + createEnvironment() { + const arena = new GenerationalArena(); + arena.insert(undefined); + arena.insert(null); + arena.insert(self); + arena.insert(typeof document != "undefined" ? document : null); + arena.insert(typeof document != "undefined" ? document.body : null); + const context = { + functions: [ + function () { + debugger; + return 0; + }, + ], + objects: arena, + utf8dec: new TextDecoder("utf-8"), + utf8enc: new TextEncoder(), + utf16dec: new TextDecoder("utf-16"), + toCallbackArg: function (arg) { + if (typeof arg === "object") { + return context.storeObject(arg); + } + return arg; + }, + createCallback: function (cb) { + if (!this.module) { + throw new Error("module not set"); + } + const fnHandleCallback = this.module.instance.exports.handle_callback; + return function () { + const arg = arguments; + fnHandleCallback(cb, context.toCallbackArg(arg[0]), context.toCallbackArg(arg[1]), context.toCallbackArg(arg[2]), context.toCallbackArg(arg[3]), context.toCallbackArg(arg[4]), context.toCallbackArg(arg[5]), context.toCallbackArg(arg[6]), context.toCallbackArg(arg[7]), context.toCallbackArg(arg[8]), context.toCallbackArg(arg[9])); + }; + }, + readUtf8FromMemory: function (start, len) { + const text = this.utf8dec.decode(this.getMemory().subarray(start, start + len)); + return text; + }, + createAllocation: function (size) { + if (!this.module) { + throw new Error("module not set"); + } + const allocationId = this.module.instance.exports.create_allocation(size); + const allocationPtr = this.module.instance.exports.allocation_ptr(allocationId); + return [allocationId, allocationPtr]; + }, + writeUtf8ToMemory: function (str) { + const bytes = this.utf8enc.encode(str); + const len = bytes.length; + const [id, start] = this.createAllocation(len); + this.getMemory().set(bytes, start); + return id; + }, + readUtf16FromMemory: function (start, len) { + const text = this.utf16dec.decode(this.getMemory().subarray(start, start + len)); + return text; + }, + readUint8ArrayFromMemory(start) { + if (!this.module) { + throw new Error("module not set"); + } + const data32 = new Uint32Array(this.module.instance.exports.memory.buffer); + const ptr = data32[start / 4]; + const length = data32[ptr / 4]; + let b = this.getMemory().slice(ptr + 4, ptr + 4 + length); + return new Uint8Array(b); + }, + storeObject: function (obj) { + const index = this.objects.insert(obj); + return index.toNum(); + }, + getObject: function (handle) { + return this.objects.get(Index.fromNum(handle)); + }, + releaseObject: function (handle) { + this.objects.remove(Index.fromNum(handle)); + }, + getMemory: function () { + if (!this.module) { + throw new Error("module not set"); + } + return new Uint8Array(this.module.instance.exports.memory.buffer); + }, + }; + return [{ + abort() { + throw new Error("WebAssembly module aborted"); + }, + js_release(obj) { + context.releaseObject(obj); + }, + js_register_function(start, len, utfByteLen) { + let functionBody; + if (utfByteLen === 16) { + functionBody = context.readUtf16FromMemory(start, len); + } + else { + functionBody = context.readUtf8FromMemory(start, len); + } + let id = context.functions.length; + context.functions.push(Function(`"use strict";return(${functionBody})`)()); + return id; + }, + js_invoke_function(funcHandle, a, b, c, d, e, f, g, h, i, j) { + return context.functions[funcHandle].call(context, a, b, c, d, e, f, g, h, i, j); + }, + }, context]; + }, + async loadAndRunWasm(wasmURL) { + const [env, context] = JsWasm.createEnvironment(); + const response = await fetch(wasmURL); + const bytes = await response.arrayBuffer(); + const module = await WebAssembly.instantiate(bytes, { + env, + }); + context.module = module; + module.instance.exports.main(); + }, +}; +document.addEventListener("DOMContentLoaded", function () { + const wasmScripts = document.querySelectorAll("script[type='application/wasm']"); + for (let i = 0; i < wasmScripts.length; i++) { + const src = wasmScripts[i].src; + if (src) { + JsWasm.loadAndRunWasm(src); + } + else { + console.error("Script tag must have 'src' property."); + } + } +}); +if (window.WasmScriptComponents) { + window.WasmScriptComponents["js-wasm"] = function (e) { + return { + ...e, + ...JsWasm.createEnvironment(), + }; + }; +} diff --git a/__wasm/js-wasm/helloworld/justfile b/__wasm/js-wasm/helloworld/justfile new file mode 100644 index 0000000..7d00442 --- /dev/null +++ b/__wasm/js-wasm/helloworld/justfile @@ -0,0 +1,13 @@ +_: + @just --list + +build: + cargo build --target wasm32-unknown-unknown --release + +serve-py2: + python -m SimpleHTTPServer + +serve-py3: + python -m http.server + + diff --git a/__wasm/js-wasm/helloworld/src/lib.rs b/__wasm/js-wasm/helloworld/src/lib.rs new file mode 100644 index 0000000..804892c --- /dev/null +++ b/__wasm/js-wasm/helloworld/src/lib.rs @@ -0,0 +1,16 @@ +use js::*; + +#[no_mangle] +pub fn main() { + // let's dynamically create a javascript function we can invoke to write logs + let fn_log = js!(r##"function(strPtr, strLen) { + console.log(this.readUtf8FromMemory(strPtr, strLen)); + }"##); + // let fn_alert = js!(r##"function(strPtr, strLen) { + // alert(this.readUtf8FromMemory(strPtr, strLen)); + // }"##); + let msg = "Hello World!"; + fn_log.invoke_2(msg.as_ptr() as u32, msg.len() as u32); + // fn_alert.invoke_2(msg.as_ptr() as u32, msg.len() as u32); +} +