2 ============================================================
4 **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte
7 Welcome to Jaccwabyt, a JavaScript API which creates bindings for
8 WASM-compiled C structs, defining them in such a way that changes to
9 their state in JS are visible in C/WASM, and vice versa, permitting
10 two-way interchange of struct state with very little user-side
13 (If that means nothing to you, neither will the rest of this page!)
15 **Browser compatibility**: this library requires a _recent_ browser
16 and makes no attempt whatsoever to accommodate "older" or
17 lesser-capable ones, where "recent," _very roughly_, means released in
18 mid-2018 or later, with late 2021 releases required for some optional
19 features in some browsers (e.g. [BigInt64Array][] in Safari). It also
20 relies on a couple non-standard, but widespread, features, namely
21 [TextEncoder][] and [TextDecoder][]. It is developed primarily on
22 Firefox and Chrome on Linux and all claims of Safari compatibility
23 are based solely on feature compatibility tables provided at
28 - Author: [Stephan Beal][sgb]
30 - <https://fossil.wanderinghorse.net/r/jaccwabyt>\
31 Is the primary home but...
32 - <https://sqlite.org/src/dir/ext/wasm/jaccwabyt>\
33 ... most development happens here.
35 The license for both this documentation and the software it documents
36 is the same as [sqlite3][], the project from which this spinoff
43 > The author disclaims copyright to this source code. In place of a
44 > legal notice, here is a blessing:
46 > May you do good and not evil.
47 > May you find forgiveness for yourself and forgive others.
48 > May you share freely, never taking more than you give.
52 <a name='overview'></a>
54 ============================================================
56 - [Overview](#overview)
57 - [Architecture](#architecture)
58 - [Creating and Binding Structs](#creating-binding)
59 - [Step 1: Configure Jaccwabyt](#step-1)
60 - [Step 2: Struct Description](#step-2)
61 - [`P` vs `p`](#step-2-pvsp)
62 - [Step 3: Binding a Struct](#step-3)
63 - [Step 4: Creating, Using, and Destroying Instances](#step-4)
65 - [Struct Binder Factory](#api-binderfactory)
66 - [Struct Binder](#api-structbinder)
67 - [Struct Type](#api-structtype)
68 - [Struct Constructors](#api-structctor)
69 - [Struct Protypes](#api-structprototype)
70 - [Struct Instances](#api-structinstance)
72 - [Appendix A: Limitations, TODOs, etc.](#appendix-a)
73 - [Appendix D: Debug Info](#appendix-d)
74 - [Appendix G: Generating Struct Descriptions](#appendix-g)
76 <a name='overview'></a>
78 ============================================================
80 Management summary: this JavaScript-only framework provides limited
81 two-way bindings between C structs and JavaScript objects, such that
82 changes to the struct in one environment are visible in the other.
86 It works by creating JavaScript proxies for C structs. Reads and
87 writes of the JS-side members are marshaled through a flat byte array
88 allocated from the WASM heap. As that heap is shared with the C-side
89 code, and the memory block is written using the same approach C does,
90 that byte array can be used to access and manipulate a given struct
91 instance from both JS and C.
93 Motivating use case: this API was initially developed as an
94 experiment to determine whether it would be feasible to implement,
95 completely in JS, custom "VFS" and "virtual table" objects for the
96 WASM build of [sqlite3][]. Doing so was going to require some form of
97 two-way binding of several structs. Once the proof of concept was
98 demonstrated, a rabbit hole appeared and _down we went_... It has
99 since grown beyond its humble proof-of-concept origins and is believed
100 to be a useful (or at least interesting) tool for mixed JS/C
105 - These docs sometimes use [Emscripten][] as a point of reference
106 because it is the most widespread WASM toolchain, but this code is
107 specifically designed to be usable in arbitrary WASM environments.
108 It abstracts away a few Emscripten-specific features into
109 configurable options. Similarly, the build tree requires Emscripten
110 but Jaccwabyt does not have any hard Emscripten dependencies.
111 - This code is encapsulated into a single JavaScript function. It
112 should be trivial to copy/paste into arbitrary WASM/JS-using
114 - The source tree includes C code, but only for testing and
115 demonstration purposes. It is not part of the core distributable.
117 <a name='architecture'></a>
119 ------------------------------------------------------------
122 bug(?) (fossil): using "center" shrinks pikchr too much.
126 BSBF: box rad 0.3*boxht "StructBinderFactory" fit fill lightblue
127 BSB: box same "StructBinder" fit at 0.75 e of 0.7 s of BSBF.c
128 BST: box same "StructType<T>" fit at 1.5 e of BSBF
129 BSC: box same "Struct<T>" "Ctor" fit at 1.5 s of BST
130 BSI: box same "Struct<T>" "Instances" fit at 1 right of BSB.e
131 BC: box same at 0.25 right of 1.6 e of BST "C Structs" fit fill lightgrey
133 arrow -> from BSBF.s to BSB.w "Generates" aligned above
134 arrow -> from BSB.n to BST.sw "Contains" aligned above
135 arrow -> from BSB.s to BSC.nw "Generates" aligned below
136 arrow -> from BSC.ne to BSI.s "Constructs" aligned below
137 arrow <- from BST.se to BSI.n "Inherits" aligned above
138 arrow <-> from BSI.e to BC.s dotted "Shared" aligned above "Memory" aligned below
139 arrow -> from BST.e to BC.w dotted "Mirrors Struct" aligned above "Model From" aligned below
140 arrow -> from BST.s to BSC.n "Prototype of" aligned above
143 Its major classes and functions are:
145 - **[StructBinderFactory][StructBinderFactory]** is a factory function which
146 accepts a configuration object to customize it for a given WASM
147 environment. A client will typically call this only one time, with
148 an appropriate configuration, to generate a single...
149 - **[StructBinder][]** is a factory function which converts an
150 arbitrary number struct descriptions into...
151 - **[StructTypes][StructCtors]** are constructors, one per struct
152 description, which inherit from
153 **[`StructBinder.StructType`][StructType]** and are used to instantiate...
154 - **[Struct instances][StructInstance]** are objects representing
155 individual instances of generated struct types.
157 An app may have any number of StructBinders, but will typically
158 need only one. Each StructBinder is effectively a separate
159 namespace for struct creation.
162 <a name='creating-binding'></a>
163 Creating and Binding Structs
164 ============================================================
166 From the amount of documentation provided, it may seem that
167 creating and using struct bindings is a daunting task, but it
168 essentially boils down to:
170 1. [Confire Jaccwabyt for your WASM environment](#step-1). This is a
171 one-time task per project and results is a factory function which
172 can create new struct bindings.
173 2. [Create a JSON-format description of your C structs](#step-2). This is
174 required once for each struct and required updating if the C
176 3. [Feed (2) to the function generated by (1)](#step-3) to create JS
177 constuctor functions for each struct. This is done at runtime, as
178 opposed to during a build-process step, and can be set up in such a
179 way that it does not require any maintenace after its initial
181 4. [Create and use instances of those structs](#step-4).
183 Detailed instructions for each of those steps follows...
185 <a name='step-1'></a>
186 Step 1: Configure Jaccwabyt for the Environment
187 ------------------------------------------------------------
189 Jaccwabyt's highest-level API is a single function. It creates a
190 factory for processing struct descriptions, but does not process any
191 descriptions itself. This level of abstraction exist primarily so that
192 the struct-specific factories can be configured for a given WASM
193 environment. Its usage looks like:
197 const MyBinder = StructBinderFactory({
198 // These config options are all required:
199 heap: WebAssembly.Memory instance or a function which returns
200 a Uint8Array or Int8Array view of the WASM memory,
201 alloc: function(howMuchMemory){...},
202 dealloc: function(pointerToFree){...}
206 It also offers a number of other settings, but all are optional except
207 for the ones shown above. Those three config options abstract away
208 details which are specific to a given WASM environment. They provide
209 the WASM "heap" memory, the memory allocator, and the deallocator. In
210 a conventional Emscripten setup, that config might simply look like:
215 heap: Module['asm']['memory'],
217 // heap: ()=>Module['HEAP8'],
218 alloc: (n)=>Module['_malloc'](n),
219 dealloc: (m)=>Module['_free'](m)
223 The StructBinder factory function returns a function which can then be
224 used to create bindings for our structs.
226 <a name='step-2'></a>
227 Step 2: Create a Struct Description
228 ------------------------------------------------------------
230 The primary input for this framework is a JSON-compatible construct
231 which describes a struct we want to bind. For example, given this C
244 Its JSON description looks like:
252 "member1": {"offset": 0,"sizeof": 4,"signature": "i"},
253 "member2": {"offset": 4,"sizeof": 4,"signature": "p"},
254 "member3": {"offset": 8,"sizeof": 8,"signature": "j"}
259 These data _must_ match up with the C-side definition of the struct
260 (if any). See [Appendix G][appendix-g] for one way to easily generate
263 Each entry in the `members` object maps the member's name to
264 its low-level layout:
266 - `offset`: the byte offset from the start of the struct, as reported
267 by C's `offsetof()` feature.
268 - `sizeof`: as reported by C's `sizeof()`.
269 - `signature`: described below.
270 - `readOnly`: optional. If set to true, the binding layer will
271 throw if JS code tries to set that property.
273 The order of the `members` entries is not important: their memory
274 layout is determined by their `offset` and `sizeof` members. The
275 `name` property is technically optional, but one of the steps in the
276 binding process requires that either it be passed an explicit name or
277 there be one in the struct description. The names of the `members`
278 entries need not match their C counterparts. Project conventions may
279 call for giving them different names in the JS side and the
280 [StructBinderFactory][] can be configured to automatically add a
281 prefix and/or suffix to their names.
283 Nested structs are as-yet unsupported by this tool.
285 Struct member "signatures" describe the data types of the members and
286 are an extended variant of the format used by Emscripten's
287 `addFunction()`. A signature for a non-function-pointer member, or
288 function pointer member which is to be modelled as an opaque pointer,
289 is a single letter. A signature for a function pointer may also be
290 modelled as a series of letters describing the call signature. The
291 supported letters are:
293 - **`v`** = `void` (only used as return type for function pointer members)
294 - **`i`** = `int32` (4 bytes)
295 - **`j`** = `int64` (8 bytes) is only really usable if this code is built
296 with BigInt support (e.g. using the Emscripten `-sWASM_BIGINT` build
297 flag). Without that, this API may throw when encountering the `j`
299 - **`f`** = `float` (4 bytes)
300 - **`d`** = `double` (8 bytes)
301 - **`c`** = `int8` (1 byte) char - see notes below!
302 - **`C`** = `uint8` (1 byte) unsigned char - see notes below!
303 - **`p`** = `int32` (see notes below!)
304 - **`P`** = Like `p` but with extra handling. Described below.
305 - **`s`** = like `int32` but is a _hint_ that it's a pointer to a
306 string so that _some_ (very limited) contexts may treat it as such,
307 noting that such algorithms must, for lack of information to the
308 contrary, assume both that the encoding is UTF-8 and that the
309 pointer's member is NUL-terminated. If that is _not_ the case for a
310 given string member, do not use `s`: use `i` or `p` instead and do
311 any string handling yourself.
315 - **All of these types are numeric**. Attempting to set any
316 struct-bound property to a non-numeric value will trigger an
317 exception except in cases explicitly noted otherwise.
318 - **"Char" types**: WASM does not define an `int8` type, nor does it
319 distinguish between signed and unsigned. This API treats `c` as
320 `int8` and `C` as `uint8` for purposes of getting and setting values
321 when using the `DataView` class. It is _not_ recommended that client
322 code use these types in new WASM-capable code, but they were added
323 for the sake of binding some immutable legacy code to WASM.
325 > Sidebar: Emscripten's public docs do not mention `p`, but their
326 generated code includes `p` as an alias for `i`, presumably to mean
327 "pointer". Though `i` is legal for pointer types in the signature, `p`
328 is more descriptive, so this framework encourages the use of `p` for
329 pointer-type members. Using `p` for pointers also helps future-proof
330 the signatures against the eventuality that WASM eventually supports
331 64-bit pointers. Note that sometimes `p` really means
332 pointer-to-pointer, but the Emscripten JS/WASM glue does not offer
333 that level of expressiveness in these signatures. We simply have to be
334 aware of when we need to deal with pointers and pointers-to-pointers
337 > Trivia: this API treates `p` as distinctly different from `i` in
338 some contexts, so its use is encouraged for pointer types.
340 Signatures in the form `x(...)` denote function-pointer members and
341 `x` denotes non-function members. Functions with no arguments use the
342 form `x()`. For function-type signatures, the strings are formulated
343 such that they can be passed to Emscripten's `addFunction()` after
344 stripping out the `(` and `)` characters. For good measure, to match
345 the public Emscripten docs, `p`, `c`, and `C`, should also be replaced
346 with `i`. In JavaScript that might look like:
350 signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i');
353 <a name='step-2-pvsp'></a>
354 ### `P` vs `p` in Method Signatures
356 *This support is experimental and subject to change.*
358 The method signature letter `p` means "pointer," which, in WASM, means
359 "integer." `p` is treated as an integer for most contexts, while still
360 also being a separate type (analog to how pointers in C are just a
361 special use of unsigned numbers). A capital `P` changes the semantics
362 of plain member pointers (but not, as of this writing, function
363 pointer members) as follows:
365 - When a `P`-type member is **set** via `myStruct.x=y`, if
366 [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is
367 stored in `myStruct.x`. If `y` is neither a number nor
368 a [StructType][], an exception is triggered (regardless of whether
372 <a name='step-3'></a>
373 Step 3: Binding the Struct
374 ------------------------------------------------------------
376 We can now use the results of steps 1 and 2:
380 const MyStruct = MyBinder(myStructDescription);
383 That creates a new constructor function, `MyStruct`, which can be used
384 to instantiate new instances. The binder will throw if it encounters
387 That's all there is to it.
389 > Sidebar: that function may modify the struct description object
390 and/or its sub-objects, or may even replace sub-objects, in order to
391 simplify certain later operations. If that is not desired, then feed
392 it a copy of the original, e.g. by passing it
393 `JSON.parse(JSON.stringify(structDefinition))`.
395 <a name='step-4'></a>
396 Step 4: Creating, Using, and Destroying Struct Instances
397 ------------------------------------------------------------
399 Now that we have our constructor...
403 const my = new MyStruct();
406 It is important to understand that creating a new instance allocates
407 memory on the WASM heap. We must not simply rely on garbage collection
408 to clean up the instances because doing so will not free up the WASM
409 heap memory. The correct way to free up that memory is to use the
410 object's `dispose()` method.
412 The following usage pattern offers one way to easily ensure proper
413 cleanup of struct instances:
417 const my = new MyStruct();
419 console.log(my.member1, my.member2, my.member3);
421 assert(12 === my.member1);
422 /* ^^^ it may seem silly to test that, but recall that assigning that
423 property encodes the value into a byte array in heap memory, not
424 a normal JS property. Similarly, fetching the property decodes it
425 from the byte array. */
426 // Pass the struct to C code which takes a MyStruct pointer:
427 aCFunction( my.pointer );
433 > Sidebar: the `finally` block will be run no matter how the `try`
434 exits, whether it runs to completion, propagates an exception, or uses
435 flow-control keywords like `return` or `break`. It is perfectly legal
436 to use `try`/`finally` without a `catch`, and doing so is an ideal
437 match for the memory management requirements of Jaccwaby-bound struct
440 It is often useful to wrap an existing instance of a C-side struct
441 without taking over ownership of its memory. That can be achieved by
442 simply passing a pointer to the constructor. For example:
445 const m = new MyStruct( functionReturningASharedPtr() );
446 // calling m.dispose() will _not_ free the wrapped C-side instance
447 // but will trigger any ondispose handler.
450 Now that we have struct instances, there are a number of things we
451 can do with them, as covered in the rest of this document.
456 ============================================================
458 <a name='api-binderfactory'></a>
460 ------------------------------------------------------------
462 This is the top-most function of the API, from which all other
463 functions and types are generated. The binder factory's signature is:
467 Function StructBinderFactory(object configOptions);
470 It returns a function which these docs refer to as a [StructBinder][]
471 (covered in the next section). It throws on error.
473 The binder factory supports the following options in its
474 configuration object argument:
478 Must be either a `WebAssembly.Memory` instance representing the WASM
479 heap memory OR a function which returns an Int8Array or Uint8Array
480 view of the WASM heap. In the latter case the function should, if
481 appropriate for the environment, account for the heap being able to
482 grow. Jaccwabyt uses this property in such a way that it "should" be
483 okay for the WASM heap to grow at runtime (that case is, however,
487 Must be a function semantically compatible with Emscripten's
488 `Module._malloc()`. That is, it is passed the number of bytes to
489 allocate and it returns a pointer. On allocation failure it may
490 either return 0 or throw an exception. This API will throw an
491 exception if allocation fails or will propagate whatever exception
492 the allocator throws. The allocator _must_ use the same heap as the
493 `heap` config option.
496 Must be a function semantically compatible with Emscripten's
497 `Module._free()`. That is, it takes a pointer returned from
498 `alloc()` and releases that memory. It must never throw and must
499 accept a value of 0/null to mean "do nothing" (noting that 0 is
500 _technically_ a legal memory address in WASM, but that seems like a
503 - `bigIntEnabled` (bool=true if BigInt64Array is available, else false)
504 If true, the WASM bits this code is used with must have been
505 compiled with int64 support (e.g. using Emscripten's `-sWASM_BIGINT`
506 flag). If that's not the case, this flag should be set to false. If
507 it's enabled, BigInt support is assumed to work and certain extra
508 features are enabled. Trying to use features which requires BigInt
509 when it is disabled (e.g. using 64-bit integer types) will trigger
512 - `memberPrefix` and `memberSuffix` (string="")
513 If set, struct-defined properties get bound to JS with this string
514 as a prefix resp. suffix. This can be used to avoid symbol name
515 collisions between the struct-side members and the JS-side ones
516 and/or to make more explicit which object-level properties belong to
517 the struct mapping and which to the JS side. This does not modify
518 the values in the struct description objects, just the property
519 names through which they are accessed via property access operations
520 and the various a [StructInstance][] APIs (noting that the latter
521 tend to permit both the original names and the names as modified by
525 Optional function used for debugging output. By default
526 `console.log` is used but by default no debug output is generated.
527 This API assumes that the function will space-separate each argument
528 (like `console.log` does). See [Appendix D](#appendix-d) for info
529 about enabling debugging output.
532 <a name='api-structbinder'></a>
534 ------------------------------------------------------------
536 Struct Binders are factories which are created by the
537 [StructBinderFactory][]. A given Struct Binder can process any number
538 of distinct structs. In a typical setup, an app will have ony one
539 shared Binder Factory and one Struct Binder. Struct Binders which are
540 created via different [StructBinderFactory][] calls are unrelated to each
541 other, sharing no state except, perhaps, indirectly via
542 [StructBinderFactory][] configuration (e.g. the memory heap).
544 These factories have two call signatures:
548 Function StructBinder([string structName,] object structDescription)
551 If the struct description argument has a `name` property then the name
552 argument is optional, otherwise it is required.
554 The returned object is a constructor for instances of the struct
555 described by its argument(s), each of which derives from
556 a separate [StructType][] instance.
558 The Struct Binder has the following members:
560 - `allocCString(str)`
561 Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS
562 string and returns its address relative to `config.heap()`. If
563 allocation returns 0 this function throws. Ownership of the memory
564 is transfered to the caller, who must eventually pass it to the
565 configured `config.dealloc()` function.
568 The configuration object passed to the [StructBinderFactory][],
569 primarily for accessing the memory (de)allocator and memory. Modifying
570 any of its "significant" configuration values may have undefined
573 <a name='api-structtype'></a>
575 ------------------------------------------------------------
577 The StructType class is a property of the [StructBinder][] function.
579 Each constructor created by a [StructBinder][] inherits from _its own
580 instance_ of the StructType class, which contains state specific to
581 that struct type (e.g. the struct name and description metadata).
582 StructTypes which are created via different [StructBinder][] instances
583 are unrelated to each other, sharing no state except [StructBinderFactory][]
586 The StructType constructor cannot be called from client code. It is
587 only called by the [StructBinder][]-generated
588 [constructors][StructCtors]. The `StructBinder.StructType` object
589 has the following "static" properties (^Which are accessible from
590 individual instances via `theInstance.constructor`.):
592 - `addOnDispose(...value)`\
593 If this object has no `ondispose` property, this function creates it
594 as an array and pushes the given value(s) onto it. If the object has
595 a function-typed `ondispose` property, this call replaces it with an
596 array and moves that function into the array. In all other cases,
597 `ondispose` is assumed to be an array and the argument(s) is/are
598 appended to it. Returns `this`.
600 - `allocCString(str)`
601 Identical to the [StructBinder][] method of the same name.
603 - `hasExternalPointer(object)`
604 Returns true if the given object's `pointer` member refers to an
605 "external" object. That is the case when a pointer is passed to a
606 [struct's constructor][StructCtors]. If true, the memory is owned by
607 someone other than the object and must outlive the object.
610 Returns true if its argument is a StructType instance _from the same
611 [StructBinder][]_ as this StructType.
613 - `memberKey(string)`
614 Returns the given string wrapped in the configured `memberPrefix`
615 and `memberSuffix` values. e.g. if passed `"x"` and `memberPrefix`
616 is `"$"` then it returns `"$x"`. This does not verify that the
617 property is actually a struct a member, it simply transforms the
618 given string. TODO(?): add a 2nd parameter indicating whether it
619 should validate that it's a known member name.
621 The base StructType prototype has the following members, all of which
622 are inherited by [struct instances](#api-structinstance) and may only
623 legally be called on concrete struct instances unless noted otherwise:
626 Frees, if appropriate, the WASM-allocated memory which is allocated
627 by the constructor. If this is not called before the JS engine
628 cleans up the object, a leak in the WASM heap memory pool will result.
629 When `dispose()` is called, if the object has a property named `ondispose`
630 then it is treated as follows:
631 - If it is a function, it is called with the struct object as its `this`.
632 That method must not throw - if it does, the exception will be
634 - If it is an array, it may contain functions, pointers, other
635 [StructType] instances, and/or JS strings. If an entry is a
636 function, it is called as described above. If it's a number, it's
637 assumed to be a pointer and is passed to the `dealloc()` function
638 configured for the parent [StructBinder][]. If it's a
639 [StructType][] instance then its `dispose()` method is called. If
640 it's a JS string, it's assumed to be a helpful description of the
641 next entry in the list and is simply ignored. Strings are
642 supported primarily for use as debugging information.
643 - Some struct APIs will manipulate the `ondispose` member, creating
644 it as an array or converting it from a function to array as
647 - `lookupMember(memberName,throwIfNotFound=true)`
648 Given the name of a mapped struct member, it returns the member
649 description object. If not found, it either throws (if the 2nd
650 argument is true) or returns `undefined` (if the second argument is
651 false). The first argument may be either the member name as it is
652 mapped in the struct description or that same name with the
653 configured `memberPrefix` and `memberSuffix` applied, noting that
654 the lookup in the former case is faster.\
655 This method may be called directly on the prototype, without a
658 - `memberToJsString(memberName)`
659 Uses `this.lookupMember(memberName,true)` to look up the given
660 member. If its signature is `s` then it is assumed to refer to a
661 NUL-terminated, UTF-8-encoded string and its memory is decoded as
662 such. If its signature is not one of those then an exception is
663 thrown. If its address is 0, `null` is returned. See also:
664 `setMemberCString()`.
666 - `memberIsString(memberName [,throwIfNotFound=true])`
667 Uses `this.lookupMember(memberName,throwIfNotFound)` to look up the
668 given member. Returns the member description object if the member
669 has a signature of `s`, else returns false. If the given member is
670 not found, it throws if the 2nd argument is true, else it returns
673 - `memberKey(string)`
674 Works identically to `StructBinder.StructType.memberKey()`.
677 Returns an array of the names of the properties of this object
678 which refer to C-side struct counterparts.
680 - `memberSignature(memberName [,emscriptenFormat=false])`
681 Returns the signature for a given a member property, either in this
682 framework's format or, if passed a truthy 2nd argument, in a format
683 suitable for the 2nd argument to Emscripten's `addFunction()`.
684 Throws if the first argument does not resolve to a struct-bound
685 member name. The member name is resolved using `this.lookupMember()`
686 and throws if the member is found mapped.
689 Returns a Uint8Array which contains the current state of this
690 object's raw memory buffer. Potentially useful for debugging, but
691 not much else. Note that the memory is necessarily, for
692 compatibility with C, written in the host platform's endianness and
693 is thus not useful as a persistent/portable serialization format.
695 - `setMemberCString(memberName,str)`
696 Uses `StructType.allocCString()` to allocate a new C-style string,
697 assign it to the given member, and add the new string to this
698 object's `ondispose` list for cleanup when `this.dispose()` is
699 called. This function throws if `lookupMember()` fails for the given
700 member name, if allocation of the string fails, or if the member has
701 a signature value of anything other than `s`. Returns `this`.
702 *Achtung*: calling this repeatedly will not immediately free the
703 previous values because this code cannot know whether they are in
704 use in other places, namely C. Instead, each time this is called,
705 the prior value is retained in the `ondispose` list for cleanup when
706 the struct is disposed of. Because of the complexities and general
707 uncertainties of memory ownership and lifetime in such
708 constellations, it is recommended that the use of C-string members
709 from JS be kept to a minimum or that the relationship be one-way:
710 let C manage the strings and only fetch them from JS using, e.g.,
711 `memberToJsString()`.
714 <a name='api-structctor'></a>
715 API: Struct Constructors
716 ------------------------------------------------------------
718 Struct constructors (the functions returned from [StructBinder][])
719 are used for, intuitively enough, creating new instances of a given
724 const x = new MyStruct;
727 Normally they should be passed no arguments, but they optionally
728 accept a single argument: a WASM heap pointer address of memory
729 which the object will use for storage. It does _not_ take over
730 ownership of that memory and that memory must be valid at
731 for least as long as this struct instance. This is used, for example,
732 to proxy static/shared C-side instances:
736 const x = new MyStruct( someCFuncWhichReturnsAMyStructPointer() );
738 x.dispose(); // does NOT free the memory
741 The JS-side construct does not own the memory in that case and has no
742 way of knowing when the C-side struct is destroyed. Results are
743 specifically undefined if the JS-side struct is used after the C-side
744 struct's member is freed.
746 > Potential TODO: add a way of passing ownership of the C-side struct
747 to the JS-side object. e.g. maybe simply pass `true` as the second
748 argument to tell the constructor to take over ownership. Currently the
749 pointer can be taken over using something like
750 `myStruct.ondispose=[myStruct.pointer]` immediately after creation.
752 These constructors have the following "static" members:
755 Returns true if its argument was created by this constructor.
757 - `memberKey(string)`
758 Works exactly as documented for [StructType][].
760 - `memberKeys(string)`
761 Works exactly as documented for [StructType][].
764 The structure description passed to [StructBinder][] when this
765 constructor was generated.
768 The structure name passed to [StructBinder][] when this constructor
772 <a name='api-structprototype'></a>
773 API: Struct Prototypes
774 ------------------------------------------------------------
776 The prototypes of structs created via [the constructors described in
777 the previous section][StructCtors] are each a struct-type-specific
778 instance of [StructType][] and add the following struct-type-specific
779 properties to the mix:
782 The struct description metadata, as it was given to the
783 [StructBinder][] which created this class.
786 The name of the struct, as it was given to the [StructBinder][] which
789 <a name='api-structinstance'></a>
790 API: Struct Instances
791 ------------------------------------------------------------------------
793 Instances of structs created via [the constructors described
794 above][StructCtors] each have the following instance-specific state in
798 A read-only numeric property which is the "pointer" returned by the
799 configured allocator when this object is constructed. After
800 `dispose()` (inherited from [StructType][]) is called, this property
801 has the `undefined` value. When calling C-side code which takes a
802 pointer to a struct of this type, simply pass it `myStruct.pointer`.
804 <a name='appendices'></a>
806 ============================================================
808 <a name='appendix-a'></a>
809 Appendix A: Limitations, TODOs, and Non-TODOs
810 ------------------------------------------------------------
812 - This library only supports the basic set of member types supported
813 by WASM: numbers (which includes pointers). Nested structs are not
814 handled except that a member may be a _pointer_ to such a
815 struct. Whether or not it ever will depends entirely on whether its
816 developer ever needs that support. Conversion of strings between
817 JS and C requires infrastructure specific to each WASM environment
818 and is not directly supported by this library.
820 - Binding functions to struct instances, such that C can see and call
821 JS-defined functions, is not as transparent as it really could be,
822 due to [shortcomings in the Emscripten
823 `addFunction()`/`removeFunction()`
824 interfaces](https://github.com/emscripten-core/emscripten/issues/17323). Until
825 a replacement for that API can be written, this support will be
826 quite limited. It _is_ possible to bind a JS-defined function to a
827 C-side function pointer and call that function from C. What's
828 missing is easier-to-use/more transparent support for doing so.
829 - In the meantime, a [standalone
830 subproject](/file/common/whwasmutil.js) of Jaccwabyt provides such a
831 binding mechanism, but integrating it directly with Jaccwabyt would
832 not only more than double its size but somehow feels inappropriate, so
833 experimentation is in order for how to offer that capability via
834 completely optional [StructBinderFactory][] config options.
836 - It "might be interesting" to move access of the C-bound members into
837 a sub-object. e.g., from JS they might be accessed via
838 `myStructInstance.s.structMember`. The main advantage is that it would
839 eliminate any potential confusion about which members are part of
840 the C struct and which exist purely in JS. "The problem" with that
841 is that it requires internally mapping the `s` member back to the
842 object which contains it, which makes the whole thing more costly
843 and adds one more moving part which can break. Even so, it's
844 something to try out one rainy day. Maybe even make it optional and
845 make the `s` name configurable via the [StructBinderFactory][]
846 options. (Over-engineering is an arguably bad habit of mine.)
848 - It "might be interesting" to offer (de)serialization support. It
849 would be very limited, e.g. we can't serialize arbitrary pointers in
850 any meaningful way, but "might" be useful for structs which contain
851 only numeric or C-string state. As it is, it's easy enough for
852 client code to write wrappers for that and handle the members in
853 ways appropriate to their apps. Any impl provided in this library
854 would have the shortcoming that it may inadvertently serialize
855 pointers (since they're just integers), resulting in potential chaos
856 after deserialization. Perhaps the struct description can be
857 extended to tag specific members as serializable and how to
860 <a name='appendix-d'></a>
861 Appendix D: Debug Info
862 ------------------------------------------------------------
864 The [StructBinderFactory][], [StructBinder][], and [StructType][] classes
865 all have the following "unsupported" method intended primarily
866 to assist in their own development, as opposed to being for use in
869 - `debugFlags(flags)` (integer)
870 An "unsupported" debugging option which may change or be removed at
871 any time. Its argument is a set of flags to enable/disable certain
872 debug/tracing output for property accessors: 0x01 for getters, 0x02
873 for setters, 0x04 for allocations, 0x08 for deallocations. Pass 0 to
874 disable all flags and pass a negative value to _completely_ clear
875 all flags. The latter has the side effect of telling the flags to be
876 inherited from the next-higher-up class in the hierarchy, with
877 [StructBinderFactory][] being top-most, followed by [StructBinder][], then
881 <a name='appendix-g'></a>
882 Appendix G: Generating Struct Descriptions From C
883 ------------------------------------------------------------
885 Struct definitions are _ideally_ generated from WASM-compiled C, as
886 opposed to simply guessing the sizeofs and offsets, so that the sizeof
887 and offset information can be collected using C's `sizeof()` and
888 `offsetof()` features (noting that struct padding may impact offsets
889 in ways which might not be immediately obvious, so writing them by
890 hand is _most certainly not recommended_).
892 How exactly the desciption is generated is necessarily
893 project-dependent. It's tempting say, "oh, that's easy! We'll just
894 write it by hand!" but that would be folly. The struct sizes and byte
895 offsets into the struct _must_ be precisely how C-side code sees the
896 struct or the runtime results are completely undefined.
898 The approach used in developing and testing _this_ software is...
900 Below is a complete copy/pastable example of how we can use a small
901 set of macros to generate struct descriptions from C99 or later into
902 static string memory. Simply add such a file to your WASM build,
903 arrange for its function to be exported[^export-func], and call it
904 from JS (noting that it requires environment-specific JS glue to
905 convert the returned pointer to a JS-side string). Use `JSON.parse()`
906 to process it, then feed the included struct descriptions into the
907 binder factory at your leisure.
909 ------------------------------------------------------------
912 #include <string.h> /* memset() */
913 #include <stddef.h> /* offsetof() */
914 #include <stdio.h> /* snprintf() */
915 #include <stdint.h> /* int64_t */
918 struct ExampleStruct {
922 void (*xFunc)(void*);
924 typedef struct ExampleStruct ExampleStruct;
926 const char * wasm__ctype_json(void){
927 static char strBuf[512 * 8] = {0}
928 /* Static buffer which must be sized large enough for
929 our JSON. The string-generation macros try very
930 hard to assert() if this buffer is too small. */;
931 int n = 0, structCount = 0 /* counters for the macros */;
932 char * pos = &strBuf[1]
933 /* Write-position cursor. Skip the first byte for now to help
934 protect against a small race condition */;
935 char const * const zEnd = pos + sizeof(strBuf)
936 /* one-past-the-end cursor (virtual EOF) */;
937 if(strBuf[0]) return strBuf; // Was set up in a previous call.
939 ////////////////////////////////////////////////////////////////////
940 // First we need to build up our macro framework...
942 ////////////////////////////////////////////////////////////////////
943 // Core output-generating macros...
944 #define lenCheck assert(pos < zEnd - 100)
945 #define outf(format,...) \
946 pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
948 #define out(TXT) outf("%s",TXT)
949 #define CloseBrace(LEVEL) \
950 assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck
952 ////////////////////////////////////////////////////////////////////
953 // Macros for emiting StructBinders...
954 #define StructBinder__(TYPE) \
956 outf("%s{", (structCount++ ? ", " : "")); \
957 out("\"name\": \"" # TYPE "\","); \
958 outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
959 out(",\"members\": {");
960 #define StructBinder_(T) StructBinder__(T)
961 // ^^^ extra indirection needed to expand CurrentStruct
962 #define StructBinder StructBinder_(CurrentStruct)
963 #define _StructBinder CloseBrace(2)
964 #define M(MEMBER,SIG) \
966 "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
967 (n++ ? ", " : ""), #MEMBER, \
968 (int)offsetof(CurrentStruct,MEMBER), \
969 (int)sizeof(((CurrentStruct*)0)->MEMBER), \
972 ////////////////////////////////////////////////////////////////////
974 ////////////////////////////////////////////////////////////////////
975 // With that out of the way, we can do what we came here to do.
976 out("\"structs\": ["); {
978 // For each struct description, do...
979 #define CurrentStruct ExampleStruct
988 } out( "]"/*structs*/);
989 ////////////////////////////////////////////////////////////////////
990 // Done! Finalize the output...
991 out("}"/*top-level wrapper*/);
993 strBuf[0] = '{'/*end of the race-condition workaround*/;
996 // If this file will ever be concatenated or #included with others,
997 // it's good practice to clean up our macros:
1000 #undef StructBinder__
1002 #undef _StructBinder
1010 ------------------------------------------------------------
1014 counter-reset: h1 -1;
1016 div.content h1, div.content h2, div.content h3 {
1017 border-radius: 0.25em;
1018 border-bottom: 1px solid #70707070;
1023 div.content h1::before, div.content h2::before, div.content h3::before {
1024 background-color: #a5a5a570;
1025 margin-right: 0.5em;
1026 border-radius: 0.25em;
1028 div.content h1::before {
1029 counter-increment: h1;
1030 content: counter(h1) ;
1032 border-radius: 0.25em;
1034 div.content h2::before {
1035 counter-increment: h2;
1036 content: counter(h1) "." counter(h2);
1037 padding: 0 0.5em 0 1.75em;
1038 border-radius: 0.25em;
1043 div.content h3::before {
1044 counter-increment: h3;
1045 content: counter(h1) "." counter(h2) "." counter(h3);
1046 padding: 0 0.5em 0 2.5em;
1048 div.content h3 {border-left-width: 2.5em}
1051 [sqlite3]: https://sqlite.org
1052 [emscripten]: https://emscripten.org
1053 [sgb]: https://wanderinghorse.net/home/stephan/
1054 [appendix-g]: #appendix-g
1055 [StructBinderFactory]: #api-binderfactory
1056 [StructCtors]: #api-structctor
1057 [StructType]: #api-structtype
1058 [StructBinder]: #api-structbinder
1059 [StructInstance]: #api-structinstance
1060 [^export-func]: In Emscripten, add its name, prefixed with `_`, to the
1061 project's `EXPORT_FUNCTIONS` list.
1062 [BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array
1063 [TextDecoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
1064 [TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
1065 [MDN]: https://developer.mozilla.org/docs/Web/API