]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Reworking of JS internals to support binding of nested C structs (like sqlite3_index_...
authorstephan <stephan@noemail.net>
Mon, 10 Nov 2025 07:41:54 +0000 (07:41 +0000)
committerstephan <stephan@noemail.net>
Mon, 10 Nov 2025 07:41:54 +0000 (07:41 +0000)
FossilOrigin-Name: bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643

ext/wasm/api/sqlite3-api-cleanup.js
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/common/whwasmutil.js
ext/wasm/jaccwabyt/jaccwabyt.js
ext/wasm/mkwasmbuilds.c
ext/wasm/tester1.c-pp.js
manifest
manifest.uuid

index 22356632618c5cc0c7e63f84db1554f2b9e811e5..107da6968e0d4088c43c1982fa4d82ad71465e69 100644 (file)
@@ -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().
index 069f3fdb5c24bcfb76b3364429d47fe167f6e4c5..c2ba47f20eae588ca122a64f777d972be7e6bdaa 100644 (file)
@@ -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.
index 1c678f31f6899ecf99b9a7a1d87799a4b1aadeca..aadcf1f8b0f4ede98861cddb6143d1c8bd9141f2 100644 (file)
@@ -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
index 8ea08e2136e09a127a6a7b649c33983c7d62d5d5..20e39b0a9dc59c839a4cd85a3126795b9278dcdc 100644 (file)
@@ -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);
   }
index 67b4a8915f26dda0cc696e0e114afb4ea426f3db..2730d9d766341bd44ef3fa6c42869cdc8276f2b3 100644 (file)
@@ -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",
index cff66c39c9833e50a0fd304fb0fdf1bec2a8cb7a..a146a871d8bfc3af403ce3ce8bcf4aa595811d87 100644 (file)
@@ -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();
index 5c4a88a3992efacec08a73dccb876a0efc1bd44d..54bfcdf8420faa6c9b1ea273327e59590e06257e 100644 (file)
--- 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.
index f4779a416a6a00bff996210e6577aa23f8a18f8d..963f0603598f7c50f8e6f40e31c338c1c412da3f 100644 (file)
@@ -1 +1 @@
-2f918c14bac28c567cc854b3d41dcdd59191a118bb5fdea9373945fe860161f5
+bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643