From 40ce52a4e5970cb78e5044483fbd20875ffcc88f Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 21 Nov 2025 10:49:32 +0000 Subject: [PATCH] Move the JS pieces of kvvfs into their own file to facilitate pending feature experimentation. FossilOrigin-Name: 3c40614285449df259a3444e36f888cfb5e782ea58287914f97f496ea61e9e9f --- ext/wasm/GNUmakefile | 1 + ext/wasm/api/sqlite3-api-glue.c-pp.js | 102 ------ ext/wasm/api/sqlite3-api-oo1.c-pp.js | 51 +-- ext/wasm/api/sqlite3-api-prologue.js | 80 ----- ext/wasm/jaccwabyt/jaccwabyt.js | 472 ++++++++++++++++++-------- ext/wasm/tester1.c-pp.js | 5 +- manifest | 24 +- manifest.uuid | 2 +- 8 files changed, 344 insertions(+), 393 deletions(-) diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 481c17d899..82c3913b7b 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -910,6 +910,7 @@ sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js ifeq (0,$(wasm-bare-bones)) sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js endif +sqlite3-api.jses += $(dir.api)/sqlite3-vfs-kvvfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js diff --git a/ext/wasm/api/sqlite3-api-glue.c-pp.js b/ext/wasm/api/sqlite3-api-glue.c-pp.js index 1b0f8a0807..1fc8337775 100644 --- a/ext/wasm/api/sqlite3-api-glue.c-pp.js +++ b/ext/wasm/api/sqlite3-api-glue.c-pp.js @@ -988,8 +988,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const notThese = Object.assign(Object.create(null),{ // For each struct to NOT register, map its name to true: WasmTestStruct: true, - /* We unregister the kvvfs VFS from Worker threads below. */ - sqlite3_kvvfs_methods: !util.isUIThread(), /* sqlite3_index_info and friends require int64: */ sqlite3_index_info: !wasm.bigIntEnabled, sqlite3_index_constraint: !wasm.bigIntEnabled, @@ -1783,106 +1781,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; }/* auto-extension */ - const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); - if( pKvvfs ){/* kvvfs-specific glue */ - if(util.isUIThread()){ - const kvvfsMethods = new capi.sqlite3_kvvfs_methods( - wasm.exports.sqlite3__wasm_kvvfs_methods() - ); - delete capi.sqlite3_kvvfs_methods; - - const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack, - pstack = wasm.pstack; - - const kvvfsStorage = (zClass)=> - ((115/*=='s'*/===wasm.peek(zClass)) - ? sessionStorage : localStorage); - - /** - Implementations for members of the object referred to by - sqlite3__wasm_kvvfs_methods(). We swap out the native - implementations with these, which use localStorage or - sessionStorage for their backing store. - */ - const kvvfsImpls = { - xRead: (zClass, zKey, zBuf, nBuf)=>{ - const stack = pstack.pointer, - astack = wasm.scopedAllocPush(); - try { - const zXKey = kvvfsMakeKey(zClass,zKey); - if(!zXKey) return -3/*OOM*/; - const jKey = wasm.cstrToJs(zXKey); - const jV = kvvfsStorage(zClass).getItem(jKey); - if(!jV) return -1; - const nV = jV.length /* We are relying 100% on v being - ASCII so that jV.length is equal - to the C-string's byte length. */; - if(nBuf<=0) return nV; - else if(1===nBuf){ - wasm.poke(zBuf, 0); - return nV; - } - const zV = wasm.scopedAllocCString(jV); - if(nBuf > nV + 1) nBuf = nV + 1; - wasm.heap8u().copyWithin( - Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1) - ); - wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0); - return nBuf - 1; - }catch(e){ - sqlite3.config.error("kvstorageRead()",e); - return -2; - }finally{ - pstack.restore(stack); - wasm.scopedAllocPop(astack); - } - }, - xWrite: (zClass, zKey, zData)=>{ - const stack = pstack.pointer; - try { - const zXKey = kvvfsMakeKey(zClass,zKey); - if(!zXKey) return 1/*OOM*/; - const jKey = wasm.cstrToJs(zXKey); - kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); - return 0; - }catch(e){ - sqlite3.config.error("kvstorageWrite()",e); - return capi.SQLITE_IOERR; - }finally{ - pstack.restore(stack); - } - }, - xDelete: (zClass, zKey)=>{ - const stack = pstack.pointer; - try { - const zXKey = kvvfsMakeKey(zClass,zKey); - if(!zXKey) return 1/*OOM*/; - kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); - return 0; - }catch(e){ - sqlite3.config.error("kvstorageDelete()",e); - return capi.SQLITE_IOERR; - }finally{ - pstack.restore(stack); - } - } - }/*kvvfsImpls*/; - for(const k of Object.keys(kvvfsImpls)){ - kvvfsMethods[kvvfsMethods.memberKey(k)] = - wasm.installFunction( - kvvfsMethods.memberSignature(k), - kvvfsImpls[k] - ); - } - }else{ - /* Worker thread: unregister kvvfs to avoid it being used - for anything other than local/sessionStorage. It "can" - be used that way but it's not really intended to be. */ - capi.sqlite3_vfs_unregister(pKvvfs); - delete capi.sqlite3_kvvfs_methods; - } - }/*pKvvfs*/ - /* Warn if client-level code makes use of FuncPtrAdapter. */ wasm.xWrap.FuncPtrAdapter.warnOnUse = true; diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 8c2f35e677..f28e376f1a 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -239,7 +239,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ ctor._name2vfs = Object.create(null); const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/) - ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.") + ? (n)=>toss3("VFS",n,"is only available in the main window thread.") : false; ctor._name2vfs[':localStorage:'] = { vfs: 'kvvfs', filename: isWorkerThread || (()=>'local') @@ -2295,55 +2295,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Stmt }/*oo1 object*/; - if(util.isUIThread()){ - /** - Functionally equivalent to DB(storageName,'c','kvvfs') except - that it throws if the given storage name is not one of 'local' - or 'session'. - - As of version 3.46, the argument may optionally be an options - object in the form: - - { - filename: 'session'|'local', - ... etc. (all options supported by the DB ctor) - } - - noting that the 'vfs' option supported by main DB - constructor is ignored here: the vfs is always 'kvvfs'. - */ - sqlite3.oo1.JsStorageDb = function(storageName='session'){ - const opt = dbCtorHelper.normalizeArgs(...arguments); - storageName = opt.filename; - if('session'!==storageName && 'local'!==storageName){ - toss3("JsStorageDb db name must be one of 'session' or 'local'."); - } - opt.vfs = 'kvvfs'; - dbCtorHelper.call(this, opt); - }; - const jdb = sqlite3.oo1.JsStorageDb; - jdb.prototype = Object.create(DB.prototype); - /** Equivalent to sqlite3_js_kvvfs_clear(). */ - jdb.clearStorage = capi.sqlite3_js_kvvfs_clear; - /** - Clears this database instance's storage or throws if this - instance has been closed. Returns the number of - database blocks which were cleaned up. - */ - jdb.prototype.clearStorage = function(){ - return jdb.clearStorage(affirmDbOpen(this).filename); - }; - /** Equivalent to sqlite3_js_kvvfs_size(). */ - jdb.storageSize = capi.sqlite3_js_kvvfs_size; - /** - Returns the _approximate_ number of bytes this database takes - up in its storage or throws if this instance has been closed. - */ - jdb.prototype.storageSize = function(){ - return jdb.storageSize(affirmDbOpen(this).filename); - }; - }/*main-window-only bits*/ - }); //#else /* Built with the omit-oo1 flag. */ diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index d63bd14a83..065ea532e6 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -1604,86 +1604,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( return x===v ? undefined : x; } - if( util.isUIThread() ){ - /* Features specific to the main window thread... */ - - /** - Internal helper for sqlite3_js_kvvfs_clear() and friends. - Its argument should be one of ('local','session',""). - */ - const __kvvfsInfo = function(which){ - const rc = Object.create(null); - rc.prefix = 'kvvfs-'+which; - rc.stores = []; - if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage); - if('local'===which || ""===which) rc.stores.push(globalThis.localStorage); - return rc; - }; - - /** - Clears all storage used by the kvvfs DB backend, deleting any - DB(s) stored there. Its argument must be either 'session', - 'local', or "". In the first two cases, only sessionStorage - resp. localStorage is cleared. If it's an empty string (the - default) then both are cleared. Only storage keys which match - the pattern used by kvvfs are cleared: any other client-side - data are retained. - - This function is only available in the main window thread. - - Returns the number of entries cleared. - */ - capi.sqlite3_js_kvvfs_clear = function(which=""){ - let rc = 0; - const kvinfo = __kvvfsInfo(which); - kvinfo.stores.forEach((s)=>{ - const toRm = [] /* keys to remove */; - let i; - for( i = 0; i < s.length; ++i ){ - const k = s.key(i); - if(k.startsWith(kvinfo.prefix)) toRm.push(k); - } - toRm.forEach((kk)=>s.removeItem(kk)); - rc += toRm.length; - }); - return rc; - }; - - /** - This routine guesses the approximate amount of - window.localStorage and/or window.sessionStorage in use by the - kvvfs database backend. Its argument must be one of - ('session', 'local', ""). In the first two cases, only - sessionStorage resp. localStorage is counted. If it's an empty - string (the default) then both are counted. Only storage keys - which match the pattern used by kvvfs are counted. The returned - value is the "length" value of every matching key and value, - noting that JavaScript stores each character in 2 bytes. - - Note that the returned size is not authoritative from the - perspective of how much data can fit into localStorage and - sessionStorage, as the precise algorithms for determining - those limits are unspecified and may include per-entry - overhead invisible to clients. - */ - capi.sqlite3_js_kvvfs_size = function(which=""){ - let sz = 0; - const kvinfo = __kvvfsInfo(which); - kvinfo.stores.forEach((s)=>{ - let i; - for(i = 0; i < s.length; ++i){ - const k = s.key(i); - if(k.startsWith(kvinfo.prefix)){ - sz += k.length; - sz += s.getItem(k).length; - } - } - }); - return sz * 2 /* because JS uses 2-byte char encoding */; - }; - - }/* main-window-only bits */ - /** Wraps all known variants of the C-side variadic sqlite3_db_config(). diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index 20e39b0a9d..894c390ca7 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -30,19 +30,32 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ all args with a space between each. */ const toss = (...args)=>{throw new Error(args.join(' '))}; - if(!(config.heap instanceof WebAssembly.Memory) - && !(config.heap instanceof Function)){ - toss("config.heap must be WebAssembly.Memory instance or a function which returns one."); + { + let h = config.heap; + if( h instanceof WebAssembly.Memory ){ + h = function(){return new Uint8Array(this.buffer)}.bind(h); + }else if( !(h instanceof Function) ){ + //console.warn("The bothersome StructBinderFactory config:",config); + toss("config.heap must be WebAssembly.Memory instance or", + "a function which returns one."); + } + config.heap = h; } ['alloc','dealloc'].forEach(function(k){ (config[k] instanceof Function) || toss("Config option '"+k+"' must be a function."); }); - const __heap = config.heap; const SBF = StructBinderFactory; - const heap = __heap ? __heap : ()=>new Uint8Array(__heap.buffer), + const heap = config.heap, alloc = config.alloc, dealloc = config.dealloc, + realloc = (config.realloc || function(){ + toss("This StructBinderFactory was configured without realloc()"); + /* We can't know the original memory's size from here unless + we internally proxy alloc()/dealloc() to track all + pointers (not going to happen), so we can't fall back to + doing alloc()/copy/dealloc(). */ + }), log = config.log || console.debug.bind(console), memberPrefix = (config.memberPrefix || ""), memberSuffix = (config.memberSuffix || ""), @@ -51,10 +64,10 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array; //console.warn("config",config); - let ptr = alloc(1); + let ptr; const ptrIR = config.pointerIR - || config.ptrIR/*deprecated*/ - || ('bigint'===typeof ptr ? 'i64' : 'i32'); + || config.ptrIR/*deprecated*/ + || ('bigint'===typeof (ptr = alloc(1)) ? 'i64' : 'i32'); /* Undocumented (on purpose) config options: */ const ptrSize = config.ptrSize/*deprecated*/ || ('i32'===ptrIR ? 4 : 8); @@ -91,6 +104,10 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ return rc; }; + const __ptrAddSelf = function(...args){ + return __ptrAdd(this.pointer,...args); + }; + if(!SBF.debugFlags){ SBF.__makeDebugFlags = function(deriveFrom=null){ /* This is disgustingly overengineered. :/ */ @@ -120,12 +137,12 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ SBF.debugFlags = SBF.__makeDebugFlags(); }/*static init*/ - const isLittleEndian = (function() { + const isLittleEndian = true || (function() { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true /* littleEndian */); // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] === 256; - })(); + })() /* WASM is, by definition, Little Endian */; /** Some terms used in the internal docs: @@ -146,8 +163,9 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; /** Returns p if SIG s is a function SIG, else returns s[0]. */ const sigLetter = (s)=>s ? (isFuncSig(s) ? 'p' : s[0]) : undefined; - /** Returns the WASM IR form of the Emscripten-conventional letter - at SIG s[0]. Throws for an unknown SIG. */ + + /** Returns the WASM IR form of the letter at SIG s[0]. Throws for + an unknown SIG. */ const sigIR = function(s){ switch(sigLetter(s)){ case 'c': case 'C': return 'i8'; @@ -159,8 +177,9 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ } toss("Unhandled signature IR:",s); }; - /** Returns the WASM sizeof of the Emscripten-conventional letter - at SIG s[0]. Throws for an unknown SIG. */ + + /** Returns the WASM sizeof of the letter at SIG s[0]. Throws for an + unknown SIG. */ const sigSize = function(s){ switch(sigLetter(s)){ case 'c': case 'C': return 1; @@ -175,6 +194,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); + /** Returns the name of a DataView getter method corresponding to the given SIG. */ const sigDVGetter = function(s){ @@ -195,6 +215,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ } toss("Unhandled DataView getter for signature:",s); }; + /** Returns the name of a DataView setter method corresponding to the given SIG. */ const sigDVSetter = function(s){ @@ -246,26 +267,44 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ /** In order to completely hide StructBinder-bound struct pointers from JS code, we store them in a scope-local WeakMap which maps - the struct-bound objects to their WASM pointers. The pointers are - accessible via boundObject.pointer, which is gated behind a - property interceptor, but are not exposed anywhere else in the - object. - - This approach means we cannot proxy arrays, or any type which - might be realloced, as that pointer could change out from under - us. That's not an issue for nested structs, but it might be for a - struct they're embedded in. In the case of nested structs we - "could" record their top-most parent object and their offset into - that object, instead of storing the pointer itself. We could that - by allowing a function instead of a pointer in this map, that - function returning the (lazily-calculated) address. Hmm. + the struct-bound objects to an object with their metadata: + + { + .p = the native pointer, + .o = self (for an eventual reverse-mapping), + .xb = extra bytes allocated for p, + .zod = zeroOnDispose, + .ownsPointer = true if this object owns p + } + + The .p data are accessible via obj.pointer, which is gated behind + a property interceptor, but are not exposed anywhere else in the + public API. */ - const __instancePointerMap = new WeakMap(); - - const getInstancePtr = (obj)=>__instancePointerMap.get(obj); + const getInstanceHandle = function f(obj, create=true){ + let ii = f.map.get(obj); + if( !ii && create ){ + f.map.set(obj, (ii=f.create(obj))); + } + return ii; + }; + getInstanceHandle.map = new WeakMap; + getInstanceHandle.create = (forObj)=>{ + return Object.assign(Object.create(null),{ + o: forObj, + p: undefined/*native ptr*/, + ownsPointer: false, + zod: false/*zeroOnDispose*/, + xb: 0/*extraBytes*/ + }); + }; - /** Property name for the pointer-is-external marker. */ - const xPtrPropName = Symbol('(pointer-is-external)'); + /** + Remove the getInstanceHandle() mapping for obj. + */ + const rmInstanceHandle = (obj)=>getInstanceHandle.map.delete(obj) + /* If/when we have a reverse map of ptr-to-objects, we need to + clean that here. */; const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0); const __isPtr64 = (ptr)=>( @@ -278,85 +317,127 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ */ const __isPtr = (4===ptrSize) ? __isPtr32 : __isPtr64; - /** Frees the obj.pointer memory and clears the pointer - property. */ + const __isNonNullPtr = (v)=>__isPtr(v) && (v>0); + + /** Frees the obj.pointer memory (a.k.a. m), handles obj.ondispose, + and unmaps obj from its native resources. */ const __freeStruct = function(ctor, obj, m){ - if(!m) m = getInstancePtr(obj); - if(m) { - __instancePointerMap.delete(obj); - if(Array.isArray(obj.ondispose)){ - let x; - while((x = obj.ondispose.shift())){ - try{ - if(x instanceof Function) x.call(obj); - else if(x instanceof StructType) x.dispose(); - else if(__isPtr(x)) dealloc(x); - // else ignore. Strings are permitted to annotate entries - // to assist in debugging. - }catch(e){ - console.warn("ondispose() for",ctor.structName,'@', - m,'threw. NOT propagating it.',e); - } - } - }else if(obj.ondispose instanceof Function){ - try{obj.ondispose()} - catch(e){ - /*do not rethrow: destructors must not throw*/ + const ii = getInstanceHandle(obj, false); + if( !ii ) return; + rmInstanceHandle(obj); + if( !m && !(m = ii.p) ){ + console.warn("Cannot(?) happen: __freeStruct() found no instanceInfo"); + return; + } + if(Array.isArray(obj.ondispose)){ + let x; + while((x = obj.ondispose.pop())){ + try{ + if(x instanceof Function) x.call(obj); + else if(x instanceof StructType) x.dispose(); + else if(__isPtr(x)) dealloc(x); + // else ignore. Strings are permitted to annotate entries + // to assist in debugging. + }catch(e){ console.warn("ondispose() for",ctor.structName,'@', m,'threw. NOT propagating it.',e); } } - delete obj.ondispose; - if(ctor.debugFlags.__flags.dealloc){ - log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), - ctor.structName,"instance:", - ctor.structInfo.sizeof,"bytes @"+m); + }else if(obj.ondispose instanceof Function){ + try{obj.ondispose()} + catch(e){ + /*do not rethrow: destructors must not throw*/ + console.warn("ondispose() for",ctor.structName,'@', + m,'threw. NOT propagating it.',e); } - if(!obj[xPtrPropName]){ - if( ctor.structInfo.zeroOnFree ){ - heap().fill(0, Number(m), Number(m)+ctor.structInfo.sizeof); - } - dealloc(m); + } + delete obj.ondispose; + if(ctor.debugFlags.__flags.dealloc){ + log("debug.dealloc:",(ii.ownsPointer?"":"EXTERNAL"), + ctor.structName,"instance:", + ctor.structInfo.sizeof,"bytes @"+m); + } + if(ii.ownsPointer){ + if( ii.zod || ctor.structInfo.zeroOnDispose ){ + heap().fill(0, Number(m), + Number(m) + ctor.structInfo.sizeof + ii.xb); } + dealloc(m); } }; + /** Returns a skeleton for a read-only, non-iterable property + * descriptor. */ + const rop0 = ()=>{return {configurable: false, writable: false, + iterable: false}}; + /** Returns a skeleton for a read-only property accessor wrapping value v. */ - const rop = (v)=>{return {configurable: false, writable: false, - iterable: false, value: v}}; + const rop = (v)=>{return {...rop0(), value: v}}; /** Allocates obj's memory buffer based on the size defined in ctor.structInfo.sizeof. */ - const __allocStruct = function(ctor, obj, m){ - let fill = !m; - if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); - else{ - m = alloc(ctor.structInfo.sizeof); - if(!m) toss("Allocation of",ctor.structName,"structure failed."); + const __allocStruct = function f(ctor, obj, xm){ + let opt; + const checkPtr = (ptr)=>{ + __isNonNullPtr(ptr) || + toss("Invalid pointer value",arguments[0],"for",ctor.structName,"constructor."); + }; + if( arguments.length>=3 ){ + if( xm && ('object'===typeof xm) ){ + opt = xm; + xm = opt?.wrap; + }else{ + checkPtr(xm); + opt = {wrap: xm}; + } + }else{ + opt = {} + } + + const fill = !xm /* true if we need to zero the memory */; + let nAlloc = 0; + let ownsPointer = false; + if(xm){ + /* Externally-allocated memory. */ + checkPtr(xm); + ownsPointer = !!opt?.takeOwnership; + }else{ + const nX = opt?.extraBytes ?? 0; + if( nX<0 || (nX!==(nX|0)) ){ + toss("Invalid extraBytes value:",opt?.extraBytes); + } + nAlloc = ctor.structInfo.sizeof + nX; + xm = alloc(nAlloc) + || toss("Allocation of",ctor.structName,"structure failed."); + ownsPointer = true; } try { - if(ctor.debugFlags.__flags.alloc){ + if( opt?.debugFlags ){ + /* specifically undocumented */ + obj.debugFlags(opt.debugFlags); + } + if(ctor./*prototype.???*/debugFlags.__flags.alloc){ log("debug.alloc:",(fill?"":"EXTERNAL"), ctor.structName,"instance:", - ctor.structInfo.sizeof,"bytes @"+m); + ctor.structInfo.sizeof,"bytes @"+xm); } if(fill){ - heap().fill(0, Number(m), Number(m) + ctor.structInfo.sizeof); + heap().fill(0, Number(xm), Number(xm) + nAlloc); + } + const ii = getInstanceHandle(obj); + ii.p = xm; + ii.ownsPointer = ownsPointer; + ii.xb = nAlloc ? (nAlloc-ctor.structInfo.sizeof) : 0; + ii.zod = !!opt?.zeroOnDispose; + if( opt?.ondispose && opt.ondispose!==xm ){ + obj.addOnDispose( opt.ondispose ); } - __instancePointerMap.set(obj, m); }catch(e){ - __freeStruct(ctor, obj, m); + __freeStruct(ctor, obj, xm); throw e; } }; - /** Gets installed as the memoryDump() method of all structs. */ - const __memoryDump = function(){ - const p = this.pointer; - return p - ? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof)) - : null; - }; /** True if sig looks like an emscripten/jaccwabyt type signature, else false. */ @@ -390,25 +471,33 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ // StructBinder::adaptGet() const __adaptGet = function(key, ...args){ - /*if( looksLikeASig(key) ){ - toss("Getter adaptor's name (",key,") collides with a data type signature."); - }*/ return __adaptor(this, 0, key, ...args); }; + const __affirmNotASig = function(ctx,key){ + looksLikeASig(key) && + toss(ctx,"(",key,") collides with a data type signature."); + }; + // StructBinder::adaptSet() const __adaptSet = function(key, ...args){ - if( looksLikeASig(key) ){ - toss("Setter adaptor's name (",key,") collides with a data type signature."); - } + __affirmNotASig('Setter adaptor',key); return __adaptor(this, 1, key, ...args); }; // StructBinder::adaptStruct() const __adaptStruct = function(key, ...args){ + __affirmNotASig('Struct adaptor',key); return __adaptor(this, 2, key, ...args); }; + /** + An internal counterpart of __adaptStruct(). If key is-a string, + uses __adaptor(who) to fetch the struct-adaptor entry for key, + else key is assumed to be a struct description object. If it + resolves to an object, that's returned, else an exception is + thrown. + */ const __adaptStruct2 = function(who,key){ const si = ('string'===typeof key) ? __adaptor(who, 2, key) : key; @@ -455,15 +544,6 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ return emscriptenFormat ? f._(m.signature) : m.signature; }; - const __ptrPropDescriptor = { - configurable: false, enumerable: false, - get: function(){return getInstancePtr(this)}, - set: ()=>toss("Cannot assign the 'pointer' property of a struct.") - // Reminder: leaving `set` undefined makes assignments - // to the property _silently_ do nothing. Current unit tests - // rely on it throwing, though. - }; - /** Impl of X.memberKeys() for StructType and struct ctors. */ const __structMemberKeys = rop(function(){ const a = []; @@ -597,13 +677,13 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ Prototype for all StructFactory instances (the constructors returned from StructBinder). */ - const StructType = function ctor(structName, structInfo){ - if(arguments[2]!==rop){ + const StructType = function StructType(structName, structInfo){ + if(arguments[2]!==rop/*internal sentinel value*/){ toss("Do not call the StructType constructor", "from client-level code."); } Object.defineProperties(this,{ - //isA: rop((v)=>v instanceof ctor), + //isA: rop((v)=>v instanceof StructType), structName: rop(structName), structInfo: rop(structInfo) }); @@ -629,8 +709,31 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ memberSignature: rop(function(memberName, emscriptenFormat=false){ return __memberSignature(this, memberName, emscriptenFormat); }), - memoryDump: rop(__memoryDump), - pointer: __ptrPropDescriptor, + memoryDump: rop(function(){ + const p = this.pointer; + return p + ? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof)) + : null; + }), + extraBytes: { + configurable: false, enumerable: false, + get: function(){return getInstanceHandle(this, false)?.xb ?? 0;} + }, + zeroOnDispose: { + configurable: false, enumerable: false, + get: function(){ + return getInstanceHandle(this, false)?.zod + ?? !!this.structInfo.zeroOnDispose; + } + }, + pointer: { + configurable: false, enumerable: false, + get: function(){return getInstanceHandle(this, false)?.p}, + set: ()=>toss("Cannot assign the 'pointer' property of a struct.") + // Reminder: leaving `set` undefined makes assignments + // to the property _silently_ do nothing. Current unit tests + // rely on it throwing, though. + }, setMemberCString: rop(function(memberName, str){ return __setMemberCString(this, memberName, str); }) @@ -649,8 +752,12 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ Object.defineProperties(StructType, { allocCString: rop(__allocCString), isA: rop((v)=>v instanceof StructType), - hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), + hasExternalPointer: rop((v)=>{ + const ii = getInstanceHandle(v, false); + return !!(ii?.p && !ii?.ownsPointer); + }), memberKey: __memberKeyProp + //ptrAdd = rop(__ptrAdd) no b/c one might think that it adds based on this.pointer. }); /** @@ -693,7 +800,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ do any permanent harm, as the wrapper object will be recreated when accessing f.bar, pointing to the same memory in f. - The si.zeroOnFree flag has no effect on embedded structs because + The si.zeroOnDispose flag has no effect on embedded structs because they wrap "external" memory, so do not own it, and are thus never freed, as such. */ @@ -725,7 +832,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ enumerable: false, set: __propThrowOnSet(ctor/*not si.constructor*/.structName, key), get: function(){ - const dbg = si.constructor.prototype.debugFlags.__flags; + const dbg = this.debugFlags.__flags; const p = this.pointer; const k = p+'.'+key; let s = __innerStructs.get(k); @@ -758,16 +865,16 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ return makeMemberStructWrapper.call(this, ctor, name, si); } - if(!f._){ + if(!f.cache){ /* Cache all available getters/setters/set-wrappers for direct reuse in each accessor function. */ - f._ = {getters: {}, setters: {}, sw:{}}; + f.cache = {getters: {}, setters: {}, sw:{}}; const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ - f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; - f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; - f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values + f.cache.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */; + f.cache.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */; + f.cache.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values for conversion */; }); f.sigCheck = function(obj, name, key,sig){ @@ -784,8 +891,8 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ si.key = key; si.name = name; const sigGlyph = sigLetter(si.signature); - const xPropName = sPropName(ctor.prototype.structName,key); - const dbg = ctor.prototype.debugFlags.__flags; + const xPropName = sPropName(ctor.structName,key); + const dbg = ctor.debugFlags.__flags; /* TODO?: set prototype of si to an object which can set/fetch its preferred representation, e.g. conversion to string or mapped @@ -797,14 +904,20 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ prop.configurable = false; prop.enumerable = false; prop.get = function(){ + /** + This getter proxy reads its value from the appropriate pointer + address in the heap. It knows where and how much to read based on + this.pointer, si.offset, and si.sizeof. + */ if(dbg.getter){ - log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), + log("debug.getter:",f.cache.getters[sigGlyph],"for", sigIR(sigGlyph), xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof); } let rc = ( new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof) - )[f._.getters[sigGlyph]](0, isLittleEndian); - if(getterProxy) rc = getterProxy.apply(this,[rc,key]); + )[f.cache.getters[sigGlyph]](0, isLittleEndian); + + if(getterProxy) rc = getterProxy.apply(this,[key,rc]); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); return rc; }; @@ -813,28 +926,33 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ }else{ const setterProxy = memberSetterProxy(si); prop.set = function(v){ + /** + The converse of prop.get(), this encodes v into the appropriate + spot in the WASM heap. + */ if(dbg.setter){ - log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), + log("debug.setter:",f.cache.setters[sigGlyph],"for", sigIR(sigGlyph), xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof, v); } if(!this.pointer){ - toss("Cannot set native property on a disposed struct instance."); + toss("Cannot set native property on a disposed", + this.structSame,"instance."); } - if( setterProxy ) v = setterProxy.apply(this,[v]); + if( setterProxy ) v = setterProxy.apply(this,[key,v]); if( null===v || undefined===v ) v = __NullPtr; - else while( isPtrSig(si.signature) && !__isPtr(v) ){ + else if( isPtrSig(si.signature) && !__isPtr(v) ){ if(isAutoPtrSig(si.signature) && (v instanceof StructType)){ - // It's a struct instance: let's store its pointer value! + // It's a struct instance: store its pointer value v = v.pointer || __NullPtr; if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); - break; + }else{ + toss("Invalid value for pointer-type",xPropName+'.'); } - toss("Invalid value for pointer-type",xPropName+'.'); } ( new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof) - )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); + )[f.cache.setters[sigGlyph]](0, f.cache.sw[sigGlyph](v), isLittleEndian); }; } Object.defineProperty(ctor.prototype, key, prop); @@ -863,25 +981,59 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ StructCtor is the eventual return value of this function. We need to populate this early on so that we can do some trickery in feeding it through recursion. + + Uses: + + // heap-allocated: + const x = new StructCtor; + // externally-managed memory: + const y = new StructCtor( aPtrToACompatibleCStruct ); + + or, more recently: + + const z = new StructCtor({ + extraBytes: [int=0] extra bytes to allocate after the struct + + wrap: [aPtrToACompatibleCStruct=undefined]. If provided, this + instance waps, but does not (by default) own the memory, else + a new instance is allocated from the WASM heap. + + ownsPointer: true if this object takes over ownership of + wrap. + + zeroOnDispose: [bool=StructCtor.structInfo.zeroOnDispose] + + autoCalcSizeOffset: [bool=false] Automatically calculate + sizeof an offset. This is fine for pure-JS structs (which + probably aren't useful beyond testing of Jaccwabyt) but it's + dangerous to use with actual WASM objects because we cannot + be guaranteed to have the same memory layout as an ostensibly + matching C struct. This applies recursively to all children + of the struct description. + + // TODO? Per-instance overrides of the struct-level flags? + + get: (k,v)=>v, + set: (k,v)=>v, + adaptGet: string, + adaptSet: string + + // That wouldn't fit really well right now, apparently. + }); + */ - const StructCtor = function StructCtor(externalMemory){ - externalMemory = __asPtrType(externalMemory); - //console.warn("externalMemory",externalMemory,arguments[0]); + const StructCtor = function StructCtor(arg){ + //console.warn("opt",opt,arguments[0]); if(!(this instanceof StructCtor)){ toss("The",structName,"constructor may only be called via 'new'."); - }else if(arguments.length){ - if( !__isPtr(externalMemory) ){ - toss("Invalid pointer value",arguments[0],"for",structName,"constructor."); - } - __allocStruct(StructCtor, this, externalMemory); - }else{ - __allocStruct(StructCtor, this); } + __allocStruct(StructCtor, this, ...arguments); }; const self = this; /** "Convert" struct description x to a struct description, if - needed. + needed. This expands adaptStruct() mappings and transforms + {memberName:signatureString} signature syntax to object form. */ const ads = (x)=>{ //console.warn("looksLikeASig(",x,") =",looksLikeASig(x)); @@ -902,6 +1054,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ structName ??= opt.structName; if( !structName ) toss("One of 'name' or 'structName' are required."); if( si.adapt ){ + /* Install adaptGet(), adaptSet(), and adaptStruct() proxies. */ Object.keys(si.adapt.struct||{}).forEach((k)=>{ __adaptStruct.call(StructBinderImpl, k, si.adapt.struct[k]); }); @@ -924,21 +1077,32 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ memberKeys: __structMemberKeys, //methodInfoForKey: rop(function(mKey){/*???*/}), structInfo: rop(si), - structName: rop(structName) + structName: rop(structName), + ptrAdd: rop(__ptrAdd) }); StructCtor.prototype = new StructType(structName, si, rop); Object.defineProperties(StructCtor.prototype,{ debugFlags: debugFlags, constructor: rop(StructCtor) /*if we assign StructCtor.prototype and don't do - this then StructCtor!==instance.constructor*/ + this then StructCtor!==instance.constructor*/, + ptrAdd: rop(__ptrAddSelf) }); - - let lastMember = false; let offset = 0; + const autoCalc = !!si.autoCalcSizeOffset; //console.warn(structName,"si =",si); - si.offset ??= 0; + if( !autoCalc ){ + if( !si.sizeof ){ + toss(structName,"description is missing its sizeof property."); + } + /*if( undefined===si.offset ){ + toss(structName,"description is missing its offset property."); + }*/ + si.offset ??= 0; + }else{ + si.offset ??= 0; + } Object.keys(si.members || {}).forEach((k)=>{ // Sanity checks of sizeof/offset info... let m = ads(si.members[k]); @@ -951,11 +1115,23 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ } } if( undefined===m.offset ){ - m.offset = offset; + if( autoCalc ) m.offset = offset; + else{ + toss(sPropName(structName,k),"is missing its offset.", + JSON.stringify(m)); + } + /* A missing offset on the initial child is okay (it's always + zero), but we don't know for sure that the members are + their natural order, so we don't know, at this point, which + one is "first". */ } - si.members[k] = m; + si.members[k] = m /* in case ads() resolved it to something else */; if(!lastMember || lastMember.offset < m.offset) lastMember = m; - makeMemberWrapper.call(self, StructCtor, k, m) + const oldAutoCalc = !!m.autoCalc; + if( autoCalc ) m.autoCalcSizeOffset = true; + makeMemberWrapper.call(self, StructCtor, k, m); + if( oldAutoCalc ) m.autoCalcSizeOffset = true; + else delete m.autoCalcSizeOffset; offset += m.sizeof; //console.warn("offset",sPropName(structName,k),offset); }); @@ -979,12 +1155,15 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ toss(structName,"offset is not aligned. offset="+si.offset); } } - if( si.sizeof < offset ){ console.warn("Suspect struct description:",si,"offset =",offset); toss("Mismatch in the calculated vs. the provided sizeof/offset info.", "Expected sizeof",offset,"but got",si.sizeof,"for",si); + /* It is legal for the native struct to be larger, so long as + we're pointing to all the right offsets for the members + exposed here. */ } + delete si.autoCalcSizeOffset; return StructCtor; }/*StructBinderImpl*/; @@ -999,6 +1178,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ StructBinder.adaptGet = __adaptGet; StructBinder.adaptSet = __adaptSet; StructBinder.adaptStruct = __adaptStruct; + StructBinder.ptrAdd = __ptrAdd; if(!StructBinder.debugFlags){ StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); } diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index a146a871d8..33bf733e9c 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -1027,7 +1027,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let ptr = k1.pointer; k1.dispose(); T.assert(undefined === k1.pointer). - mustThrowMatching(()=>{k1.$pP=1}, /disposed struct instance/); + mustThrowMatching(()=>{k1.$pP=1}, /disposed/); }finally{ k1.dispose(); k2.dispose(); @@ -2907,11 +2907,12 @@ globalThis.sqlite3InitModule = sqlite3InitModule; ]); T.assert(3 === db.selectValue('select count(*) from kvvfs')); db.close(); + db = undefined; db = new JDb(filename); db.exec('insert into kvvfs(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from kvvfs')); }finally{ - db.close(); + if( db ) db.close(); } } }/*kvvfs sanity checks*/) diff --git a/manifest b/manifest index 957b28b1a3..4122d483d3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Test\scases\sfor\sthe\sfix\sin\sthe\sprior\scheck-in. -D 2025-11-20T22:46:27.556 +C Move\sthe\sJS\spieces\sof\skvvfs\sinto\stheir\sown\sfile\sto\sfacilitate\spending\sfeature\sexperimentation. +D 2025-11-21T10:49:32.462 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -578,7 +578,7 @@ F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009e F ext/session/sqlite3session.c b3de195ce668cace9b324599bf6255a70290cbfb5451e826e946f3aee6e64c54 F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb -F ext/wasm/GNUmakefile 01f5e1bc688911b0c0accb0b152dccb19818cb301a90ba9772e3b329a7f625b0 +F ext/wasm/GNUmakefile bff7f432a65bc5152ae5aaf19f480dd16fa7fdd11a451773b7dbcac544c703b3 F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259 F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff @@ -593,9 +593,9 @@ F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b F ext/wasm/api/post-js-header.js d24bd0d065f3489c8b78ddf3ead6321e5d047187a162cd503c41700e03dd1f06 F ext/wasm/api/pre-js.c-pp.js ad2546290e0c8ce5ca2081bff6e85cc25eeb904a3303921f1184290a7ff1b32f -F ext/wasm/api/sqlite3-api-glue.c-pp.js 6ff61b7a7a8fb4367ebb2262d9f95a4b8c43eb6964890af99fd322f6cb1b4f00 -F ext/wasm/api/sqlite3-api-oo1.c-pp.js 31dbfd470c91ffd96d77399b749bab6b69e3ba9074188833f97ac13f087cf07b -F ext/wasm/api/sqlite3-api-prologue.js a7cab3e8f78d5a93d7173657677fef18265f48d4e0b1e1755740f734a69cae6d +F ext/wasm/api/sqlite3-api-glue.c-pp.js 9eaed1801be392f6687aa7da8e3a5a41d03de19993d8fe62ee6c52617eab4985 +F ext/wasm/api/sqlite3-api-oo1.c-pp.js 8ce38bd4b22aa2b0311c7a8e87e748e06213766fe2141de7574672d103ece255 +F ext/wasm/api/sqlite3-api-prologue.js 7004b569624765c5132984bfecee2305bef928a6adf44e0202dacc9cbc5c8e2a F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966 @@ -627,7 +627,7 @@ F ext/wasm/fiddle/fiddle.js 84fd75967e0af8b69d3dd849818342227d0f81d13db92e0dcbc6 F ext/wasm/fiddle/index.html a27b8127ef9ecf19612da93b2a6a73bdb3777b5c56b5450bb7200a94bc108ff9 F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290 F ext/wasm/index.html 54e27db740695ab2cb296e02d42c4c66b3f11b65797340d19fa6590f5b287da1 -F ext/wasm/jaccwabyt/jaccwabyt.js 1e734c624205cdf621f322972dfb0fc8013d573a5882f57492a6830e5ec23e17 +F ext/wasm/jaccwabyt/jaccwabyt.js 236464b7c8e2d2540a296ba049b3e812e2697ef8439ac96f0d20716e46ba6809 F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x F ext/wasm/mkwasmbuilds.c ef42e404236dd98cedb6ecea47b6d2474e3c593633ce6d992d316289dfc442b6 @@ -646,7 +646,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c F ext/wasm/tester1-worker.c-pp.html 0e432ec2c0d99cd470484337066e8d27e7aee4641d97115338f7d962bf7b081a F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb -F ext/wasm/tester1.c-pp.js 2c255093205a0dac9dae7475030665c2c9d6dccc857de68ee7daf49aa82e6de8 +F ext/wasm/tester1.c-pp.js 015b4133cc3a5fb41d6236a6b39d23d996cc2d61a4877acde31a1f69574d4ce3 F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 8896185ae0b0af8918aa8ce449f18759e6ae60358afbac1019397bae307b15d5 -R 254d017f2dd051b92cbdd62f2ef390cd -U drh -Z 1247e56565ac3eb588c6a5672754d68c +P 4d41bee75eda51251121c8e3903f47941116e5182238a03f41a593c47efb6fcf +R 89e12ac8833b73cf56a70154881e6f51 +U stephan +Z 653a6aeee27ed8b2d1b86bc8d2dfe977 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ccec045e55..17a24daa49 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4d41bee75eda51251121c8e3903f47941116e5182238a03f41a593c47efb6fcf +3c40614285449df259a3444e36f888cfb5e782ea58287914f97f496ea61e9e9f -- 2.47.3