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,1413 @@
|
|
|
1
|
+
// src/backend/wasm/index.js
|
|
2
|
+
// WASM Backend compiler entry point: lowered AST + types -> Module IR -> binary / WAT.
|
|
3
|
+
|
|
4
|
+
import { Lexer } from "../../lexer.js";
|
|
5
|
+
import { lower } from "../../lower/index.js";
|
|
6
|
+
import { Parser } from "../../parser/index.js";
|
|
7
|
+
import { TypeChecker } from "../../typechecker/index.js";
|
|
8
|
+
import {
|
|
9
|
+
addBoundaryHelpers,
|
|
10
|
+
collectBoundaryMeta,
|
|
11
|
+
generateFacade,
|
|
12
|
+
scanBoundaryImports,
|
|
13
|
+
} from "./boundary.js";
|
|
14
|
+
import { FunctionEmitter } from "./emit.js";
|
|
15
|
+
import { encodeModule, isGoFrontWasm } from "./encode.js";
|
|
16
|
+
import { getFuncSignature, isTestingT, toWasmType } from "./types.js";
|
|
17
|
+
import { emitWat } from "./wat.js";
|
|
18
|
+
|
|
19
|
+
export { isGoFrontWasm };
|
|
20
|
+
|
|
21
|
+
export class ModuleEmitter {
|
|
22
|
+
constructor(checker, lowerResult = null, programs = [], options = {}) {
|
|
23
|
+
this.checker = checker;
|
|
24
|
+
this.lowerResult = lowerResult;
|
|
25
|
+
// Local package names whose code is linked into this module; selectors
|
|
26
|
+
// like `mathx.NewVec3` are de-qualified to `NewVec3` by the emitter.
|
|
27
|
+
this.bundledPackages = options.bundledPackages ?? new Set();
|
|
28
|
+
this.constCache = new Map(); // name -> BasicLit (package-level literal consts)
|
|
29
|
+
this.nonLiteralConsts = new Set(); // package-level consts the backend cannot evaluate yet
|
|
30
|
+
|
|
31
|
+
this.types = []; // type entries
|
|
32
|
+
this.typeCache = new Map(); // signature string -> index
|
|
33
|
+
this._openRec = null; // rec group receiving new types while structs are resolved
|
|
34
|
+
|
|
35
|
+
this.structTypes = new Map(); // name -> struct info
|
|
36
|
+
this.boxTypes = new Map(); // wType key -> box info
|
|
37
|
+
this.arrayTypes = new Map(); // wType key -> array info
|
|
38
|
+
this.sliceTypes = new Map(); // wType key -> slice info
|
|
39
|
+
this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
|
|
40
|
+
this.closureTypes = new Map(); // key -> closure info
|
|
41
|
+
this.envTypes = new Map(); // key -> env info
|
|
42
|
+
|
|
43
|
+
this.elements = []; // declarative elements for ref.func
|
|
44
|
+
this.elementSet = new Set();
|
|
45
|
+
|
|
46
|
+
this.imports = []; // import entries
|
|
47
|
+
this.importCache = new Map(); // key -> func index
|
|
48
|
+
|
|
49
|
+
this.tags = []; // exception tags
|
|
50
|
+
this.globals = []; // global entries
|
|
51
|
+
this.globalCache = new Map(); // name -> index
|
|
52
|
+
|
|
53
|
+
this.funcs = []; // internal func definitions
|
|
54
|
+
this.funcMap = new Map(); // name -> global func index
|
|
55
|
+
this.funcParamTypes = new Map(); // name -> param types array
|
|
56
|
+
this.funcParamGoTypes = new Map(); // name -> param Go types array
|
|
57
|
+
|
|
58
|
+
this.exports = []; // export entries
|
|
59
|
+
this.stringTable = []; // string literals
|
|
60
|
+
this.stringCache = new Map();
|
|
61
|
+
|
|
62
|
+
if (programs && programs.length > 0) {
|
|
63
|
+
this.initTypes(programs);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._importsLocked = false;
|
|
67
|
+
this._initRuntimeImports();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
initTypes(programs) {
|
|
71
|
+
const progs = Array.isArray(programs) ? programs : [programs];
|
|
72
|
+
const rawStructs = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const aliases = []; // qualified key (pkg.T) -> unqualified struct name
|
|
75
|
+
|
|
76
|
+
for (const p of progs) {
|
|
77
|
+
for (const d of p.decls ?? []) {
|
|
78
|
+
if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
|
|
79
|
+
if (!seen.has(d.name)) {
|
|
80
|
+
seen.add(d.name);
|
|
81
|
+
rawStructs.push({
|
|
82
|
+
name: d.name,
|
|
83
|
+
astFields: d.type.fields ?? [],
|
|
84
|
+
pkgName: p.pkg?.name ?? "main",
|
|
85
|
+
pkgTarget: p.target ?? "wasm",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.checker?.types) {
|
|
93
|
+
for (const [key, t] of this.checker.types.entries()) {
|
|
94
|
+
if (t.kind === "named" && t.underlying?.kind === "struct") {
|
|
95
|
+
const name = t.name ?? key;
|
|
96
|
+
if (!seen.has(name)) {
|
|
97
|
+
seen.add(name);
|
|
98
|
+
rawStructs.push({ name, structType: t.underlying });
|
|
99
|
+
}
|
|
100
|
+
if (key !== name) aliases.push([key, name]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (rawStructs.length === 0) return;
|
|
106
|
+
|
|
107
|
+
// Field resolution may create array/slice/box types; they join this rec
|
|
108
|
+
// group so struct indices (base + i) stay valid and may be referenced
|
|
109
|
+
// from the auxiliary types in any order.
|
|
110
|
+
const base = this._getTotalTypeCount();
|
|
111
|
+
const structEntries = rawStructs.map(() => ({
|
|
112
|
+
form: "struct",
|
|
113
|
+
fields: [],
|
|
114
|
+
}));
|
|
115
|
+
this.types.push({ form: "rec", types: structEntries });
|
|
116
|
+
this._openRec = structEntries;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < rawStructs.length; i++) {
|
|
119
|
+
const s = rawStructs[i];
|
|
120
|
+
this.structTypes.set(s.name, {
|
|
121
|
+
name: s.name,
|
|
122
|
+
typeIndex: base + i,
|
|
123
|
+
fields: [],
|
|
124
|
+
fieldIndexMap: new Map(),
|
|
125
|
+
embeds: [],
|
|
126
|
+
pkgName: s.pkgName ?? null,
|
|
127
|
+
pkgTarget: s.pkgTarget ?? "wasm",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// `mathx.Vec3` resolves to the same struct info as `Vec3` once linked.
|
|
131
|
+
for (const [key, name] of aliases) {
|
|
132
|
+
if (!this.structTypes.has(key))
|
|
133
|
+
this.structTypes.set(key, this.structTypes.get(name));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < rawStructs.length; i++) {
|
|
137
|
+
const s = rawStructs[i];
|
|
138
|
+
const info = this.structTypes.get(s.name);
|
|
139
|
+
const resolvedStruct = this.checker?.types?.get(s.name)?.underlying;
|
|
140
|
+
|
|
141
|
+
const fields = [];
|
|
142
|
+
const fieldIndexMap = new Map();
|
|
143
|
+
const embeds = [];
|
|
144
|
+
|
|
145
|
+
if (s.astFields && s.astFields.length > 0) {
|
|
146
|
+
let fIdx = 0;
|
|
147
|
+
for (const f of s.astFields) {
|
|
148
|
+
const names = f.embedded
|
|
149
|
+
? [f.type.name]
|
|
150
|
+
: f.names?.length
|
|
151
|
+
? f.names
|
|
152
|
+
: f.name
|
|
153
|
+
? [f.name]
|
|
154
|
+
: [];
|
|
155
|
+
for (const n of names) {
|
|
156
|
+
const wType = toWasmType(f.type, this.checker, this);
|
|
157
|
+
fields.push({
|
|
158
|
+
name: n,
|
|
159
|
+
goType: f.type,
|
|
160
|
+
wType,
|
|
161
|
+
embedded: Boolean(f.embedded),
|
|
162
|
+
});
|
|
163
|
+
fieldIndexMap.set(n, fIdx);
|
|
164
|
+
if (f.embedded) {
|
|
165
|
+
embeds.push({
|
|
166
|
+
name: n,
|
|
167
|
+
fieldIndex: fIdx,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
fIdx++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else if (resolvedStruct?.fields) {
|
|
174
|
+
let fIdx = 0;
|
|
175
|
+
for (const [fName, fType] of resolvedStruct.fields.entries()) {
|
|
176
|
+
const wType = toWasmType(fType, this.checker, this);
|
|
177
|
+
fields.push({ name: fName, goType: fType, wType });
|
|
178
|
+
fieldIndexMap.set(fName, fIdx);
|
|
179
|
+
fIdx++;
|
|
180
|
+
}
|
|
181
|
+
if (resolvedStruct?._embeds) {
|
|
182
|
+
for (const embed of resolvedStruct._embeds) {
|
|
183
|
+
const embedName = embed.kind === "named" ? embed.name : null;
|
|
184
|
+
if (embedName && fieldIndexMap.has(embedName)) {
|
|
185
|
+
embeds.push({
|
|
186
|
+
name: embedName,
|
|
187
|
+
fieldIndex: fieldIndexMap.get(embedName),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
info.fields = fields;
|
|
195
|
+
info.fieldIndexMap = fieldIndexMap;
|
|
196
|
+
info.embeds = embeds;
|
|
197
|
+
|
|
198
|
+
const typeEntry = structEntries[i];
|
|
199
|
+
typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
|
|
200
|
+
info.typeEntry = typeEntry;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this._openRec = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_pushType(typeEntry) {
|
|
207
|
+
(this._openRec ?? this.types).push(typeEntry);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getStructType(name) {
|
|
211
|
+
return this.structTypes.get(name) ?? null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
getBoxType(goType) {
|
|
215
|
+
const wType = toWasmType(goType, this.checker, this);
|
|
216
|
+
const key = typeof wType === "string" ? wType : JSON.stringify(wType);
|
|
217
|
+
if (this.boxTypes.has(key)) {
|
|
218
|
+
return this.boxTypes.get(key);
|
|
219
|
+
}
|
|
220
|
+
const typeIndex = this._getTotalTypeCount();
|
|
221
|
+
const typeEntry = {
|
|
222
|
+
form: "struct",
|
|
223
|
+
fields: [{ type: wType, mutable: true }],
|
|
224
|
+
};
|
|
225
|
+
this._pushType(typeEntry);
|
|
226
|
+
const boxInfo = { typeIndex, typeEntry, wType };
|
|
227
|
+
this.boxTypes.set(key, boxInfo);
|
|
228
|
+
return boxInfo;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
getArrayType(elemGoType) {
|
|
232
|
+
const elemWType = toWasmType(elemGoType, this.checker, this);
|
|
233
|
+
const key =
|
|
234
|
+
typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
|
|
235
|
+
if (this.arrayTypes.has(key)) {
|
|
236
|
+
return this.arrayTypes.get(key);
|
|
237
|
+
}
|
|
238
|
+
const typeIndex = this._getTotalTypeCount();
|
|
239
|
+
const typeEntry = {
|
|
240
|
+
form: "array",
|
|
241
|
+
elemType: elemWType,
|
|
242
|
+
mutable: true,
|
|
243
|
+
};
|
|
244
|
+
this._pushType(typeEntry);
|
|
245
|
+
const arrInfo = { typeIndex, typeEntry, elemWType, elemGoType };
|
|
246
|
+
this.arrayTypes.set(key, arrInfo);
|
|
247
|
+
return arrInfo;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
getSliceType(elemGoType) {
|
|
251
|
+
const elemWType = toWasmType(elemGoType, this.checker, this);
|
|
252
|
+
const key =
|
|
253
|
+
typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
|
|
254
|
+
if (this.sliceTypes.has(key)) {
|
|
255
|
+
return this.sliceTypes.get(key);
|
|
256
|
+
}
|
|
257
|
+
const arrInfo = this.getArrayType(elemGoType);
|
|
258
|
+
const typeIndex = this._getTotalTypeCount();
|
|
259
|
+
const typeEntry = {
|
|
260
|
+
form: "struct",
|
|
261
|
+
fields: [
|
|
262
|
+
{
|
|
263
|
+
type: {
|
|
264
|
+
kind: "ref",
|
|
265
|
+
nullable: true,
|
|
266
|
+
typeIndex: arrInfo.typeIndex,
|
|
267
|
+
},
|
|
268
|
+
mutable: false,
|
|
269
|
+
},
|
|
270
|
+
{ type: "i32", mutable: false }, // offset
|
|
271
|
+
{ type: "i32", mutable: false }, // len
|
|
272
|
+
{ type: "i32", mutable: false }, // cap
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
this._pushType(typeEntry);
|
|
276
|
+
|
|
277
|
+
const emptyGlobalIndex = this.globals.length;
|
|
278
|
+
this.globals.push({
|
|
279
|
+
type: {
|
|
280
|
+
kind: "ref",
|
|
281
|
+
nullable: true,
|
|
282
|
+
typeIndex,
|
|
283
|
+
},
|
|
284
|
+
mutable: false,
|
|
285
|
+
init: [
|
|
286
|
+
{ op: "ref.null", heapType: arrInfo.typeIndex },
|
|
287
|
+
{ op: "i32.const", value: 0 },
|
|
288
|
+
{ op: "i32.const", value: 0 },
|
|
289
|
+
{ op: "i32.const", value: 0 },
|
|
290
|
+
{ op: "struct.new", typeIndex },
|
|
291
|
+
],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const sliceInfo = {
|
|
295
|
+
typeIndex,
|
|
296
|
+
typeEntry,
|
|
297
|
+
arrInfo,
|
|
298
|
+
elemWType,
|
|
299
|
+
elemGoType,
|
|
300
|
+
emptyGlobalIndex,
|
|
301
|
+
};
|
|
302
|
+
this.sliceTypes.set(key, sliceInfo);
|
|
303
|
+
this.sliceTypesByIndex.set(typeIndex, sliceInfo);
|
|
304
|
+
return sliceInfo;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getSliceTypeByIndex(typeIndex) {
|
|
308
|
+
return this.sliceTypesByIndex?.get(typeIndex) ?? null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getClosureType(goType) {
|
|
312
|
+
const sig = getFuncSignature(goType, this.checker);
|
|
313
|
+
const paramWTypes = sig.params
|
|
314
|
+
.map((p) => toWasmType(p, this.checker, this))
|
|
315
|
+
.filter(Boolean);
|
|
316
|
+
const returnWTypes = sig.returns
|
|
317
|
+
.map((r) => toWasmType(r, this.checker, this))
|
|
318
|
+
.filter(Boolean);
|
|
319
|
+
|
|
320
|
+
const key = `${paramWTypes.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${returnWTypes.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
|
|
321
|
+
if (this.closureTypes.has(key)) {
|
|
322
|
+
return this.closureTypes.get(key);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 1. Function signature type index: (param anyref, ...paramWTypes) -> (...returnWTypes)
|
|
326
|
+
const funcTypeIndex = this.getTypeIndex(
|
|
327
|
+
["anyref", ...paramWTypes],
|
|
328
|
+
returnWTypes,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// 2. Closure struct type index: struct { fn: (ref funcTypeIndex), env: anyref }
|
|
332
|
+
const typeIndex = this._getTotalTypeCount();
|
|
333
|
+
const typeEntry = {
|
|
334
|
+
form: "struct",
|
|
335
|
+
fields: [
|
|
336
|
+
{
|
|
337
|
+
type: {
|
|
338
|
+
kind: "ref",
|
|
339
|
+
nullable: false,
|
|
340
|
+
typeIndex: funcTypeIndex,
|
|
341
|
+
},
|
|
342
|
+
mutable: false,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
type: "anyref",
|
|
346
|
+
mutable: true,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
};
|
|
350
|
+
this._pushType(typeEntry);
|
|
351
|
+
|
|
352
|
+
const closureInfo = {
|
|
353
|
+
typeIndex,
|
|
354
|
+
typeEntry,
|
|
355
|
+
funcTypeIndex,
|
|
356
|
+
paramWTypes,
|
|
357
|
+
returnWTypes,
|
|
358
|
+
sig,
|
|
359
|
+
};
|
|
360
|
+
this.closureTypes.set(key, closureInfo);
|
|
361
|
+
return closureInfo;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
getEnvType(fieldTypes) {
|
|
365
|
+
const key = fieldTypes
|
|
366
|
+
.map((f) =>
|
|
367
|
+
typeof f.wType === "object" ? JSON.stringify(f.wType) : f.wType,
|
|
368
|
+
)
|
|
369
|
+
.join(",");
|
|
370
|
+
if (this.envTypes.has(key)) {
|
|
371
|
+
return this.envTypes.get(key);
|
|
372
|
+
}
|
|
373
|
+
const typeIndex = this._getTotalTypeCount();
|
|
374
|
+
const typeEntry = {
|
|
375
|
+
form: "struct",
|
|
376
|
+
fields: fieldTypes.map((f) => ({ type: f.wType, mutable: true })),
|
|
377
|
+
};
|
|
378
|
+
this._pushType(typeEntry);
|
|
379
|
+
const envInfo = { typeIndex, typeEntry, fieldTypes };
|
|
380
|
+
this.envTypes.set(key, envInfo);
|
|
381
|
+
return envInfo;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
addRefFuncElement(funcIdx) {
|
|
385
|
+
if (!this.elementSet.has(funcIdx)) {
|
|
386
|
+
this.elementSet.add(funcIdx);
|
|
387
|
+
this.elements.push(funcIdx);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
_getTotalTypeCount() {
|
|
392
|
+
let count = 0;
|
|
393
|
+
for (const entry of this.types) {
|
|
394
|
+
if (entry.form === "rec") count += entry.types.length;
|
|
395
|
+
else count += 1;
|
|
396
|
+
}
|
|
397
|
+
return count;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
_initRuntimeImports() {
|
|
401
|
+
this._initPanicTag();
|
|
402
|
+
this.getPanicImportIndex(); // env.panic
|
|
403
|
+
this.internString("runtime error: index out of range");
|
|
404
|
+
this.internString("runtime error: slice bounds out of range");
|
|
405
|
+
|
|
406
|
+
// String built-ins
|
|
407
|
+
this.getStringImportIndex(); // env.str
|
|
408
|
+
this.getStringLenImportIndex(); // env.str_len
|
|
409
|
+
this.getStringConcatImportIndex(); // env.str_concat
|
|
410
|
+
this.getStringCmpImportIndex("=="); // env.str_eq
|
|
411
|
+
this.getStringCmpImportIndex("!="); // env.str_ne
|
|
412
|
+
this.getStringCmpImportIndex("<"); // env.str_lt
|
|
413
|
+
this.getStringCmpImportIndex("<="); // env.str_le
|
|
414
|
+
this.getStringCmpImportIndex(">"); // env.str_gt
|
|
415
|
+
this.getStringCmpImportIndex(">="); // env.str_ge
|
|
416
|
+
this.getStringGetImportIndex(); // env.str_get
|
|
417
|
+
this.getStringSliceImportIndex(); // env.str_slice
|
|
418
|
+
this.getStringFromCodePointImportIndex(); // env.str_from_code_point
|
|
419
|
+
this.getStringCodePointAtImportIndex(); // env.str_code_point_at
|
|
420
|
+
this.getIsStringImportIndex(); // env.is_string
|
|
421
|
+
|
|
422
|
+
// Logging built-ins
|
|
423
|
+
this.getPrintlnEmptyIndex(); // env.println_empty
|
|
424
|
+
const logTypes = ["i32", "i64", "f32", "f64", "externref", "anyref"];
|
|
425
|
+
for (const t of logTypes) {
|
|
426
|
+
this.getLogImportIndex(t, false, false); // env.print_*
|
|
427
|
+
this.getLogImportIndex(t, true, false); // env.println_*
|
|
428
|
+
}
|
|
429
|
+
this.getLogImportIndex("i32", false, true); // env.print_bool
|
|
430
|
+
this.getLogImportIndex("i32", true, true); // env.println_bool
|
|
431
|
+
|
|
432
|
+
// Math built-ins
|
|
433
|
+
const unaryMath = [
|
|
434
|
+
"sin",
|
|
435
|
+
"cos",
|
|
436
|
+
"tan",
|
|
437
|
+
"asin",
|
|
438
|
+
"acos",
|
|
439
|
+
"atan",
|
|
440
|
+
"exp",
|
|
441
|
+
"log",
|
|
442
|
+
"log2",
|
|
443
|
+
"log10",
|
|
444
|
+
"round",
|
|
445
|
+
];
|
|
446
|
+
for (const m of unaryMath) {
|
|
447
|
+
this.getMathImportIndex(m, false);
|
|
448
|
+
}
|
|
449
|
+
const binaryMath = ["atan2", "pow"];
|
|
450
|
+
for (const m of binaryMath) {
|
|
451
|
+
this.getMathImportIndex(m, true);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
_initPanicTag() {
|
|
456
|
+
// Type: (externref) -> ()
|
|
457
|
+
const typeIdx = this.getTypeIndex(["externref"], []);
|
|
458
|
+
// Tag 0
|
|
459
|
+
this.tags.push({ typeIndex: typeIdx });
|
|
460
|
+
// Export tag 0 as "panicTag"
|
|
461
|
+
this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
getTypeIndex(params, results) {
|
|
465
|
+
const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
|
|
466
|
+
if (this.typeCache.has(key)) {
|
|
467
|
+
return this.typeCache.get(key);
|
|
468
|
+
}
|
|
469
|
+
const idx = this._getTotalTypeCount();
|
|
470
|
+
this._pushType({ form: "func", params, results });
|
|
471
|
+
this.typeCache.set(key, idx);
|
|
472
|
+
return idx;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
internString(str) {
|
|
476
|
+
if (this.stringCache.has(str)) {
|
|
477
|
+
return this.stringCache.get(str);
|
|
478
|
+
}
|
|
479
|
+
const idx = this.stringTable.length;
|
|
480
|
+
this.stringTable.push(str);
|
|
481
|
+
this.stringCache.set(str, idx);
|
|
482
|
+
return idx;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
getOrAddFuncImport(module, name, params, results) {
|
|
486
|
+
const key = `${module}.${name}`;
|
|
487
|
+
if (this.importCache.has(key)) {
|
|
488
|
+
return this.importCache.get(key);
|
|
489
|
+
}
|
|
490
|
+
if (this._importsLocked) {
|
|
491
|
+
throw new Error(`Unexpected import added after lock: ${key}`);
|
|
492
|
+
}
|
|
493
|
+
const typeIndex = this.getTypeIndex(params, results);
|
|
494
|
+
const globalFuncIdx = this.imports.filter((i) => i.kind === "func").length;
|
|
495
|
+
this.imports.push({
|
|
496
|
+
module,
|
|
497
|
+
name,
|
|
498
|
+
kind: "func",
|
|
499
|
+
typeIndex,
|
|
500
|
+
});
|
|
501
|
+
this.importCache.set(key, globalFuncIdx);
|
|
502
|
+
return globalFuncIdx;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
getStringImportIndex() {
|
|
506
|
+
return this.getOrAddFuncImport("env", "str", ["i32"], ["externref"]);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Panics are raised by a JS import that throws a plain `Error`, so wasm
|
|
510
|
+
// frames unwind without a wasm `throw` and the JS caller needs no
|
|
511
|
+
// try/catch guard around exports. (V8 cannot inline JS→wasm calls made
|
|
512
|
+
// inside a try block, and a non-inlined call boxes every float argument.)
|
|
513
|
+
getPanicImportIndex() {
|
|
514
|
+
return this.getOrAddFuncImport("env", "panic", ["externref"], []);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getBoundsPanicFuncIndex() {
|
|
518
|
+
return this.resolveFuncIndex("__bounds_panic");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
getSliceBoundsPanicFuncIndex() {
|
|
522
|
+
return this.resolveFuncIndex("__slice_bounds_panic");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
getPrintlnEmptyIndex() {
|
|
526
|
+
return this.getOrAddFuncImport("env", "println_empty", [], []);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// `*testing.T` method calls: args are pushed one by one to a JS-side
|
|
530
|
+
// buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
|
|
531
|
+
getTestingArgImportIndex(wType, isBool = false) {
|
|
532
|
+
if (isBool)
|
|
533
|
+
return this.getOrAddFuncImport("env", "testing_arg_bool", ["i32"], []);
|
|
534
|
+
const suffix =
|
|
535
|
+
wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
|
|
536
|
+
if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
"only primitive, string and any arguments are supported for testing.T methods in wasm packages",
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return this.getOrAddFuncImport(
|
|
542
|
+
"env",
|
|
543
|
+
`testing_arg_${suffix}`,
|
|
544
|
+
[suffix === "str" ? "externref" : suffix === "any" ? "anyref" : wType],
|
|
545
|
+
[],
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
getTestingCallImportIndex() {
|
|
550
|
+
return this.getOrAddFuncImport(
|
|
551
|
+
"env",
|
|
552
|
+
"testing_call",
|
|
553
|
+
["externref", "i32"],
|
|
554
|
+
[],
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
getTestingNameImportIndex() {
|
|
559
|
+
return this.getOrAddFuncImport(
|
|
560
|
+
"env",
|
|
561
|
+
"testing_name",
|
|
562
|
+
["externref"],
|
|
563
|
+
["externref"],
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
getTestingFlagImportIndex() {
|
|
568
|
+
return this.getOrAddFuncImport(
|
|
569
|
+
"env",
|
|
570
|
+
"testing_flag",
|
|
571
|
+
["externref", "i32"],
|
|
572
|
+
["i32"],
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
getLogImportIndex(wType, isLast = false, isBool = false) {
|
|
577
|
+
const prefix = isLast ? "println" : "print";
|
|
578
|
+
if (isBool) {
|
|
579
|
+
return this.getOrAddFuncImport("env", `${prefix}_bool`, ["i32"], []);
|
|
580
|
+
}
|
|
581
|
+
switch (wType) {
|
|
582
|
+
case "i32":
|
|
583
|
+
return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
|
|
584
|
+
case "i64":
|
|
585
|
+
return this.getOrAddFuncImport("env", `${prefix}_i64`, ["i64"], []);
|
|
586
|
+
case "f32":
|
|
587
|
+
return this.getOrAddFuncImport("env", `${prefix}_f32`, ["f32"], []);
|
|
588
|
+
case "f64":
|
|
589
|
+
return this.getOrAddFuncImport("env", `${prefix}_f64`, ["f64"], []);
|
|
590
|
+
case "externref":
|
|
591
|
+
return this.getOrAddFuncImport(
|
|
592
|
+
"env",
|
|
593
|
+
`${prefix}_str`,
|
|
594
|
+
["externref"],
|
|
595
|
+
[],
|
|
596
|
+
);
|
|
597
|
+
case "anyref":
|
|
598
|
+
return this.getOrAddFuncImport("env", `${prefix}_any`, ["anyref"], []);
|
|
599
|
+
default:
|
|
600
|
+
return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
getStringLenImportIndex() {
|
|
605
|
+
return this.getOrAddFuncImport("env", "str_len", ["externref"], ["i32"]);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
getStringConcatImportIndex() {
|
|
609
|
+
return this.getOrAddFuncImport(
|
|
610
|
+
"env",
|
|
611
|
+
"str_concat",
|
|
612
|
+
["externref", "externref"],
|
|
613
|
+
["externref"],
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
getStringCmpImportIndex(op) {
|
|
618
|
+
const opMap = {
|
|
619
|
+
"==": "str_eq",
|
|
620
|
+
"!=": "str_ne",
|
|
621
|
+
"<": "str_lt",
|
|
622
|
+
"<=": "str_le",
|
|
623
|
+
">": "str_gt",
|
|
624
|
+
">=": "str_ge",
|
|
625
|
+
};
|
|
626
|
+
const fn = opMap[op] ?? "str_eq";
|
|
627
|
+
return this.getOrAddFuncImport(
|
|
628
|
+
"env",
|
|
629
|
+
fn,
|
|
630
|
+
["externref", "externref"],
|
|
631
|
+
["i32"],
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
getStringGetImportIndex() {
|
|
636
|
+
return this.getOrAddFuncImport(
|
|
637
|
+
"env",
|
|
638
|
+
"str_get",
|
|
639
|
+
["externref", "i32"],
|
|
640
|
+
["i32"],
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
getStringSliceImportIndex() {
|
|
645
|
+
return this.getOrAddFuncImport(
|
|
646
|
+
"env",
|
|
647
|
+
"str_slice",
|
|
648
|
+
["externref", "i32", "i32"],
|
|
649
|
+
["externref"],
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
getStringFromCodePointImportIndex() {
|
|
654
|
+
return this.getOrAddFuncImport(
|
|
655
|
+
"env",
|
|
656
|
+
"str_from_code_point",
|
|
657
|
+
["i32"],
|
|
658
|
+
["externref"],
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
getStringCodePointAtImportIndex() {
|
|
663
|
+
return this.getOrAddFuncImport(
|
|
664
|
+
"env",
|
|
665
|
+
"str_code_point_at",
|
|
666
|
+
["externref", "i32"],
|
|
667
|
+
["i32"],
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
getIsStringImportIndex() {
|
|
672
|
+
return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
getMathImportIndex(jsName, isBinary = false) {
|
|
676
|
+
const params = isBinary ? ["f64", "f64"] : ["f64"];
|
|
677
|
+
return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
resolveFuncIndex(name) {
|
|
681
|
+
return this.funcMap.get(name) ?? null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
getFuncParamTypes(name) {
|
|
685
|
+
return this.funcParamTypes.get(name) ?? [];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
getFuncParamGoTypes(name) {
|
|
689
|
+
return this.funcParamGoTypes.get(name) ?? [];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
resolveGlobal(name) {
|
|
693
|
+
return this.globalCache.get(name) ?? null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
resolveConst(name) {
|
|
697
|
+
return this.constCache.get(name) ?? null;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
isNonLiteralConst(name) {
|
|
701
|
+
return this.nonLiteralConsts.has(name);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function peepholeOptimize(instructions) {
|
|
706
|
+
let changed = true;
|
|
707
|
+
let current = instructions;
|
|
708
|
+
while (changed) {
|
|
709
|
+
changed = false;
|
|
710
|
+
const next = [];
|
|
711
|
+
for (let i = 0; i < current.length; i++) {
|
|
712
|
+
const c = current[i];
|
|
713
|
+
const n = current[i + 1];
|
|
714
|
+
|
|
715
|
+
// Pattern 1: local.get X; local.set X -> nothing
|
|
716
|
+
if (
|
|
717
|
+
c?.op === "local.get" &&
|
|
718
|
+
n?.op === "local.set" &&
|
|
719
|
+
c.index === n.index
|
|
720
|
+
) {
|
|
721
|
+
changed = true;
|
|
722
|
+
i++; // skip both
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Pattern 2: local.set X; local.get X -> local.tee X
|
|
727
|
+
if (
|
|
728
|
+
c?.op === "local.set" &&
|
|
729
|
+
n?.op === "local.get" &&
|
|
730
|
+
c.index === n.index
|
|
731
|
+
) {
|
|
732
|
+
changed = true;
|
|
733
|
+
next.push({ op: "local.tee", index: c.index });
|
|
734
|
+
i++; // skip n (local.get)
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Pattern 3: local.tee X; local.set X -> local.set X
|
|
739
|
+
if (
|
|
740
|
+
c?.op === "local.tee" &&
|
|
741
|
+
n?.op === "local.set" &&
|
|
742
|
+
c.index === n.index
|
|
743
|
+
) {
|
|
744
|
+
changed = true;
|
|
745
|
+
next.push({ op: "local.set", index: c.index });
|
|
746
|
+
i++;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Pattern 4: local.tee X; drop -> local.set X
|
|
751
|
+
if (c?.op === "local.tee" && (n === "drop" || n?.op === "drop")) {
|
|
752
|
+
changed = true;
|
|
753
|
+
next.push({ op: "local.set", index: c.index });
|
|
754
|
+
i++;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
next.push(c);
|
|
759
|
+
}
|
|
760
|
+
current = next;
|
|
761
|
+
}
|
|
762
|
+
return current;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export function compileWasmModule(
|
|
766
|
+
programs,
|
|
767
|
+
checker,
|
|
768
|
+
lowerResult = null,
|
|
769
|
+
options = {},
|
|
770
|
+
) {
|
|
771
|
+
const progs = Array.isArray(programs) ? programs : [programs];
|
|
772
|
+
const resolvedLowerResult = lowerResult ?? lower(progs, checker);
|
|
773
|
+
const mod = new ModuleEmitter(checker, resolvedLowerResult, progs, {
|
|
774
|
+
bundledPackages: options.bundledPackages,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// 1. Collect all functions, methods, and package-level globals
|
|
778
|
+
const funcDecls = [];
|
|
779
|
+
let initCount = 0;
|
|
780
|
+
const initNames = [];
|
|
781
|
+
for (const p of progs) {
|
|
782
|
+
const pkgName = p.pkg?.name ?? "main";
|
|
783
|
+
const pkgTarget = p.target ?? "wasm";
|
|
784
|
+
for (const d of p.decls ?? []) {
|
|
785
|
+
if (d.kind === "FuncDecl") {
|
|
786
|
+
d._pkgName = pkgName;
|
|
787
|
+
d._pkgTarget = pkgTarget;
|
|
788
|
+
if (d.name === "init" || d.name.startsWith("init$")) {
|
|
789
|
+
const renamed = initCount === 0 ? "init" : `init$${initCount}`;
|
|
790
|
+
d.name = renamed;
|
|
791
|
+
initNames.push(renamed);
|
|
792
|
+
initCount++;
|
|
793
|
+
}
|
|
794
|
+
funcDecls.push(d);
|
|
795
|
+
} else if (d.kind === "MethodDecl") {
|
|
796
|
+
const recvTypeName =
|
|
797
|
+
d.recvType?.name ??
|
|
798
|
+
(d.recvType?.kind === "TypeName" ? d.recvType.name : "Recv");
|
|
799
|
+
const recvGoType = d.recvPointer
|
|
800
|
+
? { kind: "pointer", base: { kind: "named", name: recvTypeName } }
|
|
801
|
+
: { kind: "named", name: recvTypeName };
|
|
802
|
+
const normFn = {
|
|
803
|
+
kind: "FuncDecl",
|
|
804
|
+
name: `${recvTypeName}.${d.name}`,
|
|
805
|
+
params: [{ name: d.recvName, type: recvGoType }, ...(d.params ?? [])],
|
|
806
|
+
returnType: d.returnType,
|
|
807
|
+
body: d.body,
|
|
808
|
+
_isMethod: true,
|
|
809
|
+
_recvTypeName: recvTypeName,
|
|
810
|
+
_methodName: d.name,
|
|
811
|
+
_exportName: `${recvTypeName}_${d.name}`,
|
|
812
|
+
_sourceDecl: d,
|
|
813
|
+
_pkgName: pkgName,
|
|
814
|
+
_pkgTarget: pkgTarget,
|
|
815
|
+
};
|
|
816
|
+
funcDecls.push(normFn);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// 1b. Scan and lift all closures (FuncLits)
|
|
822
|
+
let closureCounter = 0;
|
|
823
|
+
const closures = [];
|
|
824
|
+
|
|
825
|
+
function scanClosures(node, rootFn) {
|
|
826
|
+
if (!node || typeof node !== "object") return;
|
|
827
|
+
if (Array.isArray(node)) {
|
|
828
|
+
for (const item of node) scanClosures(item, rootFn);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (node.kind === "FuncLit") {
|
|
832
|
+
closureCounter++;
|
|
833
|
+
const closureName = `_closure$${closureCounter}`;
|
|
834
|
+
node._liftedName = closureName;
|
|
835
|
+
node._rootFuncDecl = rootFn;
|
|
836
|
+
|
|
837
|
+
const liftedFn = {
|
|
838
|
+
kind: "FuncDecl",
|
|
839
|
+
name: closureName,
|
|
840
|
+
params: [
|
|
841
|
+
{ name: "__env", type: { kind: "basic", name: "any" } },
|
|
842
|
+
...(node.params ?? []),
|
|
843
|
+
],
|
|
844
|
+
returnType: node.returnType,
|
|
845
|
+
body: node.body,
|
|
846
|
+
_isClosure: true,
|
|
847
|
+
_funcLit: node,
|
|
848
|
+
_rootFuncDecl: rootFn,
|
|
849
|
+
};
|
|
850
|
+
node._liftedFn = liftedFn;
|
|
851
|
+
closures.push(liftedFn);
|
|
852
|
+
|
|
853
|
+
scanClosures(node.body, rootFn);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
for (const key of Object.keys(node)) {
|
|
857
|
+
if (key.startsWith("_")) continue;
|
|
858
|
+
scanClosures(node[key], rootFn);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
for (const fn of funcDecls) {
|
|
863
|
+
fn._rootFuncDecl = fn;
|
|
864
|
+
if (fn.body) {
|
|
865
|
+
scanClosures(fn.body, fn);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
funcDecls.push(...closures);
|
|
869
|
+
|
|
870
|
+
// 1c. Add trampolines for named functions (for when they are passed as function values)
|
|
871
|
+
const topLevelFuncs = new Map();
|
|
872
|
+
for (const fn of funcDecls) {
|
|
873
|
+
if (
|
|
874
|
+
!fn._isClosure &&
|
|
875
|
+
!fn._isMethod &&
|
|
876
|
+
fn.name !== "main" &&
|
|
877
|
+
!fn.name.startsWith("_")
|
|
878
|
+
) {
|
|
879
|
+
topLevelFuncs.set(fn.name, fn);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
mod.topLevelFuncMap = topLevelFuncs;
|
|
883
|
+
|
|
884
|
+
const trampolines = [];
|
|
885
|
+
for (const [name, targetFn] of topLevelFuncs.entries()) {
|
|
886
|
+
const trampName = `_tramp$${name}`;
|
|
887
|
+
const callExpr = {
|
|
888
|
+
kind: "CallExpr",
|
|
889
|
+
func: { kind: "Ident", name: targetFn.name },
|
|
890
|
+
args: (targetFn.params ?? []).map((p) => ({
|
|
891
|
+
kind: "Ident",
|
|
892
|
+
name: p.name,
|
|
893
|
+
_type: p.type,
|
|
894
|
+
})),
|
|
895
|
+
_type: targetFn.returnType ?? { kind: "basic", name: "void" },
|
|
896
|
+
};
|
|
897
|
+
const bodyStmts = targetFn.returnType
|
|
898
|
+
? [
|
|
899
|
+
{
|
|
900
|
+
kind: "ReturnStmt",
|
|
901
|
+
values: [callExpr],
|
|
902
|
+
},
|
|
903
|
+
]
|
|
904
|
+
: [
|
|
905
|
+
{ kind: "ExprStmt", expr: callExpr },
|
|
906
|
+
{ kind: "ReturnStmt", values: [] },
|
|
907
|
+
];
|
|
908
|
+
|
|
909
|
+
const trampFn = {
|
|
910
|
+
kind: "FuncDecl",
|
|
911
|
+
name: trampName,
|
|
912
|
+
params: [
|
|
913
|
+
{ name: "__env", type: { kind: "basic", name: "any" } },
|
|
914
|
+
...(targetFn.params ?? []),
|
|
915
|
+
],
|
|
916
|
+
returnType: targetFn.returnType,
|
|
917
|
+
body: {
|
|
918
|
+
kind: "Block",
|
|
919
|
+
stmts: bodyStmts,
|
|
920
|
+
list: bodyStmts,
|
|
921
|
+
},
|
|
922
|
+
_isClosure: true,
|
|
923
|
+
_isTrampoline: true,
|
|
924
|
+
_targetFuncName: name,
|
|
925
|
+
_rootFuncDecl: targetFn,
|
|
926
|
+
};
|
|
927
|
+
trampolines.push(trampFn);
|
|
928
|
+
}
|
|
929
|
+
funcDecls.push(...trampolines);
|
|
930
|
+
|
|
931
|
+
const pendingInits = _collectPackageGlobals(progs, mod, checker);
|
|
932
|
+
_collectPackageConsts(progs, mod);
|
|
933
|
+
const globalInitFn =
|
|
934
|
+
pendingInits.length > 0 || initNames.length > 0
|
|
935
|
+
? _makeGlobalInitFunc(pendingInits, initNames)
|
|
936
|
+
: null;
|
|
937
|
+
if (globalInitFn) funcDecls.push(globalInitFn);
|
|
938
|
+
|
|
939
|
+
const boundsPanicFn = {
|
|
940
|
+
kind: "FuncDecl",
|
|
941
|
+
name: "__bounds_panic",
|
|
942
|
+
params: [],
|
|
943
|
+
returnType: null,
|
|
944
|
+
_isRuntimePanic: true,
|
|
945
|
+
_panicMsg: "runtime error: index out of range",
|
|
946
|
+
};
|
|
947
|
+
funcDecls.push(boundsPanicFn);
|
|
948
|
+
|
|
949
|
+
const sliceBoundsPanicFn = {
|
|
950
|
+
kind: "FuncDecl",
|
|
951
|
+
name: "__slice_bounds_panic",
|
|
952
|
+
params: [],
|
|
953
|
+
returnType: null,
|
|
954
|
+
_isRuntimePanic: true,
|
|
955
|
+
_panicMsg: "runtime error: slice bounds out of range",
|
|
956
|
+
};
|
|
957
|
+
funcDecls.push(sliceBoundsPanicFn);
|
|
958
|
+
|
|
959
|
+
// 1d. Boundary metadata (exported surface of wasm packages)
|
|
960
|
+
const boundaryMeta = options.boundary
|
|
961
|
+
? collectBoundaryMeta(progs, funcDecls, mod)
|
|
962
|
+
: null;
|
|
963
|
+
|
|
964
|
+
// 2. Pre-scan for needed imports so import func indices are fixed
|
|
965
|
+
for (const fn of funcDecls) {
|
|
966
|
+
_scanImportsInBody(fn.body, mod);
|
|
967
|
+
}
|
|
968
|
+
if (boundaryMeta) scanBoundaryImports(mod, boundaryMeta);
|
|
969
|
+
|
|
970
|
+
const importFuncCount = mod.imports.filter((i) => i.kind === "func").length;
|
|
971
|
+
mod._importsLocked = true;
|
|
972
|
+
|
|
973
|
+
// 3. Register all internal functions and their type signatures
|
|
974
|
+
for (let i = 0; i < funcDecls.length; i++) {
|
|
975
|
+
const fn = funcDecls[i];
|
|
976
|
+
const globalIdx = importFuncCount + i;
|
|
977
|
+
mod.funcMap.set(fn.name, globalIdx);
|
|
978
|
+
if (fn._isClosure) {
|
|
979
|
+
if (fn._funcLit) {
|
|
980
|
+
fn._funcLit._globalFuncIndex = globalIdx;
|
|
981
|
+
}
|
|
982
|
+
mod.addRefFuncElement(globalIdx);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const paramTypes = (fn.params ?? []).map((p) =>
|
|
986
|
+
toWasmType(p.type, checker, mod),
|
|
987
|
+
);
|
|
988
|
+
mod.funcParamTypes.set(fn.name, paramTypes);
|
|
989
|
+
mod.funcParamGoTypes.set(
|
|
990
|
+
fn.name,
|
|
991
|
+
(fn.params ?? []).map((p) => p.type),
|
|
992
|
+
);
|
|
993
|
+
const returnTypes = [];
|
|
994
|
+
if (fn.returnType) {
|
|
995
|
+
if (
|
|
996
|
+
fn.returnType.kind === "TupleType" ||
|
|
997
|
+
fn.returnType.kind === "tuple"
|
|
998
|
+
) {
|
|
999
|
+
for (const t of fn.returnType.types) {
|
|
1000
|
+
const wt = toWasmType(t, checker, mod);
|
|
1001
|
+
if (wt) returnTypes.push(wt);
|
|
1002
|
+
}
|
|
1003
|
+
} else {
|
|
1004
|
+
const wt = toWasmType(fn.returnType, checker, mod);
|
|
1005
|
+
if (wt) returnTypes.push(wt);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
|
|
1010
|
+
fn._typeIndex = typeIndex;
|
|
1011
|
+
fn._globalFuncIndex = globalIdx;
|
|
1012
|
+
|
|
1013
|
+
// Export if public (capitalized) or main
|
|
1014
|
+
if (
|
|
1015
|
+
!fn._isGlobalInit &&
|
|
1016
|
+
(options.exportAll ||
|
|
1017
|
+
fn.name === "main" ||
|
|
1018
|
+
(fn.name[0] >= "A" && fn.name[0] <= "Z") ||
|
|
1019
|
+
(fn._isMethod && fn._methodName[0] >= "A" && fn._methodName[0] <= "Z"))
|
|
1020
|
+
) {
|
|
1021
|
+
mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
|
|
1022
|
+
if (fn._isMethod && fn._exportName) {
|
|
1023
|
+
mod.exports.push({
|
|
1024
|
+
name: fn._exportName,
|
|
1025
|
+
kind: "func",
|
|
1026
|
+
index: globalIdx,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// 4. Emit function bodies
|
|
1033
|
+
for (const fn of funcDecls) {
|
|
1034
|
+
const emitter = new FunctionEmitter(mod, fn, fn._globalFuncIndex);
|
|
1035
|
+
if (fn._isRuntimePanic) {
|
|
1036
|
+
const strIdx = mod.internString(fn._panicMsg);
|
|
1037
|
+
const funcIdx = mod.getStringImportIndex();
|
|
1038
|
+
emitter.pushInstruction({ op: "i32.const", value: strIdx });
|
|
1039
|
+
emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
|
|
1040
|
+
emitter.emitPanicThrow();
|
|
1041
|
+
} else if (fn.body) {
|
|
1042
|
+
emitter.emitFunctionBody(fn.body);
|
|
1043
|
+
}
|
|
1044
|
+
// If last instruction is not return or unreachable, auto-emit return or unreachable
|
|
1045
|
+
let body = emitter.body;
|
|
1046
|
+
if (
|
|
1047
|
+
body.length === 0 ||
|
|
1048
|
+
(body[body.length - 1].op !== "return" &&
|
|
1049
|
+
body[body.length - 1].op !== "unreachable")
|
|
1050
|
+
) {
|
|
1051
|
+
if (emitter.returnTypes.length === 0) {
|
|
1052
|
+
body.push({ op: "return" });
|
|
1053
|
+
} else {
|
|
1054
|
+
body.push({ op: "unreachable" });
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
body = peepholeOptimize(body);
|
|
1059
|
+
|
|
1060
|
+
mod.funcs.push({
|
|
1061
|
+
typeIndex: fn._typeIndex,
|
|
1062
|
+
locals: emitter.localTypes,
|
|
1063
|
+
body,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// 5. Boundary marshalling helpers + JS facade
|
|
1068
|
+
let facade = null;
|
|
1069
|
+
if (boundaryMeta) {
|
|
1070
|
+
addBoundaryHelpers(mod, boundaryMeta);
|
|
1071
|
+
facade = generateFacade(boundaryMeta, {
|
|
1072
|
+
stringTable: mod.stringTable,
|
|
1073
|
+
callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const moduleIR = {
|
|
1078
|
+
types: mod.types,
|
|
1079
|
+
imports: mod.imports,
|
|
1080
|
+
tags: mod.tags,
|
|
1081
|
+
globals: mod.globals,
|
|
1082
|
+
elements: mod.elements,
|
|
1083
|
+
funcs: mod.funcs,
|
|
1084
|
+
exports: mod.exports,
|
|
1085
|
+
start: globalInitFn ? globalInitFn._globalFuncIndex : null,
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const wasmBytes = encodeModule(moduleIR);
|
|
1089
|
+
const watText = options.emitWat ? emitWat(moduleIR) : null;
|
|
1090
|
+
|
|
1091
|
+
return {
|
|
1092
|
+
wasm: wasmBytes,
|
|
1093
|
+
wat: watText,
|
|
1094
|
+
stringTable: mod.stringTable,
|
|
1095
|
+
moduleIR,
|
|
1096
|
+
facade,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Package-level `const` declarations with literal values (iota is already
|
|
1101
|
+
// substituted by the parser). Non-literal constant expressions are not
|
|
1102
|
+
// supported by the wasm backend yet.
|
|
1103
|
+
function _collectPackageConsts(progs, mod) {
|
|
1104
|
+
for (const p of progs) {
|
|
1105
|
+
for (const d of p.decls ?? []) {
|
|
1106
|
+
if (d.kind !== "ConstDecl") continue;
|
|
1107
|
+
for (const spec of d.decls ?? []) {
|
|
1108
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
1109
|
+
let lit = spec.value?.[i];
|
|
1110
|
+
if (
|
|
1111
|
+
lit?.kind === "UnaryExpr" &&
|
|
1112
|
+
lit.op === "-" &&
|
|
1113
|
+
lit.operand?.kind === "BasicLit"
|
|
1114
|
+
) {
|
|
1115
|
+
lit = { ...lit.operand, value: `-${lit.operand.value}` };
|
|
1116
|
+
}
|
|
1117
|
+
if (lit?.kind === "BasicLit") mod.constCache.set(spec.names[i], lit);
|
|
1118
|
+
else if (lit) mod.nonLiteralConsts.add(spec.names[i]);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function _scanImportsInBody(node, mod) {
|
|
1126
|
+
if (!node || typeof node !== "object") return;
|
|
1127
|
+
|
|
1128
|
+
if (node.kind === "CallExpr") {
|
|
1129
|
+
const { func, args } = node;
|
|
1130
|
+
if (
|
|
1131
|
+
func.kind === "Ident" &&
|
|
1132
|
+
(func.name === "print" || func.name === "println")
|
|
1133
|
+
) {
|
|
1134
|
+
const isPrintln = func.name === "println";
|
|
1135
|
+
if (args.length === 0 && isPrintln) {
|
|
1136
|
+
mod.getPrintlnEmptyIndex();
|
|
1137
|
+
}
|
|
1138
|
+
for (let i = 0; i < args.length; i++) {
|
|
1139
|
+
const arg = args[i];
|
|
1140
|
+
const isLast = i === args.length - 1 && isPrintln;
|
|
1141
|
+
const isBool =
|
|
1142
|
+
arg._type?.name === "bool" ||
|
|
1143
|
+
(arg.kind === "BasicLit" && arg.litKind === "BOOL");
|
|
1144
|
+
const wType = toWasmType(arg._type, mod.checker);
|
|
1145
|
+
mod.getLogImportIndex(wType, isLast, isBool);
|
|
1146
|
+
}
|
|
1147
|
+
} else if (
|
|
1148
|
+
func.kind === "SelectorExpr" &&
|
|
1149
|
+
func.expr?.name === "math" &&
|
|
1150
|
+
func.field
|
|
1151
|
+
) {
|
|
1152
|
+
const jsMathMap = {
|
|
1153
|
+
Sin: "sin",
|
|
1154
|
+
Cos: "cos",
|
|
1155
|
+
Tan: "tan",
|
|
1156
|
+
Asin: "asin",
|
|
1157
|
+
Acos: "acos",
|
|
1158
|
+
Atan: "atan",
|
|
1159
|
+
Atan2: "atan2",
|
|
1160
|
+
Pow: "pow",
|
|
1161
|
+
Exp: "exp",
|
|
1162
|
+
Log: "log",
|
|
1163
|
+
Log2: "log2",
|
|
1164
|
+
Log10: "log10",
|
|
1165
|
+
Round: "round",
|
|
1166
|
+
};
|
|
1167
|
+
if (jsMathMap[func.field]) {
|
|
1168
|
+
const isBinary = func.field === "Atan2" || func.field === "Pow";
|
|
1169
|
+
mod.getMathImportIndex(jsMathMap[func.field], isBinary);
|
|
1170
|
+
}
|
|
1171
|
+
} else if (
|
|
1172
|
+
func.kind === "SelectorExpr" &&
|
|
1173
|
+
isTestingT(func.expr?._type) &&
|
|
1174
|
+
func.field
|
|
1175
|
+
) {
|
|
1176
|
+
mod.internString(func.field);
|
|
1177
|
+
if (func.field === "Name") mod.getTestingNameImportIndex();
|
|
1178
|
+
else if (func.field === "Failed" || func.field === "Skipped")
|
|
1179
|
+
mod.getTestingFlagImportIndex();
|
|
1180
|
+
else if (func.field !== "Run") {
|
|
1181
|
+
mod.getTestingCallImportIndex();
|
|
1182
|
+
for (const arg of args) {
|
|
1183
|
+
mod.getTestingArgImportIndex(
|
|
1184
|
+
toWasmType(arg._type, mod.checker),
|
|
1185
|
+
arg._type?.name === "bool" ||
|
|
1186
|
+
(arg.kind === "BasicLit" && arg.litKind === "BOOL"),
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (node.kind === "BasicLit" && node.litKind === "STRING") {
|
|
1194
|
+
mod.getStringImportIndex();
|
|
1195
|
+
const strVal = String(node.value ?? "");
|
|
1196
|
+
mod.internString(strVal);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Recurse child properties
|
|
1200
|
+
for (const key of Object.keys(node)) {
|
|
1201
|
+
if (key.startsWith("_")) continue;
|
|
1202
|
+
const child = node[key];
|
|
1203
|
+
if (Array.isArray(child)) {
|
|
1204
|
+
for (const item of child) _scanImportsInBody(item, mod);
|
|
1205
|
+
} else if (child && typeof child === "object") {
|
|
1206
|
+
_scanImportsInBody(child, mod);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export function compileWasm(source, options = {}) {
|
|
1212
|
+
const filename = options.filename ?? "main.go";
|
|
1213
|
+
const tokens = new Lexer(source, filename).tokenize();
|
|
1214
|
+
const ast = new Parser(tokens, filename, source).parse();
|
|
1215
|
+
|
|
1216
|
+
const checker = new TypeChecker();
|
|
1217
|
+
checker.target = "wasm";
|
|
1218
|
+
checker.pkgName = ast.pkg?.name ?? "main";
|
|
1219
|
+
|
|
1220
|
+
const errors = checker.check(ast);
|
|
1221
|
+
if (errors.length > 0) {
|
|
1222
|
+
return { wasm: null, errors };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const lowerRes = lower([ast], checker);
|
|
1226
|
+
const res = compileWasmModule([ast], checker, lowerRes, {
|
|
1227
|
+
...options,
|
|
1228
|
+
exportAll: options.exportAll ?? true,
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
return { ...res, errors: [] };
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function _collectPackageGlobals(progs, mod, checker) {
|
|
1235
|
+
const pending = []; // non-constant initializers, run by the start function
|
|
1236
|
+
for (const p of progs) {
|
|
1237
|
+
for (const d of p.decls ?? []) {
|
|
1238
|
+
if (d.kind !== "VarDecl") continue;
|
|
1239
|
+
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
1240
|
+
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
1241
|
+
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
1242
|
+
for (let i = 0; i < names.length; i++) {
|
|
1243
|
+
const name = names[i];
|
|
1244
|
+
const rawType = spec.type ?? values[i]?._type;
|
|
1245
|
+
const wType = toWasmType(rawType, checker, mod);
|
|
1246
|
+
const gIdx = mod.globals.length;
|
|
1247
|
+
let initInsts = [{ op: `${wType}.const`, value: 0 }];
|
|
1248
|
+
if (wType === "i64") initInsts = [{ op: "i64.const", value: 0n }];
|
|
1249
|
+
else if (wType === "f32" || wType === "f64")
|
|
1250
|
+
initInsts = [{ op: `${wType}.const`, value: 0.0 }];
|
|
1251
|
+
else if (
|
|
1252
|
+
wType === "externref" ||
|
|
1253
|
+
wType === "anyref" ||
|
|
1254
|
+
typeof wType === "object"
|
|
1255
|
+
) {
|
|
1256
|
+
const sliceInfo =
|
|
1257
|
+
typeof wType === "object" && typeof wType.typeIndex === "number"
|
|
1258
|
+
? mod.getSliceTypeByIndex(wType.typeIndex)
|
|
1259
|
+
: null;
|
|
1260
|
+
if (sliceInfo) {
|
|
1261
|
+
initInsts = [
|
|
1262
|
+
{ op: "global.get", index: sliceInfo.emptyGlobalIndex },
|
|
1263
|
+
];
|
|
1264
|
+
} else if (
|
|
1265
|
+
typeof wType === "object" &&
|
|
1266
|
+
typeof wType.typeIndex === "number" &&
|
|
1267
|
+
rawType?.kind !== "pointer"
|
|
1268
|
+
) {
|
|
1269
|
+
const structInfo = Array.from(mod.structTypes.values()).find(
|
|
1270
|
+
(s) => s.typeIndex === wType.typeIndex,
|
|
1271
|
+
);
|
|
1272
|
+
if (structInfo) {
|
|
1273
|
+
initInsts = [];
|
|
1274
|
+
for (const f of structInfo.fields) {
|
|
1275
|
+
if (f.wType === "i64")
|
|
1276
|
+
initInsts.push({ op: "i64.const", value: 0n });
|
|
1277
|
+
else if (f.wType === "f32" || f.wType === "f64")
|
|
1278
|
+
initInsts.push({ op: `${f.wType}.const`, value: 0.0 });
|
|
1279
|
+
else if (typeof f.wType === "object") {
|
|
1280
|
+
const fieldSlice =
|
|
1281
|
+
typeof f.wType.typeIndex === "number"
|
|
1282
|
+
? mod.getSliceTypeByIndex(f.wType.typeIndex)
|
|
1283
|
+
: null;
|
|
1284
|
+
if (fieldSlice) {
|
|
1285
|
+
initInsts.push({
|
|
1286
|
+
op: "global.get",
|
|
1287
|
+
index: fieldSlice.emptyGlobalIndex,
|
|
1288
|
+
});
|
|
1289
|
+
} else {
|
|
1290
|
+
initInsts.push({
|
|
1291
|
+
op: "ref.null",
|
|
1292
|
+
heapType: f.wType.typeIndex ?? "any",
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
} else initInsts.push({ op: "i32.const", value: 0 });
|
|
1296
|
+
}
|
|
1297
|
+
initInsts.push({
|
|
1298
|
+
op: "struct.new",
|
|
1299
|
+
typeIndex: structInfo.typeIndex,
|
|
1300
|
+
});
|
|
1301
|
+
} else {
|
|
1302
|
+
initInsts = [
|
|
1303
|
+
{ op: "ref.null", heapType: wType.typeIndex ?? "any" },
|
|
1304
|
+
];
|
|
1305
|
+
}
|
|
1306
|
+
} else {
|
|
1307
|
+
initInsts = [
|
|
1308
|
+
{
|
|
1309
|
+
op: "ref.null",
|
|
1310
|
+
heapType:
|
|
1311
|
+
typeof wType === "object"
|
|
1312
|
+
? (wType.heapType ?? wType.typeIndex ?? "any")
|
|
1313
|
+
: wType.replace("ref", ""),
|
|
1314
|
+
},
|
|
1315
|
+
];
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
let litNode = values?.[i];
|
|
1320
|
+
let sign = 1;
|
|
1321
|
+
if (
|
|
1322
|
+
litNode &&
|
|
1323
|
+
litNode.kind === "UnaryExpr" &&
|
|
1324
|
+
litNode.op === "-" &&
|
|
1325
|
+
litNode.operand?.kind === "BasicLit"
|
|
1326
|
+
) {
|
|
1327
|
+
litNode = litNode.operand;
|
|
1328
|
+
sign = -1;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
let isConstInit = false;
|
|
1332
|
+
if (litNode && litNode.kind === "BasicLit") {
|
|
1333
|
+
if (litNode.litKind === "INT") {
|
|
1334
|
+
isConstInit = true;
|
|
1335
|
+
initInsts = [
|
|
1336
|
+
wType === "i64"
|
|
1337
|
+
? {
|
|
1338
|
+
op: "i64.const",
|
|
1339
|
+
value: BigInt(sign) * BigInt(litNode.value),
|
|
1340
|
+
}
|
|
1341
|
+
: {
|
|
1342
|
+
op: "i32.const",
|
|
1343
|
+
value: (sign * Number(litNode.value)) | 0,
|
|
1344
|
+
},
|
|
1345
|
+
];
|
|
1346
|
+
} else if (litNode.litKind === "FLOAT") {
|
|
1347
|
+
isConstInit = true;
|
|
1348
|
+
initInsts = [
|
|
1349
|
+
{
|
|
1350
|
+
op: `${wType}.const`,
|
|
1351
|
+
value: sign * Number(litNode.value),
|
|
1352
|
+
},
|
|
1353
|
+
];
|
|
1354
|
+
} else if (litNode.litKind === "BOOL") {
|
|
1355
|
+
isConstInit = true;
|
|
1356
|
+
initInsts = [
|
|
1357
|
+
{
|
|
1358
|
+
op: "i32.const",
|
|
1359
|
+
value: litNode.value === "true" ? 1 : 0,
|
|
1360
|
+
},
|
|
1361
|
+
];
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (values[i] && !isConstInit && names.length === values.length) {
|
|
1365
|
+
pending.push({ name, expr: values[i] });
|
|
1366
|
+
}
|
|
1367
|
+
mod.globals.push({
|
|
1368
|
+
type: wType,
|
|
1369
|
+
mutable: true,
|
|
1370
|
+
init: initInsts,
|
|
1371
|
+
});
|
|
1372
|
+
mod.globalCache.set(name, {
|
|
1373
|
+
index: gIdx,
|
|
1374
|
+
type: wType,
|
|
1375
|
+
goType: rawType,
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return pending;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Synthesises `__init_globals()` assigning every non-constant package-level
|
|
1385
|
+
// initializer in declaration order and invoking package `init()` functions;
|
|
1386
|
+
// registered as the module start function.
|
|
1387
|
+
function _makeGlobalInitFunc(pending, initNames = []) {
|
|
1388
|
+
const stmts = pending.map(({ name, expr }) => ({
|
|
1389
|
+
kind: "AssignStmt",
|
|
1390
|
+
lhs: [{ kind: "Ident", name, _type: expr._type }],
|
|
1391
|
+
op: "=",
|
|
1392
|
+
rhs: [expr],
|
|
1393
|
+
}));
|
|
1394
|
+
for (const initName of initNames) {
|
|
1395
|
+
stmts.push({
|
|
1396
|
+
kind: "ExprStmt",
|
|
1397
|
+
expr: {
|
|
1398
|
+
kind: "CallExpr",
|
|
1399
|
+
func: { kind: "Ident", name: initName },
|
|
1400
|
+
args: [],
|
|
1401
|
+
},
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
stmts.push({ kind: "ReturnStmt", values: [] });
|
|
1405
|
+
return {
|
|
1406
|
+
kind: "FuncDecl",
|
|
1407
|
+
name: "__init_globals",
|
|
1408
|
+
params: [],
|
|
1409
|
+
returnType: null,
|
|
1410
|
+
body: { kind: "Block", stmts, list: stmts },
|
|
1411
|
+
_isGlobalInit: true,
|
|
1412
|
+
};
|
|
1413
|
+
}
|