summaryrefslogtreecommitdiffstats
path: root/ext/wasm/common/whwasmutil.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 14:07:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 14:07:11 +0000
commit63847496f14c813a5d80efd5b7de0f1294ffe1e3 (patch)
tree01c7571c7c762ceee70638549a99834fdd7c411b /ext/wasm/common/whwasmutil.js
parentInitial commit. (diff)
downloadsqlite3-63847496f14c813a5d80efd5b7de0f1294ffe1e3.tar.xz
sqlite3-63847496f14c813a5d80efd5b7de0f1294ffe1e3.zip
Adding upstream version 3.45.1.upstream/3.45.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ext/wasm/common/whwasmutil.js')
-rw-r--r--ext/wasm/common/whwasmutil.js2263
1 files changed, 2263 insertions, 0 deletions
diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js
new file mode 100644
index 0000000..0437ef3
--- /dev/null
+++ b/ext/wasm/common/whwasmutil.js
@@ -0,0 +1,2263 @@
+/**
+ 2022-07-08
+
+ The author disclaims copyright to this source code. In place of a
+ legal notice, here is a blessing:
+
+ * May you do good and not evil.
+ * May you find forgiveness for yourself and forgive others.
+ * May you share freely, never taking more than you give.
+
+ ***********************************************************************
+
+ The whwasmutil is developed in conjunction with the Jaccwabyt
+ project:
+
+ https://fossil.wanderinghorse.net/r/jaccwabyt
+
+ and sqlite3:
+
+ https://sqlite.org
+
+ This file is kept in sync between both of those trees.
+
+ Maintenance reminder: If you're reading this in a tree other than
+ one of those listed above, note that this copy may be replaced with
+ upstream copies of that one from time to time. Thus the code
+ installed by this function "should not" be edited outside of those
+ projects, else it risks getting overwritten.
+*/
+/**
+ This function is intended to simplify porting around various bits
+ of WASM-related utility code from project to project.
+
+ The primary goal of this code is to replace, where possible,
+ Emscripten-generated glue code with equivalent utility code which
+ can be used in arbitrary WASM environments built with toolchains
+ other than Emscripten. As of this writing, this code is capable of
+ acting as a replacement for Emscripten's generated glue code
+ _except_ that the latter installs handlers for Emscripten-provided
+ APIs such as its "FS" (virtual filesystem) API. Loading of such
+ things still requires using Emscripten's glue, but the post-load
+ utility APIs provided by this code are still usable as replacements
+ for their sub-optimally-documented Emscripten counterparts.
+
+ Intended usage:
+
+ ```
+ globalThis.WhWasmUtilInstaller(appObject);
+ delete globalThis.WhWasmUtilInstaller;
+ ```
+
+ Its global-scope symbol is intended only to provide an easy way to
+ make it available to 3rd-party scripts and "should" be deleted
+ after calling it. That symbols is _not_ used within the library.
+
+ Forewarning: this API explicitly targets only browser
+ environments. If a given non-browser environment has the
+ capabilities needed for a given feature (e.g. TextEncoder), great,
+ but it does not go out of its way to account for them and does not
+ provide compatibility crutches for them.
+
+ It currently offers alternatives to the following
+ Emscripten-generated APIs:
+
+ - OPTIONALLY memory allocation, but how this gets imported is
+ environment-specific. Most of the following features only work
+ if allocation is available.
+
+ - WASM-exported "indirect function table" access and
+ manipulation. e.g. creating new WASM-side functions using JS
+ functions, analog to Emscripten's addFunction() and
+ uninstallFunction() but slightly different.
+
+ - Get/set specific heap memory values, analog to Emscripten's
+ getValue() and setValue().
+
+ - String length counting in UTF-8 bytes (C-style and JS strings).
+
+ - JS string to C-string conversion and vice versa, analog to
+ Emscripten's stringToUTF8Array() and friends, but with slighter
+ different interfaces.
+
+ - JS string to Uint8Array conversion, noting that browsers actually
+ already have this built in via TextEncoder.
+
+ - "Scoped" allocation, such that allocations made inside of a given
+ explicit scope will be automatically cleaned up when the scope is
+ closed. This is fundamentally similar to Emscripten's
+ stackAlloc() and friends but uses the heap instead of the stack
+ because access to the stack requires C code.
+
+ - Create JS wrappers for WASM functions, analog to Emscripten's
+ ccall() and cwrap() functions, except that the automatic
+ conversions for function arguments and return values can be
+ easily customized by the client by assigning custom function
+ signature type names to conversion functions. Essentially,
+ it's ccall() and cwrap() on steroids.
+
+ How to install...
+
+ Passing an object to this function will install the functionality
+ into that object. Afterwards, client code "should" delete the global
+ symbol.
+
+ This code requires that the target object have the following
+ properties, noting that they needn't be available until the first
+ time one of the installed APIs is used (as opposed to when this
+ function is called) except where explicitly noted:
+
+ - `exports` must be a property of the target object OR a property
+ of `target.instance` (a WebAssembly.Module instance) and it must
+ contain the symbols exported by the WASM module associated with
+ this code. In an Enscripten environment it must be set to
+ `Module['asm']` (versions <=3.1.43) or `wasmExports` (versions
+ >=3.1.44). The exports object must contain a minimum of the
+ following symbols:
+
+ - `memory`: a WebAssembly.Memory object representing the WASM
+ memory. _Alternately_, the `memory` property can be set as
+ `target.memory`, in particular if the WASM heap memory is
+ initialized in JS an _imported_ into WASM, as opposed to being
+ initialized in WASM and exported to JS.
+
+ - `__indirect_function_table`: the WebAssembly.Table object which
+ holds WASM-exported functions. This API does not strictly
+ require that the table be able to grow but it will throw if its
+ `installFunction()` is called and the table cannot grow.
+
+ In order to simplify downstream usage, if `target.exports` is not
+ set when this is called then a property access interceptor
+ (read-only, configurable, enumerable) gets installed as `exports`
+ which resolves to `target.instance.exports`, noting that the latter
+ property need not exist until the first time `target.exports` is
+ accessed.
+
+ Some APIs _optionally_ make use of the `bigIntEnabled` property of
+ the target object. It "should" be set to true if the WASM
+ environment is compiled with BigInt support, else it must be
+ false. If it is false, certain BigInt-related features will trigger
+ an exception if invoked. This property, if not set when this is
+ called, will get a default value of true only if the BigInt64Array
+ constructor is available, else it will default to false. Note that
+ having the BigInt type is not sufficient for full int64 integration
+ with WASM: the target WASM file must also have been built with
+ that support. In Emscripten that's done using the `-sWASM_BIGINT`
+ flag.
+
+ Some optional APIs require that the target have the following
+ methods:
+
+ - 'alloc()` must behave like C's `malloc()`, allocating N bytes of
+ memory and returning its pointer. In Emscripten this is
+ conventionally made available via `Module['_malloc']`. This API
+ requires that the alloc routine throw on allocation error, as
+ opposed to returning null or 0.
+
+ - 'dealloc()` must behave like C's `free()`, accepting either a
+ pointer returned from its allocation counterpart or the values
+ null/0 (for which it must be a no-op). allocating N bytes of
+ memory and returning its pointer. In Emscripten this is
+ conventionally made available via `Module['_free']`.
+
+ APIs which require allocation routines are explicitly documented as
+ such and/or have "alloc" in their names.
+
+ This code is developed and maintained in conjunction with the
+ Jaccwabyt project:
+
+ https://fossil.wanderinghorse.net/r/jaccwabbyt
+
+ More specifically:
+
+ https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js
+*/
+globalThis.WhWasmUtilInstaller = function(target){
+ 'use strict';
+ if(undefined===target.bigIntEnabled){
+ target.bigIntEnabled = !!globalThis['BigInt64Array'];
+ }
+
+ /** Throws a new Error, the message of which is the concatenation of
+ all args with a space between each. */
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+
+ if(!target.exports){
+ Object.defineProperty(target, 'exports', {
+ enumerable: true, configurable: true,
+ get: ()=>(target.instance && target.instance.exports)
+ });
+ }
+
+ /*********
+ alloc()/dealloc() auto-install...
+
+ This would be convenient but it can also cause us to pick up
+ malloc() even when the client code is using a different exported
+ allocator (who, me?), which is bad. malloc() may be exported even
+ if we're not explicitly using it and overriding the malloc()
+ function, linking ours first, is not always feasible when using a
+ malloc() proxy, as it can lead to recursion and stack overflow
+ (who, me?). So... we really need the downstream code to set up
+ target.alloc/dealloc() itself.
+ ******/
+ /******
+ if(target.exports){
+ //Maybe auto-install alloc()/dealloc()...
+ if(!target.alloc && target.exports.malloc){
+ target.alloc = function(n){
+ const m = this(n);
+ return m || toss("Allocation of",n,"byte(s) failed.");
+ }.bind(target.exports.malloc);
+ }
+
+ if(!target.dealloc && target.exports.free){
+ target.dealloc = function(ptr){
+ if(ptr) this(ptr);
+ }.bind(target.exports.free);
+ }
+ }*******/
+
+ /**
+ Pointers in WASM are currently assumed to be 32-bit, but someday
+ that will certainly change.
+ */
+ const ptrIR = target.pointerIR || 'i32';
+ const ptrSizeof = target.ptrSizeof =
+ ('i32'===ptrIR ? 4
+ : ('i64'===ptrIR
+ ? 8 : toss("Unhandled ptrSizeof:",ptrIR)));
+ /** Stores various cached state. */
+ const cache = Object.create(null);
+ /** Previously-recorded size of cache.memory.buffer, noted so that
+ we can recreate the view objects if the heap grows. */
+ cache.heapSize = 0;
+ /** WebAssembly.Memory object extracted from target.memory or
+ target.exports.memory the first time heapWrappers() is
+ called. */
+ cache.memory = null;
+ /** uninstallFunction() puts table indexes in here for reuse and
+ installFunction() extracts them. */
+ cache.freeFuncIndexes = [];
+ /**
+ Used by scopedAlloc() and friends.
+ */
+ cache.scopedAlloc = [];
+
+ cache.utf8Decoder = new TextDecoder();
+ cache.utf8Encoder = new TextEncoder('utf-8');
+
+ /**
+ For the given IR-like string in the set ('i8', 'i16', 'i32',
+ 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value
+ ending in '*', returns the sizeof for that value
+ (target.ptrSizeof in the latter case). For any other value, it
+ returns the undefined value.
+ */
+ target.sizeofIR = (n)=>{
+ switch(n){
+ case 'i8': return 1;
+ case 'i16': return 2;
+ case 'i32': case 'f32': case 'float': return 4;
+ case 'i64': case 'f64': case 'double': return 8;
+ case '*': return ptrSizeof;
+ default:
+ return (''+n).endsWith('*') ? ptrSizeof : undefined;
+ }
+ };
+
+ /**
+ If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
+ the heap has grown since the last call, updates cache.HEAPxyz.
+ Returns the cache object.
+ */
+ const heapWrappers = function(){
+ if(!cache.memory){
+ cache.memory = (target.memory instanceof WebAssembly.Memory)
+ ? target.memory : target.exports.memory;
+ }else if(cache.heapSize === cache.memory.buffer.byteLength){
+ return cache;
+ }
+ // heap is newly-acquired or has been resized....
+ const b = cache.memory.buffer;
+ cache.HEAP8 = new Int8Array(b); cache.HEAP8U = new Uint8Array(b);
+ cache.HEAP16 = new Int16Array(b); cache.HEAP16U = new Uint16Array(b);
+ cache.HEAP32 = new Int32Array(b); cache.HEAP32U = new Uint32Array(b);
+ if(target.bigIntEnabled){
+ cache.HEAP64 = new BigInt64Array(b); cache.HEAP64U = new BigUint64Array(b);
+ }
+ cache.HEAP32F = new Float32Array(b); cache.HEAP64F = new Float64Array(b);
+ cache.heapSize = b.byteLength;
+ return cache;
+ };
+
+ /** Convenience equivalent of this.heapForSize(8,false). */
+ target.heap8 = ()=>heapWrappers().HEAP8;
+
+ /** Convenience equivalent of this.heapForSize(8,true). */
+ target.heap8u = ()=>heapWrappers().HEAP8U;
+
+ /** Convenience equivalent of this.heapForSize(16,false). */
+ target.heap16 = ()=>heapWrappers().HEAP16;
+
+ /** Convenience equivalent of this.heapForSize(16,true). */
+ target.heap16u = ()=>heapWrappers().HEAP16U;
+
+ /** Convenience equivalent of this.heapForSize(32,false). */
+ target.heap32 = ()=>heapWrappers().HEAP32;
+
+ /** Convenience equivalent of this.heapForSize(32,true). */
+ target.heap32u = ()=>heapWrappers().HEAP32U;
+
+ /**
+ Requires n to be one of:
+
+ - integer 8, 16, or 32.
+ - A integer-type TypedArray constructor: Int8Array, Int16Array,
+ Int32Array, or their Uint counterparts.
+
+ If this.bigIntEnabled is true, it also accepts the value 64 or a
+ BigInt64Array/BigUint64Array, else it throws if passed 64 or one
+ of those constructors.
+
+ Returns an integer-based TypedArray view of the WASM heap
+ memory buffer associated with the given block size. If passed
+ an integer as the first argument and unsigned is truthy then
+ the "U" (unsigned) variant of that view is returned, else the
+ signed variant is returned. If passed a TypedArray value, the
+ 2nd argument is ignored. Note that Float32Array and
+ Float64Array views are not supported by this function.
+
+ Note that growth of the heap will invalidate any references to
+ this heap, so do not hold a reference longer than needed and do
+ not use a reference after any operation which may
+ allocate. Instead, re-fetch the reference by calling this
+ function again.
+
+ Throws if passed an invalid n.
+
+ Pedantic side note: the name "heap" is a bit of a misnomer. In a
+ WASM environment, the stack and heap memory are all accessed via
+ the same view(s) of the memory.
+ */
+ target.heapForSize = function(n,unsigned = true){
+ let ctor;
+ const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
+ ? cache : heapWrappers();
+ switch(n){
+ case Int8Array: return c.HEAP8; case Uint8Array: return c.HEAP8U;
+ case Int16Array: return c.HEAP16; case Uint16Array: return c.HEAP16U;
+ case Int32Array: return c.HEAP32; case Uint32Array: return c.HEAP32U;
+ case 8: return unsigned ? c.HEAP8U : c.HEAP8;
+ case 16: return unsigned ? c.HEAP16U : c.HEAP16;
+ case 32: return unsigned ? c.HEAP32U : c.HEAP32;
+ case 64:
+ if(c.HEAP64) return unsigned ? c.HEAP64U : c.HEAP64;
+ break;
+ default:
+ if(target.bigIntEnabled){
+ if(n===globalThis['BigUint64Array']) return c.HEAP64U;
+ else if(n===globalThis['BigInt64Array']) return c.HEAP64;
+ break;
+ }
+ }
+ toss("Invalid heapForSize() size: expecting 8, 16, 32,",
+ "or (if BigInt is enabled) 64.");
+ };
+
+ /**
+ Returns the WASM-exported "indirect function table."
+ */
+ target.functionTable = function(){
+ return target.exports.__indirect_function_table;
+ /** -----------------^^^^^ "seems" to be a standardized export name.
+ From Emscripten release notes from 2020-09-10:
+ - Use `__indirect_function_table` as the import name for the
+ table, which is what LLVM does.
+ */
+ };
+
+ /**
+ Given a function pointer, returns the WASM function table entry
+ if found, else returns a falsy value: undefined if fptr is out of
+ range or null if it's in range but the table entry is empty.
+ */
+ target.functionEntry = function(fptr){
+ const ft = target.functionTable();
+ return fptr < ft.length ? ft.get(fptr) : undefined;
+ };
+
+ /**
+ Creates a WASM function which wraps the given JS function and
+ returns the JS binding of that WASM function. The signature
+ string must be the Jaccwabyt-format or Emscripten
+ addFunction()-format function signature string. In short: in may
+ have one of the following formats:
+
+ - Emscripten: `"x..."`, where the first x is a letter representing
+ the result type and subsequent letters represent the argument
+ types. Functions with no arguments have only a single
+ letter. See below.
+
+ - Jaccwabyt: `"x(...)"` where `x` is the letter representing the
+ result type and letters in the parens (if any) represent the
+ argument types. Functions with no arguments use `x()`. See
+ below.
+
+ Supported letters:
+
+ - `i` = int32
+ - `p` = int32 ("pointer")
+ - `j` = int64
+ - `f` = float32
+ - `d` = float64
+ - `v` = void, only legal for use as the result type
+
+ It throws if an invalid signature letter is used.
+
+ Jaccwabyt-format signatures support some additional letters which
+ have no special meaning here but (in this context) act as aliases
+ for other letters:
+
+ - `s`, `P`: same as `p`
+
+ Sidebar: this code is developed together with Jaccwabyt, thus the
+ support for its signature format.
+
+ The arguments may be supplied in either order: (func,sig) or
+ (sig,func).
+ */
+ target.jsFuncToWasm = function f(func, sig){
+ /** Attribution: adapted up from Emscripten-generated glue code,
+ refactored primarily for efficiency's sake, eliminating
+ call-local functions and superfluous temporary arrays. */
+ if(!f._){/*static init...*/
+ f._ = {
+ // Map of signature letters to type IR values
+ sigTypes: Object.assign(Object.create(null),{
+ i: 'i32', p: 'i32', P: 'i32', s: 'i32',
+ j: 'i64', f: 'f32', d: 'f64'
+ }),
+ // Map of type IR values to WASM type code values
+ typeCodes: Object.assign(Object.create(null),{
+ f64: 0x7c, f32: 0x7d, i64: 0x7e, i32: 0x7f
+ }),
+ /** Encodes n, which must be <2^14 (16384), into target array
+ tgt, as a little-endian value, using the given method
+ ('push' or 'unshift'). */
+ uleb128Encode: function(tgt, method, n){
+ if(n<128) tgt[method](n);
+ else tgt[method]( (n % 128) | 128, n>>7);
+ },
+ /** Intentionally-lax pattern for Jaccwabyt-format function
+ pointer signatures, the intent of which is simply to
+ distinguish them from Emscripten-format signatures. The
+ downstream checks are less lax. */
+ rxJSig: /^(\w)\((\w*)\)$/,
+ /** Returns the parameter-value part of the given signature
+ string. */
+ sigParams: function(sig){
+ const m = f._.rxJSig.exec(sig);
+ return m ? m[2] : sig.substr(1);
+ },
+ /** Returns the IR value for the given letter or throws
+ if the letter is invalid. */
+ letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x),
+ /** Returns an object describing the result type and parameter
+ type(s) of the given function signature, or throws if the
+ signature is invalid. */
+ /******** // only valid for use with the WebAssembly.Function ctor, which
+ // is not yet documented on MDN.
+ sigToWasm: function(sig){
+ const rc = {parameters:[], results: []};
+ if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0]));
+ for(const x of f._.sigParams(sig)){
+ rc.parameters.push(f._.typeCodes(x));
+ }
+ return rc;
+ },************/
+ /** Pushes the WASM data type code for the given signature
+ letter to the given target array. Throws if letter is
+ invalid. */
+ pushSigType: (dest, letter)=>dest.push(f._.typeCodes[f._.letterType(letter)])
+ };
+ }/*static init*/
+ if('string'===typeof func){
+ const x = sig;
+ sig = func;
+ func = x;
+ }
+ const sigParams = f._.sigParams(sig);
+ const wasmCode = [0x01/*count: 1*/, 0x60/*function*/];
+ f._.uleb128Encode(wasmCode, 'push', sigParams.length);
+ for(const x of sigParams) f._.pushSigType(wasmCode, x);
+ if('v'===sig[0]) wasmCode.push(0);
+ else{
+ wasmCode.push(1);
+ f._.pushSigType(wasmCode, sig[0]);
+ }
+ f._.uleb128Encode(wasmCode, 'unshift', wasmCode.length)/* type section length */;
+ wasmCode.unshift(
+ 0x00, 0x61, 0x73, 0x6d, /* magic: "\0asm" */
+ 0x01, 0x00, 0x00, 0x00, /* version: 1 */
+ 0x01 /* type section code */
+ );
+ wasmCode.push(
+ /* import section: */ 0x02, 0x07,
+ /* (import "e" "f" (func 0 (type 0))): */
+ 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
+ /* export section: */ 0x07, 0x05,
+ /* (export "f" (func 0 (type 0))): */
+ 0x01, 0x01, 0x66, 0x00, 0x00
+ );
+ return (new WebAssembly.Instance(
+ new WebAssembly.Module(new Uint8Array(wasmCode)), {
+ e: { f: func }
+ })).exports['f'];
+ }/*jsFuncToWasm()*/;
+
+ /**
+ Documented as target.installFunction() except for the 3rd
+ argument: if truthy, the newly-created function pointer
+ is stashed in the current scoped-alloc scope and will be
+ cleaned up at the matching scopedAllocPop(), else it
+ is not stashed there.
+ */
+ const __installFunction = function f(func, sig, scoped){
+ if(scoped && !cache.scopedAlloc.length){
+ toss("No scopedAllocPush() scope is active.");
+ }
+ if('string'===typeof func){
+ const x = sig;
+ sig = func;
+ func = x;
+ }
+ if('string'!==typeof sig || !(func instanceof Function)){
+ toss("Invalid arguments: expecting (function,signature) "+
+ "or (signature,function).");
+ }
+ const ft = target.functionTable();
+ const oldLen = ft.length;
+ let ptr;
+ while(cache.freeFuncIndexes.length){
+ ptr = cache.freeFuncIndexes.pop();
+ if(ft.get(ptr)){ /* Table was modified via a different API */
+ ptr = null;
+ continue;
+ }else{
+ break;
+ }
+ }
+ if(!ptr){
+ ptr = oldLen;
+ ft.grow(1);
+ }
+ try{
+ /*this will only work if func is a WASM-exported function*/
+ ft.set(ptr, func);
+ if(scoped){
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
+ }
+ return ptr;
+ }catch(e){
+ if(!(e instanceof TypeError)){
+ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
+ throw e;
+ }
+ }
+ // It's not a WASM-exported function, so compile one...
+ try {
+ const fptr = target.jsFuncToWasm(func, sig);
+ ft.set(ptr, fptr);
+ if(scoped){
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
+ }
+ }catch(e){
+ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
+ throw e;
+ }
+ return ptr;
+ };
+
+ /**
+ Expects a JS function and signature, exactly as for
+ this.jsFuncToWasm(). It uses that function to create a
+ WASM-exported function, installs that function to the next
+ available slot of this.functionTable(), and returns the
+ function's index in that table (which acts as a pointer to that
+ function). The returned pointer can be passed to
+ uninstallFunction() to uninstall it and free up the table slot for
+ reuse.
+
+ If passed (string,function) arguments then it treats the first
+ argument as the signature and second as the function.
+
+ As a special case, if the passed-in function is a WASM-exported
+ function then the signature argument is ignored and func is
+ installed as-is, without requiring re-compilation/re-wrapping.
+
+ This function will propagate an exception if
+ WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
+ The former case can happen in an Emscripten-compiled
+ environment when building without Emscripten's
+ `-sALLOW_TABLE_GROWTH` flag.
+
+ Sidebar: this function differs from Emscripten's addFunction()
+ _primarily_ in that it does not share that function's
+ undocumented behavior of reusing a function if it's passed to
+ addFunction() more than once, which leads to uninstallFunction()
+ breaking clients which do not take care to avoid that case:
+
+ https://github.com/emscripten-core/emscripten/issues/17323
+ */
+ target.installFunction = (func, sig)=>__installFunction(func, sig, false);
+
+ /**
+ Works exactly like installFunction() but requires that a
+ scopedAllocPush() is active and uninstalls the given function
+ when that alloc scope is popped via scopedAllocPop().
+ This is used for implementing JS/WASM function bindings which
+ should only persist for the life of a call into a single
+ C-side function.
+ */
+ target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true);
+
+ /**
+ Requires a pointer value previously returned from
+ this.installFunction(). Removes that function from the WASM
+ function table, marks its table slot as free for re-use, and
+ returns that function. It is illegal to call this before
+ installFunction() has been called and results are undefined if
+ ptr was not returned by that function. The returned function
+ may be passed back to installFunction() to reinstall it.
+
+ To simplify certain use cases, if passed a falsy non-0 value
+ (noting that 0 is a valid function table index), this function
+ has no side effects and returns undefined.
+ */
+ target.uninstallFunction = function(ptr){
+ if(!ptr && 0!==ptr) return undefined;
+ const fi = cache.freeFuncIndexes;
+ const ft = target.functionTable();
+ fi.push(ptr);
+ const rc = ft.get(ptr);
+ ft.set(ptr, null);
+ return rc;
+ };
+
+ /**
+ Given a WASM heap memory address and a data type name in the form
+ (i8, i16, i32, i64, float (or f32), double (or f64)), this
+ fetches the numeric value from that address and returns it as a
+ number or, for the case of type='i64', a BigInt (noting that that
+ type triggers an exception if this.bigIntEnabled is
+ falsy). Throws if given an invalid type.
+
+ If the first argument is an array, it is treated as an array of
+ addresses and the result is an array of the values from each of
+ those address, using the same 2nd argument for determining the
+ value type to fetch.
+
+ As a special case, if type ends with a `*`, it is considered to
+ be a pointer type and is treated as the WASM numeric type
+ appropriate for the pointer size (`i32`).
+
+ While likely not obvious, this routine and its poke()
+ counterpart are how pointer-to-value _output_ parameters
+ in WASM-compiled C code can be interacted with:
+
+ ```
+ const ptr = alloc(4);
+ poke(ptr, 0, 'i32'); // clear the ptr's value
+ aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x);
+ const result = peek(ptr, 'i32'); // fetch ptr's value
+ dealloc(ptr);
+ ```
+
+ scopedAlloc() and friends can be used to make handling of
+ `ptr` safe against leaks in the case of an exception:
+
+ ```
+ let result;
+ const scope = scopedAllocPush();
+ try{
+ const ptr = scopedAlloc(4);
+ poke(ptr, 0, 'i32');
+ aCFuncWithOutputPtrArg( ptr );
+ result = peek(ptr, 'i32');
+ }finally{
+ scopedAllocPop(scope);
+ }
+ ```
+
+ As a rule poke() must be called to set (typically zero
+ out) the pointer's value, else it will contain an essentially
+ random value.
+
+ ACHTUNG: calling this often, e.g. in a loop, can have a noticably
+ painful impact on performance. Rather than doing so, use
+ heapForSize() to fetch the heap object and read directly from it.
+
+ See: poke()
+ */
+ target.peek = function f(ptr, type='i8'){
+ if(type.endsWith('*')) type = ptrIR;
+ const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
+ ? cache : heapWrappers();
+ const list = Array.isArray(ptr) ? [] : undefined;
+ let rc;
+ do{
+ if(list) ptr = arguments[0].shift();
+ switch(type){
+ case 'i1':
+ case 'i8': rc = c.HEAP8[ptr>>0]; break;
+ case 'i16': rc = c.HEAP16[ptr>>1]; break;
+ case 'i32': rc = c.HEAP32[ptr>>2]; break;
+ case 'float': case 'f32': rc = c.HEAP32F[ptr>>2]; break;
+ case 'double': case 'f64': rc = Number(c.HEAP64F[ptr>>3]); break;
+ case 'i64':
+ if(target.bigIntEnabled){
+ rc = BigInt(c.HEAP64[ptr>>3]);
+ break;
+ }
+ /* fallthru */
+ default:
+ toss('Invalid type for peek():',type);
+ }
+ if(list) list.push(rc);
+ }while(list && arguments[0].length);
+ return list || rc;
+ };
+
+ /**
+ The counterpart of peek(), this sets a numeric value at
+ the given WASM heap address, using the type to define how many
+ bytes are written. Throws if given an invalid type. See
+ peek() for details about the type argument. If the 3rd
+ argument ends with `*` then it is treated as a pointer type and
+ this function behaves as if the 3rd argument were `i32`.
+
+ If the first argument is an array, it is treated like a list
+ of pointers and the given value is written to each one.
+
+ Returns `this`. (Prior to 2022-12-09 it returns this function.)
+
+ ACHTUNG: calling this often, e.g. in a loop, can have a noticably
+ painful impact on performance. Rather than doing so, use
+ heapForSize() to fetch the heap object and assign directly to it
+ or use the heap's set() method.
+ */
+ target.poke = function(ptr, value, type='i8'){
+ if (type.endsWith('*')) type = ptrIR;
+ const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
+ ? cache : heapWrappers();
+ for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
+ switch (type) {
+ case 'i1':
+ case 'i8': c.HEAP8[p>>0] = value; continue;
+ case 'i16': c.HEAP16[p>>1] = value; continue;
+ case 'i32': c.HEAP32[p>>2] = value; continue;
+ case 'float': case 'f32': c.HEAP32F[p>>2] = value; continue;
+ case 'double': case 'f64': c.HEAP64F[p>>3] = value; continue;
+ case 'i64':
+ if(c.HEAP64){
+ c.HEAP64[p>>3] = BigInt(value);
+ continue;
+ }
+ /* fallthru */
+ default:
+ toss('Invalid type for poke(): ' + type);
+ }
+ }
+ return this;
+ };
+
+ /**
+ Convenience form of peek() intended for fetching
+ pointer-to-pointer values. If passed a single non-array argument
+ it returns the value of that one pointer address. If passed
+ multiple arguments, or a single array of arguments, it returns an
+ array of their values.
+ */
+ target.peekPtr = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), ptrIR );
+
+ /**
+ A variant of poke() intended for setting pointer-to-pointer
+ values. Its differences from poke() are that (1) it defaults to a
+ value of 0 and (2) it always writes to the pointer-sized heap
+ view.
+ */
+ target.pokePtr = (ptr, value=0)=>target.poke(ptr, value, ptrIR);
+
+ /**
+ Convenience form of peek() intended for fetching i8 values. If
+ passed a single non-array argument it returns the value of that
+ one pointer address. If passed multiple arguments, or a single
+ array of arguments, it returns an array of their values.
+ */
+ target.peek8 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i8' );
+ /**
+ Convience form of poke() intended for setting individual bytes.
+ Its difference from poke() is that it always writes to the
+ i8-sized heap view.
+ */
+ target.poke8 = (ptr, value)=>target.poke(ptr, value, 'i8');
+ /** i16 variant of peek8(). */
+ target.peek16 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i16' );
+ /** i16 variant of poke8(). */
+ target.poke16 = (ptr, value)=>target.poke(ptr, value, 'i16');
+ /** i32 variant of peek8(). */
+ target.peek32 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i32' );
+ /** i32 variant of poke8(). */
+ target.poke32 = (ptr, value)=>target.poke(ptr, value, 'i32');
+ /** i64 variant of peek8(). Will throw if this build is not
+ configured for BigInt support. */
+ target.peek64 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i64' );
+ /** i64 variant of poke8(). Will throw if this build is not
+ configured for BigInt support. Note that this returns
+ a BigInt-type value, not a Number-type value. */
+ target.poke64 = (ptr, value)=>target.poke(ptr, value, 'i64');
+ /** f32 variant of peek8(). */
+ target.peek32f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f32' );
+ /** f32 variant of poke8(). */
+ target.poke32f = (ptr, value)=>target.poke(ptr, value, 'f32');
+ /** f64 variant of peek8(). */
+ target.peek64f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f64' );
+ /** f64 variant of poke8(). */
+ target.poke64f = (ptr, value)=>target.poke(ptr, value, 'f64');
+
+ /** Deprecated alias for getMemValue() */
+ target.getMemValue = target.peek;
+ /** Deprecated alias for peekPtr() */
+ target.getPtrValue = target.peekPtr;
+ /** Deprecated alias for poke() */
+ target.setMemValue = target.poke;
+ /** Deprecated alias for pokePtr() */
+ target.setPtrValue = target.pokePtr;
+
+ /**
+ Returns true if the given value appears to be legal for use as
+ a WASM pointer value. Its _range_ of values is not (cannot be)
+ validated except to ensure that it is a 32-bit integer with a
+ value of 0 or greater. Likewise, it cannot verify whether the
+ value actually refers to allocated memory in the WASM heap.
+ */
+ target.isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0);
+
+ /**
+ isPtr() is an alias for isPtr32(). If/when 64-bit WASM pointer
+ support becomes widespread, it will become an alias for either
+ isPtr32() or the as-yet-hypothetical isPtr64(), depending on a
+ configuration option.
+ */
+ target.isPtr = target.isPtr32;
+
+ /**
+ Expects ptr to be a pointer into the WASM heap memory which
+ refers to a NUL-terminated C-style string encoded as UTF-8.
+ Returns the length, in bytes, of the string, as for `strlen(3)`.
+ As a special case, if !ptr or if it's not a pointer then it
+ returns `null`. Throws if ptr is out of range for
+ target.heap8u().
+ */
+ target.cstrlen = function(ptr){
+ if(!ptr || !target.isPtr(ptr)) return null;
+ const h = heapWrappers().HEAP8U;
+ let pos = ptr;
+ for( ; h[pos] !== 0; ++pos ){}
+ return pos - ptr;
+ };
+
+ /** Internal helper to use in operations which need to distinguish
+ between SharedArrayBuffer heap memory and non-shared heap. */
+ const __SAB = ('undefined'===typeof SharedArrayBuffer)
+ ? function(){} : SharedArrayBuffer;
+ const __utf8Decode = function(arrayBuffer, begin, end){
+ return cache.utf8Decoder.decode(
+ (arrayBuffer.buffer instanceof __SAB)
+ ? arrayBuffer.slice(begin, end)
+ : arrayBuffer.subarray(begin, end)
+ );
+ };
+
+ /**
+ Expects ptr to be a pointer into the WASM heap memory which
+ refers to a NUL-terminated C-style string encoded as UTF-8. This
+ function counts its byte length using cstrlen() then returns a
+ JS-format string representing its contents. As a special case, if
+ ptr is falsy or not a pointer, `null` is returned.
+ */
+ target.cstrToJs = function(ptr){
+ const n = target.cstrlen(ptr);
+ return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : "");
+ };
+
+ /**
+ Given a JS string, this function returns its UTF-8 length in
+ bytes. Returns null if str is not a string.
+ */
+ target.jstrlen = function(str){
+ /** Attribution: derived from Emscripten's lengthBytesUTF8() */
+ if('string'!==typeof str) return null;
+ const n = str.length;
+ let len = 0;
+ for(let i = 0; i < n; ++i){
+ let u = str.charCodeAt(i);
+ if(u>=0xd800 && u<=0xdfff){
+ u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
+ }
+ if(u<=0x7f) ++len;
+ else if(u<=0x7ff) len += 2;
+ else if(u<=0xffff) len += 3;
+ else len += 4;
+ }
+ return len;
+ };
+
+ /**
+ Encodes the given JS string as UTF8 into the given TypedArray
+ tgt, starting at the given offset and writing, at most, maxBytes
+ bytes (including the NUL terminator if addNul is true, else no
+ NUL is added). If it writes any bytes at all and addNul is true,
+ it always NUL-terminates the output, even if doing so means that
+ the NUL byte is all that it writes.
+
+ If maxBytes is negative (the default) then it is treated as the
+ remaining length of tgt, starting at the given offset.
+
+ If writing the last character would surpass the maxBytes count
+ because the character is multi-byte, that character will not be
+ written (as opposed to writing a truncated multi-byte character).
+ This can lead to it writing as many as 3 fewer bytes than
+ maxBytes specifies.
+
+ Returns the number of bytes written to the target, _including_
+ the NUL terminator (if any). If it returns 0, it wrote nothing at
+ all, which can happen if:
+
+ - str is empty and addNul is false.
+ - offset < 0.
+ - maxBytes == 0.
+ - maxBytes is less than the byte length of a multi-byte str[0].
+
+ Throws if tgt is not an Int8Array or Uint8Array.
+
+ Design notes:
+
+ - In C's strcpy(), the destination pointer is the first
+ argument. That is not the case here primarily because the 3rd+
+ arguments are all referring to the destination, so it seems to
+ make sense to have them grouped with it.
+
+ - Emscripten's counterpart of this function (stringToUTF8Array())
+ returns the number of bytes written sans NUL terminator. That
+ is, however, ambiguous: str.length===0 or maxBytes===(0 or 1)
+ all cause 0 to be returned.
+ */
+ target.jstrcpy = function(jstr, tgt, offset = 0, maxBytes = -1, addNul = true){
+ /** Attribution: the encoding bits are taken from Emscripten's
+ stringToUTF8Array(). */
+ if(!tgt || (!(tgt instanceof Int8Array) && !(tgt instanceof Uint8Array))){
+ toss("jstrcpy() target must be an Int8Array or Uint8Array.");
+ }
+ if(maxBytes<0) maxBytes = tgt.length - offset;
+ if(!(maxBytes>0) || !(offset>=0)) return 0;
+ let i = 0, max = jstr.length;
+ const begin = offset, end = offset + maxBytes - (addNul ? 1 : 0);
+ for(; i < max && offset < end; ++i){
+ let u = jstr.charCodeAt(i);
+ if(u>=0xd800 && u<=0xdfff){
+ u = 0x10000 + ((u & 0x3FF) << 10) | (jstr.charCodeAt(++i) & 0x3FF);
+ }
+ if(u<=0x7f){
+ if(offset >= end) break;
+ tgt[offset++] = u;
+ }else if(u<=0x7ff){
+ if(offset + 1 >= end) break;
+ tgt[offset++] = 0xC0 | (u >> 6);
+ tgt[offset++] = 0x80 | (u & 0x3f);
+ }else if(u<=0xffff){
+ if(offset + 2 >= end) break;
+ tgt[offset++] = 0xe0 | (u >> 12);
+ tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
+ tgt[offset++] = 0x80 | (u & 0x3f);
+ }else{
+ if(offset + 3 >= end) break;
+ tgt[offset++] = 0xf0 | (u >> 18);
+ tgt[offset++] = 0x80 | ((u >> 12) & 0x3f);
+ tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
+ tgt[offset++] = 0x80 | (u & 0x3f);
+ }
+ }
+ if(addNul) tgt[offset++] = 0;
+ return offset - begin;
+ };
+
+ /**
+ Works similarly to C's strncpy(), copying, at most, n bytes (not
+ characters) from srcPtr to tgtPtr. It copies until n bytes have
+ been copied or a 0 byte is reached in src. _Unlike_ strncpy(), it
+ returns the number of bytes it assigns in tgtPtr, _including_ the
+ NUL byte (if any). If n is reached before a NUL byte in srcPtr,
+ tgtPtr will _not_ be NULL-terminated. If a NUL byte is reached
+ before n bytes are copied, tgtPtr will be NUL-terminated.
+
+ If n is negative, cstrlen(srcPtr)+1 is used to calculate it, the
+ +1 being for the NUL byte.
+
+ Throws if tgtPtr or srcPtr are falsy. Results are undefined if:
+
+ - either is not a pointer into the WASM heap or
+
+ - srcPtr is not NUL-terminated AND n is less than srcPtr's
+ logical length.
+
+ ACHTUNG: it is possible to copy partial multi-byte characters
+ this way, and converting such strings back to JS strings will
+ have undefined results.
+ */
+ target.cstrncpy = function(tgtPtr, srcPtr, n){
+ if(!tgtPtr || !srcPtr) toss("cstrncpy() does not accept NULL strings.");
+ if(n<0) n = target.cstrlen(strPtr)+1;
+ else if(!(n>0)) return 0;
+ const heap = target.heap8u();
+ let i = 0, ch;
+ for(; i < n && (ch = heap[srcPtr+i]); ++i){
+ heap[tgtPtr+i] = ch;
+ }
+ if(i<n) heap[tgtPtr + i++] = 0;
+ return i;
+ };
+
+ /**
+ For the given JS string, returns a Uint8Array of its contents
+ encoded as UTF-8. If addNul is true, the returned array will have
+ a trailing 0 entry, else it will not.
+ */
+ target.jstrToUintArray = (str, addNul=false)=>{
+ return cache.utf8Encoder.encode(addNul ? (str+"\0") : str);
+ // Or the hard way...
+ /** Attribution: derived from Emscripten's stringToUTF8Array() */
+ //const a = [], max = str.length;
+ //let i = 0, pos = 0;
+ //for(; i < max; ++i){
+ // let u = str.charCodeAt(i);
+ // if(u>=0xd800 && u<=0xdfff){
+ // u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
+ // }
+ // if(u<=0x7f) a[pos++] = u;
+ // else if(u<=0x7ff){
+ // a[pos++] = 0xC0 | (u >> 6);
+ // a[pos++] = 0x80 | (u & 63);
+ // }else if(u<=0xffff){
+ // a[pos++] = 0xe0 | (u >> 12);
+ // a[pos++] = 0x80 | ((u >> 6) & 63);
+ // a[pos++] = 0x80 | (u & 63);
+ // }else{
+ // a[pos++] = 0xf0 | (u >> 18);
+ // a[pos++] = 0x80 | ((u >> 12) & 63);
+ // a[pos++] = 0x80 | ((u >> 6) & 63);
+ // a[pos++] = 0x80 | (u & 63);
+ // }
+ // }
+ // return new Uint8Array(a);
+ };
+
+ const __affirmAlloc = (obj,funcName)=>{
+ if(!(obj.alloc instanceof Function) ||
+ !(obj.dealloc instanceof Function)){
+ toss("Object is missing alloc() and/or dealloc() function(s)",
+ "required by",funcName+"().");
+ }
+ };
+
+ const __allocCStr = function(jstr, returnWithLength, allocator, funcName){
+ __affirmAlloc(target, funcName);
+ if('string'!==typeof jstr) return null;
+ if(0){/* older impl, possibly more widely compatible? */
+ const n = target.jstrlen(jstr),
+ ptr = allocator(n+1);
+ target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true);
+ return returnWithLength ? [ptr, n] : ptr;
+ }else{/* newer, (probably) faster and (certainly) simpler impl */
+ const u = cache.utf8Encoder.encode(jstr),
+ ptr = allocator(u.length+1),
+ heap = heapWrappers().HEAP8U;
+ heap.set(u, ptr);
+ heap[ptr + u.length] = 0;
+ return returnWithLength ? [ptr, u.length] : ptr;
+ }
+ };
+
+ /**
+ Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1
+ bytes of memory, copies jstr to that memory using jstrcpy(),
+ NUL-terminates it, and returns the pointer to that C-string.
+ Ownership of the pointer is transfered to the caller, who must
+ eventually pass the pointer to dealloc() to free it.
+
+ If passed a truthy 2nd argument then its return semantics change:
+ it returns [ptr,n], where ptr is the C-string's pointer and n is
+ its cstrlen().
+
+ Throws if `target.alloc` or `target.dealloc` are not functions.
+ */
+ target.allocCString =
+ (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
+ target.alloc, 'allocCString()');
+
+ /**
+ Starts an "allocation scope." All allocations made using
+ scopedAlloc() are recorded in this scope and are freed when the
+ value returned from this function is passed to
+ scopedAllocPop().
+
+ This family of functions requires that the API's object have both
+ `alloc()` and `dealloc()` methods, else this function will throw.
+
+ Intended usage:
+
+ ```
+ const scope = scopedAllocPush();
+ try {
+ const ptr1 = scopedAlloc(100);
+ const ptr2 = scopedAlloc(200);
+ const ptr3 = scopedAlloc(300);
+ ...
+ // Note that only allocations made via scopedAlloc()
+ // are managed by this allocation scope.
+ }finally{
+ scopedAllocPop(scope);
+ }
+ ```
+
+ The value returned by this function must be treated as opaque by
+ the caller, suitable _only_ for passing to scopedAllocPop().
+ Its type and value are not part of this function's API and may
+ change in any given version of this code.
+
+ `scopedAlloc.level` can be used to determine how many scoped
+ alloc levels are currently active.
+ */
+ target.scopedAllocPush = function(){
+ __affirmAlloc(target, 'scopedAllocPush');
+ const a = [];
+ cache.scopedAlloc.push(a);
+ return a;
+ };
+
+ /**
+ Cleans up all allocations made using scopedAlloc() in the context
+ of the given opaque state object, which must be a value returned
+ by scopedAllocPush(). See that function for an example of how to
+ use this function.
+
+ Though scoped allocations are managed like a stack, this API
+ behaves properly if allocation scopes are popped in an order
+ other than the order they were pushed.
+
+ If called with no arguments, it pops the most recent
+ scopedAllocPush() result:
+
+ ```
+ scopedAllocPush();
+ try{ ... } finally { scopedAllocPop(); }
+ ```
+
+ It's generally recommended that it be passed an explicit argument
+ to help ensure that push/push are used in matching pairs, but in
+ trivial code that may be a non-issue.
+ */
+ target.scopedAllocPop = function(state){
+ __affirmAlloc(target, 'scopedAllocPop');
+ const n = arguments.length
+ ? cache.scopedAlloc.indexOf(state)
+ : cache.scopedAlloc.length-1;
+ if(n<0) toss("Invalid state object for scopedAllocPop().");
+ if(0===arguments.length) state = cache.scopedAlloc[n];
+ cache.scopedAlloc.splice(n,1);
+ for(let p; (p = state.pop()); ){
+ if(target.functionEntry(p)){
+ //console.warn("scopedAllocPop() uninstalling function",p);
+ target.uninstallFunction(p);
+ }
+ else target.dealloc(p);
+ }
+ };
+
+ /**
+ Allocates n bytes of memory using this.alloc() and records that
+ fact in the state for the most recent call of scopedAllocPush().
+ Ownership of the memory is given to scopedAllocPop(), which
+ will clean it up when it is called. The memory _must not_ be
+ passed to this.dealloc(). Throws if this API object is missing
+ the required `alloc()` or `dealloc()` functions or no scoped
+ alloc is active.
+
+ See scopedAllocPush() for an example of how to use this function.
+
+ The `level` property of this function can be queried to query how
+ many scoped allocation levels are currently active.
+
+ See also: scopedAllocPtr(), scopedAllocCString()
+ */
+ target.scopedAlloc = function(n){
+ if(!cache.scopedAlloc.length){
+ toss("No scopedAllocPush() scope is active.");
+ }
+ const p = target.alloc(n);
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(p);
+ return p;
+ };
+
+ Object.defineProperty(target.scopedAlloc, 'level', {
+ configurable: false, enumerable: false,
+ get: ()=>cache.scopedAlloc.length,
+ set: ()=>toss("The 'active' property is read-only.")
+ });
+
+ /**
+ Works identically to allocCString() except that it allocates the
+ memory using scopedAlloc().
+
+ Will throw if no scopedAllocPush() call is active.
+ */
+ target.scopedAllocCString =
+ (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
+ target.scopedAlloc, 'scopedAllocCString()');
+
+ // impl for allocMainArgv() and scopedAllocMainArgv().
+ const __allocMainArgv = function(isScoped, list){
+ const pList = target[
+ isScoped ? 'scopedAlloc' : 'alloc'
+ ]((list.length + 1) * target.ptrSizeof);
+ let i = 0;
+ list.forEach((e)=>{
+ target.pokePtr(pList + (target.ptrSizeof * i++),
+ target[
+ isScoped ? 'scopedAllocCString' : 'allocCString'
+ ](""+e));
+ });
+ target.pokePtr(pList + (target.ptrSizeof * i), 0);
+ return pList;
+ };
+
+ /**
+ Creates an array, using scopedAlloc(), suitable for passing to a
+ C-level main() routine. The input is a collection with a length
+ property and a forEach() method. A block of memory
+ (list.length+1) entries long is allocated and each pointer-sized
+ block of that memory is populated with a scopedAllocCString()
+ conversion of the (""+value) of each element, with the exception
+ that the final entry is a NULL pointer. Returns a pointer to the
+ start of the list, suitable for passing as the 2nd argument to a
+ C-style main() function.
+
+ Throws if scopedAllocPush() is not active.
+
+ Design note: the returned array is allocated with an extra NULL
+ pointer entry to accommodate certain APIs, but client code which
+ does not need that functionality should treat the returned array
+ as list.length entries long.
+ */
+ target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list);
+
+ /**
+ Identical to scopedAllocMainArgv() but uses alloc() instead of
+ scopedAlloc().
+ */
+ target.allocMainArgv = (list)=>__allocMainArgv(false, list);
+
+ /**
+ Expects to be given a C-style string array and its length. It
+ returns a JS array of strings and/or nulls: any entry in the
+ pArgv array which is NULL results in a null entry in the result
+ array. If argc is 0 then an empty array is returned.
+
+ Results are undefined if any entry in the first argc entries of
+ pArgv are neither 0 (NULL) nor legal UTF-format C strings.
+
+ To be clear, the expected C-style arguments to be passed to this
+ function are `(int, char **)` (optionally const-qualified).
+ */
+ target.cArgvToJs = (argc, pArgv)=>{
+ const list = [];
+ for(let i = 0; i < argc; ++i){
+ const arg = target.peekPtr(pArgv + (target.ptrSizeof * i));
+ list.push( arg ? target.cstrToJs(arg) : null );
+ }
+ return list;
+ };
+
+ /**
+ Wraps function call func() in a scopedAllocPush() and
+ scopedAllocPop() block, such that all calls to scopedAlloc() and
+ friends from within that call will have their memory freed
+ automatically when func() returns. If func throws or propagates
+ an exception, the scope is still popped, otherwise it returns the
+ result of calling func().
+ */
+ target.scopedAllocCall = function(func){
+ target.scopedAllocPush();
+ try{ return func() } finally{ target.scopedAllocPop() }
+ };
+
+ /** Internal impl for allocPtr() and scopedAllocPtr(). */
+ const __allocPtr = function(howMany, safePtrSize, method){
+ __affirmAlloc(target, method);
+ const pIr = safePtrSize ? 'i64' : ptrIR;
+ let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof));
+ target.poke(m, 0, pIr)
+ if(1===howMany){
+ return m;
+ }
+ const a = [m];
+ for(let i = 1; i < howMany; ++i){
+ m += (safePtrSize ? 8 : ptrSizeof);
+ a[i] = m;
+ target.poke(m, 0, pIr);
+ }
+ return a;
+ };
+
+ /**
+ Allocates one or more pointers as a single chunk of memory and
+ zeroes them out.
+
+ The first argument is the number of pointers to allocate. The
+ second specifies whether they should use a "safe" pointer size (8
+ bytes) or whether they may use the default pointer size
+ (typically 4 but also possibly 8).
+
+ How the result is returned depends on its first argument: if
+ passed 1, it returns the allocated memory address. If passed more
+ than one then an array of pointer addresses is returned, which
+ can optionally be used with "destructuring assignment" like this:
+
+ ```
+ const [p1, p2, p3] = allocPtr(3);
+ ```
+
+ ACHTUNG: when freeing the memory, pass only the _first_ result
+ value to dealloc(). The others are part of the same memory chunk
+ and must not be freed separately.
+
+ The reason for the 2nd argument is..
+
+ When one of the returned pointers will refer to a 64-bit value,
+ e.g. a double or int64, an that value must be written or fetched,
+ e.g. using poke() or peek(), it is important that
+ the pointer in question be aligned to an 8-byte boundary or else
+ it will not be fetched or written properly and will corrupt or
+ read neighboring memory. It is only safe to pass false when the
+ client code is certain that it will only get/fetch 4-byte values
+ (or smaller).
+ */
+ target.allocPtr =
+ (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'alloc');
+
+ /**
+ Identical to allocPtr() except that it allocates using scopedAlloc()
+ instead of alloc().
+ */
+ target.scopedAllocPtr =
+ (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'scopedAlloc');
+
+ /**
+ If target.exports[name] exists, it is returned, else an
+ exception is thrown.
+ */
+ target.xGet = function(name){
+ return target.exports[name] || toss("Cannot find exported symbol:",name);
+ };
+
+ const __argcMismatch =
+ (f,n)=>toss(f+"() requires",n,"argument(s).");
+
+ /**
+ Looks up a WASM-exported function named fname from
+ target.exports. If found, it is called, passed all remaining
+ arguments, and its return value is returned to xCall's caller. If
+ not found, an exception is thrown. This function does no
+ conversion of argument or return types, but see xWrap() and
+ xCallWrapped() for variants which do.
+
+ As a special case, if passed only 1 argument after the name and
+ that argument in an Array, that array's entries become the
+ function arguments. (This is not an ambiguous case because it's
+ not legal to pass an Array object to a WASM function.)
+ */
+ target.xCall = function(fname, ...args){
+ const f = target.xGet(fname);
+ if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function.");
+ if(f.length!==args.length) __argcMismatch(fname,f.length)
+ /* This is arguably over-pedantic but we want to help clients keep
+ from shooting themselves in the foot when calling C APIs. */;
+ return (2===arguments.length && Array.isArray(arguments[1]))
+ ? f.apply(null, arguments[1])
+ : f.apply(null, args);
+ };
+
+ /**
+ State for use with xWrap()
+ */
+ cache.xWrap = Object.create(null);
+ cache.xWrap.convert = Object.create(null);
+ /** Map of type names to argument conversion functions. */
+ cache.xWrap.convert.arg = new Map;
+ /** Map of type names to return result conversion functions. */
+ cache.xWrap.convert.result = new Map;
+ const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result;
+
+ if(target.bigIntEnabled){
+ xArg.set('i64', (i)=>BigInt(i));
+ }
+ const __xArgPtr = 'i32' === ptrIR
+ ? ((i)=>(i | 0)) : ((i)=>(BigInt(i) | BigInt(0)));
+ xArg.set('i32', __xArgPtr )
+ .set('i16', (i)=>((i | 0) & 0xFFFF))
+ .set('i8', (i)=>((i | 0) & 0xFF))
+ .set('f32', (i)=>Number(i).valueOf())
+ .set('float', xArg.get('f32'))
+ .set('f64', xArg.get('f32'))
+ .set('double', xArg.get('f64'))
+ .set('int', xArg.get('i32'))
+ .set('null', (i)=>i)
+ .set(null, xArg.get('null'))
+ .set('**', __xArgPtr)
+ .set('*', __xArgPtr);
+ xResult.set('*', __xArgPtr)
+ .set('pointer', __xArgPtr)
+ .set('number', (v)=>Number(v))
+ .set('void', (v)=>undefined)
+ .set('null', (v)=>v)
+ .set(null, xResult.get('null'));
+
+ { /* Copy certain xArg[...] handlers to xResult[...] and
+ add pointer-style variants of them. */
+ const copyToResult = ['i8', 'i16', 'i32', 'int',
+ 'f32', 'float', 'f64', 'double'];
+ if(target.bigIntEnabled) copyToResult.push('i64');
+ const adaptPtr = xArg.get(ptrIR);
+ for(const t of copyToResult){
+ xArg.set(t+'*', adaptPtr);
+ xResult.set(t+'*', adaptPtr);
+ xResult.set(t, (xArg.get(t) || toss("Missing arg converter:",t)));
+ }
+ }
+
+ /**
+ In order for args of type string to work in various contexts in
+ the sqlite3 API, we need to pass them on as, variably, a C-string
+ or a pointer value. Thus for ARGs of type 'string' and
+ '*'/'pointer' we behave differently depending on whether the
+ argument is a string or not:
+
+ - If v is a string, scopeAlloc() a new C-string from it and return
+ that temp string's pointer.
+
+ - Else return the value from the arg adapter defined for ptrIR.
+
+ TODO? Permit an Int8Array/Uint8Array and convert it to a string?
+ Would that be too much magic concentrated in one place, ready to
+ backfire? We handle that at the client level in sqlite3 with a
+ custom argument converter.
+ */
+ const __xArgString = function(v){
+ if('string'===typeof v) return target.scopedAllocCString(v);
+ return v ? __xArgPtr(v) : null;
+ };
+ xArg.set('string', __xArgString)
+ .set('utf8', __xArgString)
+ .set('pointer', __xArgString);
+ //xArg.set('*', __xArgString);
+
+ xResult.set('string', (i)=>target.cstrToJs(i))
+ .set('utf8', xResult.get('string'))
+ .set('string:dealloc', (i)=>{
+ try { return i ? target.cstrToJs(i) : null }
+ finally{ target.dealloc(i) }
+ })
+ .set('utf8:dealloc', xResult.get('string:dealloc'))
+ .set('json', (i)=>JSON.parse(target.cstrToJs(i)))
+ .set('json:dealloc', (i)=>{
+ try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
+ finally{ target.dealloc(i) }
+ });
+
+ /**
+ Internal-use-only base class for FuncPtrAdapter and potentially
+ additional stateful argument adapter classes.
+
+ Note that its main interface (convertArg()) is strictly
+ internal, not to be exposed to client code, as it may still
+ need re-shaping. Only the constructors of concrete subclasses
+ should be exposed to clients, and those in such a way that
+ does not hinder internal redesign of the convertArg()
+ interface.
+ */
+ const AbstractArgAdapter = class {
+ constructor(opt){
+ this.name = opt.name || 'unnamed adapter';
+ }
+ /**
+ Gets called via xWrap() to "convert" v to whatever type
+ this specific class supports.
+
+ argIndex is the argv index of _this_ argument in the
+ being-xWrap()'d call. argv is the current argument list
+ undergoing xWrap() argument conversion. argv entries to the
+ left of argIndex will have already undergone transformation and
+ those to the right will not have (they will have the values the
+ client-level code passed in, awaiting conversion). The RHS
+ indexes must never be relied upon for anything because their
+ types are indeterminate, whereas the LHS values will be
+ WASM-compatible values by the time this is called.
+ */
+ convertArg(v,argv,argIndex){
+ toss("AbstractArgAdapter must be subclassed.");
+ }
+ };
+
+ /**
+ An attempt at adding function pointer conversion support to
+ xWrap(). This type is recognized by xWrap() as a proxy for
+ converting a JS function to a C-side function, either
+ permanently, for the duration of a single call into the C layer,
+ or semi-contextual, where it may keep track of a single binding
+ for a given context and uninstall the binding if it's replaced.
+
+ The constructor requires an options object with these properties:
+
+ - name (optional): string describing the function binding. This
+ is solely for debugging and error-reporting purposes. If not
+ provided, an empty string is assumed.
+
+ - signature: a function signature string compatible with
+ jsFuncToWasm().
+
+ - bindScope (string): one of ('transient', 'context',
+ 'singleton'). Bind scopes are:
+
+ - 'transient': it will convert JS functions to WASM only for
+ the duration of the xWrap()'d function call, using
+ scopedInstallFunction(). Before that call returns, the
+ WASM-side binding will be uninstalled.
+
+ - 'singleton': holds one function-pointer binding for this
+ instance. If it's called with a different function pointer,
+ it uninstalls the previous one after converting the new
+ value. This is only useful for use with "global" functions
+ which do not rely on any state other than this function
+ pointer. If the being-converted function pointer is intended
+ to be mapped to some sort of state object (e.g. an
+ `sqlite3*`) then "context" (see below) is the proper mode.
+
+ - 'context': similar to singleton mode but for a given
+ "context", where the context is a key provided by the user
+ and possibly dependent on a small amount of call-time
+ context. This mode is the default if bindScope is _not_ set
+ but a property named contextKey (described below) is.
+
+ - 'permanent': the function is installed and left there
+ forever. There is no way to recover its pointer address
+ later on.
+
+ - callProxy (function): if set, this must be a function which
+ will act as a proxy for any "converted" JS function. It is
+ passed the being-converted function value and must return
+ either that function or a function which acts on its
+ behalf. The returned function will be the one which gets
+ installed into the WASM function table. The proxy must perform
+ any required argument conversion (noting that it will be called
+ from C code, so will receive C-format arguments) before passing
+ them on to the being-converted function. Whether or not the
+ proxy itself must return a value depends on the context. If it
+ does, it must be a WASM-friendly value, as it will be returning
+ from a call made from native code.
+
+ - contextKey (function): is only used if bindScope is 'context'
+ or if bindScope is not set and this function is, in which case
+ 'context' is assumed. This function gets bound to this object,
+ so its "this" is this object. It gets passed (argv,argIndex),
+ where argIndex is the index of _this_ function pointer in its
+ _wrapping_ function's arguments and argv is the _current_
+ still-being-xWrap()-processed args array. All arguments to the
+ left of argIndex will have been processed by xWrap() by the
+ time this is called. argv[argIndex] will be the value the user
+ passed in to the xWrap()'d function for the argument this
+ FuncPtrAdapter is mapped to. Arguments to the right of
+ argv[argIndex] will not yet have been converted before this is
+ called. The function must return a key which uniquely
+ identifies this function mapping context for _this_
+ FuncPtrAdapter instance (other instances are not considered),
+ taking into account that C functions often take some sort of
+ state object as one or more of their arguments. As an example,
+ if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and
+ this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2)
+ might return 'T@'+argv[1], or even just argv[1]. Note,
+ however, that the (X*) argument will not yet have been
+ processed by the time this is called and should not be used as
+ part of that key because its pre-conversion data type might be
+ unpredictable. Similarly, care must be taken with C-string-type
+ arguments: those to the left in argv will, when this is called,
+ be WASM pointers, whereas those to the right might (and likely
+ do) have another data type. When using C-strings in keys, never
+ use their pointers in the key because most C-strings in this
+ constellation are transient.
+
+ Yes, that ^^^ is quite awkward, but it's what we have.
+
+ The constructor only saves the above state for later, and does
+ not actually bind any functions. Its convertArg() method is
+ called via xWrap() to perform any bindings.
+
+ Shortcomings:
+
+ - These "reverse" bindings, i.e. calling into a JS-defined
+ function from a WASM-defined function (the generated proxy
+ wrapper), lack all type conversion support. That means, for
+ example, that...
+
+ - Function pointers which include C-string arguments may still
+ need a level of hand-written wrappers around them, depending on
+ how they're used, in order to provide the client with JS
+ strings. Alternately, clients will need to perform such conversions
+ on their own, e.g. using cstrtojs(). Or maybe we can find a way
+ to perform such conversions here, via addition of an xWrap()-style
+ function signature to the options argument.
+ */
+ xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter {
+ constructor(opt) {
+ super(opt);
+ if(xArg.FuncPtrAdapter.warnOnUse){
+ console.warn('xArg.FuncPtrAdapter is an internal-only API',
+ 'and is not intended to be invoked from',
+ 'client-level code. Invoked with:',opt);
+ }
+ this.name = opt.name || "unnamed";
+ this.signature = opt.signature;
+ if(opt.contextKey instanceof Function){
+ this.contextKey = opt.contextKey;
+ if(!opt.bindScope) opt.bindScope = 'context';
+ }
+ this.bindScope = opt.bindScope
+ || toss("FuncPtrAdapter options requires a bindScope (explicit or implied).");
+ if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){
+ toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+
+ "Expecting one of: ("+FuncPtrAdapter.bindScopes.join(', ')+')');
+ }
+ this.isTransient = 'transient'===this.bindScope;
+ this.isContext = 'context'===this.bindScope;
+ this.isPermanent = 'permanent'===this.bindScope;
+ this.singleton = ('singleton'===this.bindScope) ? [] : undefined;
+ //console.warn("FuncPtrAdapter()",opt,this);
+ this.callProxy = (opt.callProxy instanceof Function)
+ ? opt.callProxy : undefined;
+ }
+
+ /**
+ Note that static class members are defined outside of the class
+ to work around an emcc toolchain build problem: one of the
+ tools in emsdk v3.1.42 does not support the static keyword.
+ */
+
+ /* Dummy impl. Overwritten per-instance as needed. */
+ contextKey(argv,argIndex){
+ return this;
+ }
+
+ /* Returns this objects mapping for the given context key, in the
+ form of an an array, creating the mapping if needed. The key
+ may be anything suitable for use in a Map. */
+ contextMap(key){
+ const cm = (this.__cmap || (this.__cmap = new Map));
+ let rc = cm.get(key);
+ if(undefined===rc) cm.set(key, (rc = []));
+ return rc;
+ }
+
+ /**
+ Gets called via xWrap() to "convert" v to a WASM-bound function
+ pointer. If v is one of (a pointer, null, undefined) then
+ (v||0) is returned and any earlier function installed by this
+ mapping _might_, depending on how it's bound, be uninstalled.
+ If v is not one of those types, it must be a Function, for
+ which it creates (if needed) a WASM function binding and
+ returns the WASM pointer to that binding. If this instance is
+ not in 'transient' mode, it will remember the binding for at
+ least the next call, to avoid recreating the function binding
+ unnecessarily.
+
+ If it's passed a pointer(ish) value for v, it does _not_
+ perform any function binding, so this object's bindMode is
+ irrelevant for such cases.
+
+ See the parent class's convertArg() docs for details on what
+ exactly the 2nd and 3rd arguments are.
+ */
+ convertArg(v,argv,argIndex){
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v);
+ let pair = this.singleton;
+ if(!pair && this.isContext){
+ pair = this.contextMap(this.contextKey(argv,argIndex));
+ //FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair);
+ }
+ if(pair && pair[0]===v) return pair[1];
+ if(v instanceof Function){
+ /* Install a WASM binding and return its pointer. */
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
+ if(this.callProxy) v = this.callProxy(v);
+ const fp = __installFunction(v, this.signature, this.isTransient);
+ if(FuncPtrAdapter.debugFuncInstall){
+ FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this,
+ this.contextKey(argv,argIndex), '@'+fp, v);
+ }
+ if(pair){
+ /* Replace existing stashed mapping */
+ if(pair[1]){
+ if(FuncPtrAdapter.debugFuncInstall){
+ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
+ this.contextKey(argv,argIndex), '@'+pair[1], v);
+ }
+ try{
+ /* Because the pending native call might rely on the
+ pointer we're replacing, e.g. as is normally the case
+ with sqlite3's xDestroy() methods, we don't
+ immediately uninstall but instead add its pointer to
+ the scopedAlloc stack, which will be cleared when the
+ xWrap() mechanism is done calling the native
+ function. We're relying very much here on xWrap()
+ having pushed an alloc scope.
+ */
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]);
+ }
+ catch(e){/*ignored*/}
+ }
+ pair[0] = v;
+ pair[1] = fp;
+ }
+ return fp;
+ }else if(target.isPtr(v) || null===v || undefined===v){
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
+ if(pair && pair[1] && pair[1]!==v){
+ /* uninstall stashed mapping and replace stashed mapping with v. */
+ if(FuncPtrAdapter.debugFuncInstall){
+ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
+ this.contextKey(argv,argIndex), '@'+pair[1], v);
+ }
+ try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) }
+ catch(e){/*ignored*/}
+ pair[0] = pair[1] = (v | 0);
+ }
+ return v || 0;
+ }else{
+ throw new TypeError("Invalid FuncPtrAdapter argument type. "+
+ "Expecting a function pointer or a "+
+ (this.name ? this.name+' ' : '')+
+ "function matching signature "+
+ this.signature+".");
+ }
+ }/*convertArg()*/
+ }/*FuncPtrAdapter*/;
+
+ /** If true, the constructor emits a warning. The intent is that
+ this be set to true after bootstrapping of the higher-level
+ client library is complete, to warn downstream clients that
+ they shouldn't be relying on this implemenation detail which
+ does not have a stable interface. */
+ xArg.FuncPtrAdapter.warnOnUse = false;
+
+ /** If true, convertArg() will FuncPtrAdapter.debugOut() when it
+ (un)installs a function binding to/from WASM. Note that
+ deinstallation of bindScope=transient bindings happens
+ via scopedAllocPop() so will not be output. */
+ xArg.FuncPtrAdapter.debugFuncInstall = false;
+
+ /** Function used for debug output. */
+ xArg.FuncPtrAdapter.debugOut = console.debug.bind(console);
+
+ xArg.FuncPtrAdapter.bindScopes = [
+ 'transient', 'context', 'singleton', 'permanent'
+ ];
+
+ const __xArgAdapterCheck =
+ (t)=>xArg.get(t) || toss("Argument adapter not found:",t);
+
+ const __xResultAdapterCheck =
+ (t)=>xResult.get(t) || toss("Result adapter not found:",t);
+
+ cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args);
+ cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args);
+
+ cache.xWrap.convertResult =
+ (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined));
+ cache.xWrap.convertResultNoCheck =
+ (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined));
+
+ /**
+ Creates a wrapper for another function which converts the arguments
+ of the wrapper to argument types accepted by the wrapped function,
+ then converts the wrapped function's result to another form
+ for the wrapper.
+
+ The first argument must be one of:
+
+ - A JavaScript function.
+ - The name of a WASM-exported function. In the latter case xGet()
+ is used to fetch the exported function, which throws if it's not
+ found.
+ - A pointer into the indirect function table. e.g. a pointer
+ returned from target.installFunction().
+
+ It returns either the passed-in function or a wrapper for that
+ function which converts the JS-side argument types into WASM-side
+ types and converts the result type.
+
+ The second argument, `resultType`, describes the conversion for
+ the wrapped functions result. A literal `null` or the string
+ `'null'` both mean to return the original function's value as-is
+ (mnemonic: there is "null" conversion going on). Literal
+ `undefined` or the string `"void"` both mean to ignore the
+ function's result and return `undefined`. Aside from those two
+ special cases, the `resultType` value may be one of the values
+ described below or any mapping installed by the client using
+ xWrap.resultAdapter().
+
+ If passed 3 arguments and the final one is an array, that array
+ must contain a list of type names (see below) for adapting the
+ arguments from JS to WASM. If passed 2 arguments, more than 3,
+ or the 3rd is not an array, all arguments after the 2nd (if any)
+ are treated as type names. i.e.:
+
+ ```
+ xWrap('funcname', 'i32', 'string', 'f64');
+ // is equivalent to:
+ xWrap('funcname', 'i32', ['string', 'f64']);
+ ```
+
+ This function enforces that the given list of arguments has the
+ same arity as the being-wrapped function (as defined by its
+ `length` property) and it will throw if that is not the case.
+ Similarly, the created wrapper will throw if passed a differing
+ argument count.
+
+ Type names are symbolic names which map the arguments to an
+ adapter function to convert, if needed, the value before passing
+ it on to WASM or to convert a return result from WASM. The list
+ of built-in names:
+
+ - `i8`, `i16`, `i32` (args and results): all integer conversions
+ which convert their argument to an integer and truncate it to
+ the given bit length.
+
+ - `N*` (args): a type name in the form `N*`, where N is a numeric
+ type name, is treated the same as WASM pointer.
+
+ - `*` and `pointer` (args): are assumed to be WASM pointer values
+ and are returned coerced to an appropriately-sized pointer
+ value (i32 or i64). Non-numeric values will coerce to 0 and
+ out-of-range values will have undefined results (just as with
+ any pointer misuse).
+
+ - `*` and `pointer` (results): aliases for the current
+ WASM pointer numeric type.
+
+ - `**` (args): is simply a descriptive alias for the WASM pointer
+ type. It's primarily intended to mark output-pointer arguments.
+
+ - `i64` (args and results): passes the value to BigInt() to
+ convert it to an int64. Only available if bigIntEnabled is
+ true.
+
+ - `f32` (`float`), `f64` (`double`) (args and results): pass
+ their argument to Number(). i.e. the adapter does not currently
+ distinguish between the two types of floating-point numbers.
+
+ - `number` (results): converts the result to a JS Number using
+ Number(theValue).valueOf(). Note that this is for result
+ conversions only, as it's not possible to generically know
+ which type of number to convert arguments to.
+
+ Non-numeric conversions include:
+
+ - `null` literal or `"null"` string (args and results): perform
+ no translation and pass the arg on as-is. This is primarily
+ useful for results but may have a use or two for arguments.
+
+ - `string` or `utf8` (args): has two different semantics in order
+ to accommodate various uses of certain C APIs
+ (e.g. output-style strings)...
+
+ - If the arg is a string, it creates a _temporary_
+ UTF-8-encoded C-string to pass to the exported function,
+ cleaning it up before the wrapper returns. If a long-lived
+ C-string pointer is required, that requires client-side code
+ to create the string, then pass its pointer to the function.
+
+ - Else the arg is assumed to be a pointer to a string the
+ client has already allocated and it's passed on as
+ a WASM pointer.
+
+ - `string` or `utf8` (results): treats the result value as a
+ const C-string, encoded as UTF-8, copies it to a JS string,
+ and returns that JS string.
+
+ - `string:dealloc` or `utf8:dealloc) (results): treats the result value
+ as a non-const UTF-8 C-string, ownership of which has just been
+ transfered to the caller. It copies the C-string to a JS
+ string, frees the C-string, and returns the JS string. If such
+ a result value is NULL, the JS result is `null`. Achtung: when
+ using an API which returns results from a specific allocator,
+ e.g. `my_malloc()`, this conversion _is not legal_. Instead, an
+ equivalent conversion which uses the appropriate deallocator is
+ required. For example:
+
+```js
+ target.xWrap.resultAdapter('string:my_free',(i)=>{
+ try { return i ? target.cstrToJs(i) : null }
+ finally{ target.exports.my_free(i) }
+ };
+```
+
+ - `json` (results): treats the result as a const C-string and
+ returns the result of passing the converted-to-JS string to
+ JSON.parse(). Returns `null` if the C-string is a NULL pointer.
+
+ - `json:dealloc` (results): works exactly like `string:dealloc` but
+ returns the same thing as the `json` adapter. Note the
+ warning in `string:dealloc` regarding maching allocators and
+ deallocators.
+
+ The type names for results and arguments are validated when
+ xWrap() is called and any unknown names will trigger an
+ exception.
+
+ Clients may map their own result and argument adapters using
+ xWrap.resultAdapter() and xWrap.argAdapter(), noting that not all
+ type conversions are valid for both arguments _and_ result types
+ as they often have different memory ownership requirements.
+
+ Design note: the ability to pass in a JS function as the first
+ argument is of relatively limited use, primarily for testing
+ argument and result converters. JS functions, by and large, will
+ not want to deal with C-type arguments.
+
+ TODOs:
+
+ - Figure out how/whether we can (semi-)transparently handle
+ pointer-type _output_ arguments. Those currently require
+ explicit handling by allocating pointers, assigning them before
+ the call using poke(), and fetching them with
+ peek() after the call. We may be able to automate some
+ or all of that.
+
+ - Figure out whether it makes sense to extend the arg adapter
+ interface such that each arg adapter gets an array containing
+ the results of the previous arguments in the current call. That
+ might allow some interesting type-conversion feature. Use case:
+ handling of the final argument to sqlite3_prepare_v2() depends
+ on the type (pointer vs JS string) of its 2nd
+ argument. Currently that distinction requires hand-writing a
+ wrapper for that function. That case is unusual enough that
+ abstracting it into this API (and taking on the associated
+ costs) may well not make good sense.
+ */
+ target.xWrap = function(fArg, resultType, ...argTypes){
+ if(3===arguments.length && Array.isArray(arguments[2])){
+ argTypes = arguments[2];
+ }
+ if(target.isPtr(fArg)){
+ fArg = target.functionEntry(fArg)
+ || toss("Function pointer not found in WASM function table.");
+ }
+ const fIsFunc = (fArg instanceof Function);
+ const xf = fIsFunc ? fArg : target.xGet(fArg);
+ if(fIsFunc) fArg = xf.name || 'unnamed function';
+ if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length);
+ if((null===resultType) && 0===xf.length){
+ /* Func taking no args with an as-is return. We don't need a wrapper.
+ We forego the argc check here, though. */
+ return xf;
+ }
+ /*Verify the arg type conversions are valid...*/;
+ if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType);
+ for(const t of argTypes){
+ if(t instanceof AbstractArgAdapter) xArg.set(t, (...args)=>t.convertArg(...args));
+ else __xArgAdapterCheck(t);
+ }
+ const cxw = cache.xWrap;
+ if(0===xf.length){
+ // No args to convert, so we can create a simpler wrapper...
+ return (...args)=>(args.length
+ ? __argcMismatch(fArg, xf.length)
+ : cxw.convertResult(resultType, xf.call(null)));
+ }
+ return function(...args){
+ if(args.length!==xf.length) __argcMismatch(fArg, xf.length);
+ const scope = target.scopedAllocPush();
+ try{
+ /*
+ Maintenance reminder re. arguments passed to convertArg():
+ The public interface of argument adapters is that they take
+ ONE argument and return a (possibly) converted result for
+ it. The passing-on of arguments after the first is an
+ internal implementation detail for the sake of
+ AbstractArgAdapter, and not to be relied on or documented
+ for other cases. The fact that this is how
+ AbstractArgAdapter.convertArgs() gets its 2nd+ arguments,
+ and how FuncPtrAdapter.contextKey() gets its args, is also
+ an implementation detail and subject to change. i.e. the
+ public interface of 1 argument is stable. The fact that any
+ arguments may be passed in after that one, and what those
+ arguments are, is _not_ part of the public interface and is
+ _not_ stable.
+ */
+ for(const i in args) args[i] = cxw.convertArgNoCheck(
+ argTypes[i], args[i], args, i
+ );
+ return cxw.convertResultNoCheck(resultType, xf.apply(null,args));
+ }finally{
+ target.scopedAllocPop(scope);
+ }
+ };
+ }/*xWrap()*/;
+
+ /** Internal impl for xWrap.resultAdapter() and argAdapter(). */
+ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){
+ if('string'===typeof typeName){
+ if(1===argc) return xcvPart.get(typeName);
+ else if(2===argc){
+ if(!adapter){
+ delete xcvPart.get(typeName);
+ return func;
+ }else if(!(adapter instanceof Function)){
+ toss(modeName,"requires a function argument.");
+ }
+ xcvPart.set(typeName, adapter);
+ return func;
+ }
+ }
+ toss("Invalid arguments to",modeName);
+ };
+
+ /**
+ Gets, sets, or removes a result value adapter for use with
+ xWrap(). If passed only 1 argument, the adapter function for the
+ given type name is returned. If the second argument is explicit
+ falsy (as opposed to defaulted), the adapter named by the first
+ argument is removed. If the 2nd argument is not falsy, it must be
+ a function which takes one value and returns a value appropriate
+ for the given type name. The adapter may throw if its argument is
+ not of a type it can work with. This function throws for invalid
+ arguments.
+
+ Example:
+
+ ```
+ xWrap.resultAdapter('twice',(v)=>v+v);
+ ```
+
+ xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of
+ APIs to allocate a result value. xWrap()-generated wrappers run
+ in the context of scopedAllocPush() so that argument adapters can
+ easily convert, e.g., to C-strings, and have them cleaned up
+ automatically before the wrapper returns to the caller. Likewise,
+ if a _result_ adapter uses scoped allocation, the result will be
+ freed before because they would be freed before the wrapper
+ returns, leading to chaos and undefined behavior.
+
+ Except when called as a getter, this function returns itself.
+ */
+ target.xWrap.resultAdapter = function f(typeName, adapter){
+ return __xAdapter(f, arguments.length, typeName, adapter,
+ 'resultAdapter()', xResult);
+ };
+
+ /**
+ Functions identically to xWrap.resultAdapter() but applies to
+ call argument conversions instead of result value conversions.
+
+ xWrap()-generated wrappers perform argument conversion in the
+ context of a scopedAllocPush(), so any memory allocation
+ performed by argument adapters really, really, really should be
+ made using the scopedAlloc() family of functions unless
+ specifically necessary. For example:
+
+ ```
+ xWrap.argAdapter('my-string', function(v){
+ return ('string'===typeof v)
+ ? myWasmObj.scopedAllocCString(v) : null;
+ };
+ ```
+
+ Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc()
+ to allocate its results because they would be freed before the
+ xWrap()-created wrapper returns.
+
+ Note that it is perfectly legitimate to use these adapters to
+ perform argument validation, as opposed (or in addition) to
+ conversion.
+ */
+ target.xWrap.argAdapter = function f(typeName, adapter){
+ return __xAdapter(f, arguments.length, typeName, adapter,
+ 'argAdapter()', xArg);
+ };
+
+ target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter;
+
+ /**
+ Functions like xCall() but performs argument and result type
+ conversions as for xWrap(). The first, second, and third
+ arguments are as documented for xWrap(), except that the 3rd
+ argument may be either a falsy value or empty array to represent
+ nullary functions. The 4th+ arguments are arguments for the call,
+ with the special case that if the 4th argument is an array, it is
+ used as the arguments for the call. Returns the converted result
+ of the call.
+
+ This is just a thin wrapper around xWrap(). If the given function
+ is to be called more than once, it's more efficient to use
+ xWrap() to create a wrapper, then to call that wrapper as many
+ times as needed. For one-shot calls, however, this variant is
+ arguably more efficient because it will hypothetically free the
+ wrapper function quickly.
+ */
+ target.xCallWrapped = function(fArg, resultType, argTypes, ...args){
+ if(Array.isArray(arguments[3])) args = arguments[3];
+ return target.xWrap(fArg, resultType, argTypes||[]).apply(null, args||[]);
+ };
+
+ /**
+ This function is ONLY exposed in the public API to facilitate
+ testing. It should not be used in application-level code, only
+ in test code.
+
+ Expects to be given (typeName, value) and returns a conversion
+ of that value as has been registered using argAdapter().
+ It throws if no adapter is found.
+
+ ACHTUNG: the adapter may require that a scopedAllocPush() is
+ active and it may allocate memory within that scope. It may also
+ require additional arguments, depending on the type of
+ conversion.
+ */
+ target.xWrap.testConvertArg = cache.xWrap.convertArg;
+
+ /**
+ This function is ONLY exposed in the public API to facilitate
+ testing. It should not be used in application-level code, only
+ in test code.
+
+ Expects to be given (typeName, value) and returns a conversion
+ of that value as has been registered using resultAdapter().
+ It throws if no adapter is found.
+
+ ACHTUNG: the adapter may allocate memory which the caller may need
+ to know how to free.
+ */
+ target.xWrap.testConvertResult = cache.xWrap.convertResult;
+
+ return target;
+};
+
+/**
+ yawl (Yet Another Wasm Loader) provides very basic wasm loader.
+ It requires a config object:
+
+ - `uri`: required URI of the WASM file to load.
+
+ - `onload(loadResult,config)`: optional callback. The first
+ argument is the result object from
+ WebAssembly.instantiate[Streaming](). The 2nd is the config
+ object passed to this function. Described in more detail below.
+
+ - `imports`: optional imports object for
+ WebAssembly.instantiate[Streaming](). The default is an empty set
+ of imports. If the module requires any imports, this object
+ must include them.
+
+ - `wasmUtilTarget`: optional object suitable for passing to
+ WhWasmUtilInstaller(). If set, it gets passed to that function
+ after the promise resolves. This function sets several properties
+ on it before passing it on to that function (which sets many
+ more):
+
+ - `module`, `instance`: the properties from the
+ instantiate[Streaming]() result.
+
+ - If `instance.exports.memory` is _not_ set then it requires that
+ `config.imports.env.memory` be set (else it throws), and
+ assigns that to `target.memory`.
+
+ - If `wasmUtilTarget.alloc` is not set and
+ `instance.exports.malloc` is, it installs
+ `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()`
+ wrappers for the exports `malloc` and `free` functions.
+
+ It returns a function which, when called, initiates loading of the
+ module and returns a Promise. When that Promise resolves, it calls
+ the `config.onload` callback (if set) and passes it
+ `(loadResult,config)`, where `loadResult` is the result of
+ WebAssembly.instantiate[Streaming](): an object in the form:
+
+ ```
+ {
+ module: a WebAssembly.Module,
+ instance: a WebAssembly.Instance
+ }
+ ```
+
+ (Note that the initial `then()` attached to the promise gets only
+ that object, and not the `config` one.)
+
+ Error handling is up to the caller, who may attach a `catch()` call
+ to the promise.
+*/
+globalThis.WhWasmUtilInstaller.yawl = function(config){
+ const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'});
+ const wui = this;
+ const finalThen = function(arg){
+ //log("finalThen()",arg);
+ if(config.wasmUtilTarget){
+ const toss = (...args)=>{throw new Error(args.join(' '))};
+ const tgt = config.wasmUtilTarget;
+ tgt.module = arg.module;
+ tgt.instance = arg.instance;
+ //tgt.exports = tgt.instance.exports;
+ if(!tgt.instance.exports.memory){
+ /**
+ WhWasmUtilInstaller requires either tgt.exports.memory
+ (exported from WASM) or tgt.memory (JS-provided memory
+ imported into WASM).
+ */
+ tgt.memory = (config.imports && config.imports.env
+ && config.imports.env.memory)
+ || toss("Missing 'memory' object!");
+ }
+ if(!tgt.alloc && arg.instance.exports.malloc){
+ const exports = arg.instance.exports;
+ tgt.alloc = function(n){
+ return exports.malloc(n) || toss("Allocation of",n,"bytes failed.");
+ };
+ tgt.dealloc = function(m){exports.free(m)};
+ }
+ wui(tgt);
+ }
+ if(config.onload) config.onload(arg,config);
+ return arg /* for any then() handler attached to
+ yetAnotherWasmLoader()'s return value */;
+ };
+ const loadWasm = WebAssembly.instantiateStreaming
+ ? function loadWasmStreaming(){
+ return WebAssembly.instantiateStreaming(wfetch(), config.imports||{})
+ .then(finalThen);
+ }
+ : function loadWasmOldSchool(){ // Safari < v15
+ return wfetch()
+ .then(response => response.arrayBuffer())
+ .then(bytes => WebAssembly.instantiate(bytes, config.imports||{}))
+ .then(finalThen);
+ };
+ return loadWasm;
+}.bind(globalThis.WhWasmUtilInstaller)/*yawl()*/;