diff options
author | stephan <stephan@noemail.net> | 2022-10-27 03:03:16 +0000 |
---|---|---|
committer | stephan <stephan@noemail.net> | 2022-10-27 03:03:16 +0000 |
commit | de868175c3e82fb0b9c71d4b810bdb61c6f63231 (patch) | |
tree | e25d0e865b0d34ba21afd98748ecdbcdd487d7d1 /ext/wasm/api/sqlite3-api-prologue.js | |
parent | 195687f1bfaf15460da345d0b5faae31a0c1eff3 (diff) | |
download | sqlite-de868175c3e82fb0b9c71d4b810bdb61c6f63231.tar.gz sqlite-de868175c3e82fb0b9c71d4b810bdb61c6f63231.zip |
Expose sqlite3_randomness() to WASM and add a custom binding for it which can populate a JS byte array. Add WhWasmUtil.isPtr().
FossilOrigin-Name: 333e67076b4bc967bb543ef8e265c63f6e3498c38ac121a7d1eff4a1d7a71c63
Diffstat (limited to 'ext/wasm/api/sqlite3-api-prologue.js')
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 143 |
1 files changed, 111 insertions, 32 deletions
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 1fcc9a64a..71058d174 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -215,6 +215,32 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false; }; + + /** Internal helper to use in operations which need to distinguish + between TypedArrays which are backed by a SharedArrayBuffer + from those which are not. */ + const __SAB = ('undefined'===typeof SharedArrayBuffer) + ? function(){} : SharedArrayBuffer; + /** Returns true if the given TypedArray object is backed by a + SharedArrayBuffer, else false. */ + const isSharedTypedArray = (aTypedArray)=>(aTypedArray.buffer instanceof __SAB); + + /** + Returns either aTypedArray.slice(begin,end) (if + aTypedArray.buffer is a SharedArrayBuffer) or + aTypedArray.subarray(begin,end) (if it's not). + + This distinction is important for APIs which don't like to + work on SABs, e.g. TextDecoder, and possibly for our + own APIs which work on memory ranges which "might" be + modified by other threads while it's working. + */ + const typedArrayPart = (aTypedArray, begin, end)=>{ + return isSharedTypedArray(aTypedArray) + ? aTypedArray.slice(begin, end) + : aTypedArray.subarray(begin, end); + }; + /** Returns true if v appears to be one of our bind()-able TypedArray types: Uint8Array or Int8Array. Support for @@ -246,16 +272,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const utf8Decoder = new TextDecoder('utf-8'); - /** Internal helper to use in operations which need to distinguish - between SharedArrayBuffer heap memory and non-shared heap. */ - const __SAB = ('undefined'===typeof SharedArrayBuffer) - ? function(){} : SharedArrayBuffer; - const typedArrayToString = function(arrayBuffer, begin, end){ - return utf8Decoder.decode( - (arrayBuffer.buffer instanceof __SAB) - ? arrayBuffer.slice(begin, end) - : arrayBuffer.subarray(begin, end) - ); + /** + Uses TextDecoder to decode the given half-open range of the + given TypedArray to a string. This differs from a simple + call to TextDecoder in that it accounts for whether the + first argument is based by a SharedArrayBuffer or not, + and can work more efficiently if it's not (TextDecoder + refuses to act upon an SAB). + */ + const typedArrayToString = function(typedArray, begin, end){ + return utf8Decoder.decode(typedArrayPart(typedArray, begin,end)); }; /** @@ -502,11 +528,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( If the callback is a function, then for the duration of the sqlite3_exec() call, it installs a WASM-bound function which - acts as a proxy for the given callback. That proxy will - also perform a conversion of the callback's arguments from + acts as a proxy for the given callback. That proxy will also + perform a conversion of the callback's arguments from `(char**)` to JS arrays of strings. However, for API - consistency's sake it will still honor the C-level - callback parameter order and will call it like: + consistency's sake it will still honor the C-level callback + parameter order and will call it like: `callback(pVoid, colCount, listOfValues, listOfColNames)` @@ -518,11 +544,26 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, /** + If passed a single argument which appears to be a byte-oriented + TypedArray (Int8Array or Uint8Array), this function treats that + TypedArray as an output target, fetches `theArray.byteLength` + bytes of randomness, and populates the whole array with it. As + a special case, if the array's length is 0, this function + behaves as if it were passed (0,0). When called this way, it + returns its argument, else it returns the `undefined` value. + + If called with any other arguments, they are passed on as-is + to the C API. Results are undefined if passed any incompatible + values. + */ + sqlite3_randomness: (n, outPtr)=>{/*installed later*/}, + + /** Various internal-use utilities are added here as needed. They are bound to an object only so that we have access to them in the differently-scoped steps of the API bootstrapping process. At the end of the API setup process, this object gets - removed. + removed. These are NOT part of the public API. */ util:{ affirmBindableTypedArray, flexibleString, @@ -530,7 +571,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( isBindableTypedArray, isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, - isUIThread: ()=>'undefined'===typeof WorkerGlobalScope + isUIThread: ()=>'undefined'===typeof WorkerGlobalScope, + isSharedTypedArray, + typedArrayPart }, /** @@ -617,7 +660,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }/*wasm*/ }/*capi*/; - const wasm = capi.wasm; + const wasm = capi.wasm, util = capi.util; /** wasm.alloc()'s srcTypedArray.byteLength bytes, @@ -743,8 +786,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( // Please keep these sorted by function name! ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" - /* We should arguably write a custom wrapper which knows how - to handle Blob, TypedArrays, and JS strings. */ + /* TODO: we should arguably write a custom wrapper which knows + how to handle Blob, TypedArrays, and JS strings. */ ], ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], ["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"], @@ -752,10 +795,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" - /* We should arguably create a hand-written binding - which does more flexible text conversion, along the lines of - sqlite3_prepare_v3(). The slightly problematic part is the - final argument (text destructor). */ + /* We should arguably create a hand-written binding of + bind_text() which does more flexible text conversion, along + the lines of sqlite3_prepare_v3(). The slightly problematic + part is the final argument (text destructor). */ ], ["sqlite3_close_v2", "int", "sqlite3*"], ["sqlite3_changes", "int", "sqlite3*"], @@ -770,15 +813,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], ["sqlite3_compileoption_get", "string", "int"], ["sqlite3_compileoption_used", "int", "string"], - /* sqlite3_create_function_v2() is handled separate to simplify conversion - of its callback argument */ + /* sqlite3_create_function(), sqlite3_create_function_v2(), and + sqlite3_create_window_function() use hand-written bindings to + simplify handling of their function-type arguments. */ ["sqlite3_data_count", "int", "sqlite3_stmt*"], ["sqlite3_db_filename", "string", "sqlite3*", "string"], ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], ["sqlite3_db_name", "string", "sqlite3*", "int"], ["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"] /* Careful! Short version: de/serialize() are problematic because they - might use a different allocator that the user for managing the + might use a different allocator than the user for managing the deserialized block. de/serialize() are ONLY safe to use with sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */, ["sqlite3_errmsg", "string", "sqlite3*"], @@ -806,6 +850,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled separately due to us requiring two different sets of semantics for those, depending on how their SQL argument is provided. */ + /* sqlite3_randomness() uses a hand-written wrapper to extend + the range of supported argument types. */ ["sqlite3_realloc", "*","*","int"], ["sqlite3_reset", "int", "sqlite3_stmt*"], ["sqlite3_result_blob",undefined, "*", "*", "int", "*"], @@ -1052,8 +1098,41 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( throw new SQLite3Error(...args); }; + capi.sqlite3_randomness = (...args)=>{ + if(1===args.length && util.isTypedArray(args[0]) + && 1===args[0].BYTES_PER_ELEMENT){ + const ta = args[0]; + if(0===ta.byteLength){ + wasm.exports.sqlite3_randomness(0,0); + return ta; + } + const stack = wasm.pstack.pointer; + try { + let n = ta.byteLength, offset = 0; + const r = wasm.exports.sqlite3_randomness; + const heap = wasm.heap8u(); + const nAlloc = n < 512 ? n : 512; + const ptr = wasm.pstack.alloc(nAlloc); + do{ + const j = (n>nAlloc ? nAlloc : n); + r(j, ptr); + ta.set(typedArrayPart(heap, ptr, ptr+j), offset); + n -= j; + offset += j; + } while(n > 0); + }catch(e){ + console.error("Highly unexpected (and ignored!) "+ + "exception in sqlite3_randomness():",e); + }finally{ + wasm.pstack.restore(stack); + } + return ta; + } + capi.wasm.exports.sqlite3_randomness(...args); + }; + /** State for sqlite3_wasmfs_opfs_dir(). */ - let __persistentDir = undefined; + let __wasmfsOpfsDir = undefined; /** If the wasm environment has a WASMFS/OPFS-backed persistent storage directory, its path is returned by this function. If it @@ -1068,26 +1147,26 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Emscripten-managed virtual filesystem. */ capi.sqlite3_wasmfs_opfs_dir = function(){ - if(undefined !== __persistentDir) return __persistentDir; + if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir; // If we have no OPFS, there is no persistent dir const pdir = config.wasmfsOpfsDir; if(!pdir || !self.FileSystemHandle || !self.FileSystemDirectoryHandle || !self.FileSystemFileHandle){ - return __persistentDir = ""; + return __wasmfsOpfsDir = ""; } try{ if(pdir && 0===wasm.xCallWrapped( 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir )){ - return __persistentDir = pdir; + return __wasmfsOpfsDir = pdir; }else{ - return __persistentDir = ""; + return __wasmfsOpfsDir = ""; } }catch(e){ // sqlite3_wasm_init_wasmfs() is not available - return __persistentDir = ""; + return __wasmfsOpfsDir = ""; } }; |