1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-10-21 11:13:54 +03:00

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
This commit is contained in:
stephan
2025-09-19 14:21:09 +00:00
parent e7fe1d3b3e
commit 194d6edeb6
4 changed files with 211 additions and 156 deletions

View File

@@ -230,7 +230,8 @@
** Which sqlite3.c we're using needs to be configurable to enable ** Which sqlite3.c we're using needs to be configurable to enable
** building against a custom copy, e.g. the SEE variant. We #include ** building against a custom copy, e.g. the SEE variant. We #include
** the .c file, rather than the header, so that the WASM extensions ** 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 ** The caveat here is that custom variants need to account for
** exporting any necessary symbols (e.g. sqlite3_activate_see()). We ** 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 SQLITE_WASM_EXPORT
int sqlite3__wasm_init_wasmfs(const char *zUnused){ int sqlite3__wasm_init_wasmfs(const char *zUnused){
//emscripten_console_warn("WASMFS OPFS is not compiled in."); //emscripten_console_warn("WASMFS OPFS is not compiled in.");
if(zUnused){/*unused*/} (void)zUnused;
return SQLITE_NOTFOUND; return SQLITE_NOTFOUND;
} }
#endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */ #endif /* __EMSCRIPTEN__ && SQLITE_ENABLE_WASMFS */

View File

@@ -10,28 +10,18 @@
*********************************************************************** ***********************************************************************
The whwasmutil is developed in conjunction with the Jaccwabyt This code is developed in conjunction with the Jaccwabyt project:
project:
https://fossil.wanderinghorse.net/r/jaccwabyt https://fossil.wanderinghorse.net/r/jaccwabyt
and sqlite3: and SQLite:
https://sqlite.org https://sqlite.org
This file is kept in sync between both of those trees. 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 The primary goal of this function is to replace, where possible,
of WASM-related utility code from project to project.
The primary goal of this code is to replace, where possible,
Emscripten-generated glue code with equivalent utility code which Emscripten-generated glue code with equivalent utility code which
can be used in arbitrary WASM environments built with toolchains can be used in arbitrary WASM environments built with toolchains
other than Emscripten. As of this writing, this code is capable of other than Emscripten. As of this writing, this code is capable of
@@ -40,18 +30,7 @@
APIs such as its "FS" (virtual filesystem) API. Loading of such APIs such as its "FS" (virtual filesystem) API. Loading of such
things still requires using Emscripten's glue, but the post-load things still requires using Emscripten's glue, but the post-load
utility APIs provided by this code are still usable as replacements utility APIs provided by this code are still usable as replacements
for their sub-optimally-documented Emscripten counterparts. for their Emscripten counterparts.
Intended usage:
```
globalThis.WhWasmUtilInstaller(appObject);
delete globalThis.WhWasmUtilInstaller;
```
Its global-scope symbol is intended only to provide an easy way to
make it available to 3rd-party scripts and "should" be deleted
after calling it. That symbol is _not_ used within the library.
Forewarning: this API explicitly targets only browser Forewarning: this API explicitly targets only browser
environments. If a given non-browser environment has the environments. If a given non-browser environment has the
@@ -59,6 +38,22 @@
but it does not go out of its way to account for them and does not but it does not go out of its way to account for them and does not
provide compatibility crutches for them. provide compatibility crutches for them.
Intended usage:
```
const target = {}; // ... some object ...
globalThis.WhWasmUtilInstaller(target);
delete globalThis.WhWasmUtilInstaller;
```
The `target` object then holds the APIs. It may have certain
properties set to configure it, as documented below.
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 It currently offers alternatives to the following
Emscripten-generated APIs: Emscripten-generated APIs:
@@ -104,9 +99,9 @@
symbol. symbol.
This code requires that the target object have the following This code requires that the target object have the following
properties, noting that they needn't be available until the first properties, though they needn't be available until the first time
time one of the installed APIs is used (as opposed to when this one of the installed APIs is used (as opposed to when this function
function is called) except where explicitly noted: is called) except where explicitly noted:
- `exports` must be a property of the target object OR a property - `exports` must be a property of the target object OR a property
of `target.instance` (a WebAssembly.Module instance) and it must 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 false. If it is false, certain BigInt-related features will trigger
an exception if invoked. This property, if not set when this is an exception if invoked. This property, if not set when this is
called, will get a default value of true only if the BigInt64Array called, will get a default value of true only if the BigInt64Array
constructor is available, else it will default to false. Note that constructor is available, else it will default to false. Having
having the BigInt type is not sufficient for full int64 integration the BigInt type is not sufficient for full int64 integration with
with WASM: the target WASM file must also have been built with WASM: the target WASM file must also have been built with that
that support. In Emscripten that's done using the `-sWASM_BIGINT` support. In Emscripten that's done using the `-sWASM_BIGINT` flag.
flag.
Some optional APIs require that the target have the following Some optional APIs require that the target have the following
methods: methods:
@@ -163,6 +157,15 @@
APIs which require allocation routines are explicitly documented as APIs which require allocation routines are explicitly documented as
such and/or have "alloc" in their names. 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 This code is developed and maintained in conjunction with the
Jaccwabyt project: Jaccwabyt project:
@@ -240,10 +243,17 @@ globalThis.WhWasmUtilInstaller = function(target){
installFunction() extracts them. */ installFunction() extracts them. */
cache.freeFuncIndexes = []; cache.freeFuncIndexes = [];
/** /**
Used by scopedAlloc() and friends. List-of-lists used by scopedAlloc() and friends.
*/ */
cache.scopedAlloc = []; 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.utf8Decoder = new TextDecoder();
cache.utf8Encoder = new TextEncoder('utf-8'); cache.utf8Encoder = new TextEncoder('utf-8');
@@ -320,25 +330,20 @@ globalThis.WhWasmUtilInstaller = function(target){
BigInt64Array/BigUint64Array, else it throws if passed 64 or one BigInt64Array/BigUint64Array, else it throws if passed 64 or one
of those constructors. of those constructors.
Returns an integer-based TypedArray view of the WASM heap Returns an integer-based TypedArray view of the WASM heap memory
memory buffer associated with the given block size. If passed buffer associated with the given block size. If passed an integer
an integer as the first argument and unsigned is truthy then as the first argument and unsigned is truthy then the "U"
the "U" (unsigned) variant of that view is returned, else the (unsigned) variant of that view is returned, else the signed
signed variant is returned. If passed a TypedArray value, the variant is returned. If passed a TypedArray value, the 2nd
2nd argument is ignored. Note that Float32Array and argument is ignored. Float32Array and Float64Array views are not
Float64Array views are not supported by this function. supported by this function.
Note that growth of the heap will invalidate any references to Growth of the heap will invalidate any references to this heap,
this heap, so do not hold a reference longer than needed and do so do not hold a reference longer than needed and do not use a
not use a reference after any operation which may reference after any operation which may allocate. Instead,
allocate. Instead, re-fetch the reference by calling this re-fetch the reference by calling this function again.
function again.
Throws if passed an invalid n. 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){ target.heapForSize = function(n,unsigned = true){
let ctor; let ctor;
@@ -522,7 +527,7 @@ globalThis.WhWasmUtilInstaller = function(target){
is stashed in the current scoped-alloc scope and will be is stashed in the current scoped-alloc scope and will be
cleaned up at the matching scopedAllocPop(), else it cleaned up at the matching scopedAllocPop(), else it
is not stashed there. is not stashed there.
*/ */
const __installFunction = function f(func, sig, scoped){ const __installFunction = function f(func, sig, scoped){
if(scoped && !cache.scopedAlloc.length){ if(scoped && !cache.scopedAlloc.length){
toss("No scopedAllocPush() scope is active."); 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*/ /*this will only work if func is a WASM-exported function*/
ft.set(ptr, func); ft.set(ptr, func);
if(scoped){ if(scoped){
cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); cache.scopedAlloc.pushPtr(ptr);
} }
return ptr; return ptr;
}catch(e){ }catch(e){
@@ -570,7 +575,7 @@ globalThis.WhWasmUtilInstaller = function(target){
const fptr = target.jsFuncToWasm(func, sig); const fptr = target.jsFuncToWasm(func, sig);
ft.set(ptr, fptr); ft.set(ptr, fptr);
if(scoped){ if(scoped){
cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr); cache.scopedAlloc.pushPtr(ptr);
} }
}catch(e){ }catch(e){
if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); 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 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 (i8, i16, i32, i64, float (or f32), double (or f64)), this
fetches the numeric value from that address and returns it as a 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 number or, for the case of type='i64', a BigInt (with the caveat
type triggers an exception if this.bigIntEnabled is BigInt will trigger an exception if this.bigIntEnabled is
falsy). Throws if given an invalid type. falsy). Throws if given an invalid type.
If the first argument is an array, it is treated as an array of 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 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 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() While likely not obvious, this routine and its poke()
counterpart are how pointer-to-value _output_ parameters counterpart are how pointer-to-value _output_ parameters
@@ -668,9 +673,9 @@ globalThis.WhWasmUtilInstaller = function(target){
``` ```
const ptr = alloc(4); 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); 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); dealloc(ptr);
``` ```
@@ -682,23 +687,23 @@ globalThis.WhWasmUtilInstaller = function(target){
const scope = scopedAllocPush(); const scope = scopedAllocPush();
try{ try{
const ptr = scopedAlloc(4); const ptr = scopedAlloc(4);
poke(ptr, 0, 'i32'); poke32(ptr, 0);
aCFuncWithOutputPtrArg( ptr ); aCFuncWithOutputPtrArg( ptr );
result = peek(ptr, 'i32'); result = peek32(ptr);
}finally{ }finally{
scopedAllocPop(scope); scopedAllocPop(scope);
} }
``` ```
As a rule poke() must be called to set (typically zero As a rule poke() must be called to set (typically zero out) the
out) the pointer's value, else it will contain an essentially pointer's value, else it will contain an essentially random
random value. value.
ACHTUNG: calling this often, e.g. in a loop, can have a noticably ACHTUNG: calling this often, e.g. in a loop, can have a noticably
painful impact on performance. Rather than doing so, use painful impact on performance. Rather than doing so, use
heapForSize() to fetch the heap object and read directly from it. heapForSize() to fetch the heap object and read directly from it.
See: poke() See also: poke()
*/ */
target.peek = function f(ptr, type='i8'){ target.peek = function f(ptr, type='i8'){
if(type.endsWith('*')) type = ptrIR; if(type.endsWith('*')) type = ptrIR;
@@ -735,7 +740,7 @@ globalThis.WhWasmUtilInstaller = function(target){
bytes are written. Throws if given an invalid type. See peek() bytes are written. Throws if given an invalid type. See peek()
for details about the `type` argument. If the 3rd argument ends for details about the `type` argument. If the 3rd argument ends
with `*` then it is treated as a pointer type and this function 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 If the first argument is an array, it is treated like a list
of pointers and the given value is written to each one. 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 Cleans up all allocations made using scopedAlloc() in the context
of the given opaque state object, which must be a value returned of the given opaque state object, which must be a value returned
by scopedAllocPush(). See that function for an example of how to 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 Though scoped allocations are managed like a stack, this API
behaves properly if allocation scopes are popped in an order 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 If called with no arguments, it pops the most recent
scopedAllocPush() result: scopedAllocPush() result:
@@ -1181,8 +1188,9 @@ globalThis.WhWasmUtilInstaller = function(target){
if(target.functionEntry(p)){ if(target.functionEntry(p)){
//console.warn("scopedAllocPop() uninstalling function",p); //console.warn("scopedAllocPop() uninstalling function",p);
target.uninstallFunction(p); target.uninstallFunction(p);
}else{
target.dealloc(p);
} }
else target.dealloc(p);
} }
}; };
@@ -1206,9 +1214,7 @@ globalThis.WhWasmUtilInstaller = function(target){
if(!cache.scopedAlloc.length){ if(!cache.scopedAlloc.length){
toss("No scopedAllocPush() scope is active."); toss("No scopedAllocPush() scope is active.");
} }
const p = target.alloc(n); return cache.scopedAlloc.pushPtr(target.alloc(n));
cache.scopedAlloc[cache.scopedAlloc.length-1].push(p);
return p;
}; };
Object.defineProperty(target.scopedAlloc, 'level', { 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 value to dealloc(). The others are part of the same memory chunk
and must not be freed separately. 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, When one of the returned pointers will refer to a 64-bit value,
e.g. a double or int64, an that value must be written or fetched, e.g. 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 = Object.create(null);
cache.xWrap.convert = Object.create(null); cache.xWrap.convert = Object.create(null);
@@ -1438,8 +1444,9 @@ globalThis.WhWasmUtilInstaller = function(target){
.set('null', (v)=>v) .set('null', (v)=>v)
.set(null, xResult.get('null')); .set(null, xResult.get('null'));
{ /* Copy certain xArg[...] handlers to xResult[...] and { /* Copy xArg[...] handlers to xResult[...] for cases which have
add pointer-style variants of them. */ identical semantics. Also add pointer-style variants of
them. */
const copyToResult = ['i8', 'i16', 'i32', 'int', const copyToResult = ['i8', 'i16', 'i32', 'int',
'f32', 'float', 'f64', 'double']; 'f32', 'float', 'f64', 'double'];
if(target.bigIntEnabled) copyToResult.push('i64'); 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 - If v is a string, scopeAlloc() a new C-string from it and return
that temp string's pointer. 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? TODO? Permit an Int8Array/Uint8Array and convert it to a string?
Would that be too much magic concentrated in one place, ready to 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 Internal-use-only base class for FuncPtrAdapter and potentially
additional stateful argument adapter classes. additional stateful argument adapter classes.
Note that its main interface (convertArg()) is strictly Its main interface (convertArg()) is strictly internal, not to be
internal, not to be exposed to client code, as it may still exposed to client code, as it may still need re-shaping. Only the
need re-shaping. Only the constructors of concrete subclasses constructors of concrete subclasses should be exposed to clients,
should be exposed to clients, and those in such a way that and those in such a way that does not hinder internal redesign of
does not hinder internal redesign of the convertArg() the convertArg() interface.
interface.
*/ */
const AbstractArgAdapter = class { const AbstractArgAdapter = class {
constructor(opt){ constructor(opt){
@@ -1518,6 +1524,11 @@ globalThis.WhWasmUtilInstaller = function(target){
indexes must never be relied upon for anything because their indexes must never be relied upon for anything because their
types are indeterminate, whereas the LHS values will be types are indeterminate, whereas the LHS values will be
WASM-compatible values by the time this is called. 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){ convertArg(v,argv,argIndex){
toss("AbstractArgAdapter must be subclassed."); toss("AbstractArgAdapter must be subclassed.");
@@ -1525,12 +1536,11 @@ globalThis.WhWasmUtilInstaller = function(target){
}; };
/** /**
An attempt at adding function pointer conversion support to This type is recognized by xWrap() as a proxy for converting a JS
xWrap(). This type is recognized by xWrap() as a proxy for function to a C-side function, either permanently, for the
converting a JS function to a C-side function, either duration of a single call into the C layer, or semi-contextual,
permanently, for the duration of a single call into the C layer, where it may keep track of a single binding for a given context
or semi-contextual, where it may keep track of a single binding and uninstall the binding if it's replaced.
for a given context and uninstall the binding if it's replaced.
The constructor requires an options object with these properties: 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 - 'permanent': the function is installed and left there
forever. There is no way to recover its pointer address 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 - callProxy (function): if set, this must be a function which
will act as a proxy for any "converted" JS function. It is 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 either that function or a function which acts on its
behalf. The returned function will be the one which gets behalf. The returned function will be the one which gets
installed into the WASM function table. The proxy must perform installed into the WASM function table. The proxy must perform
any required argument conversion (noting that it will be called any required argument conversion (it will be called from C
from C code, so will receive C-format arguments) before passing code, so will receive C-format arguments) before passing them
them on to the being-converted function. Whether or not the on to the being-converted function. Whether or not the proxy
proxy itself must return a value depends on the context. If it itself must return a value depends on the context. If it does,
does, it must be a WASM-friendly value, as it will be returning it must be a WASM-friendly value, as it will be returning from
from a call made from native code. a call made from WASM code.
- contextKey (function): is only used if bindScope is 'context' - contextKey (function): is only used if bindScope is 'context'
or if bindScope is not set and this function is, in which case 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), FuncPtrAdapter instance (other instances are not considered),
taking into account that C functions often take some sort of taking into account that C functions often take some sort of
state object as one or more of their arguments. As an example, state object as one or more of their arguments. As an example,
if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and if the xWrap()'d function takes `(int,T*,functionPtr,X*)` then
this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2) this FuncPtrAdapter instance is argv[2], and contextKey(argv,2)
might return 'T@'+argv[1], or even just argv[1]. Note, 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 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 part of that key because its pre-conversion data type might be
unpredictable. Similarly, care must be taken with C-string-type 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 be WASM pointers, whereas those to the right might (and likely
do) have another data type. When using C-strings in keys, never do) have another data type. When using C-strings in keys, never
use their pointers in the key because most C-strings in this 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 The constructor only saves the above state for later, and does
not actually bind any functions. Its convertArg() method is not actually bind any functions. The conversion, if any, is
called via xWrap() to perform any bindings. performed when its convertArg() method is called via xWrap().
Shortcomings: Shortcomings:
@@ -1627,10 +1641,9 @@ globalThis.WhWasmUtilInstaller = function(target){
- Function pointers which include C-string arguments may still - Function pointers which include C-string arguments may still
need a level of hand-written wrappers around them, depending on need a level of hand-written wrappers around them, depending on
how they're used, in order to provide the client with JS how they're used, in order to provide the client with JS
strings. Alternately, clients will need to perform such conversions strings. Alternately, clients will need to perform such
on their own, e.g. using cstrToJs(). Or maybe we can find a way conversions on their own, e.g. using cstrToJs(). The purpose of
to perform such conversions here, via addition of an xWrap()-style the callProxy() method is to account for such cases.
function signature to the options argument.
*/ */
xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter { xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter {
constructor(opt) { constructor(opt) {
@@ -1662,9 +1675,9 @@ globalThis.WhWasmUtilInstaller = function(target){
} }
/** /**
Note that static class members are defined outside of the class The static class members are defined outside of the class to
to work around an emcc toolchain build problem: one of the work around an emcc toolchain build problem: one of the tools
tools in emsdk v3.1.42 does not support the static keyword. in emsdk v3.1.42 does not support the static keyword.
*/ */
/* Dummy impl. Overwritten per-instance as needed. */ /* Dummy impl. Overwritten per-instance as needed. */
@@ -1672,9 +1685,15 @@ globalThis.WhWasmUtilInstaller = function(target){
return this; 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 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){ contextMap(key){
const cm = (this.__cmap || (this.__cmap = new Map)); const cm = (this.__cmap || (this.__cmap = new Map));
let rc = cm.get(key); 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 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 (v||0) is returned and any earlier function installed by this
mapping _might_, depending on how it's bound, be uninstalled. mapping _might_, depending on how it's bound, be uninstalled.
If v is not one of those types, it must be a Function, for If v is not one of those types, it must be a Function, for
which it creates (if needed) a WASM function binding and which this method creates (if needed) a WASM function binding
returns the WASM pointer to that binding. If this instance is and returns the WASM pointer to that binding.
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_ If this instance is not in 'transient' mode, it will remember
perform any function binding, so this object's bindMode is the binding for at least the next call, to avoid recreating the
irrelevant for such cases. function binding unnecessarily.
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 See the parent class's convertArg() docs for details on what
exactly the 2nd and 3rd arguments are. exactly the 2nd and 3rd arguments are.
@@ -1708,11 +1729,16 @@ globalThis.WhWasmUtilInstaller = function(target){
pair = this.contextMap(this.contextKey(argv,argIndex)); pair = this.contextMap(this.contextKey(argv,argIndex));
//FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair); //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){ if(v instanceof Function){
/* Install a WASM binding and return its pointer. */ /* Install a WASM binding and return its pointer. */
//FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair); //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); const fp = __installFunction(v, this.signature, this.isTransient);
if(FuncPtrAdapter.debugFuncInstall){ if(FuncPtrAdapter.debugFuncInstall){
FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this,
@@ -1735,11 +1761,11 @@ globalThis.WhWasmUtilInstaller = function(target){
function. We're relying very much here on xWrap() function. We're relying very much here on xWrap()
having pushed an alloc scope. having pushed an alloc scope.
*/ */
cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]); cache.scopedAlloc.pushPtr(pair[1]);
} }
catch(e){/*ignored*/} catch(e){/*ignored*/}
} }
pair[0] = v; pair[0] = arguments[0]/*the original v*/;
pair[1] = fp; pair[1] = fp;
} }
return fp; return fp;
@@ -1751,7 +1777,7 @@ globalThis.WhWasmUtilInstaller = function(target){
FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
this.contextKey(argv,argIndex), '@'+pair[1], v); 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*/} catch(e){/*ignored*/}
pair[0] = pair[1] = (v | 0); pair[0] = pair[1] = (v | 0);
} }
@@ -1782,13 +1808,19 @@ globalThis.WhWasmUtilInstaller = function(target){
/** Function used for debug output. */ /** Function used for debug output. */
xArg.FuncPtrAdapter.debugOut = console.debug.bind(console); xArg.FuncPtrAdapter.debugOut = console.debug.bind(console);
/**
List of legal values for the FuncPtrAdapter bindScope config
option.
*/
xArg.FuncPtrAdapter.bindScopes = [ xArg.FuncPtrAdapter.bindScopes = [
'transient', 'context', 'singleton', 'permanent' 'transient', 'context', 'singleton', 'permanent'
]; ];
/** Throws if xArg.get(t) returns falsy. */
const __xArgAdapterCheck = const __xArgAdapterCheck =
(t)=>xArg.get(t) || toss("Argument adapter not found:",t); (t)=>xArg.get(t) || toss("Argument adapter not found:",t);
/** Throws if xResult.get(t) returns falsy. */
const __xResultAdapterCheck = const __xResultAdapterCheck =
(t)=>xResult.get(t) || toss("Result adapter not found:",t); (t)=>xResult.get(t) || toss("Result adapter not found:",t);
@@ -1942,8 +1974,8 @@ globalThis.WhWasmUtilInstaller = function(target){
```js ```js
target.xWrap.resultAdapter('string:my_free',(i)=>{ target.xWrap.resultAdapter('string:my_free',(i)=>{
try { return i ? target.cstrToJs(i) : null } try { return i ? target.cstrToJs(i) : null; }
finally{ target.exports.my_free(i) } finally{ target.exports.my_free(i); }
}; };
``` ```
@@ -2003,8 +2035,7 @@ globalThis.WhWasmUtilInstaller = function(target){
if(fIsFunc) fArg = xf.name || 'unnamed function'; if(fIsFunc) fArg = xf.name || 'unnamed function';
if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length); if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length);
if((null===resultType) && 0===xf.length){ if((null===resultType) && 0===xf.length){
/* Func taking no args with an as-is return. We don't need a wrapper. /* Func taking no args with an as-is return. We don't need a wrapper. */
We forego the argc check here, though. */
return xf; return xf;
} }
/*Verify the arg type conversions are valid...*/; /*Verify the arg type conversions are valid...*/;
@@ -2041,7 +2072,9 @@ globalThis.WhWasmUtilInstaller = function(target){
_not_ stable. _not_ stable.
Maintenance reminder: the Ember framework modifies the core 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; let i = 0;
for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck( for(; i < args.length; ++i) args[i] = cxw.convertArgNoCheck(
@@ -2054,7 +2087,28 @@ globalThis.WhWasmUtilInstaller = function(target){
}; };
}/*xWrap()*/; }/*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){ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){
if('string'===typeof typeName){ if('string'===typeof typeName){
if(1===argc) return xcvPart.get(typeName); if(1===argc) return xcvPart.get(typeName);
@@ -2089,16 +2143,16 @@ globalThis.WhWasmUtilInstaller = function(target){
xWrap.resultAdapter('twice',(v)=>v+v); xWrap.resultAdapter('twice',(v)=>v+v);
``` ```
xWrap.resultAdapter() MUST NOT use the scopedAlloc() family of Result adapters MUST NOT use the scopedAlloc() family of APIs to
APIs to allocate a result value. xWrap()-generated wrappers run allocate a result value. xWrap()-generated wrappers run in the
in the context of scopedAllocPush() so that argument adapters can context of scopedAllocPush() so that argument adapters can easily
easily convert, e.g., to C-strings, and have them cleaned up convert, e.g., to C-strings, and have them cleaned up
automatically before the wrapper returns to the caller. Likewise, automatically before the wrapper returns to the caller. Likewise,
if a _result_ adapter uses scoped allocation, the result will be if a _result_ adapter uses scoped allocation, the result will be
freed before the wrapper returns, leading to chaos and undefined freed before the wrapper returns, leading to chaos and undefined
behavior. 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){ target.xWrap.resultAdapter = function f(typeName, adapter){
return __xAdapter(f, arguments.length, typeName, adapter, return __xAdapter(f, arguments.length, typeName, adapter,
@@ -2122,13 +2176,13 @@ globalThis.WhWasmUtilInstaller = function(target){
}; };
``` ```
Contrariwise, xWrap.resultAdapter() must _not_ use scopedAlloc() Contrariwise, _result_ adapters _must not_ use scopedAlloc() to
to allocate its results because they would be freed before the allocate results because they would be freed before the
xWrap()-created wrapper returns. xWrap()-created wrapper returns.
Note that it is perfectly legitimate to use these adapters to It is perfectly legitimate to use these adapters to perform
perform argument validation, as opposed (or in addition) to argument validation, as opposed (or in addition) to conversion.
conversion. When used that way, they should throw for invalid arguments.
*/ */
target.xWrap.argAdapter = function f(typeName, adapter){ target.xWrap.argAdapter = function f(typeName, adapter){
return __xAdapter(f, arguments.length, 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 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 xWrap() to create a wrapper, then to call that wrapper as many
times as needed. For one-shot calls, however, this variant is times as needed. For one-shot calls, however, this variant is
arguably more efficient because it will hypothetically free the simpler.
wrapper function quickly.
*/ */
target.xCallWrapped = function(fArg, resultType, argTypes, ...args){ target.xCallWrapped = function(fArg, resultType, argTypes, ...args){
if(Array.isArray(arguments[3])) args = arguments[3]; 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 (The initial `then()` attached to the promise gets only that
that object, and not the `config` one.) 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 Error handling is up to the caller, who may attach a `catch()` call
to the promise. to the promise.

View File

@@ -1,5 +1,5 @@
C Fix\stypo\sin\sthe\sname\sof\sthe\sCursorHints\soptimization\sin\sthe\sCLI. 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-19T09:18:22.076 D 2025-09-19T14:21:09.961
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea 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-sahpool.c-pp.js 0f68a64e508598910e7c01214ae27d603dfc8baec6a184506fafac603a901931
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 4ab0704ee198de7d1059eccedc7703c931510b588d10af0ee36ea5b3ebbac284 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-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-promiser.c-pp.js 4ad256b4ff7f839ad18931ed35d46cced544207bd2209665ec552e193f7f4544
F ext/wasm/api/sqlite3-worker1.c-pp.js 5e8706c2c4af2a57fbcdc02f4e7ef79869971bc21bb8ede777687786ce1c92d5 F ext/wasm/api/sqlite3-worker1.c-pp.js 5e8706c2c4af2a57fbcdc02f4e7ef79869971bc21bb8ede777687786ce1c92d5
F ext/wasm/batch-runner-sahpool.html e9a38fdeb36a13eac7b50241dfe7ae066fe3f51f5c0b0151e7baee5fce0d07a7 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/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51
F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f 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/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a
F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 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-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 42c225a2ed7fc95f9b01467c64ba2bf97bca216fdcd6ab1ba3fb49c068650de9 P 468a11fd415710042b23880772f6c2c7771008208823fe3b554227a9244dbf92
R fec5e9cf9c4aa5198306438cd736e135 R 45b30de6d5f1d826a12d811876f55290
U drh U stephan
Z 1e7399cfaf854450b90c6eb04c4097ba Z 31a53fb96f385e882a14f4d3fa47d2d3
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
468a11fd415710042b23880772f6c2c7771008208823fe3b554227a9244dbf92 5e5139c2a162562cee0071d03954ebc0b8938da0b045ec3f5eba32dc8e19604d