jz 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6271
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +299 -44
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +892 -32
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +301 -84
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +71 -5
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +979 -54
- package/src/compile/index.js +271 -29
- package/src/compile/infer.js +34 -3
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/narrow.js +543 -15
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1060 -750
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +972 -67
- package/src/prepare/index.js +792 -63
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/src/ctx.js
CHANGED
|
@@ -202,6 +202,20 @@ export function resolveIncludes() {
|
|
|
202
202
|
for (const dep of autoDepsOf(name)) add(dep)
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
+
// Self-host divergence diagnostics (scripts/self.js compileDiag): snapshot
|
|
206
|
+
// what THIS side resolved, so a host-vs-kernel JSON diff names the first
|
|
207
|
+
// differing fact instead of leaving byte-drift archaeology. Near-zero cost
|
|
208
|
+
// when the sink is absent (one truthiness test per call).
|
|
209
|
+
if (ctx.core.diagSink) {
|
|
210
|
+
if (!ctx.core.diagSink.resolve) ctx.core.diagSink.resolve = []
|
|
211
|
+
ctx.core.diagSink.resolve.push({
|
|
212
|
+
includes: [...ctx.core.includes].sort().join(' '),
|
|
213
|
+
autoAlloc: autoDepsOf('__alloc').join(' '),
|
|
214
|
+
memShared: !!ctx.memory.shared,
|
|
215
|
+
memSharedRaw: String(ctx.memory.shared),
|
|
216
|
+
allocOwned: typeof stdlib['__alloc'] === 'string' && stdlib['__alloc'].indexOf('global.get $__heap') >= 0,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
/** Reset all compilation state. Called once per jz() invocation. */
|
|
@@ -298,6 +312,7 @@ export function reset(proto, globals, bridge) {
|
|
|
298
312
|
dynKeyVars: null,
|
|
299
313
|
dynWriteVars: null,
|
|
300
314
|
anyDynKey: false,
|
|
315
|
+
literalWriteKeys: null, // Map<var, Set<key>> — literal-key prop writes per bare-var receiver (plan/index.js)
|
|
301
316
|
}
|
|
302
317
|
|
|
303
318
|
ctx.schema = {
|
|
@@ -321,6 +336,21 @@ export function reset(proto, globals, bridge) {
|
|
|
321
336
|
// returns the slot's kind for `.prop` AST nodes, letting
|
|
322
337
|
// `+`/`===`/method dispatch elide `__is_str_key` checks
|
|
323
338
|
// on numeric properties of known shapes.
|
|
339
|
+
slotTypedCtors: new Map(), // schemaId → Array<ctor-string | null | undefined>
|
|
340
|
+
// undefined: no observation, null: ≥2 distinct
|
|
341
|
+
// ctors, string: every observed value of the slot
|
|
342
|
+
// is that typed-array kind. The elem-width sibling
|
|
343
|
+
// of slotTypes' VAL.TYPED — populated by
|
|
344
|
+
// observeProgramSlots on object literals; read by
|
|
345
|
+
// ctx.schema.slotTypedCtorAt (gated on the prop
|
|
346
|
+
// never being WRITTEN program-wide) so `plan.twRe`
|
|
347
|
+
// keeps its concrete Float64Array kind through
|
|
348
|
+
// field provenance (bench: provenance, fftplan).
|
|
349
|
+
slotIntLevels: new Map(), // schemaId → Array<0|1|2 | undefined> — the int
|
|
350
|
+
// census's WORKING state (type.js lattice:
|
|
351
|
+
// 1 integral, 2 strict-int32). Consumers read
|
|
352
|
+
// the two projections below, published when
|
|
353
|
+
// `analyzeSchemaSlotIntCertain`'s rounds settle.
|
|
324
354
|
slotIntCertain: new Map(), // schemaId → Array<boolean | undefined>
|
|
325
355
|
// undefined: no write observed, true: all observed
|
|
326
356
|
// writes are integer-shaped, false: poisoned by at
|
|
@@ -330,6 +360,25 @@ export function reset(proto, globals, bridge) {
|
|
|
330
360
|
// writes). Read by `ctx.schema.slotIntCertainAt`
|
|
331
361
|
// so Math.floor/toNumF64/intIndexIR consumers fire
|
|
332
362
|
// on `.prop` reads of provably-integer slots.
|
|
363
|
+
slotI32Certain: new Map(), // schemaId → Array<boolean> — the strict (=2)
|
|
364
|
+
// projection: every write is exactly-int32 and
|
|
365
|
+
// never -0, so `i32.trunc_sat_f64_s` of the slot's
|
|
366
|
+
// f64 is an exact round-trip. Read by
|
|
367
|
+
// `ctx.schema.slotI32CertainAt` → raw i32 slot
|
|
368
|
+
// loads (module/core.js) + i32 local typing
|
|
369
|
+
// (type.js exprType '.').
|
|
370
|
+
externSlotSids: new Set(), // schemaId set — sids whose slot VALUES can be
|
|
371
|
+
// written by machinery the write censuses never
|
|
372
|
+
// see: the JSON const emitter / shaped runtime
|
|
373
|
+
// parser (arbitrary runtime JSON into a sid
|
|
374
|
+
// shared with source literals) and spread /
|
|
375
|
+
// Object.assign slot copies across schemas.
|
|
376
|
+
// Populated by plan's markExternSlotSids sweep
|
|
377
|
+
// (+ belts at the emit registration sites); the
|
|
378
|
+
// slot censuses pre-poison these sids and every
|
|
379
|
+
// census reader (slotVT / slotTypedCtor* /
|
|
380
|
+
// slotIntCertainAt / guardedNumSlot stamp)
|
|
381
|
+
// answers null/false for them.
|
|
333
382
|
inlineArray: new Set(), // schemaId set — schemas whose `Array<S>` instances
|
|
334
383
|
// use the `structInline` SRoA carrier (K f64
|
|
335
384
|
// fields inlined per element, no per-row object).
|
|
@@ -548,16 +597,22 @@ export function err(msg, cause) {
|
|
|
548
597
|
throw e
|
|
549
598
|
}
|
|
550
599
|
|
|
600
|
+
// Recursive walk, NOT a stringify replacer — the kernel drops replacers, so
|
|
601
|
+
// in-kernel error nodes would print with bigints/cycles unhandled. Cold path.
|
|
551
602
|
function formatErrorNode(node) {
|
|
552
|
-
const seen = new
|
|
553
|
-
const
|
|
554
|
-
if (typeof
|
|
555
|
-
if (typeof
|
|
556
|
-
if (Array.isArray(
|
|
557
|
-
if (seen.has(
|
|
558
|
-
seen.add(
|
|
603
|
+
const seen = new Set()
|
|
604
|
+
const fmt = (v) => {
|
|
605
|
+
if (typeof v === 'bigint') return `"${v}n"`
|
|
606
|
+
if (typeof v === 'string') return JSON.stringify(v)
|
|
607
|
+
if (Array.isArray(v)) {
|
|
608
|
+
if (seen.has(v)) return '"[Circular]"'
|
|
609
|
+
seen.add(v)
|
|
610
|
+
let s = '['
|
|
611
|
+
for (let i = 0; i < v.length; i++) s += (i ? ',' : '') + fmt(v[i])
|
|
612
|
+
return s + ']'
|
|
559
613
|
}
|
|
560
|
-
return
|
|
561
|
-
}
|
|
614
|
+
return v === undefined ? 'null' : String(v)
|
|
615
|
+
}
|
|
616
|
+
const json = fmt(node)
|
|
562
617
|
return json.length > 2000 ? `${json.slice(0, 2000)}...` : json
|
|
563
618
|
}
|
package/src/helper-counters.js
CHANGED
|
@@ -22,6 +22,12 @@ export const HELPER_COUNTERS = [
|
|
|
22
22
|
['__dyn_get', 'dyn_get'],
|
|
23
23
|
['__dyn_get_t', 'dyn_get_t'],
|
|
24
24
|
['__dyn_get_t_h', 'dyn_get_t_h'],
|
|
25
|
+
['__dyn_get_expr', 'dyn_get_expr'],
|
|
26
|
+
['__dyn_get_expr_t', 'dyn_get_expr_t'],
|
|
27
|
+
['__dyn_get_expr_t_h', 'dyn_get_expr_t_h'],
|
|
28
|
+
['__dyn_get_any', 'dyn_get_any'],
|
|
29
|
+
['__dyn_get_any_t', 'dyn_get_any_t'],
|
|
30
|
+
['__dyn_get_any_t_h', 'dyn_get_any_t_h'],
|
|
25
31
|
['__dyn_set', 'dyn_set'],
|
|
26
32
|
['__arr_grow', 'arr_grow'],
|
|
27
33
|
['__arr_grow_known', 'arr_grow_known'],
|
|
@@ -52,7 +58,7 @@ ${[...COUNTER_BY_HELPER.values()].map(counter => ` (global.set $${counter} (i
|
|
|
52
58
|
|
|
53
59
|
// Bump the helper's counter once on entry. NOTE the semantics: this counts FUNCTION
|
|
54
60
|
// ENTRIES at runtime — a call site that jz inlined or specialized away (fusedRewrite,
|
|
55
|
-
// specializeMkptr
|
|
61
|
+
// specializeMkptr, …) never enters the function and is NOT counted. So
|
|
56
62
|
// the numbers are a relative ranking / lower bound for picking hot helpers, not exact
|
|
57
63
|
// operation counts. Good enough to choose targets; don't read them as call totals.
|
|
58
64
|
export function instrumentHelperCounter(helper, fn) {
|
package/src/ir.js
CHANGED
|
@@ -338,6 +338,13 @@ export const fromI64 = n => {
|
|
|
338
338
|
* At the JS boundary, null and undefined preserve their identity for interop. */
|
|
339
339
|
export const NULL_NAN = atomNanHex(1)
|
|
340
340
|
export const UNDEF_NAN = atomNanHex(2)
|
|
341
|
+
/** Zombie-entry key sentinel for the durable-slot heal (__durable_slot_heal,
|
|
342
|
+
* module/core.js): written over a healed durable dict entry's KEY so probes and
|
|
343
|
+
* enumeration skip it. Unforgeable: ATOM tag with a saturated aux+offset no
|
|
344
|
+
* boxing path ever produces (real atom ids are tiny). Every equality family is
|
|
345
|
+
* deref-free on it: i64.eq mismatches, __str_eq bails on the non-STRING tag,
|
|
346
|
+
* __same_value_zero's atom arm is bit-equality. */
|
|
347
|
+
export const TOMB_NAN = '0x7FF87FFFFFFFFFFF'
|
|
341
348
|
/** Boxed-boolean carrier. `false`/`true` are reserved atoms — materialized only
|
|
342
349
|
* where boolean identity is observed (typeof/String/JSON/host boundary); in
|
|
343
350
|
* branch/arithmetic position booleans stay raw i32/f64 0/1. The atomId encodes
|
|
@@ -354,7 +361,18 @@ export const NULL_IR = ['f64.const', `nan:${NULL_NAN}`]
|
|
|
354
361
|
export const UNDEF_IR = ['f64.const', `nan:${UNDEF_NAN}`]
|
|
355
362
|
export const FALSE_IR = ['f64.const', `nan:${FALSE_NAN}`]
|
|
356
363
|
export const TRUE_IR = ['f64.const', `nan:${TRUE_NAN}`]
|
|
357
|
-
|
|
364
|
+
// .slice() before typed(): NULL_IR is a shared module-level template (like its
|
|
365
|
+
// UNDEF_IR/FALSE_IR/TRUE_IR siblings below, which already copy) — typed() tags
|
|
366
|
+
// `.type` onto the node it's given, so calling it on the shared array directly
|
|
367
|
+
// mutates ONE instance repeatedly. Natively harmless (same idempotent value each
|
|
368
|
+
// time, plain GC heap). In the self-hosted kernel `.type=` is a dynamic-key write
|
|
369
|
+
// that lazily allocates a per-object props sidecar the FIRST time it's called —
|
|
370
|
+
// which happens well after module-init (`__start`), so that sidecar lives ABOVE
|
|
371
|
+
// `__heap_reset` in the bump arena and dangles after `_clear` rewinds it: the
|
|
372
|
+
// NEXT `nullExpr()` call (next compile) reads NULL_IR's now-stale header propsPtr
|
|
373
|
+
// and corrupts memory. A missing `.slice()` this whole time — surfaced only by
|
|
374
|
+
// warm-instance reuse actually re-invoking it post-`_clear`.
|
|
375
|
+
export const nullExpr = () => typed(NULL_IR.slice(), 'f64')
|
|
358
376
|
export const undefExpr = () => typed(UNDEF_IR.slice(), 'f64')
|
|
359
377
|
|
|
360
378
|
/** Materialize the boxed-boolean carrier from a 0/1-valued expression. The atom
|
|
@@ -367,6 +385,17 @@ export function boolBoxIR(e) {
|
|
|
367
385
|
return mkPtrIR(['i32.const', PTR.ATOM], ['i32.or', ['i32.const', BOOL_ATOM_BASE], i], ['i32.const', 0])
|
|
368
386
|
}
|
|
369
387
|
|
|
388
|
+
/** Value-preserving f64 carrier for a value entering an untyped slot — container
|
|
389
|
+
* stores, collection keys/values, dyn-prop writes, generic call args. A boolean
|
|
390
|
+
* keeps its identity as the TRUE/FALSE atom box (typeof/String/strict-eq survive
|
|
391
|
+
* the round-trip); everything else takes the plain asF64 box. Never use in branch
|
|
392
|
+
* or arithmetic position — truthyIR/toNumF64 own those (raw 0/1 there by design).
|
|
393
|
+
* Callers emit(node) ONCE and pass both (emitting per-arm inside a ternary wrapped
|
|
394
|
+
* by different coercions is the self-host-fragile shape — see emit.js 'return'). */
|
|
395
|
+
export function carrierF64(node, emitted) {
|
|
396
|
+
return valTypeOf(node) === VAL.BOOL ? boolBoxIR(emitted) : asF64(emitted)
|
|
397
|
+
}
|
|
398
|
+
|
|
370
399
|
/** Recover the 0/1 i32 value of a known boxed-boolean f64 expression: `aux & 1`. */
|
|
371
400
|
export function unboxBoolIR(f64expr) {
|
|
372
401
|
if (Array.isArray(f64expr) && f64expr[0] === 'f64.const') {
|
|
@@ -693,10 +722,25 @@ export function ptrTypeEq(f64Expr, ptr) {
|
|
|
693
722
|
export function sidecarOverride(objIR, nameIR, onOverride, onFallback) {
|
|
694
723
|
const o = temp('vo'), p = temp('vp')
|
|
695
724
|
inc('__dyn_get_expr', '__ptr_type')
|
|
725
|
+
// Primitive receivers can never carry an own property that shadows a
|
|
726
|
+
// builtin (numbers: no props at all; strings: property writes drop —
|
|
727
|
+
// module/collection.js STRING arms), so the override probe is statically
|
|
728
|
+
// futile for them. One inline number test + tag test skips the 3-frame
|
|
729
|
+
// __dyn_get_expr chain — parser loops calling s.charCodeAt through an
|
|
730
|
+
// unproven receiver were paying it per character (jessie: 1.19M/run at
|
|
731
|
+
// one site). The or's second operand reads garbage tag bits when the
|
|
732
|
+
// first is true (real number) — harmless, the or is already decided.
|
|
696
733
|
return block64(
|
|
697
734
|
['local.set', `$${o}`, asF64(objIR)],
|
|
698
|
-
['local.set', `$${p}`, ['f64
|
|
699
|
-
['
|
|
735
|
+
['local.set', `$${p}`, ['if', ['result', 'f64'],
|
|
736
|
+
['i32.and',
|
|
737
|
+
['f64.ne', ['local.get', `$${o}`], ['local.get', `$${o}`]],
|
|
738
|
+
['i64.ne',
|
|
739
|
+
['i64.and', ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['i64.const', i64Hex(BigInt(LAYOUT.TAG_MASK) << BigInt(LAYOUT.TAG_SHIFT))]],
|
|
740
|
+
['i64.const', i64Hex(BigInt(PTR.STRING) << BigInt(LAYOUT.TAG_SHIFT))]]],
|
|
741
|
+
['then', ['f64.reinterpret_i64',
|
|
742
|
+
['call', '$__dyn_get_expr', ['i64.reinterpret_f64', ['local.get', `$${o}`]], nameIR]]],
|
|
743
|
+
['else', undefExpr()]]],
|
|
700
744
|
['if', ['result', 'f64'],
|
|
701
745
|
ptrTypeEq(['local.get', `$${p}`], PTR.CLOSURE),
|
|
702
746
|
['then', onOverride(p, o)],
|
|
@@ -774,7 +818,18 @@ function toPrimitiveChain(node, v, order) {
|
|
|
774
818
|
return typed(['block', blk, ...body], 'i64')
|
|
775
819
|
}
|
|
776
820
|
|
|
777
|
-
|
|
821
|
+
/** Structural clone of an already-emitted IR node. Two DIFFERENT positions in the
|
|
822
|
+
* final tree must never share one node object: a later pass (CSE, peephole, local
|
|
823
|
+
* renumbering) that walks the tree and mutates/tags a node in place only sees ONE
|
|
824
|
+
* of the two logical occurrences and silently mutates both — the exact "IR-aliasing"
|
|
825
|
+
* hazard multiple emit sites must avoid when a value is READ into more than one
|
|
826
|
+
* branch of an if/else or more than one argument position. Caller's responsibility:
|
|
827
|
+
* the node must be side-effect-free to duplicate (a local/cell read, not a call) —
|
|
828
|
+
* see coerceNullishToNum below for the established idiom. Does not preserve `.type`/
|
|
829
|
+
* `.ptrKind` (a plain array map): every consumer of a cloned node already re-derives
|
|
830
|
+
* those from context (`node?.type ? node : typed(node, 'f64')`), so this only needs
|
|
831
|
+
* to reproduce the value-computing shape, not its cached metadata. */
|
|
832
|
+
export const cloneIR = (n) => Array.isArray(n) ? n.map(cloneIR) : n
|
|
778
833
|
|
|
779
834
|
/** ToNumber for a runtime value that may carry a nullish sentinel: null→+0, undefined→NaN,
|
|
780
835
|
* anything else → itself. `valIR` must be side-effect-free (a local read) — it is duplicated,
|
|
@@ -839,6 +894,22 @@ export function toNumF64(node, v) {
|
|
|
839
894
|
if (Array.isArray(node) && node[0] === '.' && typeof node[1] === 'string' && typeof node[2] === 'string') {
|
|
840
895
|
if (ctx.schema.slotIntCertainAt?.(node[1], node[2]) === true) return asF64(v)
|
|
841
896
|
}
|
|
897
|
+
// Guarded schema-slot read whose ONE schema censuses the slot NUMBER
|
|
898
|
+
// (emitSchemaSlotGuarded's stamp): SINK the coercion into the arms — the
|
|
899
|
+
// guard-HIT raw load is already a plain number; only the dyn-miss arm pays
|
|
900
|
+
// __to_num. The shapes-dispatch pattern (`measure(o)` over 8 schemas) drops
|
|
901
|
+
// a per-field ToNumber call from every hot read this way.
|
|
902
|
+
if (v.guardedNumSlot && Array.isArray(v) && v[0] === 'if') {
|
|
903
|
+
const out = v.map((c, i) => {
|
|
904
|
+
if (Array.isArray(c) && c[0] === 'else' && c.length === 2) {
|
|
905
|
+
if (!ctx.core.stdlib['__to_num']) return c
|
|
906
|
+
inc('__to_num')
|
|
907
|
+
return ['else', typed(['call', '$__to_num', asI64(typed(c[1], 'f64'))], 'f64')]
|
|
908
|
+
}
|
|
909
|
+
return c
|
|
910
|
+
})
|
|
911
|
+
return typed(out, 'f64')
|
|
912
|
+
}
|
|
842
913
|
// IR-level shapes that produce real f64 numbers (never NaN-boxed pointers):
|
|
843
914
|
// i32→f64 conversions, stdlib clock helper, length/ptr helpers.
|
|
844
915
|
// Skip the __to_num call wrapper for these — they always return plain f64.
|
|
@@ -880,6 +951,20 @@ export function toNumF64(node, v) {
|
|
|
880
951
|
return f
|
|
881
952
|
}
|
|
882
953
|
inc('__to_num')
|
|
954
|
+
// Inline number fast path (the engines' move): every non-NaN f64 IS its own
|
|
955
|
+
// ToNumber — only NaN bit patterns (all NaN-boxed pointers + sentinels, plus
|
|
956
|
+
// genuine NaN) take the call. One self-compare against a call per site; the
|
|
957
|
+
// dictionary-count idiom (`o[k] | 0` on a number-or-undefined slot) drops a
|
|
958
|
+
// per-token call this way. Optimize-gated: the O0 tier keeps the compact call.
|
|
959
|
+
if (ctx.transform.optimize) {
|
|
960
|
+
const t = temp('tnum')
|
|
961
|
+
return typed(['block', ['result', 'f64'],
|
|
962
|
+
['local.set', `$${t}`, asF64(v)],
|
|
963
|
+
['if', ['result', 'f64'],
|
|
964
|
+
['f64.eq', ['local.get', `$${t}`], ['local.get', `$${t}`]],
|
|
965
|
+
['then', ['local.get', `$${t}`]],
|
|
966
|
+
['else', ['call', '$__to_num', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]], 'f64')
|
|
967
|
+
}
|
|
883
968
|
return typed(['call', '$__to_num', asI64(v)], 'f64')
|
|
884
969
|
}
|
|
885
970
|
|
|
@@ -1073,12 +1158,24 @@ export function boxedAddr(name) {
|
|
|
1073
1158
|
// construction is a map hit and every downstream comparison is bit-eq.
|
|
1074
1159
|
// Module-level: in-kernel it lives per instance (arena strings are immortal),
|
|
1075
1160
|
// natively it is a plain cross-compile cache; the name vocabulary is bounded.
|
|
1076
|
-
|
|
1161
|
+
let DOLLAR = new Map()
|
|
1077
1162
|
export const dollar = (name) => {
|
|
1078
1163
|
let v = DOLLAR.get(name)
|
|
1079
1164
|
if (v === undefined) { v = '$' + name; DOLLAR.set(name, v) }
|
|
1080
1165
|
return v
|
|
1081
1166
|
}
|
|
1167
|
+
// Self-host-only: DOLLAR's keys/values are both arena strings built during compile
|
|
1168
|
+
// (the `name`s come from the source being compiled) AND the Map's own backing
|
|
1169
|
+
// table is itself an arena allocation. Natively the arena is the host GC heap, so
|
|
1170
|
+
// stale entries (or a `.clear()`) are enough — the old backing store just becomes
|
|
1171
|
+
// garbage. In-kernel the arena is a bump allocator that `_clear` rewinds between
|
|
1172
|
+
// compiles: `.clear()` alone leaves the Map pointing at its OLD backing table,
|
|
1173
|
+
// which a later allocation can overwrite while still "owned" by DOLLAR (as opposed
|
|
1174
|
+
// to the entries becoming merely unreachable) — so a warm-instance compile loop
|
|
1175
|
+
// must swap in a FRESH Map (not just empty this one) after every `_clear`
|
|
1176
|
+
// (see scripts/self.js setupSelf). Verified empirically: `.clear()` alone still
|
|
1177
|
+
// trapped `__hash_set_local` on the 2nd compile of a warm instance.
|
|
1178
|
+
export const clearDollar = () => { DOLLAR = new Map() }
|
|
1082
1179
|
|
|
1083
1180
|
/** Read variable value: boxed → f64.load, global → global.get, local → local.get.
|
|
1084
1181
|
* Unboxed pointer locals (repOf(name).ptrKind) tag the returned node with `.ptrKind`
|
|
@@ -1098,6 +1195,11 @@ export function readVar(name) {
|
|
|
1098
1195
|
// — an f64 consumer widens the i32.const via convert, which folds back to f64.const.
|
|
1099
1196
|
const ci = ctx.scope.constInts?.get?.(name)
|
|
1100
1197
|
if (ci != null && isI32(ci)) return typed(['i32.const', ci], 'i32')
|
|
1198
|
+
// Fractional pre-folded const (`const nv = 2610/16384`): same immutability
|
|
1199
|
+
// argument as the integer arm — substitute the literal so downstream
|
|
1200
|
+
// compile-time folds (constant-exponent pow, ranges) see the value.
|
|
1201
|
+
const cn = ctx.scope.constNums?.get?.(name)
|
|
1202
|
+
if (cn != null) { const node = typed(['f64.const', cn], 'f64'); node.valKind = VAL.NUMBER; return node }
|
|
1101
1203
|
const gt = ctx.scope.globalTypes.get(name) || 'f64'
|
|
1102
1204
|
const node = typed(['global.get', dollar(name)], gt)
|
|
1103
1205
|
const grep = repOfGlobal(name)
|
|
@@ -1171,6 +1273,12 @@ export function writeVar(name, valIR, void_) {
|
|
|
1171
1273
|
['global.set', dollar(name), ['local.get', `$${t}`]],
|
|
1172
1274
|
['local.get', `$${t}`]], gt)
|
|
1173
1275
|
}
|
|
1276
|
+
// NOTE: an unknown name is NOT minted here — a write-legalized binding lets a
|
|
1277
|
+
// later-emitted read of the same undeclared name resolve to 0 instead of
|
|
1278
|
+
// rejecting (test262 pins the ReferenceError: `x = x`, `x++`, `x + (x = 1)`
|
|
1279
|
+
// — 50 in-scope failures from an unconditional mint). The one structural
|
|
1280
|
+
// write-only binder, a bare undeclared `for (k in o)` head, is declared at
|
|
1281
|
+
// its prepare lowering instead.
|
|
1174
1282
|
const t = ctx.func.locals.get(name) || 'f64'
|
|
1175
1283
|
const ptrKind = repOf(name)?.ptrKind
|
|
1176
1284
|
let coerced
|
package/src/kind-traits.js
CHANGED
|
@@ -49,11 +49,61 @@ export const CALLEE_VAL = {
|
|
|
49
49
|
'BigInt.asUintN': VAL.BIGINT,
|
|
50
50
|
'performance.now': VAL.NUMBER,
|
|
51
51
|
'Date.now': VAL.NUMBER,
|
|
52
|
+
// ES2024 groupBy: a dictionary (HASH) keyed by ToPropertyKey strings, and a
|
|
53
|
+
// real Map keyed by SameValueZero — result reads dispatch to the right table.
|
|
54
|
+
'Object.groupBy': VAL.HASH,
|
|
55
|
+
'Map.groupBy': VAL.MAP,
|
|
56
|
+
'RegExp.escape': VAL.STRING,
|
|
57
|
+
// Predicate builtins return booleans (raw 0/1 carrier) — same classification
|
|
58
|
+
// BOOL_METHODS gives includes/some/every: without it `isFinite(x) === false`
|
|
59
|
+
// falls to the unknown-identity path and bit-compares 0.0 against the FALSE
|
|
60
|
+
// atom (always false). typeof/String/JSON/host boundary observe it faithfully.
|
|
61
|
+
isNaN: VAL.BOOL,
|
|
62
|
+
isFinite: VAL.BOOL,
|
|
63
|
+
'Array.isArray': VAL.BOOL,
|
|
64
|
+
'Number.isNaN': VAL.BOOL,
|
|
65
|
+
'Number.isFinite': VAL.BOOL,
|
|
66
|
+
'Number.isInteger': VAL.BOOL,
|
|
67
|
+
'Number.isSafeInteger': VAL.BOOL,
|
|
68
|
+
'Object.is': VAL.BOOL,
|
|
69
|
+
'Object.hasOwn': VAL.BOOL,
|
|
70
|
+
'Object.isFrozen': VAL.BOOL,
|
|
71
|
+
'Object.isSealed': VAL.BOOL,
|
|
72
|
+
'Object.isExtensible': VAL.BOOL,
|
|
73
|
+
'ArrayBuffer.isView': VAL.BOOL,
|
|
74
|
+
// jzify-synthesized `instanceof Map/Set/TypedArray` predicates (autoload
|
|
75
|
+
// CALL_MODULES) — same boolean-carrier classification as the ops they lower.
|
|
76
|
+
__is_map: VAL.BOOL,
|
|
77
|
+
__is_set: VAL.BOOL,
|
|
78
|
+
__is_typed: VAL.BOOL,
|
|
79
|
+
// Atomics (module/atomics.js): wait → result string, isLockFree → boolean,
|
|
80
|
+
// notify → count. Value ops resolve by RECEIVER width in calleeValType below
|
|
81
|
+
// (Int32Array → NUMBER, BigInt64Array → BIGINT).
|
|
82
|
+
'Atomics.notify': VAL.NUMBER,
|
|
83
|
+
'Atomics.wait': VAL.STRING, 'Atomics.isLockFree': VAL.BOOL,
|
|
52
84
|
}
|
|
53
85
|
|
|
86
|
+
const ATOMICS_VALUE_OPS = new Set(['Atomics.load', 'Atomics.store', 'Atomics.add',
|
|
87
|
+
'Atomics.sub', 'Atomics.and', 'Atomics.or', 'Atomics.xor', 'Atomics.exchange',
|
|
88
|
+
'Atomics.compareExchange'])
|
|
89
|
+
|
|
54
90
|
export function calleeValType(callee, _args, ctx) {
|
|
55
91
|
if (typeof callee !== 'string') return null
|
|
56
92
|
if (callee in CALLEE_VAL) return CALLEE_VAL[callee]
|
|
93
|
+
// Atomics value ops: the result kind follows the receiver's element width —
|
|
94
|
+
// a proven BigInt64Array receiver yields BIGINT (raw i64 carrier), else NUMBER.
|
|
95
|
+
if (ATOMICS_VALUE_OPS.has(callee)) {
|
|
96
|
+
// _args is the '()' node tail: [callee, argsNode] — the receiver is the
|
|
97
|
+
// first real argument (unwrap a ','-group).
|
|
98
|
+
const a1 = _args?.[1]
|
|
99
|
+
const arr = Array.isArray(a1) && a1[0] === ',' ? a1[1] : a1
|
|
100
|
+
const ctor = Array.isArray(arr) && arr[0] === 'new' ? 'new.' + arr[1]
|
|
101
|
+
: typeof arr === 'string'
|
|
102
|
+
? (ctx.func?.localTypedElemsOverlay?.get(arr) ?? ctx.types?.typedElem?.get(arr)
|
|
103
|
+
?? ctx.func?.localReps?.get(arr)?.typedCtor ?? ctx.scope?.globalTypedElem?.get(arr))
|
|
104
|
+
: null
|
|
105
|
+
return ctor === 'new.BigInt64Array' || ctor === 'new.BigInt64Array.view' ? VAL.BIGINT : VAL.NUMBER
|
|
106
|
+
}
|
|
57
107
|
if (callee.startsWith('new.')) return VAL.TYPED
|
|
58
108
|
if (callee.startsWith('math.')) return VAL.NUMBER
|
|
59
109
|
const hostVT = ctx.module.hostImportValTypes?.get(callee)
|
|
@@ -89,8 +139,16 @@ export function methodValType(method, obj, objType, ctx) {
|
|
|
89
139
|
// Mirrors the objType guard on map/filter/slice/concat.
|
|
90
140
|
if (method === 'add') return objType === VAL.SET ? VAL.SET : null
|
|
91
141
|
if (method === 'set') return objType === VAL.MAP ? VAL.MAP : null
|
|
142
|
+
// jz's valueOf is a receiver passthrough (module/string.js `.valueOf`), so the
|
|
143
|
+
// result kind IS the receiver kind — `Boolean(x).valueOf() === true` needs it.
|
|
144
|
+
if (method === 'valueOf') return objType ?? null
|
|
92
145
|
if (BOOL_METHODS.has(method)) return VAL.BOOL
|
|
93
146
|
if ((method === 'has' || method === 'delete') && (objType === VAL.MAP || objType === VAL.SET)) return VAL.BOOL
|
|
147
|
+
// ES2025 Set algebra — proven-SET receiver only (same guard rationale as add/set).
|
|
148
|
+
if ((method === 'union' || method === 'intersection' || method === 'difference' ||
|
|
149
|
+
method === 'symmetricDifference') && objType === VAL.SET) return VAL.SET
|
|
150
|
+
if ((method === 'isSubsetOf' || method === 'isSupersetOf' || method === 'isDisjointFrom') &&
|
|
151
|
+
objType === VAL.SET) return VAL.BOOL
|
|
94
152
|
if (STRING_METHODS.has(method)) return VAL.STRING
|
|
95
153
|
if (NUMBER_METHODS.has(method)) return VAL.NUMBER
|
|
96
154
|
if (method === 'split') return VAL.ARRAY
|
|
@@ -98,9 +156,14 @@ export function methodValType(method, obj, objType, ctx) {
|
|
|
98
156
|
if (objType === VAL.STRING || objType === VAL.ARRAY || objType === VAL.TYPED) return objType
|
|
99
157
|
return null
|
|
100
158
|
}
|
|
101
|
-
// .subarray
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
// .subarray returns a typed-array view (no plain-array analog). The ES2023
|
|
160
|
+
// change-by-copy trio return a fresh value of the RECEIVER's kind: a typed
|
|
161
|
+
// array from a typed receiver, a plain array from a plain-array receiver.
|
|
162
|
+
if (method === 'subarray') return objType === VAL.TYPED ? VAL.TYPED : null
|
|
163
|
+
if (method === 'toReversed' || method === 'toSorted' || method === 'with')
|
|
164
|
+
return objType === VAL.TYPED ? VAL.TYPED : objType === VAL.ARRAY ? VAL.ARRAY : null
|
|
165
|
+
// copyWithin mutates and returns the receiver.
|
|
166
|
+
if (method === 'copyWithin') return objType === VAL.TYPED || objType === VAL.ARRAY ? objType : null
|
|
104
167
|
return null
|
|
105
168
|
}
|
|
106
169
|
|
package/src/kind.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ctx } from './ctx.js'
|
|
10
|
-
import { VAL, lookupValType } from './reps.js'
|
|
10
|
+
import { VAL, lookupValType, repOf } from './reps.js'
|
|
11
11
|
import { intLiteralValue, staticIndexKey } from './static.js'
|
|
12
12
|
import {
|
|
13
13
|
BOOL_OPS, NUMERIC_BINARY_OPS, NUMERIC_UNARY_OPS, COMPOUND_NUMERIC_OPS,
|
|
@@ -138,17 +138,46 @@ VT['?:'] = (args) => {
|
|
|
138
138
|
if (truthy != null) return valTypeOf(truthy ? args[1] : args[2])
|
|
139
139
|
const ta = valTypeOf(args[1]), tb = valTypeOf(args[2])
|
|
140
140
|
if (ta && ta === tb) return ta
|
|
141
|
-
// A boolean branch coerces to 0/1 in
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// polymorphic string-concat dispatch
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
// A boolean branch coerces to 0/1 in NUMERIC context: when the other branch is a
|
|
142
|
+
// known NUMBER, the conditional carries NUMBER — the raw 0/1 bool carrier IS its
|
|
143
|
+
// ToNumber image, so the claim is benign and keeps `num + (cond ? num : num>k)`
|
|
144
|
+
// off the polymorphic string-concat dispatch (which pins the whole number→string
|
|
145
|
+
// formatter — __str_concat → __to_str → __static_str, a pure-int program
|
|
146
|
+
// ballooning 1 → ~19 funcs; see test/wat-invariants.js, .work/todo.md).
|
|
147
|
+
// Any OTHER mix is null: both ternary arms are "the value", so claiming the
|
|
148
|
+
// non-bool arm's kind would let strict-eq's differing-class fold constant-fold
|
|
149
|
+
// `x === true` on a value that IS sometimes a boolean (watr's `i ? true :
|
|
150
|
+
// [from,len]` rec marker); the bool arm materializes as its atom at emit
|
|
151
|
+
// (emit.js '?:') and stays observable. (&&/||/?? below keep the full carry —
|
|
152
|
+
// there the bool side is a GUARD whose value surfaces only when falsy, and the
|
|
153
|
+
// carry is what types `cond && typedArr` guarded-use idioms.)
|
|
154
|
+
if (ta === VAL.BOOL && tb && tb !== VAL.BOOL) return tb === VAL.NUMBER ? VAL.NUMBER : null
|
|
155
|
+
if (tb === VAL.BOOL && ta && ta !== VAL.BOOL) return ta === VAL.NUMBER ? VAL.NUMBER : null
|
|
156
|
+
// BIGINT arm + nullish-LITERAL arm carries BIGINT. BIGINT is the one kind
|
|
157
|
+
// with NO runtime tag — raw i64 bits ride the f64 slot, indistinguishable
|
|
158
|
+
// from a number — so a dispatcher that loses the static kind has no runtime
|
|
159
|
+
// fork to fall back on: tryRuntimeStringFork's non-NaN arm claimed
|
|
160
|
+
// `(c ? BigInt(x) : null).toString(16)` as NUMBER and formatted the bits as
|
|
161
|
+
// a denormal ("0.000…"), watr's `cb ? BigInt(cb.value) : null` folder shape.
|
|
162
|
+
// Sound where the bool-arm carry above is not: a nullish receiver is
|
|
163
|
+
// TypeError-class in JS (no method table to mis-pick), the nullish arm
|
|
164
|
+
// materializes as its ATOM whose bits the sentinel compare still matches at
|
|
165
|
+
// runtime, and the decl-site mayBeNullish flag (analyze.js) plus
|
|
166
|
+
// nullableOperand (emit.js) keep `x == null` folds honest — narrow.js
|
|
167
|
+
// re-derives that nullability across call boundaries for BIGINT params.
|
|
168
|
+
// Tagged kinds stay null here on purpose: their runtime fork handles the
|
|
169
|
+
// mix soundly and their eq-folds stay maximally live.
|
|
170
|
+
if (ta === VAL.BIGINT && nullishArm(args[2])) return VAL.BIGINT
|
|
171
|
+
if (tb === VAL.BIGINT && nullishArm(args[1])) return VAL.BIGINT
|
|
149
172
|
return null
|
|
150
173
|
}
|
|
151
174
|
|
|
175
|
+
// AST nullish literal — mirrors ir.js isNullishLit ([null,null] = null literal,
|
|
176
|
+
// [] = undefined) plus the bare `undefined` name form recordGlobalRep accepts;
|
|
177
|
+
// local copy because ir.js already imports valTypeOf from here (cycle).
|
|
178
|
+
const nullishArm = (n) => n === 'undefined' ||
|
|
179
|
+
(Array.isArray(n) && ((n.length === 2 && n[0] == null && n[1] == null) || n.length === 0))
|
|
180
|
+
|
|
152
181
|
// Value-preserving logical: `&&`/`||` return one of their operands.
|
|
153
182
|
// When both sides share a type, return it. When one side is boolean
|
|
154
183
|
// (a condition/guard) and the other has a known non-boolean type,
|
|
@@ -175,6 +204,18 @@ VT['[]'] = (args) => {
|
|
|
175
204
|
// element type. Returning a numeric elem type here would let `a[-1] === undefined`
|
|
176
205
|
// fold to false (a NUMBER can't be undefined), silently dropping the guard.
|
|
177
206
|
{ const li = intLiteralValue(args[1]); if (li != null && li < 0) return null }
|
|
207
|
+
// A non-numeric STRING-literal key is a PROPERTY read, not an element read:
|
|
208
|
+
// on arrays/typed arrays it yields undefined (or a builtin method), never the
|
|
209
|
+
// element kind. Typing it by elem let `a['@@iterator'] != null` fold TRUE on
|
|
210
|
+
// a known array — the drain/GetIterator guards then called undefined (table
|
|
211
|
+
// OOB). Canonical numeric strings ('0','1',…) DO address elements
|
|
212
|
+
// (ToPropertyKey) and keep the elem typing below.
|
|
213
|
+
{
|
|
214
|
+
const k = args[1]
|
|
215
|
+
const lit = Array.isArray(k) && k.length === 2 && k[0] == null ? k[1]
|
|
216
|
+
: Array.isArray(k) && k[0] === 'str' ? k[1] : undefined
|
|
217
|
+
if (typeof lit === 'string' && !/^(0|[1-9][0-9]*)$/.test(lit)) return null
|
|
218
|
+
}
|
|
178
219
|
// SRoA flat-array slot read: `a[k]` (static index) where `a` dissolved into
|
|
179
220
|
// scalar `a#i` locals (scanFlatObjects). A write-once slot's value-type is its
|
|
180
221
|
// element literal's — same numeric-binding as the `VT['.']` object case, so
|
|
@@ -191,6 +232,11 @@ VT['[]'] = (args) => {
|
|
|
191
232
|
}
|
|
192
233
|
// Indexed read on a known typed-array receiver yields Number except for
|
|
193
234
|
// BigInt64Array/BigUint64Array, whose i64 carriers must stay BigInt-typed.
|
|
235
|
+
// An UNPROVEN index can read past the end (= undefined per spec), but the undef
|
|
236
|
+
// box is a NaN bit-pattern, so it COINCIDES with ToNumber(undefined) through
|
|
237
|
+
// every numeric path — the NUMBER claim stays sound for dispatch (numeric arms,
|
|
238
|
+
// the vectorizer). Only identity observations diverge; those folds consult
|
|
239
|
+
// typedReadMaybeOob below and keep the runtime compare.
|
|
194
240
|
if (typeof args[0] === 'string' && lookupValType(args[0]) === VAL.TYPED)
|
|
195
241
|
return typedCtorElemValType(ctx.types.typedElem?.get(args[0])) || VAL.NUMBER
|
|
196
242
|
// Indexed read on a STRING returns a 1-char string (SSO at runtime).
|
|
@@ -260,10 +306,25 @@ VT['.'] = (args) => {
|
|
|
260
306
|
// OBJECT `.prop` propagation: when the receiver chain roots at a binding
|
|
261
307
|
// sourced from `JSON.parse(stringConst)`, walk the shape tree to recover the
|
|
262
308
|
// child's val-type. Generic for any compile-time-known JSON literal.
|
|
309
|
+
// The shape's per-prop kind is a DECL-SITE fact — writes can invalidate it:
|
|
310
|
+
// - a sid-bound receiver whose schema declares the prop: the slot census
|
|
311
|
+
// above (slotVT) is authoritative — it saw every resolvable write and
|
|
312
|
+
// answered null on clash/poison, so the stale decl kind must not revive
|
|
313
|
+
// (`o.x = 'oops'; o.x + 1` skipped concat dispatch — live miscompile);
|
|
314
|
+
// - otherwise, the write-hazard sets cover unresolvable-receiver writes
|
|
315
|
+
// that could reach this object through an alias.
|
|
263
316
|
const sh = shapeOf(args[0])
|
|
264
317
|
if (sh?.val === VAL.OBJECT || sh?.val === VAL.HASH) {
|
|
265
318
|
const child = sh.props[args[1]]
|
|
266
|
-
if (child)
|
|
319
|
+
if (child) {
|
|
320
|
+
const sid = typeof args[0] === 'string'
|
|
321
|
+
? (repOf(args[0])?.schemaId ?? ctx.schema?.vars?.get(args[0])) : null
|
|
322
|
+
if (sid != null && ctx.schema?.list?.[sid]?.indexOf(args[1]) >= 0) return null
|
|
323
|
+
const hz = ctx.schema?.slotWriteHazards
|
|
324
|
+
if (hz && (hz.all || hz.props.has(args[1]) ||
|
|
325
|
+
(hz.numeric && /^(0|[1-9][0-9]*)$/.test(args[1])))) return null
|
|
326
|
+
return child.val
|
|
327
|
+
}
|
|
267
328
|
}
|
|
268
329
|
// Built-in property on a known sized kind — `.length` on STRING/ARRAY/TYPED,
|
|
269
330
|
// `.size` on SET/MAP, `.byteLength`/`.byteOffset` on TYPED/BUFFER. These are
|
|
@@ -339,7 +400,11 @@ VT['()'] = (args) => {
|
|
|
339
400
|
if (c === '{') return VAL.OBJECT
|
|
340
401
|
if (c === '[') return VAL.ARRAY
|
|
341
402
|
if (c === '"') return VAL.STRING
|
|
342
|
-
|
|
403
|
+
// 't'/'f' → boolean: the parser mints the TRUE/FALSE atom (module/json.js
|
|
404
|
+
// litCase), NOT a raw 0/1 — claiming NUMBER here would let numeric fast
|
|
405
|
+
// paths raw-add the atom bits.
|
|
406
|
+
if (c === 't' || c === 'f') return VAL.BOOL
|
|
407
|
+
if (c === '-' || (c >= '0' && c <= '9')) return VAL.NUMBER
|
|
343
408
|
}
|
|
344
409
|
} else {
|
|
345
410
|
const vt = calleeValType(callee, args, ctx)
|
|
@@ -487,7 +552,14 @@ export function shapeOf(expr) {
|
|
|
487
552
|
}
|
|
488
553
|
if (op === '[]' && args.length === 2) {
|
|
489
554
|
const parent = shapeOf(args[0])
|
|
490
|
-
if (parent?.val === VAL.ARRAY)
|
|
555
|
+
if (parent?.val === VAL.ARRAY) {
|
|
556
|
+
// non-numeric string-literal key = PROPERTY read, not an element (see VT['[]'])
|
|
557
|
+
const k = args[1]
|
|
558
|
+
const lit = Array.isArray(k) && k.length === 2 && k[0] == null ? k[1]
|
|
559
|
+
: Array.isArray(k) && k[0] === 'str' ? k[1] : undefined
|
|
560
|
+
if (typeof lit === 'string' && !/^(0|[1-9][0-9]*)$/.test(lit)) return null
|
|
561
|
+
return parent.elem || null
|
|
562
|
+
}
|
|
491
563
|
}
|
|
492
564
|
return null
|
|
493
565
|
}
|
package/src/op-policy.js
CHANGED
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
/** Ops prepare rejects when they appear in the AST (handler or identifier). */
|
|
12
12
|
export const REJECT_OPS = {
|
|
13
|
-
async: 'async/
|
|
14
|
-
await: '
|
|
13
|
+
async: 'this `async` shape needs the jzify lowering (async fns/arrows are supported in default mode; async methods are not yet)',
|
|
14
|
+
await: '`await` outside an async function (or in a shape the jzify lowering does not cover)',
|
|
15
15
|
class: 'class not supported: use object literals',
|
|
16
|
-
yield: '
|
|
16
|
+
yield: 'yield outside a generator body (or in an unsupported position — see jzify/generators.js v1 surface)',
|
|
17
|
+
'yield*': 'yield* not supported yet: loop over the inner iterator and yield each value',
|
|
18
|
+
'new.target': '`new.target` not supported: no constructor reflection',
|
|
19
|
+
using: '`using` declarations need the jzify lowering (try/finally + [Symbol.dispose]) — not in the strict canonical subset',
|
|
17
20
|
instanceof: 'instanceof not supported: use typeof',
|
|
18
21
|
with: '`with` not supported: deprecated',
|
|
19
22
|
':': 'labeled statements not supported',
|
|
@@ -48,7 +51,7 @@ export const JZIFY_CLASS_ERRORS = {
|
|
|
48
51
|
computedField: 'non-constant computed/destructured class fields are not supported',
|
|
49
52
|
computedStaticMember: 'non-constant computed static class member names are not supported',
|
|
50
53
|
accessor: 'class getters/setters are not supported — jz objects have no accessors',
|
|
51
|
-
staticMember: '`static`
|
|
54
|
+
staticMember: 'this `static` member shape is not supported (static fields, methods and blocks are)',
|
|
52
55
|
superProp: '`super` property access is not supported yet',
|
|
53
56
|
}
|
|
54
57
|
|