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