return ()=>toss(sPropName(structName,propName),"is read-only.");
};
- /**
- When C code passes a pointer of a bound struct to back into
- a JS function via a function pointer struct member, it
- arrives in JS as a number (pointer).
- StructType.instanceForPointer(ptr) can be used to get the
- instance associated with that pointer, and __ptrBacklinks
- holds that mapping. WeakMap keys must be objects, so we
- cannot use a weak map to map pointers to instances. We use
- the StructType constructor as the WeakMap key, mapped to a
- plain, prototype-less Object which maps the pointers to
- struct instances. That arrangement gives us a
- per-StructType type-safe way to resolve pointers.
- */
- const __ptrBacklinks = new WeakMap();
- /**
- Similar to __ptrBacklinks but is scoped at the StructBinder
- level and holds pointer-to-object mappings for all struct
- instances created by any struct from any StructFactory
- which this specific StructBinder has created. The intention
- of this is to help implement more transparent handling of
- pointer-type property resolution.
- */
- const __ptrBacklinksGlobal = Object.create(null);
-
/**
In order to completely hide StructBinder-bound struct
pointers from JS code, we store them in a scope-local
});
}
delete obj.ondispose;
- delete __ptrBacklinks.get(ctor)[m];
- delete __ptrBacklinksGlobal[m];
__instancePointerMap.delete(obj);
if(ctor.debugFlags.__flags.dealloc){
log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
}
if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
__instancePointerMap.set(obj, m);
- __ptrBacklinks.get(ctor)[m] = obj;
- __ptrBacklinksGlobal[m] = obj;
}catch(e){
__freeStruct(ctor, obj, m);
throw e;
return emscriptenFormat ? f._(m.signature) : m.signature;
};
- /**
- Returns the instanceForPointer() impl for the given
- StructType constructor.
- */
- const __instanceBacklinkFactory = function(ctor){
- const b = Object.create(null);
- __ptrBacklinks.set(ctor, b);
- return (ptr)=>b[ptr];
- };
-
const __ptrPropDescriptor = {
configurable: false, enumerable: false,
get: function(){return __instancePointerMap.get(this)},
*/
Object.defineProperties(StructType, {
allocCString: rop(__allocCString),
- instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]),
isA: rop((v)=>v instanceof StructType),
hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
memberKey: __memberKeyProp
new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
)[f._.getters[sigGlyph]](0, isLittleEndian);
if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
- /* // Removed: it is legal for multiple StructType instances to
- // proxy the same pointer, and instanceForPointer() cannot account
- // for that.
- if(rc && isAutoPtrSig(descr.signature)){
- rc = StructType.instanceForPointer(rc) || rc;
- if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc);
- }*/
return rc;
};
if(descr.readOnly){
};
Object.defineProperties(StructCtor,{
debugFlags: debugFlags,
- disposeAll: rop(function(){
- const map = __ptrBacklinks.get(StructCtor);
- Object.keys(map).forEach(function(ptr){
- const b = map[ptr];
- if(b) __freeStruct(StructCtor, b, ptr);
- });
- __ptrBacklinks.set(StructCtor, Object.create(null));
- return StructCtor;
- }),
- instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)),
isA: rop((v)=>v instanceof StructCtor),
memberKey: __memberKeyProp,
memberKeys: __structMemberKeys,
- resolveToInstance: rop(function(v, throwIfNot=false){
- if(!(v instanceof StructCtor)){
- v = Number.isSafeInteger(v)
- ? StructCtor.instanceForPointer(v) : undefined;
- }
- if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName);
- return v;
- }),
methodInfoForKey: rop(function(mKey){
}),
structInfo: rop(structInfo),
);
return StructCtor;
};
- StructBinder.instanceForPointer = StructType.instanceForPointer;
StructBinder.StructType = StructType;
StructBinder.config = config;
StructBinder.allocCString = __allocCString;
of plain member pointers (but not, as of this writing, function
pointer members) as follows:
-- <s>When a `P`-type member is **fetched** via `myStruct.x` and its
- value is a non-0 integer,
- [`StructBinder.instanceForPointer()`][StructBinder] is used to try
- to map that pointer to a struct instance. If a match is found, the
- "get" operation returns that instance instead of the integer. If no
- match is found, it behaves exactly as for `p`, returning the integer
- value.</s> Removed because it's legal and possible to for multiple
- instances to proxy the same pointer and this infrastructure cannot
- account for that.
- When a `P`-type member is **set** via `myStruct.x=y`, if
[`(y instanceof StructType)`][StructType] then the value of `y.pointer` is
stored in `myStruct.x`. If `y` is neither a number nor
memory on the WASM heap. We must not simply rely on garbage collection
to clean up the instances because doing so will not free up the WASM
heap memory. The correct way to free up that memory is to use the
-object's `dispose()` method. Alternately, there is a "nuclear option":
-`MyBinder.disposeAll()` will free the memory allocated for _all_
-instances which have not been manually disposed.
+object's `dispose()` method.
The following usage pattern offers one way to easily ensure proper
cleanup of struct instances:
any of its "significant" configuration values may have undefined
results.
-- <s>`instanceForPointer(pointer)`</s> (DEPRECATED)
- *Do not use this - it will be removed* because it is legal for
- multiple StructType instances to proxy the same pointer, and
- instanceForPointer() cannot account for that.\
- Given a pointer value relative to `config.memory`, if that pointer
- resolves to a struct of _any type_ generated via the same Struct
- Binder, this returns the struct instance associated with it, or
- `undefined` if no struct object is mapped to that pointer. This
- differs from the struct-type-specific member of the same name in
- that this one is not "type-safe": it does not know the type of the
- returned object (if any) and may return a struct of any
- [StructType][] for which this Struct Binder has created a
- constructor. It cannot return instances created via a different
- [StructBinderFactory][] because each factory can hypothetically have
- a different memory heap.
-
-
<a name='api-structtype'></a>
API: Struct Type
------------------------------------------------------------
[struct's constructor][StructCtors]. If true, the memory is owned by
someone other than the object and must outlive the object.
-- <s>`instanceForPointer(pointer)`</s> (DEPRECATED)
- Works identically to the [StructBinder][] method of the same name.
-
- `isA(value)`
Returns true if its argument is a StructType instance _from the same
[StructBinder][]_ as this StructType.
These constructors have the following "static" members:
-- `disposeAll()`
- For each instance of this struct, the equivalent of its `dispose()`
- method is called. This frees all WASM-allocated memory associated
- with _all_ instances and clears the `instanceForPointer()`
- mappings. Returns `this`.
-
-- <s>`instanceForPointer(pointer)`</s> (DEPRECATED)
- Given a pointer value (accessible via the `pointer` property of all
- struct instances) which ostensibly refers to an instance of this
- class, this returns the instance associated with it, or `undefined`
- if no object _of this specific struct type_ is mapped to that
- pointer. When C-side code calls back into JS code and passes a
- pointer to an object, this function can be used to type-safely
- "cast" that pointer back to its original object.
-
- `isA(value)`
Returns true if its argument was created by this constructor.
- `memberKeys(string)`
Works exactly as documented for [StructType][].
-- `resolveToInstance(value [,throwIfNot=false])`
- Works like `instanceForPointer()` but accepts either an instance
- of this struct type or a pointer which resolves to one.
- It returns an instance of this struct type on success.
- By default it returns a falsy value if its argument is not,
- or does not resolve to, an instance of this struct type,
- but if passed a truthy second argument then it will throw
- instead.
-
- `structInfo`
The structure description passed to [StructBinder][] when this
constructor was generated.
assert(undefined === K.prototype.lookupMember('nope',false)).
assert(k1 instanceof StructType).
assert(StructType.isA(k1)).
- assert(K.resolveToInstance(k1.pointer)===k1).
- mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
- assert(k1 === StructType.instanceForPointer(k1.pointer)).
mustThrowMatching(()=>k1.$ro = 1, /read-only/);
Object.keys(MyStructDef.members).forEach(function(key){
key = K.memberKey(key);
" from "+k1.memoryDump());
});
T.assert('number' === typeof k1.pointer).
- mustThrowMatching(()=>k1.pointer = 1, /pointer/).
- assert(K.instanceForPointer(k1.pointer) === k1);
+ mustThrowMatching(()=>k1.pointer = 1, /pointer/);
k1.$p4 = 1; k1.$pP = 2;
T.assert(1 === k1.$p4).assert(2 === k1.$pP);
if(MyStructDef.members.$p8){
assert('number' === typeof k1.$cstr).
assert('A C-string.' === k1.memberToJsString('cstr'));
k1.$pP = k2;
- T.assert(k1.$pP === k2);
+ T.assert(k1.$pP === k2.pointer);
k1.$pP = null/*null is special-cased to 0.*/;
T.assert(0===k1.$pP);
let ptr = k1.pointer;
k1.dispose();
T.assert(undefined === k1.pointer).
- assert(undefined === K.instanceForPointer(ptr)).
mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
- const k3 = new K();
- ptr = k3.pointer;
- T.assert(k3 === K.instanceForPointer(ptr));
- K.disposeAll();
- T.assert(ptr).
- assert(undefined === k2.pointer).
- assert(undefined === k3.pointer).
- assert(undefined === K.instanceForPointer(ptr));
}finally{
k1.dispose();
k2.dispose();
assert(wts instanceof WTStruct).
assert(wts instanceof StructType).
assert(StructType.isA(wts)).
- assert(wts === StructType.instanceForPointer(wts.pointer));
- T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
- assert(0===wts.$ppV).assert(0===wts.$xFunc).
- assert(WTStruct.instanceForPointer(wts.pointer) === wts);
+ assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
+ assert(0===wts.$ppV).assert(0===wts.$xFunc);
const testFunc =
W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
let counter = 0;
/*log("This from a JS function called from C, "+
"which itself was called from JS. arg =",arg);*/
++counter;
- T.assert(WTStruct.instanceForPointer(arg) === wts);
if(3===counter){
tossQuietly("Testing exception propagation.");
}
testFunc(wts.pointer);
//log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
- .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer))
+ .assert(wts.$ppV === wts.pointer)
.assert('string' === typeof wts.memberToJsString('cstr'))
.assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
.mustThrowMatching(()=>wts.memberToJsString('xFunc'),
;
testFunc(wts.pointer);
T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
- .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer));
+ .assert(wts.$ppV === wts.pointer);
/** The 3rd call to wtsFunc throw from JS, which is called
from C, which is called from JS. Let's ensure that
that exception propagates back here... */
T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
W.uninstallFunction(wts.$xFunc);
wts.$xFunc = 0;
- if(autoResolvePtr){
- wts.$ppV = 0;
- T.assert(!wts.$ppV);
- //WTStruct.debugFlags(0x03);
- wts.$ppV = wts;
- T.assert(wts === wts.$ppV)
- //WTStruct.debugFlags(0);
- }
+ wts.$ppV = 0;
+ T.assert(!wts.$ppV);
+ //WTStruct.debugFlags(0x03);
+ wts.$ppV = wts;
+ T.assert(wts.pointer === wts.$ppV)
wts.setMemberCString('cstr', "A C-string.");
T.assert(Array.isArray(wts.ondispose)).
assert(wts.ondispose[0] === wts.$cstr).
assert('A C-string.' === wts.memberToJsString('cstr'));
const ptr = wts.pointer;
wts.dispose();
- T.assert(ptr).assert(undefined === wts.pointer).
- assert(undefined === WTStruct.instanceForPointer(ptr))
+ T.assert(ptr).assert(undefined === wts.pointer);
}finally{
wts.dispose();
}
}/*StructBinder*/)
- ////////////////////////////////////////////////////////////////////
- .t('sqlite3.StructBinder part 2', function(sqlite3){
- // https://www.sqlite.org/c3ref/vfs.html
- // https://www.sqlite.org/c3ref/io_methods.html
- const sqlite3_io_methods = capi.sqlite3_io_methods,
- sqlite3_vfs = capi.sqlite3_vfs,
- sqlite3_file = capi.sqlite3_file;
- //log("struct sqlite3_file", sqlite3_file.memberKeys());
- //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
- //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
- const installMethod = function callee(tgt, name, func){
- if(1===arguments.length){
- return (n,f)=>callee(tgt,n,f);
- }
- if(!callee.argcProxy){
- callee.argcProxy = function(func,sig){
- return function(...args){
- if(func.length!==arguments.length){
- toss("Argument mismatch. Native signature is:",sig);
- }
- return func.apply(this, args);
- }
- };
- callee.ondisposeRemoveFunc = function(){
- if(this.__ondispose){
- const who = this;
- this.__ondispose.forEach(
- (v)=>{
- if('number'===typeof v){
- try{wasm.uninstallFunction(v)}
- catch(e){/*ignore*/}
- }else{/*wasm function wrapper property*/
- delete who[v];
- }
- }
- );
- delete this.__ondispose;
- }
- };
- }/*static init*/
- const sigN = tgt.memberSignature(name),
- memKey = tgt.memberKey(name);
- //log("installMethod",tgt, name, sigN);
- if(!tgt.__ondispose){
- T.assert(undefined === tgt.ondispose);
- tgt.ondispose = [callee.ondisposeRemoveFunc];
- tgt.__ondispose = [];
- }
- const fProxy = callee.argcProxy(func, sigN);
- const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
- tgt[memKey] = pFunc;
- /**
- ACHTUNG: function pointer IDs are from a different pool than
- allocation IDs, starting at 1 and incrementing in steps of 1,
- so if we set tgt[memKey] to those values, we'd very likely
- later misinterpret them as plain old pointer addresses unless
- unless we use some silly heuristic like "all values <5k are
- presumably function pointers," or actually perform a function
- lookup on every pointer to first see if it's a function. That
- would likely work just fine, but would be kludgy.
-
- It turns out that "all values less than X are functions" is
- essentially how it works in wasm: a function pointer is
- reported to the client as its index into the
- __indirect_function_table.
-
- So... once jaccwabyt can be told how to access the
- function table, it could consider all pointer values less
- than that table's size to be functions. As "real" pointer
- values start much, much higher than the function table size,
- that would likely work reasonably well. e.g. the object
- pointer address for sqlite3's default VFS is (in this local
- setup) 65104, whereas the function table has fewer than 600
- entries.
- */
- const wrapperKey = '$'+memKey;
- tgt[wrapperKey] = fProxy;
- tgt.__ondispose.push(pFunc, wrapperKey);
- //log("tgt.__ondispose =",tgt.__ondispose);
- return (n,f)=>callee(tgt, n, f);
- }/*installMethod*/;
-
- const installIOMethods = function instm(iom){
- (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
- if(!instm._requireFileArg){
- instm._requireFileArg = function(arg,methodName){
- arg = capi.sqlite3_file.resolveToInstance(arg);
- if(!arg){
- err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
- }
- return arg;
- };
- instm._methods = {
- // https://sqlite.org/c3ref/io_methods.html
- xClose: /*i(P)*/function(f){
- /* int (*xClose)(sqlite3_file*) */
- log("xClose(",f,")");
- if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
- f.dispose(/*noting that f has externally-owned memory*/);
- return 0;
- },
- xRead: /*i(Ppij)*/function(f,dest,n,offset){
- /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
- log("xRead(",arguments,")");
- if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
- wasm.heap8().fill(0, dest + offset, n);
- return 0;
- },
- xWrite: /*i(Ppij)*/function(f,dest,n,offset){
- /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
- log("xWrite(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xTruncate: /*i(Pj)*/function(f){
- /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
- log("xTruncate(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xSync: /*i(Pi)*/function(f){
- /* int (*xSync)(sqlite3_file*, int flags) */
- log("xSync(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xFileSize: /*i(Pp)*/function(f,pSz){
- /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
- log("xFileSize(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
- wasm.setMemValue(pSz, 0/*file size*/);
- return 0;
- },
- xLock: /*i(Pi)*/function(f){
- /* int (*xLock)(sqlite3_file*, int) */
- log("xLock(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xUnlock: /*i(Pi)*/function(f){
- /* int (*xUnlock)(sqlite3_file*, int) */
- log("xUnlock(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xCheckReservedLock: /*i(Pp)*/function(){
- /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
- log("xCheckReservedLock(",arguments,")");
- return 0;
- },
- xFileControl: /*i(Pip)*/function(){
- /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
- log("xFileControl(",arguments,")");
- return capi.SQLITE_NOTFOUND;
- },
- xSectorSize: /*i(P)*/function(){
- /* int (*xSectorSize)(sqlite3_file*) */
- log("xSectorSize(",arguments,")");
- return 0/*???*/;
- },
- xDeviceCharacteristics:/*i(P)*/function(){
- /* int (*xDeviceCharacteristics)(sqlite3_file*) */
- log("xDeviceCharacteristics(",arguments,")");
- return 0;
- }
- };
- }/*static init*/
- iom.$iVersion = 1;
- Object.keys(instm._methods).forEach(
- (k)=>installMethod(iom, k, instm._methods[k])
- );
- }/*installIOMethods()*/;
-
- const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
- const err = console.error.bind(console);
- try {
- const IOM = sqlite3_io_methods, S3F = sqlite3_file;
- //log("iom proto",iom,iom.constructor.prototype);
- //log("sfile",sfile,sfile.constructor.prototype);
- T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
- //log("iom",iom);
- sfile.$pMethods = iom.pointer;
- T.assert(iom.pointer === sfile.$pMethods)
- .assert(IOM.resolveToInstance(iom))
- .assert(undefined ===IOM.resolveToInstance(sfile))
- .mustThrow(()=>IOM.resolveToInstance(0,true))
- .assert(S3F.resolveToInstance(sfile.pointer))
- .assert(undefined===S3F.resolveToInstance(iom))
- .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
- T.assert(0===iom.$iVersion);
- installIOMethods(iom);
- T.assert(1===iom.$iVersion);
- //log("iom.__ondispose",iom.__ondispose);
- T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
- }finally{
- iom.dispose();
- T.assert(undefined === iom.__ondispose);
- }
-
- const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
- try {
- const SB = sqlite3.StructBinder;
- T.assert(dVfs instanceof SB.StructType)
- .assert(dVfs.pointer)
- .assert('sqlite3_vfs' === dVfs.structName)
- .assert(!!dVfs.structInfo)
- .assert(SB.StructType.hasExternalPointer(dVfs))
- .assert(dVfs.$iVersion>0)
- .assert('number'===typeof dVfs.$zName)
- .assert('number'===typeof dVfs.$xSleep)
- .assert(wasm.functionEntry(dVfs.$xOpen))
- .assert(dVfs.memberIsString('zName'))
- .assert(dVfs.memberIsString('$zName'))
- .assert(!dVfs.memberIsString('pAppData'))
- .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
- /Invalid member type signature for C-string/)
- .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
- .assert('string' === typeof dVfs.memberToJsString('zName'))
- .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
- ;
- //log("Default VFS: @",dVfs.pointer);
- Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
- const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
- addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
- if(1===mbr.signature.length){
- let sep = '?', val = undefined;
- switch(mbr.signature[0]){
- // TODO: move this into an accessor, e.g. getPreferredValue(member)
- case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
- case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
- case 's': sep = '=';
- val = dVfs.memberToJsString(mname);
- break;
- }
- //log(prefix, sep, val);
- }else{
- //log(prefix," = funcptr @",addr, wasm.functionEntry(addr));
- }
- });
- }finally{
- dVfs.dispose();
- T.assert(undefined===dVfs.pointer);
- }
- }/*StructBinder part 2*/)
-
////////////////////////////////////////////////////////////////////
.t('sqlite3.wasm.pstack', function(sqlite3){
const P = wasm.pstack;