Fixes default log output to console for macOS
[sqlcipher.git] / ext / wasm / jaccwabyt / jaccwabyt.js
blob1846441e55f87566433e889e73c9253fec7b8d1d
1 /**
2   2022-06-30
4   The author disclaims copyright to this source code.  In place of a
5   legal notice, here is a blessing:
7   *   May you do good and not evil.
8   *   May you find forgiveness for yourself and forgive others.
9   *   May you share freely, never taking more than you give.
11   ***********************************************************************
13   The Jaccwabyt API is documented in detail in an external file,
14   _possibly_ called jaccwabyt.md in the same directory as this file.
16   Project homes:
17   - https://fossil.wanderinghorse.net/r/jaccwabyt
18   - https://sqlite.org/src/dir/ext/wasm/jaccwabyt
21 'use strict';
22 globalThis.Jaccwabyt = function StructBinderFactory(config){
23 /* ^^^^ it is recommended that clients move that object into wherever
24    they'd like to have it and delete the self-held copy ("self" being
25    the global window or worker object).  This API does not require the
26    global reference - it is simply installed as a convenience for
27    connecting these bits to other co-developed code before it gets
28    removed from the global namespace.
31   /** Throws a new Error, the message of which is the concatenation
32       all args with a space between each. */
33   const toss = (...args)=>{throw new Error(args.join(' '))};
35   /**
36      Implementing function bindings revealed significant
37      shortcomings in Emscripten's addFunction()/removeFunction()
38      interfaces:
40      https://github.com/emscripten-core/emscripten/issues/17323
42      Until those are resolved, or a suitable replacement can be
43      implemented, our function-binding API will be more limited
44      and/or clumsier to use than initially hoped.
45   */
46   if(!(config.heap instanceof WebAssembly.Memory)
47      && !(config.heap instanceof Function)){
48     toss("config.heap must be WebAssembly.Memory instance or a function.");
49   }
50   ['alloc','dealloc'].forEach(function(k){
51     (config[k] instanceof Function) ||
52       toss("Config option '"+k+"' must be a function.");
53   });
54   const SBF = StructBinderFactory;
55   const heap = (config.heap instanceof Function)
56         ? config.heap : (()=>new Uint8Array(config.heap.buffer)),
57         alloc = config.alloc,
58         dealloc = config.dealloc,
59         log = config.log || console.log.bind(console),
60         memberPrefix = (config.memberPrefix || ""),
61         memberSuffix = (config.memberSuffix || ""),
62         bigIntEnabled = (undefined===config.bigIntEnabled
63                          ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled),
64         BigInt = globalThis['BigInt'],
65         BigInt64Array = globalThis['BigInt64Array'],
66         /* Undocumented (on purpose) config options: */
67         ptrSizeof = config.ptrSizeof || 4,
68         ptrIR = config.ptrIR || 'i32'
69   ;
71   if(!SBF.debugFlags){
72     SBF.__makeDebugFlags = function(deriveFrom=null){
73       /* This is disgustingly overengineered. :/ */
74       if(deriveFrom && deriveFrom.__flags) deriveFrom = deriveFrom.__flags;
75       const f = function f(flags){
76         if(0===arguments.length){
77           return f.__flags;
78         }
79         if(flags<0){
80           delete f.__flags.getter; delete f.__flags.setter;
81           delete f.__flags.alloc; delete f.__flags.dealloc;
82         }else{
83           f.__flags.getter  = 0!==(0x01 & flags);
84           f.__flags.setter  = 0!==(0x02 & flags);
85           f.__flags.alloc   = 0!==(0x04 & flags);
86           f.__flags.dealloc = 0!==(0x08 & flags);
87         }
88         return f._flags;
89       };
90       Object.defineProperty(f,'__flags', {
91         iterable: false, writable: false,
92         value: Object.create(deriveFrom)
93       });
94       if(!deriveFrom) f(0);
95       return f;
96     };
97     SBF.debugFlags = SBF.__makeDebugFlags();
98   }/*static init*/
100   const isLittleEndian = (function() {
101     const buffer = new ArrayBuffer(2);
102     new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
103     // Int16Array uses the platform's endianness.
104     return new Int16Array(buffer)[0] === 256;
105   })();
106   /**
107      Some terms used in the internal docs:
109      StructType: a struct-wrapping class generated by this
110      framework.
111      DEF: struct description object.
112      SIG: struct member signature string.
113   */
115   /** True if SIG s looks like a function signature, else
116       false. */
117   const isFuncSig = (s)=>'('===s[1];
118   /** True if SIG s is-a pointer signature. */
119   const isPtrSig = (s)=>'p'===s || 'P'===s;
120   const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/;
121   const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0];
122   /** Returns the WASM IR form of the Emscripten-conventional letter
123       at SIG s[0]. Throws for an unknown SIG. */
124   const sigIR = function(s){
125     switch(sigLetter(s)){
126         case 'c': case 'C': return 'i8';
127         case 'i': return 'i32';
128         case 'p': case 'P': case 's': return ptrIR;
129         case 'j': return 'i64';
130         case 'f': return 'float';
131         case 'd': return 'double';
132     }
133     toss("Unhandled signature IR:",s);
134   };
136   const affirmBigIntArray = BigInt64Array
137         ? ()=>true : ()=>toss('BigInt64Array is not available.');
138   /** Returns the name of a DataView getter method corresponding
139       to the given SIG. */
140   const sigDVGetter = function(s){
141     switch(sigLetter(s)) {
142         case 'p': case 'P': case 's': {
143           switch(ptrSizeof){
144               case 4: return 'getInt32';
145               case 8: return affirmBigIntArray() && 'getBigInt64';
146           }
147           break;
148         }
149         case 'i': return 'getInt32';
150         case 'c': return 'getInt8';
151         case 'C': return 'getUint8';
152         case 'j': return affirmBigIntArray() && 'getBigInt64';
153         case 'f': return 'getFloat32';
154         case 'd': return 'getFloat64';
155     }
156     toss("Unhandled DataView getter for signature:",s);
157   };
158   /** Returns the name of a DataView setter method corresponding
159       to the given SIG. */
160   const sigDVSetter = function(s){
161     switch(sigLetter(s)){
162         case 'p': case 'P': case 's': {
163           switch(ptrSizeof){
164               case 4: return 'setInt32';
165               case 8: return affirmBigIntArray() && 'setBigInt64';
166           }
167           break;
168         }
169         case 'i': return 'setInt32';
170         case 'c': return 'setInt8';
171         case 'C': return 'setUint8';
172         case 'j': return affirmBigIntArray() && 'setBigInt64';
173         case 'f': return 'setFloat32';
174         case 'd': return 'setFloat64';
175     }
176     toss("Unhandled DataView setter for signature:",s);
177   };
178   /**
179      Returns either Number of BigInt, depending on the given
180      SIG. This constructor is used in property setters to coerce
181      the being-set value to the correct size.
182   */
183   const sigDVSetWrapper = function(s){
184     switch(sigLetter(s)) {
185         case 'i': case 'f': case 'c': case 'C': case 'd': return Number;
186         case 'j': return affirmBigIntArray() && BigInt;
187         case 'p': case 'P': case 's':
188           switch(ptrSizeof){
189               case 4: return Number;
190               case 8: return affirmBigIntArray() && BigInt;
191           }
192           break;
193     }
194     toss("Unhandled DataView set wrapper for signature:",s);
195   };
197   /** Returns the given struct and member name in a form suitable for
198       debugging and error output. */
199   const sPropName = (s,k)=>s+'::'+k;
201   const __propThrowOnSet = function(structName,propName){
202     return ()=>toss(sPropName(structName,propName),"is read-only.");
203   };
205   /**
206      In order to completely hide StructBinder-bound struct
207      pointers from JS code, we store them in a scope-local
208      WeakMap which maps the struct-bound objects to their WASM
209      pointers. The pointers are accessible via
210      boundObject.pointer, which is gated behind an accessor
211      function, but are not exposed anywhere else in the
212      object. The main intention of that is to make it impossible
213      for stale copies to be made.
214   */
215   const __instancePointerMap = new WeakMap();
217   /** Property name for the pointer-is-external marker. */
218   const xPtrPropName = '(pointer-is-external)';
220   /** Frees the obj.pointer memory and clears the pointer
221       property. */
222   const __freeStruct = function(ctor, obj, m){
223     if(!m) m = __instancePointerMap.get(obj);
224     if(m) {
225       __instancePointerMap.delete(obj);
226       if(Array.isArray(obj.ondispose)){
227         let x;
228         while((x = obj.ondispose.shift())){
229           try{
230             if(x instanceof Function) x.call(obj);
231             else if(x instanceof StructType) x.dispose();
232             else if('number' === typeof x) dealloc(x);
233             // else ignore. Strings are permitted to annotate entries
234             // to assist in debugging.
235           }catch(e){
236             console.warn("ondispose() for",ctor.structName,'@',
237                          m,'threw. NOT propagating it.',e);
238           }
239         }
240       }else if(obj.ondispose instanceof Function){
241         try{obj.ondispose()}
242         catch(e){
243           /*do not rethrow: destructors must not throw*/
244           console.warn("ondispose() for",ctor.structName,'@',
245                        m,'threw. NOT propagating it.',e);
246         }
247       }
248       delete obj.ondispose;
249       if(ctor.debugFlags.__flags.dealloc){
250         log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
251             ctor.structName,"instance:",
252             ctor.structInfo.sizeof,"bytes @"+m);
253       }
254       if(!obj[xPtrPropName]) dealloc(m);
255     }
256   };
258   /** Returns a skeleton for a read-only property accessor wrapping
259       value v. */
260   const rop = (v)=>{return {configurable: false, writable: false,
261                             iterable: false, value: v}};
263   /** Allocates obj's memory buffer based on the size defined in
264       ctor.structInfo.sizeof. */
265   const __allocStruct = function(ctor, obj, m){
266     let fill = !m;
267     if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
268     else{
269       m = alloc(ctor.structInfo.sizeof);
270       if(!m) toss("Allocation of",ctor.structName,"structure failed.");
271     }
272     try {
273       if(ctor.debugFlags.__flags.alloc){
274         log("debug.alloc:",(fill?"":"EXTERNAL"),
275             ctor.structName,"instance:",
276             ctor.structInfo.sizeof,"bytes @"+m);
277       }
278       if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
279       __instancePointerMap.set(obj, m);
280     }catch(e){
281       __freeStruct(ctor, obj, m);
282       throw e;
283     }
284   };
285   /** Gets installed as the memoryDump() method of all structs. */
286   const __memoryDump = function(){
287     const p = this.pointer;
288     return p
289       ? new Uint8Array(heap().slice(p, p+this.structInfo.sizeof))
290       : null;
291   };
293   const __memberKey = (k)=>memberPrefix + k + memberSuffix;
294   const __memberKeyProp = rop(__memberKey);
296   /**
297      Looks up a struct member in structInfo.members. Throws if found
298      if tossIfNotFound is true, else returns undefined if not
299      found. The given name may be either the name of the
300      structInfo.members key (faster) or the key as modified by the
301      memberPrefix and memberSuffix settings.
302   */
303   const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
304     let m = structInfo.members[memberName];
305     if(!m && (memberPrefix || memberSuffix)){
306       // Check for a match on members[X].key
307       for(const v of Object.values(structInfo.members)){
308         if(v.key===memberName){ m = v; break; }
309       }
310       if(!m && tossIfNotFound){
311         toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.');
312       }
313     }
314     return m;
315   };
317   /**
318      Uses __lookupMember(obj.structInfo,memberName) to find a member,
319      throwing if not found. Returns its signature, either in this
320      framework's native format or in Emscripten format.
321   */
322   const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
323     if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i');
324     const m = __lookupMember(obj.structInfo, memberName, true);
325     return emscriptenFormat ? f._(m.signature) : m.signature;
326   };
328   const __ptrPropDescriptor = {
329     configurable: false, enumerable: false,
330     get: function(){return __instancePointerMap.get(this)},
331     set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
332     // Reminder: leaving `set` undefined makes assignments
333     // to the property _silently_ do nothing. Current unit tests
334     // rely on it throwing, though.
335   };
337   /** Impl of X.memberKeys() for StructType and struct ctors. */
338   const __structMemberKeys = rop(function(){
339     const a = [];
340     for(const k of Object.keys(this.structInfo.members)){
341       a.push(this.memberKey(k));
342     }
343     return a;
344   });
346   const __utf8Decoder = new TextDecoder('utf-8');
347   const __utf8Encoder = new TextEncoder();
348   /** Internal helper to use in operations which need to distinguish
349       between SharedArrayBuffer heap memory and non-shared heap. */
350   const __SAB = ('undefined'===typeof SharedArrayBuffer)
351         ? function(){} : SharedArrayBuffer;
352   const __utf8Decode = function(arrayBuffer, begin, end){
353     return __utf8Decoder.decode(
354       (arrayBuffer.buffer instanceof __SAB)
355         ? arrayBuffer.slice(begin, end)
356         : arrayBuffer.subarray(begin, end)
357     );
358   };
359   /**
360      Uses __lookupMember() to find the given obj.structInfo key.
361      Returns that member if it is a string, else returns false. If the
362      member is not found, throws if tossIfNotFound is true, else
363      returns false.
364    */
365   const __memberIsString = function(obj,memberName, tossIfNotFound=false){
366     const m = __lookupMember(obj.structInfo, memberName, tossIfNotFound);
367     return (m && 1===m.signature.length && 's'===m.signature[0]) ? m : false;
368   };
370   /**
371      Given a member description object, throws if member.signature is
372      not valid for assigning to or interpretation as a C-style string.
373      It optimistically assumes that any signature of (i,p,s) is
374      C-string compatible.
375   */
376   const __affirmCStringSignature = function(member){
377     if('s'===member.signature) return;
378     toss("Invalid member type signature for C-string value:",
379          JSON.stringify(member));
380   };
382   /**
383      Looks up the given member in obj.structInfo. If it has a
384      signature of 's' then it is assumed to be a C-style UTF-8 string
385      and a decoded copy of the string at its address is returned. If
386      the signature is of any other type, it throws. If an s-type
387      member's address is 0, `null` is returned.
388   */
389   const __memberToJsString = function f(obj,memberName){
390     const m = __lookupMember(obj.structInfo, memberName, true);
391     __affirmCStringSignature(m);
392     const addr = obj[m.key];
393     //log("addr =",addr,memberName,"m =",m);
394     if(!addr) return null;
395     let pos = addr;
396     const mem = heap();
397     for( ; mem[pos]!==0; ++pos ) {
398       //log("mem[",pos,"]",mem[pos]);
399     };
400     //log("addr =",addr,"pos =",pos);
401     return (addr===pos) ? "" : __utf8Decode(mem, addr, pos);
402   };
404   /**
405      Adds value v to obj.ondispose, creating ondispose,
406      or converting it to an array, if needed.
407   */
408   const __addOnDispose = function(obj, ...v){
409     if(obj.ondispose){
410       if(!Array.isArray(obj.ondispose)){
411         obj.ondispose = [obj.ondispose];
412       }
413     }else{
414       obj.ondispose = [];
415     }
416     obj.ondispose.push(...v);
417   };
419   /**
420      Allocates a new UTF-8-encoded, NUL-terminated copy of the given
421      JS string and returns its address relative to heap(). If
422      allocation returns 0 this function throws. Ownership of the
423      memory is transfered to the caller, who must eventually pass it
424      to the configured dealloc() function.
425   */
426   const __allocCString = function(str){
427     const u = __utf8Encoder.encode(str);
428     const mem = alloc(u.length+1);
429     if(!mem) toss("Allocation error while duplicating string:",str);
430     const h = heap();
431     //let i = 0;
432     //for( ; i < u.length; ++i ) h[mem + i] = u[i];
433     h.set(u, mem);
434     h[mem + u.length] = 0;
435     //log("allocCString @",mem," =",u);
436     return mem;
437   };
439   /**
440      Sets the given struct member of obj to a dynamically-allocated,
441      UTF-8-encoded, NUL-terminated copy of str. It is up to the caller
442      to free any prior memory, if appropriate. The newly-allocated
443      string is added to obj.ondispose so will be freed when the object
444      is disposed.
446      The given name may be either the name of the structInfo.members
447      key (faster) or the key as modified by the memberPrefix and
448      memberSuffix settings.
449   */
450   const __setMemberCString = function(obj, memberName, str){
451     const m = __lookupMember(obj.structInfo, memberName, true);
452     __affirmCStringSignature(m);
453     /* Potential TODO: if obj.ondispose contains obj[m.key] then
454        dealloc that value and clear that ondispose entry */
455     const mem = __allocCString(str);
456     obj[m.key] = mem;
457     __addOnDispose(obj, mem);
458     return obj;
459   };
461   /**
462      Prototype for all StructFactory instances (the constructors
463      returned from StructBinder).
464   */
465   const StructType = function ctor(structName, structInfo){
466     if(arguments[2]!==rop){
467       toss("Do not call the StructType constructor",
468            "from client-level code.");
469     }
470     Object.defineProperties(this,{
471       //isA: rop((v)=>v instanceof ctor),
472       structName: rop(structName),
473       structInfo: rop(structInfo)
474     });
475   };
477   /**
478      Properties inherited by struct-type-specific StructType instances
479      and (indirectly) concrete struct-type instances.
480   */
481   StructType.prototype = Object.create(null, {
482     dispose: rop(function(){__freeStruct(this.constructor, this)}),
483     lookupMember: rop(function(memberName, tossIfNotFound=true){
484       return __lookupMember(this.structInfo, memberName, tossIfNotFound);
485     }),
486     memberToJsString: rop(function(memberName){
487       return __memberToJsString(this, memberName);
488     }),
489     memberIsString: rop(function(memberName, tossIfNotFound=true){
490       return __memberIsString(this, memberName, tossIfNotFound);
491     }),
492     memberKey: __memberKeyProp,
493     memberKeys: __structMemberKeys,
494     memberSignature: rop(function(memberName, emscriptenFormat=false){
495       return __memberSignature(this, memberName, emscriptenFormat);
496     }),
497     memoryDump: rop(__memoryDump),
498     pointer: __ptrPropDescriptor,
499     setMemberCString: rop(function(memberName, str){
500       return __setMemberCString(this, memberName, str);
501     })
502   });
503   // Function-type non-Property inherited members 
504   Object.assign(StructType.prototype,{
505     addOnDispose: function(...v){
506       __addOnDispose(this,...v);
507       return this;
508     }
509   });
511   /**
512      "Static" properties for StructType.
513   */
514   Object.defineProperties(StructType, {
515     allocCString: rop(__allocCString),
516     isA: rop((v)=>v instanceof StructType),
517     hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
518     memberKey: __memberKeyProp
519   });
521   const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number));
523   /**
524      Pass this a StructBinder-generated prototype, and the struct
525      member description object. It will define property accessors for
526      proto[memberKey] which read from/write to memory in
527      this.pointer. It modifies descr to make certain downstream
528      operations much simpler.
529   */
530   const makeMemberWrapper = function f(ctor,name, descr){
531     if(!f._){
532       /*cache all available getters/setters/set-wrappers for
533         direct reuse in each accessor function. */
534       f._ = {getters: {}, setters: {}, sw:{}};
535       const a = ['i','c','C','p','P','s','f','d','v()'];
536       if(bigIntEnabled) a.push('j');
537       a.forEach(function(v){
538         //const ir = sigIR(v);
539         f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
540         f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
541         f._.sw[v] = sigDVSetWrapper(v)  /* BigInt or Number ctor to wrap around values
542                                            for conversion */;
543       });
544       const rxSig1 = /^[ipPsjfdcC]$/,
545             rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/;
546       f.sigCheck = function(obj, name, key,sig){
547         if(Object.prototype.hasOwnProperty.call(obj, key)){
548           toss(obj.structName,'already has a property named',key+'.');
549         }
550         rxSig1.test(sig) || rxSig2.test(sig)
551           || toss("Malformed signature for",
552                   sPropName(obj.structName,name)+":",sig);
553       };
554     }
555     const key = ctor.memberKey(name);
556     f.sigCheck(ctor.prototype, name, key, descr.signature);
557     descr.key = key;
558     descr.name = name;
559     const sigGlyph = sigLetter(descr.signature);
560     const xPropName = sPropName(ctor.prototype.structName,key);
561     const dbg = ctor.prototype.debugFlags.__flags;
562     /*
563       TODO?: set prototype of descr to an object which can set/fetch
564       its prefered representation, e.g. conversion to string or mapped
565       function. Advantage: we can avoid doing that via if/else if/else
566       in the get/set methods.
567     */
568     const prop = Object.create(null);
569     prop.configurable = false;
570     prop.enumerable = false;
571     prop.get = function(){
572       if(dbg.getter){
573         log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
574             xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof);
575       }
576       let rc = (
577         new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
578       )[f._.getters[sigGlyph]](0, isLittleEndian);
579       if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
580       return rc;
581     };
582     if(descr.readOnly){
583       prop.set = __propThrowOnSet(ctor.prototype.structName,key);
584     }else{
585       prop.set = function(v){
586         if(dbg.setter){
587           log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
588               xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v);
589         }
590         if(!this.pointer){
591           toss("Cannot set struct property on disposed instance.");
592         }
593         if(null===v) v = 0;
594         else while(!isNumericValue(v)){
595           if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){
596             // It's a struct instance: let's store its pointer value!
597             v = v.pointer || 0;
598             if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
599             break;
600           }
601           toss("Invalid value for pointer-type",xPropName+'.');
602         }
603         (
604           new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
605         )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
606       };
607     }
608     Object.defineProperty(ctor.prototype, key, prop);
609   }/*makeMemberWrapper*/;
610   
611   /**
612      The main factory function which will be returned to the
613      caller.
614   */
615   const StructBinder = function StructBinder(structName, structInfo){
616     if(1===arguments.length){
617       structInfo = structName;
618       structName = structInfo.name;
619     }else if(!structInfo.name){
620       structInfo.name = structName;
621     }
622     if(!structName) toss("Struct name is required.");
623     let lastMember = false;
624     Object.keys(structInfo.members).forEach((k)=>{
625       // Sanity checks of sizeof/offset info...
626       const m = structInfo.members[k];
627       if(!m.sizeof) toss(structName,"member",k,"is missing sizeof.");
628       else if(m.sizeof===1){
629         (m.signature === 'c' || m.signature === 'C') ||
630           toss("Unexpected sizeof==1 member",
631                sPropName(structInfo.name,k),
632                "with signature",m.signature);
633       }else{
634         // sizes and offsets of size-1 members may be odd values, but
635         // others may not.
636         if(0!==(m.sizeof%4)){
637           console.warn("Invalid struct member description =",m,"from",structInfo);
638           toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof);
639         }
640         if(0!==(m.offset%4)){
641           console.warn("Invalid struct member description =",m,"from",structInfo);
642           toss(structName,"member",k,"offset is not aligned. offset="+m.offset);
643         }
644       }
645       if(!lastMember || lastMember.offset < m.offset) lastMember = m;
646     });
647     if(!lastMember) toss("No member property descriptions found.");
648     else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){
649       toss("Invalid struct config:",structName,
650            "max member offset ("+lastMember.offset+") ",
651            "extends past end of struct (sizeof="+structInfo.sizeof+").");
652     }
653     const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags));
654     /** Constructor for the StructCtor. */
655     const StructCtor = function StructCtor(externalMemory){
656       if(!(this instanceof StructCtor)){
657         toss("The",structName,"constructor may only be called via 'new'.");
658       }else if(arguments.length){
659         if(externalMemory!==(externalMemory|0) || externalMemory<=0){
660           toss("Invalid pointer value for",structName,"constructor.");
661         }
662         __allocStruct(StructCtor, this, externalMemory);
663       }else{
664         __allocStruct(StructCtor, this);
665       }
666     };
667     Object.defineProperties(StructCtor,{
668       debugFlags: debugFlags,
669       isA: rop((v)=>v instanceof StructCtor),
670       memberKey: __memberKeyProp,
671       memberKeys: __structMemberKeys,
672       methodInfoForKey: rop(function(mKey){
673       }),
674       structInfo: rop(structInfo),
675       structName: rop(structName)
676     });
677     StructCtor.prototype = new StructType(structName, structInfo, rop);
678     Object.defineProperties(StructCtor.prototype,{
679       debugFlags: debugFlags,
680       constructor: rop(StructCtor)
681       /*if we assign StructCtor.prototype and don't do
682         this then StructCtor!==instance.constructor!*/
683     });
684     Object.keys(structInfo.members).forEach(
685       (name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name])
686     );
687     return StructCtor;
688   };
689   StructBinder.StructType = StructType;
690   StructBinder.config = config;
691   StructBinder.allocCString = __allocCString;
692   if(!StructBinder.debugFlags){
693     StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
694   }
695   return StructBinder;
696 }/*StructBinderFactory*/;