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