'use strict';
globalThis.Jaccwabyt = function StructBinderFactory(config){
/* ^^^^ it is recommended that clients move that object into wherever
- they'd like to have it and delete the self-held copy ("self" being
- the global window or worker object). This API does not require the
- global reference - it is simply installed as a convenience for
- connecting these bits to other co-developed code before it gets
- removed from the global namespace.
+ they'd like to have it and delete the globalThis-held copy. This
+ API does not require the global reference - it is simply installed
+ as a convenience for connecting these bits to other co-developed
+ code before it gets removed from the global namespace.
*/
/** Throws a new Error, the message of which is the concatenation
all args with a space between each. */
const toss = (...args)=>{throw new Error(args.join(' '))};
- /**
- Implementing function bindings revealed significant
- shortcomings in Emscripten's addFunction()/removeFunction()
- interfaces:
-
- https://github.com/emscripten-core/emscripten/issues/17323
-
- Until those are resolved, or a suitable replacement can be
- implemented, our function-binding API will be more limited
- and/or clumsier to use than initially hoped.
- */
if(!(config.heap instanceof WebAssembly.Memory)
&& !(config.heap instanceof Function)){
toss("config.heap must be WebAssembly.Memory instance or a function.");
(config[k] instanceof Function) ||
toss("Config option '"+k+"' must be a function.");
});
+ const __heap = config.heap;
const SBF = StructBinderFactory;
- const heap = (config.heap instanceof Function)
- ? config.heap : (()=>new Uint8Array(config.heap.buffer)),
+ const heap = __heap ? __heap : ()=>new Uint8Array(__heap.buffer),
alloc = config.alloc,
dealloc = config.dealloc,
- log = config.log || console.log.bind(console),
+ log = config.log || console.debug.bind(console),
memberPrefix = (config.memberPrefix || ""),
memberSuffix = (config.memberSuffix || ""),
BigInt = globalThis['BigInt'],
BigInt64Array = globalThis['BigInt64Array'],
bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array,
- /* Undocumented (on purpose) config options: */
ptrIR = config.pointerIR
|| config.ptrIR/*deprecated*/
|| 'i32',
+ /* Undocumented (on purpose) config options: */
ptrSize = config.ptrSize/*deprecated*/
|| ('i32'===ptrIR ? 4 : 8)
;
// Int16Array uses the platform's endianness.
return new Int16Array(buffer)[0] === 256;
})();
+
/**
Some terms used in the internal docs:
StructType: a struct-wrapping class generated by this
framework.
+
DEF: struct description object.
+
SIG: struct member signature string.
*/
/** True if SIG s is-a pointer signature. */
const isPtrSig = (s)=>'p'===s || 'P'===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];
/** Returns the WASM IR form of the Emscripten-conventional letter
at SIG s[0]. Throws for an unknown SIG. */
}
toss("Unhandled DataView setter for signature:",s);
};
+
/**
- Returns either Number of BigInt, depending on the given
- SIG. This constructor is used in property setters to coerce
- the being-set value to the correct size.
+ Returns a factory for either Number or BigInt, depending on the
+ given SIG. This constructor is used in property setters to coerce
+ the being-set value to the correct pointer size.
*/
const sigDVSetWrapper = function(s){
switch(sigLetter(s)) {
};
/**
- In order to completely hide StructBinder-bound struct
- pointers from JS code, we store them in a scope-local
- WeakMap which maps the struct-bound objects to their WASM
- pointers. The pointers are accessible via
- boundObject.pointer, which is gated behind an accessor
- function, but are not exposed anywhere else in the
- object. The main intention of that is to make it impossible
- for stale copies to be made.
+ In order to completely hide StructBinder-bound struct pointers
+ from JS code, we store them in a scope-local WeakMap which maps
+ the struct-bound objects to their WASM pointers. The pointers are
+ accessible via boundObject.pointer, which is gated behind a
+ property interceptor, but are not exposed anywhere else in the
+ object.
*/
const __instancePointerMap = new WeakMap();
/** Property name for the pointer-is-external marker. */
const xPtrPropName = '(pointer-is-external)';
+ const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0);
+ const __isPtr64 = (ptr)=>(
+ ('bigint'===typeof ptr && ptr >= 0) || __isPtr32(ptr)
+ );
+
+ /**
+ isPtr() is an alias for isPtr32() or isPtr64(), depending on
+ ptrSize.
+ */
+ const __isPtr = (4===ptrSize) ? __isPtr32 : __isPtr64;
+
/** Frees the obj.pointer memory and clears the pointer
property. */
const __freeStruct = function(ctor, obj, m){
try{
if(x instanceof Function) x.call(obj);
else if(x instanceof StructType) x.dispose();
- else if('number' === typeof x) dealloc(x);
+ else if(__isPtr(x)) dealloc(x);
// else ignore. Strings are permitted to annotate entries
// to assist in debugging.
}catch(e){
memberKey: __memberKeyProp
});
- const isNumericValue = (v)=>{
- return (v instanceof Number)
- || Number.isFinite(v)
- || (bigIntEnabled
- ? ('bigint'===typeof v /*does not work: v instanceof BigInt*/)
- : false);
- };
-
/**
Pass this a StructBinder-generated prototype, and the struct
member description object. It will define property accessors for
toss("Cannot set struct property on disposed instance.");
}
if(null===v) v = __NullPtr;
- else while(!isNumericValue(v)){
+ else while(!__isPtr(v)){
if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){
// It's a struct instance: let's store its pointer value!
v = v.pointer || __NullPtr;
are based solely on feature compatibility tables provided at
[MDN][].
+**Non-browser compatibility**: this code does not target non-browser
+JS engines and is completely untested on them. That said, it "might
+work".
+
+**64-bit WASM:** as of 2025-09-21 this API supports 64-bit WASM builds
+but it has to be configured for it (see [](#api-binderfactory) for
+details).
+
**Formalities:**
- Author: [Stephan Beal][sgb]
> The author disclaims copyright to this source code. In place of a
> legal notice, here is a blessing:
>
-> May you do good and not evil.
-> May you find forgiveness for yourself and forgive others.
-> May you share freely, never taking more than you give.
+> - May you do good and not evil.
+> - May you find forgiveness for yourself and forgive others.
+> - May you share freely, never taking more than you give.
-----
a Uint8Array or Int8Array view of the WASM memory,
alloc: function(howMuchMemory){...},
dealloc: function(pointerToFree){...},
- pointerIR: 'i32' or 'i64' (WASM pointer type)
+ pointerIR: 'i32' or 'i64' // WASM pointer type - default = 'i32'
});
```
+See [](#api-binderfactory) for full details about the config options.
+
It also offers a number of other settings, but all are optional except
for the ones shown above. Those three config options abstract away
details which are specific to a given WASM environment. They provide
- **All of these types are numeric**. Attempting to set any
struct-bound property to a non-numeric value will trigger an
exception except in cases explicitly noted otherwise.
-- **"Char" types**: WASM does not define an `int8` type, nor does it
- distinguish between signed and unsigned. This API treats `c` as
- `int8` and `C` as `uint8` for purposes of getting and setting values
- when using the `DataView` class. It is _not_ recommended that client
- code use these types in new WASM-capable code, but they were added
- for the sake of binding some immutable legacy code to WASM.
+- **"Char" types**: WASM does not define an `int8` type, nor does its
+ JS representation distinguish between signed and unsigned. This API
+ treats `c` as `int8` and `C` as `uint8` for purposes of getting and
+ setting values when using the `DataView` class. It is _not_
+ recommended that client code use these types in new WASM-capable
+ code, but they were added for the sake of binding some immutable
+ legacy code to WASM.
> Sidebar: Emscripten's public docs do not mention `p`, but their
generated code includes `p` as an alias for `i`, presumably to mean
is more descriptive, so this framework encourages the use of `p` for
pointer-type members. Using `p` for pointers also helps future-proof
the signatures against the eventuality that WASM eventually supports
-64-bit pointers. Note that sometimes `p` really means
-pointer-to-pointer, but the Emscripten JS/WASM glue does not offer
-that level of expressiveness in these signatures. We simply have to be
-aware of when we need to deal with pointers and pointers-to-pointers
-in JS code.
+64-bit pointers. Note that sometimes `p` _really_ means a
+pointer-to-pointer. We simply have to be aware of when we need to deal
+with pointers and pointers-to-pointers in JS code.
> Trivia: this API treates `p` as distinctly different from `i` in
some contexts, so its use is encouraged for pointer types.
*This support is experimental and subject to change.*
The method signature letter `p` means "pointer," which, in WASM, means
-"integer." `p` is treated as an integer for most contexts, while still
-also being a separate type (analog to how pointers in C are just a
-special use of unsigned numbers). A capital `P` changes the semantics
-of plain member pointers (but not, as of this writing, function
-pointer members) as follows:
+either int32 or int64. `p` is treated as a point for most contexts,
+while still also being a separate type for function signature purposes
+(analog to how pointers in C are just a special use of unsigned
+numbers). A capital `P` changes the semantics of plain member pointers
+(but not, as of this writing, function pointer members) as follows:
-- 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
- a [StructType][], an exception is triggered (regardless of whether
- `p` or `P` is used).
+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 pointer nor a
+[StructType][], an exception is triggered (regardless of whether `p`
+or `P` is used).
<a name='step-3'></a>
The binder factory supports the following options in its
configuration object argument:
+- `pointerIR` (Added 2025-09-21)
+ Optionally specify the WASM pointer size with the string `'i32'` or
+ `'i64'`, defaulting to the former. When using with 64-bit WASM
+ builds, this must be set to `'i64'` by the client. Any other value
+ triggers an exception.
- `heap`
Must be either a `WebAssembly.Memory` instance representing the WASM
heap memory OR a function which returns an Int8Array or Uint8Array
view of the WASM heap. In the latter case the function should, if
appropriate for the environment, account for the heap being able to
- grow. Jaccwabyt uses this property in such a way that it "should" be
- okay for the WASM heap to grow at runtime (that case is, however,
- untested).
+ grow. Jaccwabyt uses this property in such a way that it is legal
+ for the WASM heap to grow at runtime.
- `alloc`
Must be a function semantically compatible with Emscripten's
- `log`
Optional function used for debugging output. By default
- `console.log` is used but by default no debug output is generated.
+ `console.debug` is used but by default no debug output is generated.
This API assumes that the function will space-separate each argument
- (like `console.log` does). See [Appendix D](#appendix-d) for info
+ (like `console.debug` does). See [Appendix D](#appendix-d) for info
about enabling debugging output.
-
<a name='api-structbinder'></a>
API: Struct Binder
------------------------------------------------------------
- `memoryDump()`
Returns a Uint8Array which contains the current state of this
object's raw memory buffer. Potentially useful for debugging, but
- not much else. Note that the memory is necessarily, for
- compatibility with C, written in the host platform's endianness and
- is thus not useful as a persistent/portable serialization format.
+ not much else. The memory is necessarily, for compatibility with C,
+ written in the host platform's endianness. Since all WASM is
+ little-endian, it's the same everywhere. Even so: it should not be
+ used as a persistent serialization format because (A) any changes to
+ the struct will invalidate older serialized data and (B) serializing
+ member pointers is useless.
- `setMemberCString(memberName,str)`
Uses `StructType.allocCString()` to allocate a new C-style string,
let SQLite3 /* populated after module load */;
- const skipIn64BitBuild = function(msg=''){
- if( 8===SQLite3.wasm.ptr.size ){
- error("Skipping known-broken tests for 64-bit build.",msg); return true;
- }
- return false;
- };
-
{
const mapToString = (v)=>{
switch(typeof v){
name: 'virtual table #1: eponymous w/ manual exception handling',
predicate: (sqlite3)=>(!!sqlite3.capi.sqlite3_vtab || "Missing vtab support"),
test: function(sqlite3){
- //if( skipIn64BitBuild('virtual table #1') ) return;
const VT = sqlite3.vtab;
const tmplCols = Object.assign(Object.create(null),{
A: 0, B: 1
name: 'virtual table #2: non-eponymous w/ automated exception wrapping',
predicate: (sqlite3)=>!!sqlite3.capi.sqlite3_vtab || "Missing vtab support",
test: function(sqlite3){
- //if( skipIn64BitBuild('virtual table #2') ) return;
const VT = sqlite3.vtab;
const tmplCols = Object.assign(Object.create(null),{
A: 0, B: 1
.t({
name: 'OPFS db sanity checks',
test: async function(sqlite3){
- //if( skipIn64BitBuild('"opfs" vfs') ) return;
T.assert(capi.sqlite3_vfs_find('opfs'));
const opfs = sqlite3.opfs;
const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
.t({
name: 'OPFS import',
test: async function(sqlite3){
- //if( skipIn64BitBuild('"opfs" vfs import') ) return;
let db;
const filename = this.opfsDbFile;
try {
.t({
name: '(Internal-use) OPFS utility APIs',
test: async function(sqlite3){
- //if( skipIn64BitBuild('"opfs" internal APIs') ) return;
const filename = this.opfsDbFile;
const unlink = this.opfsUnlink;
T.assert(filename && !!unlink);
.t({
name: 'SAH sanity checks',
test: async function(sqlite3){
- //if( skipIn64BitBuild('"opfs-sahpool" vfs') ) return;
T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
.assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
const inst = sqlite3.installOpfsSAHPoolVfs,
predicate: ()=>hasOpfs() || "Requires OPFS to reproduce",
//predicate: ()=>false,
test: async function(sqlite3){
- //if( skipIn64BitBuild('pending repair of opfs') ) return;
/* https://sqlite.org/forum/forumpost/cf37d5ff1182c31081
The "opfs" VFS (but not SAHPool) was formerly misbehaving
-C General\scleanups\sand\sdead\scode\sremoval.
-D 2025-09-21T19:39:06.527
+C Fix\sa\sStructType.dispose()/ondispose()\smemleak\sin\sJaccwabyt\sbindings\sin\s64-bit\sbuilds\sand\supdate\sits\sdocs\sfor\s64-bit\sadditions.\sRemove\sthe\sparts\sof\stester1.js\srelated\sto\sskipping\sspecific\stests\sin\s64-bit\sbuilds.
+D 2025-09-21T20:26:47.094
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F ext/wasm/fiddle/index.html 17c7d6b21f40fbf462162c4311b63d760b065e419d9f5a96534963b0e52af940
F ext/wasm/index-dist.html 56132399702b15d70c474c3f1952541e25cb0922942868f70daf188f024b3730
F ext/wasm/index.html bcaa00eca521b372a6a62c7e7b17a870b0fcdf3e418a5921df1fd61e5344080d
-F ext/wasm/jaccwabyt/jaccwabyt.js a5809f1b9c4be24fdc5abc436f3ab978f93da626b044db92861db48d67bdd151
-F ext/wasm/jaccwabyt/jaccwabyt.md 3eb94b708090edcaba435aa15dc8346553162e0d01ba3eae7b7bf34c92d263b7
+F ext/wasm/jaccwabyt/jaccwabyt.js bbac67bc7a79dca34afe6215fd16b27768d84e22273507206f888c117e2ede7d
+F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f
F ext/wasm/mkwasmbuilds.c b722a3a44edc1498575d935939dfcbe23172f98b0f40d068998e0950707e749d
F ext/wasm/module-symbols.html dc476b403369b26a1a23773e13b80f41b9a49f0825e81435fe3600a7cfbbe337
F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96
F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
-F ext/wasm/tester1.c-pp.js 616c783aa6e4d29dd53202fa58cb10603954e141928766eb332874c0c539520a
+F ext/wasm/tester1.c-pp.js a2bc1a96dbe55e3b14ace35e8561893e84221d63794a9bc2ab5a5f1b1d6bc5a1
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
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P a6b9567001dad0293dc6a7fe9a7ec1a220e41d9426448e2ab91dbd551948be15
-R b1a746924c607bfa0934fa2cb810d7b7
+P 0336fa95e15c53ac6ab8152a840163a5aac64725874ffb848ce1d95e3af90586
+R b1fcd52f2c860acb4ea0ae4f12feba2f
U stephan
-Z 193b461c45ffcc12495a77014d8c0df4
+Z bf93d953cde2ac0d4ff0dc420f01f479
# Remove this line to create a well-formed Fossil manifest.
-0336fa95e15c53ac6ab8152a840163a5aac64725874ffb848ce1d95e3af90586
+6baea1d719b0c345fee5f32a3917ce9c507a2d8cb029eaca675daca77f297eba