From: stephan Date: Fri, 19 Sep 2025 14:21:09 +0000 (+0000) Subject: Wasm: (A) diverse internal doc updates. (B) when generating automated JS-to-WASM... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=194d6edeb6d9b21d7dc6b209b81a9be6afd384b6;p=thirdparty%2Fsqlite.git Wasm: (A) diverse internal doc updates. (B) when generating automated JS-to-WASM function proxies for converters which require an additional middle-man proxy, e.g. sqlite3_exec(), use the client-provided function, not the proxy function, as the cache key, to keep from re-generating the conversion in some common use patterns. FossilOrigin-Name: 5e5139c2a162562cee0071d03954ebc0b8938da0b045ec3f5eba32dc8e19604d --- diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index e3ded91987..16545270df 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -230,7 +230,8 @@ ** Which sqlite3.c we're using needs to be configurable to enable ** building against a custom copy, e.g. the SEE variant. We #include ** the .c file, rather than the header, so that the WASM extensions -** have access to private API internals. +** have access to private API internals (namely for kvvfs and +** SQLTester pieces). ** ** The caveat here is that custom variants need to account for ** exporting any necessary symbols (e.g. sqlite3_activate_see()). We @@ -1753,7 +1754,7 @@ int sqlite3__wasm_init_wasmfs(const char *zMountPoint){ SQLITE_WASM_EXPORT int sqlite3__wasm_init_wasmfs(const char *zUnused){ //emscripten_console_warn("WASMFS OPFS is not compiled in."); - if(zUnused){/*unused*/} + (void)zUnused; return SQLITE_NOTFOUND; } #endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */ diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index e856695797..48af31ceb0 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -10,28 +10,18 @@ *********************************************************************** - The whwasmutil is developed in conjunction with the Jaccwabyt - project: + This code is developed in conjunction with the Jaccwabyt project: https://fossil.wanderinghorse.net/r/jaccwabyt - and sqlite3: + and SQLite: 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, + The primary goal of this function 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 @@ -40,24 +30,29 @@ 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. + for their Emscripten counterparts. + + 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. Intended usage: ``` - globalThis.WhWasmUtilInstaller(appObject); + const target = {}; // ... some object ... + globalThis.WhWasmUtilInstaller(target); 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 symbol is _not_ used within the library. + The `target` object then holds the APIs. It may have certain + properties set to configure it, as documented below. - 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. + The global-scope symbol for this function is intended only to + provide an easy way to make it available to 3rd-party scripts and + "should" be deleted after calling it. That symbol is _not_ used + within the library. It currently offers alternatives to the following Emscripten-generated APIs: @@ -104,9 +99,9 @@ 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: + properties, though 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 @@ -140,11 +135,10 @@ 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. + constructor is available, else it will default to false. 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: @@ -163,6 +157,15 @@ APIs which require allocation routines are explicitly documented as such and/or have "alloc" in their names. + Optional configuration values which may be set on target before + calling this: + + - `pointerIR`: an IR-format string for the WASM environment's + pointer size. If set it must be either 'i32' or 'i64'. If not + set, it defaults to whatever this code thinks the pointer size + is. Modifying it after this call has no effect. + + This code is developed and maintained in conjunction with the Jaccwabyt project: @@ -240,10 +243,17 @@ globalThis.WhWasmUtilInstaller = function(target){ installFunction() extracts them. */ cache.freeFuncIndexes = []; /** - Used by scopedAlloc() and friends. + List-of-lists used by scopedAlloc() and friends. */ cache.scopedAlloc = []; + /** Push the pointer ptr to the current cache.scopedAlloc list + (which must already exist) and return ptr. */ + cache.scopedAlloc.pushPtr = (ptr)=>{ + cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); + return ptr; + }; + cache.utf8Decoder = new TextDecoder(); cache.utf8Encoder = new TextEncoder('utf-8'); @@ -320,25 +330,20 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + 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. 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. + 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; @@ -522,7 +527,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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."); @@ -556,7 +561,7 @@ globalThis.WhWasmUtilInstaller = function(target){ /*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); + cache.scopedAlloc.pushPtr(ptr); } return ptr; }catch(e){ @@ -570,7 +575,7 @@ globalThis.WhWasmUtilInstaller = function(target){ const fptr = target.jsFuncToWasm(func, sig); ft.set(ptr, fptr); if(scoped){ - cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); + cache.scopedAlloc.pushPtr(ptr); } }catch(e){ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); @@ -649,8 +654,8 @@ globalThis.WhWasmUtilInstaller = function(target){ 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 + number or, for the case of type='i64', a BigInt (with the caveat + BigInt will trigger 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 @@ -660,7 +665,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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`). + appropriate for the pointer size (==this.pointerIR). While likely not obvious, this routine and its poke() counterpart are how pointer-to-value _output_ parameters @@ -668,9 +673,9 @@ globalThis.WhWasmUtilInstaller = function(target){ ``` const ptr = alloc(4); - poke(ptr, 0, 'i32'); // clear the ptr's value + poke32(ptr, 0); // clear the ptr's value aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x); - const result = peek(ptr, 'i32'); // fetch ptr's value + const result = peek32(ptr); // fetch ptr's value dealloc(ptr); ``` @@ -682,23 +687,23 @@ globalThis.WhWasmUtilInstaller = function(target){ const scope = scopedAllocPush(); try{ const ptr = scopedAlloc(4); - poke(ptr, 0, 'i32'); + poke32(ptr, 0); aCFuncWithOutputPtrArg( ptr ); - result = peek(ptr, 'i32'); + result = peek32(ptr); }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. + 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() + See also: poke() */ target.peek = function f(ptr, type='i8'){ if(type.endsWith('*')) type = ptrIR; @@ -735,7 +740,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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`. + behaves as if the 3rd argument were this.pointerIR. If the first argument is an array, it is treated like a list of pointers and the given value is written to each one. @@ -1151,11 +1156,13 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + use this function. It also uninstalls any WASM functions + installed with scopedInstallFunction(). 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. + other than the order they were pushed. The intent is that it + _always_ be used in a stack-like manner. If called with no arguments, it pops the most recent scopedAllocPush() result: @@ -1181,8 +1188,9 @@ globalThis.WhWasmUtilInstaller = function(target){ if(target.functionEntry(p)){ //console.warn("scopedAllocPop() uninstalling function",p); target.uninstallFunction(p); + }else{ + target.dealloc(p); } - else target.dealloc(p); } }; @@ -1206,9 +1214,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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; + return cache.scopedAlloc.pushPtr(target.alloc(n)); }; Object.defineProperty(target.scopedAlloc, 'level', { @@ -1343,7 +1349,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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.. + 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, @@ -1404,7 +1410,7 @@ globalThis.WhWasmUtilInstaller = function(target){ }; /** - State for use with xWrap() + State for use with xWrap(). */ cache.xWrap = Object.create(null); cache.xWrap.convert = Object.create(null); @@ -1438,8 +1444,9 @@ globalThis.WhWasmUtilInstaller = function(target){ .set('null', (v)=>v) .set(null, xResult.get('null')); - { /* Copy certain xArg[...] handlers to xResult[...] and - add pointer-style variants of them. */ + { /* Copy xArg[...] handlers to xResult[...] for cases which have + identical semantics. Also add pointer-style variants of + them. */ const copyToResult = ['i8', 'i16', 'i32', 'int', 'f32', 'float', 'f64', 'double']; if(target.bigIntEnabled) copyToResult.push('i64'); @@ -1461,7 +1468,7 @@ globalThis.WhWasmUtilInstaller = function(target){ - 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. + - 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 @@ -1494,12 +1501,11 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + 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){ @@ -1518,6 +1524,11 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + + The reason for the argv and argIndex arguments is that we + frequently need more context than v for a specific conversion, + and that context invariably lies in the LHS arguments of v. + Examples of how this is useful can be found in FuncPtrAdapter. */ convertArg(v,argv,argIndex){ toss("AbstractArgAdapter must be subclassed."); @@ -1525,12 +1536,11 @@ globalThis.WhWasmUtilInstaller = function(target){ }; /** - 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. + 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: @@ -1566,7 +1576,7 @@ globalThis.WhWasmUtilInstaller = function(target){ - 'permanent': the function is installed and left there forever. There is no way to recover its pointer address - later on. + later on for cleanup purposes. i.e. it effectively leaks. - callProxy (function): if set, this must be a function which will act as a proxy for any "converted" JS function. It is @@ -1574,12 +1584,12 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + any required argument conversion (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 WASM code. - contextKey (function): is only used if bindScope is 'context' or if bindScope is not set and this function is, in which case @@ -1598,10 +1608,10 @@ globalThis.WhWasmUtilInstaller = function(target){ 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) + if the xWrap()'d function takes `(int,T*,functionPtr,X*)` then + this FuncPtrAdapter instance is argv[2], and contextKey(argv,2) might return 'T@'+argv[1], or even just argv[1]. Note, - however, that the (X*) argument will not yet have been + 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 @@ -1609,13 +1619,17 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + constellation are transient. Conversely, the pointer address + makes an ideal key for longer-lived native pointer types. - Yes, that ^^^ is quite awkward, but it's what we have. + Yes, that ^^^ is quite awkward, but it's what we have. In + context, as it were, it actually makes some sense, but one must + look under its hook a bit to understand why it's shaped the + way it is. 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. + not actually bind any functions. The conversion, if any, is + performed when its convertArg() method is called via xWrap(). Shortcomings: @@ -1627,10 +1641,9 @@ globalThis.WhWasmUtilInstaller = function(target){ - 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. + strings. Alternately, clients will need to perform such + conversions on their own, e.g. using cstrToJs(). The purpose of + the callProxy() method is to account for such cases. */ xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter { constructor(opt) { @@ -1662,9 +1675,9 @@ globalThis.WhWasmUtilInstaller = function(target){ } /** - 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. + The 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. */ @@ -1672,9 +1685,15 @@ globalThis.WhWasmUtilInstaller = function(target){ return this; } - /* Returns this objects mapping for the given context key, in the + /** + Returns this object's 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. */ + may be anything suitable for use in a Map. + + The returned array is intended to be used as a pair of + [JSValue, WasmFuncPtr], where the first element is one passed + to this.convertArg() and the second is its WASM form. + */ contextMap(key){ const cm = (this.__cmap || (this.__cmap = new Map)); let rc = cm.get(key); @@ -1684,19 +1703,21 @@ globalThis.WhWasmUtilInstaller = function(target){ /** Gets called via xWrap() to "convert" v to a WASM-bound function - pointer. If v is one of (a pointer, null, undefined) then + pointer. If v is one of (a WASM 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. + which this method 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. + If it's passed a pointer(ish) value for v, it assumes it's a + WASM function pointer and does _not_ perform any function + binding, so this object's bindMode is irrelevant/ignored for + such cases. See the parent class's convertArg() docs for details on what exactly the 2nd and 3rd arguments are. @@ -1708,11 +1729,16 @@ globalThis.WhWasmUtilInstaller = function(target){ 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(pair && pair[0]===v){ + /* We have already handled this function. */ + 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); + if(this.callProxy){ + v = this.callProxy(v); + } const fp = __installFunction(v, this.signature, this.isTransient); if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, @@ -1735,11 +1761,11 @@ globalThis.WhWasmUtilInstaller = function(target){ function. We're relying very much here on xWrap() having pushed an alloc scope. */ - cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]); + cache.scopedAlloc.pushPtr(pair[1]); } catch(e){/*ignored*/} } - pair[0] = v; + pair[0] = arguments[0]/*the original v*/; pair[1] = fp; } return fp; @@ -1751,7 +1777,7 @@ globalThis.WhWasmUtilInstaller = function(target){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } - try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) } + try{ cache.scopedAlloc.pushPtr(pair[1]); } catch(e){/*ignored*/} pair[0] = pair[1] = (v | 0); } @@ -1782,13 +1808,19 @@ globalThis.WhWasmUtilInstaller = function(target){ /** Function used for debug output. */ xArg.FuncPtrAdapter.debugOut = console.debug.bind(console); + /** + List of legal values for the FuncPtrAdapter bindScope config + option. + */ xArg.FuncPtrAdapter.bindScopes = [ 'transient', 'context', 'singleton', 'permanent' ]; + /** Throws if xArg.get(t) returns falsy. */ const __xArgAdapterCheck = (t)=>xArg.get(t) || toss("Argument adapter not found:",t); + /** Throws if xResult.get(t) returns falsy. */ const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); @@ -1942,8 +1974,8 @@ globalThis.WhWasmUtilInstaller = function(target){ ```js target.xWrap.resultAdapter('string:my_free',(i)=>{ - try { return i ? target.cstrToJs(i) : null } - finally{ target.exports.my_free(i) } + try { return i ? target.cstrToJs(i) : null; } + finally{ target.exports.my_free(i); } }; ``` @@ -2003,8 +2035,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. */ + /* Func taking no args with an as-is return. We don't need a wrapper. */ return xf; } /*Verify the arg type conversions are valid...*/; @@ -2041,7 +2072,9 @@ globalThis.WhWasmUtilInstaller = function(target){ _not_ stable. Maintenance reminder: the Ember framework modifies the core - Array type, breaking for-in loops. + Array type, breaking for-in loops: + + https://sqlite.org/forum/forumpost/b549992634b55104 */ let i = 0; for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck( @@ -2054,7 +2087,28 @@ globalThis.WhWasmUtilInstaller = function(target){ }; }/*xWrap()*/; - /** Internal impl for xWrap.resultAdapter() and argAdapter(). */ + /** + Internal impl for xWrap.resultAdapter() and argAdapter(). + + func = one of xWrap.resultAdapter or xWrap.argAdapter. + + argc = the number of args in the wrapping call to this + function. + + typeName = the first arg to the wrapping function. + + adapter = the second arg to the wrapping function. + + modeName = a descriptive name of the wrapping function for + error-reporting purposes. + + xcvPart = one of xResult or xArg. + + This acts as either a getter (if 1===argc) or setter (if + 2===argc) for the given adapter. Returns func on success or + throws if (A) called with 2 args but adapter is-not-a Function or + (B) typeName is not a string or (C) argc is not one of (1, 2). + */ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){ if('string'===typeof typeName){ if(1===argc) return xcvPart.get(typeName); @@ -2089,16 +2143,16 @@ globalThis.WhWasmUtilInstaller = function(target){ 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 + Result adapters 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 the wrapper returns, leading to chaos and undefined behavior. - Except when called as a getter, this function returns itself. + When called as a setter, this function returns itself. */ target.xWrap.resultAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, @@ -2122,13 +2176,13 @@ globalThis.WhWasmUtilInstaller = function(target){ }; ``` - Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc() - to allocate its results because they would be freed before the + Contrariwise, _result_ adapters _must not_ use scopedAlloc() to + allocate 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. + It is perfectly legitimate to use these adapters to perform + argument validation, as opposed (or in addition) to conversion. + When used that way, they should throw for invalid arguments. */ target.xWrap.argAdapter = function f(typeName, adapter){ return __xAdapter(f, arguments.length, typeName, adapter, @@ -2151,8 +2205,7 @@ globalThis.WhWasmUtilInstaller = function(target){ 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. + simpler. */ target.xCallWrapped = function(fArg, resultType, argTypes, ...args){ if(Array.isArray(arguments[3])) args = arguments[3]; @@ -2239,8 +2292,9 @@ globalThis.WhWasmUtilInstaller = function(target){ } ``` - (Note that the initial `then()` attached to the promise gets only - that object, and not the `config` one.) + (The initial `then()` attached to the promise gets only that + object, and not the `config` object, thus the potential need for a + `config.onload` handler.) Error handling is up to the caller, who may attach a `catch()` call to the promise. diff --git a/manifest b/manifest index d11a18e8cb..e79e5c8b1e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\stypo\sin\sthe\sname\sof\sthe\sCursorHints\soptimization\sin\sthe\sCLI. -D 2025-09-19T09:18:22.076 +C Wasm:\s(A)\sdiverse\sinternal\sdoc\supdates.\s(B)\swhen\sgenerating\sautomated\sJS-to-WASM\sfunction\sproxies\sfor\sconverters\swhich\srequire\san\sadditional\smiddle-man\sproxy,\se.g.\ssqlite3_exec(),\suse\sthe\sclient-provided\sfunction,\snot\sthe\sproxy\sfunction,\sas\sthe\scache\skey,\sto\skeep\sfrom\sre-generating\sthe\sconversion\sin\ssome\scommon\suse\spatterns. +D 2025-09-19T14:21:09.961 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -607,7 +607,7 @@ F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 0f68a64e508598910e7c01214ae27d603dfc8baec6a184506fafac603a901931 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 4ab0704ee198de7d1059eccedc7703c931510b588d10af0ee36ea5b3ebbac284 F ext/wasm/api/sqlite3-vtab-helper.c-pp.js e809739d71e8b35dfe1b55d24d91f02d04239e6aef7ca1ea92a15a29e704f616 -F ext/wasm/api/sqlite3-wasm.c 7b207c10c6b4019cf667c4e332bdd33c98afc08c943f8cc0aea7693ad8635f7c +F ext/wasm/api/sqlite3-wasm.c 404cc1f0f5c307210a8d7c3a7dda57834e0e8b3d406ba51977a97a6d14a74734 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 4ad256b4ff7f839ad18931ed35d46cced544207bd2209665ec552e193f7f4544 F ext/wasm/api/sqlite3-worker1.c-pp.js 5e8706c2c4af2a57fbcdc02f4e7ef79869971bc21bb8ede777687786ce1c92d5 F ext/wasm/batch-runner-sahpool.html e9a38fdeb36a13eac7b50241dfe7ae066fe3f51f5c0b0151e7baee5fce0d07a7 @@ -618,7 +618,7 @@ F ext/wasm/c-pp.c cca55c5b55ebd8d29916adbedb0e40baa12caa9a2e8429f812683c308f9b0e F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f -F ext/wasm/common/whwasmutil.js ea50e847cf08c2a96694f87d00b49fb97ee5fe62c15439cc269c8e1d9ab74c0a +F ext/wasm/common/whwasmutil.js 4ea5a413016d9a561a26976c699a2670f2385b93be04cd2d463dd6c1955bd175 F ext/wasm/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 @@ -2175,8 +2175,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 42c225a2ed7fc95f9b01467c64ba2bf97bca216fdcd6ab1ba3fb49c068650de9 -R fec5e9cf9c4aa5198306438cd736e135 -U drh -Z 1e7399cfaf854450b90c6eb04c4097ba +P 468a11fd415710042b23880772f6c2c7771008208823fe3b554227a9244dbf92 +R 45b30de6d5f1d826a12d811876f55290 +U stephan +Z 31a53fb96f385e882a14f4d3fa47d2d3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5f03ba8a91..4dc27434ec 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -468a11fd415710042b23880772f6c2c7771008208823fe3b554227a9244dbf92 +5e5139c2a162562cee0071d03954ebc0b8938da0b045ec3f5eba32dc8e19604d