From: stephan Date: Sat, 22 Nov 2025 02:23:02 +0000 (+0000) Subject: Latest side-stream jaccwabyt/wasmutil. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7845d312b18385253a69e627c9ea56eff94c3a5b;p=thirdparty%2Fsqlite.git Latest side-stream jaccwabyt/wasmutil. FossilOrigin-Name: e0b33b51229a977cc3fa8a5a6c8ea59669f8bf566b2a6330fd24da1ad886a716 --- diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index 0d75675ca5..bca05a1eef 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -16,27 +16,35 @@ More specifically: - https://fossil.wanderinghorse.net/r/jaccwabyt/file/common/whwasmutil.js + https://fossil.wanderinghorse.net/r/jaccwabyt/dir/wasmutil and SQLite: https://sqlite.org This file is kept in sync between both of those trees. + + This build was generated using: + + ./c-pp -o js/whwasmutil.js -@policy=error wasmutil/whwasmutil.c-pp.js + + by libcmpp 2.x 2fc4afc31f6505c27b9c34988973a2bd9b157d559247cdd26868ae75632c3a5e @ 2025-11-16 23:03:27.352 UTC */ /** - The primary goal of this function is to replace, where possible, - Emscripten-generated glue code with equivalent utility code which - can be used in arbitrary WASM environments built with toolchains - other than Emscripten. To that end, it populates the given object - with various WASM-specific APIs. These APIs work with both 32- and - 64-bit WASM builds. + The primary goal of this function is to provide JS/WASM utility + code similar to some of that provided by Emscripten-generated + builds, the difference being that this one can be used in arbitrary + WASM environments built with toolchains other than Emscripten. To + that end, it populates the given object with various WASM-specific + APIs. These APIs work with both 32- and 64-bit WASM builds. Forewarning: this API explicitly targets only browser environments. If a given non-browser environment has the capabilities needed for a given feature (e.g. TextEncoder), great, but it does not go out of its way to account for them and does not provide compatibility - crutches for them. + crutches for them. That said: no specific incompatibilities with, + e.g., node.js are known (whereas it is known that some folks + use this with node.js). Intended usage: @@ -217,7 +225,9 @@ newly-created (or config-provided) target. The current approach seemed better at the time. */ -globalThis.WhWasmUtilInstaller = function(target){ +'use strict'; +globalThis.WhWasmUtilInstaller = +function WhWasmUtilInstaller(target){ 'use strict'; if(undefined===target.bigIntEnabled){ target.bigIntEnabled = !!globalThis['BigInt64Array']; @@ -766,6 +776,7 @@ globalThis.WhWasmUtilInstaller = function(target){ */ target.uninstallFunction = function(ptr){ if(!ptr && __NullPtr!==ptr) return undefined; + const ft = target.functionTable(); cache.freeFuncIndexes.push(ptr); const rc = ft.get(ptr); @@ -2461,7 +2472,8 @@ globalThis.WhWasmUtilInstaller = function(target){ - If `wasmUtilTarget.alloc` is not set and `instance.exports.malloc` is, it installs `wasmUtilTarget.alloc()` and `wasmUtilTarget.dealloc()` - wrappers for the exports `malloc` and `free` functions. + wrappers for the exports' `malloc` and `free` functions + if exports.malloc exists. It returns a function which, when called, initiates loading of the module and returns a Promise. When that Promise resolves, it calls @@ -2484,7 +2496,9 @@ globalThis.WhWasmUtilInstaller = function(target){ Error handling is up to the caller, who may attach a `catch()` call to the promise. */ -globalThis.WhWasmUtilInstaller.yawl = function(config){ +globalThis.WhWasmUtilInstaller +.yawl = function yawl(config){ + 'use strict'; const wfetch = ()=>fetch(config.uri, {credentials: 'same-origin'}); const wui = this; const finalThen = function(arg){ @@ -2509,7 +2523,7 @@ globalThis.WhWasmUtilInstaller.yawl = function(config){ tgt.alloc = function(n){ return exports.malloc(n) || toss("Allocation of",n,"bytes failed."); }; - tgt.dealloc = function(m){exports.free(m)}; + tgt.dealloc = function(m){m && exports.free(m)}; } wui(tgt); } @@ -2528,4 +2542,6 @@ globalThis.WhWasmUtilInstaller.yawl = function(config){ .then(finalThen) ; return loadWasm; -}.bind(globalThis.WhWasmUtilInstaller)/*yawl()*/; +}.bind( +globalThis.WhWasmUtilInstaller +)/*yawl()*/; diff --git a/ext/wasm/jaccwabyt/jaccwabyt.js b/ext/wasm/jaccwabyt/jaccwabyt.js index 894c390ca7..6fa86c3416 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.js +++ b/ext/wasm/jaccwabyt/jaccwabyt.js @@ -16,9 +16,17 @@ Project homes: - https://fossil.wanderinghorse.net/r/jaccwabyt - https://sqlite.org/src/dir/ext/wasm/jaccwabyt + + This build was generated using: + + ./c-pp -o js/jaccwabyt.js -@policy=error jaccwabyt/jaccwabyt.c-pp.js + + by libcmpp 2.x 2fc4afc31f6505c27b9c34988973a2bd9b157d559247cdd26868ae75632c3a5e @ 2025-11-16 23:03:27.352 UTC */ 'use strict'; -globalThis.Jaccwabyt = function StructBinderFactory(config){ +globalThis.Jaccwabyt = +function StructBinderFactory(config){ + 'use strict'; /* ^^^^ it is recommended that clients move that object into wherever they'd like to have it and delete the globalThis-held copy. This API does not require the global reference - it is simply installed @@ -65,18 +73,20 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ //console.warn("config",config); let ptr; - const ptrIR = config.pointerIR + const ptrSize = config.pointerSize + || config.ptrSize/*deprecated*/ + || ('bigint'===typeof (ptr = alloc(1)) ? 8 : 4); + const ptrIR = config.pointerIR/*deprecated*/ || config.ptrIR/*deprecated*/ - || ('bigint'===typeof (ptr = alloc(1)) ? 'i64' : 'i32'); - /* Undocumented (on purpose) config options: */ - const ptrSize = config.ptrSize/*deprecated*/ - || ('i32'===ptrIR ? 4 : 8); - dealloc(ptr); - ptr = undefined; + || (4===ptrSize ? 'i32' : 'i64'); + if( ptr ){ + 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); + if(ptrIR!=='i32' && ptrIR!=='i64') toss("Invalid pointer representation:",ptrIR); /** Either BigInt or, if !bigIntEnabled, a function which throws complaining that BigInt is not enabled. */ @@ -853,6 +863,8 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ }/*makeMemberStructWrapper()*/; /** + This is where most of the magic happens. + 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 @@ -936,7 +948,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){ } if(!this.pointer){ toss("Cannot set native property on a disposed", - this.structSame,"instance."); + this.structName,"instance."); } if( setterProxy ) v = setterProxy.apply(this,[key,v]); if( null===v || undefined===v ) v = __NullPtr; diff --git a/ext/wasm/jaccwabyt/jaccwabyt.md b/ext/wasm/jaccwabyt/jaccwabyt.md index 5ec3151d50..5c30268e89 100644 --- a/ext/wasm/jaccwabyt/jaccwabyt.md +++ b/ext/wasm/jaccwabyt/jaccwabyt.md @@ -12,6 +12,35 @@ friction. (If that means nothing to you, neither will the rest of this page!) +To the best of its creator's fallible knowledge, Jaccwabyt is the only +library of its kind (as of 2025-11). Aside from wrapping existing +structs, e.g. to integrate "legacy" C code into JS/WASM, it can also +model C structs without requiring a native C counterpart, a feature +which probably has only exceedingly obscure uses in JS-side +implementations for native callbacks. + +How it works: + +- The client provides a JSON-friendly description of a C struct, + describing the names, sizes, and offsets of each member. +- Pass that description to a factory function to create + a JS constructor for that C struct. +- That constructor allocates a block of heap memory of the C struct's + size and maps it to the new JS-side struct instance. Each instance + inherits property interceptors for each struct member, such that + fetching the C struct's members reads directly from the struct's + memory and setting them writes to that memory. Similarly, these + objects can be provided with memory constructed elsewhere, e.g. + a struct pointer returned from a WASM function, and can proxy + that memory via the struct's interface. +- Clients eventually call the `dispose()` method to free the + instance's heap memory and disassociate the JS instance with its + WASM-side resources. + +Easy peasy! + +**Build instructions**: [see Appendix B](#appendix-b). + **Browser compatibility**: this library requires a _recent_ browser and makes no attempt whatsoever to accommodate "older" or lesser-capable ones, where "recent," _very roughly_, means released in @@ -25,7 +54,10 @@ are based solely on feature compatibility tables provided at **Non-browser compatibility**: this code does not target non-browser JS engines and is completely untested on them. That said, it "might -work". +work". These JS APIs do not use the DOM API in any way, so are not +specifically tied to a browser, but they _are_ fully untested in such +environments. This code is known to work with both [Emscripten][] builds +and [WASI-SDK][] SDK builds (at of this writing, 2025-11-08). **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 @@ -57,7 +89,7 @@ project was spawned: ----- - + Table of Contents ============================================================ @@ -72,16 +104,18 @@ Table of Contents - APIs - [Struct Binder Factory](#api-binderfactory) - [Struct Binder](#api-structbinder) - - [Struct Type](#api-structtype) + - [Struct Description Objects](#struct-descr) +- [Struct Type](#api-structtype) - [Struct Constructors](#api-structctor) - [Struct Protypes](#api-structprototype) - [Struct Instances](#api-structinstance) - Appendices - [Appendix A: Limitations, TODOs, etc.](#appendix-a) + - [Appendix B: Build](#appendix-b) - [Appendix D: Debug Info](#appendix-d) - [Appendix G: Generating Struct Descriptions](#appendix-g) - + Overview ============================================================ @@ -114,15 +148,16 @@ Portability notes: because it is the most widespread WASM toolchain, but this code is specifically designed to be usable in arbitrary WASM environments. It abstracts away a few Emscripten-specific features into - configurable options. Similarly, the build tree requires Emscripten - but Jaccwabyt does not have any hard Emscripten dependencies. + configurable options. The build tree supports both [Emscripten][] + and [WASI-SDK][] to demonstrate that it has no dependencies on + either. - This code is encapsulated into a single JavaScript function. It should be trivial to copy/paste into arbitrary WASM/JS-using projects. - The source tree includes C code, but only for testing and - demonstration purposes. It is not part of the core distributable. + demonstration purposes. It is not a core distributable. - + Architecture ------------------------------------------------------------ @@ -156,9 +191,10 @@ Its major classes and functions are: an appropriate configuration, to generate a single... - **[StructBinder][]** is a factory function which converts an arbitrary number struct descriptions into... -- **[StructTypes][StructCtors]** are constructors, one per struct +- **[StructType][]** are [constructors][StructCtor], one per struct description, which inherit from - **[`StructBinder.StructType`][StructType]** and are used to instantiate... + **[`StructBinder.StructType`][StructType]** and are used to + instantiate... - **[Struct instances][StructInstance]** are objects representing individual instances of generated struct types. @@ -167,7 +203,7 @@ need only one. Each StructBinder is effectively a separate namespace for struct creation. - + Creating and Binding Structs ============================================================ @@ -190,7 +226,7 @@ essentially boils down to: Detailed instructions for each of those steps follows... - + Step 1: Configure Jaccwabyt for the Environment ------------------------------------------------------------ @@ -208,7 +244,7 @@ const MyBinder = StructBinderFactory({ a Uint8Array or Int8Array view of the WASM memory, alloc: function(howMuchMemory){...}, dealloc: function(pointerToFree){...}, - pointerIR: 'i32' or 'i64' // WASM pointer type - default = 'i32' + pointerSize: 4 or 8 // WASM pointer size }); ``` @@ -234,7 +270,7 @@ a conventional Emscripten setup, that config might simply look like: The StructBinder factory function returns a function which can then be used to create bindings for our structs. - + Step 2: Create a Struct Description ------------------------------------------------------------ @@ -267,100 +303,9 @@ Its JSON description looks like: } ``` -These data _must_ match up with the C-side definition of the struct -(if any). See [Appendix G][appendix-g] for one way to easily generate -these from C code. - -Each entry in the `members` object maps the member's name to -its low-level layout: - -- `offset`: the byte offset from the start of the struct, as reported - by C's `offsetof()` feature. -- `sizeof`: as reported by C's `sizeof()`. -- `signature`: described below. -- `readOnly`: optional. If set to true, the binding layer will - throw if JS code tries to set that property. - -The order of the `members` entries is not important: their memory -layout is determined by their `offset` and `sizeof` members. The -`name` property is technically optional, but one of the steps in the -binding process requires that either it be passed an explicit name or -there be one in the struct description. The names of the `members` -entries need not match their C counterparts. Project conventions may -call for giving them different names in the JS side and the -[StructBinderFactory][] can be configured to automatically add a -prefix and/or suffix to their names. - -Nested structs are as-yet unsupported by this tool. +This is described in more detail in [][StructBinder]. -Struct member "signatures" describe the data types of the members and -are an extended variant of the format used by Emscripten's -`addFunction()`. A signature for a non-function-pointer member, or -function pointer member which is to be modelled as an opaque pointer, -is a single letter. A signature for a function pointer may also be -modelled as a series of letters describing the call signature. The -supported letters are: - -- **`v`** = `void` (only used as return type for function pointer members) -- **`i`** = `int32` (4 bytes) -- **`j`** = `int64` (8 bytes) is only really usable if this code is built - with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build - flag). Without that, this API may throw when encountering the `j` - signature entry. -- **`f`** = `float` (4 bytes) -- **`d`** = `double` (8 bytes) -- **`c`** = `int8` (1 byte) char - see notes below! -- **`C`** = `uint8` (1 byte) unsigned char - see notes below! -- **`p`** = `int32` (see notes below!) -- **`P`** = Like `p` but with extra handling. Described below. -- **`s`** = like `int32` but is a _hint_ that it's a pointer to a - string so that _some_ (very limited) contexts may treat it as such, - noting that such algorithms must, for lack of information to the - contrary, assume both that the encoding is UTF-8 and that the - pointer's member is NUL-terminated. If that is _not_ the case for a - given string member, do not use `s`: use `i` or `p` instead and do - any string handling yourself. - -Noting that: - -- **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 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 -"pointer". Though `i` is legal for pointer types in the signature, `p` -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 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. - -Signatures in the form `x(...)` denote function-pointer members and -`x` denotes non-function members. Functions with no arguments use the -form `x()`. For function-type signatures, the strings are formulated -such that they can be passed to Emscripten's `addFunction()` after -stripping out the `(` and `)` characters. For good measure, to match -the public Emscripten docs, `p`, `c`, and `C`, should also be replaced -with `i`. In JavaScript that might look like: - -> -``` -signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); -``` - - + ### `P` vs `p` in Method Signatures *This support is experimental and subject to change.* @@ -379,7 +324,7 @@ stored in `myStruct.x`. If `y` is neither a pointer nor a or `P` is used). - + Step 3: Binding the Struct ------------------------------------------------------------ @@ -402,7 +347,7 @@ simplify certain later operations. If that is not desired, then feed it a copy of the original, e.g. by passing it `JSON.parse(JSON.stringify(structDefinition))`. - + Step 4: Creating, Using, and Destroying Struct Instances ------------------------------------------------------------ @@ -461,11 +406,11 @@ Now that we have struct instances, there are a number of things we can do with them, as covered in the rest of this document. - + API Reference ============================================================ - + API: Binder Factory ------------------------------------------------------------ @@ -480,14 +425,14 @@ Function StructBinderFactory(object configOptions); It returns a function which these docs refer to as a [StructBinder][] (covered in the next section). It throws on error. -The binder factory supports the following options in its -configuration object argument: +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. +- `pointerSize` (Added 2025-11-15 to replace `pointerIR`) + Optionally specify the WASM pointer size of 4 (32-bit) or 8 + (64-bit). Any other truthy value triggers an exception. If + `pointerSize` is not set then it will guess the size by `alloc()`ing + one byte, checking the result type, and `dealloc()`ing it. - `heap` Must be either a `WebAssembly.Memory` instance representing the WASM @@ -498,21 +443,24 @@ configuration object argument: for the WASM heap to grow at runtime. - `alloc` - Must be a function semantically compatible with Emscripten's - `Module._malloc()`. That is, it is passed the number of bytes to - allocate and it returns a pointer. On allocation failure it may - either return 0 or throw an exception. This API will throw an - exception if allocation fails or will propagate whatever exception - the allocator throws. The allocator _must_ use the same heap as the - `heap` config option. + Must be a function semantically compatible with C's + `malloc(3)`. That is, it is passed the number of bytes to allocate + and it returns a pointer. On allocation failure it may either return + 0 or throw an exception. This API will throw an exception if + allocation fails or will propagate whatever exception the allocator + throws. The allocator _must_ use the same heap as the `heap` config + option. - `dealloc` - Must be a function semantically compatible with Emscripten's - `Module._free()`. That is, it takes a pointer returned from - `alloc()` and releases that memory. It must never throw and must - accept a value of 0/null to mean "do nothing" (noting that 0 is - _technically_ a legal memory address in WASM, but that seems like a - design flaw). + Must be a function semantically compatible with C's `free(3)`. That + is, it takes a pointer returned from `alloc()` and releases that + memory. It must never throw and must accept a value of 0/null to + mean "do nothing". + +- `realloc` + Optional but required for (eventual (and optional) realloc support + of structs. If set, it must be a function semantically compatible + with C's `realloc()`. See `alloc`, above, for other requirements. - `bigIntEnabled` (bool=true if BigInt64Array is available, else false) If true, the WASM bits this code is used with must have been @@ -542,13 +490,13 @@ configuration object argument: (like `console.debug` does). See [Appendix D](#appendix-d) for info about enabling debugging output. - + API: Struct Binder ------------------------------------------------------------ Struct Binders are factories which are created by the [StructBinderFactory][]. A given Struct Binder can process any number -of distinct structs. In a typical setup, an app will have ony one +of distinct structs. In a typical setup, an app will have only one shared Binder Factory and one Struct Binder. Struct Binders which are created via different [StructBinderFactory][] calls are unrelated to each other, sharing no state except, perhaps, indirectly via @@ -568,7 +516,7 @@ The returned object is a constructor for instances of the struct described by its argument(s), each of which derives from a separate [StructType][] instance. -The Struct Binder has the following members: +StructBinder has the following members: - `allocCString(str)` Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS @@ -583,7 +531,221 @@ The Struct Binder has the following members: any of its "significant" configuration values may have undefined results. - +- `adaptGet(key [,func])` + Gets or sets a "get adaptor" by name - an arbitrary client-defined + string, e.g. `"to-js-string"`. Struct description objects may have + their `adaptGet` property set to the name of a mapped getter to + behave exactly as if that struct description had set the given + function as its `get` property. This offers a JSON-friendly way of + storing adaptor mappings, with the caveat that the adaptors need to + be defined _somewhere_ outside of JSON (typically it should be done + immediately after creating the StructBinder). + +- `adaptSet(key [,func])` + The "set" counterpart of `adaptGet`. + +- `ptrAdd(...)` + Coerces all of its arguments to the WASM pointer type, adds them + together, and returns a result of that same type. This is a + workaround for mixed-BigInt/Number pointer math being illegal in JS. + +The `structDescription` argument is described in detail in the +following section. + + +### Struct Description Object + +C structs are described in a JSON-friendly format: + +> +```json +{ + "name": "MyStruct", + "sizeof": 16, + "members": { + "member1": {"offset": 0,"sizeof": 4,"signature": "i"}, + "member2": {"offset": 4,"sizeof": 4,"signature": "p"}, + "member3": {"offset": 8,"sizeof": 8,"signature": "j"} + } +} +``` + +Forewarning: these data _must_ match up with the C-side definition of +the struct (if any). See [Appendix G][appendix-g] for one way to +easily generate these from C code. + +Every struct must have a `sizeof`. (Though we _could_ calculate it +based on the list of members, we don't. Actually, we do, then we throw +if the values don't match up.) The `name` is required as well but it +may optionally be passed as the first argument to +`StructBinder(structName,structDescription)`. the `name` property +represents the member's symbolic name, typically its native name. + +Abstractly speaking, a struct description in an object with the +properties `sizeof`, `offset`, and either `signature` _or_ +`member`. `offset` is optional only in the top-most object of a struct +description. Every sub-object (a.k.a. member description object) +requires the `offset` property. + +Member description objects are those in the `members` property: + +`"members": {"memberName": {...member description...}, ...}` + +A struct description which has its own `members` object represents a +nested struct, with an identical description syntax to that of a +top-level struct except that nested structs require an `offset` +property. + +Each entry in a struct/member description object maps the member's +name to its low-level layout and other metadata: + +- `offset` + The byte offset from the start of the struct, as reported by C's + `offsetof()` feature. For nested structs's members, this value is + relative to the nested struct, not the parent struct. +- `sizeof` + As reported by C's `sizeof()`. +- `signature` + A type-id signature for this member. Described below. +- `readOnly [=false]` + Optional boolean. If set to true, the binding layer will throw if JS + code tries to set that property. +- `zeroOnDispose [=false]` + If true, then the library will zero out the memory of instances of + this struct when their `dispose()` method is called. Because + `StructType.dispose()` does not free instances which wrap + externally-provided memory, those instances are not wiped when + disposed (doing so would likely interfere with other users of that + memory). (There is no need for a `zeroOnAlloc` counterpart because + newly-allocated instances are always zero-filled for sanity's + sake.) +- `members` + This object describes the individual struct members, mapping their + names to a member description object. `members` gets processed + recursively. Any member with its own `members` property is a + nested-struct, and the property accessor for such members will + return an instance of the distinct StructType which wraps that + member's native memory. + Nested-struct members cannot be assigned over. `signature` is + illegal if `members` is set. +- `get` + Optional function. When fetching this member, this getter is passed + `(K,V)`, where `K` is the struct member's key and `V` is the + _native_ value. `get()`'s return value becomes the value of the + property access operation. This enables custom "native-to-JS" + conversions. If the member is a nested struct, the value passed to + the getter is a StructType proxy object which provides access to its + own members, a subset of the parent object's memory. In the context + of the getter call, "this" is the object upon which the get is being + performed. +- `set` + Optional function. When setting this member, this setter is passed + `(K,V)`, where `K` is the struct member's key and `V` is the _JS_ + value the property is being assigned to. The `set()` return value is + assigned to the _native_ struct member. Thus `set()` _must_ return + an appropriate numeric value and can perform "JS-to-native" + conversions. `set` is not currently legal for nested struct values, + but it is on their own non-nested-struct members. In the context of + the setter call, "this" is the object upon which the set is being + performed. +- `adaptGet` and `adaptSet` + JSON-friendly variants of `get` and `set`. Each may be assigned a + string value, and each such string must be mapped with + `StructBinder.adaptGet(key,func)` + resp. `StructBinder.adaptSet(key,func)`. With that in place, these + behave like `get` resp. `set`. +- `structName` + Optional descriptive name, possibly distinct from the `name`, + primarily used for nested structs. The intent is that this be some + form of the struct type's name, optionally with leading parts of + this object is a nested struct. +- `name` + Is usually optional, and is always optional in `members` entries + because their name is conveniently derived from their containing + object. `name` must be provided only for the top-most struct. The + intent is that `name` maps to the member's property name and that + `structName` optionally be set for nested structs (it will be + derived from the name if it's not set). + +The order of the `members` entries is not important: their memory +layout is determined by their `offset` and `sizeof` members. The +`name` property is technically optional, but one of the steps in the +binding process requires that either it be passed an explicit name or +there be one in the struct description. The names of the `members` +entries need not match their C counterparts. Project conventions may +call for giving them different names in the JS side and the +[StructBinderFactory][] can be configured to automatically add a +prefix and/or suffix to their names. + +Struct member "signatures" describe the data types of the members and +are an extended variant of the format used by Emscripten's +`addFunction()`. A signature for a non-function-pointer member, or +function pointer member which is to be modelled as an opaque pointer, +is a single letter. A signature for a function pointer may also be +modelled as a series of letters describing the call signature. The +supported letters are: + +- **`v`** = `void` (only used as return type for function pointer members) +- **`i`** = `int32` (4 bytes) +- **`j`** = `int64` (8 bytes) is only really usable if this code is built + with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build + flag). Without that, this API may throw when encountering the `j` + signature entry. +- **`f`** = `float` (4 bytes) +- **`d`** = `double` (8 bytes) +- **`c`** = `int8` (1 byte) char - see notes below! +- **`C`** = `uint8` (1 byte) unsigned char - see notes below! +- **`p`** = `int32` (see notes below!) +- **`P`** = Like `p` but with extra handling. Described below. +- **`s`** = like `int32` but is a _hint_ that it's a pointer to a + string so that _some_ (very limited) contexts may treat it as such, + noting that such algorithms must, for lack of information to the + contrary, assume both that the encoding is UTF-8 and that the + pointer's member is NUL-terminated. If that is _not_ the case for a + given string member, do not use `s`: use `i` or `p` instead and do + any string handling yourself. + +Noting that: + +- **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 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 +"pointer". Though `i` is legal for pointer types in the signature, `p` +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 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. + +Signatures in the form `x(...)` denote function-pointer members and +`x` denotes non-function members. Functions with no arguments use the +form `x()`. For function-type signatures, the strings are formulated +such that they can be passed to Emscripten's `addFunction()` after +stripping out the `(` and `)` characters. For good measure, to match +the public Emscripten docs, `p`, `c`, and `C`, should also be replaced +with `i`. In JavaScript that might look like: + +> +``` +signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i'); +``` + + + API: Struct Type ------------------------------------------------------------ @@ -598,7 +760,7 @@ config options. The StructType constructor cannot be called from client code. It is only called by the [StructBinder][]-generated -[constructors][StructCtors]. The `StructBinder.StructType` object +[constructors][StructCtor]. The `StructBinder.StructType` object has the following "static" properties (^Which are accessible from individual instances via `theInstance.constructor`.): @@ -608,7 +770,8 @@ individual instances via `theInstance.constructor`.): a function-typed `ondispose` property, this call replaces it with an array and moves that function into the array. In all other cases, `ondispose` is assumed to be an array and the argument(s) is/are - appended to it. Returns `this`. + appended to it. Returns `this`. See `dispose()`, below, for where + this applies. - `allocCString(str)` Identical to the [StructBinder][] method of the same name. @@ -616,7 +779,7 @@ individual instances via `theInstance.constructor`.): - `hasExternalPointer(object)` Returns true if the given object's `pointer` member refers to an "external" object. That is the case when a pointer is passed to a - [struct's constructor][StructCtors]. If true, the memory is owned by + [struct's constructor][StructCtor]. If true, the memory is owned by someone other than the object and must outlive the object. - `isA(value)` @@ -633,7 +796,7 @@ individual instances via `theInstance.constructor`.): The base StructType prototype has the following members, all of which are inherited by [struct instances](#api-structinstance) and may only -legally be called on concrete struct instances unless noted otherwise: +legally be used with concrete struct instances unless noted otherwise: - `dispose()` Frees, if appropriate, the WASM-allocated memory which is allocated @@ -641,11 +804,11 @@ legally be called on concrete struct instances unless noted otherwise: cleans up the object, a leak in the WASM heap memory pool will result. When `dispose()` is called, if the object has a property named `ondispose` then it is treated as follows: - - If it is a function, it is called with the struct object as its `this`. - That method must not throw - if it does, the exception will be - ignored. + - If it is a function, it is called with the struct object as its + `this`. That method must not throw - if it does, the exception + will be ignored. - If it is an array, it may contain functions, pointers, other - [StructType] instances, and/or JS strings. If an entry is a + [StructType][] instances, and/or JS strings. If an entry is a function, it is called as described above. If it's a number, it's assumed to be a pointer and is passed to the `dealloc()` function configured for the parent [StructBinder][]. If it's a @@ -655,7 +818,12 @@ legally be called on concrete struct instances unless noted otherwise: supported primarily for use as debugging information. - Some struct APIs will manipulate the `ondispose` member, creating it as an array or converting it from a function to array as - needed. + needed. Most simply, `addOnDispose()` is used to manipulate the + on-dispose data. + +- `extraBytes` (integer, read-only) + If this instance was allocated with the `extraBytes` option, this is + that value, else it is 0. - `lookupMember(memberName,throwIfNotFound=true)` Given the name of a mapped struct member, it returns the member @@ -708,6 +876,19 @@ legally be called on concrete struct instances unless noted otherwise: the struct will invalidate older serialized data and (B) serializing member pointers is useless. +- `pointer` (number, read-only) + A read-only numeric property which is the "pointer" returned by the + configured allocator when this object is constructed. After + `dispose()` (inherited from [StructType][]) is called, this property + has the `undefined` value. When calling C-side code which takes a + pointer to a struct of this type, simply pass it `myStruct.pointer`. + Whether this member is of type Number or BigInt depends on whether + the WASM environment is 32-bit (Number) or 64-bit (BigInt). + +- `ptrAdd(args...)` + Equivalent to [StructBinder][]`.ptrAdd(this.pointer, args...)` + or [StructCtor][]`.ptrAdd(this.pointer, args...)`. + - `setMemberCString(memberName,str)` Uses `StructType.allocCString()` to allocate a new C-style string, assign it to the given member, and add the new string to this @@ -725,9 +906,13 @@ legally be called on concrete struct instances unless noted otherwise: from JS be kept to a minimum or that the relationship be one-way: let C manage the strings and only fetch them from JS using, e.g., `memberToJsString()`. - - +- `zeroOnDispose` (bool, read-only) + True if this instance or its prototype were configured with + the `zeroOnDispose` flag. + + + API: Struct Constructors ------------------------------------------------------------ @@ -741,29 +926,90 @@ const x = new MyStruct; ``` Normally they should be passed no arguments, but they optionally -accept a single argument: a WASM heap pointer address of memory -which the object will use for storage. It does _not_ take over -ownership of that memory and that memory must be valid at -for least as long as this struct instance. This is used, for example, -to proxy static/shared C-side instances: +accept a single argument: a WASM heap pointer address of memory which +the object will use for storage. It does _not_ take over ownership of +that memory and that memory must remain valid for at least as long as +this struct instance. This is used, for example, to proxy +static/shared C-side instances or those which we simply want to +get access to via this higher-level API for a while: > ``` const x = new MyStruct( someCFuncWhichReturnsAMyStructPointer() ); ... -x.dispose(); // does NOT free the memory +x.dispose(); // does NOT free or zero the memory ``` The JS-side construct does not own the memory in that case and has no way of knowing when the C-side struct is destroyed. Results are specifically undefined if the JS-side struct is used after the C-side -struct's member is freed. +struct's member is freed. However, if the client has allocated that +memory themselves and wants to transfer ownership of it to the +instance, that can be done with: + +> +``` +x.addOnDispose( thePtr ); +``` + +Which will free `thePtr` when `x.dispose()` is called. + +As of 2025-11-10, a third option is available: -> Potential TODO: add a way of passing ownership of the C-side struct -to the JS-side object. e.g. maybe simply pass `true` as the second -argument to tell the constructor to take over ownership. Currently the -pointer can be taken over using something like -`myStruct.ondispose=[myStruct.pointer]` immediately after creation. +> +``` +const x = new MyStruct({ + wrap: ptr, // as per MyStruct(ptr) + takeOwnership: bool, // If true, take ownership of the wrap ptr. + zeroOnDispose: bool , // if true, overrides MyStruct.structInfo.zeroOnDispose + extraBytes: int // bytes to alloc after the end of the struct +}); +``` + +- If `wrap` is set then (A) it must be at least + `MyStruct.structInfo.sizeof` of memory owned by the caller, (B) it + _must_ have been allocated using the same allocator as Jaccwabyt is + configured for, and (C) ownership of it is transfered to the new + object. `zeroOnDispose` and `extraBytes` are disregarded if `wrap` + is set. A falsy `wrap` value is equivalent to not providing one and + a non-integer value, or a number less than 0, triggers an error. + +- If `takeOwnership` is truthy then this object takes over ownership + of the `wrap` pointer (if any). This flag is otherwise ignored. + +- If `zeroOnDispose` or `extraBytes` are are used without `wrap`, each + which is used is set as a read-only propperty on the resulting + `MyStruct` object, except that `zeroOnDispose` is only recorded if + it is truthy and `extraBytes` is only recorded if it is not 0 (a + negative value, or non-integer, triggers an exception). + +- `ondispose`: if set it is passed to the new object's + `addOnDispose()` before the constructor returns, so may have any + value legal for that method. Results are undefined if `ondispose` + and `wrap` have the same pointer value (because `wrap` will already + be cleaned up via `dispose()`, as if it had been passed to + `addOnDispose()`). + +In the case of `extraBytes`, a pointer to the tail of the memory can +be obtained by adding `theInstance.pointer` to +`theInstance.structInfo.sizeof`, with the caveat that `pointer` may be +a BigInt value (on 64-bit WASM), `sizeof` will be a Number and, +spoiler alert, `(BigInt(1) + Number(1))` is not legal. Thus this API +adds a small convenience method to work around that portability issue: + +> +``` +const ptrTail = MyStruct.ptrAdd(theInstance.pointer, theInstance.extraBytes); +``` + +`typeof ptrTail` is `'bigint'` in 64-bit builds and `'number'` in +32-bit. i.e. it's the same type as `theInstance.pointer`. +Equivalently, the inherited `MyStruct` instance method with the same +name adds an instance's own `pointer` value to its arguments: + +``` +const ptrTail = theInstance.ptrAdd(theInstance.extraBytes); +``` These constructors have the following "static" members: @@ -776,6 +1022,10 @@ These constructors have the following "static" members: - `memberKeys(string)` Works exactly as documented for [StructType][]. +- `ptrAdd(args...)` + Equivalent to [StructBinder][]`.ptrAdd(args...)`. The [_inherited_ + method with the same name][StructType] behaves differently. + - `structInfo` The structure description passed to [StructBinder][] when this constructor was generated. @@ -783,14 +1033,14 @@ These constructors have the following "static" members: - `structName` The structure name passed to [StructBinder][] when this constructor was generated. - - + + API: Struct Prototypes ------------------------------------------------------------ The prototypes of structs created via [the constructors described in -the previous section][StructCtors] are each a struct-type-specific +the previous section][StructCtor] are each a struct-type-specific instance of [StructType][] and add the following struct-type-specific properties to the mix: @@ -802,78 +1052,57 @@ properties to the mix: The name of the struct, as it was given to the [StructBinder][] which created this class. - + API: Struct Instances ------------------------------------------------------------------------ Instances of structs created via [the constructors described -above][StructCtors] each have the following instance-specific state in -common: - -- `pointer` - A read-only numeric property which is the "pointer" returned by the - configured allocator when this object is constructed. After - `dispose()` (inherited from [StructType][]) is called, this property - has the `undefined` value. When calling C-side code which takes a - pointer to a struct of this type, simply pass it `myStruct.pointer`. +above][StructCtor]. Each inherits all of the methods and properties +from their constructor's prototype. - + Appendices ============================================================ - + Appendix A: Limitations, TODOs, and Non-TODOs ------------------------------------------------------------ - This library only supports the basic set of member types supported - by WASM: numbers (which includes pointers). Nested structs are not - handled except that a member may be a _pointer_ to such a - struct. Whether or not it ever will depends entirely on whether its - developer ever needs that support. Conversion of strings between - JS and C requires infrastructure specific to each WASM environment - and is not directly supported by this library. - -- Binding functions to struct instances, such that C can see and call - JS-defined functions, is not as transparent as it really could be, - due to [shortcomings in the Emscripten - `addFunction()`/`removeFunction()` - interfaces](https://github.com/emscripten-core/emscripten/issues/17323). Until - a replacement for that API can be written, this support will be - quite limited. It _is_ possible to bind a JS-defined function to a - C-side function pointer and call that function from C. What's - missing is easier-to-use/more transparent support for doing so. - - In the meantime, a [standalone - subproject](/file/common/whwasmutil.js) of Jaccwabyt provides such a - binding mechanism, but integrating it directly with Jaccwabyt would - not only more than double its size but somehow feels inappropriate, so - experimentation is in order for how to offer that capability via - completely optional [StructBinderFactory][] config options. - -- It "might be interesting" to move access of the C-bound members into - a sub-object. e.g., from JS they might be accessed via - `myStructInstance.s.structMember`. The main advantage is that it would - eliminate any potential confusion about which members are part of - the C struct and which exist purely in JS. "The problem" with that - is that it requires internally mapping the `s` member back to the - object which contains it, which makes the whole thing more costly - and adds one more moving part which can break. Even so, it's - something to try out one rainy day. Maybe even make it optional and - make the `s` name configurable via the [StructBinderFactory][] - options. (Over-engineering is an arguably bad habit of mine.) - -- It "might be interesting" to offer (de)serialization support. It - would be very limited, e.g. we can't serialize arbitrary pointers in - any meaningful way, but "might" be useful for structs which contain - only numeric or C-string state. As it is, it's easy enough for - client code to write wrappers for that and handle the members in - ways appropriate to their apps. Any impl provided in this library - would have the shortcoming that it may inadvertently serialize - pointers (since they're just integers), resulting in potential chaos - after deserialization. Perhaps the struct description can be - extended to tag specific members as serializable and how to - serialize them. - - + by WASM: numbers (which includes pointers). + +- Binding JS functions to struct instances, such that C can see and + call JS-defined functions, is not as transparent as it really could + be. [The WhWasmUtil API][whwasmutil.js], and + standalone subproject co-developed with Jaccwabyt, provides such a + binding mechanism. There is some overlap between the two APIs and + they arguably belong bundled together, but doing so would more than + triple Jaccwabyt's size. (That said, the only known Jaccwabyt + deployment uses both APIs, so maybe it's time to merge them (he says + on 2025-11-10). As of this writing, jaccwabyt.js is 38k, half of + which is comments/docs, whereas whwasmutil.js is 100kb and 75% + docs). + + +Appendix B: Build +------------------------------------------------------------------------ + +In order to support both vanilla JS and ESM (ES6 module) builds from a +single source, this project uses [a preprocessor][c-pp], which requires +only a C compiler: + +> $ make + +The makefile requires GNU make, not BSD or POSIX make. + +The resulting files are in the `js/` subdirectory, in both "vanilla" +JS and ESM formats. + +This tree [includes all of the requisite sources](/dir/tool) and +requires no out-of-tree dependencies beyond the system's libc. + + + Appendix D: Debug Info ------------------------------------------------------------ @@ -894,7 +1123,7 @@ client code: [StructType][]. - + Appendix G: Generating Struct Descriptions From C ------------------------------------------------------------ @@ -1064,18 +1293,23 @@ div.content h3::before { div.content h3 {border-left-width: 2.5em} -[sqlite3]: https://sqlite.org -[emscripten]: https://emscripten.org -[sgb]: https://wanderinghorse.net/home/stephan/ [appendix-g]: #appendix-g -[StructBinderFactory]: #api-binderfactory -[StructCtors]: #api-structctor -[StructType]: #api-structtype +[BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array +[c-pp]: https://fossil.wanderinghorse.net/r/c-pp +[Emscripten]: https://emscripten.org +[jaccwabyt.js]: /file/jaccwabyt/jaccwabyt.c-pp.js +[MDN]: https://developer.mozilla.org/docs/Web/API +[sgb]: https://wanderinghorse.net/home/stephan/ +[sqlite3]: https://sqlite.org [StructBinder]: #api-structbinder +[StructBinderFactory]: #api-binderfactory +[StructCtor]: #api-structctor [StructInstance]: #api-structinstance -[^export-func]: In Emscripten, add its name, prefixed with `_`, to the - project's `EXPORT_FUNCTIONS` list. -[BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array +[StructType]: #api-structtype [TextDecoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder [TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder -[MDN]: https://developer.mozilla.org/docs/Web/API +[WASI-SDK]: https://github.com/WebAssembly/wasi-sdk +[whwasmutil.js]: /file/wasmutil/whwasmutil.c-pp.js + +[^export-func]: In Emscripten, add its name, prefixed with `_`, to the + project's `EXPORT_FUNCTIONS` list. diff --git a/manifest b/manifest index 13a51935e8..0236d0c8db 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Get\sinitial\sJS\soverrides\sof\sthe\skvvfs\ssqlite3_vfs\sand\ssqlite3_io_methods\sin\splace.\sIt\snow\stracks\sa\sdistinct\sStorage-ish\sobject\sper\ssqlite3_file\sinstance. -D 2025-11-21T22:13:35.200 +C Latest\sside-stream\sjaccwabyt/wasmutil. +D 2025-11-22T02:23:02.637 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -611,7 +611,7 @@ F ext/wasm/c-pp-lite.c 943be1a36774d58385dca32de36fc18d4f432fe79f7aa35e6c85dd6a6 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 804409dec98458f732b23e6e86788934ac6dec002107435cc4055355585d1c15 +F ext/wasm/common/whwasmutil.js 831f07a0d9bb61713164871370811432e96d0f813806a4d2c783d3c77c2373a0 F ext/wasm/config.make.in c424ae1cc3c89274520ad312509d36c4daa34a3fce5d0c688e5f8f4365e1049a F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 @@ -628,8 +628,8 @@ 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 236464b7c8e2d2540a296ba049b3e812e2697ef8439ac96f0d20716e46ba6809 -F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f +F ext/wasm/jaccwabyt/jaccwabyt.js 4e2b797dc170851c9c530c3567679f4aa509eec0fab73b466d945b00b356574b +F ext/wasm/jaccwabyt/jaccwabyt.md 6aa90fa1a973d0ad10d077088bea163b241d8470c75eafdef87620a1de1dea41 F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x F ext/wasm/mkwasmbuilds.c ef42e404236dd98cedb6ecea47b6d2474e3c593633ce6d992d316289dfc442b6 F ext/wasm/module-symbols.html e54f42112e0aac2a31f850ab33e7f2630a2ea4f63496f484a12469a2501e07e2 @@ -2178,8 +2178,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 90a33941c69b3581feaed271542f0238ca81ee34fe5b353ca7da48b81ac73a5f -R 831d8b062b833be06147d18462b6029d +P 19a3349a2031e2b7fae67847b55643e4f70f8dae863ebc1ace3b09d1f482c8eb +R f4817f0cbb500ac29ff861fffe4ad699 U stephan -Z 6ff913830f5e095f3c74b6184f98d188 +Z a18118b461e622bb9fd359a51fedd9be # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0d050e20e1..a10954d26a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -19a3349a2031e2b7fae67847b55643e4f70f8dae863ebc1ace3b09d1f482c8eb +e0b33b51229a977cc3fa8a5a6c8ea59669f8bf566b2a6330fd24da1ad886a716