@thi.ng/wasm-api
Advanced tools
Comparing version 1.4.36 to 1.4.37
@@ -1,2 +0,6 @@ | ||
export const EVENT_MEMORY_CHANGED = "memory-changed"; | ||
export const EVENT_PANIC = "panic"; | ||
const EVENT_MEMORY_CHANGED = "memory-changed"; | ||
const EVENT_PANIC = "panic"; | ||
export { | ||
EVENT_MEMORY_CHANGED, | ||
EVENT_PANIC | ||
}; |
839
bridge.js
@@ -1,2 +0,12 @@ | ||
import { __decorate } from "tslib"; | ||
var __defProp = Object.defineProperty; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
var __decorateClass = (decorators, target, key, kind) => { | ||
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; | ||
for (var i = decorators.length - 1, decorator; i >= 0; i--) | ||
if (decorator = decorators[i]) | ||
result = (kind ? decorator(target, key, result) : decorator(result)) || result; | ||
if (kind && result) | ||
__defProp(target, key, result); | ||
return result; | ||
}; | ||
import { INotifyMixin } from "@thi.ng/api/mixins/inotify"; | ||
@@ -8,413 +18,430 @@ import { topoSort } from "@thi.ng/arrays/topo-sort"; | ||
import { ConsoleLogger } from "@thi.ng/logger/console"; | ||
import { EVENT_MEMORY_CHANGED, EVENT_PANIC, } from "./api.js"; | ||
export const Panic = defError(() => "Panic"); | ||
export const OutOfMemoryError = defError(() => "Out of memory"); | ||
/** | ||
* The main interop API bridge between the JS host environment and a WebAssembly | ||
* module. This class provides a small core API with various typed accessors and | ||
* utils to exchange data (scalars, arrays, strings etc.) via the WASM module's | ||
* memory. | ||
* | ||
* @remarks | ||
* All typed memory accessors are assuming the given lookup addresses are | ||
* properly aligned to the corresponding primitive types (e.g. f32 values are | ||
* aligned to 4 byte boundaries, f64 to 8 bytes etc.) Unaligned access is | ||
* explicitly **not supported**! If you need such, please refer to other | ||
* mechanisms like JS `DataView`... | ||
* | ||
* 64bit integers are handled via JS `BigInt` and hence require the host env to | ||
* support it. No polyfills are provided. | ||
*/ | ||
let WasmBridge = class WasmBridge { | ||
logger; | ||
id = "wasmapi"; | ||
i8; | ||
u8; | ||
i16; | ||
u16; | ||
i32; | ||
u32; | ||
i64; | ||
u64; | ||
f32; | ||
f64; | ||
utf8Decoder = new TextDecoder(); | ||
utf8Encoder = new TextEncoder(); | ||
imports; | ||
exports; | ||
api; | ||
modules; | ||
constructor(modules = [], logger = new ConsoleLogger("wasm")) { | ||
this.logger = logger; | ||
const logN = (x) => this.logger.debug(x); | ||
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", ")); | ||
this.api = { | ||
printI8: logN, | ||
printU8: logN, | ||
printI16: logN, | ||
printU16: logN, | ||
printI32: logN, | ||
printU32: (x) => this.logger.debug(x >>> 0), | ||
printI64: (x) => this.logger.debug(x), | ||
printU64: (x) => this.logger.debug(x), | ||
printF32: logN, | ||
printF64: logN, | ||
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`), | ||
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`), | ||
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`), | ||
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`), | ||
printHexdump: (addr, len) => { | ||
this.ensureMemory(); | ||
for (let line of hexdumpLines(this.u8, addr, len)) { | ||
this.logger.debug(line); | ||
} | ||
}, | ||
_printI8Array: logA(this.getI8Array.bind(this)), | ||
_printU8Array: logA(this.getU8Array.bind(this)), | ||
_printI16Array: logA(this.getI16Array.bind(this)), | ||
_printU16Array: logA(this.getU16Array.bind(this)), | ||
_printI32Array: logA(this.getI32Array.bind(this)), | ||
_printU32Array: logA(this.getU32Array.bind(this)), | ||
_printI64Array: logA(this.getI64Array.bind(this)), | ||
_printU64Array: logA(this.getU64Array.bind(this)), | ||
_printF32Array: logA(this.getF32Array.bind(this)), | ||
_printF64Array: logA(this.getF64Array.bind(this)), | ||
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)), | ||
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)), | ||
debug: () => { | ||
debugger; | ||
}, | ||
_panic: (addr, len) => { | ||
const msg = this.getString(addr, len); | ||
if (!this.notify({ id: EVENT_PANIC, value: msg })) { | ||
throw new Panic(msg); | ||
} | ||
}, | ||
timer: () => performance.now(), | ||
epoch: () => BigInt(Date.now()), | ||
}; | ||
this.modules = modules.reduce((acc, x) => { | ||
assert(acc[x.id] === undefined && x.id !== this.id, `duplicate API module ID: ${x.id}`); | ||
acc[x.id] = x; | ||
return acc; | ||
}, {}); | ||
} | ||
/** | ||
* Instantiates WASM module from given `src` (and optional provided extra | ||
* imports), then automatically calls {@link WasmBridge.init} with the | ||
* modules exports. | ||
* | ||
* @remarks | ||
* If the given `src` is a `Response` or `Promise<Response>`, the module | ||
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise | ||
* the non-streaming version will be used. | ||
* | ||
* @param src | ||
* @param imports | ||
*/ | ||
async instantiate(src, imports) { | ||
const $src = await src; | ||
const $imports = { ...this.getImports(), ...imports }; | ||
const wasm = await ($src instanceof Response | ||
? WebAssembly.instantiateStreaming($src, $imports) | ||
: WebAssembly.instantiate($src, $imports)); | ||
return this.init(wasm.instance.exports); | ||
} | ||
/** | ||
* Receives the WASM module's combined exports, stores them for future | ||
* reference and then initializes all declared bridge child API modules in | ||
* their stated dependency order. Returns false if any of the module | ||
* initializations failed. | ||
* | ||
* @remarks | ||
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and | ||
* AFTER all child API modules have been initialized). | ||
* | ||
* @param exports | ||
*/ | ||
async init(exports) { | ||
this.exports = exports; | ||
this.ensureMemory(false); | ||
for (let id of topoSort(this.modules, (module) => module.dependencies)) { | ||
assert(!!this.modules[id], `missing API module: ${id}`); | ||
this.logger.debug(`initializing API module: ${id}`); | ||
const status = await this.modules[id].init(this); | ||
if (!status) | ||
return false; | ||
import { | ||
EVENT_MEMORY_CHANGED, | ||
EVENT_PANIC | ||
} from "./api.js"; | ||
const Panic = defError(() => "Panic"); | ||
const OutOfMemoryError = defError(() => "Out of memory"); | ||
let WasmBridge = class { | ||
constructor(modules = [], logger = new ConsoleLogger("wasm")) { | ||
this.logger = logger; | ||
const logN = (x) => this.logger.debug(x); | ||
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", ")); | ||
this.api = { | ||
printI8: logN, | ||
printU8: logN, | ||
printI16: logN, | ||
printU16: logN, | ||
printI32: logN, | ||
printU32: (x) => this.logger.debug(x >>> 0), | ||
printI64: (x) => this.logger.debug(x), | ||
printU64: (x) => this.logger.debug(x), | ||
printF32: logN, | ||
printF64: logN, | ||
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`), | ||
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`), | ||
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`), | ||
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`), | ||
printHexdump: (addr, len) => { | ||
this.ensureMemory(); | ||
for (let line of hexdumpLines(this.u8, addr, len)) { | ||
this.logger.debug(line); | ||
} | ||
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory }); | ||
return true; | ||
} | ||
/** | ||
* Called automatically during initialization and from other memory | ||
* accessors. Initializes and/or updates the various typed WASM memory views | ||
* (e.g. after growing the WASM memory and the previous buffer becoming | ||
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED} | ||
* event will be emitted if the memory views had to be updated. | ||
* | ||
* @param notify | ||
*/ | ||
ensureMemory(notify = true) { | ||
const buf = this.exports.memory.buffer; | ||
if (this.u8 && this.u8.buffer === buf) | ||
return; | ||
this.i8 = new Int8Array(buf); | ||
this.u8 = new Uint8Array(buf); | ||
this.i16 = new Int16Array(buf); | ||
this.u16 = new Uint16Array(buf); | ||
this.i32 = new Int32Array(buf); | ||
this.u32 = new Uint32Array(buf); | ||
this.i64 = new BigInt64Array(buf); | ||
this.u64 = new BigUint64Array(buf); | ||
this.f32 = new Float32Array(buf); | ||
this.f64 = new Float64Array(buf); | ||
notify && | ||
this.notify({ | ||
id: EVENT_MEMORY_CHANGED, | ||
value: this.exports.memory, | ||
}); | ||
} | ||
/** | ||
* Required use for WASM module instantiation to provide JS imports to the | ||
* module. Returns an object of all WASM imports declared by the bridge core | ||
* API and any provided bridge API modules. | ||
* | ||
* @remarks | ||
* Each API module's imports will be in their own WASM import object/table, | ||
* named using the same key which is defined by the JS side of the module | ||
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is | ||
* reserved. | ||
* | ||
* @example | ||
* The following creates a bridge with a fictional `custom` API module: | ||
* | ||
* ```ts | ||
* const bridge = new WasmBridge([new CustomAPI()]); | ||
* | ||
* // get combined imports object | ||
* bridge.getImports(); | ||
* { | ||
* // imports defined by the core API of the bridge itself | ||
* wasmapi: { ... }, | ||
* // imports defined by the CustomAPI module | ||
* custom: { ... } | ||
* } | ||
* ``` | ||
* | ||
* Any related API bindings on the WASM (Zig) side then also need to refer | ||
* to these custom import sections (also see `/zig/core.zig`): | ||
* | ||
* ```zig | ||
* pub export "custom" fn foo(x: u32) void; | ||
* ``` | ||
*/ | ||
getImports() { | ||
if (!this.imports) { | ||
this.imports = { [this.id]: this.api }; | ||
for (let id in this.modules) { | ||
this.imports[id] = this.modules[id].getImports(); | ||
} | ||
}, | ||
_printI8Array: logA(this.getI8Array.bind(this)), | ||
_printU8Array: logA(this.getU8Array.bind(this)), | ||
_printI16Array: logA(this.getI16Array.bind(this)), | ||
_printU16Array: logA(this.getU16Array.bind(this)), | ||
_printI32Array: logA(this.getI32Array.bind(this)), | ||
_printU32Array: logA(this.getU32Array.bind(this)), | ||
_printI64Array: logA(this.getI64Array.bind(this)), | ||
_printU64Array: logA(this.getU64Array.bind(this)), | ||
_printF32Array: logA(this.getF32Array.bind(this)), | ||
_printF64Array: logA(this.getF64Array.bind(this)), | ||
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)), | ||
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)), | ||
debug: () => { | ||
debugger; | ||
}, | ||
_panic: (addr, len) => { | ||
const msg = this.getString(addr, len); | ||
if (!this.notify({ id: EVENT_PANIC, value: msg })) { | ||
throw new Panic(msg); | ||
} | ||
return this.imports; | ||
}, | ||
timer: () => performance.now(), | ||
epoch: () => BigInt(Date.now()) | ||
}; | ||
this.modules = modules.reduce((acc, x) => { | ||
assert( | ||
acc[x.id] === void 0 && x.id !== this.id, | ||
`duplicate API module ID: ${x.id}` | ||
); | ||
acc[x.id] = x; | ||
return acc; | ||
}, {}); | ||
} | ||
id = "wasmapi"; | ||
i8; | ||
u8; | ||
i16; | ||
u16; | ||
i32; | ||
u32; | ||
i64; | ||
u64; | ||
f32; | ||
f64; | ||
utf8Decoder = new TextDecoder(); | ||
utf8Encoder = new TextEncoder(); | ||
imports; | ||
exports; | ||
api; | ||
modules; | ||
/** | ||
* Instantiates WASM module from given `src` (and optional provided extra | ||
* imports), then automatically calls {@link WasmBridge.init} with the | ||
* modules exports. | ||
* | ||
* @remarks | ||
* If the given `src` is a `Response` or `Promise<Response>`, the module | ||
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise | ||
* the non-streaming version will be used. | ||
* | ||
* @param src | ||
* @param imports | ||
*/ | ||
async instantiate(src, imports) { | ||
const $src = await src; | ||
const $imports = { ...this.getImports(), ...imports }; | ||
const wasm = await ($src instanceof Response ? WebAssembly.instantiateStreaming($src, $imports) : WebAssembly.instantiate($src, $imports)); | ||
return this.init(wasm.instance.exports); | ||
} | ||
/** | ||
* Receives the WASM module's combined exports, stores them for future | ||
* reference and then initializes all declared bridge child API modules in | ||
* their stated dependency order. Returns false if any of the module | ||
* initializations failed. | ||
* | ||
* @remarks | ||
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and | ||
* AFTER all child API modules have been initialized). | ||
* | ||
* @param exports | ||
*/ | ||
async init(exports) { | ||
this.exports = exports; | ||
this.ensureMemory(false); | ||
for (let id of topoSort( | ||
this.modules, | ||
(module) => module.dependencies | ||
)) { | ||
assert(!!this.modules[id], `missing API module: ${id}`); | ||
this.logger.debug(`initializing API module: ${id}`); | ||
const status = await this.modules[id].init(this); | ||
if (!status) | ||
return false; | ||
} | ||
growMemory(numPages) { | ||
this.exports.memory.grow(numPages); | ||
this.ensureMemory(); | ||
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory }); | ||
return true; | ||
} | ||
/** | ||
* Called automatically during initialization and from other memory | ||
* accessors. Initializes and/or updates the various typed WASM memory views | ||
* (e.g. after growing the WASM memory and the previous buffer becoming | ||
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED} | ||
* event will be emitted if the memory views had to be updated. | ||
* | ||
* @param notify | ||
*/ | ||
ensureMemory(notify = true) { | ||
const buf = this.exports.memory.buffer; | ||
if (this.u8 && this.u8.buffer === buf) | ||
return; | ||
this.i8 = new Int8Array(buf); | ||
this.u8 = new Uint8Array(buf); | ||
this.i16 = new Int16Array(buf); | ||
this.u16 = new Uint16Array(buf); | ||
this.i32 = new Int32Array(buf); | ||
this.u32 = new Uint32Array(buf); | ||
this.i64 = new BigInt64Array(buf); | ||
this.u64 = new BigUint64Array(buf); | ||
this.f32 = new Float32Array(buf); | ||
this.f64 = new Float64Array(buf); | ||
notify && this.notify({ | ||
id: EVENT_MEMORY_CHANGED, | ||
value: this.exports.memory | ||
}); | ||
} | ||
/** | ||
* Required use for WASM module instantiation to provide JS imports to the | ||
* module. Returns an object of all WASM imports declared by the bridge core | ||
* API and any provided bridge API modules. | ||
* | ||
* @remarks | ||
* Each API module's imports will be in their own WASM import object/table, | ||
* named using the same key which is defined by the JS side of the module | ||
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is | ||
* reserved. | ||
* | ||
* @example | ||
* The following creates a bridge with a fictional `custom` API module: | ||
* | ||
* ```ts | ||
* const bridge = new WasmBridge([new CustomAPI()]); | ||
* | ||
* // get combined imports object | ||
* bridge.getImports(); | ||
* { | ||
* // imports defined by the core API of the bridge itself | ||
* wasmapi: { ... }, | ||
* // imports defined by the CustomAPI module | ||
* custom: { ... } | ||
* } | ||
* ``` | ||
* | ||
* Any related API bindings on the WASM (Zig) side then also need to refer | ||
* to these custom import sections (also see `/zig/core.zig`): | ||
* | ||
* ```zig | ||
* pub export "custom" fn foo(x: u32) void; | ||
* ``` | ||
*/ | ||
getImports() { | ||
if (!this.imports) { | ||
this.imports = { [this.id]: this.api }; | ||
for (let id in this.modules) { | ||
this.imports[id] = this.modules[id].getImports(); | ||
} | ||
} | ||
allocate(numBytes, clear = false) { | ||
const addr = this.exports._wasm_allocate(numBytes); | ||
if (!addr) | ||
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`); | ||
this.logger.fine(() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`); | ||
this.ensureMemory(); | ||
clear && this.u8.fill(0, addr, addr + numBytes); | ||
return [addr, numBytes]; | ||
return this.imports; | ||
} | ||
growMemory(numPages) { | ||
this.exports.memory.grow(numPages); | ||
this.ensureMemory(); | ||
} | ||
allocate(numBytes, clear = false) { | ||
const addr = this.exports._wasm_allocate(numBytes); | ||
if (!addr) | ||
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`); | ||
this.logger.fine( | ||
() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32( | ||
addr + numBytes - 1 | ||
)}` | ||
); | ||
this.ensureMemory(); | ||
clear && this.u8.fill(0, addr, addr + numBytes); | ||
return [addr, numBytes]; | ||
} | ||
free([addr, numBytes]) { | ||
this.logger.fine( | ||
() => `freeing memory @ 0x${U32(addr)} .. 0x${U32( | ||
addr + numBytes - 1 | ||
)}` | ||
); | ||
this.exports._wasm_free(addr, numBytes); | ||
} | ||
getI8(addr) { | ||
return this.i8[addr]; | ||
} | ||
getU8(addr) { | ||
return this.u8[addr]; | ||
} | ||
getI16(addr) { | ||
return this.i16[addr >> 1]; | ||
} | ||
getU16(addr) { | ||
return this.u16[addr >> 1]; | ||
} | ||
getI32(addr) { | ||
return this.i32[addr >> 2]; | ||
} | ||
getU32(addr) { | ||
return this.u32[addr >> 2]; | ||
} | ||
getI64(addr) { | ||
return this.i64[addr >> 3]; | ||
} | ||
getU64(addr) { | ||
return this.u64[addr >> 3]; | ||
} | ||
getF32(addr) { | ||
return this.f32[addr >> 2]; | ||
} | ||
getF64(addr) { | ||
return this.f64[addr >> 3]; | ||
} | ||
setI8(addr, x) { | ||
this.i8[addr] = x; | ||
return this; | ||
} | ||
setU8(addr, x) { | ||
this.u8[addr] = x; | ||
return this; | ||
} | ||
setI16(addr, x) { | ||
this.i16[addr >> 1] = x; | ||
return this; | ||
} | ||
setU16(addr, x) { | ||
this.u16[addr >> 1] = x; | ||
return this; | ||
} | ||
setI32(addr, x) { | ||
this.i32[addr >> 2] = x; | ||
return this; | ||
} | ||
setU32(addr, x) { | ||
this.u32[addr >> 2] = x; | ||
return this; | ||
} | ||
setI64(addr, x) { | ||
this.i64[addr >> 3] = x; | ||
return this; | ||
} | ||
setU64(addr, x) { | ||
this.u64[addr >> 3] = x; | ||
return this; | ||
} | ||
setF32(addr, x) { | ||
this.f32[addr >> 2] = x; | ||
return this; | ||
} | ||
setF64(addr, x) { | ||
this.f64[addr >> 3] = x; | ||
return this; | ||
} | ||
getI8Array(addr, len) { | ||
return this.i8.subarray(addr, addr + len); | ||
} | ||
getU8Array(addr, len) { | ||
return this.u8.subarray(addr, addr + len); | ||
} | ||
getI16Array(addr, len) { | ||
addr >>= 1; | ||
return this.i16.subarray(addr, addr + len); | ||
} | ||
getU16Array(addr, len) { | ||
addr >>= 1; | ||
return this.u16.subarray(addr, addr + len); | ||
} | ||
getI32Array(addr, len) { | ||
addr >>= 2; | ||
return this.i32.subarray(addr, addr + len); | ||
} | ||
getU32Array(addr, len) { | ||
addr >>= 2; | ||
return this.u32.subarray(addr, addr + len); | ||
} | ||
getI64Array(addr, len) { | ||
addr >>= 3; | ||
return this.i64.subarray(addr, addr + len); | ||
} | ||
getU64Array(addr, len) { | ||
addr >>= 3; | ||
return this.u64.subarray(addr, addr + len); | ||
} | ||
getF32Array(addr, len) { | ||
addr >>= 2; | ||
return this.f32.subarray(addr, addr + len); | ||
} | ||
getF64Array(addr, len) { | ||
addr >>= 3; | ||
return this.f64.subarray(addr, addr + len); | ||
} | ||
setI8Array(addr, buf) { | ||
this.i8.set(buf, addr); | ||
return this; | ||
} | ||
setU8Array(addr, buf) { | ||
this.u8.set(buf, addr); | ||
return this; | ||
} | ||
setI16Array(addr, buf) { | ||
this.i16.set(buf, addr >> 1); | ||
return this; | ||
} | ||
setU16Array(addr, buf) { | ||
this.u16.set(buf, addr >> 1); | ||
return this; | ||
} | ||
setI32Array(addr, buf) { | ||
this.i32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setU32Array(addr, buf) { | ||
this.u32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setI64Array(addr, buf) { | ||
this.i64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
setU64Array(addr, buf) { | ||
this.u64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
setF32Array(addr, buf) { | ||
this.f32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setF64Array(addr, buf) { | ||
this.f64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
getString(addr, len = 0) { | ||
this.ensureMemory(); | ||
return this.utf8Decoder.decode( | ||
this.u8.subarray( | ||
addr, | ||
len > 0 ? addr + len : this.u8.indexOf(0, addr) | ||
) | ||
); | ||
} | ||
setString(str, addr, maxBytes, terminate = true) { | ||
this.ensureMemory(); | ||
maxBytes = Math.min(maxBytes, this.u8.length - addr); | ||
const len = this.utf8Encoder.encodeInto( | ||
str, | ||
this.u8.subarray(addr, addr + maxBytes) | ||
).written; | ||
assert( | ||
len != null && len < maxBytes + (terminate ? 0 : 1), | ||
`error writing string to 0x${U32( | ||
addr | ||
)} (max. ${maxBytes} bytes, got at least ${str.length})` | ||
); | ||
if (terminate) { | ||
this.u8[addr + len] = 0; | ||
} | ||
free([addr, numBytes]) { | ||
this.logger.fine(() => `freeing memory @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`); | ||
this.exports._wasm_free(addr, numBytes); | ||
} | ||
getI8(addr) { | ||
return this.i8[addr]; | ||
} | ||
getU8(addr) { | ||
return this.u8[addr]; | ||
} | ||
getI16(addr) { | ||
return this.i16[addr >> 1]; | ||
} | ||
getU16(addr) { | ||
return this.u16[addr >> 1]; | ||
} | ||
getI32(addr) { | ||
return this.i32[addr >> 2]; | ||
} | ||
getU32(addr) { | ||
return this.u32[addr >> 2]; | ||
} | ||
getI64(addr) { | ||
return this.i64[addr >> 3]; | ||
} | ||
getU64(addr) { | ||
return this.u64[addr >> 3]; | ||
} | ||
getF32(addr) { | ||
return this.f32[addr >> 2]; | ||
} | ||
getF64(addr) { | ||
return this.f64[addr >> 3]; | ||
} | ||
setI8(addr, x) { | ||
this.i8[addr] = x; | ||
return this; | ||
} | ||
setU8(addr, x) { | ||
this.u8[addr] = x; | ||
return this; | ||
} | ||
setI16(addr, x) { | ||
this.i16[addr >> 1] = x; | ||
return this; | ||
} | ||
setU16(addr, x) { | ||
this.u16[addr >> 1] = x; | ||
return this; | ||
} | ||
setI32(addr, x) { | ||
this.i32[addr >> 2] = x; | ||
return this; | ||
} | ||
setU32(addr, x) { | ||
this.u32[addr >> 2] = x; | ||
return this; | ||
} | ||
setI64(addr, x) { | ||
this.i64[addr >> 3] = x; | ||
return this; | ||
} | ||
setU64(addr, x) { | ||
this.u64[addr >> 3] = x; | ||
return this; | ||
} | ||
setF32(addr, x) { | ||
this.f32[addr >> 2] = x; | ||
return this; | ||
} | ||
setF64(addr, x) { | ||
this.f64[addr >> 3] = x; | ||
return this; | ||
} | ||
getI8Array(addr, len) { | ||
return this.i8.subarray(addr, addr + len); | ||
} | ||
getU8Array(addr, len) { | ||
return this.u8.subarray(addr, addr + len); | ||
} | ||
getI16Array(addr, len) { | ||
addr >>= 1; | ||
return this.i16.subarray(addr, addr + len); | ||
} | ||
getU16Array(addr, len) { | ||
addr >>= 1; | ||
return this.u16.subarray(addr, addr + len); | ||
} | ||
getI32Array(addr, len) { | ||
addr >>= 2; | ||
return this.i32.subarray(addr, addr + len); | ||
} | ||
getU32Array(addr, len) { | ||
addr >>= 2; | ||
return this.u32.subarray(addr, addr + len); | ||
} | ||
getI64Array(addr, len) { | ||
addr >>= 3; | ||
return this.i64.subarray(addr, addr + len); | ||
} | ||
getU64Array(addr, len) { | ||
addr >>= 3; | ||
return this.u64.subarray(addr, addr + len); | ||
} | ||
getF32Array(addr, len) { | ||
addr >>= 2; | ||
return this.f32.subarray(addr, addr + len); | ||
} | ||
getF64Array(addr, len) { | ||
addr >>= 3; | ||
return this.f64.subarray(addr, addr + len); | ||
} | ||
setI8Array(addr, buf) { | ||
this.i8.set(buf, addr); | ||
return this; | ||
} | ||
setU8Array(addr, buf) { | ||
this.u8.set(buf, addr); | ||
return this; | ||
} | ||
setI16Array(addr, buf) { | ||
this.i16.set(buf, addr >> 1); | ||
return this; | ||
} | ||
setU16Array(addr, buf) { | ||
this.u16.set(buf, addr >> 1); | ||
return this; | ||
} | ||
setI32Array(addr, buf) { | ||
this.i32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setU32Array(addr, buf) { | ||
this.u32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setI64Array(addr, buf) { | ||
this.i64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
setU64Array(addr, buf) { | ||
this.u64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
setF32Array(addr, buf) { | ||
this.f32.set(buf, addr >> 2); | ||
return this; | ||
} | ||
setF64Array(addr, buf) { | ||
this.f64.set(buf, addr >> 3); | ||
return this; | ||
} | ||
getString(addr, len = 0) { | ||
this.ensureMemory(); | ||
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr))); | ||
} | ||
setString(str, addr, maxBytes, terminate = true) { | ||
this.ensureMemory(); | ||
maxBytes = Math.min(maxBytes, this.u8.length - addr); | ||
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written; | ||
assert(len != null && len < maxBytes + (terminate ? 0 : 1), `error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`); | ||
if (terminate) { | ||
this.u8[addr + len] = 0; | ||
} | ||
return len; | ||
} | ||
getElementById(addr, len = 0) { | ||
const id = this.getString(addr, len); | ||
const el = document.getElementById(id); | ||
assert(!!el, `missing DOM element #${id}`); | ||
return el; | ||
} | ||
/** {@inheritDoc @thi.ng/api#INotify.addListener} */ | ||
// @ts-ignore: mixin | ||
// prettier-ignore | ||
addListener(id, fn, scope) { } | ||
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */ | ||
// @ts-ignore: mixin | ||
// prettier-ignore | ||
removeListener(id, fn, scope) { } | ||
/** {@inheritDoc @thi.ng/api#INotify.notify} */ | ||
// @ts-ignore: mixin | ||
notify(event) { } | ||
return len; | ||
} | ||
getElementById(addr, len = 0) { | ||
const id = this.getString(addr, len); | ||
const el = document.getElementById(id); | ||
assert(!!el, `missing DOM element #${id}`); | ||
return el; | ||
} | ||
/** {@inheritDoc @thi.ng/api#INotify.addListener} */ | ||
// @ts-ignore: mixin | ||
// prettier-ignore | ||
addListener(id, fn, scope) { | ||
} | ||
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */ | ||
// @ts-ignore: mixin | ||
// prettier-ignore | ||
removeListener(id, fn, scope) { | ||
} | ||
/** {@inheritDoc @thi.ng/api#INotify.notify} */ | ||
// @ts-ignore: mixin | ||
notify(event) { | ||
} | ||
}; | ||
WasmBridge = __decorate([ | ||
INotifyMixin | ||
WasmBridge = __decorateClass([ | ||
INotifyMixin | ||
], WasmBridge); | ||
export { WasmBridge }; | ||
export { | ||
OutOfMemoryError, | ||
Panic, | ||
WasmBridge | ||
}; |
# Change Log | ||
- **Last updated**: 2023-12-09T19:12:04Z | ||
- **Last updated**: 2023-12-11T10:07:09Z | ||
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub) | ||
@@ -5,0 +5,0 @@ |
import { assert } from "@thi.ng/errors/assert"; | ||
import { IDGen } from "@thi.ng/idgen"; | ||
export class ObjectIndex { | ||
name; | ||
logger; | ||
idgen; | ||
items = []; | ||
constructor(opts) { | ||
this.name = opts.name; | ||
this.logger = opts.logger; | ||
this.idgen = new IDGen(opts.bits || 32, 0); | ||
class ObjectIndex { | ||
name; | ||
logger; | ||
idgen; | ||
items = []; | ||
constructor(opts) { | ||
this.name = opts.name; | ||
this.logger = opts.logger; | ||
this.idgen = new IDGen(opts.bits || 32, 0); | ||
} | ||
keys() { | ||
return this.idgen[Symbol.iterator](); | ||
} | ||
*values() { | ||
for (let id of this.idgen) { | ||
yield this.items[id]; | ||
} | ||
keys() { | ||
return this.idgen[Symbol.iterator](); | ||
} | ||
/** | ||
* Indexes given `item` and assigns it to the next available ID (which might | ||
* be a previously freed ID) and returns it. | ||
* | ||
* @param item | ||
*/ | ||
add(item) { | ||
const id = this.idgen.next(); | ||
this.logger && this.logger.debug(`adding ${this.name} ID: ${id}`); | ||
this.items[id] = item; | ||
return id; | ||
} | ||
/** | ||
* Returns true if the given `id` is valid/active. | ||
* | ||
* @param id | ||
*/ | ||
has(id) { | ||
return this.idgen.has(id); | ||
} | ||
/** | ||
* First checks if given `id` is valid and if so frees it (for recycling) | ||
* and deletes its corresponding item. If `ensure` is true (default), throws | ||
* an error if the ID is invalid (otherwise returns false for invalid IDs). | ||
* | ||
* @param id | ||
* @param ensure | ||
*/ | ||
delete(id, ensure = true) { | ||
if (this.idgen.has(id)) { | ||
this.logger && this.logger.debug(`deleting ${this.name} ID: ${id}`); | ||
this.idgen.free(id); | ||
delete this.items[id]; | ||
return true; | ||
} | ||
*values() { | ||
for (let id of this.idgen) { | ||
yield this.items[id]; | ||
} | ||
} | ||
/** | ||
* Indexes given `item` and assigns it to the next available ID (which might | ||
* be a previously freed ID) and returns it. | ||
* | ||
* @param item | ||
*/ | ||
add(item) { | ||
const id = this.idgen.next(); | ||
this.logger && this.logger.debug(`adding ${this.name} ID: ${id}`); | ||
this.items[id] = item; | ||
assert(!ensure, `can't delete missing ${this.name} ID: ${id}`); | ||
return false; | ||
} | ||
get(id, ensure = true) { | ||
ensure && assert(this.idgen.has(id), `missing ${this.name} for ID: ${id}`); | ||
return this.items[id]; | ||
} | ||
/** | ||
* Applies given predicate to all active items and returns ID of first | ||
* matching. If `ensure` is true (default), throws an error if the `pred` | ||
* didn't match anything (otherwise returns undefined). | ||
* | ||
* @param pred | ||
* @param ensure | ||
*/ | ||
find(pred, ensure = true) { | ||
for (let id of this.idgen) { | ||
if (pred(this.items[id])) | ||
return id; | ||
} | ||
/** | ||
* Returns true if the given `id` is valid/active. | ||
* | ||
* @param id | ||
*/ | ||
has(id) { | ||
return this.idgen.has(id); | ||
} | ||
/** | ||
* First checks if given `id` is valid and if so frees it (for recycling) | ||
* and deletes its corresponding item. If `ensure` is true (default), throws | ||
* an error if the ID is invalid (otherwise returns false for invalid IDs). | ||
* | ||
* @param id | ||
* @param ensure | ||
*/ | ||
delete(id, ensure = true) { | ||
if (this.idgen.has(id)) { | ||
this.logger && this.logger.debug(`deleting ${this.name} ID: ${id}`); | ||
this.idgen.free(id); | ||
delete this.items[id]; | ||
return true; | ||
} | ||
assert(!ensure, `can't delete missing ${this.name} ID: ${id}`); | ||
return false; | ||
} | ||
get(id, ensure = true) { | ||
ensure && | ||
assert(this.idgen.has(id), `missing ${this.name} for ID: ${id}`); | ||
return this.items[id]; | ||
} | ||
/** | ||
* Applies given predicate to all active items and returns ID of first | ||
* matching. If `ensure` is true (default), throws an error if the `pred` | ||
* didn't match anything (otherwise returns undefined). | ||
* | ||
* @param pred | ||
* @param ensure | ||
*/ | ||
find(pred, ensure = true) { | ||
for (let id of this.idgen) { | ||
if (pred(this.items[id])) | ||
return id; | ||
} | ||
assert(!ensure, `given predicate matched no ${this.name}`); | ||
} | ||
assert(!ensure, `given predicate matched no ${this.name}`); | ||
} | ||
} | ||
export { | ||
ObjectIndex | ||
}; |
{ | ||
"name": "@thi.ng/wasm-api", | ||
"version": "1.4.36", | ||
"version": "1.4.37", | ||
"description": "Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAssembly projects", | ||
@@ -27,3 +27,5 @@ "type": "module", | ||
"scripts": { | ||
"build": "yarn clean && tsc --declaration", | ||
"build": "yarn build:esbuild && yarn build:decl", | ||
"build:decl": "tsc --declaration --emitDeclarationOnly", | ||
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts", | ||
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc", | ||
@@ -38,12 +40,13 @@ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", | ||
"dependencies": { | ||
"@thi.ng/api": "^8.9.11", | ||
"@thi.ng/arrays": "^2.7.7", | ||
"@thi.ng/checks": "^3.4.11", | ||
"@thi.ng/errors": "^2.4.5", | ||
"@thi.ng/hex": "^2.3.23", | ||
"@thi.ng/idgen": "^2.2.14", | ||
"@thi.ng/logger": "^2.0.1" | ||
"@thi.ng/api": "^8.9.12", | ||
"@thi.ng/arrays": "^2.7.8", | ||
"@thi.ng/checks": "^3.4.12", | ||
"@thi.ng/errors": "^2.4.6", | ||
"@thi.ng/hex": "^2.3.24", | ||
"@thi.ng/idgen": "^2.2.15", | ||
"@thi.ng/logger": "^2.0.2" | ||
}, | ||
"devDependencies": { | ||
"@microsoft/api-extractor": "^7.38.3", | ||
"esbuild": "^0.19.8", | ||
"rimraf": "^5.0.5", | ||
@@ -118,3 +121,3 @@ "tools": "^0.0.1", | ||
}, | ||
"gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n" | ||
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n" | ||
} |
@@ -1,60 +0,36 @@ | ||
/** | ||
* Generic pointer facility which on {@link Pointer.deref()} calls wrapper | ||
* function (provided as ctor arg) to realise the pointer's target value. The | ||
* pointer's target address can be accessed via {@link Pointer.addr} | ||
* (read/write). | ||
* | ||
* @remarks | ||
* The pointer always behaves like `volatile`, i.e. memoization of target values | ||
* is purposfully avoided and the wrapper function is executed anew _each_ time | ||
* the pointer is deref'd. | ||
*/ | ||
export class Pointer { | ||
mem; | ||
base; | ||
fn; | ||
constructor(mem, base, fn) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.fn = fn; | ||
} | ||
get addr() { | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
set addr(addr) { | ||
this.mem.u32[this.base >>> 2] = addr; | ||
} | ||
deref() { | ||
return this.fn(this.addr); | ||
} | ||
class Pointer { | ||
constructor(mem, base, fn) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.fn = fn; | ||
} | ||
get addr() { | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
set addr(addr) { | ||
this.mem.u32[this.base >>> 2] = addr; | ||
} | ||
deref() { | ||
return this.fn(this.addr); | ||
} | ||
} | ||
/** | ||
* Generic pointer facility for {@link WASM64} target, which on | ||
* {@link Pointer64.deref()} calls wrapper function (provided as ctor arg) to | ||
* realise the pointer's target value. The pointer's target address can be | ||
* accessed via {@link Pointer.addr} (read/write). | ||
* | ||
* @remarks | ||
* The pointer always behaves like `volatile`, i.e. memoization of target values | ||
* is purposfully avoided and the wrapper function is executed anew _each_ time | ||
* the pointer is deref'd. | ||
*/ | ||
export class Pointer64 { | ||
mem; | ||
base; | ||
fn; | ||
constructor(mem, base, fn) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.fn = fn; | ||
} | ||
get addr() { | ||
return this.mem.u64[Number(this.base >> BigInt(3))]; | ||
} | ||
set addr(addr) { | ||
this.mem.u64[Number(this.base >> BigInt(3))] = addr; | ||
} | ||
deref() { | ||
return this.fn(this.addr); | ||
} | ||
class Pointer64 { | ||
constructor(mem, base, fn) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.fn = fn; | ||
} | ||
get addr() { | ||
return this.mem.u64[Number(this.base >> BigInt(3))]; | ||
} | ||
set addr(addr) { | ||
this.mem.u64[Number(this.base >> BigInt(3))] = addr; | ||
} | ||
deref() { | ||
return this.fn(this.addr); | ||
} | ||
} | ||
export { | ||
Pointer, | ||
Pointer64 | ||
}; |
@@ -394,3 +394,3 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
Package sizes (brotli'd, pre-treeshake): ESM: 2.65 KB | ||
Package sizes (brotli'd, pre-treeshake): ESM: 2.69 KB | ||
@@ -397,0 +397,0 @@ ## Dependencies |
415
string.js
import { isNumber } from "@thi.ng/checks/is-number"; | ||
import { unsupported } from "@thi.ng/errors/unsupported"; | ||
/** | ||
* Memory mapped string wrapper for Zig-style UTF-8 encoded byte slices (aka | ||
* pointer & length pair). The actual JS string can be obtained via | ||
* {@link WasmStringSlice.deref} and possibly mutated via | ||
* {@link WasmStringSlice.set}. | ||
* | ||
* @remarks | ||
* Currently only supports wasm32 target, need alt. solution for 64bit (possibly | ||
* diff implementation) using bigint addresses (TODO) | ||
*/ | ||
export class WasmStringSlice { | ||
mem; | ||
base; | ||
isConst; | ||
terminated; | ||
maxLen; | ||
constructor(mem, base, isConst = true, terminated = true) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.isConst = isConst; | ||
this.terminated = terminated; | ||
this.maxLen = this.length; | ||
class WasmStringSlice { | ||
constructor(mem, base, isConst = true, terminated = true) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.isConst = isConst; | ||
this.terminated = terminated; | ||
this.maxLen = this.length; | ||
} | ||
maxLen; | ||
/** | ||
* Returns string start address (deref'd pointer). | ||
*/ | ||
get addr() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
/** | ||
* Returns string length (read from memory) | ||
*/ | ||
get length() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[this.base + 4 >>> 2]; | ||
} | ||
/** | ||
* Returns memory as JS string (aka wrapper for | ||
* {@link WasmBridge.getString}). | ||
*/ | ||
deref() { | ||
return this.mem.getString(this.addr, this.length); | ||
} | ||
/** | ||
* If given a JS string as arg (and if **not** a const slice), attempts to | ||
* overwrite this wrapped string's memory with bytes from given string. If | ||
* given another {@link WasmStringSlice} as arg, only the slice pointer & | ||
* new length will be updated (always succeeds). | ||
* | ||
* @remarks | ||
* When copying bytes from a JS string, an error will be thrown if the new | ||
* string is longer than the _original_ length of the slice (i.e. from when | ||
* this `WasmStringSlice` wrapper instance was created). Also updates the | ||
* slice's length field to new string length. | ||
* | ||
* Passing a `WasmString` instance as arg is faster than JS string since | ||
* only the slice definition itself will be updated. | ||
* | ||
* @param str | ||
*/ | ||
set(str) { | ||
this.mem.ensureMemory(); | ||
if (typeof str === "string") { | ||
if (this.isConst) | ||
unsupported("can't mutate const string"); | ||
this.mem.u32[this.base + 4 >>> 2] = this.mem.setString( | ||
str, | ||
this.addr, | ||
this.maxLen + ~~this.terminated, | ||
this.terminated | ||
); | ||
} else { | ||
this.mem.u32[this.base >>> 2] = str.addr; | ||
this.mem.u32[this.base + 4 >>> 2] = str.length; | ||
} | ||
/** | ||
* Returns string start address (deref'd pointer). | ||
*/ | ||
get addr() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
/** | ||
* Returns string length (read from memory) | ||
*/ | ||
get length() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[(this.base + 4) >>> 2]; | ||
} | ||
/** | ||
* Returns memory as JS string (aka wrapper for | ||
* {@link WasmBridge.getString}). | ||
*/ | ||
deref() { | ||
return this.mem.getString(this.addr, this.length); | ||
} | ||
/** | ||
* If given a JS string as arg (and if **not** a const slice), attempts to | ||
* overwrite this wrapped string's memory with bytes from given string. If | ||
* given another {@link WasmStringSlice} as arg, only the slice pointer & | ||
* new length will be updated (always succeeds). | ||
* | ||
* @remarks | ||
* When copying bytes from a JS string, an error will be thrown if the new | ||
* string is longer than the _original_ length of the slice (i.e. from when | ||
* this `WasmStringSlice` wrapper instance was created). Also updates the | ||
* slice's length field to new string length. | ||
* | ||
* Passing a `WasmString` instance as arg is faster than JS string since | ||
* only the slice definition itself will be updated. | ||
* | ||
* @param str | ||
*/ | ||
set(str) { | ||
this.mem.ensureMemory(); | ||
if (typeof str === "string") { | ||
if (this.isConst) | ||
unsupported("can't mutate const string"); | ||
this.mem.u32[(this.base + 4) >>> 2] = this.mem.setString(str, this.addr, this.maxLen + ~~this.terminated, this.terminated); | ||
} | ||
else { | ||
this.mem.u32[this.base >>> 2] = str.addr; | ||
this.mem.u32[(this.base + 4) >>> 2] = str.length; | ||
} | ||
} | ||
setSlice(...args) { | ||
this.mem.ensureMemory(); | ||
const [slice, terminated] = ((isNumber(args[0]) | ||
? [[args[0], args[1]], args[2]] | ||
: [args[0], args[1]])); | ||
this.mem.u32[this.base >>> 2] = slice[0]; | ||
this.mem.u32[(this.base + 4) >>> 2] = slice[1]; | ||
this.terminated = terminated; | ||
return slice; | ||
} | ||
/** | ||
* Encodes given string to UTF-8 (by default zero terminated), allocates | ||
* memory for it, updates this slice and returns a {@link MemorySlice} of | ||
* the allocated region. | ||
* | ||
* @remarks | ||
* If `terminated` is true, the stored slice length will **NOT** include the | ||
* sentinel! E.g. the slice length of zero-terminated string `"abc"` is 3, | ||
* but the number of allocated bytes is 4. This is done for compatibility | ||
* with Zig's sentinel-terminated slice handling (e.g. `[:0]u8` slices). | ||
* | ||
* Regardless of `terminated` setting, the returned `MemorySlice` **always** | ||
* covers the entire allocated region! | ||
* | ||
* @param str | ||
* @param terminate | ||
*/ | ||
setAlloc(str, terminate = true) { | ||
const slice = __alloc(this.mem, str, terminate); | ||
this.setSlice(terminate ? [slice[0], slice[1] - 1] : slice, terminate); | ||
return slice; | ||
} | ||
toJSON() { | ||
return this.deref(); | ||
} | ||
toString() { | ||
return this.deref(); | ||
} | ||
valueOf() { | ||
return this.deref(); | ||
} | ||
} | ||
setSlice(...args) { | ||
this.mem.ensureMemory(); | ||
const [slice, terminated] = isNumber(args[0]) ? [[args[0], args[1]], args[2]] : [args[0], args[1]]; | ||
this.mem.u32[this.base >>> 2] = slice[0]; | ||
this.mem.u32[this.base + 4 >>> 2] = slice[1]; | ||
this.terminated = terminated; | ||
return slice; | ||
} | ||
/** | ||
* Encodes given string to UTF-8 (by default zero terminated), allocates | ||
* memory for it, updates this slice and returns a {@link MemorySlice} of | ||
* the allocated region. | ||
* | ||
* @remarks | ||
* If `terminated` is true, the stored slice length will **NOT** include the | ||
* sentinel! E.g. the slice length of zero-terminated string `"abc"` is 3, | ||
* but the number of allocated bytes is 4. This is done for compatibility | ||
* with Zig's sentinel-terminated slice handling (e.g. `[:0]u8` slices). | ||
* | ||
* Regardless of `terminated` setting, the returned `MemorySlice` **always** | ||
* covers the entire allocated region! | ||
* | ||
* @param str | ||
* @param terminate | ||
*/ | ||
setAlloc(str, terminate = true) { | ||
const slice = __alloc(this.mem, str, terminate); | ||
this.setSlice(terminate ? [slice[0], slice[1] - 1] : slice, terminate); | ||
return slice; | ||
} | ||
toJSON() { | ||
return this.deref(); | ||
} | ||
toString() { | ||
return this.deref(); | ||
} | ||
valueOf() { | ||
return this.deref(); | ||
} | ||
} | ||
/** | ||
* Memory mapped string wrapper for C-style UTF-8 encoded and **always** | ||
* zero-terminated char pointers. The actual JS string can be obtained via | ||
* {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}. | ||
*/ | ||
export class WasmStringPtr { | ||
mem; | ||
base; | ||
isConst; | ||
constructor(mem, base, isConst = true) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.isConst = isConst; | ||
class WasmStringPtr { | ||
constructor(mem, base, isConst = true) { | ||
this.mem = mem; | ||
this.base = base; | ||
this.isConst = isConst; | ||
} | ||
/** | ||
* Returns string start address (deref'd pointer). | ||
*/ | ||
get addr() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
set addr(addr) { | ||
this.mem.ensureMemory(); | ||
this.mem.u32[this.base >>> 2] = addr; | ||
} | ||
/** | ||
* Returns computed string length (scanning memory for zero sentinel) | ||
*/ | ||
get length() { | ||
this.mem.ensureMemory(); | ||
const idx = this.mem.u8.indexOf(0, this.addr); | ||
return idx >= 0 ? idx - this.addr : 0; | ||
} | ||
/** | ||
* Returns memory as JS string (aka wrapper for | ||
* {@link WasmBridge.getString}). | ||
*/ | ||
deref() { | ||
return this.mem.getString(this.addr, this.length); | ||
} | ||
/** | ||
* If given a JS string as arg (and if this `WasmStringPtr` instance itself | ||
* is not a `const` pointer), attempts to overwrite this wrapped string's | ||
* memory with bytes from given string. If given another | ||
* {@link WasmStringPtr}, it merely overrides the pointer to the new one | ||
* (always succeeds). | ||
* | ||
* @remarks | ||
* Unlike with {@link WasmStringSlice.set} this implementation which | ||
* performs bounds checking when copying bytes from a JS string, this method | ||
* only throws an error if the new string is longer than the available | ||
* memory (from the start address until the end of the WASM memory). | ||
* **Therefore, this is as (un)safe as a C pointer and should be used with | ||
* caution!** | ||
* | ||
* Passing a `WasmStringPtr` instance as arg is faster than JS string since | ||
* only the pointer itself will be updated. | ||
* | ||
* @param str | ||
*/ | ||
set(str) { | ||
const addr = this.addr; | ||
if (typeof str === "string") { | ||
if (this.isConst) | ||
unsupported("can't mutate const string"); | ||
this.mem.ensureMemory(); | ||
this.mem.setString(str, addr, this.mem.u8.byteLength - addr, true); | ||
} else { | ||
this.addr = str.addr; | ||
this.isConst = str.isConst; | ||
} | ||
/** | ||
* Returns string start address (deref'd pointer). | ||
*/ | ||
get addr() { | ||
this.mem.ensureMemory(); | ||
return this.mem.u32[this.base >>> 2]; | ||
} | ||
set addr(addr) { | ||
this.mem.ensureMemory(); | ||
this.mem.u32[this.base >>> 2] = addr; | ||
} | ||
/** | ||
* Returns computed string length (scanning memory for zero sentinel) | ||
*/ | ||
get length() { | ||
this.mem.ensureMemory(); | ||
const idx = this.mem.u8.indexOf(0, this.addr); | ||
return idx >= 0 ? idx - this.addr : 0; | ||
} | ||
/** | ||
* Returns memory as JS string (aka wrapper for | ||
* {@link WasmBridge.getString}). | ||
*/ | ||
deref() { | ||
return this.mem.getString(this.addr, this.length); | ||
} | ||
/** | ||
* If given a JS string as arg (and if this `WasmStringPtr` instance itself | ||
* is not a `const` pointer), attempts to overwrite this wrapped string's | ||
* memory with bytes from given string. If given another | ||
* {@link WasmStringPtr}, it merely overrides the pointer to the new one | ||
* (always succeeds). | ||
* | ||
* @remarks | ||
* Unlike with {@link WasmStringSlice.set} this implementation which | ||
* performs bounds checking when copying bytes from a JS string, this method | ||
* only throws an error if the new string is longer than the available | ||
* memory (from the start address until the end of the WASM memory). | ||
* **Therefore, this is as (un)safe as a C pointer and should be used with | ||
* caution!** | ||
* | ||
* Passing a `WasmStringPtr` instance as arg is faster than JS string since | ||
* only the pointer itself will be updated. | ||
* | ||
* @param str | ||
*/ | ||
set(str) { | ||
const addr = this.addr; | ||
if (typeof str === "string") { | ||
if (this.isConst) | ||
unsupported("can't mutate const string"); | ||
this.mem.ensureMemory(); | ||
this.mem.setString(str, addr, this.mem.u8.byteLength - addr, true); | ||
} | ||
else { | ||
this.addr = str.addr; | ||
this.isConst = str.isConst; | ||
} | ||
} | ||
/** | ||
* Encodes given string to UTF-8 (by default zero terminated), allocates | ||
* memory for it, updates this pointer to new address and returns allocated | ||
* {@link MemorySlice}. | ||
* | ||
* @remarks | ||
* See {@link WasmStringSlice.setAlloc} for important details. | ||
* | ||
* @param str | ||
*/ | ||
setAlloc(str) { | ||
const slice = __alloc(this.mem, str, true); | ||
this.mem.u32[this.base >>> 2] = slice[0]; | ||
return slice; | ||
} | ||
toJSON() { | ||
return this.deref(); | ||
} | ||
toString() { | ||
return this.deref(); | ||
} | ||
valueOf() { | ||
return this.deref(); | ||
} | ||
} | ||
/** | ||
* Encodes given string to UTF-8 (by default zero terminated), allocates | ||
* memory for it, updates this pointer to new address and returns allocated | ||
* {@link MemorySlice}. | ||
* | ||
* @remarks | ||
* See {@link WasmStringSlice.setAlloc} for important details. | ||
* | ||
* @param str | ||
*/ | ||
setAlloc(str) { | ||
const slice = __alloc(this.mem, str, true); | ||
this.mem.u32[this.base >>> 2] = slice[0]; | ||
return slice; | ||
} | ||
toJSON() { | ||
return this.deref(); | ||
} | ||
toString() { | ||
return this.deref(); | ||
} | ||
valueOf() { | ||
return this.deref(); | ||
} | ||
} | ||
const __alloc = (mem, str, terminate) => { | ||
const buf = new TextEncoder().encode(str); | ||
const slice = mem.allocate(buf.length + ~~terminate); | ||
if (slice[1] > 0) { | ||
mem.u8.set(buf, slice[0]); | ||
terminate && (mem.u8[slice[0] + buf.length] = 0); | ||
} | ||
return slice; | ||
const buf = new TextEncoder().encode(str); | ||
const slice = mem.allocate(buf.length + ~~terminate); | ||
if (slice[1] > 0) { | ||
mem.u8.set(buf, slice[0]); | ||
terminate && (mem.u8[slice[0] + buf.length] = 0); | ||
} | ||
return slice; | ||
}; | ||
export { | ||
WasmStringPtr, | ||
WasmStringSlice | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
137386
6
1478
Updated@thi.ng/api@^8.9.12
Updated@thi.ng/arrays@^2.7.8
Updated@thi.ng/checks@^3.4.12
Updated@thi.ng/errors@^2.4.6
Updated@thi.ng/hex@^2.3.24
Updated@thi.ng/idgen@^2.2.15
Updated@thi.ng/logger@^2.0.2