diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 2235663261..107da6968e 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -55,12 +55,7 @@ try{ } ); - /** Figure out if this is a 32- or 64-bit WASM build. */ - bootstrapConfig.wasmPtrIR = - 'number'===(typeof bootstrapConfig.exports.sqlite3_libversion()) - ? 'i32' :'i64'; - const sIMS = sqlite3InitScriptInfo; - sIMS.debugModule("Bootstrapping lib config", sIMS); + sqlite3InitScriptInfo.debugModule("Bootstrapping lib config", bootstrapConfig); /** For purposes of the Emscripten build, call sqlite3ApiBootstrap(). diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 069f3fdb5c..c2ba47f20e 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -799,22 +799,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( environment via whwashutil.js. */ Object.assign(wasm, { - /** - The WASM IR (Intermediate Representation) value for - pointer-type values. If set then it MUST be one of 'i32' or - 'i64' (else an exception will be thrown). If it's not set, it - will default to 'i32'. - */ - pointerIR: config.wasmPtrIR, - - /** - True if BigInt support was enabled via (e.g.) the - Emscripten -sWASM_BIGINT flag, else false. When - enabled, certain 64-bit sqlite3 APIs are enabled which - are not otherwise enabled due to JS/WASM int64 - impedance mismatches. - */ - bigIntEnabled: !!config.bigIntEnabled, /** The symbols exported by the WASM environment. @@ -834,6 +818,29 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap( "in either config.exports.memory (exported)", "or config.memory (imported)."), + /** + The WASM pointer size. If set then it MUST be one of 4 or 8 and + it MUST correspond to the WASM environment's pointer size. We + figure out the size by calling some un-JS-wrapped WASM function + which returns a pointer-type value. If that value is a BigInt, + it's 64-bit, else it's 32-bit. The pieces which populate + sqlite3.wasm (whwasmutil.js) can figure this out _if_ they can + allocate, but we have a chicken/egg situation there which makes + it illegal for that code to invoke wasm.dealloc() at the time + it would be needed. So we need to configure it ahead of time + (here) instead. + */ + pointerSize: ('number'===typeof config.exports.sqlite3_libversion()) ? 4 : 8, + + /** + True if BigInt support was enabled via (e.g.) the + Emscripten -sWASM_BIGINT flag, else false. When + enabled, certain 64-bit sqlite3 APIs are enabled which + are not otherwise enabled due to JS/WASM int64 + impedance mismatches. + */ + bigIntEnabled: !!config.bigIntEnabled, + /** WebAssembly.Table object holding the indirect function call table. Defaults to exports.__indirect_function_table. diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 1c678f31f6..aadcf1f8b0 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -227,6 +227,14 @@ globalThis.WhWasmUtilInstaller = function(target){ all args with a space between each. */ const toss = (...args)=>{throw new Error(args.join(' '))}; + if( !target.pointerSize && !target.pointerIR + && target.alloc && target.dealloc ){ + /* Try to determine the pointer size by allocating. */ + const ptr = target.alloc(1); + target.pointerSize = ('bigint'===typeof ptr ? 8 : 4); + target.dealloc(ptr); + } + /** As of 2025-09-21, this library works with 64-bit WASM modules built with Emscripten's -sMEMORY64=1. @@ -996,12 +1004,12 @@ globalThis.WhWasmUtilInstaller = function(target){ target.heap8u(). */ target.cstrlen = function(ptr){ - if(!ptr || !target.isPtr(ptr)) return null; + if(!ptr || !target.isPtr/*64*/(ptr)) return null; ptr = Number(ptr) /*tag:64bit*/; const h = heapWrappers().HEAP8U; let pos = ptr; for( ; h[pos] !== 0; ++pos ){} - return Number(pos - ptr); + return pos - ptr; }; /** Internal helper to use in operations which need to distinguish diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index 8ea08e2136..20e39b0a9d 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -32,7 +32,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ if(!(config.heap instanceof WebAssembly.Memory) && !(config.heap instanceof Function)){ - toss("config.heap must be WebAssembly.Memory instance or a function."); + toss("config.heap must be WebAssembly.Memory instance or a function which returns one."); } ['alloc','dealloc'].forEach(function(k){ (config[k] instanceof Function) || @@ -48,14 +48,19 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ memberSuffix = (config.memberSuffix || ""), BigInt = globalThis['BigInt'], BigInt64Array = globalThis['BigInt64Array'], - bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array, - ptrIR = config.pointerIR - || config.ptrIR/*deprecated*/ - || 'i32', - /* Undocumented (on purpose) config options: */ - ptrSize = config.ptrSize/*deprecated*/ - || ('i32'===ptrIR ? 4 : 8) - ; + bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array; + + //console.warn("config",config); + let ptr = alloc(1); + const ptrIR = config.pointerIR + || config.ptrIR/*deprecated*/ + || ('bigint'===typeof ptr ? 'i64' : 'i32'); + /* Undocumented (on purpose) config options: */ + const ptrSize = config.ptrSize/*deprecated*/ + || ('i32'===ptrIR ? 4 : 8); + dealloc(ptr); + ptr = undefined; + //console.warn("ptrIR =",ptrIR,"ptrSize =",ptrSize); if(ptrIR!=='i32' && ptrIR!=='i64') toss("Invalid pointer representation:",ptrIR); if(ptrSize!==4 && ptrSize!==8) toss("Invalid pointer size:",ptrSize); @@ -136,11 +141,11 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ /** True if SIG s looks like a function signature, else false. */ const isFuncSig = (s)=>'('===s[1]; - /** True if SIG s is-a pointer signature. */ - const isPtrSig = (s)=>'p'===s || 'P'===s; + /** True if SIG s is-a pointer-type signature. */ + const isPtrSig = (s)=>'p'===s || 'P'===s || 's'===s; const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/; /** Returns p if SIG s is a function SIG, else returns s[0]. */ - const sigLetter = (s)=>isFuncSig(s) ? 'p' : 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. */ const sigIR = function(s){ @@ -154,6 +159,19 @@ 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. */ + const sigSize = function(s){ + switch(sigLetter(s)){ + case 'c': case 'C': return 1; + case 'i': return 4; + case 'p': case 'P': case 's': return ptrSize; + case 'j': return 8; + case 'f': return 4; + case 'd': return 8; + } + toss("Unhandled signature sizeof:",s); + }; const affirmBigIntArray = BigInt64Array ? ()=>true : ()=>toss('BigInt64Array is not available.'); @@ -232,11 +250,22 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ 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. */ const __instancePointerMap = new WeakMap(); + const getInstancePtr = (obj)=>__instancePointerMap.get(obj); + /** Property name for the pointer-is-external marker. */ - const xPtrPropName = '(pointer-is-external)'; + const xPtrPropName = Symbol('(pointer-is-external)'); const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0); const __isPtr64 = (ptr)=>( @@ -252,7 +281,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ /** Frees the obj.pointer memory and clears the pointer property. */ const __freeStruct = function(ctor, obj, m){ - if(!m) m = __instancePointerMap.get(obj); + if(!m) m = getInstancePtr(obj); if(m) { __instancePointerMap.delete(obj); if(Array.isArray(obj.ondispose)){ @@ -283,7 +312,12 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ ctor.structName,"instance:", ctor.structInfo.sizeof,"bytes @"+m); } - if(!obj[xPtrPropName]) dealloc(m); + if(!obj[xPtrPropName]){ + if( ctor.structInfo.zeroOnFree ){ + heap().fill(0, Number(m), Number(m)+ctor.structInfo.sizeof); + } + dealloc(m); + } } }; @@ -324,8 +358,69 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ : null; }; + /** True if sig looks like an emscripten/jaccwabyt + type signature, else false. */ + const looksLikeASig = function f(sig){ + f.rxSig1 ??= /^[ipPsjfdcC]$/; + f.rxSig2 ??= /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; + return f.rxSig1.test(sig) || f.rxSig2.test(sig); + }; + + /** Returns a pair of adaptor maps (objects) in a length-3 + array specific to the given object. */ + const __adaptorsFor = function(who){ + let x = this.get(who); + if( !x ){ + x = [ Object.create(null), Object.create(null), Object.create(null) ]; + this.set(who, x); + } + return x; + }.bind(new WeakMap); + + /** Code de-duplifier for __adaptGet(), __adaptSet(), and + __adaptStruct(). */ + const __adaptor = function(who, which, key, proxy){ + const a = __adaptorsFor(who)[which]; + if(3===arguments.length) return a[key]; + if( proxy ) return a[key] = proxy; + return delete a[key]; + }; + + const noopAdapter = (x)=>x; + + // 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); + }; + + // StructBinder::adaptSet() + const __adaptSet = function(key, ...args){ + if( looksLikeASig(key) ){ + toss("Setter adaptor's name (",key,") collides with a data type signature."); + } + return __adaptor(this, 1, key, ...args); + }; + + // StructBinder::adaptStruct() + const __adaptStruct = function(key, ...args){ + return __adaptor(this, 2, key, ...args); + }; + + const __adaptStruct2 = function(who,key){ + const si = ('string'===typeof key) + ? __adaptor(who, 2, key) : key; + if( 'object'!==typeof si ){ + toss("Invalid struct mapping object. Arg =",key,JSON.stringify(si)); + } + return si; + }; + const __memberKey = (k)=>memberPrefix + k + memberSuffix; const __memberKeyProp = rop(__memberKey); + //const __adaptGetProp = rop(__adaptGet); /** Looks up a struct member in structInfo.members. Throws if found @@ -342,7 +437,8 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ if(v.key===memberName){ m = v; break; } } if(!m && tossIfNotFound){ - toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.'); + toss(sPropName(structInfo.name || structInfo.structName, memberName), + 'is not a mapped struct member.'); } } return m; @@ -361,7 +457,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ const __ptrPropDescriptor = { configurable: false, enumerable: false, - get: function(){return __instancePointerMap.get(this)}, + 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 @@ -558,78 +654,176 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ }); /** - Pass this a StructBinder-generated prototype, and the struct - member description object. It will define property accessors for - proto[memberKey] which read from/write to memory in - this.pointer. It modifies descr to make certain downstream - operations much simpler. + If struct description object si has a getter proxy, return it (a + function), else return undefined. */ - const makeMemberWrapper = function f(ctor,name, descr){ + const memberGetterProxy = function(si){ + return si.get || (si.adaptGet + ? StructBinder.adaptGet(si.adaptGet) + : undefined); + }; + + /** + If struct description object si has a setter proxy, return it (a + function), else return undefined. + */ + const memberSetterProxy = function(si){ + return si.set || (si.adaptSet + ? StructBinder.adaptSet(si.adaptSet) + : undefined); + }; + + /** + To be called by makeMemberWrapper() when si has a 'members' + member, i.e. is an embedded struct. This function sets up that + struct like any other and also sets up property accessor for + ctor.memberKey(name) which returns an instance of that new + StructType when the member is accessed. That instance wraps the + memory of the member's part of the containing C struct instance. + + That is, if struct Foo has member bar which is an inner struct + then: + + const f = new Foo; + const b = f.bar; + assert( b is-a StructType object ); + assert( b.pointer === f.b.pointer ); + + b will be disposed of when f() is. Calling b.dispose() will not + 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 + they wrap "external" memory, so do not own it, and are thus never + freed, as such. + */ + const makeMemberStructWrapper = function callee(ctor, name, si){ + /** + Where we store inner-struct member proxies. Keys are a + combination of the parent object's pointer address and the + property's name. The values are StructType instances. + */ + const __innerStructs = (callee.innerStructs ??= new Map()); + const key = ctor.memberKey(name); + if( undefined!==si.signature ){ + toss("'signature' cannot be used on an embedded struct (", + ctor.structName,".",key,")."); + } + if( memberSetterProxy(si) ){ + toss("'set' and 'adaptSet' are not permitted for nested struct members."); + } + //console.warn("si =",ctor.structName, name, JSON.stringify(si,' ')); + si.structName ??= ctor.structName+'::'+name; + si.key = key; + si.name = name; + si.constructor = this.call(this, si.structName, si); + //console.warn("si.constructor =",si.constructor); + //console.warn("si =",si,"ctor=",ctor); + const getterProxy = memberGetterProxy(si); + const prop = Object.assign(Object.create(null),{ + configurable: false, + enumerable: false, + set: __propThrowOnSet(ctor/*not si.constructor*/.structName, key), + get: function(){ + const dbg = si.constructor.prototype.debugFlags.__flags; + const p = this.pointer; + const k = p+'.'+key; + let s = __innerStructs.get(k); + if(dbg.getter){ log("debug.getter: k =",k); } + if( !s ){ + s = new si.constructor(__ptrAdd(p, si.offset)); + __innerStructs.set(k, s); + this.addOnDispose(()=>s.dispose()); + s.addOnDispose(()=>__innerStructs.delete(k)); + //console.warn("Created",k,"proxy"); + } + if(getterProxy) s = getterProxy.apply(this,[s,key]); + if(dbg.getter) log("debug.getter: result =",s); + return s; + } + }); + Object.defineProperty(ctor.prototype, key, prop); + }/*makeMemberStructWrapper()*/; + + /** + Pass this a StructBinderImpl-generated constructor, a member + property name, and the struct member description object. It will + define property accessors for proto[memberKey] which read + from/write to memory in this.pointer. It modifies si to make + certain downstream operations simpler. + */ + const makeMemberWrapper = function f(ctor, name, si){ + si = __adaptStruct2(this, si); + if( si.members ){ + return makeMemberStructWrapper.call(this, ctor, name, si); + } + if(!f._){ - /*cache all available getters/setters/set-wrappers for - direct reuse in each accessor function. */ + /* Cache all available getters/setters/set-wrappers for + direct reuse in each accessor function. */ f._ = {getters: {}, setters: {}, sw:{}}; const a = ['i','c','C','p','P','s','f','d','v()']; if(bigIntEnabled) a.push('j'); a.forEach(function(v){ - //const ir = sigIR(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 for conversion */; }); - const rxSig1 = /^[ipPsjfdcC]$/, - rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/; f.sigCheck = function(obj, name, key,sig){ if(Object.prototype.hasOwnProperty.call(obj, key)){ toss(obj.structName,'already has a property named',key+'.'); } - rxSig1.test(sig) || rxSig2.test(sig) + looksLikeASig(sig) || toss("Malformed signature for", sPropName(obj.structName,name)+":",sig); }; } const key = ctor.memberKey(name); - f.sigCheck(ctor.prototype, name, key, descr.signature); - descr.key = key; - descr.name = name; - const sigGlyph = sigLetter(descr.signature); + f.sigCheck(ctor.prototype, name, key, si.signature); + si.key = key; + si.name = name; + const sigGlyph = sigLetter(si.signature); const xPropName = sPropName(ctor.prototype.structName,key); const dbg = ctor.prototype.debugFlags.__flags; /* - TODO?: set prototype of descr to an object which can set/fetch + TODO?: set prototype of si to an object which can set/fetch its preferred representation, e.g. conversion to string or mapped function. Advantage: we can avoid doing that via if/else if/else in the get/set methods. */ + const getterProxy = memberGetterProxy(si); const prop = Object.create(null); prop.configurable = false; prop.enumerable = false; prop.get = function(){ if(dbg.getter){ log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof); + xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof); } let rc = ( - new DataView(heap().buffer, Number(this.pointer) + descr.offset, descr.sizeof) + new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof) )[f._.getters[sigGlyph]](0, isLittleEndian); + if(getterProxy) rc = getterProxy.apply(this,[rc,key]); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); return rc; }; - if(descr.readOnly){ + if(si.readOnly){ prop.set = __propThrowOnSet(ctor.prototype.structName,key); }else{ + const setterProxy = memberSetterProxy(si); prop.set = function(v){ if(dbg.setter){ log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), - xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v); + xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof, v); } if(!this.pointer){ - toss("Cannot set struct property on disposed instance."); + toss("Cannot set native property on a disposed struct instance."); } - if(null===v) v = __NullPtr; - else while(!__isPtr(v)){ - if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){ + if( setterProxy ) v = setterProxy.apply(this,[v]); + if( null===v || undefined===v ) v = __NullPtr; + else while( isPtrSig(si.signature) && !__isPtr(v) ){ + if(isAutoPtrSig(si.signature) && (v instanceof StructType)){ // It's a struct instance: let's store its pointer value! v = v.pointer || __NullPtr; if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v); @@ -638,66 +832,45 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ toss("Invalid value for pointer-type",xPropName+'.'); } ( - new DataView(heap().buffer, Number(this.pointer) + descr.offset, - descr.sizeof) + new DataView(heap().buffer, Number(this.pointer) + si.offset, + si.sizeof) )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); }; } Object.defineProperty(ctor.prototype, key, prop); - }/*makeMemberWrapper*/; + }/*makeMemberWrapper()*/; /** The main factory function which will be returned to the - caller. + caller. The third argument is structly for internal use. + + This level of indirection is to avoid that clients can pass a + third argument to this, as that's only for internal use. + + internalOpt options: + + - None right now. This is for potential use in recursion. + + Usages: + + StructBinder(string, obj [,optObj]); + StructBinder(obj); */ - const StructBinder = function StructBinder(structName, structInfo){ - if(1===arguments.length){ - structInfo = structName; - structName = structInfo.name; - }else if(!structInfo.name){ - structInfo.name = structName; - } - if(!structName) toss("Struct name is required."); - let lastMember = false; - Object.keys(structInfo.members).forEach((k)=>{ - // Sanity checks of sizeof/offset info... - const m = structInfo.members[k]; - if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); - else if(m.sizeof===1){ - (m.signature === 'c' || m.signature === 'C') || - toss("Unexpected sizeof==1 member", - sPropName(structInfo.name,k), - "with signature",m.signature); - }else{ - // sizes and offsets of size-1 members may be odd values, but - // others may not. - if(0!==(m.sizeof%4)){ - console.warn("Invalid struct member description =",m,"from",structInfo); - toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof); - } - if(0!==(m.offset%4)){ - console.warn("Invalid struct member description =",m,"from",structInfo); - toss(structName,"member",k,"offset is not aligned. offset="+m.offset); - } - } - if(!lastMember || lastMember.offset < m.offset) lastMember = m; - }); - if(!lastMember) toss("No member property descriptions found."); - else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){ - toss("Invalid struct config:",structName, - "max member offset ("+lastMember.offset+") ", - "extends past end of struct (sizeof="+structInfo.sizeof+")."); - } - const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags)); - /** Constructor for the StructCtor. */ - const zeroAsPtr = __asPtrType(0); + const StructBinderImpl = function StructBinderImpl( + structName, si, opt = Object.create(null) + ){ + /** + 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. + */ const StructCtor = function StructCtor(externalMemory){ externalMemory = __asPtrType(externalMemory); //console.warn("externalMemory",externalMemory,arguments[0]); if(!(this instanceof StructCtor)){ toss("The",structName,"constructor may only be called via 'new'."); }else if(arguments.length){ - if(Number.isNaN(externalMemory) || externalMemory<=zeroAsPtr){ + if( !__isPtr(externalMemory) ){ toss("Invalid pointer value",arguments[0],"for",structName,"constructor."); } __allocStruct(StructCtor, this, externalMemory); @@ -705,31 +878,127 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ __allocStruct(StructCtor, this); } }; + const self = this; + /** + "Convert" struct description x to a struct description, if + needed. + */ + const ads = (x)=>{ + //console.warn("looksLikeASig(",x,") =",looksLikeASig(x)); + return (('string'===typeof x) && looksLikeASig(x)) + ? {signature: x} : __adaptStruct2(self,x); + }; + if(1===arguments.length){ + si = ads(structName); + structName = si.structName || si.name; + }else if(2===arguments.length){ + si = ads(si); + si.name ??= structName; + }else{ + si = ads(si); + } + structName ??= si.structName; + //console.warn("arguments =",JSON.stringify(arguments)); + structName ??= opt.structName; + if( !structName ) toss("One of 'name' or 'structName' are required."); + if( si.adapt ){ + Object.keys(si.adapt.struct||{}).forEach((k)=>{ + __adaptStruct.call(StructBinderImpl, k, si.adapt.struct[k]); + }); + Object.keys(si.adapt.set||{}).forEach((k)=>{ + __adaptSet.call(StructBinderImpl, k, si.adapt.set[k]); + }); + Object.keys(si.adapt.get||{}).forEach((k)=>{ + __adaptGet.call(StructBinderImpl, k, si.adapt.get[k]); + }); + } + if(!si.members && !si.sizeof){ + si.sizeof = sigSize(si.signature); + } + + const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags)); Object.defineProperties(StructCtor,{ debugFlags: debugFlags, isA: rop((v)=>v instanceof StructCtor), memberKey: __memberKeyProp, memberKeys: __structMemberKeys, - methodInfoForKey: rop(function(mKey){ - }), - structInfo: rop(structInfo), + //methodInfoForKey: rop(function(mKey){/*???*/}), + structInfo: rop(si), structName: rop(structName) }); - StructCtor.prototype = new StructType(structName, structInfo, rop); + 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*/ }); - Object.keys(structInfo.members).forEach( - (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name]) - ); + + + let lastMember = false; + let offset = 0; + //console.warn(structName,"si =",si); + si.offset ??= 0; + Object.keys(si.members || {}).forEach((k)=>{ + // Sanity checks of sizeof/offset info... + let m = ads(si.members[k]); + if(!m.members && !m.sizeof){ + /* ^^^^ fixme: we need to recursively collect all sizeofs + before updating that. */ + m.sizeof = sigSize(m.signature); + if(!m.sizeof){ + toss(sPropName(structName,k), "is missing a sizeof property.",m); + } + } + if( undefined===m.offset ){ + m.offset = offset; + } + si.members[k] = m; + if(!lastMember || lastMember.offset < m.offset) lastMember = m; + makeMemberWrapper.call(self, StructCtor, k, m) + offset += m.sizeof; + //console.warn("offset",sPropName(structName,k),offset); + }); + + if( !lastMember ) toss("No member property descriptions found."); + if( !si.sizeof ) si.sizeof = offset; + if(si.sizeof===1){ + (si.signature === 'c' || si.signature === 'C') || + toss("Unexpected sizeof==1 member", + sPropName(structName,k), + "with signature",si.signature); + }else{ + // sizes and offsets of size-1 members may be odd values, but + // others may not. + if(0!==(si.sizeof%4)){ + console.warn("Invalid struct member description",si); + toss(structName,"sizeof is not aligned. sizeof="+si.sizeof); + } + if(0!==(si.offset%4)){ + console.warn("Invalid struct member description",si); + 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); + } return StructCtor; + }/*StructBinderImpl*/; + + const StructBinder = function StructBinder(structName, structInfo){ + return (1==arguments.length) + ? StructBinderImpl.call(StructBinder, structName) + : StructBinderImpl.call(StructBinder, structName, structInfo); }; StructBinder.StructType = StructType; StructBinder.config = config; StructBinder.allocCString = __allocCString; + StructBinder.adaptGet = __adaptGet; + StructBinder.adaptSet = __adaptSet; + StructBinder.adaptStruct = __adaptStruct; if(!StructBinder.debugFlags){ StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags); } diff --git a/ext/wasm/mkwasmbuilds.c b/ext/wasm/mkwasmbuilds.c index 67b4a8915f..2730d9d766 100644 --- a/ext/wasm/mkwasmbuilds.c +++ b/ext/wasm/mkwasmbuilds.c @@ -284,7 +284,7 @@ const BuildDefs oBuildDefs = { " -DSQLITE_SPEEDTEST1_WASM" " $(SQLITE_OPT)" " -USQLITE_WASM_BARE_BONES" - " -USQLITE_C -DSQLITE_C=$(sqlite3.c)" + " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)" " $(speedtest1.exit-runtime0)" " $(speedtest1.c.in)" " -lm", @@ -312,7 +312,7 @@ const BuildDefs oBuildDefs = { " -DSQLITE_SPEEDTEST1_WASM" " $(SQLITE_OPT)" " -USQLITE_WASM_BARE_BONES" - " -USQLITE_C -DSQLITE_C=$(sqlite3.c)" + " -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c)" " $(speedtest1.exit-runtime0)" " $(speedtest1.c.in)" " -lm", diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index cff66c39c9..a146a871d8 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 instance/); + mustThrowMatching(()=>{k1.$pP=1}, /disposed struct instance/); }finally{ k1.dispose(); k2.dispose(); diff --git a/manifest b/manifest index 5c4a88a399..54bfcdf842 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sthe\s".www"\scommand\sof\sthe\sCLI\sso\sthat\sit\sworks\son\sunix\ssystems\swith\nnewer\sweb\sbrowsers\sthat\sdo\snot\sallow\saccess\sto\sfiles\sin\s/tmp. -D 2025-11-10T01:46:06.786 +C Reworking\sof\sJS\sinternals\sto\ssupport\sbinding\sof\snested\sC\sstructs\s(like\ssqlite3_index_constraint\sand\sfriends)\sand\sallow\ssome\sof\sthe\sautomated\sJS/C\sconversions\sto\sbe\splugged\sin\sat\sthe\sstruct-binding\slevel,\ssimplifying\show\sstruct\smembers,\sin\sparticular\sfunction\spointers,\scan\sbe\sused\sfrom\sJS. +D 2025-11-10T07:41:54.187 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -590,10 +590,10 @@ F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e F ext/wasm/api/post-js-footer.js 5bd7170b5e8ce7b62102702bbcf47ef7b3b49cd56ed40c043fd990aa715b74ee F ext/wasm/api/post-js-header.js 79d078aec33d93b640a19c574b504d88bb2446432f38e2fbb3bb8e36da436e70 F ext/wasm/api/pre-js.c-pp.js a876c6399dff29b6fe9e434036beb89889164cc872334e184291723ecc7cb072 -F ext/wasm/api/sqlite3-api-cleanup.js a3d6b9e449aefbb8bba283c2ba9477e2333a0eeb94a7a26b5bf952736f65a6dd +F ext/wasm/api/sqlite3-api-cleanup.js 79b54a566291e17c0c3e165c6c4969c48ec17cd297755180151af65ac616dfa0 F ext/wasm/api/sqlite3-api-glue.c-pp.js 79a54b54ca6324d28e31e19b56bbaebb7d2cc4b3079066e7e901333fa5047c53 F ext/wasm/api/sqlite3-api-oo1.c-pp.js 31dbfd470c91ffd96d77399b749bab6b69e3ba9074188833f97ac13f087cf07b -F ext/wasm/api/sqlite3-api-prologue.js 307583ff39a978c897c4ef4ce53fe231dce5c73dc84785969c81c1ab5960a293 +F ext/wasm/api/sqlite3-api-prologue.js b6b2fd1720c484e168705909862442b4524a1e61e16b4549a5725dd28c3cecc2 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 @@ -608,7 +608,7 @@ F ext/wasm/c-pp-lite.c 8fa0148e73782a86274db688c4730e2962cd675af329490493adddaf3 F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css e97549bab24126c24e0daabfe2de9bb478fb0a69fdb2ddd0a73a992c091aad6f -F ext/wasm/common/whwasmutil.js 0d539324097fc83b953e9844267359ba0fd02286caa784ea2f597ced279ea640 +F ext/wasm/common/whwasmutil.js b4fd73fac162406e67f276647ff8bae51881e15eebdf4b725aeb2b10ab1e056a F ext/wasm/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 @@ -625,10 +625,10 @@ 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 bbac67bc7a79dca34afe6215fd16b27768d84e22273507206f888c117e2ede7d +F ext/wasm/jaccwabyt/jaccwabyt.js 1e734c624205cdf621f322972dfb0fc8013d573a5882f57492a6830e5ec23e17 F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x -F ext/wasm/mkwasmbuilds.c 5e194df8763c8e5b2de070575a5f1bc7d7fb862f03c09d3cb9c56e0fa57b7e77 +F ext/wasm/mkwasmbuilds.c 1b53c4d2a1350c19a96a8cdfbda6a39baea9d2142bfe0cbef0ccb0e898787f47 F ext/wasm/module-symbols.html e54f42112e0aac2a31f850ab33e7f2630a2ea4f63496f484a12469a2501e07e2 F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 @@ -644,7 +644,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 56a7889415b996f684765aff07d35ac8a31343201f887ac61d7dd14678d9f0f0 +F ext/wasm/tester1.c-pp.js 2c255093205a0dac9dae7475030665c2c9d6dccc857de68ee7daf49aa82e6de8 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 @@ -2167,8 +2167,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 a1f9c977b83fab11c54710070dbedfaea47195050946db74075bdd3ade97a4c8 -R 433947701e1e8d1dbfecf3626f6f1737 -U drh -Z bf0d9abc0630918a2bdb7897b1798077 +P 2f918c14bac28c567cc854b3d41dcdd59191a118bb5fdea9373945fe860161f5 +R ec4a247d32a2beb55a1852fe7b3a7b07 +U stephan +Z 6d1537ab535678201b3302bce4da7f8d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f4779a416a..963f060359 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2f918c14bac28c567cc854b3d41dcdd59191a118bb5fdea9373945fe860161f5 +bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643