gofront 1.3.12 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -20
- package/package.json +2 -1
- package/src/{codegen → backend/js}/expressions.js +217 -60
- package/src/{codegen → backend/js}/index.js +47 -36
- package/src/{codegen → backend/js}/runtime.js +11 -0
- package/src/{codegen → backend/js}/statements.js +43 -48
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/index.js +3 -0
- package/src/{codegen → backend/js}/templ.js +31 -2
- package/src/backend/wasm/boundary.js +1345 -0
- package/src/backend/wasm/emit.js +5779 -0
- package/src/backend/wasm/encode.js +789 -0
- package/src/backend/wasm/glue.js +130 -0
- package/src/backend/wasm/index.js +1413 -0
- package/src/backend/wasm/types.js +420 -0
- package/src/backend/wasm/wat.js +255 -0
- package/src/cli-core.js +52 -7
- package/src/compiler.js +328 -12
- package/src/dev-server.js +56 -15
- package/src/index.js +17 -9
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +36 -0
- package/src/lower/captures.js +179 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +169 -0
- package/src/lower/functions.js +30 -0
- package/src/lower/globals.js +324 -0
- package/src/lower/index.js +115 -0
- package/src/lower/ownership.js +142 -0
- package/src/lower/range.js +70 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +4 -1
- package/src/templ-lexer.js +107 -3
- package/src/templ-parser.js +29 -0
- package/src/test-runner.js +74 -20
- package/src/typechecker/expressions.js +40 -1
- package/src/typechecker/index.js +301 -6
- package/src/typechecker/statements.js +7 -0
- package/src/typechecker/stdlib/core.js +17 -0
- package/src/typechecker/types.js +56 -2
- /package/src/{codegen → backend/js}/source-map.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
// src/backend/wasm/types.js
|
|
2
|
+
// Maps GoFront types to WasmGC types and manages type registry.
|
|
3
|
+
|
|
4
|
+
// `*testing.T` is a JS-side object (the harness's __GoFront_T) passed through
|
|
5
|
+
// the boundary as an opaque externref.
|
|
6
|
+
export function isTestingT(goType) {
|
|
7
|
+
if (!goType) return false;
|
|
8
|
+
const base =
|
|
9
|
+
goType.kind === "pointer" || goType.kind === "PointerType"
|
|
10
|
+
? goType.base
|
|
11
|
+
: goType.kind === "StarExpr"
|
|
12
|
+
? (goType.expr ?? goType.operand)
|
|
13
|
+
: null;
|
|
14
|
+
return base?.name === "testing.T";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toWasmType(goType, checker = null, mod = null) {
|
|
18
|
+
if (!goType) return "i32";
|
|
19
|
+
if (isTestingT(goType)) return "externref";
|
|
20
|
+
|
|
21
|
+
// Pointer AST nodes
|
|
22
|
+
if (goType.kind === "PointerType") {
|
|
23
|
+
return toWasmType({ kind: "pointer", base: goType.base }, checker, mod);
|
|
24
|
+
}
|
|
25
|
+
if (goType.kind === "StarExpr") {
|
|
26
|
+
return toWasmType(
|
|
27
|
+
{ kind: "pointer", base: goType.expr ?? goType.operand },
|
|
28
|
+
checker,
|
|
29
|
+
mod,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Slice and Array AST nodes
|
|
34
|
+
if (goType.kind === "SliceType") {
|
|
35
|
+
return toWasmType({ kind: "slice", elem: goType.elem }, checker, mod);
|
|
36
|
+
}
|
|
37
|
+
if (goType.kind === "ArrayType") {
|
|
38
|
+
return toWasmType({ kind: "array", elem: goType.elem }, checker, mod);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// TypeName AST node
|
|
42
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
43
|
+
if (mod?.getStructType(goType.name)) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "ref",
|
|
46
|
+
nullable: true,
|
|
47
|
+
typeIndex: mod.getStructType(goType.name).typeIndex,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const resolved = checker?.types?.get(goType.name);
|
|
51
|
+
if (resolved) {
|
|
52
|
+
return toWasmType(resolved, checker, mod);
|
|
53
|
+
}
|
|
54
|
+
return toWasmType({ kind: "basic", name: goType.name }, checker, mod);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Unwrap named types
|
|
58
|
+
if (goType.kind === "named") {
|
|
59
|
+
if (mod?.getStructType(goType.name)) {
|
|
60
|
+
return {
|
|
61
|
+
kind: "ref",
|
|
62
|
+
nullable: true,
|
|
63
|
+
typeIndex: mod.getStructType(goType.name).typeIndex,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (goType.underlying) {
|
|
67
|
+
return toWasmType(goType.underlying, checker, mod);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Untyped constants
|
|
72
|
+
if (goType.kind === "untyped") {
|
|
73
|
+
switch (goType.base) {
|
|
74
|
+
case "int":
|
|
75
|
+
return "i64"; // Default Go integer is int (i64 in WASM subset)
|
|
76
|
+
case "float64":
|
|
77
|
+
return "f64";
|
|
78
|
+
case "string":
|
|
79
|
+
return "externref";
|
|
80
|
+
case "bool":
|
|
81
|
+
return "i32";
|
|
82
|
+
default:
|
|
83
|
+
return "i64";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (goType.kind === "basic") {
|
|
88
|
+
switch (goType.name) {
|
|
89
|
+
case "bool":
|
|
90
|
+
case "int8":
|
|
91
|
+
case "int16":
|
|
92
|
+
case "int32":
|
|
93
|
+
case "uint8":
|
|
94
|
+
case "uint16":
|
|
95
|
+
case "uint32":
|
|
96
|
+
case "byte":
|
|
97
|
+
case "rune":
|
|
98
|
+
return "i32";
|
|
99
|
+
|
|
100
|
+
case "int":
|
|
101
|
+
case "uint":
|
|
102
|
+
case "int64":
|
|
103
|
+
case "uint64":
|
|
104
|
+
case "uintptr":
|
|
105
|
+
return "i64";
|
|
106
|
+
|
|
107
|
+
case "float32":
|
|
108
|
+
return "f32";
|
|
109
|
+
|
|
110
|
+
case "float64":
|
|
111
|
+
return "f64";
|
|
112
|
+
|
|
113
|
+
case "string":
|
|
114
|
+
return "externref";
|
|
115
|
+
|
|
116
|
+
case "any":
|
|
117
|
+
return "anyref";
|
|
118
|
+
|
|
119
|
+
case "void":
|
|
120
|
+
return null;
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
return "i64";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (goType.kind === "pointer") {
|
|
128
|
+
// Struct pointer or boxed scalar
|
|
129
|
+
if (goType.base) {
|
|
130
|
+
const baseWType = toWasmType(goType.base, checker, mod);
|
|
131
|
+
if (
|
|
132
|
+
typeof baseWType === "object" &&
|
|
133
|
+
baseWType !== null &&
|
|
134
|
+
baseWType.kind === "ref"
|
|
135
|
+
) {
|
|
136
|
+
return baseWType;
|
|
137
|
+
}
|
|
138
|
+
if (mod?.getBoxType) {
|
|
139
|
+
const box = mod.getBoxType(goType.base);
|
|
140
|
+
if (box)
|
|
141
|
+
return { kind: "ref", nullable: true, typeIndex: box.typeIndex };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { kind: "ref", nullable: true, heapType: "any" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (goType.kind === "slice") {
|
|
148
|
+
if (mod?.getSliceType && goType.elem) {
|
|
149
|
+
const sliceInfo = mod.getSliceType(goType.elem);
|
|
150
|
+
return {
|
|
151
|
+
kind: "ref",
|
|
152
|
+
nullable: true,
|
|
153
|
+
typeIndex: sliceInfo.typeIndex,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (goType.kind === "array") {
|
|
160
|
+
if (mod?.getArrayType && goType.elem) {
|
|
161
|
+
const arrInfo = mod.getArrayType(goType.elem);
|
|
162
|
+
return {
|
|
163
|
+
kind: "ref",
|
|
164
|
+
nullable: true,
|
|
165
|
+
typeIndex: arrInfo.typeIndex,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return { kind: "ref", nullable: true, heapType: "array" };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (goType.kind === "struct") {
|
|
172
|
+
if (goType.name && mod?.getStructType(goType.name)) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "ref",
|
|
175
|
+
nullable: true,
|
|
176
|
+
typeIndex: mod.getStructType(goType.name).typeIndex,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isFuncType(goType, checker)) {
|
|
183
|
+
if (mod) {
|
|
184
|
+
const closureInfo = mod.getClosureType(goType);
|
|
185
|
+
return {
|
|
186
|
+
kind: "ref",
|
|
187
|
+
nullable: true,
|
|
188
|
+
typeIndex: closureInfo.typeIndex,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return "funcref";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (goType.kind === "interface" || goType.kind === "InterfaceType") {
|
|
195
|
+
return "anyref";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return "i32";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function isStructType(goType, checker = null, mod = null) {
|
|
202
|
+
if (!goType) return false;
|
|
203
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
204
|
+
if (mod?.getStructType?.(goType.name)) return true;
|
|
205
|
+
const resolved = checker?.types?.get(goType.name);
|
|
206
|
+
if (resolved) return isStructType(resolved, checker, mod);
|
|
207
|
+
}
|
|
208
|
+
if (goType.kind === "named") {
|
|
209
|
+
if (mod?.getStructType?.(goType.name)) return true;
|
|
210
|
+
if (goType.underlying) return isStructType(goType.underlying, checker, mod);
|
|
211
|
+
}
|
|
212
|
+
return goType.kind === "struct" || goType.kind === "StructType";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function isPointerToStruct(goType, checker = null, mod = null) {
|
|
216
|
+
if (!goType) return false;
|
|
217
|
+
if (goType.kind === "PointerType" || goType.kind === "pointer") {
|
|
218
|
+
return isStructType(goType.base, checker, mod);
|
|
219
|
+
}
|
|
220
|
+
if (goType.kind === "StarExpr") {
|
|
221
|
+
return isStructType(goType.expr ?? goType.operand, checker, mod);
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function isSliceType(goType, checker = null) {
|
|
227
|
+
if (!goType) return false;
|
|
228
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
229
|
+
const resolved = checker?.types?.get(goType.name);
|
|
230
|
+
if (resolved) return isSliceType(resolved, checker);
|
|
231
|
+
}
|
|
232
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
233
|
+
return isSliceType(goType.underlying, checker);
|
|
234
|
+
}
|
|
235
|
+
return goType.kind === "slice" || goType.kind === "SliceType";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function isArrayType(goType, checker = null) {
|
|
239
|
+
if (!goType) return false;
|
|
240
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
241
|
+
const resolved = checker?.types?.get(goType.name);
|
|
242
|
+
if (resolved) return isArrayType(resolved, checker);
|
|
243
|
+
}
|
|
244
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
245
|
+
return isArrayType(goType.underlying, checker);
|
|
246
|
+
}
|
|
247
|
+
return goType.kind === "array" || goType.kind === "ArrayType";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function isStringType(goType, checker = null) {
|
|
251
|
+
if (!goType) return false;
|
|
252
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
253
|
+
const resolved = checker?.types?.get(goType.name);
|
|
254
|
+
if (resolved) return isStringType(resolved, checker);
|
|
255
|
+
return goType.name === "string";
|
|
256
|
+
}
|
|
257
|
+
if (goType.kind === "named") {
|
|
258
|
+
if (goType.name === "string") return true;
|
|
259
|
+
if (goType.underlying) return isStringType(goType.underlying, checker);
|
|
260
|
+
}
|
|
261
|
+
if (goType.kind === "basic") return goType.name === "string";
|
|
262
|
+
if (goType.kind === "untyped") return goType.base === "string";
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function isAnyType(goType, checker = null) {
|
|
267
|
+
if (!goType) return false;
|
|
268
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
269
|
+
const resolved = checker?.types?.get(goType.name);
|
|
270
|
+
if (resolved) return isAnyType(resolved, checker);
|
|
271
|
+
return goType.name === "any" || goType.name === "interface{}";
|
|
272
|
+
}
|
|
273
|
+
if (goType.kind === "named") {
|
|
274
|
+
if (goType.name === "any" || goType.name === "interface{}") return true;
|
|
275
|
+
if (goType.underlying) return isAnyType(goType.underlying, checker);
|
|
276
|
+
}
|
|
277
|
+
if (goType.kind === "basic") return goType.name === "any";
|
|
278
|
+
if (goType.kind === "interface") return true;
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function isNarrowInt(goType) {
|
|
283
|
+
if (!goType) return false;
|
|
284
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
285
|
+
return isNarrowInt({ kind: "basic", name: goType.name });
|
|
286
|
+
}
|
|
287
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
288
|
+
return isNarrowInt(goType.underlying);
|
|
289
|
+
}
|
|
290
|
+
if (goType.kind === "basic") {
|
|
291
|
+
switch (goType.name) {
|
|
292
|
+
case "int8":
|
|
293
|
+
case "int16":
|
|
294
|
+
case "uint8":
|
|
295
|
+
case "uint16":
|
|
296
|
+
case "byte":
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function isSigned(goType) {
|
|
304
|
+
if (!goType) return true;
|
|
305
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
306
|
+
return isSigned({ kind: "basic", name: goType.name });
|
|
307
|
+
}
|
|
308
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
309
|
+
return isSigned(goType.underlying);
|
|
310
|
+
}
|
|
311
|
+
if (goType.kind === "basic") {
|
|
312
|
+
return !goType.name.startsWith("uint") && goType.name !== "byte";
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function isFloat(goType) {
|
|
318
|
+
if (!goType) return false;
|
|
319
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
320
|
+
return isFloat({ kind: "basic", name: goType.name });
|
|
321
|
+
}
|
|
322
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
323
|
+
return isFloat(goType.underlying);
|
|
324
|
+
}
|
|
325
|
+
if (goType.kind === "untyped") return goType.base === "float64";
|
|
326
|
+
return (
|
|
327
|
+
goType.kind === "basic" &&
|
|
328
|
+
(goType.name === "float32" || goType.name === "float64")
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function isInt(goType) {
|
|
333
|
+
if (!goType) return false;
|
|
334
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
335
|
+
return isInt({ kind: "basic", name: goType.name });
|
|
336
|
+
}
|
|
337
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
338
|
+
return isInt(goType.underlying);
|
|
339
|
+
}
|
|
340
|
+
if (goType.kind === "untyped") return goType.base === "int";
|
|
341
|
+
if (goType.kind === "basic") {
|
|
342
|
+
return (
|
|
343
|
+
goType.name.startsWith("int") ||
|
|
344
|
+
goType.name.startsWith("uint") ||
|
|
345
|
+
goType.name === "byte" ||
|
|
346
|
+
goType.name === "rune" ||
|
|
347
|
+
goType.name === "uintptr"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function isFuncType(goType, checker = null) {
|
|
354
|
+
if (!goType) return false;
|
|
355
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
356
|
+
const resolved = checker?.types?.get(goType.name);
|
|
357
|
+
if (resolved) return isFuncType(resolved, checker);
|
|
358
|
+
}
|
|
359
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
360
|
+
return isFuncType(goType.underlying, checker);
|
|
361
|
+
}
|
|
362
|
+
return (
|
|
363
|
+
goType.kind === "func" ||
|
|
364
|
+
goType.kind === "FuncType" ||
|
|
365
|
+
goType.kind === "FuncLit" ||
|
|
366
|
+
goType.kind === "FuncDecl"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function getFuncSignature(goType, checker = null) {
|
|
371
|
+
if (!goType) return { params: [], returns: [] };
|
|
372
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
373
|
+
const resolved = checker?.types?.get(goType.name);
|
|
374
|
+
if (resolved) return getFuncSignature(resolved, checker);
|
|
375
|
+
}
|
|
376
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
377
|
+
return getFuncSignature(goType.underlying, checker);
|
|
378
|
+
}
|
|
379
|
+
if (
|
|
380
|
+
goType.kind === "FuncType" ||
|
|
381
|
+
goType.kind === "FuncLit" ||
|
|
382
|
+
goType.kind === "FuncDecl"
|
|
383
|
+
) {
|
|
384
|
+
const params = (goType.params ?? []).map((p) => p.type ?? p);
|
|
385
|
+
const returns = [];
|
|
386
|
+
if (goType.returnType) {
|
|
387
|
+
if (
|
|
388
|
+
goType.returnType.kind === "TupleType" ||
|
|
389
|
+
goType.returnType.kind === "tuple"
|
|
390
|
+
) {
|
|
391
|
+
returns.push(...(goType.returnType.types ?? []));
|
|
392
|
+
} else {
|
|
393
|
+
returns.push(goType.returnType);
|
|
394
|
+
}
|
|
395
|
+
} else if (goType.returns) {
|
|
396
|
+
for (const r of goType.returns) {
|
|
397
|
+
if ((r?.kind === "tuple" || r?.kind === "TupleType") && r.types) {
|
|
398
|
+
returns.push(...r.types);
|
|
399
|
+
} else {
|
|
400
|
+
returns.push(r);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { params, returns };
|
|
405
|
+
}
|
|
406
|
+
if (goType.kind === "func") {
|
|
407
|
+
const rawReturns =
|
|
408
|
+
goType.returns ?? (goType.returnType ? [goType.returnType] : []);
|
|
409
|
+
const returns = [];
|
|
410
|
+
for (const r of rawReturns) {
|
|
411
|
+
if ((r?.kind === "tuple" || r?.kind === "TupleType") && r.types) {
|
|
412
|
+
returns.push(...r.types);
|
|
413
|
+
} else {
|
|
414
|
+
returns.push(r);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return { params: goType.params ?? [], returns };
|
|
418
|
+
}
|
|
419
|
+
return { params: [], returns: [] };
|
|
420
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/backend/wasm/wat.js
|
|
2
|
+
// Emits WebAssembly Text (WAT) format from Module IR.
|
|
3
|
+
|
|
4
|
+
function formatValType(t) {
|
|
5
|
+
if (typeof t === "string") return t;
|
|
6
|
+
if (typeof t === "number") return `(type ${t})`;
|
|
7
|
+
if (typeof t === "object" && t !== null) {
|
|
8
|
+
if (t.kind === "ref") {
|
|
9
|
+
const nullStr = t.nullable ? "null " : "";
|
|
10
|
+
const target =
|
|
11
|
+
t.typeIndex !== undefined ? `$t${t.typeIndex}` : (t.heapType ?? "any");
|
|
12
|
+
return `(ref ${nullStr}${target})`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return String(t);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatTypeEntry(t, id) {
|
|
19
|
+
const typeId = id !== undefined ? ` $t${id}` : "";
|
|
20
|
+
if (t.form === "rec") {
|
|
21
|
+
const inners = t.types
|
|
22
|
+
.map(
|
|
23
|
+
(sub, i) =>
|
|
24
|
+
` ${formatTypeEntry(sub, typeof id === "number" ? id + i : `${id}_${i}`)}`,
|
|
25
|
+
)
|
|
26
|
+
.join("\n");
|
|
27
|
+
return `(rec\n${inners}\n )`;
|
|
28
|
+
}
|
|
29
|
+
if (t.form === "struct") {
|
|
30
|
+
const fields = (t.fields ?? [])
|
|
31
|
+
.map((f) => {
|
|
32
|
+
const inner = formatValType(f.type);
|
|
33
|
+
return f.mutable ? `(field (mut ${inner}))` : `(field ${inner})`;
|
|
34
|
+
})
|
|
35
|
+
.join(" ");
|
|
36
|
+
return `(type${typeId} (struct ${fields}))`;
|
|
37
|
+
}
|
|
38
|
+
if (t.form === "array") {
|
|
39
|
+
const inner = formatValType(t.elemType ?? t.type);
|
|
40
|
+
const field = t.mutable ? `(mut ${inner})` : inner;
|
|
41
|
+
return `(type${typeId} (array ${field}))`;
|
|
42
|
+
}
|
|
43
|
+
// func
|
|
44
|
+
const params = (t.params ?? []).map(formatValType).join(" ");
|
|
45
|
+
const results = (t.results ?? []).map(formatValType).join(" ");
|
|
46
|
+
const paramStr = params ? ` (param ${params})` : "";
|
|
47
|
+
const resultStr = results ? ` (result ${results})` : "";
|
|
48
|
+
return `(type${typeId} (func${paramStr}${resultStr}))`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatInstruction(inst) {
|
|
52
|
+
if (typeof inst === "string") return inst;
|
|
53
|
+
const { op } = inst;
|
|
54
|
+
switch (op) {
|
|
55
|
+
case "block":
|
|
56
|
+
case "loop":
|
|
57
|
+
case "if": {
|
|
58
|
+
const bt = inst.blockType ?? inst.resultType;
|
|
59
|
+
const res = bt && bt !== "void" ? ` (result ${formatValType(bt)})` : "";
|
|
60
|
+
return `${op}${res}`;
|
|
61
|
+
}
|
|
62
|
+
case "br":
|
|
63
|
+
case "br_if":
|
|
64
|
+
return `${op} ${inst.depth ?? inst.index ?? 0}`;
|
|
65
|
+
case "br_table": {
|
|
66
|
+
const targets = (inst.targets ?? []).join(" ");
|
|
67
|
+
return `${op} ${targets} ${inst.defaultTarget ?? 0}`;
|
|
68
|
+
}
|
|
69
|
+
case "call":
|
|
70
|
+
case "return_call":
|
|
71
|
+
return `${op} ${inst.funcIndex ?? inst.index ?? 0}`;
|
|
72
|
+
case "call_indirect":
|
|
73
|
+
return `${op} (type $t${inst.typeIndex ?? 0})`;
|
|
74
|
+
case "call_ref":
|
|
75
|
+
case "return_call_ref":
|
|
76
|
+
return `${op} (type $t${inst.typeIndex ?? 0})`;
|
|
77
|
+
case "ref.func":
|
|
78
|
+
return `ref.func ${inst.funcIndex ?? inst.index ?? 0}`;
|
|
79
|
+
case "local.get":
|
|
80
|
+
case "local.set":
|
|
81
|
+
case "local.tee":
|
|
82
|
+
return `${op} ${inst.localIndex ?? inst.index ?? 0}`;
|
|
83
|
+
case "global.get":
|
|
84
|
+
case "global.set":
|
|
85
|
+
return `${op} ${inst.globalIndex ?? inst.index ?? 0}`;
|
|
86
|
+
case "i32.const":
|
|
87
|
+
case "i64.const":
|
|
88
|
+
return `${op} ${inst.value ?? 0}`;
|
|
89
|
+
case "f32.const":
|
|
90
|
+
case "f64.const": {
|
|
91
|
+
const v = inst.value ?? 0;
|
|
92
|
+
if (Number.isNaN(v)) return `${op} nan`;
|
|
93
|
+
if (v === Infinity) return `${op} inf`;
|
|
94
|
+
if (v === -Infinity) return `${op} -inf`;
|
|
95
|
+
return `${op} ${v}`;
|
|
96
|
+
}
|
|
97
|
+
case "throw":
|
|
98
|
+
return `${op} ${inst.tagIndex ?? inst.index ?? 0}`;
|
|
99
|
+
case "struct.new":
|
|
100
|
+
return `struct.new ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
101
|
+
case "struct.get":
|
|
102
|
+
case "struct.get_s":
|
|
103
|
+
case "struct.get_u":
|
|
104
|
+
case "struct.set":
|
|
105
|
+
return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.fieldIndex ?? 0}`;
|
|
106
|
+
case "array.new":
|
|
107
|
+
case "array.new_default":
|
|
108
|
+
case "array.get":
|
|
109
|
+
case "array.get_s":
|
|
110
|
+
case "array.get_u":
|
|
111
|
+
case "array.set":
|
|
112
|
+
return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
113
|
+
case "array.new_fixed":
|
|
114
|
+
return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.size ?? inst.count ?? 0}`;
|
|
115
|
+
case "array.copy":
|
|
116
|
+
return `${op} ${inst.typeIndexDst !== undefined ? `$t${inst.typeIndexDst}` : `$t${inst.typeIndex ?? 0}`} ${inst.typeIndexSrc !== undefined ? `$t${inst.typeIndexSrc}` : `$t${inst.typeIndex ?? 0}`}`;
|
|
117
|
+
case "array.len":
|
|
118
|
+
return "array.len";
|
|
119
|
+
case "ref.test":
|
|
120
|
+
case "ref.test_null":
|
|
121
|
+
case "ref.cast":
|
|
122
|
+
case "ref.cast_null":
|
|
123
|
+
return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
124
|
+
case "any.convert_extern":
|
|
125
|
+
case "extern.convert_any":
|
|
126
|
+
return op;
|
|
127
|
+
default:
|
|
128
|
+
return op;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function emitWat(mod) {
|
|
133
|
+
const lines = ["(module"];
|
|
134
|
+
|
|
135
|
+
// 1. Types
|
|
136
|
+
if (mod.types && mod.types.length > 0) {
|
|
137
|
+
let typeIdx = 0;
|
|
138
|
+
for (let i = 0; i < mod.types.length; i++) {
|
|
139
|
+
const entry = mod.types[i];
|
|
140
|
+
lines.push(` ${formatTypeEntry(entry, typeIdx)}`);
|
|
141
|
+
if (entry.form === "rec") {
|
|
142
|
+
typeIdx += entry.types.length;
|
|
143
|
+
} else {
|
|
144
|
+
typeIdx += 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 2. Imports
|
|
150
|
+
if (mod.imports && mod.imports.length > 0) {
|
|
151
|
+
for (const imp of mod.imports) {
|
|
152
|
+
const desc =
|
|
153
|
+
imp.kind === "func"
|
|
154
|
+
? `(func (type $t${imp.typeIndex ?? 0}))`
|
|
155
|
+
: imp.kind === "tag"
|
|
156
|
+
? `(tag (type $t${imp.typeIndex ?? 0}))`
|
|
157
|
+
: `(${imp.kind})`;
|
|
158
|
+
lines.push(` (import "${imp.module}" "${imp.name}" ${desc})`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 3. Tags
|
|
163
|
+
if (mod.tags && mod.tags.length > 0) {
|
|
164
|
+
for (let i = 0; i < mod.tags.length; i++) {
|
|
165
|
+
lines.push(` (tag $tag${i} (type $t${mod.tags[i].typeIndex ?? 0}))`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 4. Globals
|
|
170
|
+
if (mod.globals && mod.globals.length > 0) {
|
|
171
|
+
for (let i = 0; i < mod.globals.length; i++) {
|
|
172
|
+
const g = mod.globals[i];
|
|
173
|
+
const t = formatValType(g.type);
|
|
174
|
+
const typeStr = g.mutable ? `(mut ${t})` : t;
|
|
175
|
+
const initStr = (g.init ?? [{ op: "i32.const", value: 0 }])
|
|
176
|
+
.map(formatInstruction)
|
|
177
|
+
.join(" ");
|
|
178
|
+
lines.push(` (global $g${i} ${typeStr} ${initStr})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 5. Elements (declarative for ref.func)
|
|
183
|
+
if (mod.elements && mod.elements.length > 0) {
|
|
184
|
+
lines.push(
|
|
185
|
+
` (elem declare func ${mod.elements.map((idx) => `$f${idx}`).join(" ")})`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Map exports by kind & index
|
|
190
|
+
const exportsByFunc = new Map();
|
|
191
|
+
for (const exp of mod.exports ?? []) {
|
|
192
|
+
if (exp.kind === "func") {
|
|
193
|
+
if (!exportsByFunc.has(exp.index)) exportsByFunc.set(exp.index, []);
|
|
194
|
+
exportsByFunc.get(exp.index).push(exp.name);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 5. Functions
|
|
199
|
+
if (mod.funcs && mod.funcs.length > 0) {
|
|
200
|
+
const importFuncCount = (mod.imports ?? []).filter(
|
|
201
|
+
(i) => i.kind === "func",
|
|
202
|
+
).length;
|
|
203
|
+
// Type indices count every member of a rec group, not the group entry.
|
|
204
|
+
const flatTypes = (mod.types ?? []).flatMap((t) =>
|
|
205
|
+
t.form === "rec" ? t.types : [t],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < mod.funcs.length; i++) {
|
|
209
|
+
const fn = mod.funcs[i];
|
|
210
|
+
const globalFuncIdx = importFuncCount + i;
|
|
211
|
+
const expNames = exportsByFunc.get(globalFuncIdx) ?? [];
|
|
212
|
+
const expStr = expNames.map((n) => ` (export "${n}")`).join("");
|
|
213
|
+
|
|
214
|
+
const typeEntry = flatTypes[fn.typeIndex] ?? null;
|
|
215
|
+
let sigStr = ` (type $t${fn.typeIndex ?? 0})`;
|
|
216
|
+
if (typeEntry && typeEntry.form === "func") {
|
|
217
|
+
const params = (typeEntry.params ?? []).map(formatValType).join(" ");
|
|
218
|
+
const results = (typeEntry.results ?? []).map(formatValType).join(" ");
|
|
219
|
+
if (params) sigStr += ` (param ${params})`;
|
|
220
|
+
if (results) sigStr += ` (result ${results})`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
lines.push(` (func $f${globalFuncIdx}${expStr}${sigStr}`);
|
|
224
|
+
|
|
225
|
+
// Locals
|
|
226
|
+
if (fn.locals && fn.locals.length > 0) {
|
|
227
|
+
const locTypes = fn.locals.map((l) =>
|
|
228
|
+
l && typeof l === "object" && "type" in l && !l.kind ? l.type : l,
|
|
229
|
+
);
|
|
230
|
+
lines.push(` (local ${locTypes.map(formatValType).join(" ")})`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Body instructions
|
|
234
|
+
for (const inst of fn.body ?? []) {
|
|
235
|
+
lines.push(` ${formatInstruction(inst)}`);
|
|
236
|
+
}
|
|
237
|
+
lines.push(" )");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 6. Remaining exports (e.g. tags, globals, memories)
|
|
242
|
+
for (const exp of mod.exports ?? []) {
|
|
243
|
+
if (exp.kind !== "func") {
|
|
244
|
+
lines.push(` (export "${exp.name}" (${exp.kind} ${exp.index}))`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 7. Start function
|
|
249
|
+
if (typeof mod.start === "number") {
|
|
250
|
+
lines.push(` (start $f${mod.start})`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lines.push(")");
|
|
254
|
+
return `${lines.join("\n")}\n`;
|
|
255
|
+
}
|