summaryrefslogtreecommitdiffstats
path: root/ext/wasm/jaccwabyt/jaccwabyt.md
blob: edcba260a7425451c33e2ff4ecb569f282cb0a98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
Jaccwabyt 🐇
============================================================

**Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte
Arrays_


Welcome to Jaccwabyt, a JavaScript API which creates bindings for
WASM-compiled C structs, defining them in such a way that changes to
their state in JS are visible in C/WASM, and vice versa, permitting
two-way interchange of struct state with very little user-side
friction.

(If that means nothing to you, neither will the rest of this page!)

**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
mid-2018 or later, with late 2021 releases required for some optional
features in some browsers (e.g. [BigInt64Array][] in Safari). It also
relies on a couple non-standard, but widespread, features, namely
[TextEncoder][] and [TextDecoder][]. It is developed primarily on
Firefox and Chrome on Linux and all claims of Safari compatibility
are based solely on feature compatibility tables provided at
[MDN][].

**Formalities:**

- Author: [Stephan Beal][sgb]
- License: Public Domain
- Project Home: <https://fossil.wanderinghorse.net/r/jaccwabyt>

<a name='overview'></a>
Table of Contents
============================================================

- [Overview](#overview)
  - [Architecture](#architecture)
- [Creating and Binding Structs](#creating-binding)
  - [Step 1: Configure Jaccwabyt](#step-1)
  - [Step 2: Struct Description](#step-2)
     - [`P` vs `p`](#step-2-pvsp)
  - [Step 3: Binding a Struct](#step-3)
  - [Step 4: Creating, Using, and Destroying Instances](#step-4)
- APIs
  - [Struct Binder Factory](#api-binderfactory)
  - [Struct Binder](#api-structbinder)
  - [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 D: Debug Info](#appendix-d)
  - [Appendix G: Generating Struct Descriptions](#appendix-g)

<a name='overview'></a>
Overview
============================================================

Management summary: this JavaScript-only framework provides limited
two-way bindings between C structs and JavaScript objects, such that
changes to the struct in one environment are visible in the other.

Details...

It works by creating JavaScript proxies for C structs. Reads and
writes of the JS-side members are marshaled through a flat byte array
allocated from the WASM heap. As that heap is shared with the C-side
code, and the memory block is written using the same approach C does,
that byte array can be used to access and manipulate a given struct
instance from both JS and C.

Motivating use case: this API was initially developed as an
experiment to determine whether it would be feasible to implement,
completely in JS, custom "VFS" and "virtual table" objects for the
WASM build of [sqlite3][]. Doing so was going to require some form of
two-way binding of several structs.  Once the proof of concept was
demonstrated, a rabbit hole appeared and _down we went_... It has
since grown beyond its humble proof-of-concept origins and is believed
to be a useful (or at least interesting) tool for mixed JS/C
applications.

Portability notes:

- These docs sometimes use [Emscripten][] as a point of reference
  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.
- 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.

<a name='architecture'></a>
Architecture
------------------------------------------------------------

<!--
bug(?) (fossil): using "center" shrinks pikchr too much.
-->

```pikchr
BSBF: box rad 0.3*boxht "StructBinderFactory" fit fill lightblue
BSB: box same "StructBinder" fit at 0.75 e of 0.7 s of BSBF.c
BST: box same "StructType<T>" fit at 1.5 e of BSBF
BSC: box same "Struct<T>" "Ctor" fit at 1.5 s of BST
BSI: box same "Struct<T>" "Instances" fit at 1 right of BSB.e
BC: box same at 0.25 right of 1.6 e of BST "C Structs" fit fill lightgrey

arrow -> from BSBF.s to BSB.w "Generates" aligned above
arrow -> from BSB.n to BST.sw "Contains" aligned above
arrow -> from BSB.s to BSC.nw "Generates" aligned below
arrow -> from BSC.ne to BSI.s "Constructs" aligned below
arrow <- from BST.se to BSI.n "Inherits" aligned above
arrow <-> from BSI.e to BC.s dotted "Shared" aligned above "Memory" aligned below
arrow -> from BST.e to BC.w dotted "Mirrors Struct" aligned above "Model From" aligned below
arrow -> from BST.s to BSC.n "Prototype of" aligned above
```

Its major classes and functions are:

- **[StructBinderFactory][StructBinderFactory]** is a factory function which
  accepts a configuration object to customize it for a given WASM
  environment. A client will typically call this only one time, with
  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
  description, which inherit from
  **[`StructBinder.StructType`][StructType]** and are used to instantiate...
- **[Struct instances][StructInstance]** are objects representing
  individual instances of generated struct types.

An app may have any number of StructBinders, but will typically
need only one. Each StructBinder is effectively a separate
namespace for struct creation.


<a name='creating-binding'></a>
Creating and Binding Structs
============================================================

From the amount of documentation provided, it may seem that
creating and using struct bindings is a daunting task, but it
essentially boils down to:

1. [Confire Jaccwabyt for your WASM environment](#step-1). This is a
   one-time task per project and results is a factory function which
   can create new struct bindings.
2. [Create a JSON-format description of your C structs](#step-2). This is
   required once for each struct and required updating if the C
   structs change.
3. [Feed (2) to the function generated by (1)](#step-3) to create JS
   constuctor functions for each struct. This is done at runtime, as
   opposed to during a build-process step, and can be set up in such a
   way that it does not require any maintenace after its initial
   setup.
4. [Create and use instances of those structs](#step-4).

Detailed instructions for each of those steps follows...

<a name='step-1'></a>
Step 1: Configure Jaccwabyt for the Environment
------------------------------------------------------------

Jaccwabyt's highest-level API is a single function. It creates a
factory for processing struct descriptions, but does not process any
descriptions itself. This level of abstraction exist primarily so that
the struct-specific factories can be configured for a given WASM
environment. Its usage looks like:

>  
```javascript
const MyBinder = StructBinderFactory({
  // These config options are all required:
  heap: WebAssembly.Memory instance or a function which returns
        a Uint8Array or Int8Array view of the WASM memory,
  alloc:   function(howMuchMemory){...},
  dealloc: function(pointerToFree){...}
});
```

It also offers a number of other settings, but all are optional except
for the ones shown above. Those three config options abstract away
details which are specific to a given WASM environment. They provide
the WASM "heap" memory (a byte array), the memory allocator, and the
deallocator. In a conventional Emscripten setup, that config might
simply look like:

>  
```javascript
{
    heap:    Module['asm']['memory'],
    //Or:
    // heap: ()=>Module['HEAP8'],
    alloc:   (n)=>Module['_malloc'](n),
    dealloc: (m)=>Module['_free'](m)
}
```

The StructBinder factory function returns a function which can then be
used to create bindings for our structs.


<a name='step-2'></a>
Step 2: Create a Struct Description
------------------------------------------------------------

The primary input for this framework is a JSON-compatible construct
which describes a struct we want to bind. For example, given this C
struct:

>  
```c
// C-side:
struct Foo {
  int member1;
  void * member2;
  int64_t member3;
};
```

Its JSON description looks like:

>  
```json
{
  "name": "Foo",
  "sizeof": 16,
  "members": {
    "member1": {"offset": 0,"sizeof": 4,"signature": "i"},
    "member2": {"offset": 4,"sizeof": 4,"signature": "p"},
    "member3": {"offset": 8,"sizeof": 8,"signature": "j"}
  }
}
```

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.

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)
- **`p`** = `int32` (but see 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
  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.

> 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
pointer-to-pointer, but the Emscripten JS/WASM glue does not offer
that level of expressiveness in these signatures. We simply have to be
aware of when we need to deal with pointers and pointers-to-pointers
in JS code.

> 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` should also be replaced with `i`. In
JavaScript that might look like:

>  
```
signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i');
```

<a name='step-2-pvsp'></a>
### `P` vs `p` in Method Signatures

*This support is experimental and subject to change.*

The method signature letter `p` means "pointer," which, in WASM, means
"integer." `p` is treated as an integer for most contexts, while still
also being a separate type (analog to how pointers in C are just a
special use of unsigned numbers). A capital `P` changes the semantics
of plain member pointers (but not, as of this writing, function
pointer members) as follows:

- When a `P`-type member is **fetched** via `myStruct.x` and its value is
  a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder]
  is used to try to map that pointer to a struct instance. If a match
  is found, the "get" operation returns that instance instead of the
  integer. If no match is found, it behaves exactly as for `p`, returning
  the integer value.
- When a `P`-type member is **set** via `myStruct.x=y`, if
  [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is
  stored in `myStruct.x`. If `y` is neither a number nor
  a [StructType][], an exception is triggered (regardless of whether
  `p` or `P` is used).


<a name='step-3'></a>
Step 3: Binding the Struct
------------------------------------------------------------

We can now use the results of steps 1 and 2:

>  
```javascript
const MyStruct = MyBinder(myStructDescription);
```

That creates a new constructor function, `MyStruct`, which can be used
to instantiate new instances. The binder will throw if it encounters
any problems.

That's all there is to it.

> Sidebar: that function may modify the struct description object
and/or its sub-objects, or may even replace sub-objects, in order to
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))`.

<a name='step-4'></a>
Step 4: Creating, Using, and Destroying Struct Instances
------------------------------------------------------------

Now that we have our constructor...

>  
```javascript
const my = new MyStruct();
```

It is important to understand that creating a new instance allocates
memory on the WASM heap. We must not simply rely on garbage collection
to clean up the instances because doing so will not free up the WASM
heap memory. The correct way to free up that memory is to use the
object's `dispose()` method. Alternately, there is a "nuclear option":
`MyBinder.disposeAll()` will free the memory allocated for _all_
instances which have not been manually disposed.

The following usage pattern offers one way to easily ensure proper
cleanup of struct instances:


>  
```javascript
const my = new MyStruct();
try {
  console.log(my.member1, my.member2, my.member3);
  my.member1 = 12;
  assert(12 === my.member1);
  /* ^^^ it may seem silly to test that, but recall that assigning that
     property encodes the value into a byte array in heap memory, not
     a normal JS property. Similarly, fetching the property decodes it
     from the byte array. */
  // Pass the struct to C code which takes a MyStruct pointer:
  aCFunction( my.pointer );
  // Type-safely check if a pointer returned from C is a MyStruct:
  const x = MyStruct.instanceForPointer( anotherCFunction() );
  // If it is a MyStruct, x now refers to that object. Note, however,
  // that this only works for instances created in JS, as the
  // pointer mapping only exists in JS space.
} finally {
  my.dispose();
}
```

> Sidebar: the `finally` block will be run no matter how the `try`
exits, whether it runs to completion, propagates an exception, or uses
flow-control keywords like `return` or `break`. It is perfectly legal
to use `try`/`finally` without a `catch`, and doing so is an ideal
match for the memory management requirements of Jaccwaby-bound struct
instances.

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.


<a name='api'></a>
API Reference
============================================================

<a name='api-binderfactory'></a>
API: Binder Factory
------------------------------------------------------------

This is the top-most function of the API, from which all other
functions and types are generated. The binder factory's signature is:

>  
```
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:


- `heap`  
  Must be either a `WebAssembly.Memory` instance representing the WASM
  heap memory OR a function which returns an Int8Array or Uint8Array
  view of the WASM heap. In the latter case the function should, if
  appropriate for the environment, account for the heap being able to
  grow. Jaccwabyt uses this property in such a way that it "should" be
  okay for the WASM heap to grow at runtime (that case is, however,
  untested).

- `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.

- `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).

- `bigIntEnabled` (bool=true if BigInt64Array is available, else false)  
  If true, the WASM bits this code is used with must have been
  compiled with int64 support (e.g. using Emscripten's `-sWASM_BIGINT`
  flag). If that's not the case, this flag should be set to false. If
  it's enabled, BigInt support is assumed to work and certain extra
  features are enabled. Trying to use features which requires BigInt
  when it is disabled (e.g. using 64-bit integer types) will trigger
  an exception.

- `memberPrefix` and `memberSuffix` (string="")  
  If set, struct-defined properties get bound to JS with this string
  as a prefix resp. suffix. This can be used to avoid symbol name
  collisions between the struct-side members and the JS-side ones
  and/or to make more explicit which object-level properties belong to
  the struct mapping and which to the JS side. This does not modify
  the values in the struct description objects, just the property
  names through which they are accessed via property access operations
  and the various a [StructInstance][] APIs (noting that the latter
  tend to permit both the original names and the names as modified by
  these settings).

- `log`  
  Optional function used for debugging output. By default
  `console.log` is used but by default no debug output is generated.
  This API assumes that the function will space-separate each argument
  (like `console.log` does). See [Appendix D](#appendix-d) for info
  about enabling debugging output.


<a name='api-structbinder'></a>
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
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
[StructBinderFactory][] configuration (e.g. the memory heap).

These factories have two call signatures:

>  
```javascript
Function StructBinder([string structName,] object structDescription)
```

If the struct description argument has a `name` property then the name
argument is optional, otherwise it is required.

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:

- `allocCString(str)`  
  Allocates a new UTF-8-encoded, NUL-terminated copy of the given JS
  string and returns its address relative to `config.heap()`. If
  allocation returns 0 this function throws. Ownership of the memory
  is transfered to the caller, who must eventually pass it to the
  configured `config.dealloc()` function.

- `config`  
  The configuration object passed to the [StructBinderFactory][],
  primarily for accessing the memory (de)allocator and memory. Modifying
  any of its "significant" configuration values may have undefined
  results.

- `instanceForPointer(pointer)`  
  Given a pointer value relative to `config.memory`, if that pointer
  resolves to a struct of _any type_ generated via the same Struct
  Binder, this returns the struct instance associated with it, or
  `undefined` if no struct object is mapped to that pointer. This
  differs from the struct-type-specific member of the same name in
  that this one is not "type-safe": it does not know the type of the
  returned object (if any) and may return a struct of any
  [StructType][] for which this Struct Binder has created a
  constructor. It cannot return instances created via a different
  [StructBinderFactory][] because each factory can hypothetically have
  a different memory heap.


<a name='api-structtype'></a>
API: Struct Type
------------------------------------------------------------

The StructType class is a property of the [StructBinder][] function.

Each constructor created by a [StructBinder][] inherits from _its own
instance_ of the StructType class, which contains state specific to
that struct type (e.g. the struct name and description metadata).
StructTypes which are created via different [StructBinder][] instances
are unrelated to each other, sharing no state except [StructBinderFactory][]
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
has the following "static" properties (^Which are accessible from
individual instances via `theInstance.constructor`.):

- `allocCString(str)`  
  Identical to the [StructBinder][] method of the same name.

- `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
  someone other than the object and must outlive the object.

- `instanceForPointer(pointer)`  
  Works identically to the [StructBinder][] method of the same name.

- `isA(value)`  
  Returns true if its argument is a StructType instance _from the same
  [StructBinder][]_ as this StructType.

- `memberKey(string)`  
  Returns the given string wrapped in the configured `memberPrefix`
  and `memberSuffix` values. e.g. if passed `"x"` and `memberPrefix`
  is `"$"` then it returns `"$x"`. This does not verify that the
  property is actually a struct a member, it simply transforms the
  given string.  TODO(?): add a 2nd parameter indicating whether it
  should validate that it's a known member name.

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:

- `dispose()`  
  Frees, if appropriate, the WASM-allocated memory which is allocated
  by the constructor. If this is not called before the JS engine
  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 an array, it may contain functions, pointers, 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 JS string, it's assumed to be a
    helpful description of the next entry in the list and is simply
    ignored. Strings are 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.

- `lookupMember(memberName,throwIfNotFound=true)`  
  Given the name of a mapped struct member, it returns the member
  description object. If not found, it either throws (if the 2nd
  argument is true) or returns `undefined` (if the second argument is
  false). The first argument may be either the member name as it is
  mapped in the struct description or that same name with the
  configured `memberPrefix` and `memberSuffix` applied, noting that
  the lookup in the former case is faster.\  
  This method may be called directly on the prototype, without a
  struct instance.

- `memberToJsString(memberName)`  
  Uses `this.lookupMember(memberName,true)` to look up the given
  member. If its signature is `s` then it is assumed to refer to a
  NUL-terminated, UTF-8-encoded string and its memory is decoded as
  such. If its signature is not one of those then an exception is
  thrown.  If its address is 0, `null` is returned. See also:
  `setMemberCString()`.

- `memberIsString(memberName [,throwIfNotFound=true])`  
  Uses `this.lookupMember(memberName,throwIfNotFound)` to look up the
  given member. Returns the member description object if the member
  has a signature of `s`, else returns false. If the given member is
  not found, it throws if the 2nd argument is true, else it returns
  false.

- `memberKey(string)`  
  Works identically to `StructBinder.StructType.memberKey()`.

- `memberKeys()`  
  Returns an array of the names of the properties of this object
  which refer to C-side struct counterparts.

- `memberSignature(memberName [,emscriptenFormat=false])`  
  Returns the signature for a given a member property, either in this
  framework's format or, if passed a truthy 2nd argument, in a format
  suitable for the 2nd argument to Emscripten's `addFunction()`.
  Throws if the first argument does not resolve to a struct-bound
  member name. The member name is resolved using `this.lookupMember()`
  and throws if the member is found mapped.

- `memoryDump()`  
  Returns a Uint8Array which contains the current state of this
  object's raw memory buffer. Potentially useful for debugging, but
  not much else. Note that the memory is necessarily, for
  compatibility with C, written in the host platform's endianness and
  is thus not useful as a persistent/portable serialization format.

- `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
  object's `ondispose` list for cleanup when `this.dispose()` is
  called. This function throws if `lookupMember()` fails for the given
  member name, if allocation of the string fails, or if the member has
  a signature value of anything other than `s`. Returns `this`.  
  *Achtung*: calling this repeatedly will not immediately free the
  previous values because this code cannot know whether they are in
  use in other places, namely C. Instead, each time this is called,
  the prior value is retained in the `ondispose` list for cleanup when
  the struct is disposed of. Because of the complexities and general
  uncertainties of memory ownership and lifetime in such
  constellations, it is recommended that the use of C-string members
  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()`.
  

<a name='api-structctor'></a>
API: Struct Constructors
------------------------------------------------------------

Struct constructors (the functions returned from [StructBinder][])
are used for, intuitively enough, creating new instances of a given
struct type:

>  
```
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:

>  
```
const x = new MyStruct( someCFuncWhichReturnsAMyStructPointer() );
...
x.dispose(); // does NOT free 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.

> 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.

These constructors have the following "static" members:

- `disposeAll()`  
  For each instance of this struct, the equivalent of its `dispose()`
  method is called. This frees all WASM-allocated memory associated
  with _all_ instances and clears the `instanceForPointer()`
  mappings. Returns `this`.

- `instanceForPointer(pointer)`  
  Given a pointer value (accessible via the `pointer` property of all
  struct instances) which ostensibly refers to an instance of this
  class, this returns the instance associated with it, or `undefined`
  if no object _of this specific struct type_ is mapped to that
  pointer. When C-side code calls back into JS code and passes a
  pointer to an object, this function can be used to type-safely
  "cast" that pointer back to its original object.

- `isA(value)`  
  Returns true if its argument was created by this constructor.

- `memberKey(string)`  
  Works exactly as documented for [StructType][].

- `memberKeys(string)`  
  Works exactly as documented for [StructType][].

- `resolveToInstance(value [,throwIfNot=false])`  
  Works like `instanceForPointer()` but accepts either an instance
  of this struct type or a pointer which resolves to one.
  It returns an instance of this struct type on success.
  By default it returns a falsy value if its argument is not,
  or does not resolve to, an instance of this struct type,
  but if passed a truthy second argument then it will throw
  instead.

- `structInfo`  
  The structure description passed to [StructBinder][] when this
  constructor was generated.

- `structName`  
  The structure name passed to [StructBinder][] when this constructor
  was generated.
  

<a name='api-structprototype'></a>
API: Struct Prototypes
------------------------------------------------------------

The prototypes of structs created via [the constructors described in
the previous section][StructCtors] are each a struct-type-specific
instance of [StructType][] and add the following struct-type-specific
properties to the mix:

- `structInfo`  
  The struct description metadata, as it was given to the
  [StructBinder][] which created this class.

- `structName`  
  The name of the struct, as it was given to the [StructBinder][] which
  created this class.

<a name='api-structinstance'></a>
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`.

<a name='appendices'></a>
Appendices
============================================================

<a name='appendix-a'></a>
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.

<a name='appendix-d'></a>
Appendix D: Debug Info
------------------------------------------------------------

The [StructBinderFactory][], [StructBinder][], and [StructType][] classes
all have the following "unsupported" method intended primarily
to assist in their own development, as opposed to being for use in
client code:

- `debugFlags(flags)` (integer)  
  An "unsupported" debugging option which may change or be removed at
  any time. Its argument is a set of flags to enable/disable certain
  debug/tracing output for property accessors: 0x01 for getters, 0x02
  for setters, 0x04 for allocations, 0x08 for deallocations. Pass 0 to
  disable all flags and pass a negative value to _completely_ clear
  all flags. The latter has the side effect of telling the flags to be
  inherited from the next-higher-up class in the hierarchy, with
  [StructBinderFactory][] being top-most, followed by [StructBinder][], then
  [StructType][].


<a name='appendix-g'></a>
Appendix G: Generating Struct Descriptions From C
------------------------------------------------------------

Struct definitions are _ideally_ generated from WASM-compiled C, as
opposed to simply guessing the sizeofs and offsets, so that the sizeof
and offset information can be collected using C's `sizeof()` and
`offsetof()` features (noting that struct padding may impact offsets
in ways which might not be immediately obvious, so writing them by
hand is _most certainly not recommended_).

How exactly the desciption is generated is necessarily
project-dependent. It's tempting say, "oh, that's easy! We'll just
write it by hand!" but that would be folly. The struct sizes and byte
offsets into the struct _must_ be precisely how C-side code sees the
struct or the runtime results are completely undefined.

The approach used in developing and testing _this_ software is...

Below is a complete copy/pastable example of how we can use a small
set of macros to generate struct descriptions from C99 or later into
static string memory. Simply add such a file to your WASM build,
arrange for its function to be exported[^export-func], and call it
from JS (noting that it requires environment-specific JS glue to
convert the returned pointer to a JS-side string). Use `JSON.parse()`
to process it, then feed the included struct descriptions into the
binder factory at your leisure.

------------------------------------------------------------

```c
#include <string.h> /* memset() */
#include <stddef.h> /* offsetof() */
#include <stdio.h>  /* snprintf() */
#include <stdint.h> /* int64_t */
#include <assert.h>

struct ExampleStruct {
  int v4;
  void * ppV;
  int64_t v8;
  void (*xFunc)(void*);
};
typedef struct ExampleStruct ExampleStruct;

const char * wasm__ctype_json(void){
  static char strBuf[512 * 8] = {0}
    /* Static buffer which must be sized large enough for
       our JSON. The string-generation macros try very
       hard to assert() if this buffer is too small. */;
  int n = 0, structCount = 0 /* counters for the macros */;
  char * pos = &strBuf[1]
    /* Write-position cursor. Skip the first byte for now to help
       protect against a small race condition */;
  char const * const zEnd = pos + sizeof(strBuf)
    /* one-past-the-end cursor (virtual EOF) */;
  if(strBuf[0]) return strBuf; // Was set up in a previous call.

  ////////////////////////////////////////////////////////////////////
  // First we need to build up our macro framework...

  ////////////////////////////////////////////////////////////////////
  // Core output-generating macros...
#define lenCheck assert(pos < zEnd - 100)
#define outf(format,...) \
  pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
  lenCheck
#define out(TXT) outf("%s",TXT)
#define CloseBrace(LEVEL) \
  assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck

  ////////////////////////////////////////////////////////////////////
  // Macros for emiting StructBinders...
#define StructBinder__(TYPE)                 \
  n = 0;                                     \
  outf("%s{", (structCount++ ? ", " : ""));  \
  out("\"name\": \"" # TYPE "\",");          \
  outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
  out(",\"members\": {");
#define StructBinder_(T) StructBinder__(T)
// ^^^ extra indirection needed to expand CurrentStruct
#define StructBinder StructBinder_(CurrentStruct)
#define _StructBinder CloseBrace(2)
#define M(MEMBER,SIG)                                         \
  outf("%s\"%s\": "                                           \
       "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
       (n++ ? ", " : ""), #MEMBER,                            \
       (int)offsetof(CurrentStruct,MEMBER),                   \
       (int)sizeof(((CurrentStruct*)0)->MEMBER),              \
       SIG)
  // End of macros.
  ////////////////////////////////////////////////////////////////////

  ////////////////////////////////////////////////////////////////////
  // With that out of the way, we can do what we came here to do.
  out("\"structs\": ["); {

// For each struct description, do...
#define CurrentStruct ExampleStruct
    StructBinder {
      M(v4,"i");
      M(ppV,"p");
      M(v8,"j");
      M(xFunc,"v(p)");
    } _StructBinder;
#undef CurrentStruct

  } out( "]"/*structs*/);
  ////////////////////////////////////////////////////////////////////
  // Done! Finalize the output...
  out("}"/*top-level wrapper*/);
  *pos = 0;
  strBuf[0] = '{'/*end of the race-condition workaround*/;
  return strBuf;

// If this file will ever be concatenated or #included with others,
// it's good practice to clean up our macros:
#undef StructBinder
#undef StructBinder_
#undef StructBinder__
#undef M
#undef _StructBinder
#undef CloseBrace
#undef out
#undef outf
#undef lenCheck
}
```

------------------------------------------------------------

<style>
div.content {
  counter-reset: h1 -1;
}
div.content h1, div.content h2, div.content h3 {
  border-radius: 0.25em;
  border-bottom: 1px solid #70707070;
}
div.content h1 {
  counter-reset: h2;
}
div.content h1::before, div.content h2::before, div.content h3::before {
  background-color: #a5a5a570;
  margin-right: 0.5em;
  border-radius: 0.25em;
}
div.content h1::before {
  counter-increment: h1;
  content: counter(h1) ;
  padding: 0 0.5em;
  border-radius: 0.25em;
}
div.content h2::before {
  counter-increment: h2;
  content: counter(h1) "." counter(h2);
  padding: 0 0.5em 0 1.75em;
  border-radius: 0.25em;
}
div.content h2 {
  counter-reset: h3;
}
div.content h3::before {
  counter-increment: h3;
  content: counter(h1) "." counter(h2) "." counter(h3);
  padding: 0 0.5em 0 2.5em;
}
div.content h3 {border-left-width: 2.5em}
</style>

[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
[StructBinder]: #api-structbinder
[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
[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