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