diff options
author | stephan <stephan@noemail.net> | 2022-12-12 14:31:38 +0000 |
---|---|---|
committer | stephan <stephan@noemail.net> | 2022-12-12 14:31:38 +0000 |
commit | 124fc52d96f47899371782a3f3ed7f9cbf6bbeb8 (patch) | |
tree | 3c42d7c209ffd4fd44f15d01d0f1117ec5dd792b /ext/wasm/common/whwasmutil.js | |
parent | 5dbfc0dfdd5241bfc0cdc93915368e16ae50be2d (diff) | |
download | sqlite-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.js | 207 |
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 |