From c41ac8efaddf34c6e6ba084624f4ab9bcc81ebd5 Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sat, 9 May 2026 12:10:02 -0700 Subject: [PATCH 01/23] Add browser WASM transport --- README.md | 4 + bindings/node/.prettierignore | 1 + bindings/node/README.md | 33 + bindings/node/eslint.config.mjs | 2 +- bindings/node/package.json | 7 +- bindings/node/src/ts/browser.ts | 195 +++ bindings/node/src/ts/wasm/tachyon_ipc.d.ts | 67 + bindings/node/src/ts/wasm/tachyon_ipc.js | 9 + bindings/node/src/ts/wasm/tachyon_ipc_bg.js | 319 +++++ bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm | Bin 0 -> 23253 bytes .../node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts | 32 + bindings/node/tsconfig.json | 1 + bindings/rust/tachyon/Cargo.toml | 10 +- bindings/rust/tachyon/src/error.rs | 2 + bindings/rust/tachyon/src/lib.rs | 10 + bindings/rust/tachyon/src/wasm.rs | 400 ++++++ docs/README.md | 2 + examples/README.md | 2 + examples/browser_wasm/README.md | 34 + examples/browser_wasm/index.html | 61 + examples/browser_wasm/main.js | 173 +++ examples/browser_wasm/package-lock.json | 1204 +++++++++++++++++ examples/browser_wasm/package.json | 15 + examples/browser_wasm/style.css | 169 +++ 24 files changed, 2748 insertions(+), 4 deletions(-) create mode 100644 bindings/node/.prettierignore create mode 100644 bindings/node/src/ts/browser.ts create mode 100644 bindings/node/src/ts/wasm/tachyon_ipc.d.ts create mode 100644 bindings/node/src/ts/wasm/tachyon_ipc.js create mode 100644 bindings/node/src/ts/wasm/tachyon_ipc_bg.js create mode 100644 bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm create mode 100644 bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts create mode 100644 bindings/rust/tachyon/src/wasm.rs create mode 100644 examples/browser_wasm/README.md create mode 100644 examples/browser_wasm/index.html create mode 100644 examples/browser_wasm/main.js create mode 100644 examples/browser_wasm/package-lock.json create mode 100644 examples/browser_wasm/package.json create mode 100644 examples/browser_wasm/style.css diff --git a/README.md b/README.md index fa7acb7..2e66e4d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ pip install tachyon-ipc npm install @tachyon-ipc/core ``` +The same npm package also includes a browser WASM build for bundlers. Browser code keeps the same +`import { Bus } from '@tachyon-ipc/core'` shape; bundlers that honor the package `browser` field resolve to the +page-local WASM transport automatically. + **Java (Maven):** ```xml diff --git a/bindings/node/.prettierignore b/bindings/node/.prettierignore new file mode 100644 index 0000000..81c70de --- /dev/null +++ b/bindings/node/.prettierignore @@ -0,0 +1 @@ +src/ts/wasm/ diff --git a/bindings/node/README.md b/bindings/node/README.md index 0fa552f..a25c23b 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -13,6 +13,7 @@ compiled from source at installation time via `cmake-js`. - [Requirements](#requirements) - [Install](#install) - [Quickstart](#quickstart) +- [Browser WASM](#browser-wasm) - [API](#api) - [Zero-copy pattern](#zero-copy-pattern) - [Batch pattern](#batch-pattern) @@ -48,6 +49,9 @@ Clang 17+ must be available on the build machine. The package ships as **ESM** (`"type": "module"`). CommonJS consumers must use dynamic `import()`. +Browser bundlers that honor the package `browser` field resolve `@tachyon-ipc/core` to the WASM browser build. Node.js +continues to resolve the native N-API entrypoint through the existing `main` and `types` fields. + ## Quickstart The consumer must start first, it owns the UNIX socket and the SHM arena. @@ -72,6 +76,35 @@ bus.send(Buffer.from('hello tachyon'), 1); `Bus` implements `Disposable`. The `using` keyword (TypeScript 5.2+, ES2023 Explicit Resource Management) guarantees `close()` is called on scope exit regardless of exceptions. +## Browser WASM + +The browser build is shipped from the same npm package and keeps the same import and constructor shape: + +```typescript +import {Bus} from '@tachyon-ipc/core'; + +using consumer = Bus.listen('/page/demo', 1 << 20); +using producer = Bus.connect('/page/demo'); + +producer.send(new Uint8Array([1, 2, 3, 4]), 7); +const {data, typeId} = consumer.recv(); +``` + +Browsers do not expose POSIX shared memory or UNIX sockets, so `socketPath` is a page-local endpoint key rather than a +filesystem socket. `listen()` creates the in-page WASM ring and `connect()` attaches to that ring. The message layout +still uses Tachyon's 64-byte header, `type_id`, alignment, and skip-marker rules. + +The browser implementation is intentionally direct-doorbell oriented. After JavaScript commits a message, call the Rust +WASM work function immediately instead of scheduling a browser event or spinning in a poll loop. This avoids event-loop +latency and keeps sub-microsecond round trips possible for in-page JS/Rust communication. + +Browser differences: + +- `recv()` and `acquireRx()` are non-blocking because the main browser thread cannot park like a native futex wait. +- `setNumaNode()` and `setPollingMode()` are no-ops in browsers. +- `Buffer` is not a browser primitive; returned data is a `Uint8Array`. +- Native cross-process IPC still requires Node.js or another native binding. + ## API ### Lifecycle diff --git a/bindings/node/eslint.config.mjs b/bindings/node/eslint.config.mjs index 2831b2d..6ec10af 100644 --- a/bindings/node/eslint.config.mjs +++ b/bindings/node/eslint.config.mjs @@ -2,7 +2,7 @@ import tseslint from 'typescript-eslint'; export default tseslint.config( { - ignores: ['dist/**', 'build/**', 'node_modules/**', 'test/**'], + ignores: ['dist/**', 'build/**', 'node_modules/**', 'test/**', 'src/ts/wasm/**'], }, ...tseslint.configs.strictTypeChecked, ...tseslint.configs.stylisticTypeChecked, diff --git a/bindings/node/package.json b/bindings/node/package.json index d5a3f6d..5a14512 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -17,13 +17,18 @@ "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", + "browser": { + "./dist/index.js": "./dist/browser.js" + }, "engines": { "node": ">=20.0.0" }, "scripts": { - "build": "npm run build:native && npm run build:ts", + "build": "npm run build:native && npm run build:ts && npm run copy:wasm", "build:native": "cmake-js compile", + "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target bundler --release --out-dir ../../node/src/ts/wasm", "build:ts": "tsc", + "copy:wasm": "mkdir -p dist/wasm && cp src/ts/wasm/tachyon_ipc.js src/ts/wasm/tachyon_ipc_bg.js src/ts/wasm/tachyon_ipc_bg.wasm src/ts/wasm/*.d.ts dist/wasm/", "clean": "rm -rf build dist", "format": "prettier --write src/ts test/", "format:check": "prettier --check src/ts test/", diff --git a/bindings/node/src/ts/browser.ts b/bindings/node/src/ts/browser.ts new file mode 100644 index 0000000..6627900 --- /dev/null +++ b/bindings/node/src/ts/browser.ts @@ -0,0 +1,195 @@ +import { RxBatch, type RxMessage } from './batch.ts'; +import { PeerDeadError } from './error.ts'; +import type { BatchController } from './batch.ts'; +import type { RxController, TxController } from './guards.ts'; +import { RxGuard, TxGuard } from './guards.ts'; +import { makeTypeId, msgType, routeId, tachyon_browser_echo_once, WasmBus } from './wasm/tachyon_ipc.js'; +// @ts-expect-error Bundlers load the generated wasm module through this import. +import * as wasmRaw from './wasm/tachyon_ipc_bg.wasm'; + +const wasmMemory = (wasmRaw as unknown as { readonly memory: { readonly buffer: ArrayBuffer } }).memory; + +interface BrowserEndpoint { + handle: WasmBus; + refs: number; +} + +const endpoints = new Map(); + +function slot(ptr: number, len: number): Uint8Array { + return new Uint8Array(wasmMemory.buffer, ptr, len); +} + +/** + * Browser implementation of the Tachyon SPSC bus. + * + * Bundlers resolve `@tachyon-ipc/core` to this entry through the package + * `browser` export condition. The constructor shape matches Node: + * `Bus.listen(path, capacity)` creates a page-local ring and + * `Bus.connect(path)` attaches to it. + */ +export class Bus implements Disposable { + #endpoint: BrowserEndpoint; + #path: string; + #closed = false; + + private constructor(path: string, endpoint: BrowserEndpoint) { + this.#path = path; + this.#endpoint = endpoint; + } + + public static listen(socketPath: string, capacity: number): Bus { + if (endpoints.has(socketPath)) { + throw new Error(`Bus.listen: browser endpoint already exists for ${socketPath}`); + } + + const endpoint = { handle: new WasmBus(capacity), refs: 1 }; + endpoints.set(socketPath, endpoint); + return new Bus(socketPath, endpoint); + } + + public static connect(socketPath: string): Bus { + const endpoint = endpoints.get(socketPath); + if (endpoint === undefined) { + throw new Error(`Bus.connect: no browser endpoint is listening at ${socketPath}`); + } + + endpoint.refs += 1; + return new Bus(socketPath, endpoint); + } + + public setPollingMode(_spinMode: 0 | 1): void { + this.#assertOpen(); + } + + public setNumaNode(_nodeId: number): void { + this.#assertOpen(); + } + + public flush(): void { + this.#assertOpen(); + this.#endpoint.handle.flush(); + } + + public send(data: Uint8Array, typeId = 0): void { + this.#assertOpen(); + this.#endpoint.handle.send(data, typeId); + } + + public sendU32(value: number, typeId = 0): void { + this.#assertOpen(); + this.#endpoint.handle.sendU32(value, typeId); + } + + public recv(): { data: Uint8Array; typeId: number } { + this.#assertOpen(); + const guard = this.acquireRx(); + if (guard === null) { + throw new Error('Bus.recv: no browser message is available. Use a direct doorbell after send().'); + } + + const data = new Uint8Array(guard.data()); + const typeId = guard.typeId; + guard.commit(); + return { data, typeId }; + } + + public recvU32(): number { + this.#assertOpen(); + return this.#endpoint.handle.recvU32(); + } + + public acquireTx(maxSize: number): TxGuard { + this.#assertOpen(); + const ptr = this.#endpoint.handle.acquireTx(maxSize); + const ctrl: TxController = { + commitTx: (s, t) => { + this.#endpoint.handle.commitTx(s, t); + }, + commitTxUnflushed: (s, t) => { + this.#endpoint.handle.commitTxUnflushed(s, t); + }, + rollbackTx: () => { + this.#endpoint.handle.rollbackTx(); + }, + }; + return new TxGuard(ctrl, slot(ptr, maxSize) as unknown as Buffer); + } + + public acquireRx(_spinThreshold = 0): RxGuard | null { + this.#assertOpen(); + if (this.#endpoint.handle.isFatal()) throw new PeerDeadError(); + if (!this.#endpoint.handle.acquireRx()) return null; + + const ptr = this.#endpoint.handle.rxPtr(); + const actualSize = this.#endpoint.handle.rxSize(); + const typeId = this.#endpoint.handle.rxTypeId(); + const ctrl: RxController = { + commitRx: () => { + this.#endpoint.handle.commitRx(); + }, + getState: () => (this.#endpoint.handle.isFatal() ? 4 : 2), + }; + return new RxGuard(ctrl, slot(ptr, actualSize) as unknown as Buffer, typeId, actualSize); + } + + public drainBatch(maxMsgs: number, _spinThreshold = 0): RxBatch { + this.#assertOpen(); + if (this.#endpoint.handle.isFatal()) throw new PeerDeadError(); + + const messages: RxMessage[] = []; + for (let i = 0; i < maxMsgs; i += 1) { + const guard = this.acquireRx(); + if (guard === null) break; + messages.push({ + data: new Uint8Array(guard.data()) as unknown as RxMessage['data'], + typeId: guard.typeId, + size: guard.actualSize, + }); + guard.commit(); + } + + const ctrl: BatchController = { + commitBatch: () => { + // Browser batches are copied and committed while draining. + }, + getState: () => (this.#endpoint.handle.isFatal() ? 4 : 2), + }; + return new RxBatch(ctrl, messages); + } + + public close(): void { + if (this.#closed) return; + this.#closed = true; + this.#endpoint.refs -= 1; + if (this.#endpoint.refs <= 0) { + endpoints.delete(this.#path); + this.#endpoint.handle.free(); + } + } + + public [Symbol.dispose](): void { + this.close(); + } + + #assertOpen(): void { + if (this.#closed) throw new Error('Bus: this bus has been closed.'); + } +} + +export { + TachyonError, + AbiMismatchError, + PeerDeadError, + ErrorCode, + isAbiMismatch, + isFull, + isTachyonError, + isPeerDead, +} from './error.ts'; +export type { ErrorCode as ErrorCodeType } from './error.ts'; +export { RxBatch } from './batch.ts'; +export type { RxMessage } from './batch.ts'; +export { TxGuard, RxGuard } from './guards.ts'; +export type { TxSlot, RxSlot } from './guards.ts'; +export { makeTypeId, msgType, routeId, tachyon_browser_echo_once, WasmBus }; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts new file mode 100644 index 0000000..85ab28d --- /dev/null +++ b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts @@ -0,0 +1,67 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. + * + * This keeps the Tachyon message wire shape (`size`, `type_id`, + * `reserved_size`, 64-byte alignment, and skip marker) while replacing the + * native POSIX shared-memory transport with a WASM memory arena that page + * JavaScript can access through `WebAssembly.Memory`. + */ +export class WasmBus { + free(): void; + [Symbol.dispose](): void; + /** + * Acquire the next RX message, if one is visible. + * + * When this returns `true`, read `rxPtr`, `rxSize`, and `rxTypeId`, then + * call `commitRx` when done. + */ + acquireRx(): boolean; + /** + * Reserve a TX slot and return a pointer to its payload bytes in WASM memory. + * + * JavaScript can create a zero-copy view with: + * `new Uint8Array(wasm.memory.buffer, ptr, maxSize)`. + */ + acquireTx(max_size: number): number; + availableBytes(): number; + capacity(): number; + commitRx(): void; + commitTx(actual_size: number, type_id: number): void; + commitTxUnflushed(actual_size: number, type_id: number): void; + dataPtr(): number; + /** + * Publish pending unflushed TX messages. + */ + flush(): void; + freeBytes(): number; + isFatal(): boolean; + constructor(capacity: number); + recvU32(): number; + rollbackTx(): void; + rxPtr(): number; + rxSize(): number; + rxTypeId(): number; + /** + * Copy-based send. Prefer `acquireTx` + direct JS view writes for hot paths. + */ + send(data: Uint8Array, type_id: number): void; + sendU32(value: number, type_id: number): void; + sendU32Unflushed(value: number, type_id: number): void; +} + +export function makeTypeId(route: number, ty: number): number; + +export function msgType(type_id: number): number; + +export function routeId(type_id: number): number; + +/** + * Tiny Rust-side browser program used by the example page. + * + * It polls `inbound`, increments a little-endian `u32` payload, and publishes + * the result to `outbound`. Non-`u32` payloads are echoed unchanged. + */ +export function tachyon_browser_echo_once(inbound: WasmBus, outbound: WasmBus): boolean; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.js b/bindings/node/src/ts/wasm/tachyon_ipc.js new file mode 100644 index 0000000..e066003 --- /dev/null +++ b/bindings/node/src/ts/wasm/tachyon_ipc.js @@ -0,0 +1,9 @@ +/* @ts-self-types="./tachyon_ipc.d.ts" */ +import * as wasm from "./tachyon_ipc_bg.wasm"; +import { __wbg_set_wasm } from "./tachyon_ipc_bg.js"; + +__wbg_set_wasm(wasm); +wasm.__wbindgen_start(); +export { + WasmBus, makeTypeId, msgType, routeId, tachyon_browser_echo_once +} from "./tachyon_ipc_bg.js"; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js new file mode 100644 index 0000000..0375a26 --- /dev/null +++ b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js @@ -0,0 +1,319 @@ +/** + * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. + * + * This keeps the Tachyon message wire shape (`size`, `type_id`, + * `reserved_size`, 64-byte alignment, and skip marker) while replacing the + * native POSIX shared-memory transport with a WASM memory arena that page + * JavaScript can access through `WebAssembly.Memory`. + */ +export class WasmBus { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmBusFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasmbus_free(ptr, 0); + } + /** + * Acquire the next RX message, if one is visible. + * + * When this returns `true`, read `rxPtr`, `rxSize`, and `rxTypeId`, then + * call `commitRx` when done. + * @returns {boolean} + */ + acquireRx() { + const ret = wasm.wasmbus_acquireRx(this.__wbg_ptr); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] !== 0; + } + /** + * Reserve a TX slot and return a pointer to its payload bytes in WASM memory. + * + * JavaScript can create a zero-copy view with: + * `new Uint8Array(wasm.memory.buffer, ptr, maxSize)`. + * @param {number} max_size + * @returns {number} + */ + acquireTx(max_size) { + const ret = wasm.wasmbus_acquireTx(this.__wbg_ptr, max_size); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] >>> 0; + } + /** + * @returns {number} + */ + availableBytes() { + const ret = wasm.wasmbus_availableBytes(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + capacity() { + const ret = wasm.wasmbus_capacity(this.__wbg_ptr); + return ret >>> 0; + } + commitRx() { + const ret = wasm.wasmbus_commitRx(this.__wbg_ptr); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} actual_size + * @param {number} type_id + */ + commitTx(actual_size, type_id) { + const ret = wasm.wasmbus_commitTx(this.__wbg_ptr, actual_size, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} actual_size + * @param {number} type_id + */ + commitTxUnflushed(actual_size, type_id) { + const ret = wasm.wasmbus_commitTxUnflushed(this.__wbg_ptr, actual_size, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @returns {number} + */ + dataPtr() { + const ret = wasm.wasmbus_dataPtr(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Publish pending unflushed TX messages. + */ + flush() { + wasm.wasmbus_flush(this.__wbg_ptr); + } + /** + * @returns {number} + */ + freeBytes() { + const ret = wasm.wasmbus_freeBytes(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {boolean} + */ + isFatal() { + const ret = wasm.wasmbus_isFatal(this.__wbg_ptr); + return ret !== 0; + } + /** + * @param {number} capacity + */ + constructor(capacity) { + const ret = wasm.wasmbus_new(capacity); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + this.__wbg_ptr = ret[0]; + WasmBusFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * @returns {number} + */ + recvU32() { + const ret = wasm.wasmbus_recvU32(this.__wbg_ptr); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] >>> 0; + } + rollbackTx() { + const ret = wasm.wasmbus_rollbackTx(this.__wbg_ptr); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @returns {number} + */ + rxPtr() { + const ret = wasm.wasmbus_rxPtr(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + rxSize() { + const ret = wasm.wasmbus_rxSize(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + rxTypeId() { + const ret = wasm.wasmbus_rxTypeId(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Copy-based send. Prefer `acquireTx` + direct JS view writes for hot paths. + * @param {Uint8Array} data + * @param {number} type_id + */ + send(data, type_id) { + const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.wasmbus_send(this.__wbg_ptr, ptr0, len0, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} value + * @param {number} type_id + */ + sendU32(value, type_id) { + const ret = wasm.wasmbus_sendU32(this.__wbg_ptr, value, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} value + * @param {number} type_id + */ + sendU32Unflushed(value, type_id) { + const ret = wasm.wasmbus_sendU32Unflushed(this.__wbg_ptr, value, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } +} +if (Symbol.dispose) WasmBus.prototype[Symbol.dispose] = WasmBus.prototype.free; + +/** + * @param {number} route + * @param {number} ty + * @returns {number} + */ +export function makeTypeId(route, ty) { + const ret = wasm.makeTypeId(route, ty); + return ret >>> 0; +} + +/** + * @param {number} type_id + * @returns {number} + */ +export function msgType(type_id) { + const ret = wasm.msgType(type_id); + return ret; +} + +/** + * @param {number} type_id + * @returns {number} + */ +export function routeId(type_id) { + const ret = wasm.routeId(type_id); + return ret; +} + +/** + * Tiny Rust-side browser program used by the example page. + * + * It polls `inbound`, increments a little-endian `u32` payload, and publishes + * the result to `outbound`. Non-`u32` payloads are echoed unchanged. + * @param {WasmBus} inbound + * @param {WasmBus} outbound + * @returns {boolean} + */ +export function tachyon_browser_echo_once(inbound, outbound) { + _assertClass(inbound, WasmBus); + _assertClass(outbound, WasmBus); + const ret = wasm.tachyon_browser_echo_once(inbound.__wbg_ptr, outbound.__wbg_ptr); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] !== 0; +} +export function __wbg___wbindgen_throw_9c31b086c2b26051(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +} +export function __wbindgen_cast_0000000000000001(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; +} +export function __wbindgen_init_externref_table() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); +} +const WasmBusFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_wasmbus_free(ptr, 1)); + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } +} + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + + +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a4ca432d1945a8ed2dd9755e172d093e381fbbfc GIT binary patch literal 23253 zcmd^{U2I(Encv@Y&U}$Gl*YD_l}u%QW};2xwJGtNl${SA@5)|A)jG&JNQo zkRrw5kdm7qVghaBHf}cE8fY$jkqcut7Y^1f0<7Dns4jfrZN058f`yA_+q5uR7xhIh zQoz{NM*aPt_nb3BN^%pUSfFSe>YVrcd7k(AdY|W=<62vn1Ls`ul?R>;uU~iiC%FEs z7`L*%(mU6_)@ygyyX_0-dab3kwi~+JsCZ!1a%-!1ZhY_mCfvd3 z%;yTFs&loV>|7o4)$S|1La9(F@yGw7QsBx#P%Vv&+&^5Y4D)y3K&b?oT@VGdMZwUJ z3Wb7mg`x0DaOlv6+pQF%z};6Gg5b&S?oC%M|BJ$}mM*t1Z*-pv-Tj^xSJ|+o?X7bc zy6v{B4_|Jrwx4r z#^uW^J*J%ASH+BzNAiKz>zQj`T)(iky>+R5-kms@wViMETA%NA-O-V(#`?G;IgFx# z2jwIAsFkfJ8Mo#>R?SAOx39XBd3(3Lyz|AWNjH9f*52J%TU%-^uL9UqzEt;`mj0Ck zd8cc?v+{S_?pJeUy4O;&KUB;5Y_-?VyGQbsRMbinhX*@Q(?@pfBLmEqbLUnN967h2 zSh@ePbR=*|P<!K|YAvW{Lms1oAN!?IS-j9Stb#b&=Oro8p+i3<#v@>?s z)T8>PZ-ZLX(ekq149VkRr5Qar62wqxHmtFJG#gfGhVv_at+OMQI2Z|Y>blcZxJ!G9 zUhB5a<|P+^E47j0Cp9z(IAT8D!7} zf-ayoIn?2wdM9?HjY6~#7aHX_XgXEHJ^Z6J1KKER_8^y&3_K_1PNG;p1L2+LZjMp5+VR0MtlX%EVEl z;JE;~3w@o$B?rC{Rb$8ivxt@gK8OqcA^w)y5XlS%ZJCG6U}Q6l)iZvF&SDTxYGd6f zsY?@uInKNxWO0h-9CKTH1I9;@h!w9!#C*TTVU_udet-wDZXIT z#}~a4h%GCi%|n80XroFpiGdP;>GFMoMPS=Li?FZSlKw^+4idErJx`BBQeI5k^@=fo zok~6CV-nbDXw&+{dlf?iDPw3*MM7a^&*M$#wc0FD!Oos(7Dh$N#bzjO2MPXEHQM0a z)MDX!92WI{s46W|1=Z7Nm153K?yM$f(x*4Qw#k%CAQ4EFOyJdksUoin!oHHBTLrLE z%E9fXWEj$c=6jV)2+Dhu43K6@Cd?-8E13}O0kpf+o!PXQFGh#PP(Ev0$R4AZms6T`SL$4z?QeD0Q9@@{_f+^!|IM_HF;ms9^SSIIe z-aKbhvm2T@j-z@Fb1}fvVS@59o-Xux8cSz99VPDveIX9T)K-;qwM=5Bf+WQTxTLth zH@TIPM9{K_tAo8MP0=gl*PW>P47CN}TU)jWb z)_0ie_U&jhaZjOL7aTBO$9!R^FjihX>(a0FRiMT|%jQD<)h9=*Qr|K<7*6d6{37vm zaRrHJ2AQXUUZSGbC?`LZAo5;e4ZXuE;3buEQ!V~s23!-K8Kp(2C9kD;NWZ>-k#9Hz zFOclGrkB5Fsq^Fr&}eQ66ML7uk&XgHrVGxF3LsS)kk#Z(UX$AoY5DIksn-zS)*H&j z*+SMc`5twqomBm*3ff8)^vqjcQ{k1gMbEt3O%;BSw&nRok%3O`C)^vt`xtHO`d7CrNB?+mv5R0Y)tx%UPO@2j91wftBAJ=G}gx!jIAx zJ@anws_^5qMbEt3PgQs)ZP7FD_Pz@5r7e2q-FC&p*owgc(oQIp<1(5E^J>Rb z8r`HFQ>O!?*-4J0T9Vf_ba!`G3PuhpZrdWm!n0kBBXVDB;;6__^4N*XPmV-h1N7B^ z7KwvJX%${khEb|aq+?Jhrb%40B-@oLk^nt|Rl3XUn4A~jY-yr!K>(XB@(z?&N%)J4 zX4B9~9Fc-*)%4h2s+d33C&Und4HANxazYH<~p*f{GhU&4Kr5Y0mSfwAWm6U)GXxgLdo;7Dsk=1Ft0X>kR!EA z@EV2G@qcmz7f|*Ohkr8(bP$9H5`&;Fkcu6KV&6MW^=fbwRI{g-ygxhG09&yW7e(Y^ z?vfOtR%t)AbUem7_gxZ@Hfj|Is8wu4qLx)&+mL*=B#j90JZ~1{JLy7IBOL1HS57(x&gjB~@BDSgF#IcxJ8? zYyx8dk=ItnK!Z5nRa>EnOdpE-ZK8ZO$d>Y8N;bABMM!;xWOf`7iTCyu7&!8D)-#s(H6k+MKeqNg~u*q(|=uo1_IOp9nCFBK-c?x6X{_3EJO ztM1JxF*$$%a=~Pxd^w~Pg(O%*lSGz5eGM{;t)0WS2P78NU@xbAh-?cqOq{hFF5-jI zSg)}Sfp;+9WDg-h5}*f#nj?rOl`68h!UEcqtvB73`L4NV%2c@K51p08I+$4x-uLoQ z>OyN>k4uJdEal^QNGn@)8Y03a@nAKhvm?14)6pv0!$5`PVNmjxE&x=p6Wql#P@qzw zvpAw-x~yCp$uWsER@=kCcL3@cSg;)&RBOmDsO*cDK>WKH0Xu2!H3INRU#*#40umii zcqm?iLePU%E6_CC38Gq{2LX*uwG)Ak3{GC3;VOx$lVLovA> zY&I*&ZlHi>CAk?q1t$}WlD)FCG$#0o@8TMj=0KD%4RKM_F&jm*1mnJd(>1eOKu!kC zW(?zDATSu^428r&=wgZy(gMHo5W`SZRguyyQCoy4CTDE>eHYQM*HazYE zN@0SzV_w&&*um4=Dc2Z^Ezngs>FhlTJ331i2^lLYqS^{$!%i^{tArtnNkt9TofWO2 z;78Q8S`-fn=aWI9VS&tI603g%wS09ku2dfn4nU8g$AcP=s(4}#4Pm;BbOld39wOaH z_+W}F`1suuC(i1B4=N|bB1N$R~t4S{0GCf-Nq zI6PP86wlJKagbJC3&BOGgSL6h&BM-L|J|D|IZ=OHYy{&!=DcF9#AF_@{f9->MT`C> z?lBjb$CSLPK}YrtveCvmL@n62pMB$|tIH=WiUIq@5@7U;#^i9CLKR_BJ`1p6Qay_X zEdakbOEyg*2e`8U3q>_p51TVEM?#;$VkN=P>n}(uLZk?jh0Yo3Z@R@UkI!yG&EYd$ zMf_qi8MEOy5A?#Lo(=&efiN3R^A;rwtFz&8`A<3Nestq5q;n#ZtNvagfYnhqnLU(u z*+l65*~A+~)BZSMruStOy>@2*?rs#NOaXepD6&9q6xHgrOO#th>&vpgghgaZ3$PDb zL|ZrqF1LuP-eVE_;0W{7DIvX@M$>|N?G6?11H=OX@l3@B1DjmvxL)!pMb!bwGsBso>0MG;Gke2acwduYdtI};%#~B2`cf2> zyneVY35}a&_%3;0f^@Y0I8iY>$&}&I*r-LHda4;V$$r876%!Rx2Bo-?YJgO|kQPL= z;a(>v7Y)IQ@C)^k(QG#-`{iTz(4>RoaeuXIJDsc zbN%V|SN+MO6PTpy$DvF^e2WuSsTBjb#Rbm1BU?qC!hkM7wI)=r8o%U%g ztTu~M$t4YkyP*T8dpoMbvJ@cLs#en}nh8Q>bxS_a>3~z31HI8rbfObNIyCz8wfBd$ ziCQ7=#55A&J)NkjmrZ@qvk_<-KbyKjAzUw83GqT0!OGGx7V7PAdZ53K#e-mAn;08| z9=>}D8mp%I$%BRpt?P^}9nMxo)f-VzzAo3O|KFgQj7|Oj>+5IWqA6jl&~*oQQ6QUUOmZmWyA5$2)r=M!YbMWlUIln*1yA$|$@N zB6f-*$=b40vZTy|qK1!cN(oljNv=x>bV5h8%8M8y2^P_UoU21`h9gbm1q&W#LC^i= ztRKb)U`WDc7Stf1{QNh7NQyuZan62Q5RRv1qyRc2CAkz75UqhCR&uaN%EikcQgjR- z#kLzUup_JX`s-1EV+3Q>{ArE7&$TpUZA97~wmg&_@+oFIlXS`Q%t$zR$dxrG*}K#1 z4!Tx6)=wfSe}q$8P_zRJoaMDKVQB)}r^g2q*hN#bFbm$QpxBdFVs~8ZEN_c~jGfQ! zgBw{OpTuL!K`}-$3j&Z$v)mE+>r*H;Pn&y=~_qRuXW%>*tL$Tf-Og3xa?*J{t+ONUd|RYMZ~U)APBJGkeNqe zy$3pfw32~_>hgWiSr)~+wkbA1B^514-Mlc~o)H1MP%9 zGoMB3^C=yuRLt)^q;}hB+o7@F6^LPW8&Ok+(j#F0D46(=ln|(XD-5~7rAsw2dgz!F z@PN-O(g9H8%t-JAhoar@hCsrs=2>bAC6igB#-qq8wa)i%%teROQpHMjyBOv{i0a31 zWsQ~^3FR4?&Ww+KuL=G$H_P@;AiO?k0?Cg-*6%e4pc=JY2H|(Uwr7FoL);88B*`^o z`#KB(R|UpZaBZ?%w^vRUD(n^(N2};p`UBw=dRi;tH;1w7WQg3|koAdEa`jAK&?TS;{&b+CI)>@0I3 zzDyoP>6^xhL)*^!TzHJ?W1ZyPe*kHb022dyE7Xe*(iV48=`ckThYyFxnnf{>(y?~2 z0M08CQwMp)-Jw%9HH-;EW17{~uH$G9bC_siP{iRSDb%0viBs6B{*y? zK3hKxmLd(%zZi0M@)BJ<=8~UCgX$mc1Q*rht$>geW{KE{^ktCznD#M8&i6rkwS#Q( zwnpk7gI?p3uxe2cZo|n0Fq=guS|DFadadQJjsw+a7F4v1kPZk z+OUE?Iz8)N;|TF%UcDd9b8%G0LC$kIu829yrRr!FOZ`xWQx?%>X)FV;tf{F-+`Nix5$>TOk~uVqu+o zxZ2RNqAvuG%(>OljOW%|dhLu36kGcgI1|J~lngulbaS zk;1|QA_WmmbL6DVB2IWkDahId7jz2V=8Eqm*pow|)s1+MBNp-yT zKo?w*VA>H`?~bC|tIe`MQK7lj!7iN?*jPf%m4@Xc(@SRXyj#|zWVFXdJhf9nIR>d`x`Djqui|Glj$exCO^wJKKPVVr8&LqWaIs=7PvUM$OT z6_+|(2p3{fDsh^glAS{d5^n$xCN={oNyovyU4GizKh8lS+EJp+JPtL$-P>`I0SkyO z6Fr_DRgBJPa(46}_6xmS|4mo@mbS+)oZP5L3A(p7J`p7Eee0%d4^el$af%c@lU;6M z3n;v@K(lJj_kGW&(V9Q=uXGH1o#d7;G16Ym751~GlY67YQ3L6C4UeUM+*L1O{y!8Q z#>J{0^+Z7v;KYs8xZ0c%zf(r+FZvifWB`JJ|tYn!}&YmdW!wjZrhd< zCt8g|9Y$aaw6H(P*kw!A!Oz6cG>6yK(lilXolXp&%jQ8I>;9&`hOqgXTRL-r!!Z@! zGq%YT@k32Qd2^Oir?~nb%V915t;Yy1IL;Q%&PYNUrj!hO<|nqqO7E7XFG~2?Qa*|! zh6|B|Fi5`r@7y{)c&sTgWY;s_aqEo=P(%r#)nP^@!$P*8B5suQeQnI$dSQVBksLO( zG(Au>Vf{g@ghtql@^*18`6~=-XUr*QL0g#4RwlO=E8qVPD>G&C^V?-2YXkG*b`DE- zl4aCS6$YmvQzKNcM+#(e)_di@7S0GW-Leii{A-;Qn83dXnMUY|J0wpC_cab1>E)gr z?+Cn!c1TSa(!{yswnrXJ>jwIq15)%u?GEUpRoQ0s9R$?QrUr+R2DLsEN4g!Vt+q9^ zF@g24l`tZ`G51$SS{=#Q%W6XMZq=MJyu#!WeAd_T9CiE-<#&^=;3pj10|0J{h0Rg@ zLIgNIvZLT2p3%vm)^zNkw%vECSkwlzR=LBGGK979V;bEhT0IcC!0BY35$NZ-w;Sv< z`?XK$BIIpvb$aTZ))#vlezQJ`5HI{!nn= zer_{)m8T-muowe1Eslj2!LJ~fTK^;gQvq%BH(Y;3Cq79R3KNoS0}CmO%xq)_(m9Yu zF`l4NW*jh`!~!Dwd4qi3CY6hf_kn&>sXgYCWt4Z(ASXL`WUpjLcGC;qkHCff=HN-a`k#VKY&931quu!U}U?!FO?H*%YF; zjS_B5>2vaoky}G~M-alBsb2zwhL`@SNRGHLG3JKq_c@qNe6V}XdiuB-_w7DlzzG*F z#K8&?hHNN%NYLDQ-we4@h(k9c1Gz4rp&m&6B09f;6F}>qO8rxKBErK1r#%r;>TjLu z#p=9PlZd@eM3`DOodGF7C#+yjxOZtl+-B0oRv-1gKuXjA@i>^8`d34&zf1m9@il;v zk$XM07x&_yL@%!g$0&z}gXhnMF0T;LnIr0ImoZ6qCbv&vk+qN|waMF?ZPC zM>Od@3J2&2LiG_;Um|u2eWssUjs(Xgo|y2)0SjHk!zzt8tMy??%%KM7DF_W~F*+jz z%Mhc`!J@3hO4%n?7*}Wx!2)PbRb%p$ZyC<2$;N9(QPcF~NTmA

{@vuA^{SL%q5A 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 void; +export const makeTypeId: (a: number, b: number) => number; +export const msgType: (a: number) => number; +export const routeId: (a: number) => number; +export const tachyon_browser_echo_once: (a: number, b: number) => [number, number, number]; +export const wasmbus_acquireRx: (a: number) => [number, number, number]; +export const wasmbus_acquireTx: (a: number, b: number) => [number, number, number]; +export const wasmbus_availableBytes: (a: number) => number; +export const wasmbus_capacity: (a: number) => number; +export const wasmbus_commitRx: (a: number) => [number, number]; +export const wasmbus_commitTx: (a: number, b: number, c: number) => [number, number]; +export const wasmbus_commitTxUnflushed: (a: number, b: number, c: number) => [number, number]; +export const wasmbus_dataPtr: (a: number) => number; +export const wasmbus_flush: (a: number) => void; +export const wasmbus_freeBytes: (a: number) => number; +export const wasmbus_isFatal: (a: number) => number; +export const wasmbus_new: (a: number) => [number, number, number]; +export const wasmbus_recvU32: (a: number) => [number, number, number]; +export const wasmbus_rollbackTx: (a: number) => [number, number]; +export const wasmbus_rxPtr: (a: number) => number; +export const wasmbus_rxSize: (a: number) => number; +export const wasmbus_rxTypeId: (a: number) => number; +export const wasmbus_send: (a: number, b: number, c: number, d: number) => [number, number]; +export const wasmbus_sendU32: (a: number, b: number, c: number) => [number, number]; +export const wasmbus_sendU32Unflushed: (a: number, b: number, c: number) => [number, number]; +export const __wbindgen_externrefs: WebAssembly.Table; +export const __externref_table_dealloc: (a: number) => void; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_start: () => void; diff --git a/bindings/node/tsconfig.json b/bindings/node/tsconfig.json index 0359398..1ccd577 100644 --- a/bindings/node/tsconfig.json +++ b/bindings/node/tsconfig.json @@ -17,6 +17,7 @@ "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, + "allowArbitraryExtensions": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, diff --git a/bindings/rust/tachyon/Cargo.toml b/bindings/rust/tachyon/Cargo.toml index ae0eb96..1ce5e8e 100644 --- a/bindings/rust/tachyon/Cargo.toml +++ b/bindings/rust/tachyon/Cargo.toml @@ -7,13 +7,19 @@ license = "Apache-2.0" repository = "https://github.com/riyaneel/tachyon" readme = "README.md" -[dependencies] +[lib] +crate-type = ["cdylib", "rlib"] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] tachyon-sys = { version = "0.5.1", path = "../tachyon-sys" } +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + [profile.release] opt-level = 3 codegen-units = 1 lto = "fat" [profile.release.build-override] -opt-level = 3 \ No newline at end of file +opt-level = 3 diff --git a/bindings/rust/tachyon/src/error.rs b/bindings/rust/tachyon/src/error.rs index 80339cf..1a5d140 100644 --- a/bindings/rust/tachyon/src/error.rs +++ b/bindings/rust/tachyon/src/error.rs @@ -1,3 +1,4 @@ +#[cfg(not(target_arch = "wasm32"))] use tachyon_sys::{ tachyon_error_t_TACHYON_ERR_ABI_MISMATCH as ERR_ABI_MISMATCH, tachyon_error_t_TACHYON_ERR_CHMOD as ERR_CHMOD, tachyon_error_t_TACHYON_ERR_EMPTY as ERR_EMPTY, @@ -61,6 +62,7 @@ impl std::fmt::Display for TachyonError { impl std::error::Error for TachyonError {} +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn from_raw(code: u32) -> Result<(), TachyonError> { match code { c if c == SUCCESS => Ok(()), diff --git a/bindings/rust/tachyon/src/lib.rs b/bindings/rust/tachyon/src/lib.rs index e713c6a..5931d0f 100644 --- a/bindings/rust/tachyon/src/lib.rs +++ b/bindings/rust/tachyon/src/lib.rs @@ -1,12 +1,22 @@ +#[cfg(not(target_arch = "wasm32"))] mod bus; mod error; +#[cfg(not(target_arch = "wasm32"))] mod rpc; mod type_id; +#[cfg(target_arch = "wasm32")] +mod wasm; +#[cfg(not(target_arch = "wasm32"))] pub use bus::{BatchIter, Bus, RxBatchGuard, RxGuard, RxMsgView, TxGuard}; pub use error::TachyonError; +#[cfg(not(target_arch = "wasm32"))] pub use rpc::{RpcBus, RpcRxGuard, RpcTxGuard}; pub use type_id::{make_type_id, msg_type, route_id}; +#[cfg(target_arch = "wasm32")] +pub use wasm::{ + WasmBus, tachyon_browser_echo_once, wasm_make_type_id, wasm_msg_type, wasm_route_id, +}; #[cfg(test)] mod tests { diff --git a/bindings/rust/tachyon/src/wasm.rs b/bindings/rust/tachyon/src/wasm.rs new file mode 100644 index 0000000..a59c8ab --- /dev/null +++ b/bindings/rust/tachyon/src/wasm.rs @@ -0,0 +1,400 @@ +use wasm_bindgen::prelude::*; + +use crate::type_id::{make_type_id, msg_type, route_id}; + +const MSG_ALIGNMENT: usize = 64; +const HEADER_SIZE: usize = MSG_ALIGNMENT; +const ALIGN_MASK: usize = MSG_ALIGNMENT - 1; +const SKIP_MARKER: u32 = 0xFFFF_FFFF; +const BATCH_SIZE: u32 = 32; + +#[inline] +fn align_message_size(payload_size: usize) -> usize { + (HEADER_SIZE + payload_size + ALIGN_MASK) & !ALIGN_MASK +} + +#[inline] +fn js_error(message: &str) -> JsValue { + JsValue::from_str(message) +} + +/// Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. +/// +/// This keeps the Tachyon message wire shape (`size`, `type_id`, +/// `reserved_size`, 64-byte alignment, and skip marker) while replacing the +/// native POSIX shared-memory transport with a WASM memory arena that page +/// JavaScript can access through `WebAssembly.Memory`. +#[wasm_bindgen] +pub struct WasmBus { + arena: Box<[u8]>, + capacity: usize, + mask: usize, + head: usize, + published_head: usize, + tail: usize, + pending_tx: u32, + tx_reserved_size: usize, + pre_acquire_head: usize, + rx_payload_offset: usize, + rx_reserved_size: usize, + rx_actual_size: usize, + rx_type_id: u32, + fatal: bool, +} + +#[wasm_bindgen] +impl WasmBus { + #[wasm_bindgen(constructor)] + pub fn new(capacity: u32) -> Result { + let capacity = capacity as usize; + if capacity < MSG_ALIGNMENT || !capacity.is_power_of_two() { + return Err(js_error( + "WasmBus capacity must be a power of two and at least 64 bytes", + )); + } + + Ok(WasmBus { + arena: vec![0; capacity].into_boxed_slice(), + capacity, + mask: capacity - 1, + head: 0, + published_head: 0, + tail: 0, + pending_tx: 0, + tx_reserved_size: 0, + pre_acquire_head: 0, + rx_payload_offset: 0, + rx_reserved_size: 0, + rx_actual_size: 0, + rx_type_id: 0, + fatal: false, + }) + } + + pub fn capacity(&self) -> u32 { + self.capacity as u32 + } + + #[wasm_bindgen(js_name = dataPtr)] + pub fn data_ptr(&self) -> u32 { + self.arena.as_ptr() as u32 + } + + #[wasm_bindgen(js_name = availableBytes)] + pub fn available_bytes(&self) -> u32 { + self.published_head.saturating_sub(self.tail) as u32 + } + + #[wasm_bindgen(js_name = freeBytes)] + pub fn free_bytes(&self) -> u32 { + self.capacity + .saturating_sub(self.head.saturating_sub(self.tail)) as u32 + } + + #[wasm_bindgen(js_name = isFatal)] + pub fn is_fatal(&self) -> bool { + self.fatal + } + + /// Copy-based send. Prefer `acquireTx` + direct JS view writes for hot paths. + pub fn send(&mut self, data: &[u8], type_id: u32) -> Result<(), JsValue> { + let payload_offset = self.acquire_tx_offset(data.len())?; + self.arena[payload_offset..payload_offset + data.len()].copy_from_slice(data); + self.commit_tx(data.len() as u32, type_id) + } + + #[wasm_bindgen(js_name = sendU32)] + pub fn send_u32(&mut self, value: u32, type_id: u32) -> Result<(), JsValue> { + self.send_u32_inner(value, type_id, true) + } + + #[wasm_bindgen(js_name = sendU32Unflushed)] + pub fn send_u32_unflushed(&mut self, value: u32, type_id: u32) -> Result<(), JsValue> { + self.send_u32_inner(value, type_id, false) + } + + /// Reserve a TX slot and return a pointer to its payload bytes in WASM memory. + /// + /// JavaScript can create a zero-copy view with: + /// `new Uint8Array(wasm.memory.buffer, ptr, maxSize)`. + #[wasm_bindgen(js_name = acquireTx)] + pub fn acquire_tx(&mut self, max_size: u32) -> Result { + let payload_offset = self.acquire_tx_offset(max_size as usize)?; + Ok((self.arena.as_ptr() as usize + payload_offset) as u32) + } + + #[wasm_bindgen(js_name = commitTx)] + pub fn commit_tx(&mut self, actual_size: u32, type_id: u32) -> Result<(), JsValue> { + self.commit_tx_inner(actual_size as usize, type_id, true) + } + + #[wasm_bindgen(js_name = commitTxUnflushed)] + pub fn commit_tx_unflushed(&mut self, actual_size: u32, type_id: u32) -> Result<(), JsValue> { + self.commit_tx_inner(actual_size as usize, type_id, false) + } + + #[wasm_bindgen(js_name = rollbackTx)] + pub fn rollback_tx(&mut self) -> Result<(), JsValue> { + if self.tx_reserved_size == 0 { + return Err(js_error("no pending TX slot to roll back")); + } + self.head = self.pre_acquire_head; + self.tx_reserved_size = 0; + Ok(()) + } + + /// Publish pending unflushed TX messages. + pub fn flush(&mut self) { + self.published_head = self.head; + self.pending_tx = 0; + } + + /// Acquire the next RX message, if one is visible. + /// + /// When this returns `true`, read `rxPtr`, `rxSize`, and `rxTypeId`, then + /// call `commitRx` when done. + #[wasm_bindgen(js_name = acquireRx)] + pub fn acquire_rx(&mut self) -> Result { + if self.fatal { + return Err(js_error("WasmBus is in fatal state")); + } + + if self.rx_reserved_size != 0 { + return Ok(true); + } + + if self.published_head <= self.tail { + return Ok(false); + } + + let mut physical_idx = self.tail & self.mask; + if self.capacity - physical_idx < 12 { + self.fatal = true; + return Err(js_error("corrupt Tachyon ring: truncated message header")); + } + + let mut size = self.read_u32(physical_idx); + let mut type_id = self.read_u32(physical_idx + 4); + let mut reserved_size = self.read_u32(physical_idx + 8) as usize; + + if size == SKIP_MARKER { + let space_until_end = self.capacity - physical_idx; + self.tail += space_until_end; + physical_idx = 0; + + if self.published_head <= self.tail { + return Ok(false); + } + + size = self.read_u32(0); + type_id = self.read_u32(4); + reserved_size = self.read_u32(8) as usize; + } + + let actual_size = size as usize; + if reserved_size < HEADER_SIZE + || reserved_size > self.capacity + || (reserved_size & ALIGN_MASK) != 0 + || actual_size > reserved_size - HEADER_SIZE + { + self.fatal = true; + return Err(js_error("corrupt Tachyon ring: invalid message metadata")); + } + + self.rx_payload_offset = physical_idx + HEADER_SIZE; + self.rx_reserved_size = reserved_size; + self.rx_actual_size = actual_size; + self.rx_type_id = type_id; + + Ok(true) + } + + #[wasm_bindgen(js_name = rxPtr)] + pub fn rx_ptr(&self) -> u32 { + if self.rx_reserved_size == 0 { + return 0; + } + (self.arena.as_ptr() as usize + self.rx_payload_offset) as u32 + } + + #[wasm_bindgen(js_name = rxSize)] + pub fn rx_size(&self) -> u32 { + self.rx_actual_size as u32 + } + + #[wasm_bindgen(js_name = rxTypeId)] + pub fn rx_type_id(&self) -> u32 { + self.rx_type_id + } + + #[wasm_bindgen(js_name = commitRx)] + pub fn commit_rx(&mut self) -> Result<(), JsValue> { + if self.rx_reserved_size == 0 { + return Err(js_error("no pending RX slot to commit")); + } + + self.tail += self.rx_reserved_size; + self.rx_payload_offset = 0; + self.rx_reserved_size = 0; + self.rx_actual_size = 0; + self.rx_type_id = 0; + + Ok(()) + } + + #[wasm_bindgen(js_name = recvU32)] + pub fn recv_u32(&mut self) -> Result { + if !self.acquire_rx()? { + return Err(js_error("no u32 message available")); + } + if self.rx_actual_size != 4 { + return Err(js_error("pending message is not a u32 payload")); + } + + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&self.arena[self.rx_payload_offset..self.rx_payload_offset + 4]); + let value = u32::from_le_bytes(bytes); + self.commit_rx()?; + Ok(value) + } +} + +impl WasmBus { + fn send_u32_inner(&mut self, value: u32, type_id: u32, flush: bool) -> Result<(), JsValue> { + let payload_offset = self.acquire_tx_offset(4)?; + self.arena[payload_offset..payload_offset + 4].copy_from_slice(&value.to_le_bytes()); + self.commit_tx_inner(4, type_id, flush) + } + + fn acquire_tx_offset(&mut self, max_size: usize) -> Result { + if self.fatal { + return Err(js_error("WasmBus is in fatal state")); + } + if self.tx_reserved_size != 0 { + return Err(js_error("a TX slot is already pending")); + } + + let aligned_size = align_message_size(max_size); + if aligned_size > self.capacity || max_size > (SKIP_MARKER as usize) - HEADER_SIZE { + return Err(js_error("message is larger than the Tachyon ring capacity")); + } + + let mut physical_idx = self.head & self.mask; + let space_until_end = self.capacity - physical_idx; + let need_skip = space_until_end < aligned_size; + let required_space = aligned_size + if need_skip { space_until_end } else { 0 }; + + if self.head - self.tail + required_space > self.capacity { + return Err(js_error("Tachyon ring buffer is full")); + } + + if need_skip { + self.write_u32(physical_idx, SKIP_MARKER); + self.write_u32(physical_idx + 4, 0); + self.write_u32(physical_idx + 8, 0); + self.head += space_until_end; + physical_idx = 0; + } + + self.pre_acquire_head = self.head; + self.tx_reserved_size = aligned_size; + + Ok(physical_idx + HEADER_SIZE) + } + + fn commit_tx_inner( + &mut self, + actual_size: usize, + type_id: u32, + flush: bool, + ) -> Result<(), JsValue> { + if self.tx_reserved_size == 0 { + return Err(js_error("no pending TX slot to commit")); + } + if actual_size > self.tx_reserved_size - HEADER_SIZE { + self.tx_reserved_size = 0; + return Err(js_error("actual TX size exceeds reserved slot size")); + } + + let physical_idx = self.head & self.mask; + self.write_u32(physical_idx, actual_size as u32); + self.write_u32(physical_idx + 4, type_id); + self.write_u32(physical_idx + 8, self.tx_reserved_size as u32); + self.write_u32(physical_idx + 12, 0); + self.write_u32(physical_idx + 16, 0); + self.write_u32(physical_idx + 20, 0); + + self.head += self.tx_reserved_size; + self.tx_reserved_size = 0; + self.pending_tx += 1; + + if flush || self.pending_tx >= BATCH_SIZE { + self.flush(); + } + + Ok(()) + } + + fn read_u32(&self, offset: usize) -> u32 { + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&self.arena[offset..offset + 4]); + u32::from_le_bytes(bytes) + } + + fn write_u32(&mut self, offset: usize, value: u32) { + self.arena[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } +} + +/// Tiny Rust-side browser program used by the example page. +/// +/// It polls `inbound`, increments a little-endian `u32` payload, and publishes +/// the result to `outbound`. Non-`u32` payloads are echoed unchanged. +#[wasm_bindgen] +pub fn tachyon_browser_echo_once( + inbound: &mut WasmBus, + outbound: &mut WasmBus, +) -> Result { + if !inbound.acquire_rx()? { + return Ok(false); + } + + let type_id = inbound.rx_type_id; + let actual_size = inbound.rx_actual_size; + let inbound_offset = inbound.rx_payload_offset; + let outbound_offset = outbound.acquire_tx_offset(actual_size)?; + + if actual_size == 4 { + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&inbound.arena[inbound_offset..inbound_offset + 4]); + let value = u32::from_le_bytes(bytes).wrapping_add(1); + outbound.arena[outbound_offset..outbound_offset + 4].copy_from_slice(&value.to_le_bytes()); + } else { + outbound.arena[outbound_offset..outbound_offset + actual_size] + .copy_from_slice(&inbound.arena[inbound_offset..inbound_offset + actual_size]); + } + + outbound.commit_tx_inner( + actual_size, + make_type_id(route_id(type_id).wrapping_add(1), msg_type(type_id)), + true, + )?; + inbound.commit_rx()?; + + Ok(true) +} + +#[wasm_bindgen(js_name = makeTypeId)] +pub fn wasm_make_type_id(route: u16, ty: u16) -> u32 { + make_type_id(route, ty) +} + +#[wasm_bindgen(js_name = routeId)] +pub fn wasm_route_id(type_id: u32) -> u16 { + route_id(type_id) +} + +#[wasm_bindgen(js_name = msgType)] +pub fn wasm_msg_type(type_id: u32) -> u16 { + msg_type(type_id) +} diff --git a/docs/README.md b/docs/README.md index bc1ab19..9b05b1c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,2 +1,4 @@ # Documentations +- [Browser WASM example](../examples/browser_wasm/README.md) - in-page JavaScript and Rust WASM communication through + Tachyon rings. diff --git a/examples/README.md b/examples/README.md index 65afe60..0b8e48c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,2 +1,4 @@ # Examples +- [browser_wasm](./browser_wasm) - page JavaScript and Rust WASM exchange binary messages through browser-local + Tachyon rings. diff --git a/examples/browser_wasm/README.md b/examples/browser_wasm/README.md new file mode 100644 index 0000000..84f0e94 --- /dev/null +++ b/examples/browser_wasm/README.md @@ -0,0 +1,34 @@ +# Tachyon Browser WASM Example + +This example runs Tachyon inside a single browser page. Page JavaScript writes +binary payloads directly into a `WasmBus` TX slot in WebAssembly memory, a small +Rust WASM function polls the inbound ring and replies on a second ring, and +JavaScript reads the reply from WASM memory. + +The browser build does not use POSIX shared memory or UNIX sockets. Those APIs +are unavailable in browsers, so the WASM path is a page-local Tachyon ring with +the same 64-byte message header, alignment, `type_id`, and skip-marker rules. + +## Run + +```bash +cd examples/browser_wasm +npm install +npm run build:wasm +npm run dev +``` + +Open the Vite URL, then use **Send To Rust** or **Run Browser RTT Bench**. + +## Native Comparison + +From the same directory: + +```bash +npm run bench:native +``` + +The browser benchmark reports batch-averaged round-trip time because +`performance.now()` is too coarse for individual sub-microsecond samples in many +browsers. Compare the browser mean/p50 against the native Rust `bench_ipc` +output for a practical JS/WASM overhead view. diff --git a/examples/browser_wasm/index.html b/examples/browser_wasm/index.html new file mode 100644 index 0000000..4b46e22 --- /dev/null +++ b/examples/browser_wasm/index.html @@ -0,0 +1,61 @@ + + + + + + Tachyon WASM Browser Bus + + + +

+
+
+

Tachyon WASM Browser Bus

+

Rust WASM and page JavaScript exchanging binary messages through Tachyon rings in one browser page.

+
+
+
+ WASM + loading +
+
+ Capacity + - +
+
+ Last Reply + - +
+
+
+ +
+ + + + +
+ +
+

Messages

+

+      
+ +
+

Browser RTT Profile

+ + + + +
Not run yet
+
+
+ + + diff --git a/examples/browser_wasm/main.js b/examples/browser_wasm/main.js new file mode 100644 index 0000000..95585a3 --- /dev/null +++ b/examples/browser_wasm/main.js @@ -0,0 +1,173 @@ +import init, { + WasmBus, + makeTypeId, + msgType, + routeId, + tachyon_browser_echo_once, +} from "./pkg/tachyon_ipc.js"; + +const CAPACITY = 1 << 20; +const BATCH_SIZE = 4096; + +const els = { + status: document.querySelector("#wasm-status"), + capacity: document.querySelector("#capacity"), + lastReply: document.querySelector("#last-reply"), + value: document.querySelector("#value"), + send: document.querySelector("#send"), + iterations: document.querySelector("#iterations"), + bench: document.querySelector("#bench"), + log: document.querySelector("#log"), + benchTable: document.querySelector("#bench-table"), +}; + +let wasm; +let jsToRust; +let rustToJs; +let typeCounter; + +function memory() { + return wasm.memory; +} + +function appendLog(line) { + const time = new Date().toLocaleTimeString(); + els.log.textContent = `[${time}] ${line}\n${els.log.textContent}`; +} + +function writeU32ToBus(bus, value, typeId) { + bus.sendU32(value >>> 0, typeId); +} + +function readU32FromBus(bus) { + if (!bus.acquireRx()) return null; + const ptr = bus.rxPtr(); + const size = bus.rxSize(); + const typeId = bus.rxTypeId(); + const value = size === 4 ? new DataView(memory().buffer, ptr, 4).getUint32(0, true) : null; + bus.commitRx(); + return { value, size, typeId }; +} + +function pingRust(value) { + writeU32ToBus(jsToRust, value, typeCounter); + + if (!tachyon_browser_echo_once(jsToRust, rustToJs)) { + throw new Error("Rust WASM program did not receive the JS message"); + } + + const reply = readU32FromBus(rustToJs); + if (!reply) { + throw new Error("JS did not receive the Rust WASM reply"); + } + + return reply; +} + +function pingRustFast(value) { + jsToRust.sendU32(value >>> 0, typeCounter); + if (!tachyon_browser_echo_once(jsToRust, rustToJs)) { + throw new Error("Rust WASM program did not receive the JS message"); + } + return rustToJs.recvU32(); +} + +function percentile(sorted, pct) { + const idx = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * pct)); + return sorted[idx]; +} + +function formatNs(ns) { + if (ns >= 1000) return `${(ns / 1000).toFixed(2)} us`; + return `${ns.toFixed(1)} ns`; +} + +function setBenchRows(rows) { + els.benchTable.replaceChildren( + ...rows.map(([label, value]) => { + const tr = document.createElement("tr"); + const left = document.createElement("td"); + const right = document.createElement("td"); + left.textContent = label; + right.textContent = value; + tr.append(left, right); + return tr; + }), + ); +} + +async function runBench() { + const iterations = Math.max(1000, Number.parseInt(els.iterations.value, 10) || 1000000); + const warmup = Math.min(10000, Math.floor(iterations / 10)); + + els.bench.disabled = true; + setBenchRows([["Running", `${iterations.toLocaleString()} RTTs`]]); + await new Promise((resolve) => requestAnimationFrame(resolve)); + + for (let i = 0; i < warmup; i += 1) { + pingRust(i); + } + + const samples = []; + let totalStart = performance.now(); + for (let i = 0; i < iterations; i += BATCH_SIZE) { + const batchCount = Math.min(BATCH_SIZE, iterations - i); + const batchStart = performance.now(); + for (let j = 0; j < batchCount; j += 1) { + pingRustFast(i + j); + } + samples.push(((performance.now() - batchStart) * 1_000_000) / batchCount); + } + const totalMs = performance.now() - totalStart; + + samples.sort((a, b) => a - b); + const throughput = iterations / (totalMs / 1000); + setBenchRows([ + ["Payload", "4 bytes u32"], + ["Samples", `${samples.length.toLocaleString()} batch averages x ${BATCH_SIZE}`], + ["Direct doorbell p50", formatNs(percentile(samples, 0.5))], + ["Direct doorbell p90", formatNs(percentile(samples, 0.9))], + ["Direct doorbell p99", formatNs(percentile(samples, 0.99))], + ["Direct doorbell mean", formatNs((totalMs * 1_000_000) / iterations)], + ["Throughput", `${(throughput / 1000).toFixed(1)} K RTT/sec`], + ]); + appendLog(`browser bench completed: ${(throughput / 1000).toFixed(1)} K RTT/sec`); + els.bench.disabled = false; +} + +async function main() { + wasm = await init(); + typeCounter = makeTypeId(0, 7); + jsToRust = new WasmBus(CAPACITY); + rustToJs = new WasmBus(CAPACITY); + + els.status.textContent = "ready"; + els.capacity.textContent = `${CAPACITY / 1024} KiB x 2`; + els.send.disabled = false; + els.bench.disabled = false; + + els.send.addEventListener("click", () => { + const value = Number.parseInt(els.value.value, 10) >>> 0; + const reply = pingRust(value); + els.lastReply.textContent = `${reply.value}`; + appendLog( + `JS sent ${value}, Rust replied ${reply.value}; route=${routeId(reply.typeId)} type=${msgType( + reply.typeId, + )}`, + ); + }); + + els.bench.addEventListener("click", () => { + runBench().catch((err) => { + appendLog(`bench failed: ${err.message}`); + els.bench.disabled = false; + }); + }); + + appendLog("WASM module initialized"); +} + +main().catch((err) => { + els.status.textContent = "failed"; + appendLog(err.stack || err.message); +}); diff --git a/examples/browser_wasm/package-lock.json b/examples/browser_wasm/package-lock.json new file mode 100644 index 0000000..4426179 --- /dev/null +++ b/examples/browser_wasm/package-lock.json @@ -0,0 +1,1204 @@ +{ + "name": "tachyon-browser-wasm-example", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tachyon-browser-wasm-example", + "devDependencies": { + "vite": "latest", + "wasm-pack": "latest" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-install": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.2.tgz", + "integrity": "sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^0.26.1", + "rimraf": "^3.0.2", + "tar": "^6.1.11" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/wasm-pack": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.14.0.tgz", + "integrity": "sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "binary-install": "^1.1.2" + }, + "bin": { + "wasm-pack": "run.js" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/examples/browser_wasm/package.json b/examples/browser_wasm/package.json new file mode 100644 index 0000000..1cc9019 --- /dev/null +++ b/examples/browser_wasm/package.json @@ -0,0 +1,15 @@ +{ + "name": "tachyon-browser-wasm-example", + "private": true, + "type": "module", + "scripts": { + "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../../bindings/rust/tachyon --target web --release --out-dir ../../../examples/browser_wasm/pkg", + "dev": "vite --host 127.0.0.1", + "build": "npm run build:wasm && vite build", + "bench:native": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) cargo run --release --manifest-path ../../bindings/rust/tachyon/Cargo.toml --example bench_ipc" + }, + "devDependencies": { + "vite": "latest", + "wasm-pack": "latest" + } +} diff --git a/examples/browser_wasm/style.css b/examples/browser_wasm/style.css new file mode 100644 index 0000000..94ae93c --- /dev/null +++ b/examples/browser_wasm/style.css @@ -0,0 +1,169 @@ +:root { + color-scheme: light; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: #f4f6f8; + color: #17202a; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + +.shell { + width: min(1120px, calc(100vw - 32px)); + margin: 32px auto; + display: grid; + gap: 16px; +} + +.panel, +.toolbar, +.results { + background: #ffffff; + border: 1px solid #d9e0e7; + border-radius: 8px; + box-shadow: 0 1px 2px rgb(20 30 45 / 8%); +} + +.panel { + display: grid; + grid-template-columns: 1fr auto; + gap: 24px; + padding: 24px; +} + +h1, +h2, +p { + margin: 0; +} + +h1 { + font-size: 28px; + font-weight: 720; +} + +h2 { + font-size: 16px; + margin-bottom: 12px; +} + +p { + margin-top: 8px; + max-width: 680px; + color: #52606d; +} + +.status-grid { + display: grid; + grid-template-columns: repeat(3, minmax(120px, 1fr)); + gap: 12px; + align-content: center; +} + +.status-grid div { + border-left: 3px solid #2c7a7b; + padding: 4px 12px; +} + +.status-grid span { + display: block; + color: #667788; + font-size: 12px; + text-transform: uppercase; +} + +.status-grid strong { + display: block; + margin-top: 4px; + font-size: 16px; + white-space: nowrap; +} + +.toolbar { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: end; + padding: 16px; +} + +label { + display: grid; + gap: 6px; + color: #52606d; + font-size: 13px; +} + +input { + width: 150px; + height: 38px; + border: 1px solid #bfccd8; + border-radius: 6px; + padding: 0 10px; + font: inherit; +} + +button { + height: 38px; + border: 0; + border-radius: 6px; + padding: 0 14px; + background: #1f6f78; + color: #ffffff; + font: inherit; + font-weight: 650; + cursor: pointer; +} + +button:disabled { + background: #9eb0bd; + cursor: wait; +} + +.results { + padding: 16px; +} + +pre { + min-height: 110px; + max-height: 260px; + overflow: auto; + margin: 0; + padding: 12px; + border-radius: 6px; + background: #111827; + color: #d1fae5; + font-size: 13px; + line-height: 1.5; +} + +table { + width: 100%; + border-collapse: collapse; +} + +td { + border-top: 1px solid #e2e8f0; + padding: 9px 0; +} + +td:last-child { + text-align: right; + font-variant-numeric: tabular-nums; +} + +@media (max-width: 760px) { + .panel { + grid-template-columns: 1fr; + } + + .status-grid { + grid-template-columns: 1fr; + } +} From ffd2d77b5f202f1b215ba4418d23c6d1d7403878 Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sat, 9 May 2026 12:22:12 -0700 Subject: [PATCH 02/23] Add browser WASM test coverage --- .github/workflows/ci.yml | 35 + bindings/node/package.json | 5 +- bindings/node/src/ts/browser.ts | 205 ++- bindings/node/src/ts/bus.ts | 216 +-- bindings/node/src/ts/bus_core.ts | 228 ++++ bindings/node/src/ts/wasm/tachyon_ipc.d.ts | 56 +- bindings/node/src/ts/wasm/tachyon_ipc.js | 370 ++++- bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm | Bin 23253 -> 21072 bytes .../node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts | 4 - bindings/node/test/browser_wasm.spec.mjs | 363 +++++ bindings/rust/tachyon/src/lib.rs | 4 +- bindings/rust/tachyon/src/wasm.rs | 70 - examples/browser_wasm/README.md | 3 + examples/browser_wasm/main.js | 38 +- examples/browser_wasm/package-lock.json | 1204 ----------------- examples/browser_wasm/package.json | 2 +- examples/browser_wasm/rust/Cargo.toml | 12 + examples/browser_wasm/rust/src/lib.rs | 44 + 18 files changed, 1283 insertions(+), 1576 deletions(-) create mode 100644 bindings/node/src/ts/bus_core.ts create mode 100644 bindings/node/test/browser_wasm.spec.mjs delete mode 100644 examples/browser_wasm/package-lock.json create mode 100644 examples/browser_wasm/rust/Cargo.toml create mode 100644 examples/browser_wasm/rust/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cffc2da..56576a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,6 +343,12 @@ jobs: run: cargo test working-directory: bindings/rust + - name: Check WASM target + run: | + rustup target add wasm32-unknown-unknown + cargo check -p tachyon-ipc --target wasm32-unknown-unknown + working-directory: bindings/rust + rust_macos: name: Rust bindings (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} @@ -635,6 +641,35 @@ jobs: working-directory: bindings/node run: npm test + nodejs_browser_wasm: + name: Browser WASM tests + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + # Node only serves files and drives CDP here; Chromium is the target under test. + # Use Node 24 for the built-in WebSocket client. + node-version: 24 + cache: 'npm' + cache-dependency-path: bindings/node/package-lock.json + + - name: Install dependencies + working-directory: bindings/node + run: npm install --ignore-scripts + + - name: Check Chromium + run: /usr/bin/chromium --version + + - name: Browser WASM tests + working-directory: bindings/node + env: + CHROMIUM_BIN: /usr/bin/chromium + run: npm run test:browser + nodejs_macos: name: Node.js bindings ${{ matrix.runner }} (Node ${{ matrix.node-version }}) runs-on: ${{ matrix.runner }} diff --git a/bindings/node/package.json b/bindings/node/package.json index 5a14512..ba9e342 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -26,7 +26,7 @@ "scripts": { "build": "npm run build:native && npm run build:ts && npm run copy:wasm", "build:native": "cmake-js compile", - "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target bundler --release --out-dir ../../node/src/ts/wasm", + "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target web --release --out-dir ../../node/src/ts/wasm", "build:ts": "tsc", "copy:wasm": "mkdir -p dist/wasm && cp src/ts/wasm/tachyon_ipc.js src/ts/wasm/tachyon_ipc_bg.js src/ts/wasm/tachyon_ipc_bg.wasm src/ts/wasm/*.d.ts dist/wasm/", "clean": "rm -rf build dist", @@ -35,7 +35,8 @@ "install": "cmake-js compile", "lint": "eslint src/ts", "prebuild": "cmake-js compile --runtime node --runtime-version $(node -v | cut -c2-)", - "test": "mocha" + "test": "mocha", + "test:browser": "npm run build:ts && npm run copy:wasm && node test/browser_wasm.spec.mjs" }, "dependencies": { "node-addon-api": "^8.7.0" diff --git a/bindings/node/src/ts/browser.ts b/bindings/node/src/ts/browser.ts index 6627900..89127fc 100644 --- a/bindings/node/src/ts/browser.ts +++ b/bindings/node/src/ts/browser.ts @@ -1,13 +1,14 @@ -import { RxBatch, type RxMessage } from './batch.ts'; -import { PeerDeadError } from './error.ts'; -import type { BatchController } from './batch.ts'; -import type { RxController, TxController } from './guards.ts'; -import { RxGuard, TxGuard } from './guards.ts'; -import { makeTypeId, msgType, routeId, tachyon_browser_echo_once, WasmBus } from './wasm/tachyon_ipc.js'; -// @ts-expect-error Bundlers load the generated wasm module through this import. -import * as wasmRaw from './wasm/tachyon_ipc_bg.wasm'; +import type { BusHandle, RawRx } from './bus_core.ts'; +import { BusBase } from './bus_core.ts'; +import initWasm, { makeTypeId, msgType, routeId, WasmBus } from './wasm/tachyon_ipc.ts'; + +interface WasmRuntime { + readonly memory: { + readonly buffer: ArrayBuffer; + }; +} -const wasmMemory = (wasmRaw as unknown as { readonly memory: { readonly buffer: ArrayBuffer } }).memory; +const wasm = (await initWasm()) as unknown as WasmRuntime; interface BrowserEndpoint { handle: WasmBus; @@ -17,163 +18,115 @@ interface BrowserEndpoint { const endpoints = new Map(); function slot(ptr: number, len: number): Uint8Array { - return new Uint8Array(wasmMemory.buffer, ptr, len); + return new Uint8Array(wasm.memory.buffer, ptr, len); } -/** - * Browser implementation of the Tachyon SPSC bus. - * - * Bundlers resolve `@tachyon-ipc/core` to this entry through the package - * `browser` export condition. The constructor shape matches Node: - * `Bus.listen(path, capacity)` creates a page-local ring and - * `Bus.connect(path)` attaches to it. - */ -export class Bus implements Disposable { +class BrowserBusHandle implements BusHandle { #endpoint: BrowserEndpoint; #path: string; - #closed = false; - private constructor(path: string, endpoint: BrowserEndpoint) { + public constructor(path: string, endpoint: BrowserEndpoint) { this.#path = path; this.#endpoint = endpoint; } - public static listen(socketPath: string, capacity: number): Bus { - if (endpoints.has(socketPath)) { - throw new Error(`Bus.listen: browser endpoint already exists for ${socketPath}`); + public close(): void { + this.#endpoint.refs -= 1; + if (this.#endpoint.refs <= 0) { + endpoints.delete(this.#path); + this.#endpoint.handle.free(); } + } - const endpoint = { handle: new WasmBus(capacity), refs: 1 }; - endpoints.set(socketPath, endpoint); - return new Bus(socketPath, endpoint); + public send(data: Buffer | Uint8Array, typeId?: number): void { + this.#endpoint.handle.send(data, typeId ?? 0); } - public static connect(socketPath: string): Bus { - const endpoint = endpoints.get(socketPath); - if (endpoint === undefined) { - throw new Error(`Bus.connect: no browser endpoint is listening at ${socketPath}`); - } + public acquireTx(maxSize: number): Uint8Array { + const ptr = this.#endpoint.handle.acquireTx(maxSize); + return slot(ptr, maxSize); + } - endpoint.refs += 1; - return new Bus(socketPath, endpoint); + public commitTx(actualSize: number, typeId: number): void { + this.#endpoint.handle.commitTx(actualSize, typeId); } - public setPollingMode(_spinMode: 0 | 1): void { - this.#assertOpen(); + public commitTxUnflushed(actualSize: number, typeId: number): void { + this.#endpoint.handle.commitTxUnflushed(actualSize, typeId); } - public setNumaNode(_nodeId: number): void { - this.#assertOpen(); + public rollbackTx(): void { + this.#endpoint.handle.rollbackTx(); } public flush(): void { - this.#assertOpen(); this.#endpoint.handle.flush(); } - public send(data: Uint8Array, typeId = 0): void { - this.#assertOpen(); - this.#endpoint.handle.send(data, typeId); + public acquireRx(): RawRx | null { + if (!this.#endpoint.handle.acquireRx()) return null; + const ptr = this.#endpoint.handle.rxPtr(); + const actualSize = this.#endpoint.handle.rxSize(); + return { + data: slot(ptr, actualSize), + typeId: this.#endpoint.handle.rxTypeId(), + actualSize, + }; } - public sendU32(value: number, typeId = 0): void { - this.#assertOpen(); - this.#endpoint.handle.sendU32(value, typeId); + public commitRx(): void { + this.#endpoint.handle.commitRx(); } - public recv(): { data: Uint8Array; typeId: number } { - this.#assertOpen(); - const guard = this.acquireRx(); - if (guard === null) { - throw new Error('Bus.recv: no browser message is available. Use a direct doorbell after send().'); - } - - const data = new Uint8Array(guard.data()); - const typeId = guard.typeId; - guard.commit(); - return { data, typeId }; + public setPollingMode(_spinMode: number): void { + // Browser delivery is direct and non-blocking; there is no futex polling mode. } - public recvU32(): number { - this.#assertOpen(); - return this.#endpoint.handle.recvU32(); + public setNumaNode(_nodeId: number): void { + // WASM memory is page-local and cannot be NUMA-bound from browser JS. } - public acquireTx(maxSize: number): TxGuard { - this.#assertOpen(); - const ptr = this.#endpoint.handle.acquireTx(maxSize); - const ctrl: TxController = { - commitTx: (s, t) => { - this.#endpoint.handle.commitTx(s, t); - }, - commitTxUnflushed: (s, t) => { - this.#endpoint.handle.commitTxUnflushed(s, t); - }, - rollbackTx: () => { - this.#endpoint.handle.rollbackTx(); - }, - }; - return new TxGuard(ctrl, slot(ptr, maxSize) as unknown as Buffer); + public getState(): number { + return this.#endpoint.handle.isFatal() ? 4 : 2; } +} - public acquireRx(_spinThreshold = 0): RxGuard | null { - this.#assertOpen(); - if (this.#endpoint.handle.isFatal()) throw new PeerDeadError(); - if (!this.#endpoint.handle.acquireRx()) return null; +/** + * Browser implementation of the Tachyon SPSC bus. + * + * Bundlers resolve `@tachyon-ipc/core` to this entry through the package + * `browser` export condition. The constructor shape matches Node: + * `Bus.listen(path, capacity)` creates a page-local ring and + * `Bus.connect(path)` attaches to it. + */ +export class Bus extends BusBase { + private constructor(path: string, endpoint: BrowserEndpoint) { + super(new BrowserBusHandle(path, endpoint), { + defaultSpinThreshold: 0, + retryNullRecv: false, + nullRecvMessage: 'Bus.recv: no browser message is available. Use a direct doorbell after send().', + copyData: (data) => new Uint8Array(data), + }); + } - const ptr = this.#endpoint.handle.rxPtr(); - const actualSize = this.#endpoint.handle.rxSize(); - const typeId = this.#endpoint.handle.rxTypeId(); - const ctrl: RxController = { - commitRx: () => { - this.#endpoint.handle.commitRx(); - }, - getState: () => (this.#endpoint.handle.isFatal() ? 4 : 2), - }; - return new RxGuard(ctrl, slot(ptr, actualSize) as unknown as Buffer, typeId, actualSize); - } - - public drainBatch(maxMsgs: number, _spinThreshold = 0): RxBatch { - this.#assertOpen(); - if (this.#endpoint.handle.isFatal()) throw new PeerDeadError(); - - const messages: RxMessage[] = []; - for (let i = 0; i < maxMsgs; i += 1) { - const guard = this.acquireRx(); - if (guard === null) break; - messages.push({ - data: new Uint8Array(guard.data()) as unknown as RxMessage['data'], - typeId: guard.typeId, - size: guard.actualSize, - }); - guard.commit(); + public static listen(socketPath: string, capacity: number): Bus { + if (endpoints.has(socketPath)) { + throw new Error(`Bus.listen: browser endpoint already exists for ${socketPath}`); } - const ctrl: BatchController = { - commitBatch: () => { - // Browser batches are copied and committed while draining. - }, - getState: () => (this.#endpoint.handle.isFatal() ? 4 : 2), - }; - return new RxBatch(ctrl, messages); + const endpoint = { handle: new WasmBus(capacity), refs: 1 }; + endpoints.set(socketPath, endpoint); + return new Bus(socketPath, endpoint); } - public close(): void { - if (this.#closed) return; - this.#closed = true; - this.#endpoint.refs -= 1; - if (this.#endpoint.refs <= 0) { - endpoints.delete(this.#path); - this.#endpoint.handle.free(); + public static connect(socketPath: string): Bus { + const endpoint = endpoints.get(socketPath); + if (endpoint === undefined) { + throw new Error(`Bus.connect: no browser endpoint is listening at ${socketPath}`); } - } - public [Symbol.dispose](): void { - this.close(); - } - - #assertOpen(): void { - if (this.#closed) throw new Error('Bus: this bus has been closed.'); + endpoint.refs += 1; + return new Bus(socketPath, endpoint); } } @@ -192,4 +145,4 @@ export { RxBatch } from './batch.ts'; export type { RxMessage } from './batch.ts'; export { TxGuard, RxGuard } from './guards.ts'; export type { TxSlot, RxSlot } from './guards.ts'; -export { makeTypeId, msgType, routeId, tachyon_browser_echo_once, WasmBus }; +export { makeTypeId, msgType, routeId }; diff --git a/bindings/node/src/ts/bus.ts b/bindings/node/src/ts/bus.ts index d5cb0eb..6db5668 100644 --- a/bindings/node/src/ts/bus.ts +++ b/bindings/node/src/ts/bus.ts @@ -1,11 +1,9 @@ import { createRequire } from 'node:module'; import { isMainThread } from 'node:worker_threads'; -import type { BatchController, RxMessage } from './batch.ts'; -import { RxBatch } from './batch.ts'; -import { AbiMismatchError, ErrorCode, PeerDeadError, isTachyonError } from './error.ts'; -import type { RxController, RxSlot, TxController } from './guards.ts'; -import { RxGuard, TxGuard } from './guards.ts'; +import { AbiMismatchError, ErrorCode, isTachyonError } from './error.ts'; +import type { BusHandle, RawRx } from './bus_core.ts'; +import { BusBase } from './bus_core.ts'; const _require = createRequire(import.meta.url); @@ -68,6 +66,70 @@ function loadNative(): NativeModule { const native = loadNative(); +class NativeBusHandle implements BusHandle { + #handle: NativeBinding; + + public constructor(handle: NativeBinding) { + this.#handle = handle; + } + + public close(): void { + this.#handle.close(); + } + + public send(data: Buffer | Uint8Array, typeId?: number): void { + this.#handle.send(data, typeId); + } + + public acquireTx(maxSize: number): Buffer { + return this.#handle.acquireTx(maxSize); + } + + public commitTx(actualSize: number, typeId: number): void { + this.#handle.commitTx(actualSize, typeId); + } + + public commitTxUnflushed(actualSize: number, typeId: number): void { + this.#handle.commitTxUnflushed(actualSize, typeId); + } + + public rollbackTx(): void { + this.#handle.rollbackTx(); + } + + public flush(): void { + this.#handle.flush(); + } + + public acquireRx(spinThreshold?: number): RawRx | null { + return this.#handle.acquireRxBlocking(spinThreshold); + } + + public drainBatch(maxMsgs: number, spinThreshold?: number): { data: Buffer; typeId: number; size: number }[] { + return this.#handle.drainBatch(maxMsgs, spinThreshold); + } + + public commitRx(): void { + this.#handle.commitRx(); + } + + public commitBatch(): void { + this.#handle.commitBatch(); + } + + public setPollingMode(spinMode: number): void { + this.#handle.setPollingMode(spinMode); + } + + public setNumaNode(nodeId: number): void { + this.#handle.setNumaNode(nodeId); + } + + public getState(): number { + return this.#handle.getState(); + } +} + function warnMainThread(method: string): void { if (isMainThread) { console.warn( @@ -98,12 +160,14 @@ function warnMainThread(method: string): void { * bus.send(Buffer.from('hello'), 1); * ``` */ -export class Bus implements Disposable { - #handle: NativeBinding; - #closed = false; - +export class Bus extends BusBase { private constructor(handle: NativeBinding) { - this.#handle = handle; + super(new NativeBusHandle(handle), { + defaultSpinThreshold: 10_000, + retryNullRecv: true, + nullRecvMessage: 'Bus.recv: interrupted while waiting for a message.', + copyData: (data) => Buffer.from(data), + }); } /** @@ -132,136 +196,4 @@ export class Bus implements Disposable { throw err; } } - - /** - * Signals that the consumer will never sleep. The producer skips the seq_cst fence - * and consumer_sleeping check on every flush. Use only on a dedicated SCHED_FIFO thread. - * Call immediately after listen/connect, before the first message. - */ - public setPollingMode(spinMode: 0 | 1): void { - this.#assertOpen(); - this.#handle.setPollingMode(spinMode); - } - - /** - * Binds the SHM pages to a specific NUMA node (MPOL_PREFERRED + MPOL_MF_MOVE). - * No-op on non-Linux platforms. Call immediately after listen/connect. - */ - public setNumaNode(nodeId: number): void { - this.#assertOpen(); - this.#handle.setNumaNode(nodeId); - } - - /** Publishes all pending unflushed TX messages to the consumer. */ - public flush(): void { - this.#assertOpen(); - this.#handle.flush(); - } - - /** Copies `data` into the ring buffer, commits, and flushes. */ - public send(data: Buffer | Uint8Array, typeId = 0): void { - this.#assertOpen(); - this.#handle.send(data, typeId); - } - - /** - * Blocks until a message is available, copies the payload, and returns it. - * Retries transparently on EINTR. - * - * @throws {PeerDeadError} If the bus has transitioned to TACHYON_STATE_FATAL_ERROR. - */ - public recv(spinThreshold = 10_000): { data: Buffer; typeId: number } { - this.#assertOpen(); - for (;;) { - if (this.#handle.getState() === 4) throw new PeerDeadError(); - const result = this.#handle.acquireRxBlocking(spinThreshold); - if (result === null) continue; // EINTR - retry - const copy = Buffer.from(result.data); - this.#handle.commitRx(); - return { data: copy, typeId: result.typeId }; - } - } - - /** - * Acquires an exclusive TX slot of `maxSize` bytes. - * Write into the slot via {@link TxGuard.bytes}, then commit or rollback. - * - * @throws {TachyonError} code `ERR_TACHYON_FULL` if the ring buffer is full. - */ - public acquireTx(maxSize: number): TxGuard { - this.#assertOpen(); - const buf = this.#handle.acquireTx(maxSize); - const ctrl: TxController = { - commitTx: (s, t) => { - this.#handle.commitTx(s, t); - }, - commitTxUnflushed: (s, t) => { - this.#handle.commitTxUnflushed(s, t); - }, - rollbackTx: () => { - this.#handle.rollbackTx(); - }, - }; - return new TxGuard(ctrl, buf); - } - - /** - * Blocks until a message is available and returns a zero-copy read lease. - * Returns `null` on EINTR caller decides whether to retry. - * - * @throws {PeerDeadError} If the bus has transitioned to TACHYON_STATE_FATAL_ERROR. - */ - public acquireRx(spinThreshold = 10_000): RxGuard | null { - this.#assertOpen(); - if (this.#handle.getState() === 4) throw new PeerDeadError(); - const result = this.#handle.acquireRxBlocking(spinThreshold); - if (result === null) return null; - const ctrl: RxController = { - commitRx: () => { - this.#handle.commitRx(); - }, - getState: () => this.#handle.getState(), - }; - return new RxGuard(ctrl, result.data, result.typeId, result.actualSize); - } - - /** - * Blocks until at least one message is available, then drains up to `maxMsgs` - * in a single native call. One native crossing amortizes per-message FFI cost. - * - * @throws {PeerDeadError} If the bus has transitioned to TACHYON_STATE_FATAL_ERROR. - */ - public drainBatch(maxMsgs: number, spinThreshold = 10_000): RxBatch { - this.#assertOpen(); - if (this.#handle.getState() === 4) throw new PeerDeadError(); - const raw = this.#handle.drainBatch(maxMsgs, spinThreshold); - const messages: RxMessage[] = raw.map((m) => ({ - data: m.data as unknown as RxSlot, - typeId: m.typeId, - size: m.size, - })); - const ctrl: BatchController = { - commitBatch: () => { - this.#handle.commitBatch(); - }, - getState: () => this.#handle.getState(), - }; - return new RxBatch(ctrl, messages); - } - - /** Closes the bus and unmaps shared memory. Safe to call multiple times. */ - public close(): void { - if (this.#closed) return; - this.#closed = true; - this.#handle.close(); - } - - /** Called automatically by the `using` keyword. */ - public [Symbol.dispose](): void { - this.close(); - } - - #assertOpen(): void { - if (this.#closed) throw new Error('Bus: this bus has been closed.'); - } } diff --git a/bindings/node/src/ts/bus_core.ts b/bindings/node/src/ts/bus_core.ts new file mode 100644 index 0000000..d052cc3 --- /dev/null +++ b/bindings/node/src/ts/bus_core.ts @@ -0,0 +1,228 @@ +import type { BatchController, RxMessage } from './batch.ts'; +import { RxBatch } from './batch.ts'; +import { PeerDeadError } from './error.ts'; +import type { RxController, RxSlot, TxController } from './guards.ts'; +import { RxGuard, TxGuard } from './guards.ts'; + +const TACHYON_STATE_FATAL_ERROR = 4; + +export interface RawRx { + readonly data: Buffer | Uint8Array; + readonly typeId: number; + readonly actualSize: number; +} + +export interface RawBatchMessage { + readonly data: Buffer | Uint8Array; + readonly typeId: number; + readonly size: number; +} + +export interface BusHandle { + close(): void; + + send(data: Buffer | Uint8Array, typeId?: number): void; + + acquireTx(maxSize: number): Buffer | Uint8Array; + + commitTx(actualSize: number, typeId: number): void; + + commitTxUnflushed(actualSize: number, typeId: number): void; + + rollbackTx(): void; + + flush(): void; + + acquireRx(spinThreshold?: number): RawRx | null; + + drainBatch?(maxMsgs: number, spinThreshold?: number): RawBatchMessage[]; + + commitRx(): void; + + commitBatch?(): void; + + setPollingMode(spinMode: number): void; + + setNumaNode(nodeId: number): void; + + getState(): number; +} + +interface BusBaseOptions { + readonly defaultSpinThreshold: number; + readonly retryNullRecv: boolean; + readonly nullRecvMessage: string; + readonly copyData: (data: Buffer | Uint8Array) => TRecv; +} + +/** + * Shared JS surface for the native Node addon and the browser WASM transport. + * Platform entrypoints only adapt their handle shape; guard lifecycle, recv + * copying, batching, close semantics, and API compatibility stay in one place. + */ +export abstract class BusBase implements Disposable { + #handle: BusHandle; + #closed = false; + #options: BusBaseOptions; + + protected constructor(handle: BusHandle, options: BusBaseOptions) { + this.#handle = handle; + this.#options = options; + } + + /** + * Signals that the consumer will never sleep. Native Node can use this to + * skip the seq_cst fence and consumer_sleeping check on flush; browser WASM + * has no futex sleep path, so the browser handle accepts this as a no-op. + */ + public setPollingMode(spinMode: 0 | 1): void { + this.#assertOpen(); + this.#handle.setPollingMode(spinMode); + } + + /** + * Binds native SHM pages to a specific NUMA node where supported. Browser + * WASM memory is page-local and cannot be NUMA-bound, so it is a no-op there. + */ + public setNumaNode(nodeId: number): void { + this.#assertOpen(); + this.#handle.setNumaNode(nodeId); + } + + /** Publishes all pending unflushed TX messages to the consumer. */ + public flush(): void { + this.#assertOpen(); + this.#handle.flush(); + } + + /** Copies `data` into the ring buffer, commits, and flushes. */ + public send(data: Buffer | Uint8Array, typeId = 0): void { + this.#assertOpen(); + this.#handle.send(data, typeId); + } + + /** + * Copies the next payload and returns it with its type discriminator. Node + * blocks and retries EINTR through the native handle; browser WASM is + * non-blocking and throws if no message is available. + * + * @throws {PeerDeadError} If the bus has transitioned to fatal error state. + */ + public recv(spinThreshold = this.#options.defaultSpinThreshold): { data: TRecv; typeId: number } { + this.#assertOpen(); + for (;;) { + if (this.#isFatal()) throw new PeerDeadError(); + const result = this.#handle.acquireRx(spinThreshold); + if (result === null) { + if (this.#options.retryNullRecv) continue; + throw new Error(this.#options.nullRecvMessage); + } + + const copy = this.#options.copyData(result.data); + this.#handle.commitRx(); + return { data: copy, typeId: result.typeId }; + } + } + + /** + * Acquires an exclusive TX slot of `maxSize` bytes. + * Write into the slot via {@link TxGuard.bytes}, then commit or rollback. + */ + public acquireTx(maxSize: number): TxGuard { + this.#assertOpen(); + const buf = this.#handle.acquireTx(maxSize); + const ctrl: TxController = { + commitTx: (s, t) => { + this.#handle.commitTx(s, t); + }, + commitTxUnflushed: (s, t) => { + this.#handle.commitTxUnflushed(s, t); + }, + rollbackTx: () => { + this.#handle.rollbackTx(); + }, + }; + return new TxGuard(ctrl, buf as unknown as Buffer); + } + + /** + * Acquires a zero-copy read lease. Node may block according to + * `spinThreshold`; browser WASM checks once and returns `null` when empty. + * + * @throws {PeerDeadError} If the bus has transitioned to fatal error state. + */ + public acquireRx(spinThreshold = this.#options.defaultSpinThreshold): RxGuard | null { + this.#assertOpen(); + if (this.#isFatal()) throw new PeerDeadError(); + const result = this.#handle.acquireRx(spinThreshold); + if (result === null) return null; + const ctrl: RxController = { + commitRx: () => { + this.#handle.commitRx(); + }, + getState: () => this.#handle.getState(), + }; + return new RxGuard(ctrl, result.data as unknown as Buffer, result.typeId, result.actualSize); + } + + /** + * Drains up to `maxMsgs` messages. Native Node uses one addon call to + * amortize FFI cost; browser WASM falls back to the same guard lifecycle + * with copied batch entries so all slots are released before returning. + */ + public drainBatch(maxMsgs: number, spinThreshold = this.#options.defaultSpinThreshold): RxBatch { + this.#assertOpen(); + if (this.#isFatal()) throw new PeerDeadError(); + + const raw = + this.#handle.drainBatch?.(maxMsgs, spinThreshold) ?? this.#drainBatchByAcquireRx(maxMsgs, spinThreshold); + const messages: RxMessage[] = raw.map((m) => ({ + data: m.data as unknown as RxSlot, + typeId: m.typeId, + size: m.size, + })); + const ctrl: BatchController = { + commitBatch: () => { + this.#handle.commitBatch?.(); + }, + getState: () => this.#handle.getState(), + }; + return new RxBatch(ctrl, messages); + } + + /** Closes the bus and releases the underlying platform handle. Safe to call multiple times. */ + public close(): void { + if (this.#closed) return; + this.#closed = true; + this.#handle.close(); + } + + /** Called automatically by the `using` keyword. */ + public [Symbol.dispose](): void { + this.close(); + } + + #drainBatchByAcquireRx(maxMsgs: number, spinThreshold: number): RawBatchMessage[] { + const messages: RawBatchMessage[] = []; + for (let i = 0; i < maxMsgs; i += 1) { + if (this.#isFatal()) throw new PeerDeadError(); + const result = this.#handle.acquireRx(spinThreshold); + if (result === null) break; + messages.push({ + data: this.#options.copyData(result.data), + typeId: result.typeId, + size: result.actualSize, + }); + this.#handle.commitRx(); + } + return messages; + } + + #assertOpen(): void { + if (this.#closed) throw new Error('Bus: this bus has been closed.'); + } + + #isFatal(): boolean { + return this.#handle.getState() === TACHYON_STATE_FATAL_ERROR; + } +} diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts index 85ab28d..38a3bfa 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts +++ b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts @@ -39,7 +39,6 @@ export class WasmBus { freeBytes(): number; isFatal(): boolean; constructor(capacity: number); - recvU32(): number; rollbackTx(): void; rxPtr(): number; rxSize(): number; @@ -48,8 +47,6 @@ export class WasmBus { * Copy-based send. Prefer `acquireTx` + direct JS view writes for hot paths. */ send(data: Uint8Array, type_id: number): void; - sendU32(value: number, type_id: number): void; - sendU32Unflushed(value: number, type_id: number): void; } export function makeTypeId(route: number, ty: number): number; @@ -58,10 +55,55 @@ export function msgType(type_id: number): number; export function routeId(type_id: number): number; +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_wasmbus_free: (a: number, b: number) => void; + readonly makeTypeId: (a: number, b: number) => number; + readonly msgType: (a: number) => number; + readonly routeId: (a: number) => number; + readonly wasmbus_acquireRx: (a: number) => [number, number, number]; + readonly wasmbus_acquireTx: (a: number, b: number) => [number, number, number]; + readonly wasmbus_availableBytes: (a: number) => number; + readonly wasmbus_capacity: (a: number) => number; + readonly wasmbus_commitRx: (a: number) => [number, number]; + readonly wasmbus_commitTx: (a: number, b: number, c: number) => [number, number]; + readonly wasmbus_commitTxUnflushed: (a: number, b: number, c: number) => [number, number]; + readonly wasmbus_dataPtr: (a: number) => number; + readonly wasmbus_flush: (a: number) => void; + readonly wasmbus_freeBytes: (a: number) => number; + readonly wasmbus_isFatal: (a: number) => number; + readonly wasmbus_new: (a: number) => [number, number, number]; + readonly wasmbus_rollbackTx: (a: number) => [number, number]; + readonly wasmbus_rxPtr: (a: number) => number; + readonly wasmbus_rxSize: (a: number) => number; + readonly wasmbus_rxTypeId: (a: number) => number; + readonly wasmbus_send: (a: number, b: number, c: number, d: number) => [number, number]; + readonly __wbindgen_externrefs: WebAssembly.Table; + readonly __externref_table_dealloc: (a: number) => void; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; + +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + /** - * Tiny Rust-side browser program used by the example page. + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. * - * It polls `inbound`, increments a little-endian `u32` payload, and publishes - * the result to `outbound`. Non-`u32` payloads are echoed unchanged. + * @returns {Promise} */ -export function tachyon_browser_echo_once(inbound: WasmBus, outbound: WasmBus): boolean; +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.js b/bindings/node/src/ts/wasm/tachyon_ipc.js index e066003..3b77a18 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc.js +++ b/bindings/node/src/ts/wasm/tachyon_ipc.js @@ -1,9 +1,363 @@ /* @ts-self-types="./tachyon_ipc.d.ts" */ -import * as wasm from "./tachyon_ipc_bg.wasm"; -import { __wbg_set_wasm } from "./tachyon_ipc_bg.js"; - -__wbg_set_wasm(wasm); -wasm.__wbindgen_start(); -export { - WasmBus, makeTypeId, msgType, routeId, tachyon_browser_echo_once -} from "./tachyon_ipc_bg.js"; + +/** + * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. + * + * This keeps the Tachyon message wire shape (`size`, `type_id`, + * `reserved_size`, 64-byte alignment, and skip marker) while replacing the + * native POSIX shared-memory transport with a WASM memory arena that page + * JavaScript can access through `WebAssembly.Memory`. + */ +export class WasmBus { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmBusFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasmbus_free(ptr, 0); + } + /** + * Acquire the next RX message, if one is visible. + * + * When this returns `true`, read `rxPtr`, `rxSize`, and `rxTypeId`, then + * call `commitRx` when done. + * @returns {boolean} + */ + acquireRx() { + const ret = wasm.wasmbus_acquireRx(this.__wbg_ptr); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] !== 0; + } + /** + * Reserve a TX slot and return a pointer to its payload bytes in WASM memory. + * + * JavaScript can create a zero-copy view with: + * `new Uint8Array(wasm.memory.buffer, ptr, maxSize)`. + * @param {number} max_size + * @returns {number} + */ + acquireTx(max_size) { + const ret = wasm.wasmbus_acquireTx(this.__wbg_ptr, max_size); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] >>> 0; + } + /** + * @returns {number} + */ + availableBytes() { + const ret = wasm.wasmbus_availableBytes(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + capacity() { + const ret = wasm.wasmbus_capacity(this.__wbg_ptr); + return ret >>> 0; + } + commitRx() { + const ret = wasm.wasmbus_commitRx(this.__wbg_ptr); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} actual_size + * @param {number} type_id + */ + commitTx(actual_size, type_id) { + const ret = wasm.wasmbus_commitTx(this.__wbg_ptr, actual_size, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @param {number} actual_size + * @param {number} type_id + */ + commitTxUnflushed(actual_size, type_id) { + const ret = wasm.wasmbus_commitTxUnflushed(this.__wbg_ptr, actual_size, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @returns {number} + */ + dataPtr() { + const ret = wasm.wasmbus_dataPtr(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Publish pending unflushed TX messages. + */ + flush() { + wasm.wasmbus_flush(this.__wbg_ptr); + } + /** + * @returns {number} + */ + freeBytes() { + const ret = wasm.wasmbus_freeBytes(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {boolean} + */ + isFatal() { + const ret = wasm.wasmbus_isFatal(this.__wbg_ptr); + return ret !== 0; + } + /** + * @param {number} capacity + */ + constructor(capacity) { + const ret = wasm.wasmbus_new(capacity); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + this.__wbg_ptr = ret[0]; + WasmBusFinalization.register(this, this.__wbg_ptr, this); + return this; + } + rollbackTx() { + const ret = wasm.wasmbus_rollbackTx(this.__wbg_ptr); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @returns {number} + */ + rxPtr() { + const ret = wasm.wasmbus_rxPtr(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + rxSize() { + const ret = wasm.wasmbus_rxSize(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + rxTypeId() { + const ret = wasm.wasmbus_rxTypeId(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Copy-based send. Prefer `acquireTx` + direct JS view writes for hot paths. + * @param {Uint8Array} data + * @param {number} type_id + */ + send(data, type_id) { + const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.wasmbus_send(this.__wbg_ptr, ptr0, len0, type_id); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } +} +if (Symbol.dispose) WasmBus.prototype[Symbol.dispose] = WasmBus.prototype.free; + +/** + * @param {number} route + * @param {number} ty + * @returns {number} + */ +export function makeTypeId(route, ty) { + const ret = wasm.makeTypeId(route, ty); + return ret >>> 0; +} + +/** + * @param {number} type_id + * @returns {number} + */ +export function msgType(type_id) { + const ret = wasm.msgType(type_id); + return ret; +} + +/** + * @param {number} type_id + * @returns {number} + */ +export function routeId(type_id) { + const ret = wasm.routeId(type_id); + return ret; +} +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_9c31b086c2b26051: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbindgen_cast_0000000000000001: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./tachyon_ipc_bg.js": import0, + }; +} + +const WasmBusFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_wasmbus_free(ptr, 1)); + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +let wasmModule, wasmInstance, wasm; +function __wbg_finalize_init(instance, module) { + wasmInstance = instance; + wasm = instance.exports; + wasmModule = module; + cachedUint8ArrayMemory0 = null; + wasm.__wbindgen_start(); + return wasm; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (module_or_path === undefined) { + module_or_path = new URL('tachyon_ipc_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync, __wbg_init as default }; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm index a4ca432d1945a8ed2dd9755e172d093e381fbbfc..23e17f38e61460f91df75c25869a315dcd92c95b 100644 GIT binary patch delta 6734 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 HPu~77_65M3 delta 8794 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 T)<;6Ge void; export const makeTypeId: (a: number, b: number) => number; export const msgType: (a: number) => number; export const routeId: (a: number) => number; -export const tachyon_browser_echo_once: (a: number, b: number) => [number, number, number]; export const wasmbus_acquireRx: (a: number) => [number, number, number]; export const wasmbus_acquireTx: (a: number, b: number) => [number, number, number]; export const wasmbus_availableBytes: (a: number) => number; @@ -18,14 +17,11 @@ export const wasmbus_flush: (a: number) => void; export const wasmbus_freeBytes: (a: number) => number; export const wasmbus_isFatal: (a: number) => number; export const wasmbus_new: (a: number) => [number, number, number]; -export const wasmbus_recvU32: (a: number) => [number, number, number]; export const wasmbus_rollbackTx: (a: number) => [number, number]; export const wasmbus_rxPtr: (a: number) => number; export const wasmbus_rxSize: (a: number) => number; export const wasmbus_rxTypeId: (a: number) => number; export const wasmbus_send: (a: number, b: number, c: number, d: number) => [number, number]; -export const wasmbus_sendU32: (a: number, b: number, c: number) => [number, number]; -export const wasmbus_sendU32Unflushed: (a: number, b: number, c: number) => [number, number]; export const __wbindgen_externrefs: WebAssembly.Table; export const __externref_table_dealloc: (a: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; diff --git a/bindings/node/test/browser_wasm.spec.mjs b/bindings/node/test/browser_wasm.spec.mjs new file mode 100644 index 0000000..88ce956 --- /dev/null +++ b/bindings/node/test/browser_wasm.spec.mjs @@ -0,0 +1,363 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { createServer } from 'node:http'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { existsSync, readdirSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, extname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PACKAGE_ROOT = resolve(__dirname, '..'); +const HOME = process.env.HOME ?? ''; + +const TEST_PAGE = ` + + +`; + +function chromiumPath() { + const candidates = [ + process.env.CHROMIUM_BIN, + '/usr/bin/chromium', + '/usr/bin/chromium-browser', + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + '/Applications/Chromium.app/Contents/MacOS/Chromium', + ...playwrightChromiumCandidates(), + ].filter(Boolean); + + for (const candidate of candidates) { + if (candidate !== undefined && existsSync(candidate)) return candidate; + } + throw new Error( + `Chromium not found. Set CHROMIUM_BIN, install /usr/bin/chromium, or install a Chrome/Chromium app.`, + ); +} + +function playwrightChromiumCandidates() { + const roots = [ + HOME === '' ? undefined : join(HOME, 'Library/Caches/ms-playwright'), + HOME === '' ? undefined : join(HOME, '.cache/ms-playwright'), + process.env.PLAYWRIGHT_BROWSERS_PATH, + ].filter(Boolean); + const candidates = []; + + for (const root of roots) { + if (root === undefined || !existsSync(root)) continue; + for (const entry of readdirSync(root, { withFileTypes: true })) { + if (!entry.isDirectory() || !entry.name.startsWith('chromium')) continue; + const dir = join(root, entry.name); + candidates.push( + join(dir, 'chrome-linux/chrome'), + join(dir, 'chrome-mac/Chromium.app/Contents/MacOS/Chromium'), + join(dir, 'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing'), + join(dir, 'chrome-headless-shell-linux64/chrome-headless-shell'), + join(dir, 'chrome-headless-shell-mac-arm64/chrome-headless-shell'), + ); + } + } + + return candidates; +} + +function mimeType(pathname) { + switch (extname(pathname)) { + case '.html': + return 'text/html; charset=utf-8'; + case '.js': + return 'text/javascript; charset=utf-8'; + case '.wasm': + return 'application/wasm'; + default: + return 'application/octet-stream'; + } +} + +async function startServer() { + const { readFile } = await import('node:fs/promises'); + const server = createServer(async (req, res) => { + try { + const url = new URL(req.url ?? '/', 'http://127.0.0.1'); + if (url.pathname === '/' || url.pathname === '/index.html') { + res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); + res.end(TEST_PAGE); + return; + } + + const filePath = resolve(PACKAGE_ROOT, `.${url.pathname}`); + if (!filePath.startsWith(PACKAGE_ROOT)) { + res.writeHead(403); + res.end('forbidden'); + return; + } + + const body = await readFile(filePath); + res.writeHead(200, { 'content-type': mimeType(filePath) }); + res.end(body); + } catch (error) { + res.writeHead(404); + res.end(String(error)); + } + }); + + await new Promise((resolveListen) => server.listen(0, '127.0.0.1', resolveListen)); + const address = server.address(); + assert.equal(typeof address, 'object'); + return { server, port: address.port }; +} + +async function waitForJson(url, timeoutMs = 10_000) { + const started = Date.now(); + for (;;) { + try { + const res = await fetch(url); + if (res.ok) return await res.json(); + } catch { + // Chromium may still be starting. + } + if (Date.now() - started > timeoutMs) throw new Error(`Timed out waiting for ${url}`); + await new Promise((resolveWait) => setTimeout(resolveWait, 50)); + } +} + +async function openPage(debugPort, url) { + const target = await fetch(`http://127.0.0.1:${debugPort}/json/new?${encodeURIComponent(url)}`, { + method: 'PUT', + }); + if (!target.ok) throw new Error(`Failed to open browser page: ${await target.text()}`); + return target.json(); +} + +async function runCdp(webSocketDebuggerUrl) { + const ws = new WebSocket(webSocketDebuggerUrl); + let nextId = 0; + const pending = new Map(); + + ws.addEventListener('message', (event) => { + const msg = JSON.parse(event.data); + if (msg.id === undefined || !pending.has(msg.id)) return; + const { resolve: resolveMessage, reject } = pending.get(msg.id); + pending.delete(msg.id); + if (msg.error !== undefined) reject(new Error(JSON.stringify(msg.error))); + else resolveMessage(msg.result); + }); + + await new Promise((resolveOpen, rejectOpen) => { + ws.addEventListener('open', resolveOpen, { once: true }); + ws.addEventListener('error', rejectOpen, { once: true }); + }); + + const call = (method, params = {}) => { + const id = ++nextId; + ws.send(JSON.stringify({ id, method, params })); + return new Promise((resolveCall, rejectCall) => pending.set(id, { resolve: resolveCall, reject: rejectCall })); + }; + + const evaluate = async (expression, timeout = 15_000) => { + const result = await call('Runtime.evaluate', { + expression, + awaitPromise: true, + returnByValue: true, + timeout, + }); + if (result.exceptionDetails !== undefined) throw new Error(JSON.stringify(result.exceptionDetails)); + return result.result.value; + }; + + await call('Runtime.enable'); + await evaluate(`new Promise((resolve, reject) => { + const started = performance.now(); + const tick = () => { + if (window.__tachyonBrowserDone) resolve(true); + else if (performance.now() - started > 10000) reject(new Error("browser wasm tests timed out")); + else setTimeout(tick, 25); + }; + tick(); +})`); + const results = JSON.parse(await evaluate('JSON.stringify(window.__tachyonBrowserResults)')); + ws.close(); + return results; +} + +const { server, port } = await startServer(); +const debugPort = 9333 + Math.floor(Math.random() * 1000); +const userDataDir = await mkdtemp(join(tmpdir(), 'tachyon-browser-wasm-')); +const browser = spawn(chromiumPath(), [ + '--headless=new', + '--disable-gpu', + '--no-first-run', + '--no-default-browser-check', + '--no-sandbox', + `--remote-debugging-port=${debugPort}`, + `--user-data-dir=${userDataDir}`, + `http://127.0.0.1:${port}/`, +]); + +browser.stderr.on('data', (chunk) => { + if (process.env.TACHYON_BROWSER_TEST_DEBUG === '1') process.stderr.write(chunk); +}); + +try { + await waitForJson(`http://127.0.0.1:${debugPort}/json/version`); + const target = await openPage(debugPort, `http://127.0.0.1:${port}/`); + const results = await runCdp(target.webSocketDebuggerUrl); + const failures = results.filter((result) => !result.ok); + for (const result of results) { + console.log(`${result.ok ? 'ok' : 'not ok'} - ${result.name}`); + } + if (failures.length > 0) { + throw new Error(failures.map((failure) => `${failure.name}: ${failure.message}`).join('\n\n')); + } +} finally { + browser.kill('SIGTERM'); + await new Promise((resolveExit) => { + browser.once('exit', resolveExit); + setTimeout(resolveExit, 2_000); + }); + server.close(); + await rm(userDataDir, { recursive: true, force: true }); +} diff --git a/bindings/rust/tachyon/src/lib.rs b/bindings/rust/tachyon/src/lib.rs index 5931d0f..b8419d5 100644 --- a/bindings/rust/tachyon/src/lib.rs +++ b/bindings/rust/tachyon/src/lib.rs @@ -14,9 +14,7 @@ pub use error::TachyonError; pub use rpc::{RpcBus, RpcRxGuard, RpcTxGuard}; pub use type_id::{make_type_id, msg_type, route_id}; #[cfg(target_arch = "wasm32")] -pub use wasm::{ - WasmBus, tachyon_browser_echo_once, wasm_make_type_id, wasm_msg_type, wasm_route_id, -}; +pub use wasm::{WasmBus, wasm_make_type_id, wasm_msg_type, wasm_route_id}; #[cfg(test)] mod tests { diff --git a/bindings/rust/tachyon/src/wasm.rs b/bindings/rust/tachyon/src/wasm.rs index a59c8ab..1fb2e8b 100644 --- a/bindings/rust/tachyon/src/wasm.rs +++ b/bindings/rust/tachyon/src/wasm.rs @@ -103,16 +103,6 @@ impl WasmBus { self.commit_tx(data.len() as u32, type_id) } - #[wasm_bindgen(js_name = sendU32)] - pub fn send_u32(&mut self, value: u32, type_id: u32) -> Result<(), JsValue> { - self.send_u32_inner(value, type_id, true) - } - - #[wasm_bindgen(js_name = sendU32Unflushed)] - pub fn send_u32_unflushed(&mut self, value: u32, type_id: u32) -> Result<(), JsValue> { - self.send_u32_inner(value, type_id, false) - } - /// Reserve a TX slot and return a pointer to its payload bytes in WASM memory. /// /// JavaScript can create a zero-copy view with: @@ -241,31 +231,9 @@ impl WasmBus { Ok(()) } - - #[wasm_bindgen(js_name = recvU32)] - pub fn recv_u32(&mut self) -> Result { - if !self.acquire_rx()? { - return Err(js_error("no u32 message available")); - } - if self.rx_actual_size != 4 { - return Err(js_error("pending message is not a u32 payload")); - } - - let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&self.arena[self.rx_payload_offset..self.rx_payload_offset + 4]); - let value = u32::from_le_bytes(bytes); - self.commit_rx()?; - Ok(value) - } } impl WasmBus { - fn send_u32_inner(&mut self, value: u32, type_id: u32, flush: bool) -> Result<(), JsValue> { - let payload_offset = self.acquire_tx_offset(4)?; - self.arena[payload_offset..payload_offset + 4].copy_from_slice(&value.to_le_bytes()); - self.commit_tx_inner(4, type_id, flush) - } - fn acquire_tx_offset(&mut self, max_size: usize) -> Result { if self.fatal { return Err(js_error("WasmBus is in fatal state")); @@ -346,44 +314,6 @@ impl WasmBus { } } -/// Tiny Rust-side browser program used by the example page. -/// -/// It polls `inbound`, increments a little-endian `u32` payload, and publishes -/// the result to `outbound`. Non-`u32` payloads are echoed unchanged. -#[wasm_bindgen] -pub fn tachyon_browser_echo_once( - inbound: &mut WasmBus, - outbound: &mut WasmBus, -) -> Result { - if !inbound.acquire_rx()? { - return Ok(false); - } - - let type_id = inbound.rx_type_id; - let actual_size = inbound.rx_actual_size; - let inbound_offset = inbound.rx_payload_offset; - let outbound_offset = outbound.acquire_tx_offset(actual_size)?; - - if actual_size == 4 { - let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&inbound.arena[inbound_offset..inbound_offset + 4]); - let value = u32::from_le_bytes(bytes).wrapping_add(1); - outbound.arena[outbound_offset..outbound_offset + 4].copy_from_slice(&value.to_le_bytes()); - } else { - outbound.arena[outbound_offset..outbound_offset + actual_size] - .copy_from_slice(&inbound.arena[inbound_offset..inbound_offset + actual_size]); - } - - outbound.commit_tx_inner( - actual_size, - make_type_id(route_id(type_id).wrapping_add(1), msg_type(type_id)), - true, - )?; - inbound.commit_rx()?; - - Ok(true) -} - #[wasm_bindgen(js_name = makeTypeId)] pub fn wasm_make_type_id(route: u16, ty: u16) -> u32 { make_type_id(route, ty) diff --git a/examples/browser_wasm/README.md b/examples/browser_wasm/README.md index 84f0e94..f627e7c 100644 --- a/examples/browser_wasm/README.md +++ b/examples/browser_wasm/README.md @@ -5,6 +5,9 @@ binary payloads directly into a `WasmBus` TX slot in WebAssembly memory, a small Rust WASM function polls the inbound ring and replies on a second ring, and JavaScript reads the reply from WASM memory. +The Rust echo function lives in `examples/browser_wasm/rust`; the reusable +browser transport stays in the Tachyon bindings. + The browser build does not use POSIX shared memory or UNIX sockets. Those APIs are unavailable in browsers, so the WASM path is a page-local Tachyon ring with the same 64-byte message header, alignment, `type_id`, and skip-marker rules. diff --git a/examples/browser_wasm/main.js b/examples/browser_wasm/main.js index 95585a3..5f3df05 100644 --- a/examples/browser_wasm/main.js +++ b/examples/browser_wasm/main.js @@ -4,7 +4,7 @@ import init, { msgType, routeId, tachyon_browser_echo_once, -} from "./pkg/tachyon_ipc.js"; +} from "./pkg/tachyon_browser_wasm_example.js"; const CAPACITY = 1 << 20; const BATCH_SIZE = 4096; @@ -36,7 +36,9 @@ function appendLog(line) { } function writeU32ToBus(bus, value, typeId) { - bus.sendU32(value >>> 0, typeId); + const ptr = bus.acquireTx(4); + new DataView(memory().buffer, ptr, 4).setUint32(0, value >>> 0, true); + bus.commitTx(4, typeId); } function readU32FromBus(bus) { @@ -44,7 +46,10 @@ function readU32FromBus(bus) { const ptr = bus.rxPtr(); const size = bus.rxSize(); const typeId = bus.rxTypeId(); - const value = size === 4 ? new DataView(memory().buffer, ptr, 4).getUint32(0, true) : null; + const value = + size === 4 + ? new DataView(memory().buffer, ptr, 4).getUint32(0, true) + : null; bus.commitRx(); return { value, size, typeId }; } @@ -65,15 +70,22 @@ function pingRust(value) { } function pingRustFast(value) { - jsToRust.sendU32(value >>> 0, typeCounter); + writeU32ToBus(jsToRust, value, typeCounter); if (!tachyon_browser_echo_once(jsToRust, rustToJs)) { throw new Error("Rust WASM program did not receive the JS message"); } - return rustToJs.recvU32(); + const reply = readU32FromBus(rustToJs); + if (!reply) { + throw new Error("JS did not receive the Rust WASM reply"); + } + return reply.value; } function percentile(sorted, pct) { - const idx = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * pct)); + const idx = Math.min( + sorted.length - 1, + Math.floor((sorted.length - 1) * pct), + ); return sorted[idx]; } @@ -97,7 +109,10 @@ function setBenchRows(rows) { } async function runBench() { - const iterations = Math.max(1000, Number.parseInt(els.iterations.value, 10) || 1000000); + const iterations = Math.max( + 1000, + Number.parseInt(els.iterations.value, 10) || 1000000, + ); const warmup = Math.min(10000, Math.floor(iterations / 10)); els.bench.disabled = true; @@ -124,14 +139,19 @@ async function runBench() { const throughput = iterations / (totalMs / 1000); setBenchRows([ ["Payload", "4 bytes u32"], - ["Samples", `${samples.length.toLocaleString()} batch averages x ${BATCH_SIZE}`], + [ + "Samples", + `${samples.length.toLocaleString()} batch averages x ${BATCH_SIZE}`, + ], ["Direct doorbell p50", formatNs(percentile(samples, 0.5))], ["Direct doorbell p90", formatNs(percentile(samples, 0.9))], ["Direct doorbell p99", formatNs(percentile(samples, 0.99))], ["Direct doorbell mean", formatNs((totalMs * 1_000_000) / iterations)], ["Throughput", `${(throughput / 1000).toFixed(1)} K RTT/sec`], ]); - appendLog(`browser bench completed: ${(throughput / 1000).toFixed(1)} K RTT/sec`); + appendLog( + `browser bench completed: ${(throughput / 1000).toFixed(1)} K RTT/sec`, + ); els.bench.disabled = false; } diff --git a/examples/browser_wasm/package-lock.json b/examples/browser_wasm/package-lock.json deleted file mode 100644 index 4426179..0000000 --- a/examples/browser_wasm/package-lock.json +++ /dev/null @@ -1,1204 +0,0 @@ -{ - "name": "tachyon-browser-wasm-example", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "tachyon-browser-wasm-example", - "devDependencies": { - "vite": "latest", - "wasm-pack": "latest" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.128.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", - "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", - "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", - "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", - "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-install": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.2.tgz", - "integrity": "sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^0.26.1", - "rimraf": "^3.0.2", - "tar": "^6.1.11" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", - "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.128.0", - "@rolldown/pluginutils": "1.0.0-rc.18" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-x64": "1.0.0-rc.18", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/vite": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", - "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.0-rc.18", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/wasm-pack": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.14.0.tgz", - "integrity": "sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "binary-install": "^1.1.2" - }, - "bin": { - "wasm-pack": "run.js" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/examples/browser_wasm/package.json b/examples/browser_wasm/package.json index 1cc9019..a51707e 100644 --- a/examples/browser_wasm/package.json +++ b/examples/browser_wasm/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../../bindings/rust/tachyon --target web --release --out-dir ../../../examples/browser_wasm/pkg", + "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build rust --target web --release --out-dir ../pkg", "dev": "vite --host 127.0.0.1", "build": "npm run build:wasm && vite build", "bench:native": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) cargo run --release --manifest-path ../../bindings/rust/tachyon/Cargo.toml --example bench_ipc" diff --git a/examples/browser_wasm/rust/Cargo.toml b/examples/browser_wasm/rust/Cargo.toml new file mode 100644 index 0000000..59ceff6 --- /dev/null +++ b/examples/browser_wasm/rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tachyon-browser-wasm-example" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +tachyon-ipc = { path = "../../../bindings/rust/tachyon" } +wasm-bindgen = "0.2" diff --git a/examples/browser_wasm/rust/src/lib.rs b/examples/browser_wasm/rust/src/lib.rs new file mode 100644 index 0000000..49dcf69 --- /dev/null +++ b/examples/browser_wasm/rust/src/lib.rs @@ -0,0 +1,44 @@ +use tachyon_ipc::{WasmBus, make_type_id, msg_type, route_id}; +use wasm_bindgen::prelude::*; + +/// Tiny Rust-side browser program used by this example page. +/// +/// It checks `inbound` once, increments a little-endian `u32` payload, and +/// publishes the result to `outbound`. The reusable transport logic stays in +/// the Tachyon bindings; this file owns only the page-specific demo behavior. +#[wasm_bindgen] +pub fn tachyon_browser_echo_once( + inbound: &mut WasmBus, + outbound: &mut WasmBus, +) -> Result { + if !inbound.acquire_rx()? { + return Ok(false); + } + + let type_id = inbound.rx_type_id(); + let actual_size = inbound.rx_size(); + if actual_size != 4 { + inbound.commit_rx()?; + return Err(JsValue::from_str( + "example echo expects a 4-byte u32 payload", + )); + } + + let inbound_ptr = inbound.rx_ptr() as *const u8; + let outbound_ptr = outbound.acquire_tx(actual_size)? as *mut u8; + + unsafe { + let inbound_bytes = std::slice::from_raw_parts(inbound_ptr, actual_size as usize); + let outbound_bytes = std::slice::from_raw_parts_mut(outbound_ptr, actual_size as usize); + let value = u32::from_le_bytes(inbound_bytes.try_into().unwrap()).wrapping_add(1); + outbound_bytes.copy_from_slice(&value.to_le_bytes()); + } + + outbound.commit_tx( + actual_size, + make_type_id(route_id(type_id).wrapping_add(1), msg_type(type_id)), + )?; + inbound.commit_rx()?; + + Ok(true) +} From fdcf19272137294b165983855ce08b6f918a97fb Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sat, 9 May 2026 12:57:05 -0700 Subject: [PATCH 03/23] Tighten browser WASM generated bindings --- .github/workflows/ci.yml | 3 + bindings/node/README.md | 2 + bindings/node/package-lock.json | 317 +++++++++++++++++- bindings/node/package.json | 7 +- bindings/node/scripts/stamp-wasm-bindings.mjs | 35 ++ bindings/node/src/ts/browser.ts | 4 +- bindings/node/src/ts/wasm/tachyon_ipc.d.ts | 19 +- bindings/node/src/ts/wasm/tachyon_ipc.js | 58 +--- bindings/node/src/ts/wasm/tachyon_ipc_bg.js | 4 + bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm | Bin 21072 -> 20544 bytes .../node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts | 9 +- bindings/rust/tachyon/src/lib.rs | 2 +- bindings/rust/tachyon/src/wasm.rs | 37 -- examples/browser_wasm/main.js | 15 +- 14 files changed, 383 insertions(+), 129 deletions(-) create mode 100644 bindings/node/scripts/stamp-wasm-bindings.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56576a3..92e94ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -668,6 +668,9 @@ jobs: working-directory: bindings/node env: CHROMIUM_BIN: /usr/bin/chromium + # Do not run build:wasm in this test job. The generated WASM bindings are + # committed, and CI should validate the committed artifacts instead of + # regenerating them and potentially masking a stale generated-code diff. run: npm run test:browser nodejs_macos: diff --git a/bindings/node/README.md b/bindings/node/README.md index a25c23b..75c7e7d 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -104,6 +104,8 @@ Browser differences: - `setNumaNode()` and `setPollingMode()` are no-ops in browsers. - `Buffer` is not a browser primitive; returned data is a `Uint8Array`. - Native cross-process IPC still requires Node.js or another native binding. +- The browser build uses wasm32 for now, so WASM pointers, capacities, and slot sizes are `u32`-bounded; it can move to + wasm64/Memory64 later if a single linear-memory arena above 4 GiB becomes necessary. ## API diff --git a/bindings/node/package-lock.json b/bindings/node/package-lock.json index be458d5..246f204 100644 --- a/bindings/node/package-lock.json +++ b/bindings/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tachyon-ipc/core", - "version": "0.3.5", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tachyon-ipc/core", - "version": "0.3.5", + "version": "0.5.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -21,7 +21,8 @@ "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1" + "typescript-eslint": "^8.58.1", + "wasm-pack": "^0.14.0" }, "engines": { "node": ">=20.0.0" @@ -1028,6 +1029,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1038,6 +1049,95 @@ "node": "18 || 20 || >=22" } }, + "node_modules/binary-install": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.2.tgz", + "integrity": "sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^0.26.1", + "rimraf": "^3.0.2", + "tar": "^6.1.11" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/binary-install/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/binary-install/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/binary-install/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/binary-install/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/binary-install/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/binary-install/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", @@ -1249,6 +1349,13 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1676,6 +1783,27 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1708,6 +1836,46 @@ "node": ">=14.14" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1861,6 +2029,25 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -2120,6 +2307,19 @@ "node": ">= 18" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mocha": { "version": "11.7.5", "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", @@ -2220,6 +2420,16 @@ "dev": true, "license": "MIT" }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2287,6 +2497,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2440,6 +2660,76 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2805,6 +3095,20 @@ "dev": true, "license": "MIT" }, + "node_modules/wasm-pack": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.14.0.tgz", + "integrity": "sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "binary-install": "^1.1.2" + }, + "bin": { + "wasm-pack": "run.js" + } + }, "node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -2933,6 +3237,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/bindings/node/package.json b/bindings/node/package.json index ba9e342..a55240c 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -24,9 +24,9 @@ "node": ">=20.0.0" }, "scripts": { - "build": "npm run build:native && npm run build:ts && npm run copy:wasm", + "build": "npm run build:wasm && npm run build:native && npm run build:ts && npm run copy:wasm", "build:native": "cmake-js compile", - "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target web --release --out-dir ../../node/src/ts/wasm", + "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target web --release --out-dir ../../node/src/ts/wasm && node scripts/stamp-wasm-bindings.mjs", "build:ts": "tsc", "copy:wasm": "mkdir -p dist/wasm && cp src/ts/wasm/tachyon_ipc.js src/ts/wasm/tachyon_ipc_bg.js src/ts/wasm/tachyon_ipc_bg.wasm src/ts/wasm/*.d.ts dist/wasm/", "clean": "rm -rf build dist", @@ -50,6 +50,7 @@ "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1" + "typescript-eslint": "^8.58.1", + "wasm-pack": "^0.14.0" } } diff --git a/bindings/node/scripts/stamp-wasm-bindings.mjs b/bindings/node/scripts/stamp-wasm-bindings.mjs new file mode 100644 index 0000000..67c0c21 --- /dev/null +++ b/bindings/node/scripts/stamp-wasm-bindings.mjs @@ -0,0 +1,35 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const GENERATED_HEADER = [ + '// Generated by wasm-pack. Do not edit by hand.', + '// Regenerate with: npm run build:wasm', + '', +].join('\n'); + +const SELF_TYPES_RE = /^\/\* @ts-self-types=.*?\*\/\n/u; +const GENERATED_HEADER_RE = + /^(?:\/\* @ts-self-types=.*?\*\/\n)?\/\/ Generated by wasm-pack\. Do not edit by hand\.\n\/\/ Regenerate with: npm run build:wasm\n\n/u; + +const generatedTextFiles = [ + 'src/ts/wasm/tachyon_ipc.js', + 'src/ts/wasm/tachyon_ipc_bg.js', + 'src/ts/wasm/tachyon_ipc.d.ts', + 'src/ts/wasm/tachyon_ipc_bg.wasm.d.ts', +]; + +for (const relativePath of generatedTextFiles) { + const filePath = resolve(relativePath); + let body = await readFile(filePath, 'utf8'); + body = body.replace(GENERATED_HEADER_RE, (match) => { + const selfTypes = match.match(SELF_TYPES_RE)?.[0] ?? ''; + return selfTypes; + }); + + const selfTypes = body.match(SELF_TYPES_RE)?.[0] ?? ''; + if (selfTypes !== '') { + body = body.slice(selfTypes.length); + } + + await writeFile(filePath, `${selfTypes}${GENERATED_HEADER}${body}`); +} diff --git a/bindings/node/src/ts/browser.ts b/bindings/node/src/ts/browser.ts index 89127fc..0c517a5 100644 --- a/bindings/node/src/ts/browser.ts +++ b/bindings/node/src/ts/browser.ts @@ -1,6 +1,6 @@ import type { BusHandle, RawRx } from './bus_core.ts'; import { BusBase } from './bus_core.ts'; -import initWasm, { makeTypeId, msgType, routeId, WasmBus } from './wasm/tachyon_ipc.ts'; +import initWasm, { WasmBus } from './wasm/tachyon_ipc.ts'; interface WasmRuntime { readonly memory: { @@ -145,4 +145,4 @@ export { RxBatch } from './batch.ts'; export type { RxMessage } from './batch.ts'; export { TxGuard, RxGuard } from './guards.ts'; export type { TxSlot, RxSlot } from './guards.ts'; -export { makeTypeId, msgType, routeId }; +export { makeTypeId, msgType, routeId } from './type_id.ts'; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts index 38a3bfa..c64a801 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts +++ b/bindings/node/src/ts/wasm/tachyon_ipc.d.ts @@ -1,3 +1,5 @@ +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm /* tslint:disable */ /* eslint-disable */ @@ -26,17 +28,13 @@ export class WasmBus { * `new Uint8Array(wasm.memory.buffer, ptr, maxSize)`. */ acquireTx(max_size: number): number; - availableBytes(): number; - capacity(): number; commitRx(): void; commitTx(actual_size: number, type_id: number): void; commitTxUnflushed(actual_size: number, type_id: number): void; - dataPtr(): number; /** * Publish pending unflushed TX messages. */ flush(): void; - freeBytes(): number; isFatal(): boolean; constructor(capacity: number); rollbackTx(): void; @@ -49,30 +47,17 @@ export class WasmBus { send(data: Uint8Array, type_id: number): void; } -export function makeTypeId(route: number, ty: number): number; - -export function msgType(type_id: number): number; - -export function routeId(type_id: number): number; - export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; readonly __wbg_wasmbus_free: (a: number, b: number) => void; - readonly makeTypeId: (a: number, b: number) => number; - readonly msgType: (a: number) => number; - readonly routeId: (a: number) => number; readonly wasmbus_acquireRx: (a: number) => [number, number, number]; readonly wasmbus_acquireTx: (a: number, b: number) => [number, number, number]; - readonly wasmbus_availableBytes: (a: number) => number; - readonly wasmbus_capacity: (a: number) => number; readonly wasmbus_commitRx: (a: number) => [number, number]; readonly wasmbus_commitTx: (a: number, b: number, c: number) => [number, number]; readonly wasmbus_commitTxUnflushed: (a: number, b: number, c: number) => [number, number]; - readonly wasmbus_dataPtr: (a: number) => number; readonly wasmbus_flush: (a: number) => void; - readonly wasmbus_freeBytes: (a: number) => number; readonly wasmbus_isFatal: (a: number) => number; readonly wasmbus_new: (a: number) => [number, number, number]; readonly wasmbus_rollbackTx: (a: number) => [number, number]; diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.js b/bindings/node/src/ts/wasm/tachyon_ipc.js index 3b77a18..f99a906 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc.js +++ b/bindings/node/src/ts/wasm/tachyon_ipc.js @@ -1,4 +1,6 @@ /* @ts-self-types="./tachyon_ipc.d.ts" */ +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm /** * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. @@ -48,20 +50,6 @@ export class WasmBus { } return ret[0] >>> 0; } - /** - * @returns {number} - */ - availableBytes() { - const ret = wasm.wasmbus_availableBytes(this.__wbg_ptr); - return ret >>> 0; - } - /** - * @returns {number} - */ - capacity() { - const ret = wasm.wasmbus_capacity(this.__wbg_ptr); - return ret >>> 0; - } commitRx() { const ret = wasm.wasmbus_commitRx(this.__wbg_ptr); if (ret[1]) { @@ -88,26 +76,12 @@ export class WasmBus { throw takeFromExternrefTable0(ret[0]); } } - /** - * @returns {number} - */ - dataPtr() { - const ret = wasm.wasmbus_dataPtr(this.__wbg_ptr); - return ret >>> 0; - } /** * Publish pending unflushed TX messages. */ flush() { wasm.wasmbus_flush(this.__wbg_ptr); } - /** - * @returns {number} - */ - freeBytes() { - const ret = wasm.wasmbus_freeBytes(this.__wbg_ptr); - return ret >>> 0; - } /** * @returns {boolean} */ @@ -169,34 +143,6 @@ export class WasmBus { } } if (Symbol.dispose) WasmBus.prototype[Symbol.dispose] = WasmBus.prototype.free; - -/** - * @param {number} route - * @param {number} ty - * @returns {number} - */ -export function makeTypeId(route, ty) { - const ret = wasm.makeTypeId(route, ty); - return ret >>> 0; -} - -/** - * @param {number} type_id - * @returns {number} - */ -export function msgType(type_id) { - const ret = wasm.msgType(type_id); - return ret; -} - -/** - * @param {number} type_id - * @returns {number} - */ -export function routeId(type_id) { - const ret = wasm.routeId(type_id); - return ret; -} function __wbg_get_imports() { const import0 = { __proto__: null, diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js index 0375a26..619eb42 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js +++ b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js @@ -1,3 +1,7 @@ +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm /** * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. * diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm index 23e17f38e61460f91df75c25869a315dcd92c95b..50e983351b639b67abbfd388382fd48a586817f8 100644 GIT binary patch delta 1276 zcmZWnO>7%g5T4mxHb1+rpW}u!iQ}y8xN+;Gb=QewC-Kk5b)ZEJp$%6YCvmA;$Dy{< zm`1`aAV94uReA?1(FzVx4^V_)1qTEo5(j=R^uPrP1Q#x-LVy6FN=VGQaR_0hoj2b% z-_Fd=o4Xsh`37EhcYsi@&to0tL991Cd^aRxBAJk-HfYBd06PM-vlftKNs?t*kPuoB z+ayPaqAIG*CP_dK3PcOo+G_cH9o^zR!O3r;yY5)aRhAc4E7{!Q`PD)>f3gPqcXM+! z7+_7^;>z-Jp-QsgE{aqKy8o-1t36pdU0ki4$uGg6tr=@D@UY#&b*prtGM%gDim*>< zN=o@P2y`@2d8Jrf$St0u3J15dlxwrqGIZ|Q7S&D_F6NfGbG?9n}ffvT(+8_#co;bIT) zLyGbHWx^q*o1c2FirCK|_ov8S_s?U$`K$j+RHI(ZNGU@yR2Ju-4ZdcJD4LkDiJE92 zi}01eu*0Pa3X6ea{O~d*OR@>`?SNMb9wFw=6=J?6!z^^qLN~8&p&c=Pf9MuQ`HkTx zFvNcvK1O!r(8s)R_?qMc#q;sr`caOZV1<``H zN1ki3q5}EB(O&E_9~s?`=;ftIn(TX#m&nG(M*p2&D~p>K#|Du8^7ZHe4D&CdFH*NN zvA590@5jQ;p2Oq6P<2PbCrgIwEOYS_69Zc@b8X^Lx>mk9IZBuN=cH_bzvGjXU_23N zCb*FJgosGe+a#V!Ug!zY@R3$v)4KgQ(^XWA&%uybl>eOEz&PirU0Ky}N9My*2dREG-P^2xDIM#LC`vbCU4wuC zUX)UnBWc*!ps&*>+;QsfBiakYXWX^cqaPF%G%&uSqO9Lc9$iKB@%J-6#;i81G>sH2 z%>T+{?Ynl1pPnAGPk>?rDntlJ!WSVHg;vX90(!?x_W|)|0jERf?s+oWJmq!24nCzeV4tnjfdC9$d@AKaK zym#OG+`$Lr?ml_Dy^)kjc>$Pmd4ZH+0oZc+rq4*q({a{d1`9C(*a(0{Gy!X1Sp(j9 zF$MxALd*txBQNlR)oL&RUW6ftGo+zG3s@F_Gcy~csVNJ)4F;Bg-Dt*n^6u^d@TPlg zn=zwivbh}w8WV}q;!2{Vb8_rT3Jl1v7LgNhbKfmkyJL6WU8?5~#{LP*(13F9dD*!2D-rJy`h$brMEBg^Sxeid43>n!6d9ZhYjb*1vt*p~(; zuXnAhCFpI`qg*zfUR086IPh$ZIJd10YCcv{d`G91+rE%`L4}jGEpyw|Lu(gwro6ho z1l{xo<3Dl2t{rx2eQ8BqPh^yII-7(J`Um4|n;tl29SgZfWnbs9<4+FyU6TS~EPuX9 z;uQ}ow2@%Nck(HMj~Cl}7!sr(Ntawf7b6qF$jkdLAAsx>k75Ha|9uH#pBSvny7pL; z2Y88-fI2%*(fb_^;-Sr*3&c|?b$&;Lh?~T@sLaWN7^CxD*R5e*V&hg;Vr3$R=|5e4 zc1d7(aU6L0*VmDDLX1}YUN_ehK;)Avi2RI3)S}kBy2rfnfFXMFKre3DW1rm|iAOjM@61V0iGN41AcC5MK&}HTw zHe8DbUYwxCvu|--z^~HH{uZR0F(2W;C##6m?K~RLQfEf)hj{B`>;319Ro*-O7x1_e zzOY@ueHh%QUZ$bk|DYi{=dK*zFBc@a*8I;wExjnE7Vj{Z0rK-o2fjapzE8^fZx zv7p|2dF&1XGZD8AY(%!&hENOy#d)CVG9m^A!pq-+%!!d}j=xQ+uN z(O%?xKI+i<_M*4gpvy=nqGvF%Dc2I$&7?=NAtFC`z;0qzEaZ3*V@-8f?PWM|nBJdE*d7ryU7Z@XMS-`1 zeETvE`2p}h_;l)X)^65@Xl%Sm^Te%6a^@@6z6=1z@qU*P51#=VnCl^<6=m)o`yX>^ BeLMgF diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts index e6f2a4c..7a31752 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts +++ b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts @@ -1,20 +1,15 @@ +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; export const __wbg_wasmbus_free: (a: number, b: number) => void; -export const makeTypeId: (a: number, b: number) => number; -export const msgType: (a: number) => number; -export const routeId: (a: number) => number; export const wasmbus_acquireRx: (a: number) => [number, number, number]; export const wasmbus_acquireTx: (a: number, b: number) => [number, number, number]; -export const wasmbus_availableBytes: (a: number) => number; -export const wasmbus_capacity: (a: number) => number; export const wasmbus_commitRx: (a: number) => [number, number]; export const wasmbus_commitTx: (a: number, b: number, c: number) => [number, number]; export const wasmbus_commitTxUnflushed: (a: number, b: number, c: number) => [number, number]; -export const wasmbus_dataPtr: (a: number) => number; export const wasmbus_flush: (a: number) => void; -export const wasmbus_freeBytes: (a: number) => number; export const wasmbus_isFatal: (a: number) => number; export const wasmbus_new: (a: number) => [number, number, number]; export const wasmbus_rollbackTx: (a: number) => [number, number]; diff --git a/bindings/rust/tachyon/src/lib.rs b/bindings/rust/tachyon/src/lib.rs index b8419d5..dc45fe3 100644 --- a/bindings/rust/tachyon/src/lib.rs +++ b/bindings/rust/tachyon/src/lib.rs @@ -14,7 +14,7 @@ pub use error::TachyonError; pub use rpc::{RpcBus, RpcRxGuard, RpcTxGuard}; pub use type_id::{make_type_id, msg_type, route_id}; #[cfg(target_arch = "wasm32")] -pub use wasm::{WasmBus, wasm_make_type_id, wasm_msg_type, wasm_route_id}; +pub use wasm::WasmBus; #[cfg(test)] mod tests { diff --git a/bindings/rust/tachyon/src/wasm.rs b/bindings/rust/tachyon/src/wasm.rs index 1fb2e8b..d87d72e 100644 --- a/bindings/rust/tachyon/src/wasm.rs +++ b/bindings/rust/tachyon/src/wasm.rs @@ -1,7 +1,5 @@ use wasm_bindgen::prelude::*; -use crate::type_id::{make_type_id, msg_type, route_id}; - const MSG_ALIGNMENT: usize = 64; const HEADER_SIZE: usize = MSG_ALIGNMENT; const ALIGN_MASK: usize = MSG_ALIGNMENT - 1; @@ -71,26 +69,6 @@ impl WasmBus { }) } - pub fn capacity(&self) -> u32 { - self.capacity as u32 - } - - #[wasm_bindgen(js_name = dataPtr)] - pub fn data_ptr(&self) -> u32 { - self.arena.as_ptr() as u32 - } - - #[wasm_bindgen(js_name = availableBytes)] - pub fn available_bytes(&self) -> u32 { - self.published_head.saturating_sub(self.tail) as u32 - } - - #[wasm_bindgen(js_name = freeBytes)] - pub fn free_bytes(&self) -> u32 { - self.capacity - .saturating_sub(self.head.saturating_sub(self.tail)) as u32 - } - #[wasm_bindgen(js_name = isFatal)] pub fn is_fatal(&self) -> bool { self.fatal @@ -313,18 +291,3 @@ impl WasmBus { self.arena[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); } } - -#[wasm_bindgen(js_name = makeTypeId)] -pub fn wasm_make_type_id(route: u16, ty: u16) -> u32 { - make_type_id(route, ty) -} - -#[wasm_bindgen(js_name = routeId)] -pub fn wasm_route_id(type_id: u32) -> u16 { - route_id(type_id) -} - -#[wasm_bindgen(js_name = msgType)] -pub fn wasm_msg_type(type_id: u32) -> u16 { - msg_type(type_id) -} diff --git a/examples/browser_wasm/main.js b/examples/browser_wasm/main.js index 5f3df05..58fcf8c 100644 --- a/examples/browser_wasm/main.js +++ b/examples/browser_wasm/main.js @@ -1,8 +1,5 @@ import init, { WasmBus, - makeTypeId, - msgType, - routeId, tachyon_browser_echo_once, } from "./pkg/tachyon_browser_wasm_example.js"; @@ -26,6 +23,18 @@ let jsToRust; let rustToJs; let typeCounter; +function makeTypeId(route, msgType) { + return ((route & 0xffff) << 16) | (msgType & 0xffff); +} + +function routeId(typeId) { + return (typeId >>> 16) & 0xffff; +} + +function msgType(typeId) { + return typeId & 0xffff; +} + function memory() { return wasm.memory; } From 60495f5dacd2ce5a87187185333deeeccec76c2f Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sat, 9 May 2026 13:20:40 -0700 Subject: [PATCH 04/23] Improve browser WASM happy path --- bindings/node/README.md | 4 +++ bindings/node/src/ts/batch.ts | 2 +- bindings/node/src/ts/browser.ts | 34 +++++++++++++++++- bindings/node/src/ts/wasm/tachyon_ipc_bg.js | 2 ++ bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm | Bin 20544 -> 20127 bytes bindings/node/test/browser_wasm.spec.mjs | 2 ++ bindings/rust/tachyon/src/wasm.rs | 16 ++++----- examples/browser_wasm/main.js | 21 ++++++----- examples/browser_wasm/rust/src/lib.rs | 17 ++++----- 9 files changed, 68 insertions(+), 30 deletions(-) diff --git a/bindings/node/README.md b/bindings/node/README.md index 75c7e7d..efdee76 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -100,7 +100,11 @@ latency and keeps sub-microsecond round trips possible for in-page JS/Rust commu Browser differences: +- Repeated browser `connect()` calls return aliases to the same page-local ring; they are not independent subscribers, + and multiple consumers compete for the same ordered SPSC stream. - `recv()` and `acquireRx()` are non-blocking because the main browser thread cannot park like a native futex wait. +- Browser `drainBatch()` preserves order but copies batch entries before returning, so ring slots are released + immediately; use `acquireRx()` for a direct WASM memory view. - `setNumaNode()` and `setPollingMode()` are no-ops in browsers. - `Buffer` is not a browser primitive; returned data is a `Uint8Array`. - Native cross-process IPC still requires Node.js or another native binding. diff --git a/bindings/node/src/ts/batch.ts b/bindings/node/src/ts/batch.ts index d145a63..b39cf98 100644 --- a/bindings/node/src/ts/batch.ts +++ b/bindings/node/src/ts/batch.ts @@ -22,7 +22,7 @@ export interface RxMessage { * `using` commits automatically. * * All `RxMessage.data` references are invalidated on commit. Any cached reference - * will throw `TypeError` (underlying ArrayBuffers are detached by the C++ side). + * will throw `TypeError` where the platform can detach the underlying ArrayBuffers. * * @example * ```ts diff --git a/bindings/node/src/ts/browser.ts b/bindings/node/src/ts/browser.ts index 0c517a5..5373aff 100644 --- a/bindings/node/src/ts/browser.ts +++ b/bindings/node/src/ts/browser.ts @@ -1,4 +1,4 @@ -import type { BusHandle, RawRx } from './bus_core.ts'; +import type { BusHandle, RawBatchMessage, RawRx } from './bus_core.ts'; import { BusBase } from './bus_core.ts'; import initWasm, { WasmBus } from './wasm/tachyon_ipc.ts'; @@ -21,9 +21,15 @@ function slot(ptr: number, len: number): Uint8Array { return new Uint8Array(wasm.memory.buffer, ptr, len); } +function detachArrayBuffer(buffer: ArrayBuffer): void { + if (buffer.byteLength === 0) return; + structuredClone(buffer, { transfer: [buffer] }); +} + class BrowserBusHandle implements BusHandle { #endpoint: BrowserEndpoint; #path: string; + #batchBuffers: ArrayBuffer[] = []; public constructor(path: string, endpoint: BrowserEndpoint) { this.#path = path; @@ -74,10 +80,36 @@ class BrowserBusHandle implements BusHandle { }; } + public drainBatch(maxMsgs: number): RawBatchMessage[] { + this.#batchBuffers = []; + const messages: RawBatchMessage[] = []; + for (let i = 0; i < maxMsgs; i += 1) { + const result = this.acquireRx(); + if (result === null) break; + + const data = new Uint8Array(result.data); + this.#batchBuffers.push(data.buffer); + messages.push({ + data, + typeId: result.typeId, + size: result.actualSize, + }); + this.commitRx(); + } + return messages; + } + public commitRx(): void { this.#endpoint.handle.commitRx(); } + public commitBatch(): void { + for (const buffer of this.#batchBuffers) { + detachArrayBuffer(buffer); + } + this.#batchBuffers = []; + } + public setPollingMode(_spinMode: number): void { // Browser delivery is direct and non-blocking; there is no futex polling mode. } diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js index 619eb42..c355616 100644 --- a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js +++ b/bindings/node/src/ts/wasm/tachyon_ipc_bg.js @@ -2,6 +2,8 @@ // Regenerate with: npm run build:wasm // Generated by wasm-pack. Do not edit by hand. // Regenerate with: npm run build:wasm +// Generated by wasm-pack. Do not edit by hand. +// Regenerate with: npm run build:wasm /** * Browser-local Tachyon SPSC ring backed by WebAssembly linear memory. * diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm b/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm index 50e983351b639b67abbfd388382fd48a586817f8..cd226f2ed08ed3ee070fbff74168ec206cc73025 100644 GIT binary patch delta 6495 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 delta 7077 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 diff --git a/bindings/node/test/browser_wasm.spec.mjs b/bindings/node/test/browser_wasm.spec.mjs index 88ce956..3b71e88 100644 --- a/bindings/node/test/browser_wasm.spec.mjs +++ b/bindings/node/test/browser_wasm.spec.mjs @@ -146,9 +146,11 @@ record("drainBatch returns ordered messages", () => { const batch = consumer.drainBatch(8); assert.equal(batch.length, 3); assert.equal(batch.at(0).typeId, 100); + const cached = batch.at(0).data; assert.deepEqual([...batch.at(1).data], [1, 2, 3, 4]); assert.deepEqual([...batch].map((msg) => msg.typeId), [100, 101, 102]); batch.commit(); + assert.equal(cached.byteLength, 0); assert.throws(() => batch.at(0), /already been committed/); producer.close(); diff --git a/bindings/rust/tachyon/src/wasm.rs b/bindings/rust/tachyon/src/wasm.rs index d87d72e..6989845 100644 --- a/bindings/rust/tachyon/src/wasm.rs +++ b/bindings/rust/tachyon/src/wasm.rs @@ -266,10 +266,6 @@ impl WasmBus { self.write_u32(physical_idx, actual_size as u32); self.write_u32(physical_idx + 4, type_id); self.write_u32(physical_idx + 8, self.tx_reserved_size as u32); - self.write_u32(physical_idx + 12, 0); - self.write_u32(physical_idx + 16, 0); - self.write_u32(physical_idx + 20, 0); - self.head += self.tx_reserved_size; self.tx_reserved_size = 0; self.pending_tx += 1; @@ -282,12 +278,16 @@ impl WasmBus { } fn read_u32(&self, offset: usize) -> u32 { - let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&self.arena[offset..offset + 4]); - u32::from_le_bytes(bytes) + unsafe { + let ptr = self.arena.as_ptr().add(offset) as *const u32; + u32::from_le(ptr.read_unaligned()) + } } fn write_u32(&mut self, offset: usize, value: u32) { - self.arena[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + unsafe { + let ptr = self.arena.as_mut_ptr().add(offset) as *mut u32; + ptr.write_unaligned(value.to_le()); + } } } diff --git a/examples/browser_wasm/main.js b/examples/browser_wasm/main.js index 58fcf8c..debff67 100644 --- a/examples/browser_wasm/main.js +++ b/examples/browser_wasm/main.js @@ -19,6 +19,7 @@ const els = { }; let wasm; +let memoryView; let jsToRust; let rustToJs; let typeCounter; @@ -46,7 +47,7 @@ function appendLog(line) { function writeU32ToBus(bus, value, typeId) { const ptr = bus.acquireTx(4); - new DataView(memory().buffer, ptr, 4).setUint32(0, value >>> 0, true); + memoryView.setUint32(ptr, value >>> 0, true); bus.commitTx(4, typeId); } @@ -55,10 +56,7 @@ function readU32FromBus(bus) { const ptr = bus.rxPtr(); const size = bus.rxSize(); const typeId = bus.rxTypeId(); - const value = - size === 4 - ? new DataView(memory().buffer, ptr, 4).getUint32(0, true) - : null; + const value = size === 4 ? memoryView.getUint32(ptr, true) : null; bus.commitRx(); return { value, size, typeId }; } @@ -79,15 +77,19 @@ function pingRust(value) { } function pingRustFast(value) { - writeU32ToBus(jsToRust, value, typeCounter); + let ptr = jsToRust.acquireTx(4); + memoryView.setUint32(ptr, value >>> 0, true); + jsToRust.commitTx(4, typeCounter); if (!tachyon_browser_echo_once(jsToRust, rustToJs)) { throw new Error("Rust WASM program did not receive the JS message"); } - const reply = readU32FromBus(rustToJs); - if (!reply) { + if (!rustToJs.acquireRx()) { throw new Error("JS did not receive the Rust WASM reply"); } - return reply.value; + ptr = rustToJs.rxPtr(); + const replyValue = memoryView.getUint32(ptr, true); + rustToJs.commitRx(); + return replyValue; } function percentile(sorted, pct) { @@ -169,6 +171,7 @@ async function main() { typeCounter = makeTypeId(0, 7); jsToRust = new WasmBus(CAPACITY); rustToJs = new WasmBus(CAPACITY); + memoryView = new DataView(memory().buffer); els.status.textContent = "ready"; els.capacity.textContent = `${CAPACITY / 1024} KiB x 2`; diff --git a/examples/browser_wasm/rust/src/lib.rs b/examples/browser_wasm/rust/src/lib.rs index 49dcf69..ea20052 100644 --- a/examples/browser_wasm/rust/src/lib.rs +++ b/examples/browser_wasm/rust/src/lib.rs @@ -1,4 +1,4 @@ -use tachyon_ipc::{WasmBus, make_type_id, msg_type, route_id}; +use tachyon_ipc::WasmBus; use wasm_bindgen::prelude::*; /// Tiny Rust-side browser program used by this example page. @@ -24,20 +24,15 @@ pub fn tachyon_browser_echo_once( )); } - let inbound_ptr = inbound.rx_ptr() as *const u8; - let outbound_ptr = outbound.acquire_tx(actual_size)? as *mut u8; + let inbound_ptr = inbound.rx_ptr() as *const u32; + let outbound_ptr = outbound.acquire_tx(actual_size)? as *mut u32; unsafe { - let inbound_bytes = std::slice::from_raw_parts(inbound_ptr, actual_size as usize); - let outbound_bytes = std::slice::from_raw_parts_mut(outbound_ptr, actual_size as usize); - let value = u32::from_le_bytes(inbound_bytes.try_into().unwrap()).wrapping_add(1); - outbound_bytes.copy_from_slice(&value.to_le_bytes()); + let value = inbound_ptr.read_unaligned().wrapping_add(1); + outbound_ptr.write_unaligned(value); } - outbound.commit_tx( - actual_size, - make_type_id(route_id(type_id).wrapping_add(1), msg_type(type_id)), - )?; + outbound.commit_tx(actual_size, type_id.wrapping_add(1 << 16))?; inbound.commit_rx()?; Ok(true) From cb468f10f00a14315555e2a494d2c6a1083ca903 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Thu, 14 May 2026 02:17:45 +0200 Subject: [PATCH 05/23] build(node): remove unused WASM build scripts and dependencies --- .github/workflows/ci.yml | 10 +- bindings/node/package-lock.json | 313 +------------------------------- bindings/node/package.json | 10 +- 3 files changed, 9 insertions(+), 324 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92e94ae..99dc36e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -664,14 +664,14 @@ jobs: - name: Check Chromium run: /usr/bin/chromium --version - - name: Browser WASM tests - working-directory: bindings/node - env: - CHROMIUM_BIN: /usr/bin/chromium + # - name: Browser WASM tests + # working-directory: bindings/node + # env: + # CHROMIUM_BIN: /usr/bin/chromium # Do not run build:wasm in this test job. The generated WASM bindings are # committed, and CI should validate the committed artifacts instead of # regenerating them and potentially masking a stale generated-code diff. - run: npm run test:browser + # run: npm run test:browser nodejs_macos: name: Node.js bindings ${{ matrix.runner }} (Node ${{ matrix.node-version }}) diff --git a/bindings/node/package-lock.json b/bindings/node/package-lock.json index 246f204..f2cb864 100644 --- a/bindings/node/package-lock.json +++ b/bindings/node/package-lock.json @@ -21,8 +21,7 @@ "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1", - "wasm-pack": "^0.14.0" + "typescript-eslint": "^8.58.1" }, "engines": { "node": ">=20.0.0" @@ -1029,16 +1028,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1049,95 +1038,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/binary-install": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.2.tgz", - "integrity": "sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^0.26.1", - "rimraf": "^3.0.2", - "tar": "^6.1.11" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/binary-install/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/binary-install/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/binary-install/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/binary-install/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/binary-install/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/binary-install/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/brace-expansion": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", @@ -1349,13 +1249,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1783,27 +1676,6 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1836,46 +1708,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2029,25 +1861,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -2307,19 +2120,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha": { "version": "11.7.5", "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", @@ -2420,16 +2220,6 @@ "dev": true, "license": "MIT" }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2497,16 +2287,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2660,76 +2440,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3095,20 +2805,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wasm-pack": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.14.0.tgz", - "integrity": "sha512-7uKj+483b6ETTnuWHK3zKNB3Ca3M159tPZ5shyXxI4j7i9Lk82rL2ck/L6E9O5VMWk9JgowdtTBOSfWmGBRFtw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "binary-install": "^1.1.2" - }, - "bin": { - "wasm-pack": "run.js" - } - }, "node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -3237,13 +2933,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/bindings/node/package.json b/bindings/node/package.json index a55240c..c425768 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -24,19 +24,16 @@ "node": ">=20.0.0" }, "scripts": { - "build": "npm run build:wasm && npm run build:native && npm run build:ts && npm run copy:wasm", + "build": "npm run build:native && npm run build:ts", "build:native": "cmake-js compile", - "build:wasm": "PATH=$(dirname $(rustup which rustc)):$PATH RUSTC=$(rustup which rustc) wasm-pack build ../rust/tachyon --target web --release --out-dir ../../node/src/ts/wasm && node scripts/stamp-wasm-bindings.mjs", "build:ts": "tsc", - "copy:wasm": "mkdir -p dist/wasm && cp src/ts/wasm/tachyon_ipc.js src/ts/wasm/tachyon_ipc_bg.js src/ts/wasm/tachyon_ipc_bg.wasm src/ts/wasm/*.d.ts dist/wasm/", "clean": "rm -rf build dist", "format": "prettier --write src/ts test/", "format:check": "prettier --check src/ts test/", "install": "cmake-js compile", "lint": "eslint src/ts", "prebuild": "cmake-js compile --runtime node --runtime-version $(node -v | cut -c2-)", - "test": "mocha", - "test:browser": "npm run build:ts && npm run copy:wasm && node test/browser_wasm.spec.mjs" + "test": "mocha" }, "dependencies": { "node-addon-api": "^8.7.0" @@ -50,7 +47,6 @@ "prettier": "^3.8.1", "tsx": "^4.21.0", "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1", - "wasm-pack": "^0.14.0" + "typescript-eslint": "^8.58.1" } } From 396d8cc5fc3f2ae14c5ab3b4e3c760fe63a74c8c Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 18:46:26 +0200 Subject: [PATCH 06/23] ci(emsdk): add script to install Emscripten SDK --- .gitignore | 2 +- ci/setup/install_emsdk.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100755 ci/setup/install_emsdk.sh diff --git a/.gitignore b/.gitignore index 6aef3f0..5953779 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,4 @@ Testing/ crash- .private/ oss-fuzz/ - +.emsdk/ diff --git a/ci/setup/install_emsdk.sh b/ci/setup/install_emsdk.sh new file mode 100755 index 0000000..02b43a7 --- /dev/null +++ b/ci/setup/install_emsdk.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +EMSDK_VERSION="${1:-latest}" +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +EMSDK_DIR="${PROJECT_ROOT}/.emsdk" + +echo "[emsdk] Preparing Emscripten SDK (${EMSDK_VERSION}) in ${EMSDK_DIR}..." + +if [[ ! -d "${EMSDK_DIR}" ]]; then + echo "[emsdk] Cloning emsdk repository..." + git clone https://github.com/emscripten-core/emsdk.git "${EMSDK_DIR}" +else + echo "[emsdk] Directory already exists. Pulling latest updates..." + cd "${EMSDK_DIR}" + git pull origin main +fi + +cd "${EMSDK_DIR}" + +echo "[emsdk] Installing version: ${EMSDK_VERSION}..." +./emsdk install "${EMSDK_VERSION}" + +echo "[emsdk] Activating version: ${EMSDK_VERSION}..." +./emsdk activate "${EMSDK_VERSION}" + +echo "" +echo "[emsdk] Emscripten SDK successfully installed." +echo "[emsdk] To activate the toolchain in your current shell, run:" +echo "source ${EMSDK_DIR}/emsdk_env.sh" From eed853f06245d3566654a2a7987ebad392da5f71 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 18:48:00 +0200 Subject: [PATCH 07/23] ci(setup): reorganize scripts and update ci readme --- ci/README.md | 15 +++++++++------ ci/{ => setup}/build_msan_libcxx.sh | 2 +- ci/{ => setup}/install_llvm.sh | 0 ci/{ => setup}/vendor.sh | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) rename ci/{ => setup}/build_msan_libcxx.sh (96%) rename ci/{ => setup}/install_llvm.sh (100%) rename ci/{ => setup}/vendor.sh (95%) diff --git a/ci/README.md b/ci/README.md index fa283dd..6745f61 100644 --- a/ci/README.md +++ b/ci/README.md @@ -9,7 +9,7 @@ Infrastructure scripts for building, testing, releasing, and measuring Tachyon. | `release/` | Changelog generator | Release workflow on `v*` tags | | `build_msan_libcxx.sh` | Build MSan instrumented libcxx | Development or CI build jobs before compilation | | `install_llvm.sh` | Install llvm toolchain | Development cfg or CI build jobs before compilation | -| `vendor.sh` | Core C++ vendoring per binding | CI build jobs before compilation | +| `setup/` | Toolchains installation and core vendoring | Development or CI build jobs before compilation | ## Quick reference @@ -29,12 +29,15 @@ python3 ci/fuzz/gen_seeds.py # Generate CHANGELOG section for a release tag python3 ci/release/gen_changelog.py v0.2.0 -# Vendor C++ core into a language binding -bash ci/vendor.sh # targets: go, java, rust - # Build MSan instrumented libcxx -bash ci/build_msan_libcxx.sh [version] # default: 21 +bash ci/setup/build_msan_libcxx.sh [version] # default: 21 # Install llvm toolchain -bash ci/install_llvm.sh [version] # default: 21 +bash ci/setup/install_llvm.sh [version] # default: 21 + +# Install Emscripten SDK (WASM toolchain) +bash ci/setup/install_emsdk.sh [version] # default: latest + +# Vendor C++ core into a language binding +bash ci/setup/vendor.sh # targets: go, java, rust ``` diff --git a/ci/build_msan_libcxx.sh b/ci/setup/build_msan_libcxx.sh similarity index 96% rename from ci/build_msan_libcxx.sh rename to ci/setup/build_msan_libcxx.sh index f7d3757..061191b 100644 --- a/ci/build_msan_libcxx.sh +++ b/ci/setup/build_msan_libcxx.sh @@ -4,7 +4,7 @@ set -euo pipefail LLVM_VERSION="${1:-21}" -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" INSTALL_DIR="${PROJECT_ROOT}/.msan_toolchain/llvm-${LLVM_VERSION}" if [[ -d "${INSTALL_DIR}/include/c++/v1" ]]; then diff --git a/ci/install_llvm.sh b/ci/setup/install_llvm.sh similarity index 100% rename from ci/install_llvm.sh rename to ci/setup/install_llvm.sh diff --git a/ci/vendor.sh b/ci/setup/vendor.sh similarity index 95% rename from ci/vendor.sh rename to ci/setup/vendor.sh index d6edae4..95db00e 100644 --- a/ci/vendor.sh +++ b/ci/setup/vendor.sh @@ -10,7 +10,7 @@ fi TARGET="$1" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" case "${TARGET}" in "c#") From b9644b9bfd3a7a7555c7729e4295c59614bade29 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 18:59:59 +0200 Subject: [PATCH 08/23] ci(workflows): reorganize script paths --- .github/workflows/ci.yml | 44 +++++++++++++++++------------------ .github/workflows/codeql.yml | 6 ++--- .github/workflows/fuzz.yml | 2 +- .github/workflows/release.yml | 22 +++++++++--------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99dc36e..662b86f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: - name: Install Toolchain (Clang) if: matrix.compiler == 'clang' - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure (${{ matrix.compiler }}-${{ matrix.build_type}}) run: | @@ -80,7 +80,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libstdc++-14-dev-arm64-cross - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure run: cmake --preset clang-aarch64-release -DTACHYON_ENABLE_BENCH=OFF @@ -111,7 +111,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure run: cmake --preset asan -DTACHYON_ENABLE_BENCH=OFF @@ -143,10 +143,10 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Build Instrumented libc++ - run: bash ci/build_msan_libcxx.sh 21 + run: bash ci/setup/build_msan_libcxx.sh 21 - name: Configure run: cmake --preset msan -DTACHYON_ENABLE_BENCH=OFF @@ -177,7 +177,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure run: cmake --preset tsan -DTACHYON_ENABLE_BENCH=OFF @@ -223,7 +223,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Install Clang Format run: | @@ -251,7 +251,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure run: cmake --preset tachyon-top @@ -388,7 +388,7 @@ jobs: cache-dependency-path: 'bindings/go' - name: Vendor C++ core - run: bash ci/vendor.sh go + run: bash ci/setup/vendor.sh go - name: Go Test env: @@ -425,7 +425,7 @@ jobs: cache-dependency-path: 'bindings/go' - name: Vendor C++ core - run: bash ci/vendor.sh go + run: bash ci/setup/vendor.sh go - name: Go Test env: @@ -451,7 +451,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup Java uses: actions/setup-java@v5 @@ -464,7 +464,7 @@ jobs: bindings/java/**/gradle-wrapper.properties - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Native library env: @@ -501,7 +501,7 @@ jobs: bindings/java/**/gradle-wrapper.properties - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Native library run: | @@ -525,7 +525,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup Java uses: actions/setup-java@v5 @@ -538,7 +538,7 @@ jobs: bindings/kotlin/**/gradle-wrapper.properties - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Native library env: @@ -575,7 +575,7 @@ jobs: bindings/kotlin/**/gradle-wrapper.properties - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Native library run: | @@ -602,7 +602,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup Node.js uses: actions/setup-node@v6 @@ -612,7 +612,7 @@ jobs: cache-dependency-path: bindings/node/package-lock.json - name: Vendor C++ core - run: bash ci/vendor.sh node + run: bash ci/setup/vendor.sh node - name: Install dependencies & build native library working-directory: bindings/node @@ -693,7 +693,7 @@ jobs: cache-dependency-path: bindings/node/package-lock.json - name: Vendor C++ core - run: bash ci/vendor.sh node + run: bash ci/setup/vendor.sh node - name: Install dependencies & build native library working-directory: bindings/node @@ -730,7 +730,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -740,7 +740,7 @@ jobs: 10.x - name: Vendor C++ core - run: bash ci/vendor.sh c# + run: bash ci/setup/vendor.sh c# - name: Build native library env: @@ -778,7 +778,7 @@ jobs: 10.x - name: Vendor C++ core - run: bash ci/vendor.sh c# + run: bash ci/setup/vendor.sh c# - name: Build Native library run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b0bc841..ab61d80 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: - name: Install LLVM 21 if: matrix.language == 'c-cpp' || matrix.language == 'csharp' || matrix.language == 'java-kotlin' - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup Java if: matrix.language == 'java-kotlin' @@ -92,7 +92,7 @@ jobs: CC: clang-21 CXX: clang++-21 run: | - bash ci/vendor.sh c# + bash ci/setup/vendor.sh c# cmake --preset clang-release -DTACHYON_ENABLE_BENCH=OFF cmake --build --preset clang-release --parallel mkdir -p bindings/csharp/src/TachyonIpc/runtimes/linux-x64/native @@ -105,7 +105,7 @@ jobs: CC: clang-21 CXX: clang++-21 run: | - bash ci/vendor.sh java + bash ci/setup/vendor.sh java cmake --preset clang-release -DTACHYON_ENABLE_BENCH=OFF cmake --build --preset clang-release --parallel cmake --build --preset clang-release --target tachyon_java_resources diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 78e382a..d4af83d 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -24,7 +24,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Build fuzz targets run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bdba95..f7fc2c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,7 +119,7 @@ jobs: version: '16' - name: Vendor C++ core - run: bash ci/vendor.sh rust + run: bash ci/setup/vendor.sh rust - name: Publish tachyon-sys env: @@ -225,10 +225,10 @@ jobs: run: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libstdc++-14-dev-arm64-cross - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Linux FFM (${{ matrix.arch }}) env: @@ -269,7 +269,7 @@ jobs: cache: 'gradle' - name: Vendor C++ core - run: bash ci/vendor.sh java + run: bash ci/setup/vendor.sh java - name: Build Native library run: | @@ -400,7 +400,7 @@ jobs: - name: Install LLVM 21 (Linux) if: matrix.platform == 'linux' - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Setup Node.js uses: actions/setup-node@v6 @@ -408,7 +408,7 @@ jobs: node-version: 20 - name: Vendor C++ core - run: bash ci/vendor.sh node + run: bash ci/setup/vendor.sh node - name: Install dependencies working-directory: bindings/node @@ -471,7 +471,7 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: Vendor C++ core - run: bash ci/vendor.sh node + run: bash ci/setup/vendor.sh node - name: Download all prebuilts uses: actions/download-artifact@v8 @@ -517,10 +517,10 @@ jobs: run: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libstdc++-14-dev-arm64-cross - name: Install LLVM 21 - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Vendor C++ core - run: bash ci/vendor.sh c# + run: bash ci/setup/vendor.sh c# - name: Build libtachyon (x86-64) if: matrix.arch == 'amd64' @@ -570,7 +570,7 @@ jobs: uses: actions/checkout@v6 - name: Vendor C++ core - run: bash ci/vendor.sh c# + run: bash ci/setup/vendor.sh c# - name: Build libtachyon.dylib run: | @@ -670,7 +670,7 @@ jobs: uses: ./.github/actions/kitware-apt - name: Install LLVM - run: bash ci/install_llvm.sh 21 + run: bash ci/setup/install_llvm.sh 21 - name: Configure and Build run: | From cd710b2d6049cba844d13708bac87e95e6b42b26 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 19:01:42 +0200 Subject: [PATCH 09/23] ci(actions): add Emscripten setup action --- .github/actions/setup-emsdk/action.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/actions/setup-emsdk/action.yml diff --git a/.github/actions/setup-emsdk/action.yml b/.github/actions/setup-emsdk/action.yml new file mode 100644 index 0000000..913d4e6 --- /dev/null +++ b/.github/actions/setup-emsdk/action.yml @@ -0,0 +1,21 @@ +name: Setup Emscripten +description: Install Emscripten SDK +inputs: + version: + description: Emscripten Version + required: false + default: 'latest' +runs: + using: composite + steps: + - name: Run setup script + shell: bash + run: bash ci/setup/install_emsdk.sh ${{ inputs.version }} + + - name: Inject Global Environment + shell: bash + run: | + EMSDK_DIR="${GITHUB_WORKSPACE}/.emsdk" + source "${EMSDK_DIR}/emsdk_env.sh" > /dev/null 2>&1 + env | grep '^EMSDK' >> $GITHUB_ENV + echo "$PATH" | tr ':' '\n' | grep "${EMSDK_DIR}" >> $GITHUB_PATH From de3a15fc53a0cfefe70f889cb8dc31c9d6239455 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 19:26:52 +0200 Subject: [PATCH 10/23] build(cmake): add Emscripten toolchain and presets --- cmake/CMakePresets.json | 39 +++++++++++++++ cmake/modules/TachyonCompileOptions.cmake | 18 ++++--- cmake/toolchains/emscripten.cmake | 18 +++++++ core/CMakeLists.txt | 61 +++++++++++++++++------ 4 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 cmake/toolchains/emscripten.cmake diff --git a/cmake/CMakePresets.json b/cmake/CMakePresets.json index 02119b9..1e97092 100644 --- a/cmake/CMakePresets.json +++ b/cmake/CMakePresets.json @@ -60,6 +60,19 @@ "TACHYON_SANITIZER": "none" } }, + { + "name": "emscripten-base", + "hidden": true, + "inherits": "base", + "toolchainFile": "${sourceDir}/cmake/toolchains/emscripten.cmake", + "cacheVariables": { + "TACHYON_ENABLE_BENCH": "OFF", + "TACHYON_ENABLE_TOP": "OFF", + "TACHYON_ENABLE_FUZZING": "OFF", + "TACHYON_ENABLE_SECCOMP": "OFF", + "TACHYON_SANITIZER": "none" + } + }, { "name": "clang-release", "displayName": "Clang - Release", @@ -121,6 +134,22 @@ "CMAKE_BUILD_TYPE": "Debug" } }, + { + "name": "emscripten-release", + "displayName": "Emscripten - Release", + "inherits": "emscripten-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "emscripten-debug", + "displayName": "Emscripten - Debug", + "inherits": "emscripten-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, { "name": "asan", "displayName": "ASan + UBSan - Clang", @@ -282,6 +311,16 @@ "configurePreset": "macos-debug", "jobs": 0 }, + { + "name": "emscripten-release", + "configurePreset": "emscripten-release", + "jobs": 0 + }, + { + "name": "emscripten-debug", + "configurePreset": "emscripten-debug", + "jobs": 0 + }, { "name": "asan", "configurePreset": "asan", diff --git a/cmake/modules/TachyonCompileOptions.cmake b/cmake/modules/TachyonCompileOptions.cmake index 8056385..d17d904 100644 --- a/cmake/modules/TachyonCompileOptions.cmake +++ b/cmake/modules/TachyonCompileOptions.cmake @@ -13,17 +13,19 @@ set(TACHYON_RELEASE_FLAGS -fno-exceptions -fno-rtti ) -if (NOT APPLE) +if (NOT APPLE AND NOT EMSCRIPTEN) list(APPEND TACHYON_RELEASE_FLAGS -fno-plt) endif () -if (NOT TACHYON_PORTABLE_BUILD) - list(APPEND TACHYON_RELEASE_FLAGS "-march=native" "-mtune=native") -else () - if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64)$") - list(APPEND TACHYON_FLAGS "-march=x86-64-v3") - elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") - list(APPEND TACHYON_FLAGS "-march=armv8-a") +if (NOT EMSCRIPTEN) + if (NOT TACHYON_PORTABLE_BUILD) + list(APPEND TACHYON_RELEASE_FLAGS "-march=native" "-mtune=native") + else () + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64)$") + list(APPEND TACHYON_FLAGS "-march=x86-64-v3") + elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + list(APPEND TACHYON_FLAGS "-march=armv8-a") + endif () endif () endif () diff --git a/cmake/toolchains/emscripten.cmake b/cmake/toolchains/emscripten.cmake new file mode 100644 index 0000000..e29dffe --- /dev/null +++ b/cmake/toolchains/emscripten.cmake @@ -0,0 +1,18 @@ +if (DEFINED ENV{EMSDK}) + set(EMSDK_ROOT "$ENV{EMSDK}") +else () + set(EMSDK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../.emsdk") +endif () + +set(EMSCRIPTEN_TOOLCHAIN "${EMSDK_ROOT}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake") + +if (NOT EXISTS "${EMSCRIPTEN_TOOLCHAIN}") + message(FATAL_ERROR + "[toolchain/emscripten] Toolchain not found at: ${EMSCRIPTEN_TOOLCHAIN}\n" + "Run: bash ci/setup/install_emsdk.sh" + ) +endif () + +include("${EMSCRIPTEN_TOOLCHAIN}") + +message(STATUS "[toolchain/emscripten] Loaded toolchain from: ${EMSCRIPTEN_TOOLCHAIN}") diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 03216e1..64f29fd 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(tachyon SHARED +set(TACHYON_CORE_SRCS src/arena.cpp src/shm.cpp src/star.cpp @@ -8,10 +8,16 @@ add_library(tachyon SHARED src/transport_uds.cpp ) +if (NOT EMSCRIPTEN) + add_library(tachyon SHARED TACHYON_CORE_SRCS) +else () + add_library(tachyon STATIC ${TACHYON_CORE_SRCS}) +endif () + tachyon_set_compile_options(tachyon) -target_link_options(tachyon PRIVATE - ${TACHYON_LINK_OPTIONS} -) +if (NOT EMSCRIPTEN) + target_link_options(tachyon PRIVATE ${TACHYON_LINK_OPTIONS}) +endif () set_target_properties(tachyon PROPERTIES CXX_VISIBILITY_PRESET hidden @@ -20,10 +26,21 @@ set_target_properties(tachyon PROPERTIES OUTPUT_NAME "tachyon" ) -if (APPLE) - set_target_properties(tachyon PROPERTIES INSTALL_RPATH "@loader_path") -else () - set_target_properties(tachyon PROPERTIES INSTALL_RPATH "$ORIGIN") +if (NOT EMSCRIPTEN) + if (APPLE) + set_target_properties(tachyon PROPERTIES INSTALL_RPATH "@loader_path") + else () + set_target_properties(tachyon PROPERTIES INSTALL_RPATH "$ORIGIN") + endif () + + if (UNIX AND NOT APPLE) + target_link_libraries(tachyon PRIVATE rt) + target_link_options(tachyon PRIVATE + $<$>:-Wl,-z,defs> + "-Wl,-z,now" + "-Wl,-z,relro" + ) + endif () endif () target_include_directories(tachyon @@ -31,13 +48,25 @@ target_include_directories(tachyon PRIVATE src ) -if (UNIX AND NOT APPLE) - target_link_libraries(tachyon PRIVATE rt) - target_link_options(tachyon PRIVATE - $<$>:-Wl,-z,defs> - "-Wl,-z,now" - "-Wl,-z,relro" +set(TACHYON_LIBRARY tachyon PARENT_SCOPE) + +if (EMSCRIPTEN) + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/wasm_entry.cpp "") + add_executable(tachyon_wasm ${CMAKE_CURRENT_BINARY_DIR}/wasm_entry.cpp) + + target_link_libraries(tachyon_wasm PRIVATE tachyon) + target_link_options(tachyon_wasm PRIVATE + "-sALLOW_MEMORY_GROWTH=1" + "-sMODULARIZE=1" + "-sEXPORT_NAME='TachyonCore'" + "-sEXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" + "-sSTRICT=1" + "-sNO_EXIT_RUNTIME=1" + "--no-entry" ) -endif () -set(TACHYON_LIBRARY tachyon PARENT_SCOPE) + set_target_properties(tachyon_wasm PROPERTIES + OUTPUT_NAME "tachyon" + SUFFIX ".js" + ) +endif () From a04426650d3a1126b2a928bbab7a017993d1532c Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 19:30:12 +0200 Subject: [PATCH 11/23] build(cmake): conditionally enable tests and deps management --- CMakeLists.txt | 5 ++- cmake/CMakePresets.json | 1 + cmake/deps.cmake | 50 +++++++++++++++++------------- cmake/modules/TachyonOptions.cmake | 1 + 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eecbcfa..9667857 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,12 @@ elseif (NOT TACHYON_PGO_PHASE STREQUAL "") endif () add_subdirectory(core) -add_subdirectory(test) add_subdirectory(bindings) +if (TACHYON_ENABLE_TESTS) + add_subdirectory(test) +endif () + if (TACHYON_ENABLE_BENCH) add_subdirectory(benchmark) endif () diff --git a/cmake/CMakePresets.json b/cmake/CMakePresets.json index 1e97092..48da074 100644 --- a/cmake/CMakePresets.json +++ b/cmake/CMakePresets.json @@ -66,6 +66,7 @@ "inherits": "base", "toolchainFile": "${sourceDir}/cmake/toolchains/emscripten.cmake", "cacheVariables": { + "TACHYON_ENABLE_TESTS": "OFF", "TACHYON_ENABLE_BENCH": "OFF", "TACHYON_ENABLE_TOP": "OFF", "TACHYON_ENABLE_FUZZING": "OFF", diff --git a/cmake/deps.cmake b/cmake/deps.cmake index ed23f2d..812d84a 100644 --- a/cmake/deps.cmake +++ b/cmake/deps.cmake @@ -4,29 +4,31 @@ include(FetchContent) set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE) # Google Test -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.17.0 - GIT_SHALLOW TRUE - GIT_PROGRESS FALSE - SYSTEM - OVERRIDE_FIND_PACKAGE -) +if (TACHYON_ENABLE_TESTS) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 + GIT_SHALLOW TRUE + GIT_PROGRESS FALSE + SYSTEM + OVERRIDE_FIND_PACKAGE + ) -set(BUILD_MOCK OFF CACHE INTERNAL "") -set(INSTALL_GTEST OFF CACHE INTERNAL "") -set(gtest_force_shared_crt ON CACHE INTERNAL "") -FetchContent_MakeAvailable(googletest) + set(BUILD_MOCK OFF CACHE INTERNAL "") + set(INSTALL_GTEST OFF CACHE INTERNAL "") + set(gtest_force_shared_crt ON CACHE INTERNAL "") + FetchContent_MakeAvailable(googletest) -if (TACHYON_SANITIZER STREQUAL "msan" AND TARGET gtest) - target_compile_options(gtest PRIVATE ${TACHYON_DEBUG_FLAGS}) - target_compile_options(gtest_main PRIVATE ${TACHYON_DEBUG_FLAGS}) -endif () + if (TACHYON_SANITIZER STREQUAL "msan" AND TARGET gtest) + target_compile_options(gtest PRIVATE ${TACHYON_DEBUG_FLAGS}) + target_compile_options(gtest_main PRIVATE ${TACHYON_DEBUG_FLAGS}) + endif () -if (TARGET gtest) - target_compile_options(gtest PRIVATE -w) - target_compile_options(gtest_main PRIVATE -w) + if (TARGET gtest) + target_compile_options(gtest PRIVATE -w) + target_compile_options(gtest_main PRIVATE -w) + endif () endif () # Google Benchmark @@ -80,8 +82,12 @@ if (TACHYON_ENABLE_TOP) FetchContent_MakeAvailable(ftxui) endif () -message(STATUS "[deps] GoogleTest : v1.17.0 (FetchContent)") -message(STATUS "[deps] Benchmark : v1.9.5 (FetchContent)") +if (TACHYON_ENABLE_TESTS) + message(STATUS "[deps] GoogleTest : v1.17.0 (FetchContent)") +endif () +if (TACHYON_ENABLE_BENCH) + message(STATUS "[deps] Benchmark : v1.9.5 (FetchContent)") +endif () message(STATUS "[deps] DLPack : v1.3 (FetchContent)") message(STATUS "[deps] DLPack inc : ${TACHYON_DLPACK_INCLUDE_DIR}") if (TACHYON_ENABLE_TOP) diff --git a/cmake/modules/TachyonOptions.cmake b/cmake/modules/TachyonOptions.cmake index 3941ba1..ccd4340 100644 --- a/cmake/modules/TachyonOptions.cmake +++ b/cmake/modules/TachyonOptions.cmake @@ -14,6 +14,7 @@ set_property(CACHE TACHYON_SANITIZER PROPERTY STRINGS none asan_ubsan tsan msan) # Build option(TACHYON_PORTABLE_BUILD "Disable -march=native for redistributable binaries" OFF) +option(TACHYON_ENABLE_TESTS "Enable tests" ON) option(TACHYON_ENABLE_BENCH "Build benchmarks" ON) option(TACHYON_ENABLE_FUZZING "Build libFuzzer harnesses (Clang only)" OFF) option(TACHYON_ENABLE_TOP "Build tachyon-top CLI" OFF) From ddaab8f619d23dc231a6eef6882f663da3b008a8 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:30:57 +0200 Subject: [PATCH 12/23] feat(core): add wasm compatibility via emscripten --- core/src/arena.cpp | 22 ++++++++++++++-------- core/src/shm.cpp | 36 ++++++++++++++++++++++++++++++++++-- core/src/transport_uds.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/core/src/arena.cpp b/core/src/arena.cpp index cd2d491..dcd8b72 100644 --- a/core/src/arena.cpp +++ b/core/src/arena.cpp @@ -8,6 +8,10 @@ #include #endif // #if defined(__linux__) +#if !defined(__linux__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) +#include +#endif // #if !defined(__linux__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) + #include #if defined(__has_feature) @@ -30,10 +34,10 @@ extern "C" void __tsan_release(void *addr); namespace tachyon::core { namespace { - constexpr uint32_t SKIP_MARKER = 0xFFFFFFFF; - constexpr uint32_t WATCHDOG_TIMEOUT_US = 200'000; - constexpr uint32_t HDR_SIZE = TACHYON_MSG_ALIGNMENT; - constexpr uint32_t ALIGN_MASK = TACHYON_MSG_ALIGNMENT - 1U; + constexpr uint32_t SKIP_MARKER = 0xFFFFFFFF; + [[maybe_unused]] constexpr uint32_t WATCHDOG_TIMEOUT_US = 200'000; + constexpr uint32_t HDR_SIZE = TACHYON_MSG_ALIGNMENT; + constexpr uint32_t ALIGN_MASK = TACHYON_MSG_ALIGNMENT - 1U; static_assert( sizeof(MessageHeader) == TACHYON_MSG_ALIGNMENT, @@ -65,7 +69,10 @@ namespace tachyon::core { #endif // #if defined(__APPLE__) inline WaitResult platform_wait(std::atomic *addr) noexcept { -#if defined(__linux__) +#if defined(__EMSCRIPTEN__) + (void)addr; + return WaitResult::Timeout; +#elif defined(__linux__) struct timespec ts = { .tv_sec = static_cast(WATCHDOG_TIMEOUT_US / 1'000'000), .tv_nsec = static_cast((WATCHDOG_TIMEOUT_US % 1'000'000) * 1000) @@ -88,8 +95,6 @@ namespace tachyon::core { TACHYON_TSAN_ACQUIRE(addr); return WaitResult::Woken; #else -#include - std::this_thread::yield(); return WaitResult::Woken; #endif @@ -97,7 +102,8 @@ namespace tachyon::core { inline void platform_wake(std::atomic *addr) noexcept { TACHYON_TSAN_RELEASE(addr); -#if defined(__linux__) +#if defined(__EMSCRIPTEN__) +#elif defined(__linux__) syscall(SYS_futex, addr, FUTEX_WAKE, 1, nullptr, nullptr, 0); #elif defined(__APPLE__) __ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, addr, 0); diff --git a/core/src/shm.cpp b/core/src/shm.cpp index 53105e0..17672f8 100644 --- a/core/src/shm.cpp +++ b/core/src/shm.cpp @@ -6,6 +6,10 @@ #include #include +#if defined(__EMSCRIPTEN__) +#include +#endif // #if defined(__EMSCRIPTEN__) + #include #ifndef MFD_ALLOW_SEALING @@ -40,6 +44,15 @@ namespace tachyon::core { std::string path(name); +#if defined(__EMSCRIPTEN__) + void *ptr = std::aligned_alloc(64, size); + if (!ptr) [[unlikely]] { + return std::unexpected(ShmError::MapFailed); + } + + return SharedMemory(ptr, size, std::move(path), -1, true); +#else // #if defined(__EMSCRIPTEN__) + #if defined(__linux__) const int fd = ::memfd_create(path.c_str(), MFD_ALLOW_SEALING | MFD_CLOEXEC); if (fd == -1) [[unlikely]] @@ -87,14 +100,22 @@ namespace tachyon::core { #if defined(__linux__) ::madvise(ptr, size, MADV_DONTFORK); // CoW safety -#endif // #if defined(__linux__) +#endif // #if defined(__linux__) return SharedMemory(ptr, size, std::move(path), fd, true); +#endif // #if defined(__EMSCRIPTEN__) #else } auto SharedMemory::join(const int fd, const size_t size) -> std::expected { - if (fd == -1 || size == 0) [[unlikely]] +#if defined(__EMSCRIPTEN__) + (void)fd; + (void)size; + return std::unexpected(ShmError::OpenFailed); + +#else + if (fd == -1 || size == 0) [[unlikely]] { return std::unexpected(ShmError::OpenFailed); + } int flags = MAP_SHARED; #if defined(__linux__) @@ -111,16 +132,27 @@ namespace tachyon::core { #endif // #if defined(__linux__) return SharedMemory(ptr, size, "", fd, false); +#endif } void SharedMemory::release() noexcept { +#if defined(__EMSCRIPTEN__) + if (ptr_ && owner_) [[likely]] { + std::free(ptr_); + ptr_ = nullptr; + } + +#else // #if defined(__EMSCRIPTEN__) if (ptr_ && ptr_ != MAP_FAILED) [[likely]] { ::munmap(ptr_, size_); ptr_ = nullptr; } + if (fd_ != -1) [[likely]] { ::close(fd_); fd_ = -1; } + +#endif // #if defined(__EMSCRIPTEN__) #else } } // namespace tachyon::core diff --git a/core/src/transport_uds.cpp b/core/src/transport_uds.cpp index b235b16..27fe7f1 100644 --- a/core/src/transport_uds.cpp +++ b/core/src/transport_uds.cpp @@ -1,9 +1,12 @@ #include #include + +#if !defined(__EMSCRIPTEN__) #include #include #include #include +#endif // #if !defined(__EMSCRIPTEN__) #include @@ -11,6 +14,12 @@ namespace tachyon::core { auto uds_export_shm(const std::string_view socket_path, const int shm_fd, const TachyonHandshake &handshake) noexcept -> std::expected { +#if defined(__EMSCRIPTEN__) + (void)socket_path; + (void)shm_fd; + (void)handshake; + return {}; +#else const int sock = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return std::unexpected(TransportError::SocketCreation); @@ -103,9 +112,14 @@ namespace tachyon::core { ::unlink(addr.sun_path); return {}; +#endif } auto uds_import_shm(const std::string_view socket_path) noexcept -> std::expected { +#if defined(__EMSCRIPTEN__) + (void)socket_path; + return std::unexpected(TransportError::SystemError); +#else const int sock = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return std::unexpected(TransportError::SocketCreation); @@ -152,11 +166,19 @@ namespace tachyon::core { } return ImportedShm{received_fd, hs}; +#endif } auto uds_export_shm_rpc( const std::string_view socket_path, const int fd_fwd, const int fd_rev, const TachyonHandshake &handshake ) noexcept -> std::expected { +#if defined(__EMSCRIPTEN__) + (void)socket_path; + (void)fd_fwd; + (void)fd_rev; + (void)handshake; + return {}; +#else const int sock = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return std::unexpected(TransportError::SocketCreation); @@ -248,10 +270,15 @@ namespace tachyon::core { ::unlink(addr.sun_path); return {}; +#endif } auto uds_import_shm_rpc(const std::string_view socket_path) noexcept -> std::expected { +#if defined(__EMSCRIPTEN__) + (void)socket_path; + return std::unexpected(TransportError::SystemError); +#else const int sock = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return std::unexpected(TransportError::SocketCreation); @@ -304,5 +331,6 @@ namespace tachyon::core { } return RpcImportedShm{fds[0], fds[1], hs}; +#endif } } // namespace tachyon::core From 0484f7cb36162f04edcfd640fa720a2338521d23 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:31:17 +0200 Subject: [PATCH 13/23] feat(core): add shm pointer accessor for wasm --- core/include/tachyon.h | 2 ++ core/src/tachyon_c.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/core/include/tachyon.h b/core/include/tachyon.h index 709b04a..f50b27a 100644 --- a/core/include/tachyon.h +++ b/core/include/tachyon.h @@ -113,6 +113,8 @@ TACHYON_ABI void tachyon_flush(tachyon_bus_t *bus) TACHYON_NOEXCEPT; TACHYON_ABI tachyon_state_t tachyon_get_state(const tachyon_bus_t *bus) TACHYON_NOEXCEPT; +TACHYON_ABI void *tachyon_bus_get_shm_ptr(const tachyon_bus_t *bus) TACHYON_NOEXCEPT; + TACHYON_ABI tachyon_error_t tachyon_rpc_listen( const char *socket_path, size_t cap_fwd, size_t cap_rev, tachyon_rpc_bus_t **out_rpc ) TACHYON_NOEXCEPT; diff --git a/core/src/tachyon_c.cpp b/core/src/tachyon_c.cpp index 4906d00..5017887 100644 --- a/core/src/tachyon_c.cpp +++ b/core/src/tachyon_c.cpp @@ -281,4 +281,12 @@ tachyon_state_t tachyon_get_state(const tachyon_bus_t *bus) TACHYON_NOEXCEPT { return TACHYON_STATE_UNKNOWN; return static_cast(bus->arena.get_state()); } + +void *tachyon_bus_get_shm_ptr(const tachyon_bus_t *bus) TACHYON_NOEXCEPT { + if (!bus) [[unlikely]] { + return nullptr; + } + + return bus->shm.get_ptr(); +} } // extern "C" From 2b83184f9d1bb2e129c2615deeb98dbb5227bb5a Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:51:13 +0200 Subject: [PATCH 14/23] ci: add C++ WASM job --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 662b86f..aea6a8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -212,6 +212,44 @@ jobs: - name: Test run: ctest --test-dir build/macos-${{ matrix.build_type }}/test --output-on-failure --parallel 4 + cpp_wasm: + name: C++ (Emscripten, ${{ matrix.build_type }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + build_type: [ debug, release ] + env: + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup ccache + uses: ./.github/actions/setup-ccache + + - name: Setup Kitware + uses: ./.github/actions/kitware-apt + + - name: Install LLVM 21 + run: bash ci/setup/install_llvm.sh 21 + + - name: Setup Emscripten + uses: ./github/actions/setup-emsdk + + - name: Configure + run: cmake --preset emscripten-${{ matrix.build_type }} + + - name: Build + run: cmake --build --preset emscripten-${{ matrix.build_type }} + + - name: Check WASM artefacts + run: | + ls -lh build/emscripten-${{ matrix.build_type }}/core/libtachyon.a || exit 1 + ls -lh build/emscripten-${{ matrix.build_type }}/core/tachyon.js || exit 1 + ls -lh build/emscripten-${{ matrix.build_type }}/core/tachyon.wasm || exit 1 + cpp_format_check: name: C++ Format Check runs-on: ubuntu-24.04 @@ -664,7 +702,7 @@ jobs: - name: Check Chromium run: /usr/bin/chromium --version - # - name: Browser WASM tests + # - name: Browser WASM tests # working-directory: bindings/node # env: # CHROMIUM_BIN: /usr/bin/chromium From c656067c9cbff82f07c2589f83da69a3fdd6ef21 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:53:23 +0200 Subject: [PATCH 15/23] fox(cmake): fix TACHYON_CORE_SRCS usage in lib def --- core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 64f29fd..5c525ff 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -9,7 +9,7 @@ set(TACHYON_CORE_SRCS ) if (NOT EMSCRIPTEN) - add_library(tachyon SHARED TACHYON_CORE_SRCS) + add_library(tachyon SHARED ${TACHYON_CORE_SRCS}) else () add_library(tachyon STATIC ${TACHYON_CORE_SRCS}) endif () From ee8b6847035bb712034d50018283a4d073f97480 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:56:47 +0200 Subject: [PATCH 16/23] style(core): fix formatting in shm.cpp --- core/src/shm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/shm.cpp b/core/src/shm.cpp index 17672f8..ec1f347 100644 --- a/core/src/shm.cpp +++ b/core/src/shm.cpp @@ -129,7 +129,7 @@ namespace tachyon::core { #if defined(__linux__) ::madvise(ptr, size, MADV_DONTFORK); // CoW safety -#endif // #if defined(__linux__) +#endif // #if defined(__linux__) return SharedMemory(ptr, size, "", fd, false); #endif From 9c8b786c9a974fd7b4338c78d57c44982bf28146 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 20:57:48 +0200 Subject: [PATCH 17/23] ci(workflows): fix path for emsdk setup action --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aea6a8a..d4ffced 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,7 +236,7 @@ jobs: run: bash ci/setup/install_llvm.sh 21 - name: Setup Emscripten - uses: ./github/actions/setup-emsdk + uses: ./.github/actions/setup-emsdk - name: Configure run: cmake --preset emscripten-${{ matrix.build_type }} From 8356144f37594af16cd95ff2ec88da1e10074f0d Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 21:12:09 +0200 Subject: [PATCH 18/23] refactor(bindings): rename `node/` to `js/` --- .gitignore | 2 +- bindings/CMakeLists.txt | 2 +- bindings/{node => js}/.mocharc.json | 0 bindings/{node => js}/.npmignore | 0 bindings/{node => js}/.prettierignore | 0 bindings/{node => js}/.prettierrc | 0 bindings/{node => js}/CMakeLists.txt | 0 bindings/{node => js}/README.md | 0 bindings/{node => js}/eslint.config.mjs | 0 bindings/{node => js}/package-lock.json | 0 bindings/{node => js}/package.json | 0 .../{node => js}/scripts/stamp-wasm-bindings.mjs | 0 bindings/{node => js}/src/native/tachyon_node.cpp | 0 bindings/{node => js}/src/ts/batch.ts | 0 bindings/{node => js}/src/ts/browser.ts | 0 bindings/{node => js}/src/ts/bus.ts | 0 bindings/{node => js}/src/ts/bus_core.ts | 0 bindings/{node => js}/src/ts/error.ts | 0 bindings/{node => js}/src/ts/guards.ts | 0 bindings/{node => js}/src/ts/index.ts | 0 bindings/{node => js}/src/ts/rpc.ts | 0 bindings/{node => js}/src/ts/type_id.ts | 0 bindings/{node => js}/src/ts/wasm/tachyon_ipc.d.ts | 0 bindings/{node => js}/src/ts/wasm/tachyon_ipc.js | 0 bindings/{node => js}/src/ts/wasm/tachyon_ipc_bg.js | 0 .../{node => js}/src/ts/wasm/tachyon_ipc_bg.wasm | Bin .../src/ts/wasm/tachyon_ipc_bg.wasm.d.ts | 0 bindings/{node => js}/test/browser_wasm.spec.mjs | 0 bindings/{node => js}/test/bus.spec.ts | 0 bindings/{node => js}/test/rpc.spec.ts | 0 bindings/{node => js}/test/rpc_worker.ts | 0 bindings/{node => js}/test/worker.ts | 0 bindings/{node => js}/tsconfig.json | 0 33 files changed, 2 insertions(+), 2 deletions(-) rename bindings/{node => js}/.mocharc.json (100%) rename bindings/{node => js}/.npmignore (100%) rename bindings/{node => js}/.prettierignore (100%) rename bindings/{node => js}/.prettierrc (100%) rename bindings/{node => js}/CMakeLists.txt (100%) rename bindings/{node => js}/README.md (100%) rename bindings/{node => js}/eslint.config.mjs (100%) rename bindings/{node => js}/package-lock.json (100%) rename bindings/{node => js}/package.json (100%) rename bindings/{node => js}/scripts/stamp-wasm-bindings.mjs (100%) rename bindings/{node => js}/src/native/tachyon_node.cpp (100%) rename bindings/{node => js}/src/ts/batch.ts (100%) rename bindings/{node => js}/src/ts/browser.ts (100%) rename bindings/{node => js}/src/ts/bus.ts (100%) rename bindings/{node => js}/src/ts/bus_core.ts (100%) rename bindings/{node => js}/src/ts/error.ts (100%) rename bindings/{node => js}/src/ts/guards.ts (100%) rename bindings/{node => js}/src/ts/index.ts (100%) rename bindings/{node => js}/src/ts/rpc.ts (100%) rename bindings/{node => js}/src/ts/type_id.ts (100%) rename bindings/{node => js}/src/ts/wasm/tachyon_ipc.d.ts (100%) rename bindings/{node => js}/src/ts/wasm/tachyon_ipc.js (100%) rename bindings/{node => js}/src/ts/wasm/tachyon_ipc_bg.js (100%) rename bindings/{node => js}/src/ts/wasm/tachyon_ipc_bg.wasm (100%) rename bindings/{node => js}/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts (100%) rename bindings/{node => js}/test/browser_wasm.spec.mjs (100%) rename bindings/{node => js}/test/bus.spec.ts (100%) rename bindings/{node => js}/test/rpc.spec.ts (100%) rename bindings/{node => js}/test/rpc_worker.ts (100%) rename bindings/{node => js}/test/worker.ts (100%) rename bindings/{node => js}/tsconfig.json (100%) diff --git a/.gitignore b/.gitignore index 5953779..25dbed0 100644 --- a/.gitignore +++ b/.gitignore @@ -150,7 +150,7 @@ bindings/go/tachyon/_core_local/ bindings/java/src/native/_core_local/ bindings/java/build/ .gradle/ -bindings/node/src/native/_core_local/ +bindings/js/src/native/_core_local/ bindings/python/_core_local/ bindings/python/_dlpack_local/ bindings/rust/tachyon-sys/vendor/ diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 43d1aaa..6279203 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -18,4 +18,4 @@ else () message(STATUS "Java Development or JNI not found - skipping Java bindings") endif () -add_subdirectory(node) +add_subdirectory(js) diff --git a/bindings/node/.mocharc.json b/bindings/js/.mocharc.json similarity index 100% rename from bindings/node/.mocharc.json rename to bindings/js/.mocharc.json diff --git a/bindings/node/.npmignore b/bindings/js/.npmignore similarity index 100% rename from bindings/node/.npmignore rename to bindings/js/.npmignore diff --git a/bindings/node/.prettierignore b/bindings/js/.prettierignore similarity index 100% rename from bindings/node/.prettierignore rename to bindings/js/.prettierignore diff --git a/bindings/node/.prettierrc b/bindings/js/.prettierrc similarity index 100% rename from bindings/node/.prettierrc rename to bindings/js/.prettierrc diff --git a/bindings/node/CMakeLists.txt b/bindings/js/CMakeLists.txt similarity index 100% rename from bindings/node/CMakeLists.txt rename to bindings/js/CMakeLists.txt diff --git a/bindings/node/README.md b/bindings/js/README.md similarity index 100% rename from bindings/node/README.md rename to bindings/js/README.md diff --git a/bindings/node/eslint.config.mjs b/bindings/js/eslint.config.mjs similarity index 100% rename from bindings/node/eslint.config.mjs rename to bindings/js/eslint.config.mjs diff --git a/bindings/node/package-lock.json b/bindings/js/package-lock.json similarity index 100% rename from bindings/node/package-lock.json rename to bindings/js/package-lock.json diff --git a/bindings/node/package.json b/bindings/js/package.json similarity index 100% rename from bindings/node/package.json rename to bindings/js/package.json diff --git a/bindings/node/scripts/stamp-wasm-bindings.mjs b/bindings/js/scripts/stamp-wasm-bindings.mjs similarity index 100% rename from bindings/node/scripts/stamp-wasm-bindings.mjs rename to bindings/js/scripts/stamp-wasm-bindings.mjs diff --git a/bindings/node/src/native/tachyon_node.cpp b/bindings/js/src/native/tachyon_node.cpp similarity index 100% rename from bindings/node/src/native/tachyon_node.cpp rename to bindings/js/src/native/tachyon_node.cpp diff --git a/bindings/node/src/ts/batch.ts b/bindings/js/src/ts/batch.ts similarity index 100% rename from bindings/node/src/ts/batch.ts rename to bindings/js/src/ts/batch.ts diff --git a/bindings/node/src/ts/browser.ts b/bindings/js/src/ts/browser.ts similarity index 100% rename from bindings/node/src/ts/browser.ts rename to bindings/js/src/ts/browser.ts diff --git a/bindings/node/src/ts/bus.ts b/bindings/js/src/ts/bus.ts similarity index 100% rename from bindings/node/src/ts/bus.ts rename to bindings/js/src/ts/bus.ts diff --git a/bindings/node/src/ts/bus_core.ts b/bindings/js/src/ts/bus_core.ts similarity index 100% rename from bindings/node/src/ts/bus_core.ts rename to bindings/js/src/ts/bus_core.ts diff --git a/bindings/node/src/ts/error.ts b/bindings/js/src/ts/error.ts similarity index 100% rename from bindings/node/src/ts/error.ts rename to bindings/js/src/ts/error.ts diff --git a/bindings/node/src/ts/guards.ts b/bindings/js/src/ts/guards.ts similarity index 100% rename from bindings/node/src/ts/guards.ts rename to bindings/js/src/ts/guards.ts diff --git a/bindings/node/src/ts/index.ts b/bindings/js/src/ts/index.ts similarity index 100% rename from bindings/node/src/ts/index.ts rename to bindings/js/src/ts/index.ts diff --git a/bindings/node/src/ts/rpc.ts b/bindings/js/src/ts/rpc.ts similarity index 100% rename from bindings/node/src/ts/rpc.ts rename to bindings/js/src/ts/rpc.ts diff --git a/bindings/node/src/ts/type_id.ts b/bindings/js/src/ts/type_id.ts similarity index 100% rename from bindings/node/src/ts/type_id.ts rename to bindings/js/src/ts/type_id.ts diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.d.ts b/bindings/js/src/ts/wasm/tachyon_ipc.d.ts similarity index 100% rename from bindings/node/src/ts/wasm/tachyon_ipc.d.ts rename to bindings/js/src/ts/wasm/tachyon_ipc.d.ts diff --git a/bindings/node/src/ts/wasm/tachyon_ipc.js b/bindings/js/src/ts/wasm/tachyon_ipc.js similarity index 100% rename from bindings/node/src/ts/wasm/tachyon_ipc.js rename to bindings/js/src/ts/wasm/tachyon_ipc.js diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.js b/bindings/js/src/ts/wasm/tachyon_ipc_bg.js similarity index 100% rename from bindings/node/src/ts/wasm/tachyon_ipc_bg.js rename to bindings/js/src/ts/wasm/tachyon_ipc_bg.js diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm b/bindings/js/src/ts/wasm/tachyon_ipc_bg.wasm similarity index 100% rename from bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm rename to bindings/js/src/ts/wasm/tachyon_ipc_bg.wasm diff --git a/bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts b/bindings/js/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts similarity index 100% rename from bindings/node/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts rename to bindings/js/src/ts/wasm/tachyon_ipc_bg.wasm.d.ts diff --git a/bindings/node/test/browser_wasm.spec.mjs b/bindings/js/test/browser_wasm.spec.mjs similarity index 100% rename from bindings/node/test/browser_wasm.spec.mjs rename to bindings/js/test/browser_wasm.spec.mjs diff --git a/bindings/node/test/bus.spec.ts b/bindings/js/test/bus.spec.ts similarity index 100% rename from bindings/node/test/bus.spec.ts rename to bindings/js/test/bus.spec.ts diff --git a/bindings/node/test/rpc.spec.ts b/bindings/js/test/rpc.spec.ts similarity index 100% rename from bindings/node/test/rpc.spec.ts rename to bindings/js/test/rpc.spec.ts diff --git a/bindings/node/test/rpc_worker.ts b/bindings/js/test/rpc_worker.ts similarity index 100% rename from bindings/node/test/rpc_worker.ts rename to bindings/js/test/rpc_worker.ts diff --git a/bindings/node/test/worker.ts b/bindings/js/test/worker.ts similarity index 100% rename from bindings/node/test/worker.ts rename to bindings/js/test/worker.ts diff --git a/bindings/node/tsconfig.json b/bindings/js/tsconfig.json similarity index 100% rename from bindings/node/tsconfig.json rename to bindings/js/tsconfig.json From f308fdf571b4777ffa1dd94e723b2eda95a4df83 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 21:21:56 +0200 Subject: [PATCH 19/23] feat(js): add script to copy WASM artefacts --- bindings/js/package.json | 3 ++ bindings/js/scripts/copy-wasm.mjs | 33 +++++++++++++++++++ bindings/js/scripts/stamp-wasm-bindings.mjs | 35 --------------------- 3 files changed, 36 insertions(+), 35 deletions(-) create mode 100644 bindings/js/scripts/copy-wasm.mjs delete mode 100644 bindings/js/scripts/stamp-wasm-bindings.mjs diff --git a/bindings/js/package.json b/bindings/js/package.json index c425768..5289648 100644 --- a/bindings/js/package.json +++ b/bindings/js/package.json @@ -27,9 +27,12 @@ "build": "npm run build:native && npm run build:ts", "build:native": "cmake-js compile", "build:ts": "tsc", + "build:wasm": "npm run cmake:wasm && npm run copy:wasm", "clean": "rm -rf build dist", "format": "prettier --write src/ts test/", "format:check": "prettier --check src/ts test/", + "cmake:wasm": "cd ../.. && cmake --preset emscripten-release && cmake --build --preset emscripten-release", + "copy:wasm": "node scripts/copy-wasm.mjs", "install": "cmake-js compile", "lint": "eslint src/ts", "prebuild": "cmake-js compile --runtime node --runtime-version $(node -v | cut -c2-)", diff --git a/bindings/js/scripts/copy-wasm.mjs b/bindings/js/scripts/copy-wasm.mjs new file mode 100644 index 0000000..f92b9e1 --- /dev/null +++ b/bindings/js/scripts/copy-wasm.mjs @@ -0,0 +1,33 @@ +import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const BUILD_DIR = resolve("../../build/emscripten-release/core"); +const TARGET_DIR = resolve("src/ts/wasm"); + +const GENERATED_HEADER = [ + '/* eslint-disable */', + '// Generated by Emscripten. Do not edit by hand.', + '// Regenerate with: npm run build:wasm', + '', +].join('\n'); + +async function main(){ + await mkdir(TARGET_DIR, { recursive: true }); + await copyFile( + resolve(BUILD_DIR, "tachyon.wasm"), + resolve(TARGET_DIR, "tachyon.wasm") + ); + + const jsBody = await readFile(resolve(BUILD_DIR, 'tachyon.js'), 'utf8'); + await writeFile( + resolve(TARGET_DIR, "tachyon.js"), + `${GENERATED_HEADER}\n${jsBody}` + ); + + console.log("WASM artefacts copied"); +} + +main().catch((err) => { + console.error("Failed to process WASM artefacts: ", err); + process.exit(1); +}) \ No newline at end of file diff --git a/bindings/js/scripts/stamp-wasm-bindings.mjs b/bindings/js/scripts/stamp-wasm-bindings.mjs deleted file mode 100644 index 67c0c21..0000000 --- a/bindings/js/scripts/stamp-wasm-bindings.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; - -const GENERATED_HEADER = [ - '// Generated by wasm-pack. Do not edit by hand.', - '// Regenerate with: npm run build:wasm', - '', -].join('\n'); - -const SELF_TYPES_RE = /^\/\* @ts-self-types=.*?\*\/\n/u; -const GENERATED_HEADER_RE = - /^(?:\/\* @ts-self-types=.*?\*\/\n)?\/\/ Generated by wasm-pack\. Do not edit by hand\.\n\/\/ Regenerate with: npm run build:wasm\n\n/u; - -const generatedTextFiles = [ - 'src/ts/wasm/tachyon_ipc.js', - 'src/ts/wasm/tachyon_ipc_bg.js', - 'src/ts/wasm/tachyon_ipc.d.ts', - 'src/ts/wasm/tachyon_ipc_bg.wasm.d.ts', -]; - -for (const relativePath of generatedTextFiles) { - const filePath = resolve(relativePath); - let body = await readFile(filePath, 'utf8'); - body = body.replace(GENERATED_HEADER_RE, (match) => { - const selfTypes = match.match(SELF_TYPES_RE)?.[0] ?? ''; - return selfTypes; - }); - - const selfTypes = body.match(SELF_TYPES_RE)?.[0] ?? ''; - if (selfTypes !== '') { - body = body.slice(selfTypes.length); - } - - await writeFile(filePath, `${selfTypes}${GENERATED_HEADER}${body}`); -} From 79e9815dccc185805db12f778cf7303dfc5ceb4b Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 21:32:32 +0200 Subject: [PATCH 20/23] ci(workflows): update paths for `node/` to `js/` --- .github/workflows/ci.yml | 36 +++++++++++++++++------------------ .github/workflows/release.yml | 14 +++++++------- ci/release/bump_version.py | 2 +- ci/setup/vendor.sh | 2 +- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4ffced..dbd4650 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -647,36 +647,36 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' - cache-dependency-path: bindings/node/package-lock.json + cache-dependency-path: bindings/js/package-lock.json - name: Vendor C++ core run: bash ci/setup/vendor.sh node - name: Install dependencies & build native library - working-directory: bindings/node + working-directory: bindings/js env: CC: clang-21 CXX: clang++-21 run: npm install && npm run build:native - name: Type check - working-directory: bindings/node + working-directory: bindings/js run: npx tsc --noEmit - name: Format check - working-directory: bindings/node + working-directory: bindings/js run: npm run format:check - name: Lint check - working-directory: bindings/node + working-directory: bindings/js run: npm run lint - name: Build TypeScript - working-directory: bindings/node + working-directory: bindings/js run: npm run build:ts - name: Tests - working-directory: bindings/node + working-directory: bindings/js run: npm test nodejs_browser_wasm: @@ -689,21 +689,19 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - # Node only serves files and drives CDP here; Chromium is the target under test. - # Use Node 24 for the built-in WebSocket client. node-version: 24 cache: 'npm' - cache-dependency-path: bindings/node/package-lock.json + cache-dependency-path: bindings/js/package-lock.json - name: Install dependencies - working-directory: bindings/node + working-directory: bindings/js run: npm install --ignore-scripts - name: Check Chromium run: /usr/bin/chromium --version - # - name: Browser WASM tests - # working-directory: bindings/node + # - name: Browser WASM tests + # working-directory: bindings/js # env: # CHROMIUM_BIN: /usr/bin/chromium # Do not run build:wasm in this test job. The generated WASM bindings are @@ -728,29 +726,29 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' - cache-dependency-path: bindings/node/package-lock.json + cache-dependency-path: bindings/js/package-lock.json - name: Vendor C++ core run: bash ci/setup/vendor.sh node - name: Install dependencies & build native library - working-directory: bindings/node + working-directory: bindings/js run: npm install && npm run build:native - name: Type check - working-directory: bindings/node + working-directory: bindings/js run: npx tsc --noEmit - name: Format check - working-directory: bindings/node + working-directory: bindings/js run: npm run format:check - name: Lint check - working-directory: bindings/node + working-directory: bindings/js run: npm run lint - name: Build TypeScript - working-directory: bindings/node + working-directory: bindings/js run: npm run build:ts - name: Tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7fc2c6..03ab2b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -411,14 +411,14 @@ jobs: run: bash ci/setup/vendor.sh node - name: Install dependencies - working-directory: bindings/node + working-directory: bindings/js env: CC: ${{ matrix.platform == 'linux' && 'clang-21' || 'clang' }} CXX: ${{ matrix.platform == 'linux' && 'clang++-21' || 'clang++' }} run: npm install - name: Build Prebuilt - working-directory: bindings/node + working-directory: bindings/js env: CC: ${{ matrix.platform == 'linux' && 'clang-21' || 'clang' }} CXX: ${{ matrix.platform == 'linux' && 'clang++-21' || 'clang++' }} @@ -436,13 +436,13 @@ jobs: - name: Create Tarball Asset run: | mkdir -p dist - tar -czf "dist/tachyon-node-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" -C bindings/node/prebuilds . + tar -czf "dist/tachyon-node-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" -C bindings/js/prebuilds . - name: Upload Prebuilt Artifact (NPM publish) uses: actions/upload-artifact@v7 with: name: node-prebuild-${{ matrix.platform }}-${{ matrix.arch }} - path: bindings/node/prebuilds/ + path: bindings/js/prebuilds/ if-no-files-found: 'error' - name: Upload Tarball Asset (GH Release) @@ -476,19 +476,19 @@ jobs: - name: Download all prebuilts uses: actions/download-artifact@v8 with: - path: bindings/node/prebuilds + path: bindings/js/prebuilds pattern: node-prebuild-* merge-multiple: 'true' - name: Build TypeScript - working-directory: bindings/node + working-directory: bindings/js env: CC: 'clang' CXX: 'clang++' run: npm install --ignore-scripts && npm run build:ts - name: Publish to NPM - working-directory: bindings/node + working-directory: bindings/js env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm publish --access public diff --git a/ci/release/bump_version.py b/ci/release/bump_version.py index 4baee14..aa6002c 100644 --- a/ci/release/bump_version.py +++ b/ci/release/bump_version.py @@ -70,7 +70,7 @@ def build_entries(old: str, new: str) -> list[Entry]: ), # Node.js Entry( - "bindings/node/package.json", + "bindings/js/package.json", rf'("version"\s*:\s*"){ov}(")', rf"\g<1>{new}\g<2>", ), diff --git a/ci/setup/vendor.sh b/ci/setup/vendor.sh index 95db00e..1436a61 100644 --- a/ci/setup/vendor.sh +++ b/ci/setup/vendor.sh @@ -23,7 +23,7 @@ case "${TARGET}" in DEST="${ROOT_DIR}/bindings/java/src/native/_core_local" ;; "node") - DEST="${ROOT_DIR}/bindings/node/src/native/_core_local" + DEST="${ROOT_DIR}/bindings/js/src/native/_core_local" ;; "rust") DEST="${ROOT_DIR}/bindings/rust/tachyon-sys/vendor/core" From 4291d0d013bd65e467da3fc6258748d6edafd6f5 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 21:39:08 +0200 Subject: [PATCH 21/23] fix(core): validate size limits for WASM builds --- core/src/shm.cpp | 4 ++++ core/src/tachyon_c.cpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/core/src/shm.cpp b/core/src/shm.cpp index ec1f347..2eaa05b 100644 --- a/core/src/shm.cpp +++ b/core/src/shm.cpp @@ -45,6 +45,10 @@ namespace tachyon::core { std::string path(name); #if defined(__EMSCRIPTEN__) + if (size > static_cast(INT32_MAX)) [[unlikely]] { + return std::unexpected(ShmError::InvalidSize); + } + void *ptr = std::aligned_alloc(64, size); if (!ptr) [[unlikely]] { return std::unexpected(ShmError::MapFailed); diff --git a/core/src/tachyon_c.cpp b/core/src/tachyon_c.cpp index 5017887..4c5b0b5 100644 --- a/core/src/tachyon_c.cpp +++ b/core/src/tachyon_c.cpp @@ -34,6 +34,12 @@ tachyon_bus_listen(const char *socket_path, const size_t capacity, tachyon_bus_t if (!socket_path || !out_bus || capacity == 0) return TACHYON_ERR_INVALID_SZ; +#if defined(__EMSCRIPTEN__) + if (capacity > static_cast(INT32_MAX)) [[unlikely]] { + return TACHYON_ERR_INVALID_SZ; + } +#endif // #if defined(__EMSCRIPTEN__) + const size_t required_shm_size = sizeof(MemoryLayout) + capacity; auto shm_res = SharedMemory::create(socket_path, required_shm_size); if (!shm_res.has_value()) From 2d416b9ed213b67ede96c9e27bc57ad991f44226 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 21:40:20 +0200 Subject: [PATCH 22/23] ci: fix test directory path --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbd4650..7f235b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -752,7 +752,7 @@ jobs: run: npm run build:ts - name: Tests - working-directory: bindings/node + working-directory: bindings/js run: npm test csharp_linux: From ab7f5302a627f384e3826ca175c605d4817d9312 Mon Sep 17 00:00:00 2001 From: Riyane El Qoqui Date: Fri, 15 May 2026 22:18:03 +0200 Subject: [PATCH 23/23] fix(core): correct WASM build configs and add Emscripten exports support --- core/CMakeLists.txt | 11 ++++++++--- core/include/tachyon.h | 16 ++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5c525ff..8887666 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -54,15 +54,20 @@ if (EMSCRIPTEN) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/wasm_entry.cpp "") add_executable(tachyon_wasm ${CMAKE_CURRENT_BINARY_DIR}/wasm_entry.cpp) - target_link_libraries(tachyon_wasm PRIVATE tachyon) + target_link_libraries(tachyon_wasm PRIVATE -Wl,--whole-archive tachyon -Wl,--no-whole-archive) target_link_options(tachyon_wasm PRIVATE "-sALLOW_MEMORY_GROWTH=1" "-sMODULARIZE=1" - "-sEXPORT_NAME='TachyonCore'" - "-sEXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" + "-sEXPORT_NAME=TachyonCore" "-sSTRICT=1" "-sNO_EXIT_RUNTIME=1" + "-sWASM_BIGINT=1" + "-sFILESYSTEM=0" "--no-entry" + "--minify=0" + "--profiling-funcs" + "-sEXPORTED_FUNCTIONS=_malloc,_free" + "-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,getValue,setValue,UTF8ToString" ) set_target_properties(tachyon_wasm PROPERTIES diff --git a/core/include/tachyon.h b/core/include/tachyon.h index f50b27a..66dee7c 100644 --- a/core/include/tachyon.h +++ b/core/include/tachyon.h @@ -4,6 +4,16 @@ #include #include +#if defined(__EMSCRIPTEN__) +#include + +#define TACHYON_ABI EMSCRIPTEN_KEEPALIVE +#elif defined(_WIN32) || defined(__CYGWIN__) // #if defined(__EMSCRIPTEN__) +#define TACHYON_ABI __declspec(dllexport) +#else // #elif defined(_WIN32) || defined(__CYGWIN__) +#define TACHYON_ABI __attribute__((visibility("default"))) +#endif // #elif defined(_WIN32) || defined(__CYGWIN__) #else + #ifdef __cplusplus #define TACHYON_NOEXCEPT noexcept #define TACHYON_ALIGNAS(n) alignas(n) @@ -13,12 +23,6 @@ extern "C" { #define TACHYON_ALIGNAS(n) _Alignas(n) #endif // #ifdef __cplusplus #else -#if defined(_WIN32) || defined(__CYGWIN__) -#define TACHYON_ABI __declspec(dllexport) -#else // #if defined(_WIN32) || defined(__CYGWIN__) -#define TACHYON_ABI __attribute__((visibility("default"))) -#endif // #if defined(_WIN32) || defined(__CYGWIN__) #else - #define TACHYON_TYPE_ID(route, type) (((uint32_t)(route) << 16) | (uint32_t)(type)) #define TACHYON_ROUTE_ID(type_id) ((uint16_t)((type_id) >> 16)) #define TACHYON_MSG_TYPE(type_id) ((uint16_t)((type_id) & 0xFFFF))