jz 0.0.0 → 0.1.1

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.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * WASI module — console.log/warn/error via fd_write.
3
+ *
4
+ * Imports wasi_snapshot_preview1.fd_write — standard WASI Preview 1.
5
+ * Output .wasm runs natively on wasmtime/wasmer/deno.
6
+ * For browser/Node, use jz/wasi polyfill.
7
+ *
8
+ * console.log(a, b, c) → serialize each arg to string bytes,
9
+ * write space-separated to fd=1, append newline.
10
+ * console.warn/error → fd=2.
11
+ *
12
+ * @module wasi
13
+ */
14
+
15
+ import { typed, asF64 } from '../src/ir.js'
16
+ import { emit } from '../src/emit.js'
17
+ import { valTypeOf, VAL } from '../src/analyze.js'
18
+ import { exprType } from '../src/analyze.js'
19
+ import { inc, PTR } from '../src/ctx.js'
20
+
21
+ export default (ctx) => {
22
+ Object.assign(ctx.core.stdlibDeps, {
23
+ __write_val: ['__ptr_type', '__write_str', '__write_num', '__write_int', '__write_byte', '__static_str'],
24
+ __write_num: ['__ftoa', '__write_str'],
25
+ __write_int: ['__itoa', '__mkstr', '__write_str'],
26
+ __write_str: ['__sso_char', '__str_len'],
27
+ })
28
+
29
+
30
+ // Import fd_write from WASI
31
+ ctx.module.imports.push(
32
+ ['import', '"wasi_snapshot_preview1"', '"fd_write"',
33
+ ['func', '$__fd_write', ['param', 'i32'], ['param', 'i32'], ['param', 'i32'], ['param', 'i32'], ['result', 'i32']]])
34
+
35
+ // __write_str(fd: i32, ptr: f64) — write a NaN-boxed string to fd via iov
36
+ // Handles both SSO and heap strings
37
+ ctx.core.stdlib['__write_str'] = `(func $__write_str (param $fd i32) (param $ptr f64)
38
+ (local $iov i32) (local $type i32) (local $len i32) (local $off i32) (local $buf i32)
39
+ ;; Allocate iov (8 bytes: ptr + len) + nwritten (4 bytes)
40
+ (local.set $iov (call $__alloc (i32.const 12)))
41
+ (local.set $type (call $__ptr_type (local.get $ptr)))
42
+ (if (i32.eq (local.get $type) (i32.const ${PTR.SSO}))
43
+ (then
44
+ ;; SSO: unpack chars to memory buffer, then write
45
+ (local.set $len (call $__ptr_aux (local.get $ptr)))
46
+ (local.set $buf (call $__alloc (local.get $len)))
47
+ (local.set $off (i32.const 0))
48
+ (block $done (loop $loop
49
+ (br_if $done (i32.ge_s (local.get $off) (local.get $len)))
50
+ (i32.store8 (i32.add (local.get $buf) (local.get $off))
51
+ (call $__sso_char (local.get $ptr) (local.get $off)))
52
+ (local.set $off (i32.add (local.get $off) (i32.const 1)))
53
+ (br $loop)))
54
+ (i32.store (local.get $iov) (local.get $buf))
55
+ (i32.store (i32.add (local.get $iov) (i32.const 4)) (local.get $len)))
56
+ (else
57
+ ;; Heap string: offset points directly to char data
58
+ (i32.store (local.get $iov) (call $__ptr_offset (local.get $ptr)))
59
+ (i32.store (i32.add (local.get $iov) (i32.const 4)) (call $__str_len (local.get $ptr)))))
60
+ (drop (call $__fd_write (local.get $fd) (local.get $iov) (i32.const 1)
61
+ (i32.add (local.get $iov) (i32.const 8)))))`
62
+
63
+ // __write_byte(fd: i32, byte: i32) — write a single byte (space, newline)
64
+ ctx.core.stdlib['__write_byte'] = `(func $__write_byte (param $fd i32) (param $byte i32)
65
+ (local $iov i32)
66
+ (local.set $iov (call $__alloc (i32.const 13)))
67
+ (i32.store8 (i32.add (local.get $iov) (i32.const 12)) (local.get $byte))
68
+ (i32.store (local.get $iov) (i32.add (local.get $iov) (i32.const 12)))
69
+ (i32.store (i32.add (local.get $iov) (i32.const 4)) (i32.const 1))
70
+ (drop (call $__fd_write (local.get $fd) (local.get $iov) (i32.const 1)
71
+ (i32.add (local.get $iov) (i32.const 8)))))`
72
+
73
+ // __write_num(fd: i32, val: f64) — convert number to string, write to fd
74
+ ctx.core.stdlib['__write_num'] = `(func $__write_num (param $fd i32) (param $val f64)
75
+ (call $__write_str (local.get $fd) (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0))))`
76
+ // __write_int(fd: i32, val: f64) — convert integer to string, write to fd
77
+ // Skips __ftoa (~600 B) for values proven integer by exprType.
78
+ ctx.core.stdlib['__write_int'] = `(func $__write_int (param $fd i32) (param $val f64)
79
+ (local $buf i32)
80
+ (local.set $buf (call $__alloc (i32.const 12)))
81
+ (call $__write_str (local.get $fd)
82
+ (call $__mkstr (local.get $buf) (call $__itoa (i32.trunc_sat_f64_s (local.get $val)) (local.get $buf)))))`
83
+ // __write_val(fd: i32, val: f64) — write any value, auto-detecting type
84
+ ctx.core.stdlib['__write_val'] = `(func $__write_val (param $fd i32) (param $val f64)
85
+ (local $type i32)
86
+ ;; Not NaN → plain number
87
+ (if (f64.eq (local.get $val) (local.get $val))
88
+ (then (call $__write_num (local.get $fd) (local.get $val)) (return)))
89
+ ;; NaN: check if it's a pointer (type > 0) or plain NaN (type = 0)
90
+ (local.set $type (call $__ptr_type (local.get $val)))
91
+ (if (i32.eqz (local.get $type))
92
+ (then (call $__write_str (local.get $fd) (call $__static_str (i32.const 0))) (return)))
93
+ ;; String pointer
94
+ (if (i32.or (i32.eq (local.get $type) (i32.const ${PTR.STRING}))
95
+ (i32.eq (local.get $type) (i32.const ${PTR.SSO})))
96
+ (then (call $__write_str (local.get $fd) (local.get $val)) (return)))
97
+ ;; Array/Object placeholder
98
+ (call $__write_str (local.get $fd) (call $__static_str
99
+ (if (result i32) (i32.eq (local.get $type) (i32.const 1))
100
+ (then (i32.const 7)) (else (i32.const 8))))))`
101
+
102
+ // Template-literal concat chains (`a${x}b`) lower to ['()', ['.', X, 'concat'], Y]
103
+ // in prepare. Walking left from the chain root recovers the parts in order; if the
104
+ // base is a `['str', ...]` it's a template-shaped chain (vs an arbitrary user
105
+ // .concat call). Returning the parts lets console.log skip __str_concat/__to_str
106
+ // entirely — biquad's only string churn is the perf-summary line.
107
+ const flattenTemplateConcat = (node) => {
108
+ const parts = []
109
+ let n = node
110
+ while (Array.isArray(n) && n[0] === '()' && n.length === 3 &&
111
+ Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'concat') {
112
+ parts.unshift(n[2])
113
+ n = n[1][1]
114
+ }
115
+ if (!(Array.isArray(n) && n[0] === 'str')) return null
116
+ parts.unshift(n)
117
+ return parts
118
+ }
119
+
120
+ // console.log(...args) — variadic, each arg separated by space, followed by newline.
121
+ // Per-arg dispatch is monomorphized when valTypeOf proves the arg is a string or
122
+ // number — direct __write_str / __write_num skips the polymorphic __write_val
123
+ // helper (and its transitive __ptr_type / __static_str / number-or-string-test
124
+ // arms), which is the entire stdlib floor for size-conscious benches.
125
+ const makeConsole = (method, fd) => {
126
+ ctx.core.emit[`console.${method}`] = (...args) => {
127
+ inc('__write_byte')
128
+ const ir = []
129
+ const writePart = (part) => {
130
+ // Empty string segments (template boundaries) write nothing.
131
+ if (Array.isArray(part) && part[0] === 'str' && part[1] === '') return
132
+ const vt = valTypeOf(part)
133
+ if (vt === VAL.STRING) {
134
+ inc('__write_str')
135
+ ir.push(['call', '$__write_str', ['i32.const', fd], asF64(emit(part))])
136
+ } else if (vt === VAL.NUMBER) {
137
+ // Integer-valued numbers use __write_int (__itoa) instead of __write_num (__ftoa).
138
+ // exprType sees i32 for literals, bitwise ops, .length on arrays, Math.imul, etc.
139
+ if (exprType(part, ctx.func.locals) === 'i32') {
140
+ inc('__write_int')
141
+ ir.push(['call', '$__write_int', ['i32.const', fd], asF64(emit(part))])
142
+ } else {
143
+ inc('__write_num')
144
+ ir.push(['call', '$__write_num', ['i32.const', fd], asF64(emit(part))])
145
+ }
146
+ } else {
147
+ inc('__write_val')
148
+ ir.push(['call', '$__write_val', ['i32.const', fd], asF64(emit(part))])
149
+ }
150
+ }
151
+ for (let i = 0; i < args.length; i++) {
152
+ if (i > 0) ir.push(['call', '$__write_byte', ['i32.const', fd], ['i32.const', 32]]) // space
153
+ const parts = flattenTemplateConcat(args[i])
154
+ if (parts) for (const p of parts) writePart(p)
155
+ else writePart(args[i])
156
+ }
157
+ ir.push(['call', '$__write_byte', ['i32.const', fd], ['i32.const', 10]]) // newline
158
+ ir.push(['f64.const', 0]) // return undefined
159
+ return typed(['block', ['result', 'f64'], ...ir], 'f64')
160
+ }
161
+ }
162
+
163
+ makeConsole('log', 1)
164
+ makeConsole('warn', 2)
165
+ makeConsole('error', 2)
166
+
167
+ // === Date.now / performance.now via WASI clock_time_get ===
168
+
169
+ ctx.module.imports.push(
170
+ ['import', '"wasi_snapshot_preview1"', '"clock_time_get"',
171
+ ['func', '$__clock_time_get', ['param', 'i32'], ['param', 'i64'], ['param', 'i32'], ['result', 'i32']]])
172
+
173
+ // __time_ms(clock_id) → f64 milliseconds
174
+ // clock_time_get writes i64 nanoseconds to memory, we convert to f64 ms
175
+ ctx.core.stdlib['__time_ms'] = `(func $__time_ms (param $clock i32) (result f64)
176
+ (drop (call $__clock_time_get (local.get $clock) (i64.const 1000) (i32.const 0)))
177
+ (f64.div (f64.convert_i64_u (i64.load (i32.const 0))) (f64.const 1000000)))`
178
+
179
+ ctx.core.emit['Date.now'] = () => {
180
+ inc('__time_ms')
181
+ return typed(['call', '$__time_ms', ['i32.const', 0]], 'f64') // clock 0 = realtime
182
+ }
183
+
184
+ ctx.core.emit['performance.now'] = () => {
185
+ inc('__time_ms')
186
+ return typed(['call', '$__time_ms', ['i32.const', 1]], 'f64') // clock 1 = monotonic
187
+ }
188
+
189
+ // Aliases for explicit import: import { now, perfNow } from 'console'
190
+ ctx.core.emit['console.now'] = ctx.core.emit['Date.now']
191
+ ctx.core.emit['console.perfNow'] = ctx.core.emit['performance.now']
192
+ }