diff options
Diffstat (limited to 'ext/wasm/api')
-rw-r--r-- | ext/wasm/api/sqlite3-api-glue.js | 50 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 163 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-wasm.c | 51 |
3 files changed, 142 insertions, 122 deletions
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 27d75682a..2347a40c0 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -560,56 +560,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; }/*sqlite3_prepare_v2/v3()*/; - if(1){// Extend wasm.pstack, now that the wasm utils are installed - /** - Allocates n chunks, each sz bytes, as a single memory block and - returns the addresses as an array of n element, each holding - the address of one chunk. - - Throws a WasmAllocError if allocation fails. - - Example: - - ``` - const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); - ``` - */ - wasm.pstack.allocChunks = (n,sz)=>{ - const mem = wasm.pstack.alloc(n * sz); - const rc = []; - let i = 0, offset = 0; - for(; i < n; offset = (sz * ++i)){ - rc.push(mem + offset); - } - return rc; - }; - - /** - A convenience wrapper for allocChunks() which sizes each chunks - as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if - safePtrSize is falsy). - - How it returns its result differs depending on its first - argument: if it's 1, it returns a single pointer value. If it's - more than 1, it returns the same as allocChunks(). - - When a returned pointers will refer to a 64-bit value, e.g. a - double or int64, and that value must be written or fetched, - e.g. using wasm.setMemValue() or wasm.getMemValue(), it is - important that the pointer in question be aligned to an 8-byte - boundary or else it will not be fetched or written properly and - will corrupt or read neighboring memory. - - However, when all pointers involved point to "small" data, it - is safe to pass a falsy value to save to memory. - */ - wasm.pstack.allocPtr = (n=1,safePtrSize=true) =>{ - return 1===n - ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) - : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); - }; - }/*wasm.pstack filler*/ - {/* Import C-level constants and structs... */ const cJson = wasm.xCall('sqlite3_wasm_enum_json'); if(!cJson){ diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index adf56b3a0..59537a499 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -610,12 +610,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }/*wasm*/ }/*capi*/; + const wasm = capi.wasm; + /** - capi.wasm.alloc()'s srcTypedArray.byteLength bytes, + wasm.alloc()'s srcTypedArray.byteLength bytes, populates them with the values from the source TypedArray, and returns the pointer to that memory. The returned pointer must eventually be passed to - capi.wasm.dealloc() to clean it up. + wasm.dealloc() to clean it up. As a special case, to avoid further special cases where this is used, if srcTypedArray.byteLength is 0, it @@ -628,27 +630,27 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Int8Array types and will throw if srcTypedArray is of any other type. */ - capi.wasm.allocFromTypedArray = function(srcTypedArray){ + wasm.allocFromTypedArray = function(srcTypedArray){ affirmBindableTypedArray(srcTypedArray); - const pRet = capi.wasm.alloc(srcTypedArray.byteLength || 1); - capi.wasm.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet); + const pRet = wasm.alloc(srcTypedArray.byteLength || 1); + wasm.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet); return pRet; }; const keyAlloc = config.allocExportName || 'malloc', keyDealloc = config.deallocExportName || 'free'; for(const key of [keyAlloc, keyDealloc]){ - const f = capi.wasm.exports[key]; + const f = wasm.exports[key]; if(!(f instanceof Function)) toss("Missing required exports[",key,"] function."); } - capi.wasm.alloc = function(n){ - const m = capi.wasm.exports[keyAlloc](n); + wasm.alloc = function(n){ + const m = wasm.exports[keyAlloc](n); if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes."); return m; }; - capi.wasm.dealloc = (m)=>capi.wasm.exports[keyDealloc](m); + wasm.dealloc = (m)=>wasm.exports[keyDealloc](m); /** Reports info about compile-time options using @@ -679,7 +681,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( "SQLITE_" prefix. When it returns an object of all options, the prefix is elided. */ - capi.wasm.compileOptionUsed = function f(optName){ + wasm.compileOptionUsed = function f(optName){ if(!arguments.length){ if(f._result) return f._result; else if(!f._opt){ @@ -720,7 +722,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( is an array with 2+ elements: [ "c-side name", - "result type" (capi.wasm.xWrap() syntax), + "result type" (wasm.xWrap() syntax), [arg types in xWrap() syntax] // ^^^ this needn't strictly be an array: it can be subsequent // elements instead: [x,y,z] is equivalent to x,y,z @@ -730,7 +732,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( result/argument type strings gets plugged in at a later phase in the API initialization process. */ - capi.wasm.bindingSignatures = [ + wasm.bindingSignatures = [ // Please keep these sorted by function name! ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" @@ -829,13 +831,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_value_type", "int", "sqlite3_value*"], ["sqlite3_vfs_find", "*", "string"], ["sqlite3_vfs_register", "int", "*", "int"] - ]/*capi.wasm.bindingSignatures*/; + ]/*wasm.bindingSignatures*/; - if(false && capi.wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ + if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){ /* ^^^ "the problem" is that this is an option feature and the build-time function-export list does not currently take optional features into account. */ - capi.wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); + wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } /** @@ -843,7 +845,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( the others because we need to conditionally bind them or apply dummy impls, depending on the capabilities of the environment. */ - capi.wasm.bindingSignatures.int64 = [ + wasm.bindingSignatures.int64 = [ ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], ["sqlite3_changes64","i64", ["sqlite3*"]], ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], @@ -859,18 +861,18 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( /** Functions which are intended solely for API-internal use by the WASM components, not client code. These get installed into - capi.wasm. + wasm. TODO: get rid of sqlite3_wasm_vfs_unlink(). It is ill-conceived and only rarely actually useful. */ - capi.wasm.bindingSignatures.wasm = [ + wasm.bindingSignatures.wasm = [ ["sqlite3_wasm_vfs_unlink", "int", "string"] ]; /** - sqlite3.capi.wasm.pstack (pseudo-stack) holds a special-case + sqlite3.wasm.pstack (pseudo-stack) holds a special-case stack-style allocator intended only for use with _small_ data of not more than (in total) a few kb in size, managed as if it were stack-based. @@ -900,13 +902,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( memory lives in the WASM heap and can be used with routines such as wasm.setMemValue() and any wasm.heap8u().slice(). */ - capi.wasm.pstack = Object.assign(Object.create(null),{ + wasm.pstack = Object.assign(Object.create(null),{ /** Sets the current ppstack position to the given pointer. Results are undefined if the passed-in value did not come from this.pointer. */ - restore: capi.wasm.exports.sqlite3_wasm_pstack_restore, + restore: wasm.exports.sqlite3_wasm_pstack_restore, /** Attempts to allocate the given number of bytes from the pstack. On success, it zeroes out a block of memory of the @@ -920,31 +922,89 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( heap. */ alloc: (n)=>{ - return capi.wasm.exports.sqlite3_wasm_pstack_alloc(n) + return wasm.exports.sqlite3_wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); + }, + /** + Allocates n chunks, each sz bytes, as a single memory block and + returns the addresses as an array of n element, each holding + the address of one chunk. + + Throws a WasmAllocError if allocation fails. + + Example: + + ``` + const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); + ``` + */ + allocChunks: (n,sz)=>{ + const mem = wasm.pstack.alloc(n * sz); + const r += []; + let i = 0, offset = 0; + for(; i < n; offset = (sz * ++i)){ + rc.push(mem + offset); + } + return rc; + }, + /** + A convenience wrapper for allocChunks() which sizes each chunks + as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if + safePtrSize is falsy). + + How it returns its result differs depending on its first + argument: if it's 1, it returns a single pointer value. If it's + more than 1, it returns the same as allocChunks(). + + When a returned pointers will refer to a 64-bit value, e.g. a + double or int64, and that value must be written or fetched, + e.g. using wasm.setMemValue() or wasm.getMemValue(), it is + important that the pointer in question be aligned to an 8-byte + boundary or else it will not be fetched or written properly and + will corrupt or read neighboring memory. + + However, when all pointers involved point to "small" data, it + is safe to pass a falsy value to save to memory. + */ + allocPtr: (n=1,safePtrSize=true)=>{ + return 1===n + ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof) + : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof); } - // More methods get added after the capi.wasm object is populated - // by WhWasmUtilInstaller. - }); - /** - sqlite3.capi.wasm.pstack.pointer resolves to the current pstack - position pointer. This value is intended _only_ to be passed to restore(). - */ - Object.defineProperty(capi.wasm.pstack, 'pointer', { - configurable: false, iterable: true, writeable: false, - get: capi.wasm.exports.sqlite3_wasm_pstack_ptr - //Whether or not a setter as an alternative to restore() is - //clearer or would just lead to confusion is unclear. - //set: capi.wasm.exports.sqlite3_wasm_pstack_restore - }); + })/*wasm.pstack*/; + Object.defineProperties(wasm.pstack, { + /** + sqlite3.wasm.pstack.pointer resolves to the current pstack + position pointer. This value is intended _only_ to be passed to + restore(). + */ + pointer: { + configurable: false, iterable: true, writeable: false, + get: wasm.exports.sqlite3_wasm_pstack_ptr + //Whether or not a setter as an alternative to restore() is + //clearer or would just lead to confusion is unclear. + //set: wasm.exports.sqlite3_wasm_pstack_restore + }, + /** + Resolves to the total number of bytes available in the pstack, + including any space which is currently allocated. This value is + a compile-time constant. + */ + quota: { + configurable: false, iterable: true, writeable: false, + get: wasm.exports.sqlite3_wasm_pstack_quota + } + })/*wasm.pstack properties*/; + /** - sqlite3.capi.wasm.pstack.remaining resolves to the amount of + sqlite3.wasm.pstack.remaining resolves to the amount of space remaining in the pstack. */ - Object.defineProperty(capi.wasm.pstack, 'remaining', { + Object.defineProperty(wasm.pstack, 'remaining', { configurable: false, iterable: true, writeable: false, - get: capi.wasm.exports.sqlite3_wasm_pstack_remaining + get: wasm.exports.sqlite3_wasm_pstack_remaining }); /** @@ -994,7 +1054,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return __persistentDir = ""; } try{ - if(pdir && 0===capi.wasm.xCallWrapped( + if(pdir && 0===wasm.xCallWrapped( 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir )){ return __persistentDir = pdir; @@ -1024,10 +1084,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; // This bit is highly arguable and is incompatible with the fiddle shell. - if(false && 0===capi.wasm.exports.sqlite3_vfs_find(0)){ + if(false && 0===wasm.exports.sqlite3_vfs_find(0)){ /* Assume that sqlite3_initialize() has not yet been called. This will be the case in an SQLITE_OS_KV build. */ - capi.wasm.exports.sqlite3_initialize(); + wasm.exports.sqlite3_initialize(); } /** @@ -1058,15 +1118,15 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( else if(!pDb){ return capi.sqlite3_vfs_find(0)===pK ? pK : false; } - const ppVfs = capi.wasm.allocPtr(); + const ppVfs = wasm.allocPtr(); try{ return ( (0===capi.sqlite3_file_control( pDb, dbName, capi.SQLITE_FCNTL_VFS_POINTER, ppVfs - )) && (capi.wasm.getPtrValue(ppVfs) === pK) + )) && (wasm.getPtrValue(ppVfs) === pK) ) ? pK : false; }finally{ - capi.wasm.dealloc(ppVfs); + wasm.dealloc(ppVfs); } }catch(e){ /* Ignore - probably bad args to a wasm-bound function. */ @@ -1083,7 +1143,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( let pVfs = capi.sqlite3_vfs_find(0); while(pVfs){ const oVfs = new capi.sqlite3_vfs(pVfs); - rc.push(capi.wasm.cstringToJs(oVfs.$zName)); + rc.push(wasm.cstringToJs(oVfs.$zName)); pVfs = oVfs.$pNext; oVfs.dispose(); } @@ -1097,7 +1157,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( */ capi.sqlite3_web_db_export = function(pDb){ if(!pDb) toss('Invalid sqlite3* argument.'); - const wasm = capi.wasm; + const wasm = wasm; if(!wasm.bigIntEnabled) toss('BigInt64 support is not enabled.'); const stack = wasm.pstack.pointer; let pOut; @@ -1278,7 +1338,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( Note that the order of insertion into this array is significant for some pieces. e.g. sqlite3.capi.wasm cannot be fully utilized until - the whwasmutil.js part is plugged in. + the whwasmutil.js part is plugged in via sqlite3-api-glue.js. */ self.sqlite3ApiBootstrap.initializers = []; /** @@ -1290,12 +1350,15 @@ self.sqlite3ApiBootstrap.initializers = []; Counterpart of self.sqlite3ApiBootstrap.initializers, specifically for initializers which are asynchronous. All functions in this list take the sqlite3 object as their argument and MUST return a - Promise. Both the resolved value and rejection cases are ignored. + Promise. The resolved value and ignored and rejection will kill the + asyncPostInit() process but will be otherwise ignored because the + post-synchronous-init async initialization parts are (as of this + writing) all optional. This list is not processed until the client calls sqlite3.asyncPostInit(). This means, for example, that intializers added to self.sqlite3ApiBootstrap.initializers may push entries to - this list. + this list. */ self.sqlite3ApiBootstrap.initializersAsync = []; /** diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index f7acbc29c..7ed58dbe0 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -135,13 +135,13 @@ WASM_KEEP void * sqlite3_wasm_stack_begin(void){ extern void __data_end; return &__data_end; } -static void * sq3StackPtr = 0; +static void * pWasmStackPtr = 0; WASM_KEEP void * sqlite3_wasm_stack_ptr(void){ - if(!sq3StackPtr) sq3StackPtr = sqlite3_wasm_stack_end(); - return sq3StackPtr; + if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end(); + return pWasmStackPtr; } WASM_KEEP void sqlite3_wasm_stack_restore(void * p){ - sq3StackPtr = p; + pWasmStackPtr = p; } WASM_KEEP void * sqlite3_wasm_stack_alloc(int n){ if(n<=0) return 0; @@ -149,7 +149,7 @@ WASM_KEEP void * sqlite3_wasm_stack_alloc(int n){ unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr(); unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin(); if(b + n >= p || b + n < b/*overflow*/) return 0; - return sq3StackPtr = p - n; + return pWasmStackPtr = p - n; } #endif /* stack allocator experiment */ @@ -205,10 +205,9 @@ WASM_KEEP void * sqlite3_wasm_pstack_alloc(int n){ if( n<=0 ) return 0; //if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */; n = (n + 7) & ~7 /* align to 8-byte boundary */; - unsigned char * const p = PStack.pPos; - unsigned const char * const b = PStack.pBegin; - if( b + n > p || b + n <= b/*overflow*/ ) return 0; - memset((PStack.pPos = p - n), 0, (unsigned int)n); + if( PStack.pBegin + n > PStack.pPos /*not enough space left*/ + || PStack.pBegin + n <= PStack.pBegin /*overflow*/ ) return 0; + memset((PStack.pPos = PStack.pPos - n), 0, (unsigned int)n); return PStack.pPos; } /* @@ -221,6 +220,14 @@ WASM_KEEP int sqlite3_wasm_pstack_remaining(void){ return (int)(PStack.pPos - PStack.pBegin); } +/* +** Return the total number of bytes available in the pstack, including +** any space which is currently allocated. This value is a +** compile-time constant. +*/ +WASM_KEEP int sqlite3_wasm_pstack_quota(void){ + return (int)(PStack.pEnd - PStack.pBegin); +} /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -254,26 +261,26 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ ** variadic macros. ** ** Returns a string containing a JSON-format "enum" of C-level -** constants intended to be imported into the JS environment. The JSON -** is initialized the first time this function is called and that -** result is reused for all future calls. +** constants and struct-related metadata intended to be imported into +** the JS environment. The JSON is initialized the first time this +** function is called and that result is reused for all future calls. ** ** If this function returns NULL then it means that the internal -** buffer is not large enough for the generated JSON. In debug builds -** that will trigger an assert(). +** buffer is not large enough for the generated JSON and needs to be +** increased. In debug builds that will trigger an assert(). */ WASM_KEEP const char * sqlite3_wasm_enum_json(void){ - static char azBuffer[1024 * 12] = {0} /* where the JSON goes */; + static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; - char * zPos = &azBuffer[1] /* skip first byte for now to help protect + char * zPos = &aBuffer[1] /* skip first byte for now to help protect ** against a small race condition */; - char const * const zEnd = &azBuffer[0] + sizeof(azBuffer) /* one-past-the-end */; - if(azBuffer[0]) return azBuffer; - /* Leave azBuffer[0] at 0 until the end to help guard against a tiny + char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */; + if(aBuffer[0]) return aBuffer; + /* Leave aBuffer[0] at 0 until the end to help guard against a tiny ** race condition. If this is called twice concurrently, they might - ** end up both writing to azBuffer, but they'll both write the same + ** end up both writing to aBuffer, but they'll both write the same ** thing, so that's okay. If we set byte 0 up front then the 2nd ** instance might return and use the string before the 1st instance ** is done filling it. */ @@ -680,8 +687,8 @@ const char * sqlite3_wasm_enum_json(void){ out("}"/*top-level object*/); *zPos = 0; - azBuffer[0] = '{'/*end of the race-condition workaround*/; - return azBuffer; + aBuffer[0] = '{'/*end of the race-condition workaround*/; + return aBuffer; #undef StructBinder #undef StructBinder_ #undef StructBinder__ |