jz 0.9.0 → 0.9.2

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/module/console.js CHANGED
@@ -194,6 +194,8 @@ const setupWasi = (ctx) => {
194
194
  makeConsole('log', 1)
195
195
  makeConsole('warn', 2)
196
196
  makeConsole('error', 2)
197
+ makeConsole('info', 1) // spec/Node: info & debug are log-level -> stdout
198
+ makeConsole('debug', 1)
197
199
 
198
200
  const needClock = () => hostImport('wasi_snapshot_preview1', 'clock_time_get',
199
201
  ['func', '$__clock_time_get', ['param', 'i32'], ['param', 'i64'], ['param', 'i32'], ['result', 'i32']])
@@ -266,6 +268,8 @@ const setupJsHost = (ctx) => {
266
268
  makeConsole('log', 1)
267
269
  makeConsole('warn', 2)
268
270
  makeConsole('error', 2)
271
+ makeConsole('info', 1) // spec/Node: info & debug are log-level -> stdout
272
+ makeConsole('debug', 1)
269
273
 
270
274
  reg('Date.now', [], () => {
271
275
  needNow()
package/module/core.js CHANGED
@@ -15,6 +15,7 @@ import { reconstructArgsWithSpreads } from '../src/ir.js'
15
15
  import { valTypeOf, shapeOf } from '../src/kind.js'
16
16
  import { T } from '../src/ast.js'
17
17
  import { inlineArraySid } from '../src/static.js'
18
+ import { packedI32, structInline } from '../src/abi/index.js'
18
19
  import { VAL, lookupValType, lookupNotString, repOf, updateRep } from '../src/reps.js'
19
20
  import { ctx, err, inc, PTR, LAYOUT, HEAP, FORWARDING_MASK, emitArity, followForwardingWat, declGlobal } from '../src/ctx.js'
20
21
  import { ptrOffsetFwdWat, STR_INTERN_BIT } from '../layout.js'
@@ -31,6 +32,7 @@ export default (ctx) => {
31
32
  __len: ['__typed_shift', '__ptr_offset', '__ptr_offset_fwd'],
32
33
  __cap: ['__typed_shift', '__ptr_type', '__ptr_offset', '__ptr_aux'],
33
34
  __typed_data: ['__ptr_offset', '__ptr_aux'],
35
+ __typed_idx: () => (ctx.features.f16 ? ['__f16_to_f64'] : []),
34
36
  __ptr_offset: ['__ptr_offset_fwd'],
35
37
  __ptr_offset_fwd: [],
36
38
  __is_str_key: ['__ptr_type'],
@@ -233,9 +235,11 @@ export default (ctx) => {
233
235
  (then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
234
236
  (else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
235
237
  (else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
236
- (then (if (result f64) (i32.and (local.get $et) (i32.const 1))
238
+ (then ${ctx.features.f16 ? `(if (result f64) (i32.and (local.get $aux) (i32.const 32))
239
+ (then (call $__f16_to_f64 (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
240
+ (else ` : ''}(if (result f64) (i32.and (local.get $et) (i32.const 1))
237
241
  (then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
238
- (else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
242
+ (else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1)))))))${ctx.features.f16 ? '))' : ''})
239
243
  (else (if (result f64) (i32.and (local.get $et) (i32.const 1))
240
244
  (then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
241
245
  (else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))
@@ -657,6 +661,74 @@ export default (ctx) => {
657
661
  (then (i32.load (i32.add (local.get $off) (i32.const 4))))
658
662
  (else (local.get $off))))`
659
663
 
664
+ // === binary16 ↔ f64 (Float16Array / DataView.getFloat16 / Math.f16round) ===
665
+ // Pure-integer, exactly rounded — f64 → f16 rounds DIRECTLY off the f64 bits
666
+ // (an f32 hop double-rounds at the overflow boundary: 65519.999… must round
667
+ // to 65504, not Inf). f16 → f64 is exact by construction (every half is a
668
+ // double). NaN canonicalizes (sign/payload dropped) so `v !== v` stays sound
669
+ // on jz's canonical-NaN model.
670
+ ctx.core.stdlib['__f16_to_f64'] = `(func $__f16_to_f64 (param $b i32) (result f64)
671
+ (local $h i32) (local $e i32) (local $m i32) (local $r f64)
672
+ (local.set $h (i32.and (local.get $b) (i32.const 0x7FFF)))
673
+ (local.set $e (i32.shr_u (local.get $h) (i32.const 10)))
674
+ (local.set $m (i32.and (local.get $h) (i32.const 0x3FF)))
675
+ (if (i32.eq (local.get $e) (i32.const 31))
676
+ (then
677
+ (if (local.get $m)
678
+ (then (return (f64.reinterpret_i64 (i64.const ${nanPrefixHex()}))))
679
+ (else (return (f64.reinterpret_i64 (i64.or (i64.const 0x7FF0000000000000)
680
+ (i64.shl (i64.extend_i32_u (i32.and (local.get $b) (i32.const 0x8000))) (i64.const 48)))))))))
681
+ (if (i32.eqz (local.get $e))
682
+ ;; subnormal: mant · 2^-24 (exact — integer times a power of two)
683
+ (then (local.set $r (f64.mul (f64.convert_i32_u (local.get $m)) (f64.const 5.960464477539063e-8))))
684
+ (else (local.set $r (f64.reinterpret_i64 (i64.or
685
+ (i64.shl (i64.extend_i32_u (i32.add (local.get $e) (i32.const 1008))) (i64.const 52))
686
+ (i64.shl (i64.extend_i32_u (local.get $m)) (i64.const 42)))))))
687
+ (f64.reinterpret_i64 (i64.or (i64.reinterpret_f64 (local.get $r))
688
+ (i64.shl (i64.extend_i32_u (i32.and (local.get $b) (i32.const 0x8000))) (i64.const 48)))))`
689
+
690
+ ctx.core.stdlib['__f64_to_f16'] = `(func $__f64_to_f16 (param $v f64) (result i32)
691
+ (local $u i64) (local $sign i32) (local $ne i32) (local $half i32)
692
+ (local $m i64) (local $full i64) (local $sh i64) (local $rb i64) (local $hp i64)
693
+ (local.set $u (i64.reinterpret_f64 (local.get $v)))
694
+ (local.set $sign (i32.wrap_i64 (i64.and (i64.shr_u (local.get $u) (i64.const 48)) (i64.const 0x8000))))
695
+ (local.set $u (i64.and (local.get $u) (i64.const 0x7FFFFFFFFFFFFFFF)))
696
+ (if (i64.ge_u (local.get $u) (i64.const 0x7FF0000000000000))
697
+ (then (return (i32.or (local.get $sign)
698
+ (select (i32.const 0x7E00) (i32.const 0x7C00) (i64.gt_u (local.get $u) (i64.const 0x7FF0000000000000)))))))
699
+ (local.set $m (i64.and (local.get $u) (i64.const 0xFFFFFFFFFFFFF)))
700
+ (local.set $ne (i32.sub (i32.wrap_i64 (i64.shr_u (local.get $u) (i64.const 52))) (i32.const 1008)))
701
+ (if (i32.ge_s (local.get $ne) (i32.const 31))
702
+ (then (return (i32.or (local.get $sign) (i32.const 0x7C00)))))
703
+ (if (i32.ge_s (local.get $ne) (i32.const 1))
704
+ (then ;; normal: 42 dropped mantissa bits round ties-to-even; a mantissa
705
+ ;; carry overflows into the exponent or into infinity — both exact
706
+ (local.set $half (i32.or (i32.shl (local.get $ne) (i32.const 10))
707
+ (i32.wrap_i64 (i64.shr_u (local.get $m) (i64.const 42)))))
708
+ (local.set $rb (i64.and (local.get $m) (i64.const 0x3FFFFFFFFFF)))
709
+ (if (i32.or (i64.gt_u (local.get $rb) (i64.const 0x20000000000))
710
+ (i32.and (i64.eq (local.get $rb) (i64.const 0x20000000000)) (i32.and (local.get $half) (i32.const 1))))
711
+ (then (local.set $half (i32.add (local.get $half) (i32.const 1)))))
712
+ (return (i32.or (local.get $sign) (local.get $half)))))
713
+ (if (i32.lt_s (local.get $ne) (i32.const -10))
714
+ (then (return (local.get $sign)))) ;; underflow → ±0
715
+ ;; subnormal: shift the 53-bit significand by 43-ne (43…53), ties-to-even
716
+ (local.set $full (i64.or (local.get $m) (i64.const 0x10000000000000)))
717
+ (local.set $sh (i64.extend_i32_u (i32.sub (i32.const 43) (local.get $ne))))
718
+ (local.set $half (i32.wrap_i64 (i64.shr_u (local.get $full) (local.get $sh))))
719
+ (local.set $rb (i64.and (local.get $full) (i64.sub (i64.shl (i64.const 1) (local.get $sh)) (i64.const 1))))
720
+ (local.set $hp (i64.shl (i64.const 1) (i64.sub (local.get $sh) (i64.const 1))))
721
+ (if (i32.or (i64.gt_u (local.get $rb) (local.get $hp))
722
+ (i32.and (i64.eq (local.get $rb) (local.get $hp)) (i32.and (local.get $half) (i32.const 1))))
723
+ (then (local.set $half (i32.add (local.get $half) (i32.const 1)))))
724
+ (i32.or (local.get $sign) (local.get $half)))`
725
+
726
+ // ToUint8Clamp (Uint8ClampedArray stores): NaN → 0, clamp [0,255],
727
+ // round-half-to-even — f64.nearest IS ties-to-even.
728
+ ctx.core.stdlib['__u8_clamp'] = `(func $__u8_clamp (param $v f64) (result i32)
729
+ (if (f64.ne (local.get $v) (local.get $v)) (then (return (i32.const 0))))
730
+ (i32.trunc_sat_f64_u (f64.nearest (f64.min (f64.max (local.get $v) (f64.const 0)) (f64.const 255)))))`
731
+
660
732
  // Hot (~85M calls in watr self-host). Type/offset extraction inlined; forwarding
661
733
  // loop only entered for ARRAY. ARRAY fast path dominates (nodes?.length, out.length …).
662
734
  ctx.core.stdlib['__len'] = `(func $__len (param $ptr i64) (result i32)
@@ -857,11 +929,13 @@ export default (ctx) => {
857
929
  (then
858
930
  (local.set $cap (call $__cap (local.get $bits)))
859
931
  (local.set $src (call $__ptr_offset (local.get $bits)))
860
- (local.set $dst (call $__alloc_hdr_n (i32.const 0) (local.get $cap) (i32.const 24)))
932
+ ;; 28 = MAP_ENTRY + the probe hash lane (collection.js) — the wholesale
933
+ ;; copy must carry the lane or the clone's probes see stale zeros
934
+ (local.set $dst (call $__alloc_hdr_n (i32.const 0) (local.get $cap) (i32.const 28)))
861
935
  (memory.copy
862
936
  (i32.sub (local.get $dst) (i32.const 16))
863
937
  (i32.sub (local.get $src) (i32.const 16))
864
- (i32.add (i32.const 16) (i32.mul (local.get $cap) (i32.const 24))))
938
+ (i32.add (i32.const 16) (i32.mul (local.get $cap) (i32.const 28))))
865
939
  (return (call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0) (local.get $dst)))))
866
940
  (local.get $v))`
867
941
 
@@ -951,6 +1025,9 @@ export default (ctx) => {
951
1025
  const base = (baseExpr?.ptrKind != null && baseExpr.ptrKind !== VAL.ARRAY)
952
1026
  ? baseExpr
953
1027
  : (baseExpr?.type === 'f64' ? baseExpr : asF64(baseExpr))
1028
+ // Packed i32 cells (structInline + inlineCellI32, flag rides the cursor
1029
+ // node): the field IS a raw i32 at +idx*4 — one i32.load, no trunc_sat.
1030
+ if (baseExpr?.cellI32) return typed(packedI32.ops.load(ptrOffsetIR(base, VAL.OBJECT), idx), 'i32')
954
1031
  const load = ctx.abi.object.ops.load(ptrOffsetIR(base, VAL.OBJECT), idx)
955
1032
  // Strict-int32 slot (ctx.schema.slotI32CertainAt — every censused write is
956
1033
  // exactly-int32, never -0): land the value directly in i32. trunc_sat of
@@ -1306,13 +1383,15 @@ export default (ctx) => {
1306
1383
  if (Array.isArray(obj) && (obj[0] === 'str' || obj[0] == null) && typeof obj[1] === 'string') {
1307
1384
  return typed(['f64.const', new TextEncoder().encode(obj[1]).length], 'f64')
1308
1385
  }
1309
- // structInline Array<S>: the header `len` counts physical f64 cells (K
1310
- // per element), so the JS array length is `physicalLen / K`.
1386
+ // structInline Array<S>: the header `len` counts physical 8-byte cells
1387
+ // (K per element, ⌈K/2⌉ when packed i32), so the JS array length is
1388
+ // `physicalLen / cellsPerElem`.
1311
1389
  const inlSid = inlineArraySid(obj)
1312
1390
  if (inlSid != null) {
1313
1391
  const K = ctx.schema.list[inlSid].length
1392
+ const cpe = structInline(K, ctx.schema.inlineCellI32?.has(inlSid)).cpe
1314
1393
  const physLen = ['i32.load', ['i32.sub', ptrOffsetIR(asF64(emit(obj)), VAL.ARRAY), ['i32.const', 8]]]
1315
- return typed(['f64.convert_i32_s', K > 1 ? ['i32.div_s', physLen, ['i32.const', K]] : physLen], 'f64')
1394
+ return typed(['f64.convert_i32_s', cpe > 1 ? ['i32.div_s', physLen, ['i32.const', cpe]] : physLen], 'f64')
1316
1395
  }
1317
1396
  const rep = typeof obj === 'string' ? repOf(obj) : null
1318
1397
  const vt = rep ? rep.val : valTypeOf(obj)
@@ -0,0 +1,124 @@
1
+ /**
2
+ * crypto — Web Crypto's compute slice: getRandomValues + randomUUID.
3
+ *
4
+ * Entropy rides the host's CSPRNG through the same channel Math.random seeds
5
+ * from: WASI `random_get` under host:'wasi', an `env.random` byte-fill (wired
6
+ * by interop from globalThis.crypto.getRandomValues) under host:'js'. A
7
+ * numeric `randomSeed` option switches BOTH into a deterministic xorshift32
8
+ * stream — reproducible runs for sims/tests, explicitly NOT cryptographic
9
+ * (documented divergence; the default entropy mode IS the host CSPRNG).
10
+ *
11
+ * getRandomValues keeps the spec's guards (integer typed arrays only, ≤ 64 KiB
12
+ * per call) and returns its argument. randomUUID mints a lowercase v4 UUID.
13
+ * crypto.subtle stays out by doctrine — security crypto is out of scope.
14
+ *
15
+ * @module crypto
16
+ */
17
+
18
+ import { typed, asI64 } from '../src/ir.js'
19
+ import { emit, wat, hostImport, bind } from '../src/bridge.js'
20
+ import { inc, declGlobal, PTR } from '../src/ctx.js'
21
+
22
+ export default (ctx) => {
23
+ const seeded = typeof ctx.transform.randomSeed === 'number'
24
+ const seedConst = seeded ? ((ctx.transform.randomSeed >>> 0) || 1) : 0 // xorshift dies on 0
25
+ const wasi = ctx.transform.host === 'wasi'
26
+
27
+ const needEntropy = () => wasi
28
+ ? hostImport('wasi_snapshot_preview1', 'random_get',
29
+ ['func', '$__random_get', ['param', 'i32'], ['param', 'i32'], ['result', 'i32']])
30
+ : hostImport('env', 'random',
31
+ ['func', '$__env_random', ['param', 'i32'], ['param', 'i32']])
32
+
33
+ if (seeded) {
34
+ declGlobal('crypto.state', 'i32', seedConst)
35
+ wat('__crypto_fill', `(func $__crypto_fill (param $dst i32) (param $len i32)
36
+ (local $s i32) (local $i i32)
37
+ (local.set $s (global.get $crypto.state))
38
+ (block $d (loop $l
39
+ (br_if $d (i32.ge_s (local.get $i) (local.get $len)))
40
+ (local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 13))))
41
+ (local.set $s (i32.xor (local.get $s) (i32.shr_u (local.get $s) (i32.const 17))))
42
+ (local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 5))))
43
+ (i32.store8 (i32.add (local.get $dst) (local.get $i)) (local.get $s))
44
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
45
+ (br $l)))
46
+ (global.set $crypto.state (local.get $s)))`)
47
+ } else {
48
+ wat('__crypto_fill', wasi
49
+ ? `(func $__crypto_fill (param $dst i32) (param $len i32)
50
+ (drop (call $__random_get (local.get $dst) (local.get $len))))`
51
+ : `(func $__crypto_fill (param $dst i32) (param $len i32)
52
+ (call $__env_random (local.get $dst) (local.get $len)))`)
53
+ }
54
+
55
+ // Spec guards (WebCryptoAPI §10.1.1): receiver must be an integer typed
56
+ // array (floats → TypeMismatchError), byteLength ≤ 65536 (QuotaExceeded).
57
+ // Element codes 0–5 are the int family; code 7 + the bigint flag is
58
+ // BigInt64/BigUint64 (allowed). Returns the receiver, filled.
59
+ wat('__get_random_values', `(func $__get_random_values (param $p i64) (result f64)
60
+ (local $aux i32) (local $et i32) (local $bl i32)
61
+ (if (i32.ne (call $__ptr_type (local.get $p)) (i32.const ${PTR.TYPED}))
62
+ (then (throw $__jz_err (f64.const 0))))
63
+ (local.set $aux (call $__ptr_aux (local.get $p)))
64
+ (local.set $et (i32.and (local.get $aux) (i32.const 7)))
65
+ (if (i32.and
66
+ (i32.gt_u (local.get $et) (i32.const 5))
67
+ (i32.eqz (i32.and (local.get $aux) (i32.const 16))))
68
+ (then (throw $__jz_err (f64.const 0))))
69
+ (local.set $bl (i32.shl (call $__len (local.get $p)) (call $__typed_shift (local.get $et))))
70
+ (if (i32.gt_u (local.get $bl) (i32.const 65536))
71
+ (then (throw $__jz_err (f64.const 0))))
72
+ (call $__crypto_fill (call $__typed_data (local.get $p)) (local.get $bl))
73
+ (f64.reinterpret_i64 (local.get $p)))`,
74
+ ['__crypto_fill', '__ptr_type', '__ptr_aux', '__len', '__typed_shift', '__typed_data'])
75
+
76
+ // Lowercase v4 UUID: 16 entropy bytes, version/variant bits, hex + dashes.
77
+ wat('__random_uuid', `(func $__random_uuid (result f64)
78
+ (local $buf i32) (local $base i32) (local $out i32)
79
+ (local $i i32) (local $j i32) (local $b i32) (local $n i32)
80
+ (local.set $buf (call $__alloc (i32.const 16)))
81
+ (call $__crypto_fill (local.get $buf) (i32.const 16))
82
+ (i32.store8 (i32.add (local.get $buf) (i32.const 6))
83
+ (i32.or (i32.and (i32.load8_u (i32.add (local.get $buf) (i32.const 6))) (i32.const 15)) (i32.const 64)))
84
+ (i32.store8 (i32.add (local.get $buf) (i32.const 8))
85
+ (i32.or (i32.and (i32.load8_u (i32.add (local.get $buf) (i32.const 8))) (i32.const 63)) (i32.const 128)))
86
+ (local.set $base (call $__alloc (i32.const 40)))
87
+ (i32.store (local.get $base) (i32.const 36))
88
+ (local.set $out (i32.add (local.get $base) (i32.const 4)))
89
+ (block $d (loop $l
90
+ (br_if $d (i32.ge_u (local.get $i) (i32.const 16)))
91
+ (local.set $b (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
92
+ (local.set $n (i32.shr_u (local.get $b) (i32.const 4)))
93
+ (i32.store8 (i32.add (local.get $out) (local.get $j))
94
+ (select (i32.add (local.get $n) (i32.const 87)) (i32.add (local.get $n) (i32.const 48)) (i32.gt_u (local.get $n) (i32.const 9))))
95
+ (local.set $n (i32.and (local.get $b) (i32.const 15)))
96
+ (i32.store8 (i32.add (i32.add (local.get $out) (local.get $j)) (i32.const 1))
97
+ (select (i32.add (local.get $n) (i32.const 87)) (i32.add (local.get $n) (i32.const 48)) (i32.gt_u (local.get $n) (i32.const 9))))
98
+ (local.set $j (i32.add (local.get $j) (i32.const 2)))
99
+ ;; dash after bytes 3, 5, 7, 9 → 8-4-4-4-12
100
+ (if (i32.or
101
+ (i32.or (i32.eq (local.get $i) (i32.const 3)) (i32.eq (local.get $i) (i32.const 5)))
102
+ (i32.or (i32.eq (local.get $i) (i32.const 7)) (i32.eq (local.get $i) (i32.const 9))))
103
+ (then
104
+ (i32.store8 (i32.add (local.get $out) (local.get $j)) (i32.const 45))
105
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))))
106
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
107
+ (br $l)))
108
+ (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`,
109
+ ['__crypto_fill', '__alloc', '__mkptr'])
110
+
111
+ bind('crypto.getRandomValues', (arr) => {
112
+ if (!seeded) needEntropy()
113
+ ctx.runtime.throws = true
114
+ ctx.features.typedarray = true
115
+ inc('__get_random_values')
116
+ return typed(['call', '$__get_random_values', asI64(emit(arr))], 'f64')
117
+ })
118
+
119
+ bind('crypto.randomUUID', () => {
120
+ if (!seeded) needEntropy()
121
+ inc('__random_uuid')
122
+ return typed(['call', '$__random_uuid'], 'f64')
123
+ })
124
+ }
package/module/index.js CHANGED
@@ -17,4 +17,6 @@ import date from './date.js'
17
17
  import simd from './simd.js'
18
18
  import fs from './fs.js'
19
19
  import web from './web.js'
20
- export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer, date, simd , atomics, fs, web }
20
+ import crypto from './crypto.js'
21
+ import navigator from './navigator.js'
22
+ export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer, date, simd , atomics, fs, web, crypto, navigator }
package/module/math.js CHANGED
@@ -262,6 +262,11 @@ export default (ctx) => {
262
262
  ], 'f64')
263
263
  }
264
264
  ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', toNumF64(a, emit(a))]], 'f64')
265
+ // ES2025 Math.f16round — round to binary16 and back (kernels in core.js)
266
+ ctx.core.emit['math.f16round'] = a => {
267
+ inc('__f64_to_f16', '__f16_to_f64')
268
+ return typed(['call', '$__f16_to_f64', ['call', '$__f64_to_f16', toNumF64(a, emit(a))]], 'f64')
269
+ }
265
270
  // ES2025 Math.f16round — no wasm f16 ops, so round in software (exactly).
266
271
  reg('math.f16round', ['math.f16round'], a => fn('math.f16round', a))
267
272
 
@@ -0,0 +1,26 @@
1
+ /**
2
+ * navigator — the one member with compute meaning: hardwareConcurrency.
3
+ * Pairs with jz.pool/SPMD workers for in-module tile sizing.
4
+ *
5
+ * host:'js' — reads through an `env.hardwareConcurrency` service (wired by
6
+ * interop from globalThis.navigator, present in browsers and Node ≥ 21).
7
+ * host:'wasi' — no host to ask: warns and folds to 1 (a loud, documented
8
+ * degradation; WASI p1 has no concurrency introspection).
9
+ *
10
+ * @module navigator
11
+ */
12
+
13
+ import { typed } from '../src/ir.js'
14
+ import { hostImport } from '../src/bridge.js'
15
+ import { warn } from '../src/ctx.js'
16
+
17
+ export default (ctx) => {
18
+ ctx.core.emit['navigator.hardwareConcurrency'] = () => {
19
+ if (ctx.transform.host === 'wasi') {
20
+ warn('host-global', `\`navigator.hardwareConcurrency\` has no WASI source — folded to 1; size worker pools from the embedder instead`, {})
21
+ return typed(['f64.const', 1], 'f64')
22
+ }
23
+ hostImport('env', 'hardwareConcurrency', ['func', '$__hw_concurrency', ['result', 'f64']])
24
+ return typed(['call', '$__hw_concurrency'], 'f64')
25
+ }
26
+ }
package/module/schema.js CHANGED
@@ -97,12 +97,20 @@ export function initSchema(ctx) {
97
97
  if (ctx.schema.poisoned?.has(varName)) return -1
98
98
  const vt = lookupValType(varName)
99
99
  if (vt !== VAL.OBJECT) return -1
100
- // Structural subtyping: walk only schemas that contain this prop.
101
- // Consistent slot across all return slot; any mismatch -1 (dynamic lookup).
100
+ // Structural subtyping sound only under the FULL closed-world condition:
101
+ // the prop lives at the SAME slot in EVERY registered schema, so whatever
102
+ // sid this OBJECT receiver carries, the slot is right. Checking only the
103
+ // schemas that CONTAIN the prop (the old form) bet that no other schema's
104
+ // instance could flow here — wrong the moment two shapes share a binding:
105
+ // `p.x` on a {z,w,q} receiver read z (slot 0 of a foreign schema), and the
106
+ // write sibling `p.x = v` CORRUPTED z. Receivers with a genuinely unique
107
+ // prop still devirtualize via guardedSlotOf's runtime-guarded path.
102
108
  const bucket = byProp.get(prop)
103
- if (!bucket) return -1
109
+ if (!bucket || bucket.length !== ctx.schema.list.length) return -1
104
110
  const slot = bucket[0].slot
105
111
  for (let i = 1; i < bucket.length; i++) if (bucket[i].slot !== slot) return -1
112
+ if (typeof process !== 'undefined' && process.env?.JZ_DBG_SLOTOF)
113
+ console.error('[slotof-structural]', varName, '.', prop, 'slot', slot, 'bucket', bucket.length, 'of', ctx.schema.list.length, 'schemas')
106
114
  return slot
107
115
  }
108
116