mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-20 01:22:32 +03:00
Reworking of JS internals to support binding of nested C structs (like sqlite3_index_constraint and friends) and allow some of the automated JS/C conversions to be plugged in at the struct-binding level, simplifying how struct members, in particular function pointers, can be used from JS.
FossilOrigin-Name: bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643
This commit is contained in:
@@ -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().
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array;
|
||||
|
||||
//console.warn("config",config);
|
||||
let ptr = alloc(1);
|
||||
const ptrIR = config.pointerIR
|
||||
|| config.ptrIR/*deprecated*/
|
||||
|| 'i32',
|
||||
|| ('bigint'===typeof ptr ? 'i64' : 'i32');
|
||||
/* Undocumented (on purpose) config options: */
|
||||
ptrSize = config.ptrSize/*deprecated*/
|
||||
|| ('i32'===ptrIR ? 4 : 8)
|
||||
;
|
||||
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
|
||||
/* 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 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 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 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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
24
manifest
24
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.
|
||||
|
||||
@@ -1 +1 @@
|
||||
2f918c14bac28c567cc854b3d41dcdd59191a118bb5fdea9373945fe860161f5
|
||||
bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643
|
||||
|
||||
Reference in New Issue
Block a user