aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/common/whwasmutil.js
diff options
context:
space:
mode:
authorstephan <stephan@noemail.net>2022-12-12 14:31:38 +0000
committerstephan <stephan@noemail.net>2022-12-12 14:31:38 +0000
commit124fc52d96f47899371782a3f3ed7f9cbf6bbeb8 (patch)
tree3c42d7c209ffd4fd44f15d01d0f1117ec5dd792b /ext/wasm/common/whwasmutil.js
parent5dbfc0dfdd5241bfc0cdc93915368e16ae50be2d (diff)
downloadsqlite-124fc52d96f47899371782a3f3ed7f9cbf6bbeb8.tar.gz
sqlite-124fc52d96f47899371782a3f3ed7f9cbf6bbeb8.zip
Move JS-to-C binding signatures from sqlite3-api-prologue.js to sqlite3-api-glue.js to allow for use of the new/experimental sqlite3.wasm.xWrap() feature which automatically binds JS functions to WASM/C as needed, which simplifies creation of bindings which take C function pointers. Reimplement sqlite3_exec(), sqlite3_create_collation(), sqlite3_progress_handler() to use this new feature.
FossilOrigin-Name: 9386d6f634680b4e0fa5487c34c63acb29f0b7a6ae738b8f6164ad084a229b62
Diffstat (limited to 'ext/wasm/common/whwasmutil.js')
-rw-r--r--ext/wasm/common/whwasmutil.js207
1 files changed, 166 insertions, 41 deletions
diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js
index d8ac70c0b..0885db112 100644
--- a/ext/wasm/common/whwasmutil.js
+++ b/ext/wasm/common/whwasmutil.js
@@ -494,47 +494,27 @@ self.WhWasmUtilInstaller = function(target){
e: { f: func }
})).exports['f'];
}/*jsFuncToWasm()*/;
-
- /**
- Expects a JS function and signature, exactly as for
- this.jsFuncToWasm(). It uses that function to create a
- WASM-exported function, installs that function to the next
- available slot of this.functionTable(), and returns the
- function's index in that table (which acts as a pointer to that
- function). The returned pointer can be passed to
- uninstallFunction() to uninstall it and free up the table slot for
- reuse.
-
- If passed (string,function) arguments then it treats the first
- argument as the signature and second as the function.
-
- As a special case, if the passed-in function is a WASM-exported
- function then the signature argument is ignored and func is
- installed as-is, without requiring re-compilation/re-wrapping.
-
- This function will propagate an exception if
- WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
- The former case can happen in an Emscripten-compiled
- environment when building without Emscripten's
- `-sALLOW_TABLE_GROWTH` flag.
- Sidebar: this function differs from Emscripten's addFunction()
- _primarily_ in that it does not share that function's
- undocumented behavior of reusing a function if it's passed to
- addFunction() more than once, which leads to uninstallFunction()
- breaking clients which do not take care to avoid that case:
-
- https://github.com/emscripten-core/emscripten/issues/17323
- */
- target.installFunction = function f(func, sig){
- if(2!==arguments.length){
- toss("installFunction() requires exactly 2 arguments");
+ /**
+ Documented as target.installFunction() except for the 3rd
+ argument: if truthy, the newly-created function pointer
+ is stashed in the current scoped-alloc scope and will be
+ cleaned up at the matching scopedAllocPop(), else it
+ is not stashed there.
+ */
+ const __installFunction = function f(func, sig, scoped){
+ if(scoped && !cache.scopedAlloc.length){
+ toss("No scopedAllocPush() scope is active.");
}
if('string'===typeof func){
const x = sig;
sig = func;
func = x;
}
+ if('string'!==typeof sig || !(func instanceof Function)){
+ toss("Invalid arguments: expecting (function,signature) "+
+ "or (signature,function).");
+ }
const ft = target.functionTable();
const oldLen = ft.length;
let ptr;
@@ -554,6 +534,9 @@ self.WhWasmUtilInstaller = function(target){
try{
/*this will only work if func is a WASM-exported function*/
ft.set(ptr, func);
+ if(scoped){
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
+ }
return ptr;
}catch(e){
if(!(e instanceof TypeError)){
@@ -563,14 +546,64 @@ self.WhWasmUtilInstaller = function(target){
}
// It's not a WASM-exported function, so compile one...
try {
- ft.set(ptr, target.jsFuncToWasm(func, sig));
+ const fptr = target.jsFuncToWasm(func, sig);
+ ft.set(ptr, fptr);
+ if(scoped){
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
+ }
}catch(e){
- if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
- throw e;
+ if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
+ throw e;
}
return ptr;
};
+
+ /**
+ Expects a JS function and signature, exactly as for
+ this.jsFuncToWasm(). It uses that function to create a
+ WASM-exported function, installs that function to the next
+ available slot of this.functionTable(), and returns the
+ function's index in that table (which acts as a pointer to that
+ function). The returned pointer can be passed to
+ uninstallFunction() to uninstall it and free up the table slot for
+ reuse.
+
+ If passed (string,function) arguments then it treats the first
+ argument as the signature and second as the function.
+
+ As a special case, if the passed-in function is a WASM-exported
+ function then the signature argument is ignored and func is
+ installed as-is, without requiring re-compilation/re-wrapping.
+
+ This function will propagate an exception if
+ WebAssembly.Table.grow() throws or this.jsFuncToWasm() throws.
+ The former case can happen in an Emscripten-compiled
+ environment when building without Emscripten's
+ `-sALLOW_TABLE_GROWTH` flag.
+
+ Sidebar: this function differs from Emscripten's addFunction()
+ _primarily_ in that it does not share that function's
+ undocumented behavior of reusing a function if it's passed to
+ addFunction() more than once, which leads to uninstallFunction()
+ breaking clients which do not take care to avoid that case:
+
+ https://github.com/emscripten-core/emscripten/issues/17323
+ */
+ target.installFunction = (func, sig)=>__installFunction(func, sig, false);
+
+ /**
+ EXPERIMENTAL! DO NOT USE IN CLIENT CODE!
+
+ Works exactly like installFunction() but requires that a
+ scopedAllocPush() is active and uninstalls the given function
+ when that alloc scope is popped via scopedAllocPop().
+ This is used for implementing JS/WASM function bindings which
+ should only persist for the life of a call into a single
+ C-side function.
+ */
+ target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true);
+
/**
Requires a pointer value previously returned from
this.installFunction(). Removes that function from the WASM
@@ -1083,7 +1116,13 @@ self.WhWasmUtilInstaller = function(target){
if(n<0) toss("Invalid state object for scopedAllocPop().");
if(0===arguments.length) state = cache.scopedAlloc[n];
cache.scopedAlloc.splice(n,1);
- for(let p; (p = state.pop()); ) target.dealloc(p);
+ for(let p; (p = state.pop()); ){
+ if(target.functionEntry(p)){
+ //console.warn("scopedAllocPop() uninstalling transient function",p);
+ target.uninstallFunction(p);
+ }
+ else target.dealloc(p);
+ }
};
/**
@@ -1303,12 +1342,12 @@ self.WhWasmUtilInstaller = function(target){
State for use with xWrap()
*/
cache.xWrap = Object.create(null);
- const xcv = cache.xWrap.convert = Object.create(null);
+ cache.xWrap.convert = Object.create(null);
/** Map of type names to argument conversion functions. */
cache.xWrap.convert.arg = new Map;
/** Map of type names to return result conversion functions. */
cache.xWrap.convert.result = new Map;
- const xArg = xcv.arg, xResult = xcv.result;
+ const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result;
if(target.bigIntEnabled){
xArg.set('i64', (i)=>BigInt(i));
@@ -1395,6 +1434,87 @@ self.WhWasmUtilInstaller = function(target){
});
}
+ const __FuncPtrBindModes = ['transient','static','singleton'];
+ /**
+ EXPERIMENTAL.
+
+ 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
+ or only for the duration of a a single call into the C layer.
+ */
+ xArg.FuncPtrAdapter = class {
+ /**
+ Requires an options object with these properties:
+
+ - signature: an function signature compatible with
+ jsFuncToWasm().
+
+ - bindMode (string): one of ('transient', 'static',
+ 'singleton'). If 'transient', it uses
+ scopedInstallFunction() for its function bindings, meaning
+ they're limited to the lifetime of a single xWrap()-induced
+ function call. If it's 'static', the binding is permanent,
+ lasting the life of the WASM environment. If it's 'singleton'
+ then this function remembers the last function it installed
+ and uninstalls it before installing any replacements on
+ subsequent calls. If it's passed the exact same JS function
+ to install later, it will re-use the existing binding.
+
+ - name (optional): string describing the function binding. This
+ is solely for debugging and error-reporting purposes. If not
+ provided, an empty string is assumed.
+
+ The constructor only saves the above state for later, and does
+ not actually bind any functions. Its convertArg() methor is
+ called via xWrap() to perform any bindings.
+ */
+ constructor(opt){
+ this.signature = opt.signature;
+ if(__FuncPtrBindModes.indexOf(opt.bindMode)<0){
+ toss("Invalid options.bindMode ("+opt.bindMod+") for FuncPtrAdapter. "+
+ "Expecting one of: ("+__FuncPtrBindModes.join(', ')+')');
+ }
+ this.bindMode = opt.bindMode;
+ this.name = opt.name || '';
+ this.singleton = ('singleton'===this.bindMode) ? [] : undefined;
+ //console.warn("FuncPtrAdapter()",this.signature,this.transient);
+ }
+ /**
+ Gets called via xWrap() to "convert" v to a WASM-bound function
+ pointer. If v is one of (a pointer, null, undefined) then
+ (v||0) is returned, otherwise v must be a Function, for which
+ xit creates (if needed) a WASM function binding and returns the
+ WASM pointer to that binding. It throws if passed an invalid
+ type.
+ */
+ convertArg(v){
+ //console.warn("FuncPtrAdapter.convertArg()",this.signature,this.transient,v);
+ if(v instanceof Function){
+ if(this.singleton && this.singleton[0]===v){
+ return this.singleton[1];
+ }
+ const fp = __installFunction(v, this.signature, 'transient'===this.bindMode);
+ if(this.singleton){
+ if(this.singleton[1]){
+ try{target.uninstallFunction(this.singleton[1])}
+ catch(e){/*ignored*/}
+ }
+ this.singleton[0] = v;
+ this.singleton[1] = fp;
+ }
+ return fp;
+ }else if(target.isPtr(v) || null===v || undefined===v){
+ return v || 0;
+ }else{
+ throw new TypeError("Invalid FuncPtrAdapter argument type. "+
+ "Expecting "+(this.name ? this.name+' ' : '')+
+ "function matching signature "+
+ this.signature+".");
+ }
+ }
+ };
+
const __xArgAdapterCheck =
(t)=>xArg.get(t) || toss("Argument adapter not found:",t);
@@ -1565,7 +1685,10 @@ self.WhWasmUtilInstaller = function(target){
}
/*Verify the arg type conversions are valid...*/;
if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType);
- argTypes.forEach(__xArgAdapterCheck);
+ for(const t of argTypes){
+ if(t instanceof xArg.FuncPtrAdapter) xArg.set(t, (v)=>t.convertArg(v));
+ else __xArgAdapterCheck(t);
+ }
const cxw = cache.xWrap;
if(0===xf.length){
// No args to convert, so we can create a simpler wrapper...
@@ -1666,6 +1789,8 @@ self.WhWasmUtilInstaller = function(target){
'argAdapter()', xArg);
};
+ target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter;
+
/**
Functions like xCall() but performs argument and result type
conversions as for xWrap(). The first argument is the name of the