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
16 https://fossil.wanderinghorse.net/r/jaccwabyt
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.
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.
48 globalThis.WhWasmUtilInstaller(appObject);
49 delete globalThis.WhWasmUtilInstaller;
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
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.
101 Passing an object to this function will install the functionality
102 into that object. Afterwards, client code "should" delete the global
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']`. The exports object must contain a minimum of the
117 - `memory`: a WebAssembly.Memory object representing the WASM
118 memory. _Alternately_, the `memory` property can be set as
119 `target.memory`, in particular if the WASM heap memory is
120 initialized in JS an _imported_ into WASM, as opposed to being
121 initialized in WASM and exported to JS.
123 - `__indirect_function_table`: the WebAssembly.Table object which
124 holds WASM-exported functions. This API does not strictly
125 require that the table be able to grow but it will throw if its
126 `installFunction()` is called and the table cannot grow.
128 In order to simplify downstream usage, if `target.exports` is not
129 set when this is called then a property access interceptor
130 (read-only, configurable, enumerable) gets installed as `exports`
131 which resolves to `target.instance.exports`, noting that the latter
132 property need not exist until the first time `target.exports` is
135 Some APIs _optionally_ make use of the `bigIntEnabled` property of
136 the target object. It "should" be set to true if the WASM
137 environment is compiled with BigInt support, else it must be
138 false. If it is false, certain BigInt-related features will trigger
139 an exception if invoked. This property, if not set when this is
140 called, will get a default value of true only if the BigInt64Array
141 constructor is available, else it will default to false. Note that
142 having the BigInt type is not sufficient for full int64 integration
143 with WASM: the target WASM file must also have been built with
144 that support. In Emscripten that's done using the `-sWASM_BIGINT`
147 Some optional APIs require that the target have the following
150 - 'alloc()` must behave like C's `malloc()`, allocating N bytes of
151 memory and returning its pointer. In Emscripten this is
152 conventionally made available via `Module['_malloc']`. This API
153 requires that the alloc routine throw on allocation error, as
154 opposed to returning null or 0.
156 - 'dealloc()` must behave like C's `free()`, accepting either a
157 pointer returned from its allocation counterpart or the values
158 null/0 (for which it must be a no-op). allocating N bytes of
159 memory and returning its pointer. In Emscripten this is
160 conventionally made available via `Module['_free']`.
162 APIs which require allocation routines are explicitly documented as
163 such and/or have "alloc" in their names.
165 This code is developed and maintained in conjunction with the
168 https://fossil.wanderinghorse.net/r/jaccwabbyt
172 https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js
174 globalThis
.WhWasmUtilInstaller = function(target
){
176 if(undefined===target
.bigIntEnabled
){
177 target
.bigIntEnabled
= !!self
['BigInt64Array'];
180 /** Throws a new Error, the message of which is the concatenation of
181 all args with a space between each. */
182 const toss
= (...args
)=>{throw new Error(args
.join(' '))};
185 Object
.defineProperty(target
, 'exports', {
186 enumerable
: true, configurable
: true,
187 get: ()=>(target
.instance
&& target
.instance
.exports
)
192 alloc()/dealloc() auto-install...
194 This would be convenient but it can also cause us to pick up
195 malloc() even when the client code is using a different exported
196 allocator (who, me?), which is bad. malloc() may be exported even
197 if we're not explicitly using it and overriding the malloc()
198 function, linking ours first, is not always feasible when using a
199 malloc() proxy, as it can lead to recursion and stack overflow
200 (who, me?). So... we really need the downstream code to set up
201 target.alloc/dealloc() itself.
205 //Maybe auto-install alloc()/dealloc()...
206 if(!target.alloc && target.exports.malloc){
207 target.alloc = function(n){
209 return m || toss("Allocation of",n,"byte(s) failed.");
210 }.bind(target.exports.malloc);
213 if(!target.dealloc && target.exports.free){
214 target.dealloc = function(ptr){
216 }.bind(target.exports.free);
221 Pointers in WASM are currently assumed to be 32-bit, but someday
222 that will certainly change.
224 const ptrIR
= target
.pointerIR
|| 'i32';
225 const ptrSizeof
= target
.ptrSizeof
=
228 ? 8 : toss("Unhandled ptrSizeof:",ptrIR
)));
229 /** Stores various cached state. */
230 const cache
= Object
.create(null);
231 /** Previously-recorded size of cache.memory.buffer, noted so that
232 we can recreate the view objects if the heap grows. */
234 /** WebAssembly.Memory object extracted from target.memory or
235 target.exports.memory the first time heapWrappers() is
238 /** uninstallFunction() puts table indexes in here for reuse and
239 installFunction() extracts them. */
240 cache
.freeFuncIndexes
= [];
242 Used by scopedAlloc() and friends.
244 cache
.scopedAlloc
= [];
246 cache
.utf8Decoder
= new TextDecoder();
247 cache
.utf8Encoder
= new TextEncoder('utf-8');
250 For the given IR-like string in the set ('i8', 'i16', 'i32',
251 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value
252 ending in '*', returns the sizeof for that value
253 (target.ptrSizeof in the latter case). For any other value, it
254 returns the undefined value.
256 target
.sizeofIR
= (n
)=>{
259 case 'i16': return 2;
260 case 'i32': case 'f32': case 'float': return 4;
261 case 'i64': case 'f64': case 'double': return 8;
262 case '*': return ptrSizeof
;
264 return (''+n
).endsWith('*') ? ptrSizeof
: undefined;
269 If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
270 the heap has grown since the last call, updates cache.HEAPxyz.
271 Returns the cache object.
273 const heapWrappers = function(){
275 cache
.memory
= (target
.memory
instanceof WebAssembly
.Memory
)
276 ? target
.memory
: target
.exports
.memory
;
277 }else if(cache
.heapSize
=== cache
.memory
.buffer
.byteLength
){
280 // heap is newly-acquired or has been resized....
281 const b
= cache
.memory
.buffer
;
282 cache
.HEAP8
= new Int8Array(b
); cache
.HEAP8U
= new Uint8Array(b
);
283 cache
.HEAP16
= new Int16Array(b
); cache
.HEAP16U
= new Uint16Array(b
);
284 cache
.HEAP32
= new Int32Array(b
); cache
.HEAP32U
= new Uint32Array(b
);
285 if(target
.bigIntEnabled
){
286 cache
.HEAP64
= new BigInt64Array(b
); cache
.HEAP64U
= new BigUint64Array(b
);
288 cache
.HEAP32F
= new Float32Array(b
); cache
.HEAP64F
= new Float64Array(b
);
289 cache
.heapSize
= b
.byteLength
;
293 /** Convenience equivalent of this.heapForSize(8,false). */
294 target
.heap8
= ()=>heapWrappers().HEAP8
;
296 /** Convenience equivalent of this.heapForSize(8,true). */
297 target
.heap8u
= ()=>heapWrappers().HEAP8U
;
299 /** Convenience equivalent of this.heapForSize(16,false). */
300 target
.heap16
= ()=>heapWrappers().HEAP16
;
302 /** Convenience equivalent of this.heapForSize(16,true). */
303 target
.heap16u
= ()=>heapWrappers().HEAP16U
;
305 /** Convenience equivalent of this.heapForSize(32,false). */
306 target
.heap32
= ()=>heapWrappers().HEAP32
;
308 /** Convenience equivalent of this.heapForSize(32,true). */
309 target
.heap32u
= ()=>heapWrappers().HEAP32U
;
312 Requires n to be one of:
314 - integer 8, 16, or 32.
315 - A integer-type TypedArray constructor: Int8Array, Int16Array,
316 Int32Array, or their Uint counterparts.
318 If this.bigIntEnabled is true, it also accepts the value 64 or a
319 BigInt64Array/BigUint64Array, else it throws if passed 64 or one
320 of those constructors.
322 Returns an integer-based TypedArray view of the WASM heap
323 memory buffer associated with the given block size. If passed
324 an integer as the first argument and unsigned is truthy then
325 the "U" (unsigned) variant of that view is returned, else the
326 signed variant is returned. If passed a TypedArray value, the
327 2nd argument is ignored. Note that Float32Array and
328 Float64Array views are not supported by this function.
330 Note that growth of the heap will invalidate any references to
331 this heap, so do not hold a reference longer than needed and do
332 not use a reference after any operation which may
333 allocate. Instead, re-fetch the reference by calling this
336 Throws if passed an invalid n.
338 Pedantic side note: the name "heap" is a bit of a misnomer. In a
339 WASM environment, the stack and heap memory are all accessed via
340 the same view(s) of the memory.
342 target
.heapForSize = function(n
,unsigned
= true){
344 const c
= (cache
.memory
&& cache
.heapSize
=== cache
.memory
.buffer
.byteLength
)
345 ? cache
: heapWrappers();
347 case Int8Array
: return c
.HEAP8
; case Uint8Array
: return c
.HEAP8U
;
348 case Int16Array
: return c
.HEAP16
; case Uint16Array
: return c
.HEAP16U
;
349 case Int32Array
: return c
.HEAP32
; case Uint32Array
: return c
.HEAP32U
;
350 case 8: return unsigned
? c
.HEAP8U
: c
.HEAP8
;
351 case 16: return unsigned
? c
.HEAP16U
: c
.HEAP16
;
352 case 32: return unsigned
? c
.HEAP32U
: c
.HEAP32
;
354 if(c
.HEAP64
) return unsigned
? c
.HEAP64U
: c
.HEAP64
;
357 if(target
.bigIntEnabled
){
358 if(n
===self
['BigUint64Array']) return c
.HEAP64U
;
359 else if(n
===self
['BigInt64Array']) return c
.HEAP64
;
363 toss("Invalid heapForSize() size: expecting 8, 16, 32,",
364 "or (if BigInt is enabled) 64.");
368 Returns the WASM-exported "indirect function table."
370 target
.functionTable = function(){
371 return target
.exports
.__indirect_function_table
;
372 /** -----------------^^^^^ "seems" to be a standardized export name.
373 From Emscripten release notes from 2020-09-10:
374 - Use `__indirect_function_table` as the import name for the
375 table, which is what LLVM does.
380 Given a function pointer, returns the WASM function table entry
381 if found, else returns a falsy value: undefined if fptr is out of
382 range or null if it's in range but the table entry is empty.
384 target
.functionEntry = function(fptr
){
385 const ft
= target
.functionTable();
386 return fptr
< ft
.length
? ft
.get(fptr
) : undefined;
390 Creates a WASM function which wraps the given JS function and
391 returns the JS binding of that WASM function. The signature
392 string must be the Jaccwabyt-format or Emscripten
393 addFunction()-format function signature string. In short: in may
394 have one of the following formats:
396 - Emscripten: `"x..."`, where the first x is a letter representing
397 the result type and subsequent letters represent the argument
398 types. Functions with no arguments have only a single
401 - Jaccwabyt: `"x(...)"` where `x` is the letter representing the
402 result type and letters in the parens (if any) represent the
403 argument types. Functions with no arguments use `x()`. See
409 - `p` = int32 ("pointer")
413 - `v` = void, only legal for use as the result type
415 It throws if an invalid signature letter is used.
417 Jaccwabyt-format signatures support some additional letters which
418 have no special meaning here but (in this context) act as aliases
421 - `s`, `P`: same as `p`
423 Sidebar: this code is developed together with Jaccwabyt, thus the
424 support for its signature format.
426 The arguments may be supplied in either order: (func,sig) or
429 target
.jsFuncToWasm
= function f(func
, sig
){
430 /** Attribution: adapted up from Emscripten-generated glue code,
431 refactored primarily for efficiency's sake, eliminating
432 call-local functions and superfluous temporary arrays. */
433 if(!f
._
){/*static init...*/
435 // Map of signature letters to type IR values
436 sigTypes
: Object
.assign(Object
.create(null),{
437 i
: 'i32', p
: 'i32', P
: 'i32', s
: 'i32',
438 j
: 'i64', f
: 'f32', d
: 'f64'
440 // Map of type IR values to WASM type code values
441 typeCodes
: Object
.assign(Object
.create(null),{
442 f64
: 0x7c, f32
: 0x7d, i64
: 0x7e, i32
: 0x7f
444 /** Encodes n, which must be <2^14 (16384), into target array
445 tgt, as a little-endian value, using the given method
446 ('push' or 'unshift'). */
447 uleb128Encode: function(tgt
, method
, n
){
448 if(n
<128) tgt
[method
](n
);
449 else tgt
[method
]( (n
% 128) | 128, n
>>7);
451 /** Intentionally-lax pattern for Jaccwabyt-format function
452 pointer signatures, the intent of which is simply to
453 distinguish them from Emscripten-format signatures. The
454 downstream checks are less lax. */
455 rxJSig
: /^(\w)\((\w*)\)$/,
456 /** Returns the parameter-value part of the given signature
458 sigParams: function(sig
){
459 const m
= f
._
.rxJSig
.exec(sig
);
460 return m
? m
[2] : sig
.substr(1);
462 /** Returns the IR value for the given letter or throws
463 if the letter is invalid. */
464 letterType
: (x
)=>f
._
.sigTypes
[x
] || toss("Invalid signature letter:",x
),
465 /** Returns an object describing the result type and parameter
466 type(s) of the given function signature, or throws if the
467 signature is invalid. */
468 /******** // only valid for use with the WebAssembly.Function ctor, which
469 // is not yet documented on MDN.
470 sigToWasm: function(sig){
471 const rc = {parameters:[], results: []};
472 if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0]));
473 for(const x of f._.sigParams(sig)){
474 rc.parameters.push(f._.typeCodes(x));
478 /** Pushes the WASM data type code for the given signature
479 letter to the given target array. Throws if letter is
481 pushSigType
: (dest
, letter
)=>dest
.push(f
._
.typeCodes
[f
._
.letterType(letter
)])
484 if('string'===typeof func
){
489 const sigParams
= f
._
.sigParams(sig
);
490 const wasmCode
= [0x01/*count: 1*/, 0x60/*function*/];
491 f
._
.uleb128Encode(wasmCode
, 'push', sigParams
.length
);
492 for(const x
of sigParams
) f
._
.pushSigType(wasmCode
, x
);
493 if('v'===sig
[0]) wasmCode
.push(0);
496 f
._
.pushSigType(wasmCode
, sig
[0]);
498 f
._
.uleb128Encode(wasmCode
, 'unshift', wasmCode
.length
)/* type section length */;
500 0x00, 0x61, 0x73, 0x6d, /* magic: "\0asm" */
501 0x01, 0x00, 0x00, 0x00, /* version: 1 */
502 0x01 /* type section code */
505 /* import section: */ 0x02, 0x07,
506 /* (import "e" "f" (func 0 (type 0))): */
507 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
508 /* export section: */ 0x07, 0x05,
509 /* (export "f" (func 0 (type 0))): */
510 0x01, 0x01, 0x66, 0x00, 0x00
512 return (new WebAssembly
.Instance(
513 new WebAssembly
.Module(new Uint8Array(wasmCode
)), {
519 Documented as target.installFunction() except for the 3rd
520 argument: if truthy, the newly-created function pointer
521 is stashed in the current scoped-alloc scope and will be
522 cleaned up at the matching scopedAllocPop(), else it
523 is not stashed there.
525 const __installFunction
= function f(func
, sig
, scoped
){
526 if(scoped
&& !cache
.scopedAlloc
.length
){
527 toss("No scopedAllocPush() scope is active.");
529 if('string'===typeof func
){
534 if('string'!==typeof sig
|| !(func
instanceof Function
)){
535 toss("Invalid arguments: expecting (function,signature) "+
536 "or (signature,function).");
538 const ft
= target
.functionTable();
539 const oldLen
= ft
.length
;
541 while(cache
.freeFuncIndexes
.length
){
542 ptr
= cache
.freeFuncIndexes
.pop();
543 if(ft
.get(ptr
)){ /* Table was modified via a different API */
555 /*this will only work if func is a WASM-exported function*/
558 cache
.scopedAlloc
[cache
.scopedAlloc
.length
-1].push(ptr
);
562 if(!(e
instanceof TypeError
)){
563 if(ptr
===oldLen
) cache
.freeFuncIndexes
.push(oldLen
);
567 // It's not a WASM-exported function, so compile one...
569 const fptr
= target
.jsFuncToWasm(func
, sig
);
572 cache
.scopedAlloc
[cache
.scopedAlloc
.length
-1].push(ptr
);
575 if(ptr
===oldLen
) cache
.freeFuncIndexes
.push(oldLen
);
582 Expects a JS function and signature, exactly as for
583 this.jsFuncToWasm(). It uses that function to create a
584 WASM-exported function, installs that function to the next
585 available slot of this.functionTable(), and returns the
586 function's index in that table (which acts as a pointer to that
587 function). The returned pointer can be passed to
588 uninstallFunction() to uninstall it and free up the table slot for
591 If passed (string,function) arguments then it treats the first
592 argument as the signature and second as the function.
594 As a special case, if the passed-in function is a WASM-exported
595 function then the signature argument is ignored and func is
596 installed as-is, without requiring re-compilation/re-wrapping.
598 This function will propagate an exception if
599 WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
600 The former case can happen in an Emscripten-compiled
601 environment when building without Emscripten's
602 `-sALLOW_TABLE_GROWTH` flag.
604 Sidebar: this function differs from Emscripten's addFunction()
605 _primarily_ in that it does not share that function's
606 undocumented behavior of reusing a function if it's passed to
607 addFunction() more than once, which leads to uninstallFunction()
608 breaking clients which do not take care to avoid that case:
610 https://github.com/emscripten-core/emscripten/issues/17323
612 target
.installFunction
= (func
, sig
)=>__installFunction(func
, sig
, false);
615 EXPERIMENTAL! DO NOT USE IN CLIENT CODE!
617 Works exactly like installFunction() but requires that a
618 scopedAllocPush() is active and uninstalls the given function
619 when that alloc scope is popped via scopedAllocPop().
620 This is used for implementing JS/WASM function bindings which
621 should only persist for the life of a call into a single
624 target
.scopedInstallFunction
= (func
, sig
)=>__installFunction(func
, sig
, true);
627 Requires a pointer value previously returned from
628 this.installFunction(). Removes that function from the WASM
629 function table, marks its table slot as free for re-use, and
630 returns that function. It is illegal to call this before
631 installFunction() has been called and results are undefined if
632 ptr was not returned by that function. The returned function
633 may be passed back to installFunction() to reinstall it.
635 To simplify certain use cases, if passed a falsy non-0 value
636 (noting that 0 is a valid function table index), this function
637 has no side effects and returns undefined.
639 target
.uninstallFunction = function(ptr
){
640 if(!ptr
&& 0!==ptr
) return undefined;
641 const fi
= cache
.freeFuncIndexes
;
642 const ft
= target
.functionTable();
644 const rc
= ft
.get(ptr
);
650 Given a WASM heap memory address and a data type name in the form
651 (i8, i16, i32, i64, float (or f32), double (or f64)), this
652 fetches the numeric value from that address and returns it as a
653 number or, for the case of type='i64', a BigInt (noting that that
654 type triggers an exception if this.bigIntEnabled is
655 falsy). Throws if given an invalid type.
657 If the first argument is an array, it is treated as an array of
658 addresses and the result is an array of the values from each of
659 those address, using the same 2nd argument for determining the
662 As a special case, if type ends with a `*`, it is considered to
663 be a pointer type and is treated as the WASM numeric type
664 appropriate for the pointer size (`i32`).
666 While likely not obvious, this routine and its poke()
667 counterpart are how pointer-to-value _output_ parameters
668 in WASM-compiled C code can be interacted with:
671 const ptr = alloc(4);
672 poke(ptr, 0, 'i32'); // clear the ptr's value
673 aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x);
674 const result = peek(ptr, 'i32'); // fetch ptr's value
678 scopedAlloc() and friends can be used to make handling of
679 `ptr` safe against leaks in the case of an exception:
683 const scope = scopedAllocPush();
685 const ptr = scopedAlloc(4);
687 aCFuncWithOutputPtrArg( ptr );
688 result = peek(ptr, 'i32');
690 scopedAllocPop(scope);
694 As a rule poke() must be called to set (typically zero
695 out) the pointer's value, else it will contain an essentially
698 ACHTUNG: calling this often, e.g. in a loop, can have a noticably
699 painful impact on performance. Rather than doing so, use
700 heapForSize() to fetch the heap object and read directly from it.
704 target
.peek
= function f(ptr
, type
='i8'){
705 if(type
.endsWith('*')) type
= ptrIR
;
706 const c
= (cache
.memory
&& cache
.heapSize
=== cache
.memory
.buffer
.byteLength
)
707 ? cache
: heapWrappers();
708 const list
= Array
.isArray(ptr
) ? [] : undefined;
711 if(list
) ptr
= arguments
[0].shift();
714 case 'i8': rc
= c
.HEAP8
[ptr
>>0]; break;
715 case 'i16': rc
= c
.HEAP16
[ptr
>>1]; break;
716 case 'i32': rc
= c
.HEAP32
[ptr
>>2]; break;
717 case 'float': case 'f32': rc
= c
.HEAP32F
[ptr
>>2]; break;
718 case 'double': case 'f64': rc
= Number(c
.HEAP64F
[ptr
>>3]); break;
720 if(target
.bigIntEnabled
){
721 rc
= BigInt(c
.HEAP64
[ptr
>>3]);
726 toss('Invalid type for peek():',type
);
728 if(list
) list
.push(rc
);
729 }while(list
&& arguments
[0].length
);
734 The counterpart of peek(), this sets a numeric value at
735 the given WASM heap address, using the type to define how many
736 bytes are written. Throws if given an invalid type. See
737 peek() for details about the type argument. If the 3rd
738 argument ends with `*` then it is treated as a pointer type and
739 this function behaves as if the 3rd argument were `i32`.
741 If the first argument is an array, it is treated like a list
742 of pointers and the given value is written to each one.
744 Returns `this`. (Prior to 2022-12-09 it returns this function.)
746 ACHTUNG: calling this often, e.g. in a loop, can have a noticably
747 painful impact on performance. Rather than doing so, use
748 heapForSize() to fetch the heap object and assign directly to it
749 or use the heap's set() method.
751 target
.poke = function(ptr
, value
, type
='i8'){
752 if (type
.endsWith('*')) type
= ptrIR
;
753 const c
= (cache
.memory
&& cache
.heapSize
=== cache
.memory
.buffer
.byteLength
)
754 ? cache
: heapWrappers();
755 for(const p
of (Array
.isArray(ptr
) ? ptr
: [ptr
])){
758 case 'i8': c
.HEAP8
[p
>>0] = value
; continue;
759 case 'i16': c
.HEAP16
[p
>>1] = value
; continue;
760 case 'i32': c
.HEAP32
[p
>>2] = value
; continue;
761 case 'float': case 'f32': c
.HEAP32F
[p
>>2] = value
; continue;
762 case 'double': case 'f64': c
.HEAP64F
[p
>>3] = value
; continue;
765 c
.HEAP64
[p
>>3] = BigInt(value
);
770 toss('Invalid type for poke(): ' + type
);
777 Convenience form of peek() intended for fetching
778 pointer-to-pointer values. If passed a single non-array argument
779 it returns the value of that one pointer address. If passed
780 multiple arguments, or a single array of arguments, it returns an
781 array of their values.
783 target
.peekPtr
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), ptrIR
);
786 A variant of poke() intended for setting pointer-to-pointer
787 values. Its differences from poke() are that (1) it defaults to a
788 value of 0 and (2) it always writes to the pointer-sized heap
791 target
.pokePtr
= (ptr
, value
=0)=>target
.poke(ptr
, value
, ptrIR
);
794 Convenience form of peek() intended for fetching i8 values. If
795 passed a single non-array argument it returns the value of that
796 one pointer address. If passed multiple arguments, or a single
797 array of arguments, it returns an array of their values.
799 target
.peek8
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'i8' );
801 Convience form of poke() intended for setting individual bytes.
802 Its difference from poke() is that it always writes to the
805 target
.poke8
= (ptr
, value
)=>target
.poke(ptr
, value
, 'i8');
806 /** i16 variant of peek8(). */
807 target
.peek16
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'i16' );
808 /** i16 variant of poke8(). */
809 target
.poke16
= (ptr
, value
)=>target
.poke(ptr
, value
, 'i16');
810 /** i32 variant of peek8(). */
811 target
.peek32
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'i32' );
812 /** i32 variant of poke8(). */
813 target
.poke32
= (ptr
, value
)=>target
.poke(ptr
, value
, 'i32');
814 /** i64 variant of peek8(). Will throw if this build is not
815 configured for BigInt support. */
816 target
.peek64
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'i64' );
817 /** i64 variant of poke8(). Will throw if this build is not
818 configured for BigInt support. Note that this returns
819 a BigInt-type value, not a Number-type value. */
820 target
.poke64
= (ptr
, value
)=>target
.poke(ptr
, value
, 'i64');
821 /** f32 variant of peek8(). */
822 target
.peek32f
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'f32' );
823 /** f32 variant of poke8(). */
824 target
.poke32f
= (ptr
, value
)=>target
.poke(ptr
, value
, 'f32');
825 /** f64 variant of peek8(). */
826 target
.peek64f
= (...ptr
)=>target
.peek( (1===ptr
.length
? ptr
[0] : ptr
), 'f64' );
827 /** f64 variant of poke8(). */
828 target
.poke64f
= (ptr
, value
)=>target
.poke(ptr
, value
, 'f64');
830 /** Deprecated alias for getMemValue() */
831 target
.getMemValue
= target
.peek
;
832 /** Deprecated alias for peekPtr() */
833 target
.getPtrValue
= target
.peekPtr
;
834 /** Deprecated alias for poke() */
835 target
.setMemValue
= target
.poke
;
836 /** Deprecated alias for pokePtr() */
837 target
.setPtrValue
= target
.pokePtr
;
840 Returns true if the given value appears to be legal for use as
841 a WASM pointer value. Its _range_ of values is not (cannot be)
842 validated except to ensure that it is a 32-bit integer with a
843 value of 0 or greater. Likewise, it cannot verify whether the
844 value actually refers to allocated memory in the WASM heap.
846 target
.isPtr32
= (ptr
)=>('number'===typeof ptr
&& (ptr
===(ptr
|0)) && ptr
>=0);
849 isPtr() is an alias for isPtr32(). If/when 64-bit WASM pointer
850 support becomes widespread, it will become an alias for either
851 isPtr32() or the as-yet-hypothetical isPtr64(), depending on a
852 configuration option.
854 target
.isPtr
= target
.isPtr32
;
857 Expects ptr to be a pointer into the WASM heap memory which
858 refers to a NUL-terminated C-style string encoded as UTF-8.
859 Returns the length, in bytes, of the string, as for `strlen(3)`.
860 As a special case, if !ptr or if it's not a pointer then it
861 returns `null`. Throws if ptr is out of range for
864 target
.cstrlen = function(ptr
){
865 if(!ptr
|| !target
.isPtr(ptr
)) return null;
866 const h
= heapWrappers().HEAP8U
;
868 for( ; h
[pos
] !== 0; ++pos
){}
872 /** Internal helper to use in operations which need to distinguish
873 between SharedArrayBuffer heap memory and non-shared heap. */
874 const __SAB
= ('undefined'===typeof SharedArrayBuffer
)
875 ? function(){} : SharedArrayBuffer
;
876 const __utf8Decode = function(arrayBuffer
, begin
, end
){
877 return cache
.utf8Decoder
.decode(
878 (arrayBuffer
.buffer
instanceof __SAB
)
879 ? arrayBuffer
.slice(begin
, end
)
880 : arrayBuffer
.subarray(begin
, end
)
885 Expects ptr to be a pointer into the WASM heap memory which
886 refers to a NUL-terminated C-style string encoded as UTF-8. This
887 function counts its byte length using cstrlen() then returns a
888 JS-format string representing its contents. As a special case, if
889 ptr is falsy or not a pointer, `null` is returned.
891 target
.cstrToJs = function(ptr
){
892 const n
= target
.cstrlen(ptr
);
893 return n
? __utf8Decode(heapWrappers().HEAP8U
, ptr
, ptr
+n
) : (null===n
? n
: "");
897 Given a JS string, this function returns its UTF-8 length in
898 bytes. Returns null if str is not a string.
900 target
.jstrlen = function(str
){
901 /** Attribution: derived from Emscripten's lengthBytesUTF8() */
902 if('string'!==typeof str
) return null;
903 const n
= str
.length
;
905 for(let i
= 0; i
< n
; ++i
){
906 let u
= str
.charCodeAt(i
);
907 if(u
>=0xd800 && u
<=0xdfff){
908 u
= 0x10000 + ((u
& 0x3FF) << 10) | (str
.charCodeAt(++i
) & 0x3FF);
911 else if(u
<=0x7ff) len
+= 2;
912 else if(u
<=0xffff) len
+= 3;
919 Encodes the given JS string as UTF8 into the given TypedArray
920 tgt, starting at the given offset and writing, at most, maxBytes
921 bytes (including the NUL terminator if addNul is true, else no
922 NUL is added). If it writes any bytes at all and addNul is true,
923 it always NUL-terminates the output, even if doing so means that
924 the NUL byte is all that it writes.
926 If maxBytes is negative (the default) then it is treated as the
927 remaining length of tgt, starting at the given offset.
929 If writing the last character would surpass the maxBytes count
930 because the character is multi-byte, that character will not be
931 written (as opposed to writing a truncated multi-byte character).
932 This can lead to it writing as many as 3 fewer bytes than
935 Returns the number of bytes written to the target, _including_
936 the NUL terminator (if any). If it returns 0, it wrote nothing at
937 all, which can happen if:
939 - str is empty and addNul is false.
942 - maxBytes is less than the byte length of a multi-byte str[0].
944 Throws if tgt is not an Int8Array or Uint8Array.
948 - In C's strcpy(), the destination pointer is the first
949 argument. That is not the case here primarily because the 3rd+
950 arguments are all referring to the destination, so it seems to
951 make sense to have them grouped with it.
953 - Emscripten's counterpart of this function (stringToUTF8Array())
954 returns the number of bytes written sans NUL terminator. That
955 is, however, ambiguous: str.length===0 or maxBytes===(0 or 1)
956 all cause 0 to be returned.
958 target
.jstrcpy = function(jstr
, tgt
, offset
= 0, maxBytes
= -1, addNul
= true){
959 /** Attribution: the encoding bits are taken from Emscripten's
960 stringToUTF8Array(). */
961 if(!tgt
|| (!(tgt
instanceof Int8Array
) && !(tgt
instanceof Uint8Array
))){
962 toss("jstrcpy() target must be an Int8Array or Uint8Array.");
964 if(maxBytes
<0) maxBytes
= tgt
.length
- offset
;
965 if(!(maxBytes
>0) || !(offset
>=0)) return 0;
966 let i
= 0, max
= jstr
.length
;
967 const begin
= offset
, end
= offset
+ maxBytes
- (addNul
? 1 : 0);
968 for(; i
< max
&& offset
< end
; ++i
){
969 let u
= jstr
.charCodeAt(i
);
970 if(u
>=0xd800 && u
<=0xdfff){
971 u
= 0x10000 + ((u
& 0x3FF) << 10) | (jstr
.charCodeAt(++i
) & 0x3FF);
974 if(offset
>= end
) break;
977 if(offset
+ 1 >= end
) break;
978 tgt
[offset
++] = 0xC0 | (u
>> 6);
979 tgt
[offset
++] = 0x80 | (u
& 0x3f);
981 if(offset
+ 2 >= end
) break;
982 tgt
[offset
++] = 0xe0 | (u
>> 12);
983 tgt
[offset
++] = 0x80 | ((u
>> 6) & 0x3f);
984 tgt
[offset
++] = 0x80 | (u
& 0x3f);
986 if(offset
+ 3 >= end
) break;
987 tgt
[offset
++] = 0xf0 | (u
>> 18);
988 tgt
[offset
++] = 0x80 | ((u
>> 12) & 0x3f);
989 tgt
[offset
++] = 0x80 | ((u
>> 6) & 0x3f);
990 tgt
[offset
++] = 0x80 | (u
& 0x3f);
993 if(addNul
) tgt
[offset
++] = 0;
994 return offset
- begin
;
998 Works similarly to C's strncpy(), copying, at most, n bytes (not
999 characters) from srcPtr to tgtPtr. It copies until n bytes have
1000 been copied or a 0 byte is reached in src. _Unlike_ strncpy(), it
1001 returns the number of bytes it assigns in tgtPtr, _including_ the
1002 NUL byte (if any). If n is reached before a NUL byte in srcPtr,
1003 tgtPtr will _not_ be NULL-terminated. If a NUL byte is reached
1004 before n bytes are copied, tgtPtr will be NUL-terminated.
1006 If n is negative, cstrlen(srcPtr)+1 is used to calculate it, the
1007 +1 being for the NUL byte.
1009 Throws if tgtPtr or srcPtr are falsy. Results are undefined if:
1011 - either is not a pointer into the WASM heap or
1013 - srcPtr is not NUL-terminated AND n is less than srcPtr's
1016 ACHTUNG: it is possible to copy partial multi-byte characters
1017 this way, and converting such strings back to JS strings will
1018 have undefined results.
1020 target
.cstrncpy = function(tgtPtr
, srcPtr
, n
){
1021 if(!tgtPtr
|| !srcPtr
) toss("cstrncpy() does not accept NULL strings.");
1022 if(n
<0) n
= target
.cstrlen(strPtr
)+1;
1023 else if(!(n
>0)) return 0;
1024 const heap
= target
.heap8u();
1026 for(; i
< n
&& (ch
= heap
[srcPtr
+i
]); ++i
){
1027 heap
[tgtPtr
+i
] = ch
;
1029 if(i
<n
) heap
[tgtPtr
+ i
++] = 0;
1034 For the given JS string, returns a Uint8Array of its contents
1035 encoded as UTF-8. If addNul is true, the returned array will have
1036 a trailing 0 entry, else it will not.
1038 target
.jstrToUintArray
= (str
, addNul
=false)=>{
1039 return cache
.utf8Encoder
.encode(addNul
? (str
+"\0") : str
);
1040 // Or the hard way...
1041 /** Attribution: derived from Emscripten's stringToUTF8Array() */
1042 //const a = [], max = str.length;
1043 //let i = 0, pos = 0;
1044 //for(; i < max; ++i){
1045 // let u = str.charCodeAt(i);
1046 // if(u>=0xd800 && u<=0xdfff){
1047 // u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
1049 // if(u<=0x7f) a[pos++] = u;
1050 // else if(u<=0x7ff){
1051 // a[pos++] = 0xC0 | (u >> 6);
1052 // a[pos++] = 0x80 | (u & 63);
1053 // }else if(u<=0xffff){
1054 // a[pos++] = 0xe0 | (u >> 12);
1055 // a[pos++] = 0x80 | ((u >> 6) & 63);
1056 // a[pos++] = 0x80 | (u & 63);
1058 // a[pos++] = 0xf0 | (u >> 18);
1059 // a[pos++] = 0x80 | ((u >> 12) & 63);
1060 // a[pos++] = 0x80 | ((u >> 6) & 63);
1061 // a[pos++] = 0x80 | (u & 63);
1064 // return new Uint8Array(a);
1067 const __affirmAlloc
= (obj
,funcName
)=>{
1068 if(!(obj
.alloc
instanceof Function
) ||
1069 !(obj
.dealloc
instanceof Function
)){
1070 toss("Object is missing alloc() and/or dealloc() function(s)",
1071 "required by",funcName
+"().");
1075 const __allocCStr = function(jstr
, returnWithLength
, allocator
, funcName
){
1076 __affirmAlloc(target
, funcName
);
1077 if('string'!==typeof jstr
) return null;
1078 if(0){/* older impl, possibly more widely compatible? */
1079 const n
= target
.jstrlen(jstr
),
1080 ptr
= allocator(n
+1);
1081 target
.jstrcpy(jstr
, target
.heap8u(), ptr
, n
+1, true);
1082 return returnWithLength
? [ptr
, n
] : ptr
;
1083 }else{/* newer, (probably) faster and (certainly) simpler impl */
1084 const u
= cache
.utf8Encoder
.encode(jstr
),
1085 ptr
= allocator(u
.length
+1),
1086 heap
= heapWrappers().HEAP8U
;
1088 heap
[ptr
+ u
.length
] = 0;
1089 return returnWithLength
? [ptr
, u
.length
] : ptr
;
1094 Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1
1095 bytes of memory, copies jstr to that memory using jstrcpy(),
1096 NUL-terminates it, and returns the pointer to that C-string.
1097 Ownership of the pointer is transfered to the caller, who must
1098 eventually pass the pointer to dealloc() to free it.
1100 If passed a truthy 2nd argument then its return semantics change:
1101 it returns [ptr,n], where ptr is the C-string's pointer and n is
1104 Throws if `target.alloc` or `target.dealloc` are not functions.
1106 target
.allocCString
=
1107 (jstr
, returnWithLength
=false)=>__allocCStr(jstr
, returnWithLength
,
1108 target
.alloc
, 'allocCString()');
1111 Starts an "allocation scope." All allocations made using
1112 scopedAlloc() are recorded in this scope and are freed when the
1113 value returned from this function is passed to
1116 This family of functions requires that the API's object have both
1117 `alloc()` and `dealloc()` methods, else this function will throw.
1122 const scope = scopedAllocPush();
1124 const ptr1 = scopedAlloc(100);
1125 const ptr2 = scopedAlloc(200);
1126 const ptr3 = scopedAlloc(300);
1128 // Note that only allocations made via scopedAlloc()
1129 // are managed by this allocation scope.
1131 scopedAllocPop(scope);
1135 The value returned by this function must be treated as opaque by
1136 the caller, suitable _only_ for passing to scopedAllocPop().
1137 Its type and value are not part of this function's API and may
1138 change in any given version of this code.
1140 `scopedAlloc.level` can be used to determine how many scoped
1141 alloc levels are currently active.
1143 target
.scopedAllocPush = function(){
1144 __affirmAlloc(target
, 'scopedAllocPush');
1146 cache
.scopedAlloc
.push(a
);
1151 Cleans up all allocations made using scopedAlloc() in the context
1152 of the given opaque state object, which must be a value returned
1153 by scopedAllocPush(). See that function for an example of how to
1156 Though scoped allocations are managed like a stack, this API
1157 behaves properly if allocation scopes are popped in an order
1158 other than the order they were pushed.
1160 If called with no arguments, it pops the most recent
1161 scopedAllocPush() result:
1165 try{ ... } finally { scopedAllocPop(); }
1168 It's generally recommended that it be passed an explicit argument
1169 to help ensure that push/push are used in matching pairs, but in
1170 trivial code that may be a non-issue.
1172 target
.scopedAllocPop = function(state
){
1173 __affirmAlloc(target
, 'scopedAllocPop');
1174 const n
= arguments
.length
1175 ? cache
.scopedAlloc
.indexOf(state
)
1176 : cache
.scopedAlloc
.length
-1;
1177 if(n
<0) toss("Invalid state object for scopedAllocPop().");
1178 if(0===arguments
.length
) state
= cache
.scopedAlloc
[n
];
1179 cache
.scopedAlloc
.splice(n
,1);
1180 for(let p
; (p
= state
.pop()); ){
1181 if(target
.functionEntry(p
)){
1182 //console.warn("scopedAllocPop() uninstalling transient function",p);
1183 target
.uninstallFunction(p
);
1185 else target
.dealloc(p
);
1190 Allocates n bytes of memory using this.alloc() and records that
1191 fact in the state for the most recent call of scopedAllocPush().
1192 Ownership of the memory is given to scopedAllocPop(), which
1193 will clean it up when it is called. The memory _must not_ be
1194 passed to this.dealloc(). Throws if this API object is missing
1195 the required `alloc()` or `dealloc()` functions or no scoped
1198 See scopedAllocPush() for an example of how to use this function.
1200 The `level` property of this function can be queried to query how
1201 many scoped allocation levels are currently active.
1203 See also: scopedAllocPtr(), scopedAllocCString()
1205 target
.scopedAlloc = function(n
){
1206 if(!cache
.scopedAlloc
.length
){
1207 toss("No scopedAllocPush() scope is active.");
1209 const p
= target
.alloc(n
);
1210 cache
.scopedAlloc
[cache
.scopedAlloc
.length
-1].push(p
);
1214 Object
.defineProperty(target
.scopedAlloc
, 'level', {
1215 configurable
: false, enumerable
: false,
1216 get: ()=>cache
.scopedAlloc
.length
,
1217 set: ()=>toss("The 'active' property is read-only.")
1221 Works identically to allocCString() except that it allocates the
1222 memory using scopedAlloc().
1224 Will throw if no scopedAllocPush() call is active.
1226 target
.scopedAllocCString
=
1227 (jstr
, returnWithLength
=false)=>__allocCStr(jstr
, returnWithLength
,
1228 target
.scopedAlloc
, 'scopedAllocCString()');
1230 // impl for allocMainArgv() and scopedAllocMainArgv().
1231 const __allocMainArgv = function(isScoped
, list
){
1232 const pList
= target
[
1233 isScoped
? 'scopedAlloc' : 'alloc'
1234 ]((list
.length
+ 1) * target
.ptrSizeof
);
1237 target
.pokePtr(pList
+ (target
.ptrSizeof
* i
++),
1239 isScoped
? 'scopedAllocCString' : 'allocCString'
1242 target
.pokePtr(pList
+ (target
.ptrSizeof
* i
), 0);
1247 Creates an array, using scopedAlloc(), suitable for passing to a
1248 C-level main() routine. The input is a collection with a length
1249 property and a forEach() method. A block of memory
1250 (list.length+1) entries long is allocated and each pointer-sized
1251 block of that memory is populated with a scopedAllocCString()
1252 conversion of the (""+value) of each element, with the exception
1253 that the final entry is a NULL pointer. Returns a pointer to the
1254 start of the list, suitable for passing as the 2nd argument to a
1255 C-style main() function.
1257 Throws if scopedAllocPush() is not active.
1259 Design note: the returned array is allocated with an extra NULL
1260 pointer entry to accommodate certain APIs, but client code which
1261 does not need that functionality should treat the returned array
1262 as list.length entries long.
1264 target
.scopedAllocMainArgv
= (list
)=>__allocMainArgv(true, list
);
1267 Identical to scopedAllocMainArgv() but uses alloc() instead of
1270 target
.allocMainArgv
= (list
)=>__allocMainArgv(false, list
);
1273 Expects to be given a C-style string array and its length. It
1274 returns a JS array of strings and/or nulls: any entry in the
1275 pArgv array which is NULL results in a null entry in the result
1276 array. If argc is 0 then an empty array is returned.
1278 Results are undefined if any entry in the first argc entries of
1279 pArgv are neither 0 (NULL) nor legal UTF-format C strings.
1281 To be clear, the expected C-style arguments to be passed to this
1282 function are `(int, char **)` (optionally const-qualified).
1284 target
.cArgvToJs
= (argc
, pArgv
)=>{
1286 for(let i
= 0; i
< argc
; ++i
){
1287 const arg
= target
.peekPtr(pArgv
+ (target
.ptrSizeof
* i
));
1288 list
.push( arg
? target
.cstrToJs(arg
) : null );
1294 Wraps function call func() in a scopedAllocPush() and
1295 scopedAllocPop() block, such that all calls to scopedAlloc() and
1296 friends from within that call will have their memory freed
1297 automatically when func() returns. If func throws or propagates
1298 an exception, the scope is still popped, otherwise it returns the
1299 result of calling func().
1301 target
.scopedAllocCall = function(func
){
1302 target
.scopedAllocPush();
1303 try{ return func() } finally{ target
.scopedAllocPop() }
1306 /** Internal impl for allocPtr() and scopedAllocPtr(). */
1307 const __allocPtr = function(howMany
, safePtrSize
, method
){
1308 __affirmAlloc(target
, method
);
1309 const pIr
= safePtrSize
? 'i64' : ptrIR
;
1310 let m
= target
[method
](howMany
* (safePtrSize
? 8 : ptrSizeof
));
1311 target
.poke(m
, 0, pIr
)
1316 for(let i
= 1; i
< howMany
; ++i
){
1317 m
+= (safePtrSize
? 8 : ptrSizeof
);
1319 target
.poke(m
, 0, pIr
);
1325 Allocates one or more pointers as a single chunk of memory and
1328 The first argument is the number of pointers to allocate. The
1329 second specifies whether they should use a "safe" pointer size (8
1330 bytes) or whether they may use the default pointer size
1331 (typically 4 but also possibly 8).
1333 How the result is returned depends on its first argument: if
1334 passed 1, it returns the allocated memory address. If passed more
1335 than one then an array of pointer addresses is returned, which
1336 can optionally be used with "destructuring assignment" like this:
1339 const [p1, p2, p3] = allocPtr(3);
1342 ACHTUNG: when freeing the memory, pass only the _first_ result
1343 value to dealloc(). The others are part of the same memory chunk
1344 and must not be freed separately.
1346 The reason for the 2nd argument is..
1348 When one of the returned pointers will refer to a 64-bit value,
1349 e.g. a double or int64, an that value must be written or fetched,
1350 e.g. using poke() or peek(), it is important that
1351 the pointer in question be aligned to an 8-byte boundary or else
1352 it will not be fetched or written properly and will corrupt or
1353 read neighboring memory. It is only safe to pass false when the
1354 client code is certain that it will only get/fetch 4-byte values
1358 (howMany
=1, safePtrSize
=true)=>__allocPtr(howMany
, safePtrSize
, 'alloc');
1361 Identical to allocPtr() except that it allocates using scopedAlloc()
1364 target
.scopedAllocPtr
=
1365 (howMany
=1, safePtrSize
=true)=>__allocPtr(howMany
, safePtrSize
, 'scopedAlloc');
1368 If target.exports[name] exists, it is returned, else an
1369 exception is thrown.
1371 target
.xGet = function(name
){
1372 return target
.exports
[name
] || toss("Cannot find exported symbol:",name
);
1375 const __argcMismatch
=
1376 (f
,n
)=>toss(f
+"() requires",n
,"argument(s).");
1379 Looks up a WASM-exported function named fname from
1380 target.exports. If found, it is called, passed all remaining
1381 arguments, and its return value is returned to xCall's caller. If
1382 not found, an exception is thrown. This function does no
1383 conversion of argument or return types, but see xWrap() and
1384 xCallWrapped() for variants which do.
1386 As a special case, if passed only 1 argument after the name and
1387 that argument in an Array, that array's entries become the
1388 function arguments. (This is not an ambiguous case because it's
1389 not legal to pass an Array object to a WASM function.)
1391 target
.xCall = function(fname
, ...args
){
1392 const f
= target
.xGet(fname
);
1393 if(!(f
instanceof Function
)) toss("Exported symbol",fname
,"is not a function.");
1394 if(f
.length
!==args
.length
) __argcMismatch(fname
,f
.length
)
1395 /* This is arguably over-pedantic but we want to help clients keep
1396 from shooting themselves in the foot when calling C APIs. */;
1397 return (2===arguments
.length
&& Array
.isArray(arguments
[1]))
1398 ? f
.apply(null, arguments
[1])
1399 : f
.apply(null, args
);
1403 State for use with xWrap()
1405 cache
.xWrap
= Object
.create(null);
1406 cache
.xWrap
.convert
= Object
.create(null);
1407 /** Map of type names to argument conversion functions. */
1408 cache
.xWrap
.convert
.arg
= new Map
;
1409 /** Map of type names to return result conversion functions. */
1410 cache
.xWrap
.convert
.result
= new Map
;
1411 const xArg
= cache
.xWrap
.convert
.arg
, xResult
= cache
.xWrap
.convert
.result
;
1413 if(target
.bigIntEnabled
){
1414 xArg
.set('i64', (i
)=>BigInt(i
));
1416 const __xArgPtr
= 'i32' === ptrIR
1417 ? ((i
)=>(i
| 0)) : ((i
)=>(BigInt(i
) | BigInt(0)));
1418 xArg
.set('i32', __xArgPtr
)
1419 .set('i16', (i
)=>((i
| 0) & 0xFFFF))
1420 .set('i8', (i
)=>((i
| 0) & 0xFF))
1421 .set('f32', (i
)=>Number(i
).valueOf())
1422 .set('float', xArg
.get('f32'))
1423 .set('f64', xArg
.get('f32'))
1424 .set('double', xArg
.get('f64'))
1425 .set('int', xArg
.get('i32'))
1426 .set('null', (i
)=>i
)
1427 .set(null, xArg
.get('null'))
1428 .set('**', __xArgPtr
)
1429 .set('*', __xArgPtr
);
1430 xResult
.set('*', __xArgPtr
)
1431 .set('pointer', __xArgPtr
)
1432 .set('number', (v
)=>Number(v
))
1433 .set('void', (v
)=>undefined)
1434 .set('null', (v
)=>v
)
1435 .set(null, xResult
.get('null'));
1437 { /* Copy certain xArg[...] handlers to xResult[...] and
1438 add pointer-style variants of them. */
1439 const copyToResult
= ['i8', 'i16', 'i32', 'int',
1440 'f32', 'float', 'f64', 'double'];
1441 if(target
.bigIntEnabled
) copyToResult
.push('i64');
1442 const adaptPtr
= xArg
.get(ptrIR
);
1443 for(const t
of copyToResult
){
1444 xArg
.set(t
+'*', adaptPtr
);
1445 xResult
.set(t
+'*', adaptPtr
);
1446 xResult
.set(t
, (xArg
.get(t
) || toss("Missing arg converter:",t
)));
1451 In order for args of type string to work in various contexts in
1452 the sqlite3 API, we need to pass them on as, variably, a C-string
1453 or a pointer value. Thus for ARGs of type 'string' and
1454 '*'/'pointer' we behave differently depending on whether the
1455 argument is a string or not:
1457 - If v is a string, scopeAlloc() a new C-string from it and return
1458 that temp string's pointer.
1460 - Else return the value from the arg adapter defined for ptrIR.
1462 TODO? Permit an Int8Array/Uint8Array and convert it to a string?
1463 Would that be too much magic concentrated in one place, ready to
1464 backfire? We handle that at the client level in sqlite3 with a
1465 custom argument converter.
1467 const __xArgString = function(v
){
1468 if('string'===typeof v
) return target
.scopedAllocCString(v
);
1469 return v
? __xArgPtr(v
) : null;
1471 xArg
.set('string', __xArgString
)
1472 .set('utf8', __xArgString
)
1473 .set('pointer', __xArgString
);
1474 //xArg.set('*', __xArgString);
1476 xResult
.set('string', (i
)=>target
.cstrToJs(i
))
1477 .set('utf8', xResult
.get('string'))
1478 .set('string:dealloc', (i
)=>{
1479 try { return i
? target
.cstrToJs(i
) : null }
1480 finally{ target
.dealloc(i
) }
1482 .set('utf8:dealloc', xResult
.get('string:dealloc'))
1483 .set('json', (i
)=>JSON
.parse(target
.cstrToJs(i
)))
1484 .set('json:dealloc', (i
)=>{
1485 try{ return i
? JSON
.parse(target
.cstrToJs(i
)) : null }
1486 finally{ target
.dealloc(i
) }
1490 Internal-use-only base class for FuncPtrAdapter and potentially
1491 additional stateful argument adapter classes.
1493 Note that its main interface (convertArg()) is strictly
1494 internal, not to be exposed to client code, as it may still
1495 need re-shaping. Only the constructors of concrete subclasses
1496 should be exposed to clients, and those in such a way that
1497 does not hinder internal redesign of the convertArg()
1500 const AbstractArgAdapter
= class {
1502 this.name
= opt
.name
|| 'unnamed adapter';
1505 Gets called via xWrap() to "convert" v to whatever type
1506 this specific class supports.
1508 argIndex is the argv index of _this_ argument in the
1509 being-xWrap()'d call. argv is the current argument list
1510 undergoing xWrap() argument conversion. argv entries to the
1511 left of argIndex will have already undergone transformation and
1512 those to the right will not have (they will have the values the
1513 client-level code passed in, awaiting conversion). The RHS
1514 indexes must never be relied upon for anything because their
1515 types are indeterminate, whereas the LHS values will be
1516 WASM-compatible values by the time this is called.
1518 convertArg(v
,argv
,argIndex
){
1519 toss("AbstractArgAdapter must be subclassed.");
1524 An attempt at adding function pointer conversion support to
1525 xWrap(). This type is recognized by xWrap() as a proxy for
1526 converting a JS function to a C-side function, either
1527 permanently, for the duration of a single call into the C layer,
1528 or semi-contextual, where it may keep track of a single binding
1529 for a given context and uninstall the binding if it's replaced.
1531 The constructor requires an options object with these properties:
1533 - name (optional): string describing the function binding. This
1534 is solely for debugging and error-reporting purposes. If not
1535 provided, an empty string is assumed.
1537 - signature: a function signature string compatible with
1540 - bindScope (string): one of ('transient', 'context',
1541 'singleton'). Bind scopes are:
1543 - 'transient': it will convert JS functions to WASM only for
1544 the duration of the xWrap()'d function call, using
1545 scopedInstallFunction(). Before that call returns, the
1546 WASM-side binding will be uninstalled.
1548 - 'singleton': holds one function-pointer binding for this
1549 instance. If it's called with a different function pointer,
1550 it uninstalls the previous one after converting the new
1551 value. This is only useful for use with "global" functions
1552 which do not rely on any state other than this function
1553 pointer. If the being-converted function pointer is intended
1554 to be mapped to some sort of state object (e.g. an
1555 `sqlite3*`) then "context" (see below) is the proper mode.
1557 - 'context': similar to singleton mode but for a given
1558 "context", where the context is a key provided by the user
1559 and possibly dependent on a small amount of call-time
1560 context. This mode is the default if bindScope is _not_ set
1561 but a property named contextKey (described below) is.
1563 - 'permanent': the function is installed and left there
1564 forever. There is no way to recover its pointer address
1567 - callProxy (function): if set, this must be a function which
1568 will act as a proxy for any "converted" JS function. It is
1569 passed the being-converted function value and must return
1570 either that function or a function which acts on its
1571 behalf. The returned function will be the one which gets
1572 installed into the WASM function table. The proxy must perform
1573 any required argument conversion (noting that it will be called
1574 from C code, so will receive C-format arguments) before passing
1575 them on to the being-converted function. Whether or not the
1576 proxy itself must return a value depends on the context. If it
1577 does, it must be a WASM-friendly value, as it will be returning
1578 from a call made from native code.
1580 - contextKey (function): is only used if bindScope is 'context'
1581 or if bindScope is not set and this function is, in which case
1582 'context' is assumed. This function gets bound to this object,
1583 so its "this" is this object. It gets passed (argv,argIndex),
1584 where argIndex is the index of _this_ function pointer in its
1585 _wrapping_ function's arguments and argv is the _current_
1586 still-being-xWrap()-processed args array. All arguments to the
1587 left of argIndex will have been processed by xWrap() by the
1588 time this is called. argv[argIndex] will be the value the user
1589 passed in to the xWrap()'d function for the argument this
1590 FuncPtrAdapter is mapped to. Arguments to the right of
1591 argv[argIndex] will not yet have been converted before this is
1592 called. The function must return a key which uniquely
1593 identifies this function mapping context for _this_
1594 FuncPtrAdapter instance (other instances are not considered),
1595 taking into account that C functions often take some sort of
1596 state object as one or more of their arguments. As an example,
1597 if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and
1598 this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2)
1599 might return 'T@'+argv[1], or even just argv[1]. Note,
1600 however, that the (X*) argument will not yet have been
1601 processed by the time this is called and should not be used as
1602 part of that key because its pre-conversion data type might be
1603 unpredictable. Similarly, care must be taken with C-string-type
1604 arguments: those to the left in argv will, when this is called,
1605 be WASM pointers, whereas those to the right might (and likely
1606 do) have another data type. When using C-strings in keys, never
1607 use their pointers in the key because most C-strings in this
1608 constellation are transient.
1610 Yes, that ^^^ is quite awkward, but it's what we have.
1612 The constructor only saves the above state for later, and does
1613 not actually bind any functions. Its convertArg() method is
1614 called via xWrap() to perform any bindings.
1618 - These "reverse" bindings, i.e. calling into a JS-defined
1619 function from a WASM-defined function (the generated proxy
1620 wrapper), lack all type conversion support. That means, for
1623 - Function pointers which include C-string arguments may still
1624 need a level of hand-written wrappers around them, depending on
1625 how they're used, in order to provide the client with JS
1626 strings. Alternately, clients will need to perform such conversions
1627 on their own, e.g. using cstrtojs(). Or maybe we can find a way
1628 to perform such conversions here, via addition of an xWrap()-style
1629 function signature to the options argument.
1631 xArg
.FuncPtrAdapter
= class FuncPtrAdapter
extends AbstractArgAdapter
{
1634 if(xArg
.FuncPtrAdapter
.warnOnUse
){
1635 console
.warn('xArg.FuncPtrAdapter is an internal-only API',
1636 'and is not intended to be invoked from',
1637 'client-level code. Invoked with:',opt
);
1639 this.signature
= opt
.signature
;
1640 if(opt
.contextKey
instanceof Function
){
1641 this.contextKey
= opt
.contextKey
;
1642 if(!opt
.bindScope
) opt
.bindScope
= 'context';
1644 this.bindScope
= opt
.bindScope
1645 || toss("FuncPtrAdapter options requires a bindScope (explicit or implied).");
1646 if(FuncPtrAdapter
.bindScopes
.indexOf(opt
.bindScope
)<0){
1647 toss("Invalid options.bindScope ("+opt
.bindMod
+") for FuncPtrAdapter. "+
1648 "Expecting one of: ("+FuncPtrAdapter
.bindScopes
.join(', ')+')');
1650 this.isTransient
= 'transient'===this.bindScope
;
1651 this.isContext
= 'context'===this.bindScope
;
1652 this.isPermanent
= 'permanent'===this.bindScope
;
1653 this.singleton
= ('singleton'===this.bindScope
) ? [] : undefined;
1654 //console.warn("FuncPtrAdapter()",opt,this);
1655 this.callProxy
= (opt
.callProxy
instanceof Function
)
1656 ? opt
.callProxy
: undefined;
1659 /** If true, the constructor emits a warning. The intent is that
1660 this be set to true after bootstrapping of the higher-level
1661 client library is complete, to warn downstream clients that
1662 they shouldn't be relying on this implemenation detail which
1663 does not have a stable interface. */
1664 static warnOnUse
= false;
1666 /** If true, convertArg() will FuncPtrAdapter.debugOut() when it
1667 (un)installs a function binding to/from WASM. Note that
1668 deinstallation of bindScope=transient bindings happens
1669 via scopedAllocPop() so will not be output. */
1670 static debugFuncInstall
= false;
1672 /** Function used for debug output. */
1673 static debugOut
= console
.debug
.bind(console
);
1675 static bindScopes
= [
1676 'transient', 'context', 'singleton', 'permanent'
1679 /* Dummy impl. Overwritten per-instance as needed. */
1680 contextKey(argv
,argIndex
){
1684 /* Returns this objects mapping for the given context key, in the
1685 form of an an array, creating the mapping if needed. The key
1686 may be anything suitable for use in a Map. */
1688 const cm
= (this.__cmap
|| (this.__cmap
= new Map
));
1689 let rc
= cm
.get(key
);
1690 if(undefined===rc
) cm
.set(key
, (rc
= []));
1695 Gets called via xWrap() to "convert" v to a WASM-bound function
1696 pointer. If v is one of (a pointer, null, undefined) then
1697 (v||0) is returned and any earlier function installed by this
1698 mapping _might_, depending on how it's bound, be uninstalled.
1699 If v is not one of those types, it must be a Function, for
1700 which it creates (if needed) a WASM function binding and
1701 returns the WASM pointer to that binding. If this instance is
1702 not in 'transient' mode, it will remember the binding for at
1703 least the next call, to avoid recreating the function binding
1706 If it's passed a pointer(ish) value for v, it does _not_
1707 perform any function binding, so this object's bindMode is
1708 irrelevant for such cases.
1710 See the parent class's convertArg() docs for details on what
1711 exactly the 2nd and 3rd arguments are.
1713 convertArg(v
,argv
,argIndex
){
1714 //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v);
1715 let pair
= this.singleton
;
1716 if(!pair
&& this.isContext
){
1717 pair
= this.contextMap(this.contextKey(argv
,argIndex
));
1719 if(pair
&& pair
[0]===v
) return pair
[1];
1720 if(v
instanceof Function
){
1721 /* Install a WASM binding and return its pointer. */
1722 if(this.callProxy
) v
= this.callProxy(v
);
1723 const fp
= __installFunction(v
, this.signature
, this.isTransient
);
1724 if(FuncPtrAdapter
.debugFuncInstall
){
1725 FuncPtrAdapter
.debugOut("FuncPtrAdapter installed", this,
1726 this.contextKey(argv
,argIndex
), '@'+fp
, v
);
1729 /* Replace existing stashed mapping */
1731 if(FuncPtrAdapter
.debugFuncInstall
){
1732 FuncPtrAdapter
.debugOut("FuncPtrAdapter uninstalling", this,
1733 this.contextKey(argv
,argIndex
), '@'+pair
[1], v
);
1735 try{target
.uninstallFunction(pair
[1])}
1736 catch(e
){/*ignored*/}
1742 }else if(target
.isPtr(v
) || null===v
|| undefined===v
){
1743 if(pair
&& pair
[1] && pair
[1]!==v
){
1744 /* uninstall stashed mapping and replace stashed mapping with v. */
1745 if(FuncPtrAdapter
.debugFuncInstall
){
1746 FuncPtrAdapter
.debugOut("FuncPtrAdapter uninstalling", this,
1747 this.contextKey(argv
,argIndex
), '@'+pair
[1], v
);
1749 try{target
.uninstallFunction(pair
[1])}
1750 catch(e
){/*ignored*/}
1751 pair
[0] = pair
[1] = (v
| 0);
1755 throw new TypeError("Invalid FuncPtrAdapter argument type. "+
1756 "Expecting a function pointer or a "+
1757 (this.name
? this.name
+' ' : '')+
1758 "function matching signature "+
1759 this.signature
+".");
1762 }/*FuncPtrAdapter*/;
1764 const __xArgAdapterCheck
=
1765 (t
)=>xArg
.get(t
) || toss("Argument adapter not found:",t
);
1767 const __xResultAdapterCheck
=
1768 (t
)=>xResult
.get(t
) || toss("Result adapter not found:",t
);
1770 cache
.xWrap
.convertArg
= (t
,...args
)=>__xArgAdapterCheck(t
)(...args
);
1771 cache
.xWrap
.convertArgNoCheck
= (t
,...args
)=>xArg
.get(t
)(...args
);
1773 cache
.xWrap
.convertResult
=
1774 (t
,v
)=>(null===t
? v
: (t
? __xResultAdapterCheck(t
)(v
) : undefined));
1775 cache
.xWrap
.convertResultNoCheck
=
1776 (t
,v
)=>(null===t
? v
: (t
? xResult
.get(t
)(v
) : undefined));
1779 Creates a wrapper for another function which converts the arguments
1780 of the wrapper to argument types accepted by the wrapped function,
1781 then converts the wrapped function's result to another form
1784 The first argument must be one of:
1786 - A JavaScript function.
1787 - The name of a WASM-exported function. In the latter case xGet()
1788 is used to fetch the exported function, which throws if it's not
1790 - A pointer into the indirect function table. e.g. a pointer
1791 returned from target.installFunction().
1793 It returns either the passed-in function or a wrapper for that
1794 function which converts the JS-side argument types into WASM-side
1795 types and converts the result type.
1797 The second argument, `resultType`, describes the conversion for
1798 the wrapped functions result. A literal `null` or the string
1799 `'null'` both mean to return the original function's value as-is
1800 (mnemonic: there is "null" conversion going on). Literal
1801 `undefined` or the string `"void"` both mean to ignore the
1802 function's result and return `undefined`. Aside from those two
1803 special cases, the `resultType` value may be one of the values
1804 described below or any mapping installed by the client using
1805 xWrap.resultAdapter().
1807 If passed 3 arguments and the final one is an array, that array
1808 must contain a list of type names (see below) for adapting the
1809 arguments from JS to WASM. If passed 2 arguments, more than 3,
1810 or the 3rd is not an array, all arguments after the 2nd (if any)
1811 are treated as type names. i.e.:
1814 xWrap('funcname', 'i32', 'string', 'f64');
1815 // is equivalent to:
1816 xWrap('funcname', 'i32', ['string', 'f64']);
1819 This function enforces that the given list of arguments has the
1820 same arity as the being-wrapped function (as defined by its
1821 `length` property) and it will throw if that is not the case.
1822 Similarly, the created wrapper will throw if passed a differing
1825 Type names are symbolic names which map the arguments to an
1826 adapter function to convert, if needed, the value before passing
1827 it on to WASM or to convert a return result from WASM. The list
1830 - `i8`, `i16`, `i32` (args and results): all integer conversions
1831 which convert their argument to an integer and truncate it to
1832 the given bit length.
1834 - `N*` (args): a type name in the form `N*`, where N is a numeric
1835 type name, is treated the same as WASM pointer.
1837 - `*` and `pointer` (args): are assumed to be WASM pointer values
1838 and are returned coerced to an appropriately-sized pointer
1839 value (i32 or i64). Non-numeric values will coerce to 0 and
1840 out-of-range values will have undefined results (just as with
1841 any pointer misuse).
1843 - `*` and `pointer` (results): aliases for the current
1844 WASM pointer numeric type.
1846 - `**` (args): is simply a descriptive alias for the WASM pointer
1847 type. It's primarily intended to mark output-pointer arguments.
1849 - `i64` (args and results): passes the value to BigInt() to
1850 convert it to an int64. Only available if bigIntEnabled is
1853 - `f32` (`float`), `f64` (`double`) (args and results): pass
1854 their argument to Number(). i.e. the adapter does not currently
1855 distinguish between the two types of floating-point numbers.
1857 - `number` (results): converts the result to a JS Number using
1858 Number(theValue).valueOf(). Note that this is for result
1859 conversions only, as it's not possible to generically know
1860 which type of number to convert arguments to.
1862 Non-numeric conversions include:
1864 - `null` literal or `"null"` string (args and results): perform
1865 no translation and pass the arg on as-is. This is primarily
1866 useful for results but may have a use or two for arguments.
1868 - `string` or `utf8` (args): has two different semantics in order
1869 to accommodate various uses of certain C APIs
1870 (e.g. output-style strings)...
1872 - If the arg is a string, it creates a _temporary_
1873 UTF-8-encoded C-string to pass to the exported function,
1874 cleaning it up before the wrapper returns. If a long-lived
1875 C-string pointer is required, that requires client-side code
1876 to create the string, then pass its pointer to the function.
1878 - Else the arg is assumed to be a pointer to a string the
1879 client has already allocated and it's passed on as
1882 - `string` or `utf8` (results): treats the result value as a
1883 const C-string, encoded as UTF-8, copies it to a JS string,
1884 and returns that JS string.
1886 - `string:dealloc` or `utf8:dealloc) (results): treats the result value
1887 as a non-const UTF-8 C-string, ownership of which has just been
1888 transfered to the caller. It copies the C-string to a JS
1889 string, frees the C-string, and returns the JS string. If such
1890 a result value is NULL, the JS result is `null`. Achtung: when
1891 using an API which returns results from a specific allocator,
1892 e.g. `my_malloc()`, this conversion _is not legal_. Instead, an
1893 equivalent conversion which uses the appropriate deallocator is
1894 required. For example:
1897 target.xWrap.resultAdapter('string:my_free',(i)=>{
1898 try { return i ? target.cstrToJs(i) : null }
1899 finally{ target.exports.my_free(i) }
1903 - `json` (results): treats the result as a const C-string and
1904 returns the result of passing the converted-to-JS string to
1905 JSON.parse(). Returns `null` if the C-string is a NULL pointer.
1907 - `json:dealloc` (results): works exactly like `string:dealloc` but
1908 returns the same thing as the `json` adapter. Note the
1909 warning in `string:dealloc` regarding maching allocators and
1912 The type names for results and arguments are validated when
1913 xWrap() is called and any unknown names will trigger an
1916 Clients may map their own result and argument adapters using
1917 xWrap.resultAdapter() and xWrap.argAdapter(), noting that not all
1918 type conversions are valid for both arguments _and_ result types
1919 as they often have different memory ownership requirements.
1921 Design note: the ability to pass in a JS function as the first
1922 argument is of relatively limited use, primarily for testing
1923 argument and result converters. JS functions, by and large, will
1924 not want to deal with C-type arguments.
1928 - Figure out how/whether we can (semi-)transparently handle
1929 pointer-type _output_ arguments. Those currently require
1930 explicit handling by allocating pointers, assigning them before
1931 the call using poke(), and fetching them with
1932 peek() after the call. We may be able to automate some
1935 - Figure out whether it makes sense to extend the arg adapter
1936 interface such that each arg adapter gets an array containing
1937 the results of the previous arguments in the current call. That
1938 might allow some interesting type-conversion feature. Use case:
1939 handling of the final argument to sqlite3_prepare_v2() depends
1940 on the type (pointer vs JS string) of its 2nd
1941 argument. Currently that distinction requires hand-writing a
1942 wrapper for that function. That case is unusual enough that
1943 abstracting it into this API (and taking on the associated
1944 costs) may well not make good sense.
1946 target
.xWrap = function(fArg
, resultType
, ...argTypes
){
1947 if(3===arguments
.length
&& Array
.isArray(arguments
[2])){
1948 argTypes
= arguments
[2];
1950 if(target
.isPtr(fArg
)){
1951 fArg
= target
.functionEntry(fArg
)
1952 || toss("Function pointer not found in WASM function table.");
1954 const fIsFunc
= (fArg
instanceof Function
);
1955 const xf
= fIsFunc
? fArg
: target
.xGet(fArg
);
1956 if(fIsFunc
) fArg
= xf
.name
|| 'unnamed function';
1957 if(argTypes
.length
!==xf
.length
) __argcMismatch(fArg
, xf
.length
);
1958 if((null===resultType
) && 0===xf
.length
){
1959 /* Func taking no args with an as-is return. We don't need a wrapper.
1960 We forego the argc check here, though. */
1963 /*Verify the arg type conversions are valid...*/;
1964 if(undefined!==resultType
&& null!==resultType
) __xResultAdapterCheck(resultType
);
1965 for(const t
of argTypes
){
1966 if(t
instanceof AbstractArgAdapter
) xArg
.set(t
, (...args
)=>t
.convertArg(...args
));
1967 else __xArgAdapterCheck(t
);
1969 const cxw
= cache
.xWrap
;
1971 // No args to convert, so we can create a simpler wrapper...
1972 return (...args
)=>(args
.length
1973 ? __argcMismatch(fArg
, xf
.length
)
1974 : cxw
.convertResult(resultType
, xf
.call(null)));
1976 return function(...args
){
1977 if(args
.length
!==xf
.length
) __argcMismatch(fArg
, xf
.length
);
1978 const scope
= target
.scopedAllocPush();
1981 Maintenance reminder re. arguments passed to convertArg():
1982 The public interface of argument adapters is that they take
1983 ONE argument and return a (possibly) converted result for
1984 it. The passing-on of arguments after the first is an
1985 internal implementation detail for the sake of
1986 AbstractArgAdapter, and not to be relied on or documented
1987 for other cases. The fact that this is how
1988 AbstractArgAdapter.convertArgs() gets its 2nd+ arguments,
1989 and how FuncPtrAdapter.contextKey() gets its args, is also
1990 an implementation detail and subject to change. i.e. the
1991 public interface of 1 argument is stable. The fact that any
1992 arguments may be passed in after that one, and what those
1993 arguments are, is _not_ part of the public interface and is
1996 for(const i
in args
) args
[i
] = cxw
.convertArgNoCheck(
1997 argTypes
[i
], args
[i
], args
, i
1999 return cxw
.convertResultNoCheck(resultType
, xf
.apply(null,args
));
2001 target
.scopedAllocPop(scope
);
2006 /** Internal impl for xWrap.resultAdapter() and argAdapter(). */
2007 const __xAdapter = function(func
, argc
, typeName
, adapter
, modeName
, xcvPart
){
2008 if('string'===typeof typeName
){
2009 if(1===argc
) return xcvPart
.get(typeName
);
2012 delete xcvPart
.get(typeName
);
2014 }else if(!(adapter
instanceof Function
)){
2015 toss(modeName
,"requires a function argument.");
2017 xcvPart
.set(typeName
, adapter
);
2021 toss("Invalid arguments to",modeName
);
2025 Gets, sets, or removes a result value adapter for use with
2026 xWrap(). If passed only 1 argument, the adapter function for the
2027 given type name is returned. If the second argument is explicit
2028 falsy (as opposed to defaulted), the adapter named by the first
2029 argument is removed. If the 2nd argument is not falsy, it must be
2030 a function which takes one value and returns a value appropriate
2031 for the given type name. The adapter may throw if its argument is
2032 not of a type it can work with. This function throws for invalid
2038 xWrap.resultAdapter('twice',(v)=>v+v);
2041 xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of
2042 APIs to allocate a result value. xWrap()-generated wrappers run
2043 in the context of scopedAllocPush() so that argument adapters can
2044 easily convert, e.g., to C-strings, and have them cleaned up
2045 automatically before the wrapper returns to the caller. Likewise,
2046 if a _result_ adapter uses scoped allocation, the result will be
2047 freed before because they would be freed before the wrapper
2048 returns, leading to chaos and undefined behavior.
2050 Except when called as a getter, this function returns itself.
2052 target
.xWrap
.resultAdapter
= function f(typeName
, adapter
){
2053 return __xAdapter(f
, arguments
.length
, typeName
, adapter
,
2054 'resultAdapter()', xResult
);
2058 Functions identically to xWrap.resultAdapter() but applies to
2059 call argument conversions instead of result value conversions.
2061 xWrap()-generated wrappers perform argument conversion in the
2062 context of a scopedAllocPush(), so any memory allocation
2063 performed by argument adapters really, really, really should be
2064 made using the scopedAlloc() family of functions unless
2065 specifically necessary. For example:
2068 xWrap.argAdapter('my-string', function(v){
2069 return ('string'===typeof v)
2070 ? myWasmObj.scopedAllocCString(v) : null;
2074 Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc()
2075 to allocate its results because they would be freed before the
2076 xWrap()-created wrapper returns.
2078 Note that it is perfectly legitimate to use these adapters to
2079 perform argument validation, as opposed (or in addition) to
2082 target
.xWrap
.argAdapter
= function f(typeName
, adapter
){
2083 return __xAdapter(f
, arguments
.length
, typeName
, adapter
,
2084 'argAdapter()', xArg
);
2087 target
.xWrap
.FuncPtrAdapter
= xArg
.FuncPtrAdapter
;
2090 Functions like xCall() but performs argument and result type
2091 conversions as for xWrap(). The first, second, and third
2092 arguments are as documented for xWrap(), except that the 3rd
2093 argument may be either a falsy value or empty array to represent
2094 nullary functions. The 4th+ arguments are arguments for the call,
2095 with the special case that if the 4th argument is an array, it is
2096 used as the arguments for the call. Returns the converted result
2099 This is just a thin wrapper around xWrap(). If the given function
2100 is to be called more than once, it's more efficient to use
2101 xWrap() to create a wrapper, then to call that wrapper as many
2102 times as needed. For one-shot calls, however, this variant is
2103 arguably more efficient because it will hypothetically free the
2104 wrapper function quickly.
2106 target
.xCallWrapped = function(fArg
, resultType
, argTypes
, ...args
){
2107 if(Array
.isArray(arguments
[3])) args
= arguments
[3];
2108 return target
.xWrap(fArg
, resultType
, argTypes
||[]).apply(null, args
||[]);
2112 This function is ONLY exposed in the public API to facilitate
2113 testing. It should not be used in application-level code, only
2116 Expects to be given (typeName, value) and returns a conversion
2117 of that value as has been registered using argAdapter().
2118 It throws if no adapter is found.
2120 ACHTUNG: the adapter may require that a scopedAllocPush() is
2121 active and it may allocate memory within that scope. It may also
2122 require additional arguments, depending on the type of
2125 target
.xWrap
.testConvertArg
= cache
.xWrap
.convertArg
;
2128 This function is ONLY exposed in the public API to facilitate
2129 testing. It should not be used in application-level code, only
2132 Expects to be given (typeName, value) and returns a conversion
2133 of that value as has been registered using resultAdapter().
2134 It throws if no adapter is found.
2136 ACHTUNG: the adapter may allocate memory which the caller may need
2137 to know how to free.
2139 target
.xWrap
.testConvertResult
= cache
.xWrap
.convertResult
;
2145 yawl (Yet Another Wasm Loader) provides very basic wasm loader.
2146 It requires a config object:
2148 - `uri`: required URI of the WASM file to load.
2150 - `onload(loadResult,config)`: optional callback. The first
2151 argument is the result object from
2152 WebAssembly.instantiate[Streaming](). The 2nd is the config
2153 object passed to this function. Described in more detail below.
2155 - `imports`: optional imports object for
2156 WebAssembly.instantiate[Streaming](). The default is an empty set
2157 of imports. If the module requires any imports, this object
2160 - `wasmUtilTarget`: optional object suitable for passing to
2161 WhWasmUtilInstaller(). If set, it gets passed to that function
2162 after the promise resolves. This function sets several properties
2163 on it before passing it on to that function (which sets many
2166 - `module`, `instance`: the properties from the
2167 instantiate[Streaming]() result.
2169 - If `instance.exports.memory` is _not_ set then it requires that
2170 `config.imports.env.memory` be set (else it throws), and
2171 assigns that to `target.memory`.
2173 - If `wasmUtilTarget.alloc` is not set and
2174 `instance.exports.malloc` is, it installs
2175 `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()`
2176 wrappers for the exports `malloc` and `free` functions.
2178 It returns a function which, when called, initiates loading of the
2179 module and returns a Promise. When that Promise resolves, it calls
2180 the `config.onload` callback (if set) and passes it
2181 `(loadResult,config)`, where `loadResult` is the result of
2182 WebAssembly.instantiate[Streaming](): an object in the form:
2186 module: a WebAssembly.Module,
2187 instance: a WebAssembly.Instance
2191 (Note that the initial `then()` attached to the promise gets only
2192 that object, and not the `config` one.)
2194 Error handling is up to the caller, who may attach a `catch()` call
2197 globalThis
.WhWasmUtilInstaller
.yawl = function(config
){
2198 const wfetch
= ()=>fetch(config
.uri
, {credentials
: 'same-origin'});
2200 const finalThen = function(arg
){
2201 //log("finalThen()",arg);
2202 if(config
.wasmUtilTarget
){
2203 const toss
= (...args
)=>{throw new Error(args
.join(' '))};
2204 const tgt
= config
.wasmUtilTarget
;
2205 tgt
.module
= arg
.module
;
2206 tgt
.instance
= arg
.instance
;
2207 //tgt.exports = tgt.instance.exports;
2208 if(!tgt
.instance
.exports
.memory
){
2210 WhWasmUtilInstaller requires either tgt.exports.memory
2211 (exported from WASM) or tgt.memory (JS-provided memory
2212 imported into WASM).
2214 tgt
.memory
= (config
.imports
&& config
.imports
.env
2215 && config
.imports
.env
.memory
)
2216 || toss("Missing 'memory' object!");
2218 if(!tgt
.alloc
&& arg
.instance
.exports
.malloc
){
2219 const exports
= arg
.instance
.exports
;
2220 tgt
.alloc = function(n
){
2221 return exports
.malloc(n
) || toss("Allocation of",n
,"bytes failed.");
2223 tgt
.dealloc = function(m
){exports
.free(m
)};
2227 if(config
.onload
) config
.onload(arg
,config
);
2228 return arg
/* for any then() handler attached to
2229 yetAnotherWasmLoader()'s return value */;
2231 const loadWasm
= WebAssembly
.instantiateStreaming
2232 ? function loadWasmStreaming(){
2233 return WebAssembly
.instantiateStreaming(wfetch(), config
.imports
||{})
2236 : function loadWasmOldSchool(){ // Safari < v15
2238 .then(response
=> response
.arrayBuffer())
2239 .then(bytes
=> WebAssembly
.instantiate(bytes
, config
.imports
||{}))
2243 }.bind(globalThis
.WhWasmUtilInstaller
)/*yawl()*/;