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