xref: /sqlite-3.40.0/ext/wasm/common/whwasmutil.js (revision 2cf599cf)
1/**
2  2022-07-08
3
4  The author disclaims copyright to this source code.  In place of a
5  legal notice, here is a blessing:
6
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.
10
11  ***********************************************************************
12
13  The whwasmutil is developed in conjunction with the Jaccwabyt
14  project:
15
16  https://fossil.wanderinghorse.net/r/jaccwabyt
17
18  Maintenance reminder: If you're reading this in a tree other than
19  the Jaccwabyt tree, note that this copy may be replaced with
20  upstream copies of that one from time to time. Thus the code
21  installed by this function "should not" be edited outside of that
22  project, else it risks getting overwritten.
23*/
24/**
25   This function is intended to simplify porting around various bits
26   of WASM-related utility code from project to project.
27
28   The primary goal of this code is to replace, where possible,
29   Emscripten-generated glue code with equivalent utility code which
30   can be used in arbitrary WASM environments built with toolchains
31   other than Emscripten. As of this writing, this code is capable of
32   acting as a replacement for Emscripten's generated glue code
33   _except_ that the latter installs handlers for Emscripten-provided
34   APIs such as its "FS" (virtual filesystem) API. Loading of such
35   things still requires using Emscripten's glue, but the post-load
36   utility APIs provided by this code are still usable as replacements
37   for their sub-optimally-documented Emscripten counterparts.
38
39   Intended usage:
40
41   ```
42   self.WhWasmUtilInstaller(appObject);
43   delete self.WhWasmUtilInstaller;
44   ```
45
46   Its global-scope symbol is intended only to provide an easy way to
47   make it available to 3rd-party scripts and "should" be deleted
48   after calling it. That symbols is _not_ used within the library.
49
50   Forewarning: this API explicitly targets only browser
51   environments. If a given non-browser environment has the
52   capabilities needed for a given feature (e.g. TextEncoder), great,
53   but it does not go out of its way to account for them and does not
54   provide compatibility crutches for them.
55
56   It currently offers alternatives to the following
57   Emscripten-generated APIs:
58
59   - OPTIONALLY memory allocation, but how this gets imported is
60     environment-specific.  Most of the following features only work
61     if allocation is available.
62
63   - WASM-exported "indirect function table" access and
64     manipulation. e.g.  creating new WASM-side functions using JS
65     functions, analog to Emscripten's addFunction() and
66     removeFunction() but slightly different.
67
68   - Get/set specific heap memory values, analog to Emscripten's
69     getValue() and setValue().
70
71   - String length counting in UTF-8 bytes (C-style and JS strings).
72
73   - JS string to C-string conversion and vice versa, analog to
74     Emscripten's stringToUTF8Array() and friends, but with slighter
75     different interfaces.
76
77   - JS string to Uint8Array conversion, noting that browsers actually
78     already have this built in via TextEncoder.
79
80   - "Scoped" allocation, such that allocations made inside of a given
81     explicit scope will be automatically cleaned up when the scope is
82     closed. This is fundamentally similar to Emscripten's
83     stackAlloc() and friends but uses the heap instead of the stack
84     because access to the stack requires C code.
85
86   - Create JS wrappers for WASM functions, analog to Emscripten's
87     ccall() and cwrap() functions, except that the automatic
88     conversions for function arguments and return values can be
89     easily customized by the client by assigning custom function
90     signature type names to conversion functions. Essentially,
91     it's ccall() and cwrap() on steroids.
92
93   How to install...
94
95   Passing an object to this function will install the functionality
96   into that object. Afterwards, client code "should" delete the global
97   symbol.
98
99   This code requires that the target object have the following
100   properties, noting that they needn't be available until the first
101   time one of the installed APIs is used (as opposed to when this
102   function is called) except where explicitly noted:
103
104   - `exports` must be a property of the target object OR a property
105     of `target.instance` (a WebAssembly.Module instance) and it must
106     contain the symbols exported by the WASM module associated with
107     this code. In an Enscripten environment it must be set to
108     `Module['asm']`. The exports object must contain a minimum of the
109     following symbols:
110
111     - `memory`: a WebAssembly.Memory object representing the WASM
112       memory. _Alternately_, the `memory` property can be set on the
113       target instance, in particular if the WASM heap memory is
114       initialized in JS an _imported_ into WASM, as opposed to being
115       initialized in WASM and exported to JS.
116
117     - `__indirect_function_table`: the WebAssembly.Table object which
118       holds WASM-exported functions. This API does not strictly
119       require that the table be able to grow but it will throw if its
120       `installFunction()` is called and the table cannot grow.
121
122   In order to simplify downstream usage, if `target.exports` is not
123   set when this is called then a property access interceptor
124   (read-only, configurable, enumerable) gets installed as `exports`
125   which resolves to `target.instance.exports`, noting that the latter
126   property need not exist until the first time `target.exports` is
127   accessed.
128
129   Some APIs _optionally_ make use of the `bigIntEnabled` property of
130   the target object. It "should" be set to true if the WASM
131   environment is compiled with BigInt support, else it must be
132   false. If it is false, certain BigInt-related features will trigger
133   an exception if invoked. This property, if not set when this is
134   called, will get a default value of true only if the BigInt64Array
135   constructor is available, else it will default to false.
136
137   Some optional APIs require that the target have the following
138   methods:
139
140   - 'alloc()` must behave like C's `malloc()`, allocating N bytes of
141     memory and returning its pointer. In Emscripten this is
142     conventionally made available via `Module['_malloc']`. This API
143     requires that the alloc routine throw on allocation error, as
144     opposed to returning null or 0.
145
146   - 'dealloc()` must behave like C's `free()`, accepting either a
147     pointer returned from its allocation counterpart or the values
148     null/0 (for which it must be a no-op). allocating N bytes of
149     memory and returning its pointer. In Emscripten this is
150     conventionally made available via `Module['_free']`.
151
152   APIs which require allocation routines are explicitly documented as
153   such and/or have "alloc" in their names.
154
155   This code is developed and maintained in conjunction with the
156   Jaccwabyt project:
157
158   https://fossil.wanderinghorse.net/r/jaccwabbyt
159
160   More specifically:
161
162   https://fossil.wanderinghorse.net/r/jaccwabbyt/file/common/whwasmutil.js
163*/
164self.WhWasmUtilInstaller = function(target){
165  'use strict';
166  if(undefined===target.bigIntEnabled){
167    target.bigIntEnabled = !!self['BigInt64Array'];
168  }
169
170  /** Throws a new Error, the message of which is the concatenation of
171      all args with a space between each. */
172  const toss = (...args)=>{throw new Error(args.join(' '))};
173
174  if(!target.exports){
175    Object.defineProperty(target, 'exports', {
176      enumerable: true, configurable: true,
177      get: ()=>(target.instance && target.instance.exports)
178    });
179  }
180
181  /*********
182    alloc()/dealloc() auto-install...
183
184    This would be convenient but it can also cause us to pick up
185    malloc() even when the client code is using a different exported
186    allocator (who, me?), which is bad. malloc() may be exported even
187    if we're not explicitly using it and overriding the malloc()
188    function, linking ours first, is not always feasible when using a
189    malloc() proxy, as it can lead to recursion and stack overflow
190    (who, me?). So... we really need the downstream code to set up
191    target.alloc/dealloc() itself.
192  ******/
193  /******
194  if(target.exports){
195    //Maybe auto-install alloc()/dealloc()...
196    if(!target.alloc && target.exports.malloc){
197      target.alloc = function(n){
198        const m = this(n);
199        return m || toss("Allocation of",n,"byte(s) failed.");
200      }.bind(target.exports.malloc);
201    }
202
203    if(!target.dealloc && target.exports.free){
204      target.dealloc = function(ptr){
205        if(ptr) this(ptr);
206      }.bind(target.exports.free);
207    }
208  }*******/
209
210  /**
211     Pointers in WASM are currently assumed to be 32-bit, but someday
212     that will certainly change.
213  */
214  const ptrIR = target.pointerIR || 'i32';
215  const ptrSizeof = ('i32'===ptrIR ? 4
216                     : ('i64'===ptrIR
217                        ? 8 : toss("Unhandled ptrSizeof:",ptrIR)));
218  /** Stores various cached state. */
219  const cache = Object.create(null);
220  /** Previously-recorded size of cache.memory.buffer, noted so that
221      we can recreate the view objects if the heap grows. */
222  cache.heapSize = 0;
223  /** WebAssembly.Memory object extracted from target.memory or
224      target.exports.memory the first time heapWrappers() is
225      called. */
226  cache.memory = null;
227  /** uninstallFunction() puts table indexes in here for reuse and
228      installFunction() extracts them. */
229  cache.freeFuncIndexes = [];
230  /**
231     Used by scopedAlloc() and friends.
232  */
233  cache.scopedAlloc = [];
234
235  cache.utf8Decoder = new TextDecoder();
236  cache.utf8Encoder = new TextEncoder('utf-8');
237
238  /**
239     If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
240     the heap has grown since the last call, updates cache.HEAPxyz.
241     Returns the cache object.
242  */
243  const heapWrappers = function(){
244    if(!cache.memory){
245      cache.memory = (target.memory instanceof WebAssembly.Memory)
246        ? target.memory : target.exports.memory;
247    }else if(cache.heapSize === cache.memory.buffer.byteLength){
248      return cache;
249    }
250    // heap is newly-acquired or has been resized....
251    const b = cache.memory.buffer;
252    cache.HEAP8 = new Int8Array(b); cache.HEAP8U = new Uint8Array(b);
253    cache.HEAP16 = new Int16Array(b); cache.HEAP16U = new Uint16Array(b);
254    cache.HEAP32 = new Int32Array(b); cache.HEAP32U = new Uint32Array(b);
255    if(target.bigIntEnabled){
256      cache.HEAP64 = new BigInt64Array(b); cache.HEAP64U = new BigUint64Array(b);
257    }
258    cache.HEAP32F = new Float32Array(b); cache.HEAP64F = new Float64Array(b);
259    cache.heapSize = b.byteLength;
260    return cache;
261  };
262
263  /** Convenience equivalent of this.heapForSize(8,false). */
264  target.heap8 = ()=>heapWrappers().HEAP8;
265
266  /** Convenience equivalent of this.heapForSize(8,true). */
267  target.heap8u = ()=>heapWrappers().HEAP8U;
268
269  /** Convenience equivalent of this.heapForSize(16,false). */
270  target.heap16 = ()=>heapWrappers().HEAP16;
271
272  /** Convenience equivalent of this.heapForSize(16,true). */
273  target.heap16u = ()=>heapWrappers().HEAP16U;
274
275  /** Convenience equivalent of this.heapForSize(32,false). */
276  target.heap32 = ()=>heapWrappers().HEAP32;
277
278  /** Convenience equivalent of this.heapForSize(32,true). */
279  target.heap32u = ()=>heapWrappers().HEAP32U;
280
281  /**
282     Requires n to be one of:
283
284     - integer 8, 16, or 32.
285     - A integer-type TypedArray constructor: Int8Array, Int16Array,
286     Int32Array, or their Uint counterparts.
287
288     If this.bigIntEnabled is true, it also accepts the value 64 or a
289     BigInt64Array/BigUint64Array, else it throws if passed 64 or one
290     of those constructors.
291
292     Returns an integer-based TypedArray view of the WASM heap
293     memory buffer associated with the given block size. If passed
294     an integer as the first argument and unsigned is truthy then
295     the "U" (unsigned) variant of that view is returned, else the
296     signed variant is returned. If passed a TypedArray value, the
297     2nd argument is ignores. Note that Float32Array and
298     Float64Array views are not supported by this function.
299
300     Note that growth of the heap will invalidate any references to
301     this heap, so do not hold a reference longer than needed and do
302     not use a reference after any operation which may
303     allocate. Instead, re-fetch the reference by calling this
304     function again.
305
306     Throws if passed an invalid n.
307
308     Pedantic side note: the name "heap" is a bit of a misnomer. In an
309     Emscripten environment, the memory managed via the stack
310     allocation API is in the same Memory object as the heap (which
311     makes sense because otherwise arbitrary pointer X would be
312     ambiguous: is it in the heap or the stack?).
313  */
314  target.heapForSize = function(n,unsigned = false){
315    let ctor;
316    const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
317          ? cache : heapWrappers();
318    switch(n){
319        case Int8Array: return c.HEAP8; case Uint8Array: return c.HEAP8U;
320        case Int16Array: return c.HEAP16; case Uint16Array: return c.HEAP16U;
321        case Int32Array: return c.HEAP32; case Uint32Array: return c.HEAP32U;
322        case 8:  return unsigned ? c.HEAP8U : c.HEAP8;
323        case 16: return unsigned ? c.HEAP16U : c.HEAP16;
324        case 32: return unsigned ? c.HEAP32U : c.HEAP32;
325        case 64:
326          if(c.HEAP64) return unsigned ? c.HEAP64U : c.HEAP64;
327          break;
328        default:
329          if(this.bigIntEnabled){
330            if(n===self['BigUint64Array']) return c.HEAP64U;
331            else if(n===self['BigInt64Array']) return c.HEAP64;
332            break;
333          }
334    }
335    toss("Invalid heapForSize() size: expecting 8, 16, 32,",
336         "or (if BigInt is enabled) 64.");
337  }.bind(target);
338
339  /**
340     Returns the WASM-exported "indirect function table."
341  */
342  target.functionTable = function(){
343    return target.exports.__indirect_function_table;
344    /** -----------------^^^^^ "seems" to be a standardized export name.
345        From Emscripten release notes from 2020-09-10:
346        - Use `__indirect_function_table` as the import name for the
347        table, which is what LLVM does.
348    */
349  }.bind(target);
350
351  /**
352     Given a function pointer, returns the WASM function table entry
353     if found, else returns a falsy value.
354  */
355  target.functionEntry = function(fptr){
356    const ft = this.functionTable();
357    return fptr < ft.length ? ft.get(fptr) : undefined;
358  }.bind(target);
359
360  /**
361     Creates a WASM function which wraps the given JS function and
362     returns the JS binding of that WASM function. The signature
363     argument must be the Jaccwabyt-format or Emscripten
364     addFunction()-format function signature string. In short: in may
365     have one of the following formats:
366
367     - Emscripten: `x...`, where the first x is a letter representing
368       the result type and subsequent letters represent the argument
369       types. See below.
370
371     - Jaccwabyt: `x(...)` where `x` is the letter representing the
372       result type and letters in the parens (if any) represent the
373       argument types. See below.
374
375     Supported letters:
376
377     - `i` = int32
378     - `p` = int32 ("pointer")
379     - `j` = int64
380     - `f` = float32
381     - `d` = float64
382     - `v` = void, only legal for use as the result type
383
384     It throws if an invalid signature letter is used.
385
386     Jaccwabyt-format signatures support some additional letters which
387     have no special meaning here but (in this context) act as aliases
388     for other letters:
389
390     - `s`, `P`: same as `p`
391
392     Sidebar: this code is developed together with Jaccwabyt, thus the
393     support for its signature format.
394  */
395  target.jsFuncToWasm = function f(func, sig){
396    /** Attribution: adapted up from Emscripten-generated glue code,
397        refactored primarily for efficiency's sake, eliminating
398        call-local functions and superfluous temporary arrays. */
399    if(!f._){/*static init...*/
400      f._ = {
401        // Map of signature letters to type IR values
402        sigTypes: Object.create(null),
403        // Map of type IR values to WASM type code values
404        typeCodes: Object.create(null),
405        /** Encodes n, which must be <2^14 (16384), into target array
406            tgt, as a little-endian value, using the given method
407            ('push' or 'unshift'). */
408        uleb128Encode: function(tgt, method, n){
409          if(n<128) tgt[method](n);
410          else tgt[method]( (n % 128) | 128, n>>7);
411        },
412        /** Intentionally-lax pattern for Jaccwabyt-format function
413            pointer signatures, the intent of which is simply to
414            distinguish them from Emscripten-format signatures. The
415            downstream checks are less lax. */
416        rxJSig: /^(\w)\((\w*)\)$/,
417        /** Returns the parameter-value part of the given signature
418            string. */
419        sigParams: function(sig){
420          const m = f._.rxJSig.exec(sig);
421          return m ? m[2] : sig.substr(1);
422        },
423        /** Returns the IR value for the given letter or throws
424            if the letter is invalid. */
425        letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x),
426        /** Returns an object describing the result type and parameter
427            type(s) of the given function signature, or throws if the
428            signature is invalid. */
429        /******** // only valid for use with the WebAssembly.Function ctor, which
430                  // is not yet documented on MDN.
431        sigToWasm: function(sig){
432          const rc = {parameters:[], results: []};
433          if('v'!==sig[0]) rc.results.push(f._.letterType(sig[0]));
434          for(const x of f._.sigParams(sig)){
435            rc.parameters.push(f._.letterType(x));
436          }
437          return rc;
438        },************/
439        /** Pushes the WASM data type code for the given signature
440            letter to the given target array. Throws if letter is
441            invalid. */
442        pushSigType: (dest, letter)=>dest.push(f._.typeCodes[f._.letterType(letter)])
443      };
444      f._.sigTypes.i = f._.sigTypes.p = f._.sigTypes.P = f._.sigTypes.s = 'i32';
445      f._.sigTypes.j = 'i64'; f._.sigTypes.f = 'f32'; f._.sigTypes.d = 'f64';
446      f._.typeCodes['i32'] = 0x7f; f._.typeCodes['i64'] = 0x7e;
447      f._.typeCodes['f32'] = 0x7d; f._.typeCodes['f64'] = 0x7c;
448    }/*static init*/
449    const sigParams = f._.sigParams(sig);
450    const wasmCode = [0x01/*count: 1*/, 0x60/*function*/];
451    f._.uleb128Encode(wasmCode, 'push', sigParams.length);
452    for(const x of sigParams) f._.pushSigType(wasmCode, x);
453    if('v'===sig[0]) wasmCode.push(0);
454    else{
455      wasmCode.push(1);
456      f._.pushSigType(wasmCode, sig[0]);
457    }
458    f._.uleb128Encode(wasmCode, 'unshift', wasmCode.length)/* type section length */;
459    wasmCode.unshift(
460      0x00, 0x61, 0x73, 0x6d, /* magic: "\0asm" */
461      0x01, 0x00, 0x00, 0x00, /* version: 1 */
462      0x01 /* type section code */
463    );
464    wasmCode.push(
465      /* import section: */ 0x02, 0x07,
466      /* (import "e" "f" (func 0 (type 0))): */
467      0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
468      /* export section: */ 0x07, 0x05,
469      /* (export "f" (func 0 (type 0))): */
470      0x01, 0x01, 0x66, 0x00, 0x00
471    );
472    return (new WebAssembly.Instance(
473      new WebAssembly.Module(new Uint8Array(wasmCode)), {
474        e: { f: func }
475      })).exports['f'];
476  }/*jsFuncToWasm()*/;
477
478  /**
479     Expects a JS function and signature, exactly as for
480     this.jsFuncToWasm(). It uses that function to create a
481     WASM-exported function, installs that function to the next
482     available slot of this.functionTable(), and returns the
483     function's index in that table (which acts as a pointer to that
484     function). The returned pointer can be passed to
485     removeFunction() to uninstall it and free up the table slot for
486     reuse.
487
488     As a special case, if the passed-in function is a WASM-exported
489     function then the signature argument is ignored and func is
490     installed as-is, without requiring re-compilation/re-wrapping.
491
492     This function will propagate an exception if
493     WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
494     The former case can happen in an Emscripten-compiled
495     environment when building without Emscripten's
496     `-sALLOW_TABLE_GROWTH` flag.
497
498     Sidebar: this function differs from Emscripten's addFunction()
499     _primarily_ in that it does not share that function's
500     undocumented behavior of reusing a function if it's passed to
501     addFunction() more than once, which leads to removeFunction()
502     breaking clients which do not take care to avoid that case:
503
504     https://github.com/emscripten-core/emscripten/issues/17323
505  */
506  target.installFunction = function f(func, sig){
507    const ft = this.functionTable();
508    const oldLen = ft.length;
509    let ptr;
510    while(cache.freeFuncIndexes.length){
511      ptr = cache.freeFuncIndexes.pop();
512      if(ft.get(ptr)){ /* Table was modified via a different API */
513        ptr = null;
514        continue;
515      }else{
516        break;
517      }
518    }
519    if(!ptr){
520      ptr = oldLen;
521      ft.grow(1);
522    }
523    try{
524      /*this will only work if func is a WASM-exported function*/
525      ft.set(ptr, func);
526      return ptr;
527    }catch(e){
528      if(!(e instanceof TypeError)){
529        if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
530        throw e;
531      }
532    }
533    // It's not a WASM-exported function, so compile one...
534    try {
535      ft.set(ptr, this.jsFuncToWasm(func, sig));
536    }catch(e){
537      if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
538      throw e;
539    }
540    return ptr;
541  }.bind(target);
542
543  /**
544     Requires a pointer value previously returned from
545     this.installFunction(). Removes that function from the WASM
546     function table, marks its table slot as free for re-use, and
547     returns that function. It is illegal to call this before
548     installFunction() has been called and results are undefined if
549     ptr was not returned by that function. The returned function
550     may be passed back to installFunction() to reinstall it.
551  */
552  target.uninstallFunction = function(ptr){
553    const fi = cache.freeFuncIndexes;
554    const ft = this.functionTable();
555    fi.push(ptr);
556    const rc = ft.get(ptr);
557    ft.set(ptr, null);
558    return rc;
559  }.bind(target);
560
561  /**
562     Given a WASM heap memory address and a data type name in the form
563     (i8, i16, i32, i64, float (or f32), double (or f64)), this
564     fetches the numeric value from that address and returns it as a
565     number or, for the case of type='i64', a BigInt (noting that that
566     type triggers an exception if this.bigIntEnabled is
567     falsy). Throws if given an invalid type.
568
569     As a special case, if type ends with a `*`, it is considered to
570     be a pointer type and is treated as the WASM numeric type
571     appropriate for the pointer size (`i32`).
572
573     While likely not obvious, this routine and its setMemValue()
574     counterpart are how pointer-to-value _output_ parameters
575     in WASM-compiled C code can be interacted with:
576
577     ```
578     const ptr = alloc(4);
579     setMemValue(ptr, 0, 'i32'); // clear the ptr's value
580     aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x);
581     const result = getMemValue(ptr, 'i32'); // fetch ptr's value
582     dealloc(ptr);
583     ```
584
585     scopedAlloc() and friends can be used to make handling of
586     `ptr` safe against leaks in the case of an exception:
587
588     ```
589     let result;
590     const scope = scopedAllocPush();
591     try{
592       const ptr = scopedAlloc(4);
593       setMemValue(ptr, 0, 'i32');
594       aCFuncWithOutputPtrArg( ptr );
595       result = getMemValue(ptr, 'i32');
596     }finally{
597       scopedAllocPop(scope);
598     }
599     ```
600
601     As a rule setMemValue() must be called to set (typically zero
602     out) the pointer's value, else it will contain an essentially
603     random value.
604
605     See: setMemValue()
606  */
607  target.getMemValue = function(ptr, type='i8'){
608    if(type.endsWith('*')) type = ptrIR;
609    const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
610          ? cache : heapWrappers();
611    switch(type){
612        case 'i1':
613        case 'i8': return c.HEAP8[ptr>>0];
614        case 'i16': return c.HEAP16[ptr>>1];
615        case 'i32': return c.HEAP32[ptr>>2];
616        case 'i64':
617          if(this.bigIntEnabled) return BigInt(c.HEAP64[ptr>>3]);
618          break;
619        case 'float': case 'f32': return c.HEAP32F[ptr>>2];
620        case 'double': case 'f64': return Number(c.HEAP64F[ptr>>3]);
621        default: break;
622    }
623    toss('Invalid type for getMemValue():',type);
624  }.bind(target);
625
626  /**
627     The counterpart of getMemValue(), this sets a numeric value at
628     the given WASM heap address, using the type to define how many
629     bytes are written. Throws if given an invalid type. See
630     getMemValue() for details about the type argument. If the 3rd
631     argument ends with `*` then it is treated as a pointer type and
632     this function behaves as if the 3rd argument were `i32`.
633
634     This function returns itself.
635  */
636  target.setMemValue = function f(ptr, value, type='i8'){
637    if (type.endsWith('*')) type = ptrIR;
638    const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
639          ? cache : heapWrappers();
640    switch (type) {
641        case 'i1':
642        case 'i8': c.HEAP8[ptr>>0] = value; return f;
643        case 'i16': c.HEAP16[ptr>>1] = value; return f;
644        case 'i32': c.HEAP32[ptr>>2] = value; return f;
645        case 'i64':
646          if(c.HEAP64){
647            c.HEAP64[ptr>>3] = BigInt(value);
648            return f;
649          }
650          break;
651        case 'float': case 'f32': c.HEAP32F[ptr>>2] = value; return f;
652        case 'double': case 'f64': c.HEAP64F[ptr>>3] = value; return f;
653    }
654    toss('Invalid type for setMemValue(): ' + type);
655  };
656
657  /**
658     Expects ptr to be a pointer into the WASM heap memory which
659     refers to a NUL-terminated C-style string encoded as UTF-8.
660     Returns the length, in bytes, of the string, as for `strlen(3)`.
661     As a special case, if !ptr then it it returns `null`. Throws if
662     ptr is out of range for target.heap8u().
663  */
664  target.cstrlen = function(ptr){
665    if(!ptr) return null;
666    const h = heapWrappers().HEAP8U;
667    let pos = ptr;
668    for( ; h[pos] !== 0; ++pos ){}
669    return pos - ptr;
670  };
671
672  /** Internal helper to use in operations which need to distinguish
673      between SharedArrayBuffer heap memory and non-shared heap. */
674  const __SAB = ('undefined'===typeof SharedArrayBuffer)
675        ? function(){} : SharedArrayBuffer;
676  const __utf8Decode = function(arrayBuffer, begin, end){
677    return cache.utf8Decoder.decode(
678      (arrayBuffer.buffer instanceof __SAB)
679        ? arrayBuffer.slice(begin, end)
680        : arrayBuffer.subarray(begin, end)
681    );
682  };
683
684  /**
685     Expects ptr to be a pointer into the WASM heap memory which
686     refers to a NUL-terminated C-style string encoded as UTF-8. This
687     function counts its byte length using cstrlen() then returns a
688     JS-format string representing its contents. As a special case, if
689     ptr is falsy, `null` is returned.
690  */
691  target.cstringToJs = function(ptr){
692    const n = this.cstrlen(ptr);
693    return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : "");
694  }.bind(target);
695
696  /**
697     Given a JS string, this function returns its UTF-8 length in
698     bytes. Returns null if str is not a string.
699  */
700  target.jstrlen = function(str){
701    /** Attribution: derived from Emscripten's lengthBytesUTF8() */
702    if('string'!==typeof str) return null;
703    const n = str.length;
704    let len = 0;
705    for(let i = 0; i < n; ++i){
706      let u = str.charCodeAt(i);
707      if(u>=0xd800 && u<=0xdfff){
708        u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
709      }
710      if(u<=0x7f) ++len;
711      else if(u<=0x7ff) len += 2;
712      else if(u<=0xffff) len += 3;
713      else len += 4;
714    }
715    return len;
716  };
717
718  /**
719     Encodes the given JS string as UTF8 into the given TypedArray
720     tgt, starting at the given offset and writing, at most, maxBytes
721     bytes (including the NUL terminator if addNul is true, else no
722     NUL is added). If it writes any bytes at all and addNul is true,
723     it always NUL-terminates the output, even if doing so means that
724     the NUL byte is all that it writes.
725
726     If maxBytes is negative (the default) then it is treated as the
727     remaining length of tgt, starting at the given offset.
728
729     If writing the last character would surpass the maxBytes count
730     because the character is multi-byte, that character will not be
731     written (as opposed to writing a truncated multi-byte character).
732     This can lead to it writing as many as 3 fewer bytes than
733     maxBytes specifies.
734
735     Returns the number of bytes written to the target, _including_
736     the NUL terminator (if any). If it returns 0, it wrote nothing at
737     all, which can happen if:
738
739     - str is empty and addNul is false.
740     - offset < 0.
741     - maxBytes == 0.
742     - maxBytes is less than the byte length of a multi-byte str[0].
743
744     Throws if tgt is not an Int8Array or Uint8Array.
745
746     Design notes:
747
748     - In C's strcpy(), the destination pointer is the first
749       argument. That is not the case here primarily because the 3rd+
750       arguments are all referring to the destination, so it seems to
751       make sense to have them grouped with it.
752
753     - Emscripten's counterpart of this function (stringToUTF8Array())
754       returns the number of bytes written sans NUL terminator. That
755       is, however, ambiguous: str.length===0 or maxBytes===(0 or 1)
756       all cause 0 to be returned.
757  */
758  target.jstrcpy = function(jstr, tgt, offset = 0, maxBytes = -1, addNul = true){
759    /** Attribution: the encoding bits are taken from Emscripten's
760        stringToUTF8Array(). */
761    if(!tgt || (!(tgt instanceof Int8Array) && !(tgt instanceof Uint8Array))){
762      toss("jstrcpy() target must be an Int8Array or Uint8Array.");
763    }
764    if(maxBytes<0) maxBytes = tgt.length - offset;
765    if(!(maxBytes>0) || !(offset>=0)) return 0;
766    let i = 0, max = jstr.length;
767    const begin = offset, end = offset + maxBytes - (addNul ? 1 : 0);
768    for(; i < max && offset < end; ++i){
769      let u = jstr.charCodeAt(i);
770      if(u>=0xd800 && u<=0xdfff){
771        u = 0x10000 + ((u & 0x3FF) << 10) | (jstr.charCodeAt(++i) & 0x3FF);
772      }
773      if(u<=0x7f){
774        if(offset >= end) break;
775        tgt[offset++] = u;
776      }else if(u<=0x7ff){
777        if(offset + 1 >= end) break;
778        tgt[offset++] = 0xC0 | (u >> 6);
779        tgt[offset++] = 0x80 | (u & 0x3f);
780      }else if(u<=0xffff){
781        if(offset + 2 >= end) break;
782        tgt[offset++] = 0xe0 | (u >> 12);
783        tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
784        tgt[offset++] = 0x80 | (u & 0x3f);
785      }else{
786        if(offset + 3 >= end) break;
787        tgt[offset++] = 0xf0 | (u >> 18);
788        tgt[offset++] = 0x80 | ((u >> 12) & 0x3f);
789        tgt[offset++] = 0x80 | ((u >> 6) & 0x3f);
790        tgt[offset++] = 0x80 | (u & 0x3f);
791      }
792    }
793    if(addNul) tgt[offset++] = 0;
794    return offset - begin;
795  };
796
797  /**
798     Works similarly to C's strncpy(), copying, at most, n bytes (not
799     characters) from srcPtr to tgtPtr. It copies until n bytes have
800     been copied or a 0 byte is reached in src. _Unlike_ strncpy(), it
801     returns the number of bytes it assigns in tgtPtr, _including_ the
802     NUL byte (if any). If n is reached before a NUL byte in srcPtr,
803     tgtPtr will _not_ be NULL-terminated. If a NUL byte is reached
804     before n bytes are copied, tgtPtr will be NUL-terminated.
805
806     If n is negative, cstrlen(srcPtr)+1 is used to calculate it, the
807     +1 being for the NUL byte.
808
809     Throws if tgtPtr or srcPtr are falsy. Results are undefined if:
810
811     - either is not a pointer into the WASM heap or
812
813     - srcPtr is not NUL-terminated AND n is less than srcPtr's
814       logical length.
815
816     ACHTUNG: it is possible to copy partial multi-byte characters
817     this way, and converting such strings back to JS strings will
818     have undefined results.
819  */
820  target.cstrncpy = function(tgtPtr, srcPtr, n){
821    if(!tgtPtr || !srcPtr) toss("cstrncpy() does not accept NULL strings.");
822    if(n<0) n = this.cstrlen(strPtr)+1;
823    else if(!(n>0)) return 0;
824    const heap = this.heap8u();
825    let i = 0, ch;
826    for(; i < n && (ch = heap[srcPtr+i]); ++i){
827      heap[tgtPtr+i] = ch;
828    }
829    if(i<n) heap[tgtPtr + i++] = 0;
830    return i;
831  }.bind(target);
832
833  /**
834     For the given JS string, returns a Uint8Array of its contents
835     encoded as UTF-8. If addNul is true, the returned array will have
836     a trailing 0 entry, else it will not.
837  */
838  target.jstrToUintArray = (str, addNul=false)=>{
839    return cache.utf8Encoder.encode(addNul ? (str+"\0") : str);
840    // Or the hard way...
841    /** Attribution: derived from Emscripten's stringToUTF8Array() */
842    //const a = [], max = str.length;
843    //let i = 0, pos = 0;
844    //for(; i < max; ++i){
845    //  let u = str.charCodeAt(i);
846    //  if(u>=0xd800 && u<=0xdfff){
847    //    u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
848    //  }
849    //  if(u<=0x7f) a[pos++] = u;
850    //  else if(u<=0x7ff){
851    //    a[pos++] = 0xC0 | (u >> 6);
852    //    a[pos++] = 0x80 | (u & 63);
853    //  }else if(u<=0xffff){
854    //    a[pos++] = 0xe0 | (u >> 12);
855    //    a[pos++] = 0x80 | ((u >> 6) & 63);
856    //    a[pos++] = 0x80 | (u & 63);
857    //  }else{
858    //    a[pos++] = 0xf0 | (u >> 18);
859    //    a[pos++] = 0x80 | ((u >> 12) & 63);
860    //    a[pos++] = 0x80 | ((u >> 6) & 63);
861    //    a[pos++] = 0x80 | (u & 63);
862    //  }
863    // }
864    // return new Uint8Array(a);
865  };
866
867  const __affirmAlloc = (obj,funcName)=>{
868    if(!(obj.alloc instanceof Function) ||
869       !(obj.dealloc instanceof Function)){
870      toss("Object is missing alloc() and/or dealloc() function(s)",
871           "required by",funcName+"().");
872    }
873  };
874
875  const __allocCStr = function(jstr, returnWithLength, allocator, funcName){
876    __affirmAlloc(this, funcName);
877    if('string'!==typeof jstr) return null;
878    const n = this.jstrlen(jstr),
879          ptr = allocator(n+1);
880    this.jstrcpy(jstr, this.heap8u(), ptr, n+1, true);
881    return returnWithLength ? [ptr, n] : ptr;
882  }.bind(target);
883
884  /**
885     Uses target.alloc() to allocate enough memory for jstrlen(jstr)+1
886     bytes of memory, copies jstr to that memory using jstrcpy(),
887     NUL-terminates it, and returns the pointer to that C-string.
888     Ownership of the pointer is transfered to the caller, who must
889     eventually pass the pointer to dealloc() to free it.
890
891     If passed a truthy 2nd argument then its return semantics change:
892     it returns [ptr,n], where ptr is the C-string's pointer and n is
893     its cstrlen().
894
895     Throws if `target.alloc` or `target.dealloc` are not functions.
896  */
897  target.allocCString =
898    (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
899                                                target.alloc, 'allocCString()');
900
901  /**
902     Starts an "allocation scope." All allocations made using
903     scopedAlloc() are recorded in this scope and are freed when the
904     value returned from this function is passed to
905     scopedAllocPop().
906
907     This family of functions requires that the API's object have both
908     `alloc()` and `dealloc()` methods, else this function will throw.
909
910     Intended usage:
911
912     ```
913     const scope = scopedAllocPush();
914     try {
915       const ptr1 = scopedAlloc(100);
916       const ptr2 = scopedAlloc(200);
917       const ptr3 = scopedAlloc(300);
918       ...
919       // Note that only allocations made via scopedAlloc()
920       // are managed by this allocation scope.
921     }finally{
922       scopedAllocPop(scope);
923     }
924     ```
925
926     The value returned by this function must be treated as opaque by
927     the caller, suitable _only_ for passing to scopedAllocPop().
928     Its type and value are not part of this function's API and may
929     change in any given version of this code.
930
931     `scopedAlloc.level` can be used to determine how many scoped
932     alloc levels are currently active.
933   */
934  target.scopedAllocPush = function(){
935    __affirmAlloc(this, 'scopedAllocPush');
936    const a = [];
937    cache.scopedAlloc.push(a);
938    return a;
939  }.bind(target);
940
941  /**
942     Cleans up all allocations made using scopedAlloc() in the context
943     of the given opaque state object, which must be a value returned
944     by scopedAllocPush(). See that function for an example of how to
945     use this function.
946
947     Though scoped allocations are managed like a stack, this API
948     behaves properly if allocation scopes are popped in an order
949     other than the order they were pushed.
950
951     If called with no arguments, it pops the most recent
952     scopedAllocPush() result:
953
954     ```
955     scopedAllocPush();
956     try{ ... } finally { scopedAllocPop(); }
957     ```
958
959     It's generally recommended that it be passed an explicit argument
960     to help ensure that push/push are used in matching pairs, but in
961     trivial code that may be a non-issue.
962  */
963  target.scopedAllocPop = function(state){
964    __affirmAlloc(this, 'scopedAllocPop');
965    const n = arguments.length
966          ? cache.scopedAlloc.indexOf(state)
967          : cache.scopedAlloc.length-1;
968    if(n<0) toss("Invalid state object for scopedAllocPop().");
969    if(0===arguments.length) state = cache.scopedAlloc[n];
970    cache.scopedAlloc.splice(n,1);
971    for(let p; (p = state.pop()); ) this.dealloc(p);
972  }.bind(target);
973
974  /**
975     Allocates n bytes of memory using this.alloc() and records that
976     fact in the state for the most recent call of scopedAllocPush().
977     Ownership of the memory is given to scopedAllocPop(), which
978     will clean it up when it is called. The memory _must not_ be
979     passed to this.dealloc(). Throws if this API object is missing
980     the required `alloc()` or `dealloc()` functions or no scoped
981     alloc is active.
982
983     See scopedAllocPush() for an example of how to use this function.
984
985     The `level` property of this function can be queried to query how
986     many scoped allocation levels are currently active.
987
988     See also: scopedAllocPtr(), scopedAllocCString()
989  */
990  target.scopedAlloc = function(n){
991    if(!cache.scopedAlloc.length){
992      toss("No scopedAllocPush() scope is active.");
993    }
994    const p = this.alloc(n);
995    cache.scopedAlloc[cache.scopedAlloc.length-1].push(p);
996    return p;
997  }.bind(target);
998
999  Object.defineProperty(target.scopedAlloc, 'level', {
1000    configurable: false, enumerable: false,
1001    get: ()=>cache.scopedAlloc.length,
1002    set: ()=>toss("The 'active' property is read-only.")
1003  });
1004
1005  /**
1006     Works identically to allocCString() except that it allocates the
1007     memory using scopedAlloc().
1008
1009     Will throw if no scopedAllocPush() call is active.
1010  */
1011  target.scopedAllocCString =
1012    (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
1013                                                target.scopedAlloc, 'scopedAllocCString()');
1014
1015  /**
1016     Wraps function call func() in a scopedAllocPush() and
1017     scopedAllocPop() block, such that all calls to scopedAlloc() and
1018     friends from within that call will have their memory freed
1019     automatically when func() returns. If func throws or propagates
1020     an exception, the scope is still popped, otherwise it returns the
1021     result of calling func().
1022  */
1023  target.scopedAllocCall = function(func){
1024    this.scopedAllocPush();
1025    try{ return func() } finally{ this.scopedAllocPop() }
1026  }.bind(target);
1027
1028  /** Internal impl for allocPtr() and scopedAllocPtr(). */
1029  const __allocPtr = function(howMany, method){
1030    __affirmAlloc(this, method);
1031    let m = this[method](howMany * ptrSizeof);
1032    this.setMemValue(m, 0, ptrIR)
1033    if(1===howMany){
1034      return m;
1035    }
1036    const a = [m];
1037    for(let i = 1; i < howMany; ++i){
1038      m += ptrSizeof;
1039      a[i] = m;
1040      this.setMemValue(m, 0, ptrIR);
1041    }
1042    return a;
1043  }.bind(target);
1044
1045  /**
1046     Allocates a single chunk of memory capable of holding `howMany`
1047     pointers and zeroes them out. If `howMany` is 1 then the memory
1048     chunk is returned directly, else an array of pointer addresses is
1049     returned, which can optionally be used with "destructuring
1050     assignment" like this:
1051
1052     ```
1053     const [p1, p2, p3] = allocPtr(3);
1054     ```
1055
1056     ACHTUNG: when freeing the memory, pass only the _first_ result
1057     value to dealloc(). The others are part of the same memory chunk
1058     and must not be freed separately.
1059  */
1060  target.allocPtr = (howMany=1)=>__allocPtr(howMany, 'alloc');
1061
1062  /**
1063     Identical to allocPtr() except that it allocates using scopedAlloc()
1064     instead of alloc().
1065  */
1066  target.scopedAllocPtr = (howMany=1)=>__allocPtr(howMany, 'scopedAlloc');
1067
1068  /**
1069     If target.exports[name] exists, it is returned, else an
1070     exception is thrown.
1071  */
1072  target.xGet = function(name){
1073    return target.exports[name] || toss("Cannot find exported symbol:",name);
1074  };
1075
1076  const __argcMismatch =
1077        (f,n)=>toss(f+"() requires",n,"argument(s).");
1078
1079  /**
1080     Looks up a WASM-exported function named fname from
1081     target.exports. If found, it is called, passed all remaining
1082     arguments, and its return value is returned to xCall's caller. If
1083     not found, an exception is thrown. This function does no
1084     conversion of argument or return types, but see xWrap() and
1085     xCallWrapped() for variants which do.
1086
1087     As a special case, if passed only 1 argument after the name and
1088     that argument in an Array, that array's entries become the
1089     function arguments. (This is not an ambiguous case because it's
1090     not legal to pass an Array object to a WASM function.)
1091  */
1092  target.xCall = function(fname, ...args){
1093    const f = this.xGet(fname);
1094    if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function.");
1095    if(f.length!==args.length) __argcMismatch(fname,f.length)
1096    /* This is arguably over-pedantic but we want to help clients keep
1097       from shooting themselves in the foot when calling C APIs. */;
1098    return (2===arguments.length && Array.isArray(arguments[1]))
1099      ? f.apply(null, arguments[1])
1100      : f.apply(null, args);
1101  }.bind(target);
1102
1103  /**
1104     State for use with xWrap()
1105  */
1106  cache.xWrap = Object.create(null);
1107  const xcv = cache.xWrap.convert = Object.create(null);
1108  /** Map of type names to argument conversion functions. */
1109  cache.xWrap.convert.arg = Object.create(null);
1110  /** Map of type names to return result conversion functions. */
1111  cache.xWrap.convert.result = Object.create(null);
1112
1113  xcv.arg.i64 = (i)=>BigInt(i);
1114  xcv.arg.i32 = (i)=>(i | 0);
1115  xcv.arg.i16 = (i)=>((i | 0) & 0xFFFF);
1116  xcv.arg.i8  = (i)=>((i | 0) & 0xFF);
1117  xcv.arg.f32 = xcv.arg.float = (i)=>Number(i).valueOf();
1118  xcv.arg.f64 = xcv.arg.double = xcv.arg.f32;
1119  xcv.arg.int = xcv.arg.i32;
1120  xcv.result['*'] = xcv.result['pointer'] = xcv.arg[ptrIR];
1121
1122  for(const t of ['i8', 'i16', 'i32', 'int', 'i64',
1123                  'f32', 'float', 'f64', 'double']){
1124    xcv.arg[t+'*'] = xcv.result[t+'*'] = xcv.arg[ptrIR]
1125    xcv.result[t] = xcv.arg[t] || toss("Missing arg converter:",t);
1126  }
1127  xcv.arg['**'] = xcv.arg[ptrIR];
1128
1129  /**
1130     In order for args of type string to work in various contexts in
1131     the sqlite3 API, we need to pass them on as, variably, a C-string
1132     or a pointer value. Thus for ARGs of type 'string' and
1133     '*'/'pointer' we behave differently depending on whether the
1134     argument is a string or not:
1135
1136     - If v is a string, scopeAlloc() a new C-string from it and return
1137       that temp string's pointer.
1138
1139     - Else return the value from the arg adaptor defined for ptrIR.
1140
1141     TODO? Permit an Int8Array/Uint8Array and convert it to a string?
1142     Would that be too much magic concentrated in one place, ready to
1143     backfire?
1144  */
1145  xcv.arg.string = xcv.arg['pointer'] = xcv.arg['*'] = function(v){
1146    if('string'===typeof v) return target.scopedAllocCString(v);
1147    return v ? xcv.arg[ptrIR](v) : null;
1148  };
1149  xcv.result.string = (i)=>target.cstringToJs(i);
1150  xcv.result['string:free'] = function(i){
1151    try { return i ? target.cstringToJs(i) : null }
1152    finally{ target.dealloc(i) }
1153  };
1154  xcv.result.json = (i)=>JSON.parse(target.cstringToJs(i));
1155  xcv.result['json:free'] = function(i){
1156    try{ return i ? JSON.parse(target.cstringToJs(i)) : null }
1157    finally{ target.dealloc(i) }
1158  }
1159  xcv.result['void'] = (v)=>undefined;
1160  xcv.result['null'] = (v)=>v;
1161
1162  if(0){
1163    /***
1164        This idea can't currently work because we don't know the
1165        signature for the func and don't have a way for the user to
1166        convey it. To do this we likely need to be able to match
1167        arg/result handlers by a regex, but that would incur an O(N)
1168        cost as we check the regex one at a time. Another use case for
1169        such a thing would be pseudotypes like "int:-1" to say that
1170        the value will always be treated like -1 (which has a useful
1171        case in the sqlite3 bindings).
1172    */
1173    xcv.arg['func-ptr'] = function(v){
1174      if(!(v instanceof Function)) return xcv.arg[ptrIR];
1175      const f = this.jsFuncToWasm(v, WHAT_SIGNATURE);
1176    }.bind(target);
1177  }
1178
1179  const __xArgAdapter =
1180        (t)=>xcv.arg[t] || toss("Argument adapter not found:",t);
1181
1182  const __xResultAdapter =
1183        (t)=>xcv.result[t] || toss("Result adapter not found:",t);
1184
1185  cache.xWrap.convertArg = (t,v)=>__xArgAdapter(t)(v);
1186  cache.xWrap.convertResult =
1187    (t,v)=>(null===t ? v : (t ? __xResultAdapter(t)(v) : undefined));
1188
1189  /**
1190     Creates a wrapper for the WASM-exported function fname. Uses
1191     xGet() to fetch the exported function (which throws on
1192     error) and returns either that function or a wrapper for that
1193     function which converts the JS-side argument types into WASM-side
1194     types and converts the result type. If the function takes no
1195     arguments and resultType is `null` then the function is returned
1196     as-is, else a wrapper is created for it to adapt its arguments
1197     and result value, as described below.
1198
1199     (If you're familiar with Emscripten's ccall() and cwrap(), this
1200     function is essentially cwrap() on steroids.)
1201
1202     This function's arguments are:
1203
1204     - fname: the exported function's name. xGet() is used to fetch
1205       this, so will throw if no exported function is found with that
1206       name.
1207
1208     - resultType: the name of the result type. A literal `null` means
1209       to return the original function's value as-is (mnemonic: there
1210       is "null" conversion going on). Literal `undefined` or the
1211       string `"void"` mean to ignore the function's result and return
1212       `undefined`. Aside from those two special cases, it may be one
1213       of the values described below or any mapping installed by the
1214       client using xWrap.resultAdapter().
1215
1216     If passed 3 arguments and the final one is an array, that array
1217     must contain a list of type names (see below) for adapting the
1218     arguments from JS to WASM.  If passed 2 arguments, more than 3,
1219     or the 3rd is not an array, all arguments after the 2nd (if any)
1220     are treated as type names. i.e.:
1221
1222     ```
1223     xWrap('funcname', 'i32', 'string', 'f64');
1224     // is equivalent to:
1225     xWrap('funcname', 'i32', ['string', 'f64']);
1226     ```
1227
1228     Type names are symbolic names which map the arguments to an
1229     adapter function to convert, if needed, the value before passing
1230     it on to WASM or to convert a return result from WASM. The list
1231     of built-in names:
1232
1233     - `i8`, `i16`, `i32` (args and results): all integer conversions
1234       which convert their argument to an integer and truncate it to
1235       the given bit length.
1236
1237     - `N*` (args): a type name in the form `N*`, where N is a numeric
1238       type name, is treated the same as WASM pointer.
1239
1240     - `*` and `pointer` (args): have multple semantics. They
1241       behave exactly as described below for `string` args.
1242
1243     - `*` and `pointer` (results): are aliases for the current
1244       WASM pointer numeric type.
1245
1246     - `**` (args): is simply a descriptive alias for the WASM pointer
1247       type. It's primarily intended to mark output-pointer arguments.
1248
1249     - `i64` (args and results): passes the value to BigInt() to
1250       convert it to an int64.
1251
1252     - `f32` (`float`), `f64` (`double`) (args and results): pass
1253       their argument to Number(). i.e. the adaptor does not currently
1254       distinguish between the two types of floating-point numbers.
1255
1256     Non-numeric conversions include:
1257
1258     - `string` (args): has two different semantics in order to
1259       accommodate various uses of certain C APIs (e.g. output-style
1260       strings)...
1261
1262       - If the arg is a string, it creates a _temporary_ C-string to
1263         pass to the exported function, cleaning it up before the
1264         wrapper returns. If a long-lived C-string pointer is
1265         required, that requires client-side code to create the
1266         string, then pass its pointer to the function.
1267
1268       - Else the arg is assumed to be a pointer to a string the
1269         client has already allocated and it's passed on as
1270         a WASM pointer.
1271
1272     - `string` (results): treats the result value as a const C-string,
1273       copies it to a JS string, and returns that JS string.
1274
1275     - `string:free` (results): treats the result value as a non-const
1276       C-string, ownership of which has just been transfered to the
1277       caller. It copies the C-string to a JS string, frees the
1278       C-string, and returns the JS string. If such a result value is
1279       NULL, the JS result is `null`.
1280
1281     - `json` (results): treats the result as a const C-string and
1282       returns the result of passing the converted-to-JS string to
1283       JSON.parse(). Returns `null` if the C-string is a NULL pointer.
1284
1285     - `json:free` (results): works exactly like `string:free` but
1286       returns the same thing as the `json` adapter.
1287
1288     The type names for results and arguments are validated when
1289     xWrap() is called and any unknown names will trigger an
1290     exception.
1291
1292     Clients may map their own result and argument adapters using
1293     xWrap.resultAdapter() and xWrap.argAdaptor(), noting that not all
1294     type conversions are valid for both arguments _and_ result types
1295     as they often have different memory ownership requirements.
1296
1297     TODOs:
1298
1299     - Figure out how/whether we can (semi-)transparently handle
1300       pointer-type _output_ arguments. Those currently require
1301       explicit handling by allocating pointers, assigning them before
1302       the call using setMemValue(), and fetching them with
1303       getMemValue() after the call. We may be able to automate some
1304       or all of that.
1305
1306     - Figure out whether it makes sense to extend the arg adapter
1307       interface such that each arg adapter gets an array containing
1308       the results of the previous arguments in the current call. That
1309       might allow some interesting type-conversion feature. Use case:
1310       handling of the final argument to sqlite3_prepare_v2() depends
1311       on the type (pointer vs JS string) of its 2nd
1312       argument. Currently that distinction requires hand-writing a
1313       wrapper for that function. That case is unusual enough that
1314       abstracting it into this API (and taking on the associated
1315       costs) may well not make good sense.
1316  */
1317  target.xWrap = function(fname, resultType, ...argTypes){
1318    if(3===arguments.length && Array.isArray(arguments[2])){
1319      argTypes = arguments[2];
1320    }
1321    const xf = this.xGet(fname);
1322    if(argTypes.length!==xf.length) __argcMismatch(fname, xf.length)
1323    if((null===resultType) && 0===xf.length){
1324      /* Func taking no args with an as-is return. We don't need a wrapper. */
1325      return xf;
1326    }
1327    /*Verify the arg type conversions are valid...*/;
1328    if(undefined!==resultType && null!==resultType) __xResultAdapter(resultType);
1329    argTypes.forEach(__xArgAdapter)
1330    if(0===xf.length){
1331      // No args to convert, so we can create a simpler wrapper...
1332      return function(){
1333        return (arguments.length
1334                ? __argcMismatch(fname, xf.length)
1335                : cache.xWrap.convertResult(resultType, xf.call(null)));
1336      };
1337    }
1338    return function(...args){
1339      if(args.length!==xf.length) __argcMismatch(fname, xf.length);
1340      const scope = this.scopedAllocPush();
1341      try{
1342        const rc = xf.apply(null,args.map((v,i)=>cache.xWrap.convertArg(argTypes[i], v)));
1343        return cache.xWrap.convertResult(resultType, rc);
1344      }finally{
1345        this.scopedAllocPop(scope);
1346      }
1347    }.bind(this);
1348  }.bind(target)/*xWrap()*/;
1349
1350  /** Internal impl for xWrap.resultAdapter() and argAdaptor(). */
1351  const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){
1352    if('string'===typeof typeName){
1353      if(1===argc) return xcvPart[typeName];
1354      else if(2===argc){
1355        if(!adapter){
1356          delete xcvPart[typeName];
1357          return func;
1358        }else if(!(adapter instanceof Function)){
1359          toss(modeName,"requires a function argument.");
1360        }
1361        xcvPart[typeName] = adapter;
1362        return func;
1363      }
1364    }
1365    toss("Invalid arguments to",modeName);
1366  };
1367
1368  /**
1369     Gets, sets, or removes a result value adapter for use with
1370     xWrap(). If passed only 1 argument, the adapter function for the
1371     given type name is returned.  If the second argument is explicit
1372     falsy (as opposed to defaulted), the adapter named by the first
1373     argument is removed. If the 2nd argument is not falsy, it must be
1374     a function which takes one value and returns a value appropriate
1375     for the given type name. The adapter may throw if its argument is
1376     not of a type it can work with. This function throws for invalid
1377     arguments.
1378
1379     Example:
1380
1381     ```
1382     xWrap.resultAdapter('twice',(v)=>v+v);
1383     ```
1384
1385     xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of
1386     APIs to allocate a result value. xWrap()-generated wrappers run
1387     in the context of scopedAllocPush() so that argument adapters can
1388     easily convert, e.g., to C-strings, and have them cleaned up
1389     automatically before the wrapper returns to the caller. Likewise,
1390     if a _result_ adapter uses scoped allocation, the result will be
1391     freed before because they would be freed before the wrapper
1392     returns, leading to chaos and undefined behavior.
1393
1394     Except when called as a getter, this function returns itself.
1395  */
1396  target.xWrap.resultAdapter = function f(typeName, adapter){
1397    return __xAdapter(f, arguments.length, typeName, adapter,
1398                      'resultAdaptor()', xcv.result);
1399  };
1400
1401  /**
1402     Functions identically to xWrap.resultAdapter() but applies to
1403     call argument conversions instead of result value conversions.
1404
1405     xWrap()-generated wrappers perform argument conversion in the
1406     context of a scopedAllocPush(), so any memory allocation
1407     performed by argument adapters really, really, really should be
1408     made using the scopedAlloc() family of functions unless
1409     specifically necessary. For example:
1410
1411     ```
1412     xWrap.argAdapter('my-string', function(v){
1413       return ('string'===typeof v)
1414         ? myWasmObj.scopedAllocCString(v) : null;
1415     };
1416     ```
1417
1418     Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc()
1419     to allocate its results because they would be freed before the
1420     xWrap()-created wrapper returns.
1421
1422     Note that it is perfectly legitimate to use these adapters to
1423     perform argument validation, as opposed (or in addition) to
1424     conversion.
1425  */
1426  target.xWrap.argAdapter = function f(typeName, adapter){
1427    return __xAdapter(f, arguments.length, typeName, adapter,
1428                      'argAdaptor()', xcv.arg);
1429  };
1430
1431  /**
1432     Functions like xCall() but performs argument and result type
1433     conversions as for xWrap(). The first argument is the name of the
1434     exported function to call. The 2nd its the name of its result
1435     type, as documented for xWrap(). The 3rd is an array of argument
1436     type name, as documented for xWrap() (use a falsy value or an
1437     empty array for nullary functions). The 4th+ arguments are
1438     arguments for the call, with the special case that if the 4th
1439     argument is an array, it is used as the arguments for the call
1440     (again, falsy or an empty array for nullary functions). Returns
1441     the converted result of the call.
1442
1443     This is just a thin wrapp around xWrap(). If the given function
1444     is to be called more than once, it's more efficient to use
1445     xWrap() to create a wrapper, then to call that wrapper as many
1446     times as needed. For one-shot calls, however, this variant is
1447     arguably more efficient because it will hypothetically free the
1448     wrapper function quickly.
1449  */
1450  target.xCallWrapped = function(fname, resultType, argTypes, ...args){
1451    if(Array.isArray(arguments[3])) args = arguments[3];
1452    return this.xWrap(fname, resultType, argTypes||[]).apply(null, args||[]);
1453  }.bind(target);
1454
1455  return target;
1456};
1457
1458/**
1459   yawl (Yet Another Wasm Loader) provides very basic wasm loader.
1460   It requires a config object:
1461
1462   - `uri`: required URI of the WASM file to load.
1463
1464   - `onload(loadResult,config)`: optional callback. The first
1465     argument is the result object from
1466     WebAssembly.instanitate[Streaming](). The 2nd is the config
1467     object passed to this function. Described in more detail below.
1468
1469   - `imports`: optional imports object for
1470     WebAssembly.instantiate[Streaming](). The default is am empty set
1471     of imports. If the module requires any imports, this object
1472     must include them.
1473
1474   - `wasmUtilTarget`: optional object suitable for passing to
1475     WhWasmUtilInstaller(). If set, it gets passed to that function
1476     after the promise resolves. This function sets several properties
1477     on it before passing it on to that function (which sets many
1478     more):
1479
1480     - `module`, `instance`: the properties from the
1481       instantiate[Streaming]() result.
1482
1483     - If `instance.exports.memory` is _not_ set then it requires that
1484       `config.imports.env.memory` be set (else it throws), and
1485       assigns that to `target.memory`.
1486
1487     - If `wasmUtilTarget.alloc` is not set and
1488       `instance.exports.malloc` is, it installs
1489       `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()`
1490       wrappers for the exports `malloc` and `free` functions.
1491
1492   It returns a function which, when called, initiates loading of the
1493   module and returns a Promise. When that Promise resolves, it calls
1494   the `config.onload` callback (if set) and passes it
1495   `(loadResult,config)`, where `loadResult` is the result of
1496   WebAssembly.instantiate[Streaming](): an object in the form:
1497
1498   ```
1499   {
1500     module: a WebAssembly.Module,
1501     instance: a WebAssembly.Instance
1502   }
1503   ```
1504
1505   (Note that the initial `then()` attached to the promise gets only
1506   that object, and not the `config` one.)
1507
1508   Error handling is up to the caller, who may attach a `catch()` call
1509   to the promise.
1510*/
1511self.WhWasmUtilInstaller.yawl = function(config){
1512  const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'});
1513  const wui = this;
1514  const finalThen = function(arg){
1515    //log("finalThen()",arg);
1516    if(config.wasmUtilTarget){
1517      const toss = (...args)=>{throw new Error(args.join(' '))};
1518      const tgt = config.wasmUtilTarget;
1519      tgt.module = arg.module;
1520      tgt.instance = arg.instance;
1521      //tgt.exports = tgt.instance.exports;
1522      if(!tgt.instance.exports.memory){
1523        /**
1524           WhWasmUtilInstaller requires either tgt.exports.memory
1525           (exported from WASM) or tgt.memory (JS-provided memory
1526           imported into WASM).
1527        */
1528        tgt.memory = (config.imports && config.imports.env
1529                      && config.imports.env.memory)
1530          || toss("Missing 'memory' object!");
1531      }
1532      if(!tgt.alloc && arg.instance.exports.malloc){
1533        tgt.alloc = function(n){
1534          return this(n) || toss("Allocation of",n,"bytes failed.");
1535        }.bind(arg.instance.exports.malloc);
1536        tgt.dealloc = function(m){this(m)}.bind(arg.instance.exports.free);
1537      }
1538      wui(tgt);
1539    }
1540    if(config.onload) config.onload(arg,config);
1541    return arg /* for any then() handler attached to
1542                  yetAnotherWasmLoader()'s return value */;
1543  };
1544  const loadWasm = WebAssembly.instantiateStreaming
1545        ? function loadWasmStreaming(){
1546          return WebAssembly.instantiateStreaming(wfetch(), config.imports||{})
1547            .then(finalThen);
1548        }
1549        : function loadWasmOldSchool(){ // Safari < v15
1550          return wfetch()
1551            .then(response => response.arrayBuffer())
1552            .then(bytes => WebAssembly.instantiate(bytes, config.imports||{}))
1553            .then(finalThen);
1554        };
1555  return loadWasm;
1556}.bind(self.WhWasmUtilInstaller)/*yawl()*/;
1557