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.
- package/LICENSE +21 -0
- package/README.md +381 -0
- package/cli.js +163 -0
- package/index.js +217 -0
- package/module/array.js +1317 -0
- package/module/collection.js +791 -0
- package/module/console.js +190 -0
- package/module/core.js +642 -0
- package/module/function.js +180 -0
- package/module/index.js +15 -0
- package/module/json.js +504 -0
- package/module/math.js +389 -0
- package/module/number.js +605 -0
- package/module/object.js +357 -0
- package/module/regex.js +913 -0
- package/module/schema.js +104 -0
- package/module/string.js +928 -0
- package/module/symbol.js +54 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +711 -0
- package/package.json +54 -5
- package/src/analyze.js +1906 -0
- package/src/compile.js +2175 -0
- package/src/ctx.js +243 -0
- package/src/emit.js +2095 -0
- package/src/host.js +524 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +391 -0
- package/src/optimize.js +1352 -0
- package/src/prepare.js +1598 -0
- package/wasi.js +74 -0
|
@@ -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
|
+
}
|