querysub 0.356.0 → 0.357.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/.cursorrules +8 -0
- package/bin/movelogs.js +4 -0
- package/package.json +12 -6
- package/scripts/postinstall.js +23 -0
- package/src/-a-archives/archiveCache.ts +10 -12
- package/src/-a-archives/archives.ts +29 -0
- package/src/-a-archives/archivesBackBlaze.ts +60 -12
- package/src/-a-archives/archivesDisk.ts +27 -8
- package/src/-a-archives/archivesLimitedCache.ts +21 -0
- package/src/-a-archives/archivesMemoryCache.ts +350 -0
- package/src/-a-archives/archivesPrivateFileSystem.ts +22 -0
- package/src/-g-core-values/NodeCapabilities.ts +3 -0
- package/src/0-path-value-core/auditLogs.ts +5 -1
- package/src/0-path-value-core/pathValueCore.ts +7 -7
- package/src/4-dom/qreact.tsx +1 -0
- package/src/4-querysub/Querysub.ts +1 -5
- package/src/config.ts +5 -0
- package/src/diagnostics/MachineThreadInfo.tsx +235 -0
- package/src/diagnostics/NodeViewer.tsx +3 -2
- package/src/diagnostics/logs/FastArchiveAppendable.ts +79 -42
- package/src/diagnostics/logs/FastArchiveController.ts +102 -63
- package/src/diagnostics/logs/FastArchiveViewer.tsx +36 -8
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +461 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +327 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.d.ts +18 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.js +1 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +140 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +22 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat +1145 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat.d.ts +178 -0
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +206 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +719 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +408 -0
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +598 -0
- package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +702 -0
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +236 -0
- package/src/diagnostics/logs/IndexedLogs/binding.gyp +23 -0
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +221 -0
- package/src/diagnostics/logs/IndexedLogs/moveLogsEntry.ts +10 -0
- package/src/diagnostics/logs/LogViewer2.tsx +120 -55
- package/src/diagnostics/logs/TimeRangeSelector.tsx +5 -2
- package/src/diagnostics/logs/diskLogger.ts +32 -48
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +3 -2
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +132 -15
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +180 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +106 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +5 -0
- package/src/diagnostics/logs/logViewerExtractField.ts +2 -3
- package/src/diagnostics/managementPages.tsx +10 -0
- package/src/diagnostics/trackResources.ts +1 -1
- package/src/misc/lz4_wasm_nodejs.d.ts +34 -0
- package/src/misc/lz4_wasm_nodejs.js +178 -0
- package/src/misc/lz4_wasm_nodejs_bg.js +94 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm +0 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
- package/src/storage/CompressedStream.ts +13 -0
- package/src/storage/LZ4.ts +32 -0
- package/src/storage/ZSTD.ts +10 -0
- package/src/wat/watCompiler.ts +1716 -0
- package/src/wat/watGrammar.pegjs +93 -0
- package/src/wat/watHandler.ts +179 -0
- package/src/wat/watInstructions.txt +707 -0
- package/src/zip.ts +3 -89
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +0 -125
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
// WAT Compiler - Compiles WebAssembly Text Format to binary
|
|
2
|
+
|
|
3
|
+
import * as peggy from "peggy";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
|
|
7
|
+
type ASTNode = {
|
|
8
|
+
type: "module" | "sexpr" | "instruction" | "identifier" | "index" | "number" | "string" | "attribute";
|
|
9
|
+
keyword?: string;
|
|
10
|
+
items?: ASTNode[];
|
|
11
|
+
attributes?: ASTNode[];
|
|
12
|
+
operands?: ASTNode[];
|
|
13
|
+
name?: string;
|
|
14
|
+
value?: string | number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Instruction forms represent the basic patterns instructions follow
|
|
18
|
+
type InstructionForm =
|
|
19
|
+
| "nullary" // No operands (e.g., nop, return)
|
|
20
|
+
| "unary" // One operand (e.g., drop, local.get)
|
|
21
|
+
| "binary" // Two operands (e.g., i32.store)
|
|
22
|
+
| "block" // Block structure (e.g., block, loop, if)
|
|
23
|
+
| "control" // Control flow (e.g., br, br_if)
|
|
24
|
+
| "call" // Function calls
|
|
25
|
+
| "memory" // Memory operations
|
|
26
|
+
| "const"; // Constants (e.g., i32.const)
|
|
27
|
+
|
|
28
|
+
// Instruction metadata
|
|
29
|
+
type InstructionMeta = {
|
|
30
|
+
form: InstructionForm;
|
|
31
|
+
opcode: number;
|
|
32
|
+
immediates?: ("varuint32" | "varint32" | "varint64" | "float32" | "float64" | "blocktype" | "byte")[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Instruction lookup table - maps instruction names to their forms and opcodes
|
|
36
|
+
export const INSTRUCTION_MAP: Record<string, InstructionMeta> = {
|
|
37
|
+
// Parametric instructions
|
|
38
|
+
"unreachable": { form: "nullary", opcode: 0x00 },
|
|
39
|
+
"nop": { form: "nullary", opcode: 0x01 },
|
|
40
|
+
"drop": { form: "nullary", opcode: 0x1a },
|
|
41
|
+
"select": { form: "nullary", opcode: 0x1b },
|
|
42
|
+
|
|
43
|
+
// Control instructions
|
|
44
|
+
"block": { form: "block", opcode: 0x02, immediates: ["blocktype"] },
|
|
45
|
+
"loop": { form: "block", opcode: 0x03, immediates: ["blocktype"] },
|
|
46
|
+
"if": { form: "block", opcode: 0x04, immediates: ["blocktype"] },
|
|
47
|
+
"else": { form: "nullary", opcode: 0x05 },
|
|
48
|
+
"end": { form: "nullary", opcode: 0x0b },
|
|
49
|
+
"br": { form: "control", opcode: 0x0c, immediates: ["varuint32"] },
|
|
50
|
+
"br_if": { form: "control", opcode: 0x0d, immediates: ["varuint32"] },
|
|
51
|
+
"return": { form: "nullary", opcode: 0x0f },
|
|
52
|
+
"call": { form: "call", opcode: 0x10, immediates: ["varuint32"] },
|
|
53
|
+
"call_indirect": { form: "call", opcode: 0x11, immediates: ["varuint32", "varuint32"] },
|
|
54
|
+
|
|
55
|
+
// Variable instructions
|
|
56
|
+
"local.get": { form: "unary", opcode: 0x20, immediates: ["varuint32"] },
|
|
57
|
+
"local.set": { form: "unary", opcode: 0x21, immediates: ["varuint32"] },
|
|
58
|
+
"local.tee": { form: "unary", opcode: 0x22, immediates: ["varuint32"] },
|
|
59
|
+
"global.get": { form: "unary", opcode: 0x23, immediates: ["varuint32"] },
|
|
60
|
+
"global.set": { form: "unary", opcode: 0x24, immediates: ["varuint32"] },
|
|
61
|
+
|
|
62
|
+
// Table instructions
|
|
63
|
+
"table.get": { form: "unary", opcode: 0x25, immediates: ["varuint32"] },
|
|
64
|
+
"table.set": { form: "unary", opcode: 0x26, immediates: ["varuint32"] },
|
|
65
|
+
|
|
66
|
+
// Memory instructions
|
|
67
|
+
"i32.load": { form: "memory", opcode: 0x28 },
|
|
68
|
+
"i64.load": { form: "memory", opcode: 0x29 },
|
|
69
|
+
"f32.load": { form: "memory", opcode: 0x2a },
|
|
70
|
+
"f64.load": { form: "memory", opcode: 0x2b },
|
|
71
|
+
"i32.load8_s": { form: "memory", opcode: 0x2c },
|
|
72
|
+
"i32.load8_u": { form: "memory", opcode: 0x2d },
|
|
73
|
+
"i32.load16_s": { form: "memory", opcode: 0x2e },
|
|
74
|
+
"i32.load16_u": { form: "memory", opcode: 0x2f },
|
|
75
|
+
"i64.load8_s": { form: "memory", opcode: 0x30 },
|
|
76
|
+
"i64.load8_u": { form: "memory", opcode: 0x31 },
|
|
77
|
+
"i64.load16_s": { form: "memory", opcode: 0x32 },
|
|
78
|
+
"i64.load16_u": { form: "memory", opcode: 0x33 },
|
|
79
|
+
"i64.load32_s": { form: "memory", opcode: 0x34 },
|
|
80
|
+
"i64.load32_u": { form: "memory", opcode: 0x35 },
|
|
81
|
+
"i32.store": { form: "memory", opcode: 0x36 },
|
|
82
|
+
"i64.store": { form: "memory", opcode: 0x37 },
|
|
83
|
+
"f32.store": { form: "memory", opcode: 0x38 },
|
|
84
|
+
"f64.store": { form: "memory", opcode: 0x39 },
|
|
85
|
+
"i32.store8": { form: "memory", opcode: 0x3a },
|
|
86
|
+
"i32.store16": { form: "memory", opcode: 0x3b },
|
|
87
|
+
"i64.store8": { form: "memory", opcode: 0x3c },
|
|
88
|
+
"i64.store16": { form: "memory", opcode: 0x3d },
|
|
89
|
+
"i64.store32": { form: "memory", opcode: 0x3e },
|
|
90
|
+
"memory.size": { form: "unary", opcode: 0x3f, immediates: ["varuint32"] },
|
|
91
|
+
"memory.grow": { form: "unary", opcode: 0x40, immediates: ["varuint32"] },
|
|
92
|
+
|
|
93
|
+
// Numeric instructions - constants
|
|
94
|
+
"i32.const": { form: "const", opcode: 0x41, immediates: ["varint32"] },
|
|
95
|
+
"i64.const": { form: "const", opcode: 0x42, immediates: ["varint64"] },
|
|
96
|
+
"f32.const": { form: "const", opcode: 0x43, immediates: ["float32"] },
|
|
97
|
+
"f64.const": { form: "const", opcode: 0x44, immediates: ["float64"] },
|
|
98
|
+
|
|
99
|
+
// i32 comparison and test operations
|
|
100
|
+
"i32.eqz": { form: "nullary", opcode: 0x45 },
|
|
101
|
+
"i32.eq": { form: "nullary", opcode: 0x46 },
|
|
102
|
+
"i32.ne": { form: "nullary", opcode: 0x47 },
|
|
103
|
+
"i32.lt_s": { form: "nullary", opcode: 0x48 },
|
|
104
|
+
"i32.lt_u": { form: "nullary", opcode: 0x49 },
|
|
105
|
+
"i32.gt_s": { form: "nullary", opcode: 0x4a },
|
|
106
|
+
"i32.gt_u": { form: "nullary", opcode: 0x4b },
|
|
107
|
+
"i32.le_s": { form: "nullary", opcode: 0x4c },
|
|
108
|
+
"i32.le_u": { form: "nullary", opcode: 0x4d },
|
|
109
|
+
"i32.ge_s": { form: "nullary", opcode: 0x4e },
|
|
110
|
+
"i32.ge_u": { form: "nullary", opcode: 0x4f },
|
|
111
|
+
|
|
112
|
+
// i64 comparison and test operations
|
|
113
|
+
"i64.eqz": { form: "nullary", opcode: 0x50 },
|
|
114
|
+
"i64.eq": { form: "nullary", opcode: 0x51 },
|
|
115
|
+
"i64.ne": { form: "nullary", opcode: 0x52 },
|
|
116
|
+
"i64.lt_s": { form: "nullary", opcode: 0x53 },
|
|
117
|
+
"i64.lt_u": { form: "nullary", opcode: 0x54 },
|
|
118
|
+
"i64.gt_s": { form: "nullary", opcode: 0x55 },
|
|
119
|
+
"i64.gt_u": { form: "nullary", opcode: 0x56 },
|
|
120
|
+
"i64.le_s": { form: "nullary", opcode: 0x57 },
|
|
121
|
+
"i64.le_u": { form: "nullary", opcode: 0x58 },
|
|
122
|
+
"i64.ge_s": { form: "nullary", opcode: 0x59 },
|
|
123
|
+
"i64.ge_u": { form: "nullary", opcode: 0x5a },
|
|
124
|
+
|
|
125
|
+
// f32 comparison operations
|
|
126
|
+
"f32.eq": { form: "nullary", opcode: 0x5b },
|
|
127
|
+
"f32.ne": { form: "nullary", opcode: 0x5c },
|
|
128
|
+
"f32.lt": { form: "nullary", opcode: 0x5d },
|
|
129
|
+
"f32.gt": { form: "nullary", opcode: 0x5e },
|
|
130
|
+
"f32.le": { form: "nullary", opcode: 0x5f },
|
|
131
|
+
"f32.ge": { form: "nullary", opcode: 0x60 },
|
|
132
|
+
|
|
133
|
+
// f64 comparison operations
|
|
134
|
+
"f64.eq": { form: "nullary", opcode: 0x61 },
|
|
135
|
+
"f64.ne": { form: "nullary", opcode: 0x62 },
|
|
136
|
+
"f64.lt": { form: "nullary", opcode: 0x63 },
|
|
137
|
+
"f64.gt": { form: "nullary", opcode: 0x64 },
|
|
138
|
+
"f64.le": { form: "nullary", opcode: 0x65 },
|
|
139
|
+
"f64.ge": { form: "nullary", opcode: 0x66 },
|
|
140
|
+
|
|
141
|
+
// i32 arithmetic and bitwise operations
|
|
142
|
+
"i32.clz": { form: "nullary", opcode: 0x67 },
|
|
143
|
+
"i32.ctz": { form: "nullary", opcode: 0x68 },
|
|
144
|
+
"i32.popcnt": { form: "nullary", opcode: 0x69 },
|
|
145
|
+
"i32.add": { form: "nullary", opcode: 0x6a },
|
|
146
|
+
"i32.sub": { form: "nullary", opcode: 0x6b },
|
|
147
|
+
"i32.mul": { form: "nullary", opcode: 0x6c },
|
|
148
|
+
"i32.div_s": { form: "nullary", opcode: 0x6d },
|
|
149
|
+
"i32.div_u": { form: "nullary", opcode: 0x6e },
|
|
150
|
+
"i32.rem_s": { form: "nullary", opcode: 0x6f },
|
|
151
|
+
"i32.rem_u": { form: "nullary", opcode: 0x70 },
|
|
152
|
+
"i32.and": { form: "nullary", opcode: 0x71 },
|
|
153
|
+
"i32.or": { form: "nullary", opcode: 0x72 },
|
|
154
|
+
"i32.xor": { form: "nullary", opcode: 0x73 },
|
|
155
|
+
"i32.shl": { form: "nullary", opcode: 0x74 },
|
|
156
|
+
"i32.shr_s": { form: "nullary", opcode: 0x75 },
|
|
157
|
+
"i32.shr_u": { form: "nullary", opcode: 0x76 },
|
|
158
|
+
"i32.rotl": { form: "nullary", opcode: 0x77 },
|
|
159
|
+
"i32.rotr": { form: "nullary", opcode: 0x78 },
|
|
160
|
+
|
|
161
|
+
// i64 arithmetic and bitwise operations
|
|
162
|
+
"i64.clz": { form: "nullary", opcode: 0x79 },
|
|
163
|
+
"i64.ctz": { form: "nullary", opcode: 0x7a },
|
|
164
|
+
"i64.popcnt": { form: "nullary", opcode: 0x7b },
|
|
165
|
+
"i64.add": { form: "nullary", opcode: 0x7c },
|
|
166
|
+
"i64.sub": { form: "nullary", opcode: 0x7d },
|
|
167
|
+
"i64.mul": { form: "nullary", opcode: 0x7e },
|
|
168
|
+
"i64.div_s": { form: "nullary", opcode: 0x7f },
|
|
169
|
+
"i64.div_u": { form: "nullary", opcode: 0x80 },
|
|
170
|
+
"i64.rem_s": { form: "nullary", opcode: 0x81 },
|
|
171
|
+
"i64.rem_u": { form: "nullary", opcode: 0x82 },
|
|
172
|
+
"i64.and": { form: "nullary", opcode: 0x83 },
|
|
173
|
+
"i64.or": { form: "nullary", opcode: 0x84 },
|
|
174
|
+
"i64.xor": { form: "nullary", opcode: 0x85 },
|
|
175
|
+
"i64.shl": { form: "nullary", opcode: 0x86 },
|
|
176
|
+
"i64.shr_s": { form: "nullary", opcode: 0x87 },
|
|
177
|
+
"i64.shr_u": { form: "nullary", opcode: 0x88 },
|
|
178
|
+
"i64.rotl": { form: "nullary", opcode: 0x89 },
|
|
179
|
+
"i64.rotr": { form: "nullary", opcode: 0x8a },
|
|
180
|
+
|
|
181
|
+
// f32 arithmetic operations
|
|
182
|
+
"f32.abs": { form: "nullary", opcode: 0x8b },
|
|
183
|
+
"f32.neg": { form: "nullary", opcode: 0x8c },
|
|
184
|
+
"f32.ceil": { form: "nullary", opcode: 0x8d },
|
|
185
|
+
"f32.floor": { form: "nullary", opcode: 0x8e },
|
|
186
|
+
"f32.trunc": { form: "nullary", opcode: 0x8f },
|
|
187
|
+
"f32.nearest": { form: "nullary", opcode: 0x90 },
|
|
188
|
+
"f32.sqrt": { form: "nullary", opcode: 0x91 },
|
|
189
|
+
"f32.add": { form: "nullary", opcode: 0x92 },
|
|
190
|
+
"f32.sub": { form: "nullary", opcode: 0x93 },
|
|
191
|
+
"f32.mul": { form: "nullary", opcode: 0x94 },
|
|
192
|
+
"f32.div": { form: "nullary", opcode: 0x95 },
|
|
193
|
+
"f32.min": { form: "nullary", opcode: 0x96 },
|
|
194
|
+
"f32.max": { form: "nullary", opcode: 0x97 },
|
|
195
|
+
"f32.copysign": { form: "nullary", opcode: 0x98 },
|
|
196
|
+
|
|
197
|
+
// f64 arithmetic operations
|
|
198
|
+
"f64.abs": { form: "nullary", opcode: 0x99 },
|
|
199
|
+
"f64.neg": { form: "nullary", opcode: 0x9a },
|
|
200
|
+
"f64.ceil": { form: "nullary", opcode: 0x9b },
|
|
201
|
+
"f64.floor": { form: "nullary", opcode: 0x9c },
|
|
202
|
+
"f64.trunc": { form: "nullary", opcode: 0x9d },
|
|
203
|
+
"f64.nearest": { form: "nullary", opcode: 0x9e },
|
|
204
|
+
"f64.sqrt": { form: "nullary", opcode: 0x9f },
|
|
205
|
+
"f64.add": { form: "nullary", opcode: 0xa0 },
|
|
206
|
+
"f64.sub": { form: "nullary", opcode: 0xa1 },
|
|
207
|
+
"f64.mul": { form: "nullary", opcode: 0xa2 },
|
|
208
|
+
"f64.div": { form: "nullary", opcode: 0xa3 },
|
|
209
|
+
"f64.min": { form: "nullary", opcode: 0xa4 },
|
|
210
|
+
"f64.max": { form: "nullary", opcode: 0xa5 },
|
|
211
|
+
"f64.copysign": { form: "nullary", opcode: 0xa6 },
|
|
212
|
+
|
|
213
|
+
// Conversion operations
|
|
214
|
+
"i32.wrap_i64": { form: "nullary", opcode: 0xa7 },
|
|
215
|
+
"i32.trunc_s_f32": { form: "nullary", opcode: 0xa8 },
|
|
216
|
+
"i32.trunc_u_f32": { form: "nullary", opcode: 0xa9 },
|
|
217
|
+
"i32.trunc_s_f64": { form: "nullary", opcode: 0xaa },
|
|
218
|
+
"i32.trunc_u_f64": { form: "nullary", opcode: 0xab },
|
|
219
|
+
"i64.extend_i32_s": { form: "nullary", opcode: 0xac },
|
|
220
|
+
"i64.extend_i32_u": { form: "nullary", opcode: 0xad },
|
|
221
|
+
"i64.trunc_s_f32": { form: "nullary", opcode: 0xae },
|
|
222
|
+
"i64.trunc_u_f32": { form: "nullary", opcode: 0xaf },
|
|
223
|
+
"i64.trunc_s_f64": { form: "nullary", opcode: 0xb0 },
|
|
224
|
+
"i64.trunc_u_f64": { form: "nullary", opcode: 0xb1 },
|
|
225
|
+
"f32.convert_s_i32": { form: "nullary", opcode: 0xb2 },
|
|
226
|
+
"f32.convert_u_i32": { form: "nullary", opcode: 0xb3 },
|
|
227
|
+
"f32.convert_s_i64": { form: "nullary", opcode: 0xb4 },
|
|
228
|
+
"f32.convert_u_i64": { form: "nullary", opcode: 0xb5 },
|
|
229
|
+
"f32.demote_f64": { form: "nullary", opcode: 0xb6 },
|
|
230
|
+
"f64.convert_s_i32": { form: "nullary", opcode: 0xb7 },
|
|
231
|
+
"f64.convert_u_i32": { form: "nullary", opcode: 0xb8 },
|
|
232
|
+
"f64.convert_s_i64": { form: "nullary", opcode: 0xb9 },
|
|
233
|
+
"f64.convert_u_i64": { form: "nullary", opcode: 0xba },
|
|
234
|
+
"f64.promote_f32": { form: "nullary", opcode: 0xbb },
|
|
235
|
+
"i32.reinterpret_f32": { form: "nullary", opcode: 0xbc },
|
|
236
|
+
"i64.reinterpret_f64": { form: "nullary", opcode: 0xbd },
|
|
237
|
+
"f32.reinterpret_i32": { form: "nullary", opcode: 0xbe },
|
|
238
|
+
"f64.reinterpret_i64": { form: "nullary", opcode: 0xbf },
|
|
239
|
+
|
|
240
|
+
// Sign extension operations
|
|
241
|
+
"i32.extend8_s": { form: "nullary", opcode: 0xc0 },
|
|
242
|
+
"i32.extend16_s": { form: "nullary", opcode: 0xc1 },
|
|
243
|
+
"i64.extend8_s": { form: "nullary", opcode: 0xc2 },
|
|
244
|
+
"i64.extend16_s": { form: "nullary", opcode: 0xc3 },
|
|
245
|
+
"i64.extend32_s": { form: "nullary", opcode: 0xc4 },
|
|
246
|
+
|
|
247
|
+
// Reference instructions
|
|
248
|
+
"ref.is_null": { form: "nullary", opcode: 0xd1 },
|
|
249
|
+
"ref.eq": { form: "nullary", opcode: 0xd3 },
|
|
250
|
+
"ref.as_non_null": { form: "nullary", opcode: 0xd4 },
|
|
251
|
+
|
|
252
|
+
// SIMD Vector instructions (0xFD prefix + sub-opcode)
|
|
253
|
+
"v128.load": { form: "memory", opcode: 0xfd00 },
|
|
254
|
+
"v128.store": { form: "memory", opcode: 0xfd0b },
|
|
255
|
+
"i8x16.shuffle": { form: "unary", opcode: 0xfd0d, immediates: ["byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte"] },
|
|
256
|
+
"i32x4.splat": { form: "nullary", opcode: 0xfd11 },
|
|
257
|
+
"i32x4.extract_lane": { form: "unary", opcode: 0xfd1b, immediates: ["varuint32"] },
|
|
258
|
+
"i32x4.replace_lane": { form: "unary", opcode: 0xfd1c, immediates: ["varuint32"] },
|
|
259
|
+
"i32x4.mul": { form: "nullary", opcode: 0xfdb5 },
|
|
260
|
+
"i32x4.shr_u": { form: "nullary", opcode: 0xfdad },
|
|
261
|
+
"i32x4.and": { form: "nullary", opcode: 0xfd4e },
|
|
262
|
+
"i32x4.sub": { form: "nullary", opcode: 0xfdb1 },
|
|
263
|
+
"i32x4.max_u": { form: "nullary", opcode: 0xfdb9 },
|
|
264
|
+
"i32x4.min_u": { form: "nullary", opcode: 0xfdb7 },
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Type definitions for compilation
|
|
268
|
+
type FuncType = {
|
|
269
|
+
params: string[];
|
|
270
|
+
results: string[];
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
type FuncDef = {
|
|
274
|
+
typeIdx: number;
|
|
275
|
+
locals: string[];
|
|
276
|
+
localNames: string[]; // Names for params and locals (empty string if unnamed)
|
|
277
|
+
body: ASTNode[];
|
|
278
|
+
exportName?: string;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
type ExportDef = {
|
|
282
|
+
name: string;
|
|
283
|
+
kind: "func" | "table" | "memory" | "global";
|
|
284
|
+
index: number;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Compiler context
|
|
288
|
+
class CompilerContext {
|
|
289
|
+
buffer: number[] = [];
|
|
290
|
+
labelStack: string[] = [];
|
|
291
|
+
localMap: Map<string, number> = new Map();
|
|
292
|
+
functionMap: Map<string, number> = new Map();
|
|
293
|
+
|
|
294
|
+
// Module-level data
|
|
295
|
+
types: FuncType[] = [];
|
|
296
|
+
imports: { module: string; name: string; kind: string; type?: any }[] = [];
|
|
297
|
+
functions: FuncDef[] = [];
|
|
298
|
+
globals: { type: string; mutable: boolean; init: ASTNode[] }[] = [];
|
|
299
|
+
exports: ExportDef[] = [];
|
|
300
|
+
memoryPages?: { min: number; max?: number };
|
|
301
|
+
hasImportedMemory: boolean = false;
|
|
302
|
+
|
|
303
|
+
emit(byte: number) {
|
|
304
|
+
this.buffer.push(byte & 0xff);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
emitVarint32(value: number) {
|
|
308
|
+
// LEB128 signed 32-bit encoding
|
|
309
|
+
let more = true;
|
|
310
|
+
while (more) {
|
|
311
|
+
let byte = value & 0x7f;
|
|
312
|
+
value >>= 7;
|
|
313
|
+
if ((value === 0 && (byte & 0x40) === 0) || (value === -1 && (byte & 0x40) !== 0)) {
|
|
314
|
+
more = false;
|
|
315
|
+
} else {
|
|
316
|
+
byte |= 0x80;
|
|
317
|
+
}
|
|
318
|
+
this.emit(byte);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
emitVaruint32(value: number) {
|
|
323
|
+
// LEB128 unsigned 32-bit encoding
|
|
324
|
+
do {
|
|
325
|
+
let byte = value & 0x7f;
|
|
326
|
+
value >>>= 7;
|
|
327
|
+
if (value !== 0) {
|
|
328
|
+
byte |= 0x80;
|
|
329
|
+
}
|
|
330
|
+
this.emit(byte);
|
|
331
|
+
} while (value !== 0);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
emitFloat32(value: number) {
|
|
335
|
+
const buffer = new ArrayBuffer(4);
|
|
336
|
+
new DataView(buffer).setFloat32(0, value, true);
|
|
337
|
+
const bytes = new Uint8Array(buffer);
|
|
338
|
+
for (const byte of bytes) {
|
|
339
|
+
this.emit(byte);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
emitFloat64(value: number) {
|
|
344
|
+
const buffer = new ArrayBuffer(8);
|
|
345
|
+
new DataView(buffer).setFloat64(0, value, true);
|
|
346
|
+
const bytes = new Uint8Array(buffer);
|
|
347
|
+
for (const byte of bytes) {
|
|
348
|
+
this.emit(byte);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
emitString(str: string) {
|
|
353
|
+
const bytes = new TextEncoder().encode(str);
|
|
354
|
+
this.emitVaruint32(bytes.length);
|
|
355
|
+
for (const byte of bytes) {
|
|
356
|
+
this.emit(byte);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
emitSection(sectionId: number, contents: number[]) {
|
|
361
|
+
this.emit(sectionId);
|
|
362
|
+
this.emitVaruint32(contents.length);
|
|
363
|
+
for (const byte of contents) {
|
|
364
|
+
this.emit(byte);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
emitOpcode(opcode: number) {
|
|
369
|
+
// SIMD vector instructions use 0xFD prefix
|
|
370
|
+
if (opcode >= 0xfd00 && opcode <= 0xfdff) {
|
|
371
|
+
this.emit(0xfd);
|
|
372
|
+
this.emitVaruint32(opcode & 0xff);
|
|
373
|
+
} else {
|
|
374
|
+
this.emit(opcode);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Instruction form handlers
|
|
380
|
+
const FORM_HANDLERS = {
|
|
381
|
+
nullary: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
382
|
+
ctx.emitOpcode(meta.opcode);
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
unary: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
386
|
+
ctx.emitOpcode(meta.opcode);
|
|
387
|
+
if (!node.operands) return;
|
|
388
|
+
|
|
389
|
+
// Handle multiple immediates (e.g., i8x16.shuffle has 16 byte immediates)
|
|
390
|
+
if (meta.immediates && meta.immediates.length > 1) {
|
|
391
|
+
for (let i = 0; i < meta.immediates.length && i < node.operands.length; i++) {
|
|
392
|
+
const immType = meta.immediates[i];
|
|
393
|
+
const operand = node.operands[i];
|
|
394
|
+
if (operand && operand.type === "number") {
|
|
395
|
+
const value = typeof operand.value === "number" ? operand.value : parseInt(operand.value as string, 10);
|
|
396
|
+
if (immType === "byte") {
|
|
397
|
+
ctx.emit(value & 0xff);
|
|
398
|
+
} else if (immType === "varuint32") {
|
|
399
|
+
ctx.emitVaruint32(value);
|
|
400
|
+
} else if (immType === "varint32") {
|
|
401
|
+
ctx.emitVarint32(value);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Handle single immediate (original behavior)
|
|
409
|
+
const operand = node.operands[0];
|
|
410
|
+
if (operand && operand.type === "number") {
|
|
411
|
+
const value = typeof operand.value === "number" ? operand.value : parseInt(operand.value as string, 10);
|
|
412
|
+
ctx.emitVaruint32(value);
|
|
413
|
+
} else if (operand && operand.type === "identifier") {
|
|
414
|
+
if (!operand.name) return;
|
|
415
|
+
const numericIndex = parseInt(operand.name, 10);
|
|
416
|
+
if (!isNaN(numericIndex)) {
|
|
417
|
+
ctx.emitVaruint32(numericIndex);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const index = ctx.localMap.get(operand.name);
|
|
421
|
+
if (index === undefined) {
|
|
422
|
+
throw new Error(`Unknown local: ${operand.name}`);
|
|
423
|
+
}
|
|
424
|
+
ctx.emitVaruint32(index);
|
|
425
|
+
} else if (operand && operand.type === "index") {
|
|
426
|
+
if (operand.value === undefined) return;
|
|
427
|
+
ctx.emitVaruint32(operand.value as number);
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
const: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
432
|
+
ctx.emitOpcode(meta.opcode);
|
|
433
|
+
if (!node.operands) return;
|
|
434
|
+
const operand = node.operands[0];
|
|
435
|
+
if (operand && operand.type === "number") {
|
|
436
|
+
if (operand.value === undefined) return;
|
|
437
|
+
const value = typeof operand.value === "number" ? operand.value : parseFloat(operand.value);
|
|
438
|
+
if (meta.immediates && meta.immediates[0] === "varint32") {
|
|
439
|
+
ctx.emitVarint32(value);
|
|
440
|
+
} else if (meta.immediates && meta.immediates[0] === "varint64") {
|
|
441
|
+
// TODO: Handle 64-bit integers properly
|
|
442
|
+
ctx.emitVarint32(value);
|
|
443
|
+
} else if (meta.immediates && meta.immediates[0] === "float32") {
|
|
444
|
+
ctx.emitFloat32(value);
|
|
445
|
+
} else if (meta.immediates && meta.immediates[0] === "float64") {
|
|
446
|
+
ctx.emitFloat64(value);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
block: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
452
|
+
ctx.emitOpcode(meta.opcode);
|
|
453
|
+
// Block type (0x40 = empty/void)
|
|
454
|
+
ctx.emit(0x40);
|
|
455
|
+
// Note: Block body compilation is handled specially in compileInstruction
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
control: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
459
|
+
ctx.emitOpcode(meta.opcode);
|
|
460
|
+
if (!node.operands) return;
|
|
461
|
+
const operand = node.operands[0];
|
|
462
|
+
if (operand && operand.type === "identifier") {
|
|
463
|
+
// Find label depth in the stack
|
|
464
|
+
if (!operand.name) return;
|
|
465
|
+
const labelName = operand.name;
|
|
466
|
+
let depth = -1;
|
|
467
|
+
for (let i = ctx.labelStack.length - 1; i >= 0; i--) {
|
|
468
|
+
depth++;
|
|
469
|
+
if (ctx.labelStack[i] === labelName) {
|
|
470
|
+
ctx.emitVaruint32(depth);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
throw new Error(`Unknown label: ${labelName}`);
|
|
475
|
+
} else if (operand && operand.type === "index") {
|
|
476
|
+
if (operand.value === undefined) return;
|
|
477
|
+
ctx.emitVaruint32(operand.value as number);
|
|
478
|
+
} else if (operand && operand.type === "number") {
|
|
479
|
+
// Direct numeric depth
|
|
480
|
+
const value = typeof operand.value === "number" ? operand.value : parseInt(operand.value as string, 10);
|
|
481
|
+
ctx.emitVaruint32(value);
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
call: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
486
|
+
ctx.emitOpcode(meta.opcode);
|
|
487
|
+
if (!node.operands) return;
|
|
488
|
+
const operand = node.operands[0];
|
|
489
|
+
if (operand && operand.type === "identifier") {
|
|
490
|
+
if (!operand.name) return;
|
|
491
|
+
const index = ctx.functionMap.get(operand.name);
|
|
492
|
+
if (index === undefined) {
|
|
493
|
+
throw new Error(`Unknown function: ${operand.name}`);
|
|
494
|
+
}
|
|
495
|
+
ctx.emitVaruint32(index);
|
|
496
|
+
} else if (operand && operand.type === "index") {
|
|
497
|
+
if (operand.value === undefined) return;
|
|
498
|
+
ctx.emitVaruint32(operand.value as number);
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
binary: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
503
|
+
ctx.emitOpcode(meta.opcode);
|
|
504
|
+
// Handle two operands
|
|
505
|
+
// TODO: Implement based on specific instruction needs
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
memory: (ctx: CompilerContext, meta: InstructionMeta, node: ASTNode) => {
|
|
509
|
+
ctx.emitOpcode(meta.opcode);
|
|
510
|
+
// Emit alignment and offset for memory operations
|
|
511
|
+
// Alignment: log2 of natural alignment (e.g., 2 for i32/f32, 3 for i64/f64)
|
|
512
|
+
// Offset: byte offset from the calculated address
|
|
513
|
+
|
|
514
|
+
// Extract align and offset from attributes if present
|
|
515
|
+
let alignment: number | undefined = undefined;
|
|
516
|
+
let offset = 0;
|
|
517
|
+
|
|
518
|
+
if (node.attributes) {
|
|
519
|
+
for (const attr of node.attributes) {
|
|
520
|
+
if (attr.type === "attribute" && attr.name === "align") {
|
|
521
|
+
alignment = typeof attr.value === "number" ? attr.value : parseInt(attr.value as string, 10);
|
|
522
|
+
} else if (attr.type === "attribute" && attr.name === "offset") {
|
|
523
|
+
offset = typeof attr.value === "number" ? attr.value : parseInt(attr.value as string, 10);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// If no explicit alignment, use default based on instruction
|
|
529
|
+
if (alignment === undefined) {
|
|
530
|
+
// Default alignment based on instruction type
|
|
531
|
+
// i32/f32 loads/stores: 2 (4-byte alignment)
|
|
532
|
+
// i64/f64 loads/stores: 3 (8-byte alignment)
|
|
533
|
+
// i8 loads/stores: 0 (byte alignment)
|
|
534
|
+
// i16 loads/stores: 1 (2-byte alignment)
|
|
535
|
+
if (meta.opcode >= 0x28 && meta.opcode <= 0x2b || meta.opcode >= 0x36 && meta.opcode <= 0x39) {
|
|
536
|
+
alignment = 2; // i32/f32
|
|
537
|
+
} else if (meta.opcode >= 0x2c && meta.opcode <= 0x2d || meta.opcode === 0x3a) {
|
|
538
|
+
alignment = 0; // i8
|
|
539
|
+
} else if (meta.opcode >= 0x2e && meta.opcode <= 0x2f || meta.opcode === 0x3b) {
|
|
540
|
+
alignment = 1; // i16
|
|
541
|
+
} else {
|
|
542
|
+
alignment = 0; // Default to byte alignment for safety
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
ctx.emitVaruint32(alignment);
|
|
547
|
+
ctx.emitVaruint32(offset);
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
export class WATCompiler {
|
|
552
|
+
private ctx: CompilerContext;
|
|
553
|
+
|
|
554
|
+
constructor() {
|
|
555
|
+
this.ctx = new CompilerContext();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
compile(ast: ASTNode): Uint8Array {
|
|
559
|
+
// Two-pass compilation
|
|
560
|
+
// Pass 1: Collect types, functions, exports
|
|
561
|
+
this.collectModuleInfo(ast);
|
|
562
|
+
|
|
563
|
+
// Pass 2: Generate binary
|
|
564
|
+
this.generateBinary();
|
|
565
|
+
|
|
566
|
+
return new Uint8Array(this.ctx.buffer);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private collectModuleInfo(node: ASTNode) {
|
|
570
|
+
if (node.type === "module") {
|
|
571
|
+
if (node.items) {
|
|
572
|
+
for (const item of node.items) {
|
|
573
|
+
this.collectModuleInfo(item);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} else if (node.type === "sexpr") {
|
|
577
|
+
this.collectSExpr(node);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private collectSExpr(node: ASTNode) {
|
|
582
|
+
if (!node.keyword) return;
|
|
583
|
+
|
|
584
|
+
const keyword = node.keyword;
|
|
585
|
+
|
|
586
|
+
if (keyword === "module") {
|
|
587
|
+
if (node.items) {
|
|
588
|
+
for (const item of node.items) {
|
|
589
|
+
this.collectModuleInfo(item);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} else if (keyword === "func") {
|
|
593
|
+
this.collectFunction(node);
|
|
594
|
+
} else if (keyword === "memory") {
|
|
595
|
+
this.collectMemory(node);
|
|
596
|
+
} else if (keyword === "export") {
|
|
597
|
+
this.collectExport(node);
|
|
598
|
+
} else if (keyword === "import") {
|
|
599
|
+
this.collectImport(node);
|
|
600
|
+
} else if (keyword === "global") {
|
|
601
|
+
this.collectGlobal(node);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private generateBinary() {
|
|
606
|
+
// WebAssembly binary magic number
|
|
607
|
+
this.ctx.emit(0x00);
|
|
608
|
+
this.ctx.emit(0x61);
|
|
609
|
+
this.ctx.emit(0x73);
|
|
610
|
+
this.ctx.emit(0x6d);
|
|
611
|
+
|
|
612
|
+
// Version 1
|
|
613
|
+
this.ctx.emit(0x01);
|
|
614
|
+
this.ctx.emit(0x00);
|
|
615
|
+
this.ctx.emit(0x00);
|
|
616
|
+
this.ctx.emit(0x00);
|
|
617
|
+
|
|
618
|
+
// Generate sections (order matters!)
|
|
619
|
+
this.generateTypeSection();
|
|
620
|
+
this.generateImportSection();
|
|
621
|
+
this.generateFunctionSection();
|
|
622
|
+
this.generateMemorySection();
|
|
623
|
+
this.generateGlobalSection();
|
|
624
|
+
this.generateExportSection();
|
|
625
|
+
this.generateCodeSection();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private collectFunction(node: ASTNode) {
|
|
629
|
+
if (!node.items) return;
|
|
630
|
+
|
|
631
|
+
const params: string[] = [];
|
|
632
|
+
const results: string[] = [];
|
|
633
|
+
const locals: string[] = [];
|
|
634
|
+
const body: ASTNode[] = [];
|
|
635
|
+
let exportName: string | undefined = undefined;
|
|
636
|
+
let funcName: string | undefined = undefined;
|
|
637
|
+
|
|
638
|
+
const paramNames: string[] = [];
|
|
639
|
+
|
|
640
|
+
// Check if first item is a function name (e.g., $hashUnitToIndex)
|
|
641
|
+
if (node.items[0] && node.items[0].type === "identifier" && node.items[0].name && node.items[0].name.startsWith("$")) {
|
|
642
|
+
funcName = node.items[0].name;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const item of node.items) {
|
|
646
|
+
if (item.type === "sexpr" && item.keyword === "param") {
|
|
647
|
+
if (item.items) {
|
|
648
|
+
// Check if this is a named parameter: (param $name type)
|
|
649
|
+
if (item.items.length === 2 &&
|
|
650
|
+
item.items[0].type === "identifier" &&
|
|
651
|
+
item.items[0].name &&
|
|
652
|
+
item.items[0].name.startsWith("$")) {
|
|
653
|
+
// Named parameter
|
|
654
|
+
paramNames.push(item.items[0].name);
|
|
655
|
+
if (item.items[1].type === "identifier" && item.items[1].name) {
|
|
656
|
+
params.push(item.items[1].name);
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
// Unnamed parameters: (param type1 type2 ...)
|
|
660
|
+
for (const paramItem of item.items) {
|
|
661
|
+
if (paramItem.type === "identifier" && paramItem.name) {
|
|
662
|
+
params.push(paramItem.name);
|
|
663
|
+
paramNames.push(""); // No name for this param
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} else if (item.type === "sexpr" && item.keyword === "result") {
|
|
669
|
+
if (item.items) {
|
|
670
|
+
for (const resultItem of item.items) {
|
|
671
|
+
if (resultItem.type === "identifier" && resultItem.name) {
|
|
672
|
+
results.push(resultItem.name);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
} else if (item.type === "sexpr" && item.keyword === "local") {
|
|
677
|
+
if (item.items) {
|
|
678
|
+
// Check if this is a named local: (local $name type)
|
|
679
|
+
if (item.items.length === 2 &&
|
|
680
|
+
item.items[0].type === "identifier" &&
|
|
681
|
+
item.items[0].name &&
|
|
682
|
+
item.items[0].name.startsWith("$")) {
|
|
683
|
+
// Named local
|
|
684
|
+
paramNames.push(item.items[0].name);
|
|
685
|
+
if (item.items[1].type === "identifier" && item.items[1].name) {
|
|
686
|
+
locals.push(item.items[1].name);
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
// Unnamed locals: (local type1 type2 ...)
|
|
690
|
+
for (const localItem of item.items) {
|
|
691
|
+
if (localItem.type === "identifier" && localItem.name) {
|
|
692
|
+
locals.push(localItem.name);
|
|
693
|
+
paramNames.push(""); // No name for this local
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
} else if (item.type === "sexpr" && item.keyword === "export") {
|
|
699
|
+
if (item.items && item.items[0] && item.items[0].type === "string") {
|
|
700
|
+
exportName = item.items[0].value as string;
|
|
701
|
+
}
|
|
702
|
+
} else if (item.type === "sexpr") {
|
|
703
|
+
body.push(item);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Find or create type
|
|
708
|
+
const funcType: FuncType = { params, results };
|
|
709
|
+
let typeIdx = this.ctx.types.findIndex(t =>
|
|
710
|
+
JSON.stringify(t) === JSON.stringify(funcType)
|
|
711
|
+
);
|
|
712
|
+
if (typeIdx === -1) {
|
|
713
|
+
typeIdx = this.ctx.types.length;
|
|
714
|
+
this.ctx.types.push(funcType);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const funcIdx = this.ctx.functions.length;
|
|
718
|
+
this.ctx.functions.push({ typeIdx, locals, localNames: paramNames, body, exportName });
|
|
719
|
+
|
|
720
|
+
// Register function name for internal calls
|
|
721
|
+
if (funcName) {
|
|
722
|
+
this.ctx.functionMap.set(funcName, funcIdx);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (exportName) {
|
|
726
|
+
this.ctx.exports.push({
|
|
727
|
+
name: exportName,
|
|
728
|
+
kind: "func",
|
|
729
|
+
index: funcIdx
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private collectMemory(node: ASTNode) {
|
|
735
|
+
if (!node.items) return;
|
|
736
|
+
|
|
737
|
+
let min = 1;
|
|
738
|
+
let max: number | undefined = undefined;
|
|
739
|
+
|
|
740
|
+
if (node.items[0] && node.items[0].type === "number") {
|
|
741
|
+
min = parseInt(node.items[0].value as string, 10);
|
|
742
|
+
}
|
|
743
|
+
if (node.items[1] && node.items[1].type === "number") {
|
|
744
|
+
max = parseInt(node.items[1].value as string, 10);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
this.ctx.memoryPages = { min, max };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private collectImport(node: ASTNode) {
|
|
751
|
+
if (!node.items || node.items.length < 3) return;
|
|
752
|
+
|
|
753
|
+
// (import "module" "name" (kind ...))
|
|
754
|
+
const moduleItem = node.items[0];
|
|
755
|
+
const nameItem = node.items[1];
|
|
756
|
+
const descItem = node.items[2];
|
|
757
|
+
|
|
758
|
+
if (moduleItem.type !== "string" || nameItem.type !== "string") return;
|
|
759
|
+
if (descItem.type !== "sexpr" || !descItem.keyword) return;
|
|
760
|
+
|
|
761
|
+
const module = moduleItem.value as string;
|
|
762
|
+
const name = nameItem.value as string;
|
|
763
|
+
const kind = descItem.keyword;
|
|
764
|
+
|
|
765
|
+
if (kind === "memory") {
|
|
766
|
+
this.ctx.hasImportedMemory = true;
|
|
767
|
+
// Memory imports: (import "env" "memory" (memory i64 1))
|
|
768
|
+
// For now, just mark that we have an imported memory
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
this.ctx.imports.push({ module, name, kind, type: descItem });
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
private collectGlobal(node: ASTNode) {
|
|
775
|
+
if (!node.items) return;
|
|
776
|
+
|
|
777
|
+
// Check if this is an exported global: (global (export "name") type (init))
|
|
778
|
+
let exportName: string | undefined = undefined;
|
|
779
|
+
let type = "i32";
|
|
780
|
+
const mutable = false;
|
|
781
|
+
const init: ASTNode[] = [];
|
|
782
|
+
|
|
783
|
+
for (const item of node.items) {
|
|
784
|
+
if (item.type === "sexpr" && item.keyword === "export") {
|
|
785
|
+
if (item.items && item.items[0] && item.items[0].type === "string") {
|
|
786
|
+
exportName = item.items[0].value as string;
|
|
787
|
+
}
|
|
788
|
+
} else if (item.type === "identifier" && item.name) {
|
|
789
|
+
// Type like i32, i64, etc
|
|
790
|
+
type = item.name;
|
|
791
|
+
} else if (item.type === "sexpr") {
|
|
792
|
+
// Initialization expression
|
|
793
|
+
init.push(item);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const globalIdx = this.ctx.globals.length;
|
|
798
|
+
this.ctx.globals.push({ type, mutable, init });
|
|
799
|
+
|
|
800
|
+
if (exportName) {
|
|
801
|
+
this.ctx.exports.push({
|
|
802
|
+
name: exportName,
|
|
803
|
+
kind: "global",
|
|
804
|
+
index: globalIdx
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private collectExport(node: ASTNode) {
|
|
810
|
+
// Exports are collected inline with functions for now
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private generateTypeSection() {
|
|
814
|
+
if (this.ctx.types.length === 0) return;
|
|
815
|
+
|
|
816
|
+
const section: number[] = [];
|
|
817
|
+
const tempCtx = new CompilerContext();
|
|
818
|
+
|
|
819
|
+
tempCtx.emitVaruint32(this.ctx.types.length);
|
|
820
|
+
|
|
821
|
+
for (const funcType of this.ctx.types) {
|
|
822
|
+
tempCtx.emit(0x60); // func type
|
|
823
|
+
tempCtx.emitVaruint32(funcType.params.length);
|
|
824
|
+
for (const param of funcType.params) {
|
|
825
|
+
tempCtx.emit(this.getTypeCode(param));
|
|
826
|
+
}
|
|
827
|
+
tempCtx.emitVaruint32(funcType.results.length);
|
|
828
|
+
for (const result of funcType.results) {
|
|
829
|
+
tempCtx.emit(this.getTypeCode(result));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
this.ctx.emitSection(1, tempCtx.buffer);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private generateFunctionSection() {
|
|
837
|
+
if (this.ctx.functions.length === 0) return;
|
|
838
|
+
|
|
839
|
+
const tempCtx = new CompilerContext();
|
|
840
|
+
tempCtx.emitVaruint32(this.ctx.functions.length);
|
|
841
|
+
|
|
842
|
+
for (const func of this.ctx.functions) {
|
|
843
|
+
tempCtx.emitVaruint32(func.typeIdx);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
this.ctx.emitSection(3, tempCtx.buffer);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
private generateImportSection() {
|
|
850
|
+
if (this.ctx.imports.length === 0) return;
|
|
851
|
+
|
|
852
|
+
const tempCtx = new CompilerContext();
|
|
853
|
+
tempCtx.emitVaruint32(this.ctx.imports.length);
|
|
854
|
+
|
|
855
|
+
for (const imp of this.ctx.imports) {
|
|
856
|
+
// Module name
|
|
857
|
+
tempCtx.emitString(imp.module);
|
|
858
|
+
// Field name
|
|
859
|
+
tempCtx.emitString(imp.name);
|
|
860
|
+
|
|
861
|
+
// Import kind and type
|
|
862
|
+
if (imp.kind === "memory") {
|
|
863
|
+
tempCtx.emit(0x02); // memory import kind
|
|
864
|
+
|
|
865
|
+
// Parse memory descriptor: (memory [i64|i32] min [max])
|
|
866
|
+
// For now, ignore i64/i32 and just parse limits
|
|
867
|
+
let is64 = false;
|
|
868
|
+
let min = 1;
|
|
869
|
+
let max: number | undefined = undefined;
|
|
870
|
+
|
|
871
|
+
if (imp.type && imp.type.items) {
|
|
872
|
+
let numbersSeen = 0;
|
|
873
|
+
for (let i = 0; i < imp.type.items.length; i++) {
|
|
874
|
+
const item = imp.type.items[i];
|
|
875
|
+
if (item.type === "identifier" && item.name === "i64") {
|
|
876
|
+
is64 = true;
|
|
877
|
+
} else if (item.type === "identifier" && item.name === "i32") {
|
|
878
|
+
is64 = false;
|
|
879
|
+
} else if (item.type === "number") {
|
|
880
|
+
if (numbersSeen === 0) {
|
|
881
|
+
min = parseInt(item.value as string, 10);
|
|
882
|
+
} else if (numbersSeen === 1) {
|
|
883
|
+
max = parseInt(item.value as string, 10);
|
|
884
|
+
}
|
|
885
|
+
numbersSeen++;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Emit memory limits with correct flags
|
|
891
|
+
// Flags: bit 0 = has_max, bit 2 = is_64 (memory64)
|
|
892
|
+
let flags = 0;
|
|
893
|
+
if (max !== undefined) flags |= 0x01; // has max
|
|
894
|
+
if (is64) flags |= 0x04; // memory64
|
|
895
|
+
|
|
896
|
+
tempCtx.emit(flags);
|
|
897
|
+
tempCtx.emitVaruint32(min);
|
|
898
|
+
if (max !== undefined) {
|
|
899
|
+
tempCtx.emitVaruint32(max);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// TODO: Handle other import kinds (func, table, global)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
this.ctx.emitSection(2, tempCtx.buffer);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private generateGlobalSection() {
|
|
909
|
+
if (this.ctx.globals.length === 0) return;
|
|
910
|
+
|
|
911
|
+
const tempCtx = new CompilerContext();
|
|
912
|
+
tempCtx.emitVaruint32(this.ctx.globals.length);
|
|
913
|
+
|
|
914
|
+
for (const global of this.ctx.globals) {
|
|
915
|
+
// Global type
|
|
916
|
+
tempCtx.emit(this.getTypeCode(global.type));
|
|
917
|
+
// Mutability
|
|
918
|
+
tempCtx.emit(global.mutable ? 0x01 : 0x00);
|
|
919
|
+
|
|
920
|
+
// Init expression
|
|
921
|
+
for (const initExpr of global.init) {
|
|
922
|
+
this.compileInstruction(tempCtx, initExpr);
|
|
923
|
+
}
|
|
924
|
+
tempCtx.emit(0x0b); // end of init expression
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
this.ctx.emitSection(6, tempCtx.buffer);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private generateMemorySection() {
|
|
931
|
+
if (!this.ctx.memoryPages || this.ctx.hasImportedMemory) return;
|
|
932
|
+
|
|
933
|
+
const tempCtx = new CompilerContext();
|
|
934
|
+
tempCtx.emitVaruint32(1); // number of memories
|
|
935
|
+
|
|
936
|
+
if (this.ctx.memoryPages.max !== undefined) {
|
|
937
|
+
tempCtx.emit(0x01); // has max
|
|
938
|
+
tempCtx.emitVaruint32(this.ctx.memoryPages.min);
|
|
939
|
+
tempCtx.emitVaruint32(this.ctx.memoryPages.max);
|
|
940
|
+
} else {
|
|
941
|
+
tempCtx.emit(0x00); // no max
|
|
942
|
+
tempCtx.emitVaruint32(this.ctx.memoryPages.min);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
this.ctx.emitSection(5, tempCtx.buffer);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
private generateExportSection() {
|
|
949
|
+
if (this.ctx.exports.length === 0) return;
|
|
950
|
+
|
|
951
|
+
const tempCtx = new CompilerContext();
|
|
952
|
+
tempCtx.emitVaruint32(this.ctx.exports.length);
|
|
953
|
+
|
|
954
|
+
for (const exp of this.ctx.exports) {
|
|
955
|
+
tempCtx.emitString(exp.name);
|
|
956
|
+
const kindCode = exp.kind === "func" ? 0x00 :
|
|
957
|
+
exp.kind === "table" ? 0x01 :
|
|
958
|
+
exp.kind === "memory" ? 0x02 : 0x03;
|
|
959
|
+
tempCtx.emit(kindCode);
|
|
960
|
+
tempCtx.emitVaruint32(exp.index);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
this.ctx.emitSection(7, tempCtx.buffer);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private generateCodeSection() {
|
|
967
|
+
if (this.ctx.functions.length === 0) return;
|
|
968
|
+
|
|
969
|
+
const tempCtx = new CompilerContext();
|
|
970
|
+
tempCtx.emitVaruint32(this.ctx.functions.length);
|
|
971
|
+
|
|
972
|
+
for (const func of this.ctx.functions) {
|
|
973
|
+
const funcBody = this.compileFunctionBody(func);
|
|
974
|
+
tempCtx.emitVaruint32(funcBody.length);
|
|
975
|
+
for (const byte of funcBody) {
|
|
976
|
+
tempCtx.emit(byte);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
this.ctx.emitSection(10, tempCtx.buffer);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
private compileFunctionBody(func: FuncDef): number[] {
|
|
984
|
+
const bodyCtx = new CompilerContext();
|
|
985
|
+
|
|
986
|
+
// Copy function map from module context for internal calls
|
|
987
|
+
bodyCtx.functionMap = this.ctx.functionMap;
|
|
988
|
+
|
|
989
|
+
// Build local map (params first, then locals)
|
|
990
|
+
const funcType = this.ctx.types[func.typeIdx];
|
|
991
|
+
let localIdx = 0;
|
|
992
|
+
|
|
993
|
+
// Map parameter names and indices
|
|
994
|
+
for (let i = 0; i < funcType.params.length; i++) {
|
|
995
|
+
if (func.localNames[i] && func.localNames[i] !== "") {
|
|
996
|
+
bodyCtx.localMap.set(func.localNames[i], localIdx);
|
|
997
|
+
}
|
|
998
|
+
// Also allow numeric access
|
|
999
|
+
bodyCtx.localMap.set(`${i}`, localIdx);
|
|
1000
|
+
localIdx++;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Group locals by type (required by WASM format)
|
|
1004
|
+
const localGroups: Map<string, number> = new Map();
|
|
1005
|
+
for (const local of func.locals) {
|
|
1006
|
+
const count = localGroups.get(local) || 0;
|
|
1007
|
+
localGroups.set(local, count + 1);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Map local variable names based on GROUPED order
|
|
1011
|
+
for (const [type, count] of localGroups) {
|
|
1012
|
+
for (let i = 0; i < func.locals.length; i++) {
|
|
1013
|
+
if (func.locals[i] === type) {
|
|
1014
|
+
const nameIdx = funcType.params.length + i;
|
|
1015
|
+
if (func.localNames[nameIdx] && func.localNames[nameIdx] !== "") {
|
|
1016
|
+
bodyCtx.localMap.set(func.localNames[nameIdx], localIdx);
|
|
1017
|
+
}
|
|
1018
|
+
localIdx++;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Emit locals count (grouped by type)
|
|
1024
|
+
bodyCtx.emitVaruint32(localGroups.size);
|
|
1025
|
+
for (const [type, count] of localGroups) {
|
|
1026
|
+
bodyCtx.emitVaruint32(count);
|
|
1027
|
+
bodyCtx.emit(this.getTypeCode(type));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Compile function body
|
|
1031
|
+
for (const instr of func.body) {
|
|
1032
|
+
this.compileInstruction(bodyCtx, instr);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Emit end opcode
|
|
1036
|
+
bodyCtx.emit(0x0b);
|
|
1037
|
+
|
|
1038
|
+
return bodyCtx.buffer;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private compileInstruction(ctx: CompilerContext, node: ASTNode) {
|
|
1042
|
+
if (!node.keyword) return;
|
|
1043
|
+
|
|
1044
|
+
// Special handling for block/loop/if structures
|
|
1045
|
+
if (node.keyword === "block" || node.keyword === "loop") {
|
|
1046
|
+
// Emit opcode
|
|
1047
|
+
ctx.emit(node.keyword === "block" ? 0x02 : 0x03);
|
|
1048
|
+
// Emit blocktype (0x40 = void)
|
|
1049
|
+
ctx.emit(0x40);
|
|
1050
|
+
|
|
1051
|
+
// Find and push label onto stack
|
|
1052
|
+
let label = "";
|
|
1053
|
+
if (node.items && node.items[0] && node.items[0].type === "identifier" && node.items[0].name) {
|
|
1054
|
+
label = node.items[0].name;
|
|
1055
|
+
}
|
|
1056
|
+
ctx.labelStack.push(label);
|
|
1057
|
+
|
|
1058
|
+
// Compile body (skip first item if it's a label like $break or $continue)
|
|
1059
|
+
if (node.items) {
|
|
1060
|
+
for (const item of node.items) {
|
|
1061
|
+
// Skip identifiers (labels)
|
|
1062
|
+
if (item.type === "identifier") continue;
|
|
1063
|
+
if (item.type === "sexpr" && item.keyword) {
|
|
1064
|
+
this.compileInstruction(ctx, item);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
ctx.labelStack.pop(); // Pop label
|
|
1070
|
+
ctx.emit(0x0b); // end opcode
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (node.keyword === "if") {
|
|
1075
|
+
if (node.items) {
|
|
1076
|
+
let thenIdx = -1;
|
|
1077
|
+
let elseIdx = -1;
|
|
1078
|
+
let resultType: string | undefined = undefined;
|
|
1079
|
+
|
|
1080
|
+
// Find then, else, and result clauses
|
|
1081
|
+
for (let i = 0; i < node.items.length; i++) {
|
|
1082
|
+
const item = node.items[i];
|
|
1083
|
+
if (item.type === "sexpr" && item.keyword === "then") {
|
|
1084
|
+
thenIdx = i;
|
|
1085
|
+
} else if (item.type === "sexpr" && item.keyword === "else") {
|
|
1086
|
+
elseIdx = i;
|
|
1087
|
+
} else if (item.type === "sexpr" && item.keyword === "result") {
|
|
1088
|
+
// Extract result type from (result i32)
|
|
1089
|
+
if (item.items && item.items.length > 0) {
|
|
1090
|
+
const typeItem = item.items[0];
|
|
1091
|
+
if (typeItem.type === "identifier" && typeof typeItem.value === "string") {
|
|
1092
|
+
resultType = typeItem.value;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Compile condition BEFORE emitting if opcode (instructions before then)
|
|
1099
|
+
for (let i = 0; i < (thenIdx >= 0 ? thenIdx : node.items.length); i++) {
|
|
1100
|
+
const item = node.items[i];
|
|
1101
|
+
if (item.type === "sexpr" && item.keyword && item.keyword !== "then" && item.keyword !== "else" && item.keyword !== "result") {
|
|
1102
|
+
this.compileInstruction(ctx, item);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Now emit if opcode
|
|
1107
|
+
ctx.emit(0x04); // if opcode
|
|
1108
|
+
// Emit blocktype: 0x40 for void, or type code for result type
|
|
1109
|
+
if (resultType) {
|
|
1110
|
+
ctx.emit(this.getTypeCode(resultType));
|
|
1111
|
+
} else {
|
|
1112
|
+
ctx.emit(0x40); // blocktype (void)
|
|
1113
|
+
}
|
|
1114
|
+
ctx.labelStack.push(""); // if blocks have no label
|
|
1115
|
+
|
|
1116
|
+
// Compile then clause
|
|
1117
|
+
if (thenIdx >= 0) {
|
|
1118
|
+
const thenClause = node.items[thenIdx];
|
|
1119
|
+
if (thenClause && thenClause.items) {
|
|
1120
|
+
for (const item of thenClause.items) {
|
|
1121
|
+
if (item.type === "sexpr") {
|
|
1122
|
+
if (item.keyword) {
|
|
1123
|
+
this.compileInstruction(ctx, item);
|
|
1124
|
+
}
|
|
1125
|
+
} else if (item.type === "instruction") {
|
|
1126
|
+
this.compileInstruction(ctx, item);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Compile else clause if present
|
|
1133
|
+
if (elseIdx >= 0) {
|
|
1134
|
+
ctx.emit(0x05); // else opcode
|
|
1135
|
+
const elseClause = node.items[elseIdx];
|
|
1136
|
+
if (elseClause && elseClause.items) {
|
|
1137
|
+
for (const item of elseClause.items) {
|
|
1138
|
+
if (item.type === "sexpr") {
|
|
1139
|
+
if (item.keyword) {
|
|
1140
|
+
this.compileInstruction(ctx, item);
|
|
1141
|
+
}
|
|
1142
|
+
} else if (item.type === "instruction") {
|
|
1143
|
+
this.compileInstruction(ctx, item);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
ctx.labelStack.pop(); // Pop if label
|
|
1150
|
+
ctx.emit(0x0b); // end opcode
|
|
1151
|
+
}
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const meta = INSTRUCTION_MAP[node.keyword];
|
|
1156
|
+
if (!meta) {
|
|
1157
|
+
throw new Error(`Unknown instruction: ${node.keyword}`);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const handler = FORM_HANDLERS[meta.form];
|
|
1161
|
+
if (!handler) {
|
|
1162
|
+
throw new Error(`No handler for form: ${meta.form}`);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Compile nested S-expressions BEFORE emitting the parent instruction (depth-first)
|
|
1166
|
+
// This is correct for stack machine semantics
|
|
1167
|
+
// Skip for block/loop which handle their own bodies
|
|
1168
|
+
if (node.keyword !== "block" && node.keyword !== "loop" && node.items) {
|
|
1169
|
+
for (const item of node.items) {
|
|
1170
|
+
if (item.type === "sexpr" && item.keyword) {
|
|
1171
|
+
this.compileInstruction(ctx, item);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const instructionNode = {
|
|
1177
|
+
...node,
|
|
1178
|
+
operands: node.items || [],
|
|
1179
|
+
attributes: node.attributes || []
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
handler(ctx, meta, instructionNode);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
private getTypeCode(typeName: string): number {
|
|
1186
|
+
switch (typeName) {
|
|
1187
|
+
case "i32": return 0x7f;
|
|
1188
|
+
case "i64": return 0x7e;
|
|
1189
|
+
case "f32": return 0x7d;
|
|
1190
|
+
case "f64": return 0x7c;
|
|
1191
|
+
case "v128": return 0x7b;
|
|
1192
|
+
default: throw new Error(`Unknown type: ${typeName}`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Cache the parser so we don't regenerate it every time
|
|
1198
|
+
let cachedParser: { parse: (input: string) => ASTNode } | undefined = undefined;
|
|
1199
|
+
|
|
1200
|
+
function getParser(): { parse: (input: string) => ASTNode } {
|
|
1201
|
+
if (cachedParser === undefined) {
|
|
1202
|
+
const grammarPath = path.join(__dirname, "watGrammar.pegjs");
|
|
1203
|
+
const grammarSource = fs.readFileSync(grammarPath, "utf-8");
|
|
1204
|
+
cachedParser = peggy.generate(grammarSource) as { parse: (input: string) => ASTNode };
|
|
1205
|
+
}
|
|
1206
|
+
return cachedParser;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
export function parseWAT(source: string): ASTNode {
|
|
1210
|
+
const parser = getParser();
|
|
1211
|
+
try {
|
|
1212
|
+
return parser.parse(source);
|
|
1213
|
+
} catch (e: unknown) {
|
|
1214
|
+
if (e && typeof e === "object" && "location" in e) {
|
|
1215
|
+
const error = e as { location: { start: { line: number; column: number } }; message: string };
|
|
1216
|
+
const loc = error.location.start;
|
|
1217
|
+
throw new Error(`Parse error at line ${loc.line}, column ${loc.column}: ${error.message}`);
|
|
1218
|
+
}
|
|
1219
|
+
throw e;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
export function compileWAT(source: string): Uint8Array {
|
|
1224
|
+
const ast = parseWAT(source);
|
|
1225
|
+
const compiler = new WATCompiler();
|
|
1226
|
+
return compiler.compile(ast);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Test function to verify the WAT compiler works
|
|
1230
|
+
export async function testWATCompiler() {
|
|
1231
|
+
console.log("Testing WAT compiler...\n");
|
|
1232
|
+
|
|
1233
|
+
// Test 1: Simple arithmetic function
|
|
1234
|
+
const simpleWAT = `
|
|
1235
|
+
(module
|
|
1236
|
+
(func (export "add") (param i32 i32) (result i32)
|
|
1237
|
+
(local.get 0)
|
|
1238
|
+
(local.get 1)
|
|
1239
|
+
(i32.add)
|
|
1240
|
+
)
|
|
1241
|
+
)
|
|
1242
|
+
`;
|
|
1243
|
+
|
|
1244
|
+
try {
|
|
1245
|
+
const wasmBytes = compileWAT(simpleWAT);
|
|
1246
|
+
console.log(`✓ Compiled ${wasmBytes.length} bytes`);
|
|
1247
|
+
|
|
1248
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1249
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1250
|
+
|
|
1251
|
+
const add = wasmInstance.exports.add as (a: number, b: number) => number;
|
|
1252
|
+
const result = add(5, 7);
|
|
1253
|
+
|
|
1254
|
+
if (result === 12) {
|
|
1255
|
+
console.log("✓ Test 1 passed: add(5, 7) = 12");
|
|
1256
|
+
} else {
|
|
1257
|
+
console.error(`✗ Test 1 failed: add(5, 7) = ${result}, expected 12`);
|
|
1258
|
+
}
|
|
1259
|
+
} catch (e) {
|
|
1260
|
+
console.error("✗ Test 1 failed with error:", e);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Test 2: Conditional logic with select
|
|
1264
|
+
const conditionalWAT = `
|
|
1265
|
+
(module
|
|
1266
|
+
(func (export "max") (param i32 i32) (result i32)
|
|
1267
|
+
(local.get 0)
|
|
1268
|
+
(local.get 1)
|
|
1269
|
+
(local.get 0)
|
|
1270
|
+
(local.get 1)
|
|
1271
|
+
(i32.gt_s)
|
|
1272
|
+
(select)
|
|
1273
|
+
)
|
|
1274
|
+
)
|
|
1275
|
+
`;
|
|
1276
|
+
|
|
1277
|
+
try {
|
|
1278
|
+
const wasmBytes = compileWAT(conditionalWAT);
|
|
1279
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1280
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1281
|
+
|
|
1282
|
+
const max = wasmInstance.exports.max as (a: number, b: number) => number;
|
|
1283
|
+
const result1 = max(10, 5);
|
|
1284
|
+
const result2 = max(3, 8);
|
|
1285
|
+
|
|
1286
|
+
if (result1 === 10 && result2 === 8) {
|
|
1287
|
+
console.log("✓ Test 2 passed: max(10, 5) = 10, max(3, 8) = 8");
|
|
1288
|
+
} else {
|
|
1289
|
+
console.error(`✗ Test 2 failed: max(10, 5) = ${result1}, max(3, 8) = ${result2}`);
|
|
1290
|
+
}
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
console.error("✗ Test 2 failed with error:", e);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Test 3: Multiple operations
|
|
1296
|
+
const multiOpWAT = `
|
|
1297
|
+
(module
|
|
1298
|
+
(func (export "compute") (param i32) (result i32)
|
|
1299
|
+
(local.get 0)
|
|
1300
|
+
(i32.const 10)
|
|
1301
|
+
(i32.mul)
|
|
1302
|
+
(i32.const 5)
|
|
1303
|
+
(i32.add)
|
|
1304
|
+
)
|
|
1305
|
+
)
|
|
1306
|
+
`;
|
|
1307
|
+
|
|
1308
|
+
try {
|
|
1309
|
+
const wasmBytes = compileWAT(multiOpWAT);
|
|
1310
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1311
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1312
|
+
|
|
1313
|
+
const compute = wasmInstance.exports.compute as (a: number) => number;
|
|
1314
|
+
const result = compute(3);
|
|
1315
|
+
|
|
1316
|
+
if (result === 35) {
|
|
1317
|
+
console.log("✓ Test 3 passed: compute(3) = 35 (3 * 10 + 5)");
|
|
1318
|
+
} else {
|
|
1319
|
+
console.error(`✗ Test 3 failed: compute(3) = ${result}, expected 35`);
|
|
1320
|
+
}
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
console.error("✗ Test 3 failed with error:", e);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// Test 4: Subtraction and division
|
|
1326
|
+
const mathWAT = `
|
|
1327
|
+
(module
|
|
1328
|
+
(func (export "calculate") (param i32 i32) (result i32)
|
|
1329
|
+
(local.get 0)
|
|
1330
|
+
(local.get 1)
|
|
1331
|
+
(i32.sub)
|
|
1332
|
+
(i32.const 2)
|
|
1333
|
+
(i32.div_s)
|
|
1334
|
+
)
|
|
1335
|
+
)
|
|
1336
|
+
`;
|
|
1337
|
+
|
|
1338
|
+
try {
|
|
1339
|
+
const wasmBytes = compileWAT(mathWAT);
|
|
1340
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1341
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1342
|
+
|
|
1343
|
+
const calculate = wasmInstance.exports.calculate as (a: number, b: number) => number;
|
|
1344
|
+
const result = calculate(20, 10);
|
|
1345
|
+
|
|
1346
|
+
if (result === 5) {
|
|
1347
|
+
console.log("✓ Test 4 passed: calculate(20, 10) = 5 ((20 - 10) / 2)");
|
|
1348
|
+
} else {
|
|
1349
|
+
console.error(`✗ Test 4 failed: calculate(20, 10) = ${result}, expected 5`);
|
|
1350
|
+
}
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
console.error("✗ Test 4 failed with error:", e);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Test 5: Local.set with named parameters
|
|
1356
|
+
const localSetWAT = `
|
|
1357
|
+
(module
|
|
1358
|
+
(func (export "test") (param $a i32) (result i32)
|
|
1359
|
+
(local $b i32)
|
|
1360
|
+
(local.set $b (i32.add (local.get $a) (i32.const 5)))
|
|
1361
|
+
(local.get $b)
|
|
1362
|
+
)
|
|
1363
|
+
)
|
|
1364
|
+
`;
|
|
1365
|
+
|
|
1366
|
+
try {
|
|
1367
|
+
const wasmBytes = compileWAT(localSetWAT);
|
|
1368
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1369
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1370
|
+
|
|
1371
|
+
const test = wasmInstance.exports.test as (a: number) => number;
|
|
1372
|
+
const result = test(10);
|
|
1373
|
+
|
|
1374
|
+
if (result === 15) {
|
|
1375
|
+
console.log("✓ Test 5 passed: test(10) = 15 (10 + 5)");
|
|
1376
|
+
} else {
|
|
1377
|
+
console.error(`✗ Test 5 failed: test(10) = ${result}, expected 15`);
|
|
1378
|
+
}
|
|
1379
|
+
} catch (e) {
|
|
1380
|
+
console.error("✗ Test 5 failed with error:", e);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Test 6: Block with local.set
|
|
1384
|
+
const blockWAT = `
|
|
1385
|
+
(module
|
|
1386
|
+
(func (export "test") (param $a i32) (result i32)
|
|
1387
|
+
(local $b i32)
|
|
1388
|
+
(local.set $b (i32.const 0))
|
|
1389
|
+
(block $myblock
|
|
1390
|
+
(local.set $b (i32.add (local.get $a) (i32.const 10)))
|
|
1391
|
+
)
|
|
1392
|
+
(local.get $b)
|
|
1393
|
+
)
|
|
1394
|
+
)
|
|
1395
|
+
`;
|
|
1396
|
+
|
|
1397
|
+
try {
|
|
1398
|
+
const wasmBytes = compileWAT(blockWAT);
|
|
1399
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1400
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1401
|
+
|
|
1402
|
+
const test = wasmInstance.exports.test as (a: number) => number;
|
|
1403
|
+
const result = test(5);
|
|
1404
|
+
|
|
1405
|
+
if (result === 15) {
|
|
1406
|
+
console.log("✓ Test 6 passed: test(5) = 15 (block with local.set)");
|
|
1407
|
+
} else {
|
|
1408
|
+
console.error(`✗ Test 6 failed: test(5) = ${result}, expected 15`);
|
|
1409
|
+
}
|
|
1410
|
+
} catch (e) {
|
|
1411
|
+
console.error("✗ Test 6 failed with error:", e);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Test 7: Many parameters and locals
|
|
1415
|
+
const manyLocalsWAT = `
|
|
1416
|
+
(module
|
|
1417
|
+
(func (export "test")
|
|
1418
|
+
(param $a i32) (param $b i32) (param $c i32) (param $d i32)
|
|
1419
|
+
(local $e i32) (local $f i32) (local $g i32)
|
|
1420
|
+
(local.set $e (i32.const 1))
|
|
1421
|
+
(local.set $f (i32.const 2))
|
|
1422
|
+
(local.set $g (i32.add (local.get $e) (local.get $f)))
|
|
1423
|
+
)
|
|
1424
|
+
)
|
|
1425
|
+
`;
|
|
1426
|
+
|
|
1427
|
+
try {
|
|
1428
|
+
const wasmBytes = compileWAT(manyLocalsWAT);
|
|
1429
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1430
|
+
console.log("✓ Test 7 passed: many parameters and locals compiled");
|
|
1431
|
+
} catch (e) {
|
|
1432
|
+
console.error("✗ Test 7 failed with error:", e);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// Test 8: BufferIndex excerpt
|
|
1436
|
+
const bufferIndexExcerpt = `(module
|
|
1437
|
+
(func (export "phase1_count_deduplicate")
|
|
1438
|
+
(param $unitRefsPtr i32)
|
|
1439
|
+
(param $totalUnits i32)
|
|
1440
|
+
(param $hashTablePtr i32)
|
|
1441
|
+
(param $hashTableCapacity i32)
|
|
1442
|
+
(param $uniqueUnitListPtr i32)
|
|
1443
|
+
(param $maxUniqueCount i32)
|
|
1444
|
+
(result i32)
|
|
1445
|
+
|
|
1446
|
+
(local $i i32)
|
|
1447
|
+
(local $hashTableSize i32)
|
|
1448
|
+
|
|
1449
|
+
(local.set $hashTableSize
|
|
1450
|
+
(i32.mul (local.get $hashTableCapacity) (i32.const 16)))
|
|
1451
|
+
|
|
1452
|
+
(local.set $i (i32.const 0))
|
|
1453
|
+
|
|
1454
|
+
(local.get $i)
|
|
1455
|
+
)
|
|
1456
|
+
)`;
|
|
1457
|
+
|
|
1458
|
+
try {
|
|
1459
|
+
const wasmBytes = compileWAT(bufferIndexExcerpt);
|
|
1460
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1461
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1462
|
+
const fn = wasmInstance.exports.phase1_count_deduplicate as (...args: number[]) => number;
|
|
1463
|
+
const result = fn(0, 0, 0, 16, 0, 0);
|
|
1464
|
+
console.log(`✓ Test 8 passed: BufferIndex excerpt compiled, result = ${result}`);
|
|
1465
|
+
} catch (e) {
|
|
1466
|
+
console.error("✗ Test 8 failed with error:", e);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// Test 9: Memory operations (i32.store)
|
|
1470
|
+
const memoryWAT = `(module
|
|
1471
|
+
(memory 1)
|
|
1472
|
+
(func (export "test") (result i32)
|
|
1473
|
+
(local $addr i32)
|
|
1474
|
+
(local.set $addr (i32.const 0))
|
|
1475
|
+
(i32.store (local.get $addr) (i32.const 42))
|
|
1476
|
+
(i32.load (local.get $addr))
|
|
1477
|
+
)
|
|
1478
|
+
)`;
|
|
1479
|
+
|
|
1480
|
+
try {
|
|
1481
|
+
const wasmBytes = compileWAT(memoryWAT);
|
|
1482
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1483
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1484
|
+
const fn = wasmInstance.exports.test as () => number;
|
|
1485
|
+
const result = fn();
|
|
1486
|
+
if (result === 42) {
|
|
1487
|
+
console.log("✓ Test 9 passed: memory operations work");
|
|
1488
|
+
} else {
|
|
1489
|
+
console.error(`✗ Test 9 failed: expected 42, got ${result}`);
|
|
1490
|
+
}
|
|
1491
|
+
} catch (e) {
|
|
1492
|
+
console.error("✗ Test 9 failed with error:", e);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// Test 10: Loop with if/then
|
|
1496
|
+
const loopIfWAT = `(module
|
|
1497
|
+
(func (export "test") (param $max i32) (result i32)
|
|
1498
|
+
(local $i i32)
|
|
1499
|
+
(local $sum i32)
|
|
1500
|
+
(local.set $i (i32.const 0))
|
|
1501
|
+
(local.set $sum (i32.const 0))
|
|
1502
|
+
(block $break
|
|
1503
|
+
(loop $continue
|
|
1504
|
+
(br_if $break (i32.ge_u (local.get $i) (local.get $max)))
|
|
1505
|
+
(if (i32.eq (i32.rem_u (local.get $i) (i32.const 2)) (i32.const 0))
|
|
1506
|
+
(then
|
|
1507
|
+
(local.set $sum (i32.add (local.get $sum) (local.get $i)))
|
|
1508
|
+
)
|
|
1509
|
+
)
|
|
1510
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1511
|
+
(br $continue)
|
|
1512
|
+
)
|
|
1513
|
+
)
|
|
1514
|
+
(local.get $sum)
|
|
1515
|
+
)
|
|
1516
|
+
)`;
|
|
1517
|
+
|
|
1518
|
+
try {
|
|
1519
|
+
const wasmBytes = compileWAT(loopIfWAT);
|
|
1520
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1521
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule);
|
|
1522
|
+
const fn = wasmInstance.exports.test as (max: number) => number;
|
|
1523
|
+
const result = fn(10);
|
|
1524
|
+
// Sum of even numbers 0,2,4,6,8 = 20
|
|
1525
|
+
if (result === 20) {
|
|
1526
|
+
console.log("✓ Test 10 passed: loop with if/then works");
|
|
1527
|
+
} else {
|
|
1528
|
+
console.error(`✗ Test 10 failed: expected 20, got ${result}`);
|
|
1529
|
+
}
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
console.error("✗ Test 10 failed with error:", e);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Test 11: Nested multi-line expression in memory operation
|
|
1535
|
+
const nestedLoadWAT = `(module
|
|
1536
|
+
(memory 1)
|
|
1537
|
+
(func (export "test") (param $ptr i32) (param $i i32) (result i32)
|
|
1538
|
+
(local $unit i32)
|
|
1539
|
+
(local.set $unit
|
|
1540
|
+
(i32.load (i32.add (local.get $ptr)
|
|
1541
|
+
(i32.mul (local.get $i) (i32.const 12)))))
|
|
1542
|
+
(local.get $unit)
|
|
1543
|
+
)
|
|
1544
|
+
)`;
|
|
1545
|
+
|
|
1546
|
+
try {
|
|
1547
|
+
const wasmBytes = compileWAT(nestedLoadWAT);
|
|
1548
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1549
|
+
console.log("✓ Test 11 passed: nested multi-line expression compiled");
|
|
1550
|
+
} catch (e) {
|
|
1551
|
+
console.error("✗ Test 11 failed with error:", e);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Test 12: Memory64 with i64 pointers
|
|
1555
|
+
const memory64WAT = `(module
|
|
1556
|
+
(import "env" "memory" (memory i64 1))
|
|
1557
|
+
(func (export "write_read") (param $ptr i64) (param $value i32) (result i32)
|
|
1558
|
+
(i32.store (local.get $ptr) (local.get $value))
|
|
1559
|
+
(i32.load (local.get $ptr))
|
|
1560
|
+
)
|
|
1561
|
+
)`;
|
|
1562
|
+
|
|
1563
|
+
try {
|
|
1564
|
+
const wasmBytes = compileWAT(memory64WAT);
|
|
1565
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1566
|
+
const memory = new WebAssembly.Memory({ initial: BigInt(1), address: "i64" } as any);
|
|
1567
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule, { env: { memory } });
|
|
1568
|
+
const fn = wasmInstance.exports.write_read as (ptr: bigint, value: number) => number;
|
|
1569
|
+
const result = fn(BigInt(0), 42);
|
|
1570
|
+
if (result === 42) {
|
|
1571
|
+
console.log("✓ Test 12 passed: memory64 write/read with i64 pointers");
|
|
1572
|
+
} else {
|
|
1573
|
+
console.error(`✗ Test 12 failed: expected 42, got ${result}`);
|
|
1574
|
+
}
|
|
1575
|
+
} catch (e) {
|
|
1576
|
+
console.error("✗ Test 12 failed with error:", e);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Test 13: Memory64 with i64 address arithmetic
|
|
1580
|
+
const memory64ArithmeticWAT = `(module
|
|
1581
|
+
(import "env" "memory" (memory i64 1))
|
|
1582
|
+
(func (export "store_at_offset") (param $base i64) (param $offset i32) (param $value i32)
|
|
1583
|
+
(i32.store
|
|
1584
|
+
(i64.add (local.get $base) (i64.extend_i32_u (local.get $offset)))
|
|
1585
|
+
(local.get $value))
|
|
1586
|
+
)
|
|
1587
|
+
(func (export "load_from_offset") (param $base i64) (param $offset i32) (result i32)
|
|
1588
|
+
(i32.load
|
|
1589
|
+
(i64.add (local.get $base) (i64.extend_i32_u (local.get $offset))))
|
|
1590
|
+
)
|
|
1591
|
+
)`;
|
|
1592
|
+
|
|
1593
|
+
try {
|
|
1594
|
+
const wasmBytes = compileWAT(memory64ArithmeticWAT);
|
|
1595
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1596
|
+
const memory = new WebAssembly.Memory({ initial: BigInt(1), address: "i64" } as any);
|
|
1597
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule, { env: { memory } });
|
|
1598
|
+
const store = wasmInstance.exports.store_at_offset as (base: bigint, offset: number, value: number) => void;
|
|
1599
|
+
const load = wasmInstance.exports.load_from_offset as (base: bigint, offset: number) => number;
|
|
1600
|
+
|
|
1601
|
+
store(BigInt(100), 8, 123);
|
|
1602
|
+
const result = load(BigInt(100), 8);
|
|
1603
|
+
if (result === 123) {
|
|
1604
|
+
console.log("✓ Test 13 passed: memory64 with i64 address arithmetic");
|
|
1605
|
+
} else {
|
|
1606
|
+
console.error(`✗ Test 13 failed: expected 123, got ${result}`);
|
|
1607
|
+
}
|
|
1608
|
+
} catch (e) {
|
|
1609
|
+
console.error("✗ Test 13 failed with error:", e);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Test 14: Memory64 with array-like access pattern
|
|
1613
|
+
const memory64ArrayWAT = `(module
|
|
1614
|
+
(import "env" "memory" (memory i64 1))
|
|
1615
|
+
(func (export "write_array") (param $basePtr i64) (param $index i32) (param $value i32)
|
|
1616
|
+
(i32.store
|
|
1617
|
+
(i64.add (local.get $basePtr)
|
|
1618
|
+
(i64.extend_i32_u (i32.mul (local.get $index) (i32.const 4))))
|
|
1619
|
+
(local.get $value))
|
|
1620
|
+
)
|
|
1621
|
+
(func (export "read_array") (param $basePtr i64) (param $index i32) (result i32)
|
|
1622
|
+
(i32.load
|
|
1623
|
+
(i64.add (local.get $basePtr)
|
|
1624
|
+
(i64.extend_i32_u (i32.mul (local.get $index) (i32.const 4)))))
|
|
1625
|
+
)
|
|
1626
|
+
(func (export "sum_array") (param $basePtr i64) (param $count i32) (result i32)
|
|
1627
|
+
(local $i i32)
|
|
1628
|
+
(local $sum i32)
|
|
1629
|
+
(local.set $i (i32.const 0))
|
|
1630
|
+
(local.set $sum (i32.const 0))
|
|
1631
|
+
(block $break
|
|
1632
|
+
(loop $continue
|
|
1633
|
+
(br_if $break (i32.ge_u (local.get $i) (local.get $count)))
|
|
1634
|
+
(local.set $sum
|
|
1635
|
+
(i32.add (local.get $sum)
|
|
1636
|
+
(i32.load
|
|
1637
|
+
(i64.add (local.get $basePtr)
|
|
1638
|
+
(i64.extend_i32_u (i32.mul (local.get $i) (i32.const 4)))))))
|
|
1639
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1640
|
+
(br $continue)
|
|
1641
|
+
)
|
|
1642
|
+
)
|
|
1643
|
+
(local.get $sum)
|
|
1644
|
+
)
|
|
1645
|
+
)`;
|
|
1646
|
+
|
|
1647
|
+
try {
|
|
1648
|
+
const wasmBytes = compileWAT(memory64ArrayWAT);
|
|
1649
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1650
|
+
const memory = new WebAssembly.Memory({ initial: BigInt(1), address: "i64" } as any);
|
|
1651
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule, { env: { memory } });
|
|
1652
|
+
const writeArray = wasmInstance.exports.write_array as (basePtr: bigint, index: number, value: number) => void;
|
|
1653
|
+
const readArray = wasmInstance.exports.read_array as (basePtr: bigint, index: number) => number;
|
|
1654
|
+
const sumArray = wasmInstance.exports.sum_array as (basePtr: bigint, count: number) => number;
|
|
1655
|
+
|
|
1656
|
+
// Write values [10, 20, 30, 40, 50]
|
|
1657
|
+
writeArray(BigInt(1000), 0, 10);
|
|
1658
|
+
writeArray(BigInt(1000), 1, 20);
|
|
1659
|
+
writeArray(BigInt(1000), 2, 30);
|
|
1660
|
+
writeArray(BigInt(1000), 3, 40);
|
|
1661
|
+
writeArray(BigInt(1000), 4, 50);
|
|
1662
|
+
|
|
1663
|
+
// Verify reads
|
|
1664
|
+
const val0 = readArray(BigInt(1000), 0);
|
|
1665
|
+
const val2 = readArray(BigInt(1000), 2);
|
|
1666
|
+
const val4 = readArray(BigInt(1000), 4);
|
|
1667
|
+
|
|
1668
|
+
// Sum array
|
|
1669
|
+
const sum = sumArray(BigInt(1000), 5);
|
|
1670
|
+
|
|
1671
|
+
if (val0 === 10 && val2 === 30 && val4 === 50 && sum === 150) {
|
|
1672
|
+
console.log("✓ Test 14 passed: memory64 array operations (sum = 150)");
|
|
1673
|
+
} else {
|
|
1674
|
+
console.error(`✗ Test 14 failed: val0=${val0}, val2=${val2}, val4=${val4}, sum=${sum}`);
|
|
1675
|
+
}
|
|
1676
|
+
} catch (e) {
|
|
1677
|
+
console.error("✗ Test 14 failed with error:", e);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// Test 15: Memory64 large offset (beyond 4GB-like address)
|
|
1681
|
+
const memory64LargeOffsetWAT = `(module
|
|
1682
|
+
(import "env" "memory" (memory i64 1))
|
|
1683
|
+
(func (export "test_large_ptr") (param $ptr i64) (param $value i32) (result i32)
|
|
1684
|
+
(i32.store (local.get $ptr) (local.get $value))
|
|
1685
|
+
(i32.load (local.get $ptr))
|
|
1686
|
+
)
|
|
1687
|
+
)`;
|
|
1688
|
+
|
|
1689
|
+
try {
|
|
1690
|
+
const wasmBytes = compileWAT(memory64LargeOffsetWAT);
|
|
1691
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
1692
|
+
const memory = new WebAssembly.Memory({ initial: BigInt(1), address: "i64" } as any);
|
|
1693
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule, { env: { memory } });
|
|
1694
|
+
const fn = wasmInstance.exports.test_large_ptr as (ptr: bigint, value: number) => number;
|
|
1695
|
+
|
|
1696
|
+
// Test with a large address within first page (< 64KB)
|
|
1697
|
+
const largeAddr = BigInt(60000);
|
|
1698
|
+
const result = fn(largeAddr, 999);
|
|
1699
|
+
if (result === 999) {
|
|
1700
|
+
console.log("✓ Test 15 passed: memory64 with large address (60000)");
|
|
1701
|
+
} else {
|
|
1702
|
+
console.error(`✗ Test 15 failed: expected 999, got ${result}`);
|
|
1703
|
+
}
|
|
1704
|
+
} catch (e) {
|
|
1705
|
+
console.error("✗ Test 15 failed with error:", e);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// Test 16: Instruction count
|
|
1709
|
+
const totalInstructions = Object.keys(INSTRUCTION_MAP).length;
|
|
1710
|
+
console.log(`\n✓ Total WebAssembly instructions loaded: ${totalInstructions}`);
|
|
1711
|
+
|
|
1712
|
+
console.log("\nWAT compiler tests complete!");
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// Run tests
|
|
1716
|
+
//testWATCompiler().catch(console.error);
|