gofront 1.4.0 → 1.6.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/README.md +174 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /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/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/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.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/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
|
@@ -0,0 +1,1376 @@
|
|
|
1
|
+
// src/backend/wasm/emit.js
|
|
2
|
+
// FunctionEmitter core: function prologue/epilogue, defer/recover frames,
|
|
3
|
+
// locals, control stack and shared helpers. Statement, expression, builtin,
|
|
4
|
+
// map and stdlib emission live in the emit-*.js mixins composed at the bottom.
|
|
5
|
+
|
|
6
|
+
import { hasDefer, hasDirectRecover } from "../../lower/functions.js";
|
|
7
|
+
import { BuiltinsEmitter } from "./emit-builtins.js";
|
|
8
|
+
import { ExprsEmitter } from "./emit-exprs.js";
|
|
9
|
+
import { MapsEmitter } from "./emit-maps.js";
|
|
10
|
+
import { SharedEmitter } from "./emit-shared.js";
|
|
11
|
+
import { StdlibEmitter } from "./emit-stdlib.js";
|
|
12
|
+
import { StmtsEmitter } from "./emit-stmts.js";
|
|
13
|
+
import {
|
|
14
|
+
isArrayType,
|
|
15
|
+
isPointerToStruct,
|
|
16
|
+
isSliceType,
|
|
17
|
+
isStringType,
|
|
18
|
+
isStructType,
|
|
19
|
+
toWasmType,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
|
|
22
|
+
// The emitter is one class split over several files by concern. Methods from
|
|
23
|
+
// the emit-*.js modules are assigned to FunctionEmitter.prototype below; `this`
|
|
24
|
+
// is shared, so cross-file calls are plain method calls.
|
|
25
|
+
export class FunctionEmitter {
|
|
26
|
+
constructor(moduleEmitter, funcDecl, funcIndex) {
|
|
27
|
+
this.mod = moduleEmitter;
|
|
28
|
+
this.funcDecl = funcDecl;
|
|
29
|
+
this.funcIndex = funcIndex;
|
|
30
|
+
|
|
31
|
+
this.rootFuncDecl = this.funcDecl._rootFuncDecl ?? this.funcDecl;
|
|
32
|
+
// Methods are normalized into synthetic FuncDecls; lower() keyed on the original MethodDecl.
|
|
33
|
+
this.captureAnalysis =
|
|
34
|
+
this.mod.lowerResult?.captures?.get(
|
|
35
|
+
this.rootFuncDecl._sourceDecl ?? this.rootFuncDecl,
|
|
36
|
+
) ?? null;
|
|
37
|
+
this.mutatedCaptures = this.captureAnalysis?.mutatedCaptures ?? new Set();
|
|
38
|
+
|
|
39
|
+
this.capturedNames = this.funcDecl._funcLit
|
|
40
|
+
? Array.from(
|
|
41
|
+
this.captureAnalysis?.capturesByClosure?.get(
|
|
42
|
+
this.funcDecl._funcLit,
|
|
43
|
+
) ?? [],
|
|
44
|
+
).sort()
|
|
45
|
+
: [];
|
|
46
|
+
|
|
47
|
+
this.locals = new Map(); // name -> { index, type, goType, isParam, isBoxed, boxInfo }
|
|
48
|
+
this.cachedGlobals = new Map(); // globalName -> { index, globalInfo }
|
|
49
|
+
this.localTypes = []; // list of additional local types (beyond params)
|
|
50
|
+
this.controlStack = []; // stack of { type: 'block'|'loop', label: string }
|
|
51
|
+
this.body = []; // emitted instructions
|
|
52
|
+
|
|
53
|
+
this.tempCounter = 0;
|
|
54
|
+
|
|
55
|
+
this.returnTypes = [];
|
|
56
|
+
if (this.funcDecl.returnType) {
|
|
57
|
+
if (
|
|
58
|
+
this.funcDecl.returnType.kind === "TupleType" ||
|
|
59
|
+
this.funcDecl.returnType.kind === "tuple"
|
|
60
|
+
) {
|
|
61
|
+
for (const t of this.funcDecl.returnType.types) {
|
|
62
|
+
const wt = this.toWasmType(t);
|
|
63
|
+
if (wt) this.returnTypes.push(wt);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const wt = this.toWasmType(this.funcDecl.returnType);
|
|
67
|
+
if (wt) this.returnTypes.push(wt);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this._initParams();
|
|
72
|
+
|
|
73
|
+
this.hasDefer = false;
|
|
74
|
+
this.namedReturnVars = null;
|
|
75
|
+
this.returnTempLocals = null;
|
|
76
|
+
this.defersLocalIndex = null;
|
|
77
|
+
this.panicExnLocalIndex = null;
|
|
78
|
+
this.hasPanicLocalIndex = null;
|
|
79
|
+
this.curClosureLocalIndex = null;
|
|
80
|
+
// Local holding the `__deferArmed` snapshot taken in the prologue; null
|
|
81
|
+
// when this frame can never legitimately recover().
|
|
82
|
+
this.recoverOkLocalIndex = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Go only honours recover() when it is called directly by a deferred
|
|
86
|
+
// function. The defer loop sets `__deferArmed` right before invoking a
|
|
87
|
+
// deferred closure; the closure's prologue copies the flag into a local and
|
|
88
|
+
// clears the global, so any further callee sees it unset. Synthesized
|
|
89
|
+
// wrappers (`defer f(x)` lowered to `func(){ f(__defarg$1) }`) that forward
|
|
90
|
+
// to a callee which may itself call recover() leave the flag armed.
|
|
91
|
+
_emitRecoverArmPrologue(body) {
|
|
92
|
+
const lit = this.funcDecl._funcLit;
|
|
93
|
+
const isTarget = Boolean(lit?._isDeferTarget);
|
|
94
|
+
const forwards = isTarget && Boolean(lit._deferForward);
|
|
95
|
+
if (forwards) return;
|
|
96
|
+
if (!isTarget && !hasDirectRecover(body)) return;
|
|
97
|
+
const armed = this.mod.getDeferArmedGlobalIndex();
|
|
98
|
+
const local = this.allocLocal("__recoverOk", "i32", null, true);
|
|
99
|
+
this.pushInstruction({ op: "global.get", index: armed });
|
|
100
|
+
this.pushInstruction({ op: "local.set", index: local });
|
|
101
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
102
|
+
this.pushInstruction({ op: "global.set", index: armed });
|
|
103
|
+
this.recoverOkLocalIndex = local;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
emitFunctionBody(body) {
|
|
107
|
+
if (
|
|
108
|
+
this.funcDecl._isClosure &&
|
|
109
|
+
!this.funcDecl._isTrampoline &&
|
|
110
|
+
this.capturedNames.length > 0
|
|
111
|
+
) {
|
|
112
|
+
this._unpackClosureEnv();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this._boxMutatedParams();
|
|
116
|
+
|
|
117
|
+
this._cacheScratchGlobals();
|
|
118
|
+
|
|
119
|
+
this._emitRecoverArmPrologue(body);
|
|
120
|
+
|
|
121
|
+
const hasDef = hasDefer(body);
|
|
122
|
+
this.hasDefer = hasDef;
|
|
123
|
+
|
|
124
|
+
if (hasDef) {
|
|
125
|
+
this._emitFunctionBodyWithDefer(body);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const namedReturns = this.funcDecl.returnType?._namedReturns;
|
|
130
|
+
if (namedReturns && namedReturns.length > 0) {
|
|
131
|
+
this.namedReturnVars = [];
|
|
132
|
+
for (let i = 0; i < namedReturns.length; i++) {
|
|
133
|
+
const r = namedReturns[i];
|
|
134
|
+
const isBlank = !r.name || r.name === "_";
|
|
135
|
+
const varName = isBlank ? `__ret_blank$${i}` : r.name;
|
|
136
|
+
const wType = this.toWasmType(r.type);
|
|
137
|
+
const localIdx = this.allocLocal(varName, wType, r.type, isBlank);
|
|
138
|
+
this.namedReturnVars.push({
|
|
139
|
+
name: varName,
|
|
140
|
+
localIdx,
|
|
141
|
+
type: wType,
|
|
142
|
+
goType: r.type,
|
|
143
|
+
isBlank,
|
|
144
|
+
});
|
|
145
|
+
this.emitZeroValue(r.type, wType);
|
|
146
|
+
const localInfo = this.locals.get(varName);
|
|
147
|
+
if (localInfo?.isBoxed) {
|
|
148
|
+
this.pushInstruction({
|
|
149
|
+
op: "struct.new",
|
|
150
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
this.pushInstruction({ op: "local.set", index: localIdx });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const needsOobBlock = this._needsOobBlock(body);
|
|
158
|
+
if (needsOobBlock) {
|
|
159
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
160
|
+
this.pushControl("oob");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.emitBlock(body);
|
|
164
|
+
|
|
165
|
+
if (needsOobBlock) {
|
|
166
|
+
const lastOp = this.body[this.body.length - 1]?.op;
|
|
167
|
+
if (lastOp !== "return" && lastOp !== "unreachable") {
|
|
168
|
+
if (this.returnTypes.length === 0) {
|
|
169
|
+
this.pushInstruction({ op: "return" });
|
|
170
|
+
} else {
|
|
171
|
+
this.pushInstruction({ op: "unreachable" });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
this.pushInstruction("end");
|
|
175
|
+
|
|
176
|
+
this.pushInstruction({
|
|
177
|
+
op: "call",
|
|
178
|
+
funcIndex: this.mod.getBoundsPanicFuncIndex(),
|
|
179
|
+
});
|
|
180
|
+
this.pushInstruction("unreachable");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_emitFunctionBodyWithDefer(body) {
|
|
185
|
+
const namedReturns = this.funcDecl.returnType?._namedReturns;
|
|
186
|
+
if (namedReturns && namedReturns.length > 0) {
|
|
187
|
+
this.namedReturnVars = [];
|
|
188
|
+
for (let i = 0; i < namedReturns.length; i++) {
|
|
189
|
+
const r = namedReturns[i];
|
|
190
|
+
const isBlank = !r.name || r.name === "_";
|
|
191
|
+
const varName = isBlank ? `__ret_blank$${i}` : r.name;
|
|
192
|
+
const wType = this.toWasmType(r.type);
|
|
193
|
+
const localIdx = this.allocLocal(varName, wType, r.type, isBlank);
|
|
194
|
+
this.namedReturnVars.push({
|
|
195
|
+
name: varName,
|
|
196
|
+
localIdx,
|
|
197
|
+
type: wType,
|
|
198
|
+
goType: r.type,
|
|
199
|
+
isBlank,
|
|
200
|
+
});
|
|
201
|
+
this.emitZeroValue(r.type, wType);
|
|
202
|
+
const localInfo = this.locals.get(varName);
|
|
203
|
+
if (localInfo?.isBoxed) {
|
|
204
|
+
this.pushInstruction({
|
|
205
|
+
op: "struct.new",
|
|
206
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
this.pushInstruction({ op: "local.set", index: localIdx });
|
|
210
|
+
}
|
|
211
|
+
} else if (this.returnTypes.length > 0) {
|
|
212
|
+
this.returnTempLocals = [];
|
|
213
|
+
for (let i = 0; i < this.returnTypes.length; i++) {
|
|
214
|
+
const wType = this.returnTypes[i];
|
|
215
|
+
const localIdx = this.allocLocal(`__ret$${i}`, wType, null, true);
|
|
216
|
+
this.returnTempLocals.push(localIdx);
|
|
217
|
+
this.emitZeroValue(null, wType);
|
|
218
|
+
this.pushInstruction({ op: "local.set", index: localIdx });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const deferNodeTypeIndex = this.mod.getDeferNodeTypeIndex();
|
|
223
|
+
const deferClosureSig = { kind: "Signature", params: [], results: [] };
|
|
224
|
+
const deferClosureInfo = this.mod.getClosureType(deferClosureSig);
|
|
225
|
+
|
|
226
|
+
this.defersLocalIndex = this.allocLocal(
|
|
227
|
+
"__defers",
|
|
228
|
+
{ kind: "ref", nullable: true, typeIndex: deferNodeTypeIndex },
|
|
229
|
+
null,
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
232
|
+
this.pushInstruction({ op: "ref.null", typeIndex: deferNodeTypeIndex });
|
|
233
|
+
this.pushInstruction({ op: "local.set", index: this.defersLocalIndex });
|
|
234
|
+
|
|
235
|
+
this.panicExnLocalIndex = this.allocLocal(
|
|
236
|
+
"__panicExn",
|
|
237
|
+
"exnref",
|
|
238
|
+
null,
|
|
239
|
+
true,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
this.hasPanicLocalIndex = this.allocLocal("__hasPanic", "i32", null, true);
|
|
243
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
244
|
+
this.pushInstruction({ op: "local.set", index: this.hasPanicLocalIndex });
|
|
245
|
+
|
|
246
|
+
this.curClosureLocalIndex = this.allocLocal(
|
|
247
|
+
"__curClosure",
|
|
248
|
+
{ kind: "ref", nullable: true, typeIndex: deferClosureInfo.typeIndex },
|
|
249
|
+
null,
|
|
250
|
+
true,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
254
|
+
this.pushControl("runDefers");
|
|
255
|
+
|
|
256
|
+
this.pushInstruction({ op: "block", blockType: "exnref" });
|
|
257
|
+
this.pushControl("catchHandler");
|
|
258
|
+
|
|
259
|
+
this.pushInstruction({
|
|
260
|
+
op: "try_table",
|
|
261
|
+
blockType: "void",
|
|
262
|
+
catches: [{ kind: "catch_all_ref", label: 0 }],
|
|
263
|
+
});
|
|
264
|
+
this.pushControl("try_table");
|
|
265
|
+
|
|
266
|
+
const needsOobBlock = this._needsOobBlock(body);
|
|
267
|
+
if (needsOobBlock) {
|
|
268
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
269
|
+
this.pushControl("oob");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this.emitBlock(body);
|
|
273
|
+
|
|
274
|
+
if (needsOobBlock) {
|
|
275
|
+
const lastOp = this.body[this.body.length - 1]?.op;
|
|
276
|
+
if (lastOp !== "return" && lastOp !== "unreachable") {
|
|
277
|
+
this.pushInstruction({
|
|
278
|
+
op: "br",
|
|
279
|
+
depth: this.resolveBranchDepthToRole("runDefers"),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
this.pushInstruction("end");
|
|
283
|
+
|
|
284
|
+
this.pushInstruction({
|
|
285
|
+
op: "call",
|
|
286
|
+
funcIndex: this.mod.getBoundsPanicFuncIndex(),
|
|
287
|
+
});
|
|
288
|
+
this.pushInstruction("unreachable");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Close try_table
|
|
292
|
+
this.pushInstruction("end");
|
|
293
|
+
|
|
294
|
+
// If execution reached end of try_table without return, jump to runDefers
|
|
295
|
+
this.pushInstruction({
|
|
296
|
+
op: "br",
|
|
297
|
+
depth: this.resolveBranchDepthToRole("runDefers"),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Close catchHandler
|
|
301
|
+
this.pushInstruction("end"); // stack has exnref!
|
|
302
|
+
|
|
303
|
+
// Catch handler body:
|
|
304
|
+
this.pushInstruction({ op: "local.set", index: this.panicExnLocalIndex });
|
|
305
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
306
|
+
this.pushInstruction({ op: "local.set", index: this.hasPanicLocalIndex });
|
|
307
|
+
|
|
308
|
+
// Close runDefers block
|
|
309
|
+
this.pushInstruction("end");
|
|
310
|
+
|
|
311
|
+
// Run defers loop
|
|
312
|
+
this.emitRunDefers();
|
|
313
|
+
|
|
314
|
+
// Check if unrecovered panic
|
|
315
|
+
this.emitCheckUnrecoveredPanic();
|
|
316
|
+
|
|
317
|
+
// Return values
|
|
318
|
+
if (this.namedReturnVars?.length > 0) {
|
|
319
|
+
for (const nr of this.namedReturnVars) {
|
|
320
|
+
const loc = this.locals.get(nr.name);
|
|
321
|
+
if (loc.isBoxed) {
|
|
322
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
323
|
+
this.pushInstruction({
|
|
324
|
+
op: "struct.get",
|
|
325
|
+
typeIndex: loc.boxInfo.typeIndex,
|
|
326
|
+
fieldIndex: 0,
|
|
327
|
+
});
|
|
328
|
+
} else {
|
|
329
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
this.pushInstruction("return");
|
|
333
|
+
} else if (this.returnTempLocals?.length > 0) {
|
|
334
|
+
for (const retLoc of this.returnTempLocals) {
|
|
335
|
+
this.pushInstruction({ op: "local.get", index: retLoc });
|
|
336
|
+
}
|
|
337
|
+
this.pushInstruction("return");
|
|
338
|
+
} else {
|
|
339
|
+
this.pushInstruction("return");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
emitRunDefers() {
|
|
344
|
+
const deferNodeTypeIndex = this.mod.getDeferNodeTypeIndex();
|
|
345
|
+
const deferClosureSig = { kind: "Signature", params: [], results: [] };
|
|
346
|
+
const deferClosureInfo = this.mod.getClosureType(deferClosureSig);
|
|
347
|
+
|
|
348
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
349
|
+
this.pushControl("deferLoop");
|
|
350
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
351
|
+
this.pushControl("exitDefers");
|
|
352
|
+
|
|
353
|
+
// If defers is null -> break out of loop
|
|
354
|
+
this.pushInstruction({ op: "local.get", index: this.defersLocalIndex });
|
|
355
|
+
this.pushInstruction({ op: "ref.is_null" });
|
|
356
|
+
this.pushInstruction({ op: "br_if", depth: 0 }); // break to exitDefers
|
|
357
|
+
|
|
358
|
+
// curClosure = defers.fn (field 0)
|
|
359
|
+
this.pushInstruction({ op: "local.get", index: this.defersLocalIndex });
|
|
360
|
+
this.pushInstruction({
|
|
361
|
+
op: "struct.get",
|
|
362
|
+
typeIndex: deferNodeTypeIndex,
|
|
363
|
+
fieldIndex: 0,
|
|
364
|
+
});
|
|
365
|
+
this.pushInstruction({ op: "local.set", index: this.curClosureLocalIndex });
|
|
366
|
+
|
|
367
|
+
// defers = defers.next (field 1)
|
|
368
|
+
this.pushInstruction({ op: "local.get", index: this.defersLocalIndex });
|
|
369
|
+
this.pushInstruction({
|
|
370
|
+
op: "struct.get",
|
|
371
|
+
typeIndex: deferNodeTypeIndex,
|
|
372
|
+
fieldIndex: 1,
|
|
373
|
+
});
|
|
374
|
+
this.pushInstruction({ op: "local.set", index: this.defersLocalIndex });
|
|
375
|
+
|
|
376
|
+
// Call curClosure: curClosure.fn(curClosure.env) with recover armed.
|
|
377
|
+
// The call is wrapped in its own try_table: a panic raised by a deferred
|
|
378
|
+
// function replaces the in-flight panic (Go spec) and the remaining
|
|
379
|
+
// defers still run.
|
|
380
|
+
const armedGlobal = this.mod.getDeferArmedGlobalIndex();
|
|
381
|
+
const panicNodeTypeIndex = this.mod.getPanicNodeTypeIndex();
|
|
382
|
+
const panicGlobal = this.mod.getPanicGlobalIndex();
|
|
383
|
+
|
|
384
|
+
this.pushInstruction({ op: "block", blockType: "exnref" });
|
|
385
|
+
this.pushControl("deferCatch");
|
|
386
|
+
this.pushInstruction({
|
|
387
|
+
op: "try_table",
|
|
388
|
+
blockType: "void",
|
|
389
|
+
catches: [{ kind: "catch_all_ref", label: 0 }],
|
|
390
|
+
});
|
|
391
|
+
this.pushControl("deferTry");
|
|
392
|
+
|
|
393
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
394
|
+
this.pushInstruction({ op: "global.set", index: armedGlobal });
|
|
395
|
+
this.pushInstruction({ op: "local.get", index: this.curClosureLocalIndex });
|
|
396
|
+
this.pushInstruction({
|
|
397
|
+
op: "struct.get",
|
|
398
|
+
typeIndex: deferClosureInfo.typeIndex,
|
|
399
|
+
fieldIndex: 1,
|
|
400
|
+
});
|
|
401
|
+
this.pushInstruction({ op: "local.get", index: this.curClosureLocalIndex });
|
|
402
|
+
this.pushInstruction({
|
|
403
|
+
op: "struct.get",
|
|
404
|
+
typeIndex: deferClosureInfo.typeIndex,
|
|
405
|
+
fieldIndex: 0,
|
|
406
|
+
});
|
|
407
|
+
this.pushInstruction({
|
|
408
|
+
op: "call_ref",
|
|
409
|
+
typeIndex: deferClosureInfo.funcTypeIndex,
|
|
410
|
+
});
|
|
411
|
+
// Clear in case the deferred function had no prologue (e.g. a
|
|
412
|
+
// trampoline) and never consumed the flag.
|
|
413
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
414
|
+
this.pushInstruction({ op: "global.set", index: armedGlobal });
|
|
415
|
+
this.pushInstruction("end"); // deferTry
|
|
416
|
+
// Normal completion: next defer.
|
|
417
|
+
this.pushInstruction({
|
|
418
|
+
op: "br",
|
|
419
|
+
depth: this.resolveBranchDepthToRole("deferLoop"),
|
|
420
|
+
});
|
|
421
|
+
this.pushInstruction("end"); // deferCatch — stack: exnref
|
|
422
|
+
|
|
423
|
+
// Panic inside the deferred function.
|
|
424
|
+
this.pushInstruction({ op: "local.set", index: this.panicExnLocalIndex });
|
|
425
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
426
|
+
this.pushInstruction({ op: "global.set", index: armedGlobal });
|
|
427
|
+
// If this frame was already panicking, the older panic is aborted:
|
|
428
|
+
// unlink it from under the new top node (__panic.prev = __panic.prev.prev).
|
|
429
|
+
this.pushInstruction({ op: "local.get", index: this.hasPanicLocalIndex });
|
|
430
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
431
|
+
this.pushInstruction({ op: "ref.is_null" });
|
|
432
|
+
this.pushInstruction({ op: "i32.eqz" });
|
|
433
|
+
this.pushInstruction({ op: "i32.and" });
|
|
434
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
435
|
+
this.pushControl("ifNestedPanic");
|
|
436
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
437
|
+
this.pushInstruction({
|
|
438
|
+
op: "struct.get",
|
|
439
|
+
typeIndex: panicNodeTypeIndex,
|
|
440
|
+
fieldIndex: 2, // prev
|
|
441
|
+
});
|
|
442
|
+
this.pushInstruction({ op: "ref.is_null" });
|
|
443
|
+
this.pushInstruction({ op: "i32.eqz" });
|
|
444
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
445
|
+
this.pushControl("ifHasPrev");
|
|
446
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
447
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
448
|
+
this.pushInstruction({
|
|
449
|
+
op: "struct.get",
|
|
450
|
+
typeIndex: panicNodeTypeIndex,
|
|
451
|
+
fieldIndex: 2,
|
|
452
|
+
});
|
|
453
|
+
this.pushInstruction({
|
|
454
|
+
op: "struct.get",
|
|
455
|
+
typeIndex: panicNodeTypeIndex,
|
|
456
|
+
fieldIndex: 2,
|
|
457
|
+
});
|
|
458
|
+
this.pushInstruction({
|
|
459
|
+
op: "struct.set",
|
|
460
|
+
typeIndex: panicNodeTypeIndex,
|
|
461
|
+
fieldIndex: 2,
|
|
462
|
+
});
|
|
463
|
+
this.pushInstruction("end"); // ifHasPrev
|
|
464
|
+
this.pushInstruction("end"); // ifNestedPanic
|
|
465
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
466
|
+
this.pushInstruction({ op: "local.set", index: this.hasPanicLocalIndex });
|
|
467
|
+
|
|
468
|
+
// Continue loop (br 1 = deferLoop)
|
|
469
|
+
this.pushInstruction({ op: "br", depth: 1 });
|
|
470
|
+
|
|
471
|
+
this.pushInstruction("end");
|
|
472
|
+
this.pushInstruction("end");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
emitCheckUnrecoveredPanic() {
|
|
476
|
+
const panicNodeTypeIndex = this.mod.getPanicNodeTypeIndex();
|
|
477
|
+
const panicGlobal = this.mod.getPanicGlobalIndex();
|
|
478
|
+
|
|
479
|
+
// if (hasPanic)
|
|
480
|
+
this.pushInstruction({ op: "local.get", index: this.hasPanicLocalIndex });
|
|
481
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
482
|
+
this.pushControl("ifHasPanic");
|
|
483
|
+
|
|
484
|
+
// if (__panic != null)
|
|
485
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
486
|
+
this.pushInstruction({ op: "ref.is_null" });
|
|
487
|
+
this.pushInstruction({ op: "i32.eqz" });
|
|
488
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
489
|
+
this.pushControl("ifHasNode");
|
|
490
|
+
|
|
491
|
+
// if (__panic.recovered == 1)
|
|
492
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
493
|
+
this.pushInstruction({
|
|
494
|
+
op: "struct.get",
|
|
495
|
+
typeIndex: panicNodeTypeIndex,
|
|
496
|
+
fieldIndex: 1, // recovered
|
|
497
|
+
});
|
|
498
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
499
|
+
this.pushInstruction({ op: "i32.eq" });
|
|
500
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
501
|
+
this.pushControl("ifRecovered");
|
|
502
|
+
|
|
503
|
+
// Pop recovered node: __panic = __panic.prev
|
|
504
|
+
this.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
505
|
+
this.pushInstruction({
|
|
506
|
+
op: "struct.get",
|
|
507
|
+
typeIndex: panicNodeTypeIndex,
|
|
508
|
+
fieldIndex: 2, // prev
|
|
509
|
+
});
|
|
510
|
+
this.pushInstruction({ op: "global.set", index: panicGlobal });
|
|
511
|
+
|
|
512
|
+
this.pushInstruction("else");
|
|
513
|
+
|
|
514
|
+
// Still unrecovered! Rethrow!
|
|
515
|
+
this.pushInstruction({ op: "local.get", index: this.panicExnLocalIndex });
|
|
516
|
+
this.pushInstruction({ op: "throw_ref" });
|
|
517
|
+
|
|
518
|
+
this.pushInstruction("end"); // ifRecovered
|
|
519
|
+
|
|
520
|
+
this.pushInstruction("else");
|
|
521
|
+
|
|
522
|
+
// __panic is null but hasPanic was 1 (e.g. unhandled host exception)
|
|
523
|
+
this.pushInstruction({ op: "local.get", index: this.panicExnLocalIndex });
|
|
524
|
+
this.pushInstruction({ op: "throw_ref" });
|
|
525
|
+
|
|
526
|
+
this.pushInstruction("end"); // ifHasNode
|
|
527
|
+
|
|
528
|
+
this.pushInstruction("end"); // ifHasPanic
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
emitDeferStmt(stmt) {
|
|
532
|
+
const funcLit = stmt.call.func;
|
|
533
|
+
this.emitFuncLit(funcLit);
|
|
534
|
+
// Stack has closure struct: (ref $deferClosure)
|
|
535
|
+
this.pushInstruction({ op: "local.get", index: this.defersLocalIndex });
|
|
536
|
+
this.pushInstruction({
|
|
537
|
+
op: "struct.new",
|
|
538
|
+
typeIndex: this.mod.getDeferNodeTypeIndex(),
|
|
539
|
+
});
|
|
540
|
+
this.pushInstruction({ op: "local.set", index: this.defersLocalIndex });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
_cacheScratchGlobals() {
|
|
544
|
+
if (this.funcDecl._isTrampoline) return;
|
|
545
|
+
const byFunc = this.mod.lowerResult?.cachedGlobalsByFunc;
|
|
546
|
+
const names =
|
|
547
|
+
byFunc?.get(this.rootFuncDecl) ??
|
|
548
|
+
byFunc?.get(this.rootFuncDecl._sourceDecl) ??
|
|
549
|
+
[];
|
|
550
|
+
// The list is per root function and covers nested closures; each body
|
|
551
|
+
// only pays for the globals it reads itself.
|
|
552
|
+
for (const name of names) {
|
|
553
|
+
if (!this._bodyReadsIdent(this.funcDecl.body, name)) continue;
|
|
554
|
+
const globalInfo = this.mod.resolveGlobal(name);
|
|
555
|
+
if (!globalInfo) continue;
|
|
556
|
+
|
|
557
|
+
const localIdx = this.allocLocal(
|
|
558
|
+
`__cache$${name}`,
|
|
559
|
+
globalInfo.type,
|
|
560
|
+
globalInfo.goType,
|
|
561
|
+
true,
|
|
562
|
+
);
|
|
563
|
+
this.cachedGlobals.set(name, { index: localIdx, globalInfo });
|
|
564
|
+
this.pushInstruction({ op: "global.get", index: globalInfo.index });
|
|
565
|
+
this.pushInstruction({ op: "local.set", index: localIdx });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Like _findIdentInAST but stops at FuncLit boundaries (closures are emitted
|
|
570
|
+
// as separate functions with their own entry).
|
|
571
|
+
_bodyReadsIdent(node, name) {
|
|
572
|
+
if (!node || typeof node !== "object") return false;
|
|
573
|
+
if (Array.isArray(node)) {
|
|
574
|
+
return node.some((item) => this._bodyReadsIdent(item, name));
|
|
575
|
+
}
|
|
576
|
+
if (node.kind === "FuncLit") return false;
|
|
577
|
+
if (node.kind === "Ident") return node.name === name;
|
|
578
|
+
for (const key of Object.keys(node)) {
|
|
579
|
+
if (key.startsWith("_")) continue;
|
|
580
|
+
if (this._bodyReadsIdent(node[key], name)) return true;
|
|
581
|
+
}
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
_needsOobBlock(node) {
|
|
586
|
+
if (!node || typeof node !== "object") return false;
|
|
587
|
+
if (Array.isArray(node)) {
|
|
588
|
+
return node.some((item) => this._needsOobBlock(item));
|
|
589
|
+
}
|
|
590
|
+
if (node.kind === "FuncLit") return false;
|
|
591
|
+
if (node.kind === "IndexExpr") {
|
|
592
|
+
const baseGoType = node.expr?._type ?? this._resolveExprGoType(node.expr);
|
|
593
|
+
if (
|
|
594
|
+
baseGoType &&
|
|
595
|
+
(baseGoType.kind === "map" || baseGoType.name === "map")
|
|
596
|
+
) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
for (const key of Object.keys(node)) {
|
|
602
|
+
if (key.startsWith("_")) continue;
|
|
603
|
+
if (this._needsOobBlock(node[key])) return true;
|
|
604
|
+
}
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
_findCapturedVarGoType(name) {
|
|
609
|
+
for (const p of this.rootFuncDecl.params ?? []) {
|
|
610
|
+
if (p.name === name) return p.type;
|
|
611
|
+
}
|
|
612
|
+
if (this.rootFuncDecl.recvName === name) {
|
|
613
|
+
return this.rootFuncDecl.recvType;
|
|
614
|
+
}
|
|
615
|
+
for (const r of this.rootFuncDecl.returnType?._namedReturns ?? []) {
|
|
616
|
+
if (r.name === name) return r.type;
|
|
617
|
+
}
|
|
618
|
+
const found = this._findGoTypeInAST(this.rootFuncDecl.body, name);
|
|
619
|
+
if (found) return found;
|
|
620
|
+
const global = this.mod.resolveGlobal(name);
|
|
621
|
+
if (global?.goType) return global.goType;
|
|
622
|
+
return { kind: "basic", name: "int" };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
_findGoTypeInAST(node, varName) {
|
|
626
|
+
if (!node || typeof node !== "object") return null;
|
|
627
|
+
if (Array.isArray(node)) {
|
|
628
|
+
for (const item of node) {
|
|
629
|
+
const t = this._findGoTypeInAST(item, varName);
|
|
630
|
+
if (t) return t;
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
if (node.kind === "VarDecl" || node.kind === "ConstDecl") {
|
|
635
|
+
for (const spec of node.decls ?? node.specs ?? [node]) {
|
|
636
|
+
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
637
|
+
const idx = names.indexOf(varName);
|
|
638
|
+
if (idx !== -1) {
|
|
639
|
+
if (spec.type) return spec.type;
|
|
640
|
+
if (spec.value?.[idx]?._type) return spec.value[idx]._type;
|
|
641
|
+
if (spec.init?.[idx]?._type) return spec.init[idx]._type;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (node.kind === "DefineStmt") {
|
|
646
|
+
const lhs = node.lhs ?? [];
|
|
647
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
648
|
+
if (lhs[i].name === varName) {
|
|
649
|
+
if (lhs[i]._type) return lhs[i]._type;
|
|
650
|
+
if (node.rhs?.[i]?._type) return node.rhs[i]._type;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (node.kind === "Ident" && node.name === varName && node._type) {
|
|
655
|
+
return node._type;
|
|
656
|
+
}
|
|
657
|
+
for (const key of Object.keys(node)) {
|
|
658
|
+
if (key.startsWith("_")) continue;
|
|
659
|
+
const t = this._findGoTypeInAST(node[key], varName);
|
|
660
|
+
if (t) return t;
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
_unpackClosureEnv() {
|
|
666
|
+
const envFields = [];
|
|
667
|
+
for (const name of this.capturedNames) {
|
|
668
|
+
const isBoxed = this.mutatedCaptures.has(name);
|
|
669
|
+
const goType = this._findCapturedVarGoType(name);
|
|
670
|
+
if (isBoxed) {
|
|
671
|
+
const boxInfo = this.mod.getBoxType(goType);
|
|
672
|
+
envFields.push({
|
|
673
|
+
name,
|
|
674
|
+
goType,
|
|
675
|
+
isBoxed: true,
|
|
676
|
+
boxInfo,
|
|
677
|
+
wType: {
|
|
678
|
+
kind: "ref",
|
|
679
|
+
nullable: true,
|
|
680
|
+
typeIndex: boxInfo.typeIndex,
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
} else {
|
|
684
|
+
const wType = this.toWasmType(goType);
|
|
685
|
+
envFields.push({
|
|
686
|
+
name,
|
|
687
|
+
goType,
|
|
688
|
+
isBoxed: false,
|
|
689
|
+
boxInfo: null,
|
|
690
|
+
wType,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const envInfo = this.mod.getEnvType(envFields);
|
|
696
|
+
this.envInfo = envInfo;
|
|
697
|
+
this.envFields = envFields;
|
|
698
|
+
|
|
699
|
+
const envCastTmp = this.allocLocal(
|
|
700
|
+
null,
|
|
701
|
+
{
|
|
702
|
+
kind: "ref",
|
|
703
|
+
nullable: true,
|
|
704
|
+
typeIndex: envInfo.typeIndex,
|
|
705
|
+
},
|
|
706
|
+
null,
|
|
707
|
+
true,
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
this.pushInstruction({ op: "local.get", index: 0 }); // __env
|
|
711
|
+
this.pushInstruction({
|
|
712
|
+
op: "ref.cast_null",
|
|
713
|
+
typeIndex: envInfo.typeIndex,
|
|
714
|
+
});
|
|
715
|
+
this.pushInstruction({ op: "local.set", index: envCastTmp });
|
|
716
|
+
|
|
717
|
+
for (let i = 0; i < envFields.length; i++) {
|
|
718
|
+
const f = envFields[i];
|
|
719
|
+
const locIdx = this.allocLocal(f.name, f.wType, f.goType, true);
|
|
720
|
+
this.pushInstruction({ op: "local.get", index: envCastTmp });
|
|
721
|
+
this.pushInstruction({
|
|
722
|
+
op: "struct.get",
|
|
723
|
+
typeIndex: envInfo.typeIndex,
|
|
724
|
+
fieldIndex: i,
|
|
725
|
+
});
|
|
726
|
+
this.pushInstruction({ op: "local.set", index: locIdx });
|
|
727
|
+
|
|
728
|
+
this.locals.set(f.name, {
|
|
729
|
+
index: locIdx,
|
|
730
|
+
type: f.wType,
|
|
731
|
+
goType: f.goType,
|
|
732
|
+
isParam: false,
|
|
733
|
+
isBoxed: f.isBoxed,
|
|
734
|
+
boxInfo: f.boxInfo,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
_boxMutatedParams() {
|
|
740
|
+
for (const param of this.funcDecl.params ?? []) {
|
|
741
|
+
if (
|
|
742
|
+
param.name &&
|
|
743
|
+
param.name !== "_" &&
|
|
744
|
+
param.name !== "__env" &&
|
|
745
|
+
this.mutatedCaptures.has(param.name)
|
|
746
|
+
) {
|
|
747
|
+
const pInfo = this.locals.get(param.name);
|
|
748
|
+
if (!pInfo || pInfo.isBoxed) continue;
|
|
749
|
+
const boxInfo = this.mod.getBoxType(pInfo.goType);
|
|
750
|
+
const boxWType = {
|
|
751
|
+
kind: "ref",
|
|
752
|
+
nullable: true,
|
|
753
|
+
typeIndex: boxInfo.typeIndex,
|
|
754
|
+
};
|
|
755
|
+
const boxLocalIdx = this.allocLocal(null, boxWType, pInfo.goType, true);
|
|
756
|
+
|
|
757
|
+
this.pushInstruction({ op: "local.get", index: pInfo.index });
|
|
758
|
+
this.pushInstruction({
|
|
759
|
+
op: "struct.new",
|
|
760
|
+
typeIndex: boxInfo.typeIndex,
|
|
761
|
+
});
|
|
762
|
+
this.pushInstruction({ op: "local.set", index: boxLocalIdx });
|
|
763
|
+
|
|
764
|
+
this.locals.set(param.name, {
|
|
765
|
+
index: boxLocalIdx,
|
|
766
|
+
type: boxWType,
|
|
767
|
+
goType: pInfo.goType,
|
|
768
|
+
isParam: false,
|
|
769
|
+
isBoxed: true,
|
|
770
|
+
boxInfo,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
toWasmType(t) {
|
|
777
|
+
return toWasmType(t, this.mod.checker, this.mod);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
_initParams() {
|
|
781
|
+
for (const param of this.funcDecl.params ?? []) {
|
|
782
|
+
const wType = this.toWasmType(param.type);
|
|
783
|
+
const idx = this.locals.size;
|
|
784
|
+
this.locals.set(param.name, {
|
|
785
|
+
index: idx,
|
|
786
|
+
type: wType,
|
|
787
|
+
goType: param.type,
|
|
788
|
+
isParam: true,
|
|
789
|
+
isBoxed: false,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
allocLocal(name, wasmType, goType = null, forceRaw = false) {
|
|
795
|
+
const isMutatedCapture =
|
|
796
|
+
!forceRaw &&
|
|
797
|
+
Boolean(name && name !== "_" && this.mutatedCaptures.has(name));
|
|
798
|
+
let finalWasmType = wasmType;
|
|
799
|
+
let boxInfo = null;
|
|
800
|
+
|
|
801
|
+
if (isMutatedCapture) {
|
|
802
|
+
boxInfo = this.mod.getBoxType(goType ?? { kind: "basic", name: "int" });
|
|
803
|
+
finalWasmType = {
|
|
804
|
+
kind: "ref",
|
|
805
|
+
nullable: true,
|
|
806
|
+
typeIndex: boxInfo.typeIndex,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const idx = (this.funcDecl.params?.length ?? 0) + this.localTypes.length;
|
|
811
|
+
this.localTypes.push(finalWasmType);
|
|
812
|
+
if (name && name !== "_") {
|
|
813
|
+
this.locals.set(name, {
|
|
814
|
+
index: idx,
|
|
815
|
+
type: finalWasmType,
|
|
816
|
+
goType,
|
|
817
|
+
isParam: false,
|
|
818
|
+
isBoxed: isMutatedCapture,
|
|
819
|
+
boxInfo,
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
return idx;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// `pkg.Name` where `pkg` is a linked local package -> `Name` (the module is
|
|
826
|
+
// flat, like the JS bundle). Returns a replacement Ident or null.
|
|
827
|
+
_dequalify(expr) {
|
|
828
|
+
if (
|
|
829
|
+
expr?.kind === "SelectorExpr" &&
|
|
830
|
+
expr.expr?.kind === "Ident" &&
|
|
831
|
+
this.mod.bundledPackages?.has(expr.expr.name) &&
|
|
832
|
+
!this.resolveLocal(expr.expr.name) &&
|
|
833
|
+
!this.mod.resolveGlobal(expr.expr.name)
|
|
834
|
+
) {
|
|
835
|
+
return {
|
|
836
|
+
kind: "Ident",
|
|
837
|
+
name: expr.field,
|
|
838
|
+
_type: expr._type,
|
|
839
|
+
_line: expr._line,
|
|
840
|
+
_col: expr._col,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
_resolveStructInfo(expr) {
|
|
847
|
+
if (!expr) return null;
|
|
848
|
+
let t = expr._type;
|
|
849
|
+
if (!t && expr.kind === "Ident") {
|
|
850
|
+
const local = this.resolveLocal(expr.name);
|
|
851
|
+
if (local) t = local.goType;
|
|
852
|
+
if (!t && this.cachedGlobals.has(expr.name)) {
|
|
853
|
+
t = this.cachedGlobals.get(expr.name).globalInfo.goType;
|
|
854
|
+
}
|
|
855
|
+
if (!t) {
|
|
856
|
+
const global = this.mod.resolveGlobal(expr.name);
|
|
857
|
+
if (global) t = global.goType;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (!t) return null;
|
|
861
|
+
while (
|
|
862
|
+
t.kind === "pointer" ||
|
|
863
|
+
t.kind === "PointerType" ||
|
|
864
|
+
t.kind === "StarExpr"
|
|
865
|
+
) {
|
|
866
|
+
t = t.base ?? t.expr ?? t.operand;
|
|
867
|
+
}
|
|
868
|
+
const name = t.name ?? (t.kind === "named" ? t.name : null);
|
|
869
|
+
if (name && this.mod.structTypes.has(name)) {
|
|
870
|
+
return this.mod.structTypes.get(name);
|
|
871
|
+
}
|
|
872
|
+
if (t.kind === "TypeName" || t.kind === "Ident") {
|
|
873
|
+
if (this.mod.structTypes.has(t.name))
|
|
874
|
+
return this.mod.structTypes.get(t.name);
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
_resolveFieldPath(structInfo, fieldName) {
|
|
880
|
+
if (!structInfo) return null;
|
|
881
|
+
if (structInfo.fieldIndexMap.has(fieldName)) {
|
|
882
|
+
const idx = structInfo.fieldIndexMap.get(fieldName);
|
|
883
|
+
return [{ structInfo, fieldIndex: idx, field: structInfo.fields[idx] }];
|
|
884
|
+
}
|
|
885
|
+
for (const embed of structInfo.embeds) {
|
|
886
|
+
const embedInfo = this.mod.structTypes.get(embed.name);
|
|
887
|
+
if (embedInfo) {
|
|
888
|
+
const sub = this._resolveFieldPath(embedInfo, fieldName);
|
|
889
|
+
if (sub) {
|
|
890
|
+
return [
|
|
891
|
+
{
|
|
892
|
+
structInfo,
|
|
893
|
+
fieldIndex: embed.fieldIndex,
|
|
894
|
+
field: structInfo.fields[embed.fieldIndex],
|
|
895
|
+
},
|
|
896
|
+
...sub,
|
|
897
|
+
];
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
emitCloneStruct(structInfo, baseWType) {
|
|
905
|
+
const tmp = this.acquireTemp(baseWType);
|
|
906
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
907
|
+
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
908
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
909
|
+
this.pushInstruction({
|
|
910
|
+
op: "struct.get",
|
|
911
|
+
typeIndex: structInfo.typeIndex,
|
|
912
|
+
fieldIndex: i,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
this.pushInstruction({
|
|
916
|
+
op: "struct.new",
|
|
917
|
+
typeIndex: structInfo.typeIndex,
|
|
918
|
+
});
|
|
919
|
+
this.releaseTemp(tmp, baseWType);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Go copies structs on assignment. After `node` has been emitted (value on
|
|
923
|
+
// the stack), clone it unless it is already a fresh temporary
|
|
924
|
+
// (composite literal or `*p` deref) or not a value struct at all.
|
|
925
|
+
_emitCopyIfValueStruct(node, goType, wType) {
|
|
926
|
+
if (
|
|
927
|
+
!isStructType(goType, this.mod.checker, this.mod) ||
|
|
928
|
+
isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
929
|
+
)
|
|
930
|
+
return;
|
|
931
|
+
if (
|
|
932
|
+
node.kind === "CompositeLit" ||
|
|
933
|
+
(node.kind === "UnaryExpr" && node.op === "*")
|
|
934
|
+
)
|
|
935
|
+
return;
|
|
936
|
+
const sInfo =
|
|
937
|
+
this._resolveStructInfo(node) ?? this.mod.getStructType(goType?.name);
|
|
938
|
+
if (sInfo) this.emitCloneStruct(sInfo, wType);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Unpacks slice header fields (0 arr, 1 off, 2 len, 3 cap) from the slice
|
|
942
|
+
// held in `sliceTmp` into the given temps; fields without a temp are skipped.
|
|
943
|
+
_emitSliceUnpack(sliceTmp, sliceInfo, { arr, off, len, cap }) {
|
|
944
|
+
const parts = [arr, off, len, cap];
|
|
945
|
+
for (let fieldIndex = 0; fieldIndex < parts.length; fieldIndex++) {
|
|
946
|
+
const tmp = parts[fieldIndex];
|
|
947
|
+
if (tmp === undefined || tmp === null) continue;
|
|
948
|
+
this.pushInstruction({ op: "local.get", index: sliceTmp });
|
|
949
|
+
this.pushInstruction({
|
|
950
|
+
op: "struct.get",
|
|
951
|
+
typeIndex: sliceInfo.typeIndex,
|
|
952
|
+
fieldIndex,
|
|
953
|
+
});
|
|
954
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
_getReceiverTypeName(recvType, node = null) {
|
|
959
|
+
if (recvType) {
|
|
960
|
+
let t = recvType;
|
|
961
|
+
while (t.kind === "pointer") t = t.base;
|
|
962
|
+
if (t.name) return t.name;
|
|
963
|
+
if (t.kind === "named") return t.name;
|
|
964
|
+
if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
|
|
965
|
+
}
|
|
966
|
+
if (node && node.kind === "Ident") {
|
|
967
|
+
const loc = this.resolveLocal(node.name);
|
|
968
|
+
if (loc?.goType) {
|
|
969
|
+
return this._getReceiverTypeName(loc.goType);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
emitInterfaceDispatcher(fn) {
|
|
976
|
+
let candidates = this.mod.findInterfaceCandidates(
|
|
977
|
+
fn._ifaceType,
|
|
978
|
+
fn._methodName,
|
|
979
|
+
);
|
|
980
|
+
const recvLocal = 0; // __recv is local 0
|
|
981
|
+
// Soft dispatchers (errors.Unwrap) return nil instead of panicking when
|
|
982
|
+
// the receiver is nil or has no such method.
|
|
983
|
+
const soft = Boolean(fn._softDispatch);
|
|
984
|
+
if (soft) {
|
|
985
|
+
// Only methods with the dispatcher's own result shape are callable
|
|
986
|
+
// (e.g. `Unwrap() []error` is not an `Unwrap() error`).
|
|
987
|
+
const want = this.mod.funcReturnTypesByIdx.get(fn._globalFuncIndex);
|
|
988
|
+
const key = (ts) =>
|
|
989
|
+
ts
|
|
990
|
+
.map((t) => (typeof t === "object" ? JSON.stringify(t) : t))
|
|
991
|
+
.join(",");
|
|
992
|
+
candidates = candidates.filter((c) => {
|
|
993
|
+
const got = this.mod.funcReturnTypesByIdx.get(c.funcIndex);
|
|
994
|
+
return (
|
|
995
|
+
got !== undefined && want !== undefined && key(got) === key(want)
|
|
996
|
+
);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const emitMiss = (msg) => {
|
|
1000
|
+
if (soft) {
|
|
1001
|
+
this.pushInstruction({ op: "ref.null", heapType: "any" });
|
|
1002
|
+
this.pushInstruction("return");
|
|
1003
|
+
} else {
|
|
1004
|
+
this.emitPanic(msg);
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// 1. Nil check
|
|
1009
|
+
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1010
|
+
this.pushInstruction("ref.is_null");
|
|
1011
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1012
|
+
emitMiss(
|
|
1013
|
+
"runtime error: invalid memory address or nil pointer dereference",
|
|
1014
|
+
);
|
|
1015
|
+
this.pushInstruction("end");
|
|
1016
|
+
|
|
1017
|
+
// 2. Iterate candidates
|
|
1018
|
+
for (const cand of candidates) {
|
|
1019
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1020
|
+
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1021
|
+
this.pushInstruction({ op: "ref.test", typeIndex: cand.testTypeIndex });
|
|
1022
|
+
this.pushInstruction("i32.eqz");
|
|
1023
|
+
this.pushInstruction({ op: "br_if", depth: 0 });
|
|
1024
|
+
|
|
1025
|
+
// Matched! Cast receiver
|
|
1026
|
+
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1027
|
+
this.pushInstruction({
|
|
1028
|
+
op: "ref.cast_null",
|
|
1029
|
+
typeIndex: cand.testTypeIndex,
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
if (cand.isBoxedValue) {
|
|
1033
|
+
this.pushInstruction({
|
|
1034
|
+
op: "struct.get",
|
|
1035
|
+
typeIndex: cand.testTypeIndex,
|
|
1036
|
+
fieldIndex: 0,
|
|
1037
|
+
});
|
|
1038
|
+
if (cand.embedPath) {
|
|
1039
|
+
for (const step of cand.embedPath) {
|
|
1040
|
+
this.pushInstruction({
|
|
1041
|
+
op: "struct.get",
|
|
1042
|
+
typeIndex: step.parentTypeIndex,
|
|
1043
|
+
fieldIndex: step.fieldIndex,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
} else {
|
|
1048
|
+
if (cand.embedPath) {
|
|
1049
|
+
for (const step of cand.embedPath) {
|
|
1050
|
+
this.pushInstruction({
|
|
1051
|
+
op: "struct.get",
|
|
1052
|
+
typeIndex: step.parentTypeIndex,
|
|
1053
|
+
fieldIndex: step.fieldIndex,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (cand.needsValueDeref && cand.structInfo) {
|
|
1058
|
+
this.emitCloneStruct(cand.structInfo, cand.targetRecvWType);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Push method arguments: __arg0 (local 1), __arg1 (local 2), ...
|
|
1063
|
+
for (let p = 1; p < fn.params.length; p++) {
|
|
1064
|
+
this.pushInstruction({ op: "local.get", index: p });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
|
|
1068
|
+
this.pushInstruction("return");
|
|
1069
|
+
this.pushInstruction("end");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
emitMiss("interface conversion: nil or unmatched type for method call");
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
_getArraySize(t, defaultSize = 0) {
|
|
1076
|
+
if (!t) return defaultSize;
|
|
1077
|
+
if (typeof t.size === "number") return t.size;
|
|
1078
|
+
if (t.size?.value !== undefined) return Number(t.size.value);
|
|
1079
|
+
if (typeof t.len === "number") return t.len;
|
|
1080
|
+
if (t.len?.value !== undefined) return Number(t.len.value);
|
|
1081
|
+
if (t.underlying) return this._getArraySize(t.underlying, defaultSize);
|
|
1082
|
+
return defaultSize;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
emitZeroValue(goType, wType) {
|
|
1086
|
+
if (wType === "i32") {
|
|
1087
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1088
|
+
} else if (wType === "i64") {
|
|
1089
|
+
this.pushInstruction({ op: "i64.const", value: 0n });
|
|
1090
|
+
} else if (wType === "f32") {
|
|
1091
|
+
this.pushInstruction({ op: "f32.const", value: 0.0 });
|
|
1092
|
+
} else if (wType === "f64") {
|
|
1093
|
+
this.pushInstruction({ op: "f64.const", value: 0.0 });
|
|
1094
|
+
} else if (
|
|
1095
|
+
typeof wType === "object" &&
|
|
1096
|
+
wType !== null &&
|
|
1097
|
+
wType.kind === "ref"
|
|
1098
|
+
) {
|
|
1099
|
+
if (
|
|
1100
|
+
goType &&
|
|
1101
|
+
goType.kind !== "pointer" &&
|
|
1102
|
+
isStructType(goType, this.mod.checker)
|
|
1103
|
+
) {
|
|
1104
|
+
const sName =
|
|
1105
|
+
goType.name ?? (goType.kind === "named" ? goType.name : null);
|
|
1106
|
+
const structInfo = this.mod.getStructType(sName);
|
|
1107
|
+
if (structInfo) {
|
|
1108
|
+
for (const f of structInfo.fields) {
|
|
1109
|
+
this.emitZeroValue(f.goType, f.wType);
|
|
1110
|
+
}
|
|
1111
|
+
this.pushInstruction({
|
|
1112
|
+
op: "struct.new",
|
|
1113
|
+
typeIndex: structInfo.typeIndex,
|
|
1114
|
+
});
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (
|
|
1119
|
+
goType &&
|
|
1120
|
+
goType.kind !== "pointer" &&
|
|
1121
|
+
isArrayType(goType, this.mod.checker)
|
|
1122
|
+
) {
|
|
1123
|
+
const elemType = goType.elem;
|
|
1124
|
+
const arrInfo = this.mod.getArrayType(elemType);
|
|
1125
|
+
const size = this._getArraySize(goType);
|
|
1126
|
+
if (isStructType(elemType, this.mod.checker, this.mod)) {
|
|
1127
|
+
for (let i = 0; i < size; i++) {
|
|
1128
|
+
this.emitZeroValue(elemType, arrInfo.elemWType);
|
|
1129
|
+
}
|
|
1130
|
+
this.pushInstruction({
|
|
1131
|
+
op: "array.new_fixed",
|
|
1132
|
+
typeIndex: arrInfo.typeIndex,
|
|
1133
|
+
size,
|
|
1134
|
+
});
|
|
1135
|
+
} else {
|
|
1136
|
+
this.pushInstruction({ op: "i32.const", value: size });
|
|
1137
|
+
this.pushInstruction({
|
|
1138
|
+
op: "array.new_default",
|
|
1139
|
+
typeIndex: arrInfo.typeIndex,
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (
|
|
1145
|
+
(goType &&
|
|
1146
|
+
goType.kind !== "pointer" &&
|
|
1147
|
+
isSliceType(goType, this.mod.checker)) ||
|
|
1148
|
+
(typeof wType.typeIndex === "number" &&
|
|
1149
|
+
this.mod.getSliceTypeByIndex(wType.typeIndex))
|
|
1150
|
+
) {
|
|
1151
|
+
const elemType = goType ? this._getSliceElemType(goType) : null;
|
|
1152
|
+
const sliceInfo = elemType
|
|
1153
|
+
? this.mod.getSliceType(elemType)
|
|
1154
|
+
: this.mod.getSliceTypeByIndex(wType.typeIndex);
|
|
1155
|
+
if (sliceInfo) {
|
|
1156
|
+
this.pushInstruction({
|
|
1157
|
+
op: "global.get",
|
|
1158
|
+
index: sliceInfo.emptyGlobalIndex,
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
this.pushInstruction({
|
|
1164
|
+
op: "ref.null",
|
|
1165
|
+
heapType: wType.typeIndex ?? "any",
|
|
1166
|
+
});
|
|
1167
|
+
} else if (
|
|
1168
|
+
wType === "externref" ||
|
|
1169
|
+
isStringType(goType, this.mod?.checker)
|
|
1170
|
+
) {
|
|
1171
|
+
const strIdx = this.mod.internString("");
|
|
1172
|
+
const funcIdx = this.mod.getStringImportIndex();
|
|
1173
|
+
this.pushInstruction({ op: "i32.const", value: strIdx });
|
|
1174
|
+
this.pushInstruction({ op: "call", funcIndex: funcIdx });
|
|
1175
|
+
} else if (wType === "anyref") {
|
|
1176
|
+
this.pushInstruction({ op: "ref.null", heapType: "any" });
|
|
1177
|
+
} else {
|
|
1178
|
+
this.pushInstruction({ op: "ref.null", heapType: "any" });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
allocTemp(wasmType) {
|
|
1183
|
+
return this.acquireTemp(wasmType);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Temps are keyed by a canonical string so ref-typed temps (fresh object
|
|
1187
|
+
// literals at every call site) are actually recycled across sites.
|
|
1188
|
+
_tempKey(wasmType) {
|
|
1189
|
+
if (typeof wasmType === "string") return wasmType;
|
|
1190
|
+
const nn = wasmType.nullable === false ? "!" : "";
|
|
1191
|
+
if (typeof wasmType.typeIndex === "number")
|
|
1192
|
+
return `r${wasmType.typeIndex}${nn}`;
|
|
1193
|
+
return `h${wasmType.heapType ?? "any"}${nn}`;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
acquireTemp(wasmType) {
|
|
1197
|
+
if (!this.freeTemps) this.freeTemps = new Map();
|
|
1198
|
+
const list = this.freeTemps.get(this._tempKey(wasmType));
|
|
1199
|
+
if (list && list.length > 0) {
|
|
1200
|
+
return list.pop();
|
|
1201
|
+
}
|
|
1202
|
+
return this.allocLocal(null, wasmType);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
releaseTemp(idx, wasmType) {
|
|
1206
|
+
if (!this.freeTemps) this.freeTemps = new Map();
|
|
1207
|
+
const key = this._tempKey(wasmType);
|
|
1208
|
+
let list = this.freeTemps.get(key);
|
|
1209
|
+
if (!list) {
|
|
1210
|
+
list = [];
|
|
1211
|
+
this.freeTemps.set(key, list);
|
|
1212
|
+
}
|
|
1213
|
+
list.push(idx);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
resolveLocal(name) {
|
|
1217
|
+
return this.locals.get(name) ?? null;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// The control stack mirrors every block/loop/if so `br_if` depths to the
|
|
1221
|
+
// function-level out-of-bounds block stay correct inside nested structures
|
|
1222
|
+
// that never call pushControl (struct equality, string slicing, ...).
|
|
1223
|
+
pushInstruction(inst) {
|
|
1224
|
+
const op = typeof inst === "string" ? inst : inst.op;
|
|
1225
|
+
if (op === "block" || op === "loop" || op === "if" || op === "try_table") {
|
|
1226
|
+
this.controlStack.push({ op, role: null, label: null });
|
|
1227
|
+
} else if (op === "end") {
|
|
1228
|
+
this.controlStack.pop();
|
|
1229
|
+
}
|
|
1230
|
+
this.body.push(typeof inst === "string" ? { op: inst } : inst);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// ── Control Stack ──────────────────────────────────────────
|
|
1234
|
+
|
|
1235
|
+
// Tags the innermost open block as a break/continue/oob target.
|
|
1236
|
+
pushControl(role, label = null) {
|
|
1237
|
+
const top = this.controlStack[this.controlStack.length - 1];
|
|
1238
|
+
if (top) {
|
|
1239
|
+
top.role = role;
|
|
1240
|
+
top.label = label;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
resolveBranchDepth(targetLabel, isContinue = false) {
|
|
1245
|
+
const targetRole = isContinue ? "continue" : "break";
|
|
1246
|
+
if (!targetLabel) {
|
|
1247
|
+
// Innermost break or continue
|
|
1248
|
+
for (let i = this.controlStack.length - 1; i >= 0; i--) {
|
|
1249
|
+
const ctrl = this.controlStack[i];
|
|
1250
|
+
if (ctrl.role === targetRole) {
|
|
1251
|
+
return this.controlStack.length - 1 - i;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return 0;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
for (let i = this.controlStack.length - 1; i >= 0; i--) {
|
|
1258
|
+
const ctrl = this.controlStack[i];
|
|
1259
|
+
if (ctrl.label === targetLabel && ctrl.role === targetRole) {
|
|
1260
|
+
return this.controlStack.length - 1 - i;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return 0;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
resolveBranchDepthToOob() {
|
|
1267
|
+
for (let i = this.controlStack.length - 1; i >= 0; i--) {
|
|
1268
|
+
const ctrl = this.controlStack[i];
|
|
1269
|
+
if (ctrl.role === "oob") {
|
|
1270
|
+
return this.controlStack.length - 1 - i;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
resolveBranchDepthToRole(targetRole) {
|
|
1277
|
+
for (let i = this.controlStack.length - 1; i >= 0; i--) {
|
|
1278
|
+
const ctrl = this.controlStack[i];
|
|
1279
|
+
if (ctrl.role === targetRole) {
|
|
1280
|
+
return this.controlStack.length - 1 - i;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ── Statements ─────────────────────────────────────────────
|
|
1287
|
+
|
|
1288
|
+
emitBlock(block) {
|
|
1289
|
+
for (const stmt of block.stmts ?? []) {
|
|
1290
|
+
this.emitStmt(stmt);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
emitStmt(stmt) {
|
|
1295
|
+
if (!stmt) return;
|
|
1296
|
+
switch (stmt.kind) {
|
|
1297
|
+
case "Block":
|
|
1298
|
+
this.emitBlock(stmt);
|
|
1299
|
+
break;
|
|
1300
|
+
|
|
1301
|
+
case "VarDecl":
|
|
1302
|
+
this.emitVarDecl(stmt);
|
|
1303
|
+
break;
|
|
1304
|
+
|
|
1305
|
+
case "DefineStmt":
|
|
1306
|
+
this.emitDefineStmt(stmt);
|
|
1307
|
+
break;
|
|
1308
|
+
|
|
1309
|
+
case "AssignStmt":
|
|
1310
|
+
this.emitAssignStmt(stmt);
|
|
1311
|
+
break;
|
|
1312
|
+
|
|
1313
|
+
case "IncDecStmt":
|
|
1314
|
+
this.emitIncDecStmt(stmt);
|
|
1315
|
+
break;
|
|
1316
|
+
|
|
1317
|
+
case "IfStmt":
|
|
1318
|
+
this.emitIfStmt(stmt);
|
|
1319
|
+
break;
|
|
1320
|
+
|
|
1321
|
+
case "ForStmt":
|
|
1322
|
+
this.emitForStmt(stmt);
|
|
1323
|
+
break;
|
|
1324
|
+
|
|
1325
|
+
case "ReturnStmt":
|
|
1326
|
+
this.emitReturnStmt(stmt);
|
|
1327
|
+
break;
|
|
1328
|
+
|
|
1329
|
+
case "BranchStmt":
|
|
1330
|
+
this.emitBranchStmt(stmt);
|
|
1331
|
+
break;
|
|
1332
|
+
|
|
1333
|
+
case "DeferStmt":
|
|
1334
|
+
this.emitDeferStmt(stmt);
|
|
1335
|
+
break;
|
|
1336
|
+
|
|
1337
|
+
case "ExprStmt":
|
|
1338
|
+
this.emitExprStmt(stmt);
|
|
1339
|
+
break;
|
|
1340
|
+
|
|
1341
|
+
case "LabeledStmt":
|
|
1342
|
+
if (stmt.body) {
|
|
1343
|
+
if (stmt.body.kind === "ForStmt") {
|
|
1344
|
+
stmt.body.label = stmt.label;
|
|
1345
|
+
}
|
|
1346
|
+
this.emitStmt(stmt.body);
|
|
1347
|
+
}
|
|
1348
|
+
break;
|
|
1349
|
+
|
|
1350
|
+
case "SwitchStmt":
|
|
1351
|
+
this.emitSwitchStmt(stmt);
|
|
1352
|
+
break;
|
|
1353
|
+
|
|
1354
|
+
case "TypeSwitchStmt":
|
|
1355
|
+
this.emitTypeSwitchStmt(stmt);
|
|
1356
|
+
break;
|
|
1357
|
+
|
|
1358
|
+
default:
|
|
1359
|
+
// ignore comments or unsupported stmts
|
|
1360
|
+
break;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function copyMethods(target, source) {
|
|
1366
|
+
const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
|
|
1367
|
+
delete descriptors.constructor;
|
|
1368
|
+
Object.defineProperties(target.prototype, descriptors);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
copyMethods(FunctionEmitter, MapsEmitter);
|
|
1372
|
+
copyMethods(FunctionEmitter, StmtsEmitter);
|
|
1373
|
+
copyMethods(FunctionEmitter, ExprsEmitter);
|
|
1374
|
+
copyMethods(FunctionEmitter, BuiltinsEmitter);
|
|
1375
|
+
copyMethods(FunctionEmitter, StdlibEmitter);
|
|
1376
|
+
copyMethods(FunctionEmitter, SharedEmitter);
|