Snapshot of upstream SQLite 3.46.1
[sqlcipher.git] / ext / wasm / common / whwasmutil.js
blobb8a2a877425dc0aad54f7adf9a2a0a3509164bb3
1 /**
2 2022-07-08
4 The author disclaims copyright to this source code. In place of a
5 legal notice, here is a blessing:
7 * May you do good and not evil.
8 * May you find forgiveness for yourself and forgive others.
9 * May you share freely, never taking more than you give.
11 ***********************************************************************
13 The whwasmutil is developed in conjunction with the Jaccwabyt
14 project:
16 https://fossil.wanderinghorse.net/r/jaccwabyt
18 and sqlite3:
20 https://sqlite.org
22 This file is kept in sync between both of those trees.
24 Maintenance reminder: If you're reading this in a tree other than
25 one of those listed above, note that this copy may be replaced with
26 upstream copies of that one from time to time. Thus the code
27 installed by this function "should not" be edited outside of those
28 projects, else it risks getting overwritten.
30 /**
31 This function is intended to simplify porting around various bits
32 of WASM-related utility code from project to project.
34 The primary goal of this code is to replace, where possible,
35 Emscripten-generated glue code with equivalent utility code which
36 can be used in arbitrary WASM environments built with toolchains
37 other than Emscripten. As of this writing, this code is capable of
38 acting as a replacement for Emscripten's generated glue code
39 _except_ that the latter installs handlers for Emscripten-provided
40 APIs such as its "FS" (virtual filesystem) API. Loading of such
41 things still requires using Emscripten's glue, but the post-load
42 utility APIs provided by this code are still usable as replacements
43 for their sub-optimally-documented Emscripten counterparts.
45 Intended usage:
47 ```
48 globalThis.WhWasmUtilInstaller(appObject);
49 delete globalThis.WhWasmUtilInstaller;
50 ```
52 Its global-scope symbol is intended only to provide an easy way to
53 make it available to 3rd-party scripts and "should" be deleted
54 after calling it. That symbols is _not_ used within the library.
56 Forewarning: this API explicitly targets only browser
57 environments. If a given non-browser environment has the
58 capabilities needed for a given feature (e.g. TextEncoder), great,
59 but it does not go out of its way to account for them and does not
60 provide compatibility crutches for them.
62 It currently offers alternatives to the following
63 Emscripten-generated APIs:
65 - OPTIONALLY memory allocation, but how this gets imported is
66 environment-specific. Most of the following features only work
67 if allocation is available.
69 - WASM-exported "indirect function table" access and
70 manipulation. e.g. creating new WASM-side functions using JS
71 functions, analog to Emscripten's addFunction() and
72 uninstallFunction() but slightly different.
74 - Get/set specific heap memory values, analog to Emscripten's
75 getValue() and setValue().
77 - String length counting in UTF-8 bytes (C-style and JS strings).
79 - JS string to C-string conversion and vice versa, analog to
80 Emscripten's stringToUTF8Array() and friends, but with slighter
81 different interfaces.
83 - JS string to Uint8Array conversion, noting that browsers actually
84 already have this built in via TextEncoder.
86 - "Scoped" allocation, such that allocations made inside of a given
87 explicit scope will be automatically cleaned up when the scope is
88 closed. This is fundamentally similar to Emscripten's
89 stackAlloc() and friends but uses the heap instead of the stack
90 because access to the stack requires C code.
92 - Create JS wrappers for WASM functions, analog to Emscripten's
93 ccall() and cwrap() functions, except that the automatic
94 conversions for function arguments and return values can be
95 easily customized by the client by assigning custom function
96 signature type names to conversion functions. Essentially,
97 it's ccall() and cwrap() on steroids.
99 How to install...
101 Passing an object to this function will install the functionality
102 into that object. Afterwards, client code "should" delete the global
103 symbol.
105 This code requires that the target object have the following
106 properties, noting that they needn't be available until the first
107 time one of the installed APIs is used (as opposed to when this
108 function is called) except where explicitly noted:
110 - `exports` must be a property of the target object OR a property
111 of `target.instance` (a WebAssembly.Module instance) and it must
112 contain the symbols exported by the WASM module associated with
113 this code. In an Enscripten environment it must be set to
114 `Module['asm']` (versions <=3.1.43) or `wasmExports` (versions
115 >=3.1.44). The exports object must contain a minimum of the
116 following symbols:
118 - `memory`: a WebAssembly.Memory object representing the WASM
119 memory. _Alternately_, the `memory` property can be set as
120 `target.memory`, in particular if the WASM heap memory is
121 initialized in JS an _imported_ into WASM, as opposed to being
122 initialized in WASM and exported to JS.
124 - `__indirect_function_table`: the WebAssembly.Table object which
125 holds WASM-exported functions. This API does not strictly
126 require that the table be able to grow but it will throw if its
127 `installFunction()` is called and the table cannot grow.
129 In order to simplify downstream usage, if `target.exports` is not
130 set when this is called then a property access interceptor
131 (read-only, configurable, enumerable) gets installed as `exports`
132 which resolves to `target.instance.exports`, noting that the latter
133 property need not exist until the first time `target.exports` is
134 accessed.
136 Some APIs _optionally_ make use of the `bigIntEnabled` property of
137 the target object. It "should" be set to true if the WASM
138 environment is compiled with BigInt support, else it must be
139 false. If it is false, certain BigInt-related features will trigger
140 an exception if invoked. This property, if not set when this is
141 called, will get a default value of true only if the BigInt64Array
142 constructor is available, else it will default to false. Note that
143 having the BigInt type is not sufficient for full int64 integration
144 with WASM: the target WASM file must also have been built with
145 that support. In Emscripten that's done using the `-sWASM_BIGINT`
146 flag.
148 Some optional APIs require that the target have the following
149 methods:
151 - 'alloc()` must behave like C's `malloc()`, allocating N bytes of
152 memory and returning its pointer. In Emscripten this is
153 conventionally made available via `Module['_malloc']`. This API
154 requires that the alloc routine throw on allocation error, as
155 opposed to returning null or 0.
157 - 'dealloc()` must behave like C's `free()`, accepting either a
158 pointer returned from its allocation counterpart or the values
159 null/0 (for which it must be a no-op). allocating N bytes of
160 memory and returning its pointer. In Emscripten this is
161 conventionally made available via `Module['_free']`.
163 APIs which require allocation routines are explicitly documented as
164 such and/or have "alloc" in their names.
166 This code is developed and maintained in conjunction with the
167 Jaccwabyt project:
169 https://fossil.wanderinghorse.net/r/jaccwabbyt
171 More specifically:
173 https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js
175 globalThis.WhWasmUtilInstaller = function(target){
176 'use strict';
177 if(undefined===target.bigIntEnabled){
178 target.bigIntEnabled = !!globalThis['BigInt64Array'];
181 /** Throws a new Error, the message of which is the concatenation of
182 all args with a space between each. */
183 const toss = (...args)=>{throw new Error(args.join(' '))};
185 if(!target.exports){
186 Object.defineProperty(target, 'exports', {
187 enumerable: true, configurable: true,
188 get: ()=>(target.instance && target.instance.exports)
192 /*********
193 alloc()/dealloc() auto-install...
195 This would be convenient but it can also cause us to pick up
196 malloc() even when the client code is using a different exported
197 allocator (who, me?), which is bad. malloc() may be exported even
198 if we're not explicitly using it and overriding the malloc()
199 function, linking ours first, is not always feasible when using a
200 malloc() proxy, as it can lead to recursion and stack overflow
201 (who, me?). So... we really need the downstream code to set up
202 target.alloc/dealloc() itself.
203 ******/
204 /******
205 if(target.exports){
206 //Maybe auto-install alloc()/dealloc()...
207 if(!target.alloc && target.exports.malloc){
208 target.alloc = function(n){
209 const m = this(n);
210 return m || toss("Allocation of",n,"byte(s) failed.");
211 }.bind(target.exports.malloc);
214 if(!target.dealloc && target.exports.free){
215 target.dealloc = function(ptr){
216 if(ptr) this(ptr);
217 }.bind(target.exports.free);
219 }*******/
222 Pointers in WASM are currently assumed to be 32-bit, but someday
223 that will certainly change.
225 const ptrIR = target.pointerIR || 'i32';
226 const ptrSizeof = target.ptrSizeof =
227 ('i32'===ptrIR ? 4
228 : ('i64'===ptrIR
229 ? 8 : toss("Unhandled ptrSizeof:",ptrIR)));
230 /** Stores various cached state. */
231 const cache = Object.create(null);
232 /** Previously-recorded size of cache.memory.buffer, noted so that
233 we can recreate the view objects if the heap grows. */
234 cache.heapSize = 0;
235 /** WebAssembly.Memory object extracted from target.memory or
236 target.exports.memory the first time heapWrappers() is
237 called. */
238 cache.memory = null;
239 /** uninstallFunction() puts table indexes in here for reuse and
240 installFunction() extracts them. */
241 cache.freeFuncIndexes = [];
243 Used by scopedAlloc() and friends.
245 cache.scopedAlloc = [];
247 cache.utf8Decoder = new TextDecoder();
248 cache.utf8Encoder = new TextEncoder('utf-8');
251 For the given IR-like string in the set ('i8', 'i16', 'i32',
252 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value
253 ending in '*', returns the sizeof for that value
254 (target.ptrSizeof in the latter case). For any other value, it
255 returns the undefined value.
257 target.sizeofIR = (n)=>{
258 switch(n){
259 case 'i8': return 1;
260 case 'i16': return 2;
261 case 'i32': case 'f32': case 'float': return 4;
262 case 'i64': case 'f64': case 'double': return 8;
263 case '*': return ptrSizeof;
264 default:
265 return (''+n).endsWith('*') ? ptrSizeof : undefined;
270 If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
271 the heap has grown since the last call, updates cache.HEAPxyz.
272 Returns the cache object.
274 const heapWrappers = function(){
275 if(!cache.memory){
276 cache.memory = (target.memory instanceof WebAssembly.Memory)
277 ? target.memory : target.exports.memory;
278 }else if(cache.heapSize === cache.memory.buffer.byteLength){
279 return cache;
281 // heap is newly-acquired or has been resized....
282 const b = cache.memory.buffer;
283 cache.HEAP8 = new Int8Array(b); cache.HEAP8U = new Uint8Array(b);
284 cache.HEAP16 = new Int16Array(b); cache.HEAP16U = new Uint16Array(b);
285 cache.HEAP32 = new Int32Array(b); cache.HEAP32U = new Uint32Array(b);
286 if(target.bigIntEnabled){
287 cache.HEAP64 = new BigInt64Array(b); cache.HEAP64U = new BigUint64Array(b);
289 cache.HEAP32F = new Float32Array(b); cache.HEAP64F = new Float64Array(b);
290 cache.heapSize = b.byteLength;
291 return cache;
294 /** Convenience equivalent of this.heapForSize(8,false). */
295 target.heap8 = ()=>heapWrappers().HEAP8;
297 /** Convenience equivalent of this.heapForSize(8,true). */
298 target.heap8u = ()=>heapWrappers().HEAP8U;
300 /** Convenience equivalent of this.heapForSize(16,false). */
301 target.heap16 = ()=>heapWrappers().HEAP16;
303 /** Convenience equivalent of this.heapForSize(16,true). */
304 target.heap16u = ()=>heapWrappers().HEAP16U;
306 /** Convenience equivalent of this.heapForSize(32,false). */
307 target.heap32 = ()=>heapWrappers().HEAP32;
309 /** Convenience equivalent of this.heapForSize(32,true). */
310 target.heap32u = ()=>heapWrappers().HEAP32U;
313 Requires n to be one of:
315 - integer 8, 16, or 32.
316 - A integer-type TypedArray constructor: Int8Array, Int16Array,
317 Int32Array, or their Uint counterparts.
319 If this.bigIntEnabled is true, it also accepts the value 64 or a
320 BigInt64Array/BigUint64Array, else it throws if passed 64 or one
321 of those constructors.
323 Returns an integer-based TypedArray view of the WASM heap
324 memory buffer associated with the given block size. If passed
325 an integer as the first argument and unsigned is truthy then
326 the "U" (unsigned) variant of that view is returned, else the
327 signed variant is returned. If passed a TypedArray value, the
328 2nd argument is ignored. Note that Float32Array and
329 Float64Array views are not supported by this function.
331 Note that growth of the heap will invalidate any references to
332 this heap, so do not hold a reference longer than needed and do
333 not use a reference after any operation which may
334 allocate. Instead, re-fetch the reference by calling this
335 function again.
337 Throws if passed an invalid n.
339 Pedantic side note: the name "heap" is a bit of a misnomer. In a
340 WASM environment, the stack and heap memory are all accessed via
341 the same view(s) of the memory.
343 target.heapForSize = function(n,unsigned = true){
344 let ctor;
345 const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
346 ? cache : heapWrappers();
347 switch(n){
348 case Int8Array: return c.HEAP8; case Uint8Array: return c.HEAP8U;
349 case Int16Array: return c.HEAP16; case Uint16Array: return c.HEAP16U;
350 case Int32Array: return c.HEAP32; case Uint32Array: return c.HEAP32U;
351 case 8: return unsigned ? c.HEAP8U : c.HEAP8;
352 case 16: return unsigned ? c.HEAP16U : c.HEAP16;
353 case 32: return unsigned ? c.HEAP32U : c.HEAP32;
354 case 64:
355 if(c.HEAP64) return unsigned ? c.HEAP64U : c.HEAP64;
356 break;
357 default:
358 if(target.bigIntEnabled){
359 if(n===globalThis['BigUint64Array']) return c.HEAP64U;
360 else if(n===globalThis['BigInt64Array']) return c.HEAP64;
361 break;
364 toss("Invalid heapForSize() size: expecting 8, 16, 32,",
365 "or (if BigInt is enabled) 64.");
369 Returns the WASM-exported "indirect function table."
371 target.functionTable = function(){
372 return target.exports.__indirect_function_table;
373 /** -----------------^^^^^ "seems" to be a standardized export name.
374 From Emscripten release notes from 2020-09-10:
375 - Use `__indirect_function_table` as the import name for the
376 table, which is what LLVM does.
381 Given a function pointer, returns the WASM function table entry
382 if found, else returns a falsy value: undefined if fptr is out of
383 range or null if it's in range but the table entry is empty.
385 target.functionEntry = function(fptr){
386 const ft = target.functionTable();
387 return fptr < ft.length ? ft.get(fptr) : undefined;
391 Creates a WASM function which wraps the given JS function and
392 returns the JS binding of that WASM function. The signature
393 string must be the Jaccwabyt-format or Emscripten
394 addFunction()-format function signature string. In short: in may
395 have one of the following formats:
397 - Emscripten: `"x..."`, where the first x is a letter representing
398 the result type and subsequent letters represent the argument
399 types. Functions with no arguments have only a single
400 letter. See below.
402 - Jaccwabyt: `"x(...)"` where `x` is the letter representing the
403 result type and letters in the parens (if any) represent the
404 argument types. Functions with no arguments use `x()`. See
405 below.
407 Supported letters:
409 - `i` = int32
410 - `p` = int32 ("pointer")
411 - `j` = int64
412 - `f` = float32
413 - `d` = float64
414 - `v` = void, only legal for use as the result type
416 It throws if an invalid signature letter is used.
418 Jaccwabyt-format signatures support some additional letters which
419 have no special meaning here but (in this context) act as aliases
420 for other letters:
422 - `s`, `P`: same as `p`
424 Sidebar: this code is developed together with Jaccwabyt, thus the
425 support for its signature format.
427 The arguments may be supplied in either order: (func,sig) or
428 (sig,func).
430 target.jsFuncToWasm = function f(func, sig){
431 /** Attribution: adapted up from Emscripten-generated glue code,
432 refactored primarily for efficiency's sake, eliminating
433 call-local functions and superfluous temporary arrays. */
434 if(!f._){/*static init...*/
435 f._ = {
436 // Map of signature letters to type IR values
437 sigTypes: Object.assign(Object.create(null),{
438 i: 'i32', p: 'i32', P: 'i32', s: 'i32',
439 j: 'i64', f: 'f32', d: 'f64'
441 // Map of type IR values to WASM type code values
442 typeCodes: Object.assign(Object.create(null),{
443 f64: 0x7c, f32: 0x7d, i64: 0x7e, i32: 0x7f
445 /** Encodes n, which must be <2^14 (16384), into target array
446 tgt, as a little-endian value, using the given method
447 ('push' or 'unshift'). */
448 uleb128Encode: function(tgt, method, n){
449 if(n<128) tgt[method](n);
450 else tgt[method]( (n % 128) | 128, n>>7);
452 /** Intentionally-lax pattern for Jaccwabyt-format function
453 pointer signatures, the intent of which is simply to
454 distinguish them from Emscripten-format signatures. The
455 downstream checks are less lax. */
456 rxJSig: /^(\w)\((\w*)\)$/,
457 /** Returns the parameter-value part of the given signature
458 string. */
459 sigParams: function(sig){
460 const m = f._.rxJSig.exec(sig);
461 return m ? m[2] : sig.substr(1);
463 /** Returns the IR value for the given letter or throws
464 if the letter is invalid. */
465 letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x),
466 /** Returns an object describing the result type and parameter
467 type(s) of the given function signature, or throws if the
468 signature is invalid. */
469 /******** // only valid for use with the WebAssembly.Function ctor, which
470 // is not yet documented on MDN.
471 sigToWasm: function(sig){
472 const rc = {parameters:[], results: []};
473 if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0]));
474 for(const x of f._.sigParams(sig)){
475 rc.parameters.push(f._.typeCodes(x));
477 return rc;
478 },************/
479 /** Pushes the WASM data type code for the given signature
480 letter to the given target array. Throws if letter is
481 invalid. */
482 pushSigType: (dest, letter)=>dest.push(f._.typeCodes[f._.letterType(letter)])
484 }/*static init*/
485 if('string'===typeof func){
486 const x = sig;
487 sig = func;
488 func = x;
490 const sigParams = f._.sigParams(sig);
491 const wasmCode = [0x01/*count: 1*/, 0x60/*function*/];
492 f._.uleb128Encode(wasmCode, 'push', sigParams.length);
493 for(const x of sigParams) f._.pushSigType(wasmCode, x);
494 if('v'===sig[0]) wasmCode.push(0);
495 else{
496 wasmCode.push(1);
497 f._.pushSigType(wasmCode, sig[0]);
499 f._.uleb128Encode(wasmCode, 'unshift', wasmCode.length)/* type section length */;
500 wasmCode.unshift(
501 0x00, 0x61, 0x73, 0x6d, /* magic: "\0asm" */
502 0x01, 0x00, 0x00, 0x00, /* version: 1 */
503 0x01 /* type section code */
505 wasmCode.push(
506 /* import section: */ 0x02, 0x07,
507 /* (import "e" "f" (func 0 (type 0))): */
508 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
509 /* export section: */ 0x07, 0x05,
510 /* (export "f" (func 0 (type 0))): */
511 0x01, 0x01, 0x66, 0x00, 0x00
513 return (new WebAssembly.Instance(
514 new WebAssembly.Module(new Uint8Array(wasmCode)), {
515 e: { f: func }
516 })).exports['f'];
517 }/*jsFuncToWasm()*/;
520 Documented as target.installFunction() except for the 3rd
521 argument: if truthy, the newly-created function pointer
522 is stashed in the current scoped-alloc scope and will be
523 cleaned up at the matching scopedAllocPop(), else it
524 is not stashed there.
526 const __installFunction = function f(func, sig, scoped){
527 if(scoped && !cache.scopedAlloc.length){
528 toss("No scopedAllocPush() scope is active.");
530 if('string'===typeof func){
531 const x = sig;
532 sig = func;
533 func = x;
535 if('string'!==typeof sig || !(func instanceof Function)){
536 toss("Invalid arguments: expecting (function,signature) "+
537 "or (signature,function).");
539 const ft = target.functionTable();
540 const oldLen = ft.length;
541 let ptr;
542 while(cache.freeFuncIndexes.length){
543 ptr = cache.freeFuncIndexes.pop();
544 if(ft.get(ptr)){ /* Table was modified via a different API */
545 ptr = null;
546 continue;
547 }else{
548 break;
551 if(!ptr){
552 ptr = oldLen;
553 ft.grow(1);
555 try{
556 /*this will only work if func is a WASM-exported function*/
557 ft.set(ptr, func);
558 if(scoped){
559 cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
561 return ptr;
562 }catch(e){
563 if(!(e instanceof TypeError)){
564 if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
565 throw e;
568 // It's not a WASM-exported function, so compile one...
569 try {
570 const fptr = target.jsFuncToWasm(func, sig);
571 ft.set(ptr, fptr);
572 if(scoped){
573 cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
575 }catch(e){
576 if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
577 throw e;
579 return ptr;
583 Expects a JS function and signature, exactly as for
584 this.jsFuncToWasm(). It uses that function to create a
585 WASM-exported function, installs that function to the next
586 available slot of this.functionTable(), and returns the
587 function's index in that table (which acts as a pointer to that
588 function). The returned pointer can be passed to
589 uninstallFunction() to uninstall it and free up the table slot for
590 reuse.
592 If passed (string,function) arguments then it treats the first
593 argument as the signature and second as the function.
595 As a special case, if the passed-in function is a WASM-exported
596 function then the signature argument is ignored and func is
597 installed as-is, without requiring re-compilation/re-wrapping.
599 This function will propagate an exception if
600 WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
601 The former case can happen in an Emscripten-compiled
602 environment when building without Emscripten's
603 `-sALLOW_TABLE_GROWTH` flag.
605 Sidebar: this function differs from Emscripten's addFunction()
606 _primarily_ in that it does not share that function's
607 undocumented behavior of reusing a function if it's passed to
608 addFunction() more than once, which leads to uninstallFunction()
609 breaking clients which do not take care to avoid that case:
611 https://github.com/emscripten-core/emscripten/issues/17323
613 target.installFunction = (func, sig)=>__installFunction(func, sig, false);
616 Works exactly like installFunction() but requires that a
617 scopedAllocPush() is active and uninstalls the given function
618 when that alloc scope is popped via scopedAllocPop().
619 This is used for implementing JS/WASM function bindings which
620 should only persist for the life of a call into a single
621 C-side function.
623 target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true);
626 Requires a pointer value previously returned from
627 this.installFunction(). Removes that function from the WASM
628 function table, marks its table slot as free for re-use, and
629 returns that function. It is illegal to call this before
630 installFunction() has been called and results are undefined if
631 ptr was not returned by that function. The returned function
632 may be passed back to installFunction() to reinstall it.
634 To simplify certain use cases, if passed a falsy non-0 value
635 (noting that 0 is a valid function table index), this function
636 has no side effects and returns undefined.
638 target.uninstallFunction = function(ptr){
639 if(!ptr && 0!==ptr) return undefined;
640 const fi = cache.freeFuncIndexes;
641 const ft = target.functionTable();
642 fi.push(ptr);
643 const rc = ft.get(ptr);
644 ft.set(ptr, null);
645 return rc;
649 Given a WASM heap memory address and a data type name in the form
650 (i8, i16, i32, i64, float (or f32), double (or f64)), this
651 fetches the numeric value from that address and returns it as a
652 number or, for the case of type='i64', a BigInt (noting that that
653 type triggers an exception if this.bigIntEnabled is
654 falsy). Throws if given an invalid type.
656 If the first argument is an array, it is treated as an array of
657 addresses and the result is an array of the values from each of
658 those address, using the same 2nd argument for determining the
659 value type to fetch.
661 As a special case, if type ends with a `*`, it is considered to
662 be a pointer type and is treated as the WASM numeric type
663 appropriate for the pointer size (`i32`).
665 While likely not obvious, this routine and its poke()
666 counterpart are how pointer-to-value _output_ parameters
667 in WASM-compiled C code can be interacted with:
670 const ptr = alloc(4);
671 poke(ptr, 0, 'i32'); // clear the ptr's value
672 aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x);
673 const result = peek(ptr, 'i32'); // fetch ptr's value
674 dealloc(ptr);
677 scopedAlloc() and friends can be used to make handling of
678 `ptr` safe against leaks in the case of an exception:
681 let result;
682 const scope = scopedAllocPush();
683 try{
684 const ptr = scopedAlloc(4);
685 poke(ptr, 0, 'i32');
686 aCFuncWithOutputPtrArg( ptr );
687 result = peek(ptr, 'i32');
688 }finally{
689 scopedAllocPop(scope);
693 As a rule poke() must be called to set (typically zero
694 out) the pointer's value, else it will contain an essentially
695 random value.
697 ACHTUNG: calling this often, e.g. in a loop, can have a noticably
698 painful impact on performance. Rather than doing so, use
699 heapForSize() to fetch the heap object and read directly from it.
701 See: poke()
703 target.peek = function f(ptr, type='i8'){
704 if(type.endsWith('*')) type = ptrIR;
705 const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
706 ? cache : heapWrappers();
707 const list = Array.isArray(ptr) ? [] : undefined;
708 let rc;
710 if(list) ptr = arguments[0].shift();
711 switch(type){
712 case 'i1':
713 case 'i8': rc = c.HEAP8[ptr>>0]; break;
714 case 'i16': rc = c.HEAP16[ptr>>1]; break;
715 case 'i32': rc = c.HEAP32[ptr>>2]; break;
716 case 'float': case 'f32': rc = c.HEAP32F[ptr>>2]; break;
717 case 'double': case 'f64': rc = Number(c.HEAP64F[ptr>>3]); break;
718 case 'i64':
719 if(target.bigIntEnabled){
720 rc = BigInt(c.HEAP64[ptr>>3]);
721 break;
723 /* fallthru */
724 default:
725 toss('Invalid type for peek():',type);
727 if(list) list.push(rc);
728 }while(list && arguments[0].length);
729 return list || rc;
733 The counterpart of peek(), this sets a numeric value at
734 the given WASM heap address, using the type to define how many
735 bytes are written. Throws if given an invalid type. See
736 peek() for details about the type argument. If the 3rd
737 argument ends with `*` then it is treated as a pointer type and
738 this function behaves as if the 3rd argument were `i32`.
740 If the first argument is an array, it is treated like a list
741 of pointers and the given value is written to each one.
743 Returns `this`. (Prior to 2022-12-09 it returns this function.)
745 ACHTUNG: calling this often, e.g. in a loop, can have a noticably
746 painful impact on performance. Rather than doing so, use
747 heapForSize() to fetch the heap object and assign directly to it
748 or use the heap's set() method.
750 target.poke = function(ptr, value, type='i8'){
751 if (type.endsWith('*')) type = ptrIR;
752 const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
753 ? cache : heapWrappers();
754 for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
755 switch (type) {
756 case 'i1':
757 case 'i8': c.HEAP8[p>>0] = value; continue;
758 case 'i16': c.HEAP16[p>>1] = value; continue;
759 case 'i32': c.HEAP32[p>>2] = value; continue;
760 case 'float': case 'f32': c.HEAP32F[p>>2] = value; continue;
761 case 'double': case 'f64': c.HEAP64F[p>>3] = value; continue;
762 case 'i64':
763 if(c.HEAP64){
764 c.HEAP64[p>>3] = BigInt(value);
765 continue;
767 /* fallthru */
768 default:
769 toss('Invalid type for poke(): ' + type);
772 return this;
776 Convenience form of peek() intended for fetching
777 pointer-to-pointer values. If passed a single non-array argument
778 it returns the value of that one pointer address. If passed
779 multiple arguments, or a single array of arguments, it returns an
780 array of their values.
782 target.peekPtr = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), ptrIR );
785 A variant of poke() intended for setting pointer-to-pointer
786 values. Its differences from poke() are that (1) it defaults to a
787 value of 0 and (2) it always writes to the pointer-sized heap
788 view.
790 target.pokePtr = (ptr, value=0)=>target.poke(ptr, value, ptrIR);
793 Convenience form of peek() intended for fetching i8 values. If
794 passed a single non-array argument it returns the value of that
795 one pointer address. If passed multiple arguments, or a single
796 array of arguments, it returns an array of their values.
798 target.peek8 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i8' );
800 Convience form of poke() intended for setting individual bytes.
801 Its difference from poke() is that it always writes to the
802 i8-sized heap view.
804 target.poke8 = (ptr, value)=>target.poke(ptr, value, 'i8');
805 /** i16 variant of peek8(). */
806 target.peek16 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i16' );
807 /** i16 variant of poke8(). */
808 target.poke16 = (ptr, value)=>target.poke(ptr, value, 'i16');
809 /** i32 variant of peek8(). */
810 target.peek32 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i32' );
811 /** i32 variant of poke8(). */
812 target.poke32 = (ptr, value)=>target.poke(ptr, value, 'i32');
813 /** i64 variant of peek8(). Will throw if this build is not
814 configured for BigInt support. */
815 target.peek64 = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'i64' );
816 /** i64 variant of poke8(). Will throw if this build is not
817 configured for BigInt support. Note that this returns
818 a BigInt-type value, not a Number-type value. */
819 target.poke64 = (ptr, value)=>target.poke(ptr, value, 'i64');
820 /** f32 variant of peek8(). */
821 target.peek32f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f32' );
822 /** f32 variant of poke8(). */
823 target.poke32f = (ptr, value)=>target.poke(ptr, value, 'f32');
824 /** f64 variant of peek8(). */
825 target.peek64f = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), 'f64' );
826 /** f64 variant of poke8(). */
827 target.poke64f = (ptr, value)=>target.poke(ptr, value, 'f64');
829 /** Deprecated alias for getMemValue() */
830 target.getMemValue = target.peek;
831 /** Deprecated alias for peekPtr() */
832 target.getPtrValue = target.peekPtr;
833 /** Deprecated alias for poke() */
834 target.setMemValue = target.poke;
835 /** Deprecated alias for pokePtr() */
836 target.setPtrValue = target.pokePtr;
839 Returns true if the given value appears to be legal for use as
840 a WASM pointer value. Its _range_ of values is not (cannot be)
841 validated except to ensure that it is a 32-bit integer with a
842 value of 0 or greater. Likewise, it cannot verify whether the
843 value actually refers to allocated memory in the WASM heap.
845 target.isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0);
848 isPtr() is an alias for isPtr32(). If/when 64-bit WASM pointer
849 support becomes widespread, it will become an alias for either
850 isPtr32() or the as-yet-hypothetical isPtr64(), depending on a
851 configuration option.
853 target.isPtr = target.isPtr32;
856 Expects ptr to be a pointer into the WASM heap memory which
857 refers to a NUL-terminated C-style string encoded as UTF-8.
858 Returns the length, in bytes, of the string, as for `strlen(3)`.
859 As a special case, if !ptr or if it's not a pointer then it
860 returns `null`. Throws if ptr is out of range for
861 target.heap8u().
863 target.cstrlen = function(ptr){
864 if(!ptr || !target.isPtr(ptr)) return null;
865 const h = heapWrappers().HEAP8U;
866 let pos = ptr;
867 for( ; h[pos] !== 0; ++pos ){}
868 return pos - ptr;
871 /** Internal helper to use in operations which need to distinguish
872 between SharedArrayBuffer heap memory and non-shared heap. */
873 const __SAB = ('undefined'===typeof SharedArrayBuffer)
874 ? function(){} : SharedArrayBuffer;
875 const __utf8Decode = function(arrayBuffer, begin, end){
876 return cache.utf8Decoder.decode(
877 (arrayBuffer.buffer instanceof __SAB)
878 ? arrayBuffer.slice(begin, end)
879 : arrayBuffer.subarray(begin, end)
884 Expects ptr to be a pointer into the WASM heap memory which
885 refers to a NUL-terminated C-style string encoded as UTF-8. This
886 function counts its byte length using cstrlen() then returns a
887 JS-format string representing its contents. As a special case, if
888 ptr is falsy or not a pointer, `null` is returned.
890 target.cstrToJs = function(ptr){
891 const n = target.cstrlen(ptr);
892 return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : "");
896 Given a JS string, this function returns its UTF-8 length in
897 bytes. Returns null if str is not a string.
899 target.jstrlen = function(str){
900 /** Attribution: derived from Emscripten's lengthBytesUTF8() */
901 if('string'!==typeof str) return null;
902 const n = str.length;
903 let len = 0;
904 for(let i = 0; i < n; ++i){
905 let u = str.charCodeAt(i);
906 if(u>=0xd800 && u<=0xdfff){
907 u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
909 if(u<=0x7f) ++len;
910 else if(u<=0x7ff) len += 2;
911 else if(u<=0xffff) len += 3;
912 else len += 4;
914 return len;
918 Encodes the given JS string as UTF8 into the given TypedArray
919 tgt, starting at the given offset and writing, at most, maxBytes
920 bytes (including the NUL terminator if addNul is true, else no
921 NUL is added). If it writes any bytes at all and addNul is true,
922 it always NUL-terminates the output, even if doing so means that
923 the NUL byte is all that it writes.
925 If maxBytes is negative (the default) then it is treated as the
926 remaining length of tgt, starting at the given offset.
928 If writing the last character would surpass the maxBytes count
929 because the character is multi-byte, that character will not be
930 written (as opposed to writing a truncated multi-byte character).
931 This can lead to it writing as many as 3 fewer bytes than
932 maxBytes specifies.
934 Returns the number of bytes written to the target, _including_
935 the NUL terminator (if any). If it returns 0, it wrote nothing at
936 all, which can happen if:
938 - str is empty and addNul is false.
939 - offset < 0.
940 - maxBytes == 0.
941 - maxBytes is less than the byte length of a multi-byte str[0].
943 Throws if tgt is not an Int8Array or Uint8Array.
945 Design notes:
947 - In C's strcpy(), the destination pointer is the first
948 argument. That is not the case here primarily because the 3rd+
949 arguments are all referring to the destination, so it seems to
950 make sense to have them grouped with it.
952 - Emscripten's counterpart of this function (stringToUTF8Array())
953 returns the number of bytes written sans NUL terminator. That
954 is, however, ambiguous: str.length===0 or maxBytes===(0 or 1)
955 all cause 0 to be returned.
957 target.jstrcpy = function(jstr, tgt, offset = 0, maxBytes = -1, addNul = true){
958 /** Attribution: the encoding bits are taken from Emscripten's
959 stringToUTF8Array(). */
960 if(!tgt || (!(tgt instanceof Int8Array) && !(tgt instanceof Uint8Array))){
961 toss("jstrcpy() target must be an Int8Array or Uint8Array.");
963 if(maxBytes<0) maxBytes = tgt.length - offset;
964 if(!(maxBytes>0) || !(offset>=0)) return 0;
965 let i = 0, max = jstr.length;
966 const begin = offset, end = offset + maxBytes - (addNul ? 1 : 0);
967 for(; i < max && offset < end; ++i){
968 let u = jstr.charCodeAt(i);
969 if(u>=0xd800 && u<=0xdfff){
970 u = 0x10000 + ((u & 0x3FF) << 10) | (jstr.charCodeAt(++i) & 0x3FF);
972 if(u<=0x7f){
973 if(offset >= end) break;
974 tgt[offset++] = u;
975 }else if(u<=0x7ff){
976 if(offset + 1 >= end) break;
977 tgt[offset++] = 0xC0 | (u >> 6);
978 tgt[offset++] = 0x80 | (u & 0x3f);
979 }else if(u<=0xffff){
980 if(offset + 2 >= end) break;
981 tgt[offset++] = 0xe0 | (u >> 12);
982 tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
983 tgt[offset++] = 0x80 | (u & 0x3f);
984 }else{
985 if(offset + 3 >= end) break;
986 tgt[offset++] = 0xf0 | (u >> 18);
987 tgt[offset++] = 0x80 | ((u >> 12) & 0x3f);
988 tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
989 tgt[offset++] = 0x80 | (u & 0x3f);
992 if(addNul) tgt[offset++] = 0;
993 return offset - begin;
997 Works similarly to C's strncpy(), copying, at most, n bytes (not
998 characters) from srcPtr to tgtPtr. It copies until n bytes have
999 been copied or a 0 byte is reached in src. _Unlike_ strncpy(), it
1000 returns the number of bytes it assigns in tgtPtr, _including_ the
1001 NUL byte (if any). If n is reached before a NUL byte in srcPtr,
1002 tgtPtr will _not_ be NULL-terminated. If a NUL byte is reached
1003 before n bytes are copied, tgtPtr will be NUL-terminated.
1005 If n is negative, cstrlen(srcPtr)+1 is used to calculate it, the
1006 +1 being for the NUL byte.
1008 Throws if tgtPtr or srcPtr are falsy. Results are undefined if:
1010 - either is not a pointer into the WASM heap or
1012 - srcPtr is not NUL-terminated AND n is less than srcPtr's
1013 logical length.
1015 ACHTUNG: it is possible to copy partial multi-byte characters
1016 this way, and converting such strings back to JS strings will
1017 have undefined results.
1019 target.cstrncpy = function(tgtPtr, srcPtr, n){
1020 if(!tgtPtr || !srcPtr) toss("cstrncpy() does not accept NULL strings.");
1021 if(n<0) n = target.cstrlen(strPtr)+1;
1022 else if(!(n>0)) return 0;
1023 const heap = target.heap8u();
1024 let i = 0, ch;
1025 for(; i < n && (ch = heap[srcPtr+i]); ++i){
1026 heap[tgtPtr+i] = ch;
1028 if(i<n) heap[tgtPtr + i++] = 0;
1029 return i;
1033 For the given JS string, returns a Uint8Array of its contents
1034 encoded as UTF-8. If addNul is true, the returned array will have
1035 a trailing 0 entry, else it will not.
1037 target.jstrToUintArray = (str, addNul=false)=>{
1038 return cache.utf8Encoder.encode(addNul ? (str+"\0") : str);
1039 // Or the hard way...
1040 /** Attribution: derived from Emscripten's stringToUTF8Array() */
1041 //const a = [], max = str.length;
1042 //let i = 0, pos = 0;
1043 //for(; i < max; ++i){
1044 // let u = str.charCodeAt(i);
1045 // if(u>=0xd800 && u<=0xdfff){
1046 // u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
1047 // }
1048 // if(u<=0x7f) a[pos++] = u;
1049 // else if(u<=0x7ff){
1050 // a[pos++] = 0xC0 | (u >> 6);
1051 // a[pos++] = 0x80 | (u & 63);
1052 // }else if(u<=0xffff){
1053 // a[pos++] = 0xe0 | (u >> 12);
1054 // a[pos++] = 0x80 | ((u >> 6) & 63);
1055 // a[pos++] = 0x80 | (u & 63);
1056 // }else{
1057 // a[pos++] = 0xf0 | (u >> 18);
1058 // a[pos++] = 0x80 | ((u >> 12) & 63);
1059 // a[pos++] = 0x80 | ((u >> 6) & 63);
1060 // a[pos++] = 0x80 | (u & 63);
1061 // }
1062 // }
1063 // return new Uint8Array(a);
1066 const __affirmAlloc = (obj,funcName)=>{
1067 if(!(obj.alloc instanceof Function) ||
1068 !(obj.dealloc instanceof Function)){
1069 toss("Object is missing alloc() and/or dealloc() function(s)",
1070 "required by",funcName+"().");
1074 const __allocCStr = function(jstr, returnWithLength, allocator, funcName){
1075 __affirmAlloc(target, funcName);
1076 if('string'!==typeof jstr) return null;
1077 if(0){/* older impl, possibly more widely compatible? */
1078 const n = target.jstrlen(jstr),
1079 ptr = allocator(n+1);
1080 target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true);
1081 return returnWithLength ? [ptr, n] : ptr;
1082 }else{/* newer, (probably) faster and (certainly) simpler impl */
1083 const u = cache.utf8Encoder.encode(jstr),
1084 ptr = allocator(u.length+1),
1085 heap = heapWrappers().HEAP8U;
1086 heap.set(u, ptr);
1087 heap[ptr + u.length] = 0;
1088 return returnWithLength ? [ptr, u.length] : ptr;
1093 Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1
1094 bytes of memory, copies jstr to that memory using jstrcpy(),
1095 NUL-terminates it, and returns the pointer to that C-string.
1096 Ownership of the pointer is transfered to the caller, who must
1097 eventually pass the pointer to dealloc() to free it.
1099 If passed a truthy 2nd argument then its return semantics change:
1100 it returns [ptr,n], where ptr is the C-string's pointer and n is
1101 its cstrlen().
1103 Throws if `target.alloc` or `target.dealloc` are not functions.
1105 target.allocCString =
1106 (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
1107 target.alloc, 'allocCString()');
1110 Starts an "allocation scope." All allocations made using
1111 scopedAlloc() are recorded in this scope and are freed when the
1112 value returned from this function is passed to
1113 scopedAllocPop().
1115 This family of functions requires that the API's object have both
1116 `alloc()` and `dealloc()` methods, else this function will throw.
1118 Intended usage:
1121 const scope = scopedAllocPush();
1122 try {
1123 const ptr1 = scopedAlloc(100);
1124 const ptr2 = scopedAlloc(200);
1125 const ptr3 = scopedAlloc(300);
1127 // Note that only allocations made via scopedAlloc()
1128 // are managed by this allocation scope.
1129 }finally{
1130 scopedAllocPop(scope);
1134 The value returned by this function must be treated as opaque by
1135 the caller, suitable _only_ for passing to scopedAllocPop().
1136 Its type and value are not part of this function's API and may
1137 change in any given version of this code.
1139 `scopedAlloc.level` can be used to determine how many scoped
1140 alloc levels are currently active.
1142 target.scopedAllocPush = function(){
1143 __affirmAlloc(target, 'scopedAllocPush');
1144 const a = [];
1145 cache.scopedAlloc.push(a);
1146 return a;
1150 Cleans up all allocations made using scopedAlloc() in the context
1151 of the given opaque state object, which must be a value returned
1152 by scopedAllocPush(). See that function for an example of how to
1153 use this function.
1155 Though scoped allocations are managed like a stack, this API
1156 behaves properly if allocation scopes are popped in an order
1157 other than the order they were pushed.
1159 If called with no arguments, it pops the most recent
1160 scopedAllocPush() result:
1163 scopedAllocPush();
1164 try{ ... } finally { scopedAllocPop(); }
1167 It's generally recommended that it be passed an explicit argument
1168 to help ensure that push/push are used in matching pairs, but in
1169 trivial code that may be a non-issue.
1171 target.scopedAllocPop = function(state){
1172 __affirmAlloc(target, 'scopedAllocPop');
1173 const n = arguments.length
1174 ? cache.scopedAlloc.indexOf(state)
1175 : cache.scopedAlloc.length-1;
1176 if(n<0) toss("Invalid state object for scopedAllocPop().");
1177 if(0===arguments.length) state = cache.scopedAlloc[n];
1178 cache.scopedAlloc.splice(n,1);
1179 for(let p; (p = state.pop()); ){
1180 if(target.functionEntry(p)){
1181 //console.warn("scopedAllocPop() uninstalling function",p);
1182 target.uninstallFunction(p);
1184 else target.dealloc(p);
1189 Allocates n bytes of memory using this.alloc() and records that
1190 fact in the state for the most recent call of scopedAllocPush().
1191 Ownership of the memory is given to scopedAllocPop(), which
1192 will clean it up when it is called. The memory _must not_ be
1193 passed to this.dealloc(). Throws if this API object is missing
1194 the required `alloc()` or `dealloc()` functions or no scoped
1195 alloc is active.
1197 See scopedAllocPush() for an example of how to use this function.
1199 The `level` property of this function can be queried to query how
1200 many scoped allocation levels are currently active.
1202 See also: scopedAllocPtr(), scopedAllocCString()
1204 target.scopedAlloc = function(n){
1205 if(!cache.scopedAlloc.length){
1206 toss("No scopedAllocPush() scope is active.");
1208 const p = target.alloc(n);
1209 cache.scopedAlloc[cache.scopedAlloc.length-1].push(p);
1210 return p;
1213 Object.defineProperty(target.scopedAlloc, 'level', {
1214 configurable: false, enumerable: false,
1215 get: ()=>cache.scopedAlloc.length,
1216 set: ()=>toss("The 'active' property is read-only.")
1220 Works identically to allocCString() except that it allocates the
1221 memory using scopedAlloc().
1223 Will throw if no scopedAllocPush() call is active.
1225 target.scopedAllocCString =
1226 (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
1227 target.scopedAlloc, 'scopedAllocCString()');
1229 // impl for allocMainArgv() and scopedAllocMainArgv().
1230 const __allocMainArgv = function(isScoped, list){
1231 const pList = target[
1232 isScoped ? 'scopedAlloc' : 'alloc'
1233 ]((list.length + 1) * target.ptrSizeof);
1234 let i = 0;
1235 list.forEach((e)=>{
1236 target.pokePtr(pList + (target.ptrSizeof * i++),
1237 target[
1238 isScoped ? 'scopedAllocCString' : 'allocCString'
1239 ](""+e));
1241 target.pokePtr(pList + (target.ptrSizeof * i), 0);
1242 return pList;
1246 Creates an array, using scopedAlloc(), suitable for passing to a
1247 C-level main() routine. The input is a collection with a length
1248 property and a forEach() method. A block of memory
1249 (list.length+1) entries long is allocated and each pointer-sized
1250 block of that memory is populated with a scopedAllocCString()
1251 conversion of the (""+value) of each element, with the exception
1252 that the final entry is a NULL pointer. Returns a pointer to the
1253 start of the list, suitable for passing as the 2nd argument to a
1254 C-style main() function.
1256 Throws if scopedAllocPush() is not active.
1258 Design note: the returned array is allocated with an extra NULL
1259 pointer entry to accommodate certain APIs, but client code which
1260 does not need that functionality should treat the returned array
1261 as list.length entries long.
1263 target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list);
1266 Identical to scopedAllocMainArgv() but uses alloc() instead of
1267 scopedAlloc().
1269 target.allocMainArgv = (list)=>__allocMainArgv(false, list);
1272 Expects to be given a C-style string array and its length. It
1273 returns a JS array of strings and/or nulls: any entry in the
1274 pArgv array which is NULL results in a null entry in the result
1275 array. If argc is 0 then an empty array is returned.
1277 Results are undefined if any entry in the first argc entries of
1278 pArgv are neither 0 (NULL) nor legal UTF-format C strings.
1280 To be clear, the expected C-style arguments to be passed to this
1281 function are `(int, char **)` (optionally const-qualified).
1283 target.cArgvToJs = (argc, pArgv)=>{
1284 const list = [];
1285 for(let i = 0; i < argc; ++i){
1286 const arg = target.peekPtr(pArgv + (target.ptrSizeof * i));
1287 list.push( arg ? target.cstrToJs(arg) : null );
1289 return list;
1293 Wraps function call func() in a scopedAllocPush() and
1294 scopedAllocPop() block, such that all calls to scopedAlloc() and
1295 friends from within that call will have their memory freed
1296 automatically when func() returns. If func throws or propagates
1297 an exception, the scope is still popped, otherwise it returns the
1298 result of calling func().
1300 target.scopedAllocCall = function(func){
1301 target.scopedAllocPush();
1302 try{ return func() } finally{ target.scopedAllocPop() }
1305 /** Internal impl for allocPtr() and scopedAllocPtr(). */
1306 const __allocPtr = function(howMany, safePtrSize, method){
1307 __affirmAlloc(target, method);
1308 const pIr = safePtrSize ? 'i64' : ptrIR;
1309 let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof));
1310 target.poke(m, 0, pIr)
1311 if(1===howMany){
1312 return m;
1314 const a = [m];
1315 for(let i = 1; i < howMany; ++i){
1316 m += (safePtrSize ? 8 : ptrSizeof);
1317 a[i] = m;
1318 target.poke(m, 0, pIr);
1320 return a;
1324 Allocates one or more pointers as a single chunk of memory and
1325 zeroes them out.
1327 The first argument is the number of pointers to allocate. The
1328 second specifies whether they should use a "safe" pointer size (8
1329 bytes) or whether they may use the default pointer size
1330 (typically 4 but also possibly 8).
1332 How the result is returned depends on its first argument: if
1333 passed 1, it returns the allocated memory address. If passed more
1334 than one then an array of pointer addresses is returned, which
1335 can optionally be used with "destructuring assignment" like this:
1338 const [p1, p2, p3] = allocPtr(3);
1341 ACHTUNG: when freeing the memory, pass only the _first_ result
1342 value to dealloc(). The others are part of the same memory chunk
1343 and must not be freed separately.
1345 The reason for the 2nd argument is..
1347 When one of the returned pointers will refer to a 64-bit value,
1348 e.g. a double or int64, an that value must be written or fetched,
1349 e.g. using poke() or peek(), it is important that
1350 the pointer in question be aligned to an 8-byte boundary or else
1351 it will not be fetched or written properly and will corrupt or
1352 read neighboring memory. It is only safe to pass false when the
1353 client code is certain that it will only get/fetch 4-byte values
1354 (or smaller).
1356 target.allocPtr =
1357 (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'alloc');
1360 Identical to allocPtr() except that it allocates using scopedAlloc()
1361 instead of alloc().
1363 target.scopedAllocPtr =
1364 (howMany=1, safePtrSize=true)=>__allocPtr(howMany, safePtrSize, 'scopedAlloc');
1367 If target.exports[name] exists, it is returned, else an
1368 exception is thrown.
1370 target.xGet = function(name){
1371 return target.exports[name] || toss("Cannot find exported symbol:",name);
1374 const __argcMismatch =
1375 (f,n)=>toss(f+"() requires",n,"argument(s).");
1378 Looks up a WASM-exported function named fname from
1379 target.exports. If found, it is called, passed all remaining
1380 arguments, and its return value is returned to xCall's caller. If
1381 not found, an exception is thrown. This function does no
1382 conversion of argument or return types, but see xWrap() and
1383 xCallWrapped() for variants which do.
1385 If the first argument is a function is is assumed to be
1386 a WASM-bound function and is used as-is instead of looking up
1387 the function via xGet().
1389 As a special case, if passed only 1 argument after the name and
1390 that argument in an Array, that array's entries become the
1391 function arguments. (This is not an ambiguous case because it's
1392 not legal to pass an Array object to a WASM function.)
1394 target.xCall = function(fname, ...args){
1395 const f = (fname instanceof Function) ? fname : target.xGet(fname);
1396 if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function.");
1397 if(f.length!==args.length) __argcMismatch(((f===fname) ? f.name : fname),f.length)
1398 /* This is arguably over-pedantic but we want to help clients keep
1399 from shooting themselves in the foot when calling C APIs. */;
1400 return (2===arguments.length && Array.isArray(arguments[1]))
1401 ? f.apply(null, arguments[1])
1402 : f.apply(null, args);
1406 State for use with xWrap()
1408 cache.xWrap = Object.create(null);
1409 cache.xWrap.convert = Object.create(null);
1410 /** Map of type names to argument conversion functions. */
1411 cache.xWrap.convert.arg = new Map;
1412 /** Map of type names to return result conversion functions. */
1413 cache.xWrap.convert.result = new Map;
1414 const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result;
1416 if(target.bigIntEnabled){
1417 xArg.set('i64', (i)=>BigInt(i));
1419 const __xArgPtr = 'i32' === ptrIR
1420 ? ((i)=>(i | 0)) : ((i)=>(BigInt(i) | BigInt(0)));
1421 xArg.set('i32', __xArgPtr )
1422 .set('i16', (i)=>((i | 0) & 0xFFFF))
1423 .set('i8', (i)=>((i | 0) & 0xFF))
1424 .set('f32', (i)=>Number(i).valueOf())
1425 .set('float', xArg.get('f32'))
1426 .set('f64', xArg.get('f32'))
1427 .set('double', xArg.get('f64'))
1428 .set('int', xArg.get('i32'))
1429 .set('null', (i)=>i)
1430 .set(null, xArg.get('null'))
1431 .set('**', __xArgPtr)
1432 .set('*', __xArgPtr);
1433 xResult.set('*', __xArgPtr)
1434 .set('pointer', __xArgPtr)
1435 .set('number', (v)=>Number(v))
1436 .set('void', (v)=>undefined)
1437 .set('null', (v)=>v)
1438 .set(null, xResult.get('null'));
1440 { /* Copy certain xArg[...] handlers to xResult[...] and
1441 add pointer-style variants of them. */
1442 const copyToResult = ['i8', 'i16', 'i32', 'int',
1443 'f32', 'float', 'f64', 'double'];
1444 if(target.bigIntEnabled) copyToResult.push('i64');
1445 const adaptPtr = xArg.get(ptrIR);
1446 for(const t of copyToResult){
1447 xArg.set(t+'*', adaptPtr);
1448 xResult.set(t+'*', adaptPtr);
1449 xResult.set(t, (xArg.get(t) || toss("Missing arg converter:",t)));
1454 In order for args of type string to work in various contexts in
1455 the sqlite3 API, we need to pass them on as, variably, a C-string
1456 or a pointer value. Thus for ARGs of type 'string' and
1457 '*'/'pointer' we behave differently depending on whether the
1458 argument is a string or not:
1460 - If v is a string, scopeAlloc() a new C-string from it and return
1461 that temp string's pointer.
1463 - Else return the value from the arg adapter defined for ptrIR.
1465 TODO? Permit an Int8Array/Uint8Array and convert it to a string?
1466 Would that be too much magic concentrated in one place, ready to
1467 backfire? We handle that at the client level in sqlite3 with a
1468 custom argument converter.
1470 const __xArgString = function(v){
1471 if('string'===typeof v) return target.scopedAllocCString(v);
1472 return v ? __xArgPtr(v) : null;
1474 xArg.set('string', __xArgString)
1475 .set('utf8', __xArgString)
1476 .set('pointer', __xArgString);
1477 //xArg.set('*', __xArgString);
1479 xResult.set('string', (i)=>target.cstrToJs(i))
1480 .set('utf8', xResult.get('string'))
1481 .set('string:dealloc', (i)=>{
1482 try { return i ? target.cstrToJs(i) : null }
1483 finally{ target.dealloc(i) }
1485 .set('utf8:dealloc', xResult.get('string:dealloc'))
1486 .set('json', (i)=>JSON.parse(target.cstrToJs(i)))
1487 .set('json:dealloc', (i)=>{
1488 try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
1489 finally{ target.dealloc(i) }
1493 Internal-use-only base class for FuncPtrAdapter and potentially
1494 additional stateful argument adapter classes.
1496 Note that its main interface (convertArg()) is strictly
1497 internal, not to be exposed to client code, as it may still
1498 need re-shaping. Only the constructors of concrete subclasses
1499 should be exposed to clients, and those in such a way that
1500 does not hinder internal redesign of the convertArg()
1501 interface.
1503 const AbstractArgAdapter = class {
1504 constructor(opt){
1505 this.name = opt.name || 'unnamed adapter';
1508 Gets called via xWrap() to "convert" v to whatever type
1509 this specific class supports.
1511 argIndex is the argv index of _this_ argument in the
1512 being-xWrap()'d call. argv is the current argument list
1513 undergoing xWrap() argument conversion. argv entries to the
1514 left of argIndex will have already undergone transformation and
1515 those to the right will not have (they will have the values the
1516 client-level code passed in, awaiting conversion). The RHS
1517 indexes must never be relied upon for anything because their
1518 types are indeterminate, whereas the LHS values will be
1519 WASM-compatible values by the time this is called.
1521 convertArg(v,argv,argIndex){
1522 toss("AbstractArgAdapter must be subclassed.");
1527 An attempt at adding function pointer conversion support to
1528 xWrap(). This type is recognized by xWrap() as a proxy for
1529 converting a JS function to a C-side function, either
1530 permanently, for the duration of a single call into the C layer,
1531 or semi-contextual, where it may keep track of a single binding
1532 for a given context and uninstall the binding if it's replaced.
1534 The constructor requires an options object with these properties:
1536 - name (optional): string describing the function binding. This
1537 is solely for debugging and error-reporting purposes. If not
1538 provided, an empty string is assumed.
1540 - signature: a function signature string compatible with
1541 jsFuncToWasm().
1543 - bindScope (string): one of ('transient', 'context',
1544 'singleton', 'permanent'). Bind scopes are:
1546 - 'transient': it will convert JS functions to WASM only for
1547 the duration of the xWrap()'d function call, using
1548 scopedInstallFunction(). Before that call returns, the
1549 WASM-side binding will be uninstalled.
1551 - 'singleton': holds one function-pointer binding for this
1552 instance. If it's called with a different function pointer,
1553 it uninstalls the previous one after converting the new
1554 value. This is only useful for use with "global" functions
1555 which do not rely on any state other than this function
1556 pointer. If the being-converted function pointer is intended
1557 to be mapped to some sort of state object (e.g. an
1558 `sqlite3*`) then "context" (see below) is the proper mode.
1560 - 'context': similar to singleton mode but for a given
1561 "context", where the context is a key provided by the user
1562 and possibly dependent on a small amount of call-time
1563 context. This mode is the default if bindScope is _not_ set
1564 but a property named contextKey (described below) is.
1566 - 'permanent': the function is installed and left there
1567 forever. There is no way to recover its pointer address
1568 later on.
1570 - callProxy (function): if set, this must be a function which
1571 will act as a proxy for any "converted" JS function. It is
1572 passed the being-converted function value and must return
1573 either that function or a function which acts on its
1574 behalf. The returned function will be the one which gets
1575 installed into the WASM function table. The proxy must perform
1576 any required argument conversion (noting that it will be called
1577 from C code, so will receive C-format arguments) before passing
1578 them on to the being-converted function. Whether or not the
1579 proxy itself must return a value depends on the context. If it
1580 does, it must be a WASM-friendly value, as it will be returning
1581 from a call made from native code.
1583 - contextKey (function): is only used if bindScope is 'context'
1584 or if bindScope is not set and this function is, in which case
1585 'context' is assumed. This function gets bound to this object,
1586 so its "this" is this object. It gets passed (argv,argIndex),
1587 where argIndex is the index of _this_ function pointer in its
1588 _wrapping_ function's arguments and argv is the _current_
1589 still-being-xWrap()-processed args array. All arguments to the
1590 left of argIndex will have been processed by xWrap() by the
1591 time this is called. argv[argIndex] will be the value the user
1592 passed in to the xWrap()'d function for the argument this
1593 FuncPtrAdapter is mapped to. Arguments to the right of
1594 argv[argIndex] will not yet have been converted before this is
1595 called. The function must return a key which uniquely
1596 identifies this function mapping context for _this_
1597 FuncPtrAdapter instance (other instances are not considered),
1598 taking into account that C functions often take some sort of
1599 state object as one or more of their arguments. As an example,
1600 if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and
1601 this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2)
1602 might return 'T@'+argv[1], or even just argv[1]. Note,
1603 however, that the (X*) argument will not yet have been
1604 processed by the time this is called and should not be used as
1605 part of that key because its pre-conversion data type might be
1606 unpredictable. Similarly, care must be taken with C-string-type
1607 arguments: those to the left in argv will, when this is called,
1608 be WASM pointers, whereas those to the right might (and likely
1609 do) have another data type. When using C-strings in keys, never
1610 use their pointers in the key because most C-strings in this
1611 constellation are transient.
1613 Yes, that ^^^ is quite awkward, but it's what we have.
1615 The constructor only saves the above state for later, and does
1616 not actually bind any functions. Its convertArg() method is
1617 called via xWrap() to perform any bindings.
1619 Shortcomings:
1621 - These "reverse" bindings, i.e. calling into a JS-defined
1622 function from a WASM-defined function (the generated proxy
1623 wrapper), lack all type conversion support. That means, for
1624 example, that...
1626 - Function pointers which include C-string arguments may still
1627 need a level of hand-written wrappers around them, depending on
1628 how they're used, in order to provide the client with JS
1629 strings. Alternately, clients will need to perform such conversions
1630 on their own, e.g. using cstrtojs(). Or maybe we can find a way
1631 to perform such conversions here, via addition of an xWrap()-style
1632 function signature to the options argument.
1634 xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter {
1635 constructor(opt) {
1636 super(opt);
1637 if(xArg.FuncPtrAdapter.warnOnUse){
1638 console.warn('xArg.FuncPtrAdapter is an internal-only API',
1639 'and is not intended to be invoked from',
1640 'client-level code. Invoked with:',opt);
1642 this.name = opt.name || "unnamed";
1643 this.signature = opt.signature;
1644 if(opt.contextKey instanceof Function){
1645 this.contextKey = opt.contextKey;
1646 if(!opt.bindScope) opt.bindScope = 'context';
1648 this.bindScope = opt.bindScope
1649 || toss("FuncPtrAdapter options requires a bindScope (explicit or implied).");
1650 if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){
1651 toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+
1652 "Expecting one of: ("+FuncPtrAdapter.bindScopes.join(', ')+')');
1654 this.isTransient = 'transient'===this.bindScope;
1655 this.isContext = 'context'===this.bindScope;
1656 this.isPermanent = 'permanent'===this.bindScope;
1657 this.singleton = ('singleton'===this.bindScope) ? [] : undefined;
1658 //console.warn("FuncPtrAdapter()",opt,this);
1659 this.callProxy = (opt.callProxy instanceof Function)
1660 ? opt.callProxy : undefined;
1664 Note that static class members are defined outside of the class
1665 to work around an emcc toolchain build problem: one of the
1666 tools in emsdk v3.1.42 does not support the static keyword.
1669 /* Dummy impl. Overwritten per-instance as needed. */
1670 contextKey(argv,argIndex){
1671 return this;
1674 /* Returns this objects mapping for the given context key, in the
1675 form of an an array, creating the mapping if needed. The key
1676 may be anything suitable for use in a Map. */
1677 contextMap(key){
1678 const cm = (this.__cmap || (this.__cmap = new Map));
1679 let rc = cm.get(key);
1680 if(undefined===rc) cm.set(key, (rc = []));
1681 return rc;
1685 Gets called via xWrap() to "convert" v to a WASM-bound function
1686 pointer. If v is one of (a pointer, null, undefined) then
1687 (v||0) is returned and any earlier function installed by this
1688 mapping _might_, depending on how it's bound, be uninstalled.
1689 If v is not one of those types, it must be a Function, for
1690 which it creates (if needed) a WASM function binding and
1691 returns the WASM pointer to that binding. If this instance is
1692 not in 'transient' mode, it will remember the binding for at
1693 least the next call, to avoid recreating the function binding
1694 unnecessarily.
1696 If it's passed a pointer(ish) value for v, it does _not_
1697 perform any function binding, so this object's bindMode is
1698 irrelevant for such cases.
1700 See the parent class's convertArg() docs for details on what
1701 exactly the 2nd and 3rd arguments are.
1703 convertArg(v,argv,argIndex){
1704 //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v);
1705 let pair = this.singleton;
1706 if(!pair && this.isContext){
1707 pair = this.contextMap(this.contextKey(argv,argIndex));
1708 //FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair);
1710 if(pair && pair[0]===v) return pair[1];
1711 if(v instanceof Function){
1712 /* Install a WASM binding and return its pointer. */
1713 //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
1714 if(this.callProxy) v = this.callProxy(v);
1715 const fp = __installFunction(v, this.signature, this.isTransient);
1716 if(FuncPtrAdapter.debugFuncInstall){
1717 FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this,
1718 this.contextKey(argv,argIndex), '@'+fp, v);
1720 if(pair){
1721 /* Replace existing stashed mapping */
1722 if(pair[1]){
1723 if(FuncPtrAdapter.debugFuncInstall){
1724 FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
1725 this.contextKey(argv,argIndex), '@'+pair[1], v);
1727 try{
1728 /* Because the pending native call might rely on the
1729 pointer we're replacing, e.g. as is normally the case
1730 with sqlite3's xDestroy() methods, we don't
1731 immediately uninstall but instead add its pointer to
1732 the scopedAlloc stack, which will be cleared when the
1733 xWrap() mechanism is done calling the native
1734 function. We're relying very much here on xWrap()
1735 having pushed an alloc scope.
1737 cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]);
1739 catch(e){/*ignored*/}
1741 pair[0] = v;
1742 pair[1] = fp;
1744 return fp;
1745 }else if(target.isPtr(v) || null===v || undefined===v){
1746 //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
1747 if(pair && pair[1] && pair[1]!==v){
1748 /* uninstall stashed mapping and replace stashed mapping with v. */
1749 if(FuncPtrAdapter.debugFuncInstall){
1750 FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
1751 this.contextKey(argv,argIndex), '@'+pair[1], v);
1753 try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) }
1754 catch(e){/*ignored*/}
1755 pair[0] = pair[1] = (v | 0);
1757 return v || 0;
1758 }else{
1759 throw new TypeError("Invalid FuncPtrAdapter argument type. "+
1760 "Expecting a function pointer or a "+
1761 (this.name ? this.name+' ' : '')+
1762 "function matching signature "+
1763 this.signature+".");
1765 }/*convertArg()*/
1766 }/*FuncPtrAdapter*/;
1768 /** If true, the constructor emits a warning. The intent is that
1769 this be set to true after bootstrapping of the higher-level
1770 client library is complete, to warn downstream clients that
1771 they shouldn't be relying on this implemenation detail which
1772 does not have a stable interface. */
1773 xArg.FuncPtrAdapter.warnOnUse = false;
1775 /** If true, convertArg() will FuncPtrAdapter.debugOut() when it
1776 (un)installs a function binding to/from WASM. Note that
1777 deinstallation of bindScope=transient bindings happens
1778 via scopedAllocPop() so will not be output. */
1779 xArg.FuncPtrAdapter.debugFuncInstall = false;
1781 /** Function used for debug output. */
1782 xArg.FuncPtrAdapter.debugOut = console.debug.bind(console);
1784 xArg.FuncPtrAdapter.bindScopes = [
1785 'transient', 'context', 'singleton', 'permanent'
1788 const __xArgAdapterCheck =
1789 (t)=>xArg.get(t) || toss("Argument adapter not found:",t);
1791 const __xResultAdapterCheck =
1792 (t)=>xResult.get(t) || toss("Result adapter not found:",t);
1795 Fetches the xWrap() argument adapter mapped to t, calls it,
1796 passing in all remaining arguments, and returns the result.
1797 Throws if t is not mapped to an argument converter.
1799 cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args);
1801 Identical to convertArg() except that it does not perform
1802 an is-defined check on the mapping to t before invoking it.
1804 cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args);
1807 Fetches the xWrap() result adapter mapped to t, calls it, passing
1808 it v, and returns the result. Throws if t is not mapped to an
1809 argument converter.
1811 cache.xWrap.convertResult =
1812 (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined));
1814 Identical to convertResult() except that it does not perform an
1815 is-defined check on the mapping to t before invoking it.
1817 cache.xWrap.convertResultNoCheck =
1818 (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined));
1821 Creates a wrapper for another function which converts the arguments
1822 of the wrapper to argument types accepted by the wrapped function,
1823 then converts the wrapped function's result to another form
1824 for the wrapper.
1826 The first argument must be one of:
1828 - A JavaScript function.
1829 - The name of a WASM-exported function. In the latter case xGet()
1830 is used to fetch the exported function, which throws if it's not
1831 found.
1832 - A pointer into the indirect function table. e.g. a pointer
1833 returned from target.installFunction().
1835 It returns either the passed-in function or a wrapper for that
1836 function which converts the JS-side argument types into WASM-side
1837 types and converts the result type.
1839 The second argument, `resultType`, describes the conversion for
1840 the wrapped functions result. A literal `null` or the string
1841 `'null'` both mean to return the original function's value as-is
1842 (mnemonic: there is "null" conversion going on). Literal
1843 `undefined` or the string `"void"` both mean to ignore the
1844 function's result and return `undefined`. Aside from those two
1845 special cases, the `resultType` value may be one of the values
1846 described below or any mapping installed by the client using
1847 xWrap.resultAdapter().
1849 If passed 3 arguments and the final one is an array, that array
1850 must contain a list of type names (see below) for adapting the
1851 arguments from JS to WASM. If passed 2 arguments, more than 3,
1852 or the 3rd is not an array, all arguments after the 2nd (if any)
1853 are treated as type names. i.e.:
1856 xWrap('funcname', 'i32', 'string', 'f64');
1857 // is equivalent to:
1858 xWrap('funcname', 'i32', ['string', 'f64']);
1861 This function enforces that the given list of arguments has the
1862 same arity as the being-wrapped function (as defined by its
1863 `length` property) and it will throw if that is not the case.
1864 Similarly, the created wrapper will throw if passed a differing
1865 argument count.
1867 Type names are symbolic names which map the arguments to an
1868 adapter function to convert, if needed, the value before passing
1869 it on to WASM or to convert a return result from WASM. The list
1870 of built-in names:
1872 - `i8`, `i16`, `i32` (args and results): all integer conversions
1873 which convert their argument to an integer and truncate it to
1874 the given bit length.
1876 - `N*` (args): a type name in the form `N*`, where N is a numeric
1877 type name, is treated the same as WASM pointer.
1879 - `*` and `pointer` (args): are assumed to be WASM pointer values
1880 and are returned coerced to an appropriately-sized pointer
1881 value (i32 or i64). Non-numeric values will coerce to 0 and
1882 out-of-range values will have undefined results (just as with
1883 any pointer misuse).
1885 - `*` and `pointer` (results): aliases for the current
1886 WASM pointer numeric type.
1888 - `**` (args): is simply a descriptive alias for the WASM pointer
1889 type. It's primarily intended to mark output-pointer arguments.
1891 - `i64` (args and results): passes the value to BigInt() to
1892 convert it to an int64. Only available if bigIntEnabled is
1893 true.
1895 - `f32` (`float`), `f64` (`double`) (args and results): pass
1896 their argument to Number(). i.e. the adapter does not currently
1897 distinguish between the two types of floating-point numbers.
1899 - `number` (results): converts the result to a JS Number using
1900 Number(theValue).valueOf(). Note that this is for result
1901 conversions only, as it's not possible to generically know
1902 which type of number to convert arguments to.
1904 Non-numeric conversions include:
1906 - `null` literal or `"null"` string (args and results): perform
1907 no translation and pass the arg on as-is. This is primarily
1908 useful for results but may have a use or two for arguments.
1910 - `string` or `utf8` (args): has two different semantics in order
1911 to accommodate various uses of certain C APIs
1912 (e.g. output-style strings)...
1914 - If the arg is a string, it creates a _temporary_
1915 UTF-8-encoded C-string to pass to the exported function,
1916 cleaning it up before the wrapper returns. If a long-lived
1917 C-string pointer is required, that requires client-side code
1918 to create the string, then pass its pointer to the function.
1920 - Else the arg is assumed to be a pointer to a string the
1921 client has already allocated and it's passed on as
1922 a WASM pointer.
1924 - `string` or `utf8` (results): treats the result value as a
1925 const C-string, encoded as UTF-8, copies it to a JS string,
1926 and returns that JS string.
1928 - `string:dealloc` or `utf8:dealloc` (results): treats the result
1929 value as a non-const UTF-8 C-string, ownership of which has
1930 just been transfered to the caller. It copies the C-string to a
1931 JS string, frees the C-string, and returns the JS string. If
1932 such a result value is NULL, the JS result is `null`. Achtung:
1933 when using an API which returns results from a specific
1934 allocator, e.g. `my_malloc()`, this conversion _is not
1935 legal_. Instead, an equivalent conversion which uses the
1936 appropriate deallocator is required. For example:
1938 ```js
1939 target.xWrap.resultAdapter('string:my_free',(i)=>{
1940 try { return i ? target.cstrToJs(i) : null }
1941 finally{ target.exports.my_free(i) }
1945 - `json` (results): treats the result as a const C-string and
1946 returns the result of passing the converted-to-JS string to
1947 JSON.parse(). Returns `null` if the C-string is a NULL pointer.
1949 - `json:dealloc` (results): works exactly like `string:dealloc` but
1950 returns the same thing as the `json` adapter. Note the
1951 warning in `string:dealloc` regarding maching allocators and
1952 deallocators.
1954 The type names for results and arguments are validated when
1955 xWrap() is called and any unknown names will trigger an
1956 exception.
1958 Clients may map their own result and argument adapters using
1959 xWrap.resultAdapter() and xWrap.argAdapter(), noting that not all
1960 type conversions are valid for both arguments _and_ result types
1961 as they often have different memory ownership requirements.
1963 Design note: the ability to pass in a JS function as the first
1964 argument is of relatively limited use, primarily for testing
1965 argument and result converters. JS functions, by and large, will
1966 not want to deal with C-type arguments.
1968 TODOs:
1970 - Figure out how/whether we can (semi-)transparently handle
1971 pointer-type _output_ arguments. Those currently require
1972 explicit handling by allocating pointers, assigning them before
1973 the call using poke(), and fetching them with
1974 peek() after the call. We may be able to automate some
1975 or all of that.
1977 - Figure out whether it makes sense to extend the arg adapter
1978 interface such that each arg adapter gets an array containing
1979 the results of the previous arguments in the current call. That
1980 might allow some interesting type-conversion feature. Use case:
1981 handling of the final argument to sqlite3_prepare_v2() depends
1982 on the type (pointer vs JS string) of its 2nd
1983 argument. Currently that distinction requires hand-writing a
1984 wrapper for that function. That case is unusual enough that
1985 abstracting it into this API (and taking on the associated
1986 costs) may well not make good sense.
1988 target.xWrap = function(fArg, resultType, ...argTypes){
1989 if(3===arguments.length && Array.isArray(arguments[2])){
1990 argTypes = arguments[2];
1992 if(target.isPtr(fArg)){
1993 fArg = target.functionEntry(fArg)
1994 || toss("Function pointer not found in WASM function table.");
1996 const fIsFunc = (fArg instanceof Function);
1997 const xf = fIsFunc ? fArg : target.xGet(fArg);
1998 if(fIsFunc) fArg = xf.name || 'unnamed function';
1999 if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length);
2000 if((null===resultType) && 0===xf.length){
2001 /* Func taking no args with an as-is return. We don't need a wrapper.
2002 We forego the argc check here, though. */
2003 return xf;
2005 /*Verify the arg type conversions are valid...*/;
2006 if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType);
2007 for(const t of argTypes){
2008 if(t instanceof AbstractArgAdapter) xArg.set(t, (...args)=>t.convertArg(...args));
2009 else __xArgAdapterCheck(t);
2011 const cxw = cache.xWrap;
2012 if(0===xf.length){
2013 // No args to convert, so we can create a simpler wrapper...
2014 return (...args)=>(args.length
2015 ? __argcMismatch(fArg, xf.length)
2016 : cxw.convertResult(resultType, xf.call(null)));
2018 return function(...args){
2019 if(args.length!==xf.length) __argcMismatch(fArg, xf.length);
2020 const scope = target.scopedAllocPush();
2021 try{
2023 Maintenance reminder re. arguments passed to convertArg():
2024 The public interface of argument adapters is that they take
2025 ONE argument and return a (possibly) converted result for
2026 it. The passing-on of arguments after the first is an
2027 internal implementation detail for the sake of
2028 AbstractArgAdapter, and not to be relied on or documented
2029 for other cases. The fact that this is how
2030 AbstractArgAdapter.convertArgs() gets its 2nd+ arguments,
2031 and how FuncPtrAdapter.contextKey() gets its args, is also
2032 an implementation detail and subject to change. i.e. the
2033 public interface of 1 argument is stable. The fact that any
2034 arguments may be passed in after that one, and what those
2035 arguments are, is _not_ part of the public interface and is
2036 _not_ stable.
2038 Maintenance reminder: the Ember framework modifies the core
2039 Array type, breaking for-in loops.
2041 let i = 0;
2042 for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck(
2043 argTypes[i], args[i], args, i
2045 return cxw.convertResultNoCheck(resultType, xf.apply(null,args));
2046 }finally{
2047 target.scopedAllocPop(scope);
2050 }/*xWrap()*/;
2052 /** Internal impl for xWrap.resultAdapter() and argAdapter(). */
2053 const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){
2054 if('string'===typeof typeName){
2055 if(1===argc) return xcvPart.get(typeName);
2056 else if(2===argc){
2057 if(!adapter){
2058 delete xcvPart.get(typeName);
2059 return func;
2060 }else if(!(adapter instanceof Function)){
2061 toss(modeName,"requires a function argument.");
2063 xcvPart.set(typeName, adapter);
2064 return func;
2067 toss("Invalid arguments to",modeName);
2071 Gets, sets, or removes a result value adapter for use with
2072 xWrap(). If passed only 1 argument, the adapter function for the
2073 given type name is returned. If the second argument is explicit
2074 falsy (as opposed to defaulted), the adapter named by the first
2075 argument is removed. If the 2nd argument is not falsy, it must be
2076 a function which takes one value and returns a value appropriate
2077 for the given type name. The adapter may throw if its argument is
2078 not of a type it can work with. This function throws for invalid
2079 arguments.
2081 Example:
2084 xWrap.resultAdapter('twice',(v)=>v+v);
2087 xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of
2088 APIs to allocate a result value. xWrap()-generated wrappers run
2089 in the context of scopedAllocPush() so that argument adapters can
2090 easily convert, e.g., to C-strings, and have them cleaned up
2091 automatically before the wrapper returns to the caller. Likewise,
2092 if a _result_ adapter uses scoped allocation, the result will be
2093 freed before because they would be freed before the wrapper
2094 returns, leading to chaos and undefined behavior.
2096 Except when called as a getter, this function returns itself.
2098 target.xWrap.resultAdapter = function f(typeName, adapter){
2099 return __xAdapter(f, arguments.length, typeName, adapter,
2100 'resultAdapter()', xResult);
2104 Functions identically to xWrap.resultAdapter() but applies to
2105 call argument conversions instead of result value conversions.
2107 xWrap()-generated wrappers perform argument conversion in the
2108 context of a scopedAllocPush(), so any memory allocation
2109 performed by argument adapters really, really, really should be
2110 made using the scopedAlloc() family of functions unless
2111 specifically necessary. For example:
2114 xWrap.argAdapter('my-string', function(v){
2115 return ('string'===typeof v)
2116 ? myWasmObj.scopedAllocCString(v) : null;
2120 Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc()
2121 to allocate its results because they would be freed before the
2122 xWrap()-created wrapper returns.
2124 Note that it is perfectly legitimate to use these adapters to
2125 perform argument validation, as opposed (or in addition) to
2126 conversion.
2128 target.xWrap.argAdapter = function f(typeName, adapter){
2129 return __xAdapter(f, arguments.length, typeName, adapter,
2130 'argAdapter()', xArg);
2133 target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter;
2136 Functions like xCall() but performs argument and result type
2137 conversions as for xWrap(). The first, second, and third
2138 arguments are as documented for xWrap(), except that the 3rd
2139 argument may be either a falsy value or empty array to represent
2140 nullary functions. The 4th+ arguments are arguments for the call,
2141 with the special case that if the 4th argument is an array, it is
2142 used as the arguments for the call. Returns the converted result
2143 of the call.
2145 This is just a thin wrapper around xWrap(). If the given function
2146 is to be called more than once, it's more efficient to use
2147 xWrap() to create a wrapper, then to call that wrapper as many
2148 times as needed. For one-shot calls, however, this variant is
2149 arguably more efficient because it will hypothetically free the
2150 wrapper function quickly.
2152 target.xCallWrapped = function(fArg, resultType, argTypes, ...args){
2153 if(Array.isArray(arguments[3])) args = arguments[3];
2154 return target.xWrap(fArg, resultType, argTypes||[]).apply(null, args||[]);
2158 This function is ONLY exposed in the public API to facilitate
2159 testing. It should not be used in application-level code, only
2160 in test code.
2162 Expects to be given (typeName, value) and returns a conversion
2163 of that value as has been registered using argAdapter().
2164 It throws if no adapter is found.
2166 ACHTUNG: the adapter may require that a scopedAllocPush() is
2167 active and it may allocate memory within that scope. It may also
2168 require additional arguments, depending on the type of
2169 conversion.
2171 target.xWrap.testConvertArg = cache.xWrap.convertArg;
2174 This function is ONLY exposed in the public API to facilitate
2175 testing. It should not be used in application-level code, only
2176 in test code.
2178 Expects to be given (typeName, value) and returns a conversion
2179 of that value as has been registered using resultAdapter().
2180 It throws if no adapter is found.
2182 ACHTUNG: the adapter may allocate memory which the caller may need
2183 to know how to free.
2185 target.xWrap.testConvertResult = cache.xWrap.convertResult;
2187 return target;
2191 yawl (Yet Another Wasm Loader) provides very basic wasm loader.
2192 It requires a config object:
2194 - `uri`: required URI of the WASM file to load.
2196 - `onload(loadResult,config)`: optional callback. The first
2197 argument is the result object from
2198 WebAssembly.instantiate[Streaming](). The 2nd is the config
2199 object passed to this function. Described in more detail below.
2201 - `imports`: optional imports object for
2202 WebAssembly.instantiate[Streaming](). The default is an empty set
2203 of imports. If the module requires any imports, this object
2204 must include them.
2206 - `wasmUtilTarget`: optional object suitable for passing to
2207 WhWasmUtilInstaller(). If set, it gets passed to that function
2208 after the promise resolves. This function sets several properties
2209 on it before passing it on to that function (which sets many
2210 more):
2212 - `module`, `instance`: the properties from the
2213 instantiate[Streaming]() result.
2215 - If `instance.exports.memory` is _not_ set then it requires that
2216 `config.imports.env.memory` be set (else it throws), and
2217 assigns that to `target.memory`.
2219 - If `wasmUtilTarget.alloc` is not set and
2220 `instance.exports.malloc` is, it installs
2221 `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()`
2222 wrappers for the exports `malloc` and `free` functions.
2224 It returns a function which, when called, initiates loading of the
2225 module and returns a Promise. When that Promise resolves, it calls
2226 the `config.onload` callback (if set) and passes it
2227 `(loadResult,config)`, where `loadResult` is the result of
2228 WebAssembly.instantiate[Streaming](): an object in the form:
2232 module: a WebAssembly.Module,
2233 instance: a WebAssembly.Instance
2237 (Note that the initial `then()` attached to the promise gets only
2238 that object, and not the `config` one.)
2240 Error handling is up to the caller, who may attach a `catch()` call
2241 to the promise.
2243 globalThis.WhWasmUtilInstaller.yawl = function(config){
2244 const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'});
2245 const wui = this;
2246 const finalThen = function(arg){
2247 //log("finalThen()",arg);
2248 if(config.wasmUtilTarget){
2249 const toss = (...args)=>{throw new Error(args.join(' '))};
2250 const tgt = config.wasmUtilTarget;
2251 tgt.module = arg.module;
2252 tgt.instance = arg.instance;
2253 //tgt.exports = tgt.instance.exports;
2254 if(!tgt.instance.exports.memory){
2256 WhWasmUtilInstaller requires either tgt.exports.memory
2257 (exported from WASM) or tgt.memory (JS-provided memory
2258 imported into WASM).
2260 tgt.memory = (config.imports && config.imports.env
2261 && config.imports.env.memory)
2262 || toss("Missing 'memory' object!");
2264 if(!tgt.alloc && arg.instance.exports.malloc){
2265 const exports = arg.instance.exports;
2266 tgt.alloc = function(n){
2267 return exports.malloc(n) || toss("Allocation of",n,"bytes failed.");
2269 tgt.dealloc = function(m){exports.free(m)};
2271 wui(tgt);
2273 if(config.onload) config.onload(arg,config);
2274 return arg /* for any then() handler attached to
2275 yetAnotherWasmLoader()'s return value */;
2277 const loadWasm = WebAssembly.instantiateStreaming
2278 ? function loadWasmStreaming(){
2279 return WebAssembly.instantiateStreaming(wfetch(), config.imports||{})
2280 .then(finalThen);
2282 : function loadWasmOldSchool(){ // Safari < v15
2283 return wfetch()
2284 .then(response => response.arrayBuffer())
2285 .then(bytes => WebAssembly.instantiate(bytes, config.imports||{}))
2286 .then(finalThen);
2288 return loadWasm;
2289 }.bind(globalThis.WhWasmUtilInstaller)/*yawl()*/;