0014 — Dyn: one NaN-boxed value, checked conversions, and zinc infer

Context. Section 9: in the gradual profile any and unknown are Dyn (DYN-02); in strict, any is refused and unknown must be narrowed (DYN-01). Conversions out of Dyn are checked (DYN-07, memory safety depends on it), JSON.parse without a type returns a Dyn tree (DYN-09), Dyn sites are reported (DYN-10), zinc infer types JavaScript (DYN-11) and zinc build app.js works directly (DYN-14).

Choice.

  • Representation (runtime/zrt_dyn.h, header-only): the 8-byte nanbox form of DYN-03 on every target — doubles as themselves, undefined/null/booleans/strings/arrays/objects tagged in the negative quiet-NaN space. Strings, any[] (Array<Dyn>) and objects keep their own reference counts; the tag tells Dyn which one to retain. The 4-byte word form is not written: the targets it is meant for (ps1, esp32) default to strict, where Dyn only carries narrowed unknown values.
  • Objects. JSON objects and literals typed any are DynObj: an insertion-ordered Map<String, Dyn> (DYN-05; keys hashed, not interned). Typed objects stored in a Dyn stay themselves; when the program uses Dyn, every class gets generated zrt_get/zrt_set overrides for its fields (checked on write). No per-site inline cache (DYN-04).
  • Conversions (DYN-07) use one plan computed by Sema (dynShape) and shared by both emitters: primitives check the tag; classes check isa (sim: prototype chain by class name); plain interfaces and T[] build a checked copy, identical in C++ and in the sim, so the byte-exact oracle holds. Typed arrays other than any[] cannot become Dyn (a copy would break aliasing: Z9040); functions, maps and sets cannot either (Z9041). Calls through a Dyn are refused (Z9042): narrow first.
  • Errors. A failed conversion or reading a property of null through Dyn panics with the text JS prints for an uncaught TypeError, as array bounds already do (0006). JSON.parse of invalid text throws a catchable Error (it is a @throws library call, RT-05).
  • Operators follow ECMAScript (DYN-06): + concatenates or adds after ToPrimitive, other arithmetic uses ToNumber, == the loose rules, comparisons order strings or numbers.
  • Narrowing (DYN-08): after typeof/instanceof/Array.isArray, Sema takes the checker’s narrowed type and the read becomes a checked conversion into native code.
  • DynFunction (lib/zinc.d.ts): the one way a function crosses a Dyn boundary, for embedders such as zinc:script. At run time it is (args: Dyn[]) => Dyn. A typed function passed where one is expected gets a compiler-built adapter (dynFnAdapter in C++, $z.dynfn in the sim) that converts arguments JavaScript-style: ToNumber then the machine type, String(x), truthiness, unknown as is. It never panics, because the caller may be an untrusted script. Other parameter types are Z9043.
  • Strict profile: any (written or produced by JSON.parse) is Z1006; an unknown used without narrowing in console, templates, truthiness or == is Z1016.
  • zinc infer (compiler/src/infer.ts) types parameters from their call sites and the fields the body reads (an interface per shape, which allocation sites passed to it extend, so fields share positions: DYN-13), variables from their assignments, and object literals per allocation site (p.k = v right after the literal moves into it). What stays untyped becomes any, i.e. Dyn. A .js entry is inferred in memory and compiled (DYN-14).

Not done. word representation, per-site caches, interned keys, JS integer-key ordering in objects, function specialisation (DYN-12), profiling under sim as an inference source, class fields assigned in JS constructors.

Edit this page on GitHub ↗