gofront 1.6.1 → 1.8.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 +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -1,20 +1,59 @@
|
|
|
1
1
|
// src/backend/wasm/emit-stmts.js
|
|
2
2
|
// FunctionEmitter mixin: statement emission (decls, assignment, control flow, return).
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { loopVarsCapturedInLoop } from "../../lower/captures.js";
|
|
5
|
+
import { rootIdentName } from "../../lower/ownership.js";
|
|
6
|
+
import {
|
|
7
|
+
getOrCreateIteratorFuncLit,
|
|
8
|
+
isIntRangeType,
|
|
9
|
+
isIteratorRangeFor,
|
|
10
|
+
isRangeFor,
|
|
11
|
+
} from "../../lower/range.js";
|
|
12
|
+
import { someChild } from "../../lower/walk.js";
|
|
5
13
|
import {
|
|
6
14
|
getMapKeyValTypes,
|
|
7
15
|
isArrayType,
|
|
16
|
+
isComplex,
|
|
17
|
+
isComplex64,
|
|
8
18
|
isFuncType,
|
|
9
19
|
isInterfaceType,
|
|
10
20
|
isMapType,
|
|
11
21
|
isNonEmptyInterface,
|
|
12
22
|
isPointerToStruct,
|
|
23
|
+
isScalarType,
|
|
13
24
|
isSliceType,
|
|
14
25
|
isStringType,
|
|
15
26
|
isStructType,
|
|
16
27
|
} from "./types.js";
|
|
17
28
|
|
|
29
|
+
function hasReturnInBlock(n) {
|
|
30
|
+
let found = false;
|
|
31
|
+
const check = (x) => {
|
|
32
|
+
if (!x || typeof x !== "object" || found) return;
|
|
33
|
+
if (Array.isArray(x)) {
|
|
34
|
+
for (const elem of x) check(elem);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (x.kind === "ReturnStmt") {
|
|
38
|
+
found = true;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (x.kind === "FuncLit" && !x._isYieldClosure) return;
|
|
42
|
+
for (const k of Object.keys(x)) {
|
|
43
|
+
if (k.startsWith("_")) continue;
|
|
44
|
+
check(x[k]);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
check(n);
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A plain `string`: the one string type that lives in `any` as the bare
|
|
52
|
+
// string. A named string type is boxed like any other scalar (see emitExpr).
|
|
53
|
+
function isBareStringType(t, checker) {
|
|
54
|
+
return isStringType(t, checker) && t.kind !== "named";
|
|
55
|
+
}
|
|
56
|
+
|
|
18
57
|
export class StmtsEmitter {
|
|
19
58
|
emitVarDecl(stmt) {
|
|
20
59
|
const decls = stmt.decls ?? stmt.specs ?? [stmt];
|
|
@@ -31,7 +70,7 @@ export class StmtsEmitter {
|
|
|
31
70
|
if (values?.[i]) {
|
|
32
71
|
const r = values[i];
|
|
33
72
|
this.emitExpr(r, wType);
|
|
34
|
-
this.
|
|
73
|
+
this._emitCopyIfValue(r, rawType, wType);
|
|
35
74
|
if (localInfo?.isBoxed) {
|
|
36
75
|
this.pushInstruction({
|
|
37
76
|
op: "struct.new",
|
|
@@ -88,9 +127,11 @@ export class StmtsEmitter {
|
|
|
88
127
|
return;
|
|
89
128
|
}
|
|
90
129
|
|
|
130
|
+
const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes, true);
|
|
91
131
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
92
|
-
this._emitDefineSingle(lhsNodes[i],
|
|
132
|
+
this._emitDefineSingle(lhsNodes[i], rhsVals[i]);
|
|
93
133
|
}
|
|
134
|
+
this._releaseParallelTemps(rhsVals, rhsNodes);
|
|
94
135
|
}
|
|
95
136
|
|
|
96
137
|
_emitDefineSingle(l, r) {
|
|
@@ -100,11 +141,17 @@ export class StmtsEmitter {
|
|
|
100
141
|
this.pushInstruction("drop");
|
|
101
142
|
return;
|
|
102
143
|
}
|
|
144
|
+
if (l._redecl && this.resolveLocal(l.name)) {
|
|
145
|
+
this._emitAssignSingle(l, r, "=");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
103
148
|
const wType = this.toWasmType(r._type);
|
|
149
|
+
// The value is computed before the new name exists: `x := x * 2`
|
|
150
|
+
// in an inner block reads the outer `x`.
|
|
151
|
+
this.emitExpr(r, wType);
|
|
152
|
+
this._emitCopyIfValue(r, r._type, wType);
|
|
104
153
|
const idx = this.allocLocal(l.name, wType, r._type);
|
|
105
154
|
const localInfo = this.locals.get(l.name);
|
|
106
|
-
this.emitExpr(r, wType);
|
|
107
|
-
this._emitCopyIfValueStruct(r, r._type, wType);
|
|
108
155
|
if (localInfo?.isBoxed) {
|
|
109
156
|
this.pushInstruction({
|
|
110
157
|
op: "struct.new",
|
|
@@ -114,7 +161,38 @@ export class StmtsEmitter {
|
|
|
114
161
|
this.pushInstruction({ op: "local.set", index: idx });
|
|
115
162
|
}
|
|
116
163
|
|
|
164
|
+
// Stores a multi-value call into temporaries (last result first), then
|
|
165
|
+
// assigns each one to its target. Used when a target is not a plain new
|
|
166
|
+
// local: an index, field or dereference, or a name redeclared by `:=`.
|
|
167
|
+
_emitTupleViaTemps(lhsNodes, rhsNode, assignOne, hoist) {
|
|
168
|
+
const tupleTypes = rhsNode._type.types;
|
|
169
|
+
this.emitExpr(rhsNode);
|
|
170
|
+
const temps = [];
|
|
171
|
+
for (let i = lhsNodes.length - 1; i >= 0; i--) {
|
|
172
|
+
const goType = tupleTypes[i];
|
|
173
|
+
const wType = this.toWasmType(goType);
|
|
174
|
+
const name = `__tup_tmp_${this.tempCounter++}`;
|
|
175
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
176
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
177
|
+
temps[i] = { kind: "Ident", name, _type: goType, _parTmp: true };
|
|
178
|
+
}
|
|
179
|
+
const undo = hoist ? this._hoistLhsOperands(lhsNodes) : null;
|
|
180
|
+
for (let i = 0; i < lhsNodes.length; i++) assignOne(lhsNodes[i], temps[i]);
|
|
181
|
+
undo?.();
|
|
182
|
+
for (const t of temps) this.locals.delete(t.name);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_isPlainTupleTarget(l) {
|
|
186
|
+
return l.kind === "Ident";
|
|
187
|
+
}
|
|
188
|
+
|
|
117
189
|
_emitTupleDefine(lhsNodes, rhsNode) {
|
|
190
|
+
if (lhsNodes.some((l) => !this._isPlainTupleTarget(l) || l._redecl)) {
|
|
191
|
+
this._emitTupleViaTemps(lhsNodes, rhsNode, (l, t) =>
|
|
192
|
+
this._emitDefineSingle(l, t),
|
|
193
|
+
);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
118
196
|
const tupleTypes = rhsNode._type.types;
|
|
119
197
|
const localIndices = [];
|
|
120
198
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
@@ -200,11 +278,190 @@ export class StmtsEmitter {
|
|
|
200
278
|
return;
|
|
201
279
|
}
|
|
202
280
|
|
|
281
|
+
const hoisted = op === "=" ? this._hoistLhsOperands(lhsNodes) : null;
|
|
282
|
+
const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes);
|
|
203
283
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
204
|
-
this._emitAssignSingle(lhsNodes[i],
|
|
284
|
+
this._emitAssignSingle(lhsNodes[i], rhsVals[i], op);
|
|
285
|
+
}
|
|
286
|
+
this._releaseParallelTemps(rhsVals, rhsNodes);
|
|
287
|
+
hoisted?.();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
|
|
291
|
+
// target before any assignment. An operand that mentions a variable
|
|
292
|
+
// assigned by the same statement is evaluated into a temporary first.
|
|
293
|
+
// Returns a function that restores the AST and releases the temporaries.
|
|
294
|
+
_hoistLhsOperands(lhsNodes) {
|
|
295
|
+
if (lhsNodes.length < 2) return null;
|
|
296
|
+
const assigned = new Set(
|
|
297
|
+
lhsNodes
|
|
298
|
+
.filter((l) => l.kind === "Ident" && l.name !== "_")
|
|
299
|
+
.map((l) => l.name),
|
|
300
|
+
);
|
|
301
|
+
if (assigned.size === 0) return null;
|
|
302
|
+
const undo = [];
|
|
303
|
+
const names = [];
|
|
304
|
+
for (const l of lhsNodes) {
|
|
305
|
+
let keys;
|
|
306
|
+
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
307
|
+
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
308
|
+
else continue;
|
|
309
|
+
for (const key of keys) {
|
|
310
|
+
const operand = l[key];
|
|
311
|
+
if (!this._mentions(operand, assigned)) continue;
|
|
312
|
+
const goType = operand._type ?? this._resolveExprGoType(operand);
|
|
313
|
+
// A struct or array value names the variable's storage in Go.
|
|
314
|
+
if (
|
|
315
|
+
key === "expr" &&
|
|
316
|
+
(this._isValueStruct(goType) || this._isValueArray(goType))
|
|
317
|
+
)
|
|
318
|
+
continue;
|
|
319
|
+
const wType = this.toWasmType(goType);
|
|
320
|
+
const name = `__lhs_tmp_${this.tempCounter++}`;
|
|
321
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
322
|
+
this.emitExpr(operand, wType);
|
|
323
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
324
|
+
undo.push([l, key, operand]);
|
|
325
|
+
names.push(name);
|
|
326
|
+
l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (undo.length === 0) return null;
|
|
330
|
+
return () => {
|
|
331
|
+
for (const [node, key, v] of undo) node[key] = v;
|
|
332
|
+
for (const n of names) this.locals.delete(n);
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// `x, y = y, x`: Go evaluates every right-hand side before assigning.
|
|
337
|
+
// When a later value could see an earlier assignment, every value that
|
|
338
|
+
// is not pure is stored in a temporary first (in source order, so side
|
|
339
|
+
// effects keep their order). Pure values stay inline, so `i, n = 0,
|
|
340
|
+
// len(xs)` is unchanged. A definition only exposes its redeclared
|
|
341
|
+
// names: a new name is not visible to the values being assigned to it.
|
|
342
|
+
_parallelAssignValues(lhsNodes, rhsNodes, define = false) {
|
|
343
|
+
if (lhsNodes.length < 2 || rhsNodes.length !== lhsNodes.length)
|
|
344
|
+
return rhsNodes;
|
|
345
|
+
const roots = new Set(
|
|
346
|
+
lhsNodes.map((l) => rootIdentName(l)).filter((n) => n && n !== "_"),
|
|
347
|
+
);
|
|
348
|
+
const pure = rhsNodes.map((r) => this._independentOf(r, roots));
|
|
349
|
+
const redecl = lhsNodes.some((l) => l._redecl);
|
|
350
|
+
const observes = (r, i) =>
|
|
351
|
+
define ? this._mentions(r, roots) || (redecl && !pure[i]) : !pure[i];
|
|
352
|
+
if (!rhsNodes.some((r, i) => i > 0 && observes(r, i))) return rhsNodes;
|
|
353
|
+
const inPlace = lhsNodes.some(
|
|
354
|
+
(l) => this._isInPlaceArrayStore(l) || this._isStableTarget(l),
|
|
355
|
+
);
|
|
356
|
+
return rhsNodes.map((r, i) => {
|
|
357
|
+
if (pure[i]) return r;
|
|
358
|
+
const goType = r._type ?? this._resolveExprGoType(r);
|
|
359
|
+
const wType = this.toWasmType(goType);
|
|
360
|
+
const name = `__par_tmp_${this.tempCounter++}`;
|
|
361
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
362
|
+
this.emitExpr(r, wType);
|
|
363
|
+
// An array temp must snapshot the value when a target is stored in
|
|
364
|
+
// place (`a, b = b, a` with `p := &a`): the store would otherwise
|
|
365
|
+
// change what the temp holds. The snapshot is already a fresh
|
|
366
|
+
// copy, so assigning from the temp does not copy it again.
|
|
367
|
+
const isArr = this._isValueArray(goType);
|
|
368
|
+
const snapshot = inPlace && (isArr || this._isValueStruct(goType));
|
|
369
|
+
if (snapshot) this._emitCopyIfValue(r, goType, wType);
|
|
370
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
371
|
+
return {
|
|
372
|
+
kind: "Ident",
|
|
373
|
+
name,
|
|
374
|
+
_type: goType,
|
|
375
|
+
_parTmp: true,
|
|
376
|
+
_freshArray: snapshot && isArr,
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// True when assigning to `l` writes into an existing array object (so an
|
|
382
|
+
// earlier-read array value sharing that object would see the write): a
|
|
383
|
+
// boxed (address-taken) array local, `*p` on an array pointer, or an
|
|
384
|
+
// element of an array.
|
|
385
|
+
_isInPlaceArrayStore(l) {
|
|
386
|
+
if (l.kind === "Ident") {
|
|
387
|
+
const info = this.resolveLocal(l.name);
|
|
388
|
+
return !!info?.isBoxed && this._isValueArray(info.goType);
|
|
389
|
+
}
|
|
390
|
+
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
391
|
+
const t = (l.operand ?? l.expr)?._type;
|
|
392
|
+
return this._isValueArray(t?.base ?? t);
|
|
393
|
+
}
|
|
394
|
+
if (l.kind === "IndexExpr") {
|
|
395
|
+
const t = l.expr._type ?? this._resolveExprGoType(l.expr);
|
|
396
|
+
return (
|
|
397
|
+
this._isValueArray(t) ||
|
|
398
|
+
(t?.kind === "pointer" && this._isValueArray(t.base))
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// True when `l` is an array or struct target that a whole-value
|
|
405
|
+
// assignment writes in place (see `_needsStableStore`).
|
|
406
|
+
_isStableTarget(l) {
|
|
407
|
+
const t = l._type ?? this._resolveExprGoType(l);
|
|
408
|
+
return this._needsStableStore(l, t);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_mentions(node, names) {
|
|
412
|
+
if (!node || typeof node !== "object") return false;
|
|
413
|
+
if (node.kind === "Ident" && names.has(node.name)) return true;
|
|
414
|
+
return someChild(node, (child) => this._mentions(child, names));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
_releaseParallelTemps(rhsVals, rhsNodes) {
|
|
418
|
+
if (rhsVals === rhsNodes) return;
|
|
419
|
+
for (const r of rhsVals) if (r?._parTmp) this.locals.delete(r.name);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// True when evaluating `node` cannot observe an assignment to `roots`
|
|
423
|
+
// (or through a pointer) and has no side effects.
|
|
424
|
+
_independentOf(node, roots) {
|
|
425
|
+
if (!node || typeof node !== "object") return true;
|
|
426
|
+
switch (node.kind) {
|
|
427
|
+
case "BasicLit":
|
|
428
|
+
return true;
|
|
429
|
+
case "Ident":
|
|
430
|
+
return this._independentIdent(node, roots);
|
|
431
|
+
case "BinaryExpr":
|
|
432
|
+
return (
|
|
433
|
+
this._independentOf(node.left, roots) &&
|
|
434
|
+
this._independentOf(node.right, roots)
|
|
435
|
+
);
|
|
436
|
+
case "UnaryExpr":
|
|
437
|
+
return (
|
|
438
|
+
node.op !== "*" &&
|
|
439
|
+
node.op !== "&" &&
|
|
440
|
+
this._independentOf(node.operand, roots)
|
|
441
|
+
);
|
|
442
|
+
case "CallExpr":
|
|
443
|
+
return this._isPureLenCall(node, roots);
|
|
444
|
+
default:
|
|
445
|
+
return false;
|
|
205
446
|
}
|
|
206
447
|
}
|
|
207
448
|
|
|
449
|
+
_independentIdent(node, roots) {
|
|
450
|
+
if (node._const || node.name === "nil") return true;
|
|
451
|
+
if (roots.has(node.name)) return false;
|
|
452
|
+
const local = this.resolveLocal(node.name);
|
|
453
|
+
return !!local && !local.isBoxed;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// `len`/`cap` of a pure operand.
|
|
457
|
+
_isPureLenCall(node, roots) {
|
|
458
|
+
const fn = node.func;
|
|
459
|
+
if (fn?.kind !== "Ident" || (fn.name !== "len" && fn.name !== "cap"))
|
|
460
|
+
return false;
|
|
461
|
+
if (this.resolveLocal(fn.name) || node.args?.length !== 1) return false;
|
|
462
|
+
return this._independentOf(node.args[0], roots);
|
|
463
|
+
}
|
|
464
|
+
|
|
208
465
|
_emitAssignToTarget(target, wasmType, goType, emitValue) {
|
|
209
466
|
const tempName = `__assign_tmp_${this.tempCounter++}`;
|
|
210
467
|
const effectiveGoType = goType ??
|
|
@@ -244,10 +501,48 @@ export class StmtsEmitter {
|
|
|
244
501
|
this.allocLocal(l.name, wType, r._type);
|
|
245
502
|
localInfo = this.locals.get(l.name);
|
|
246
503
|
}
|
|
504
|
+
if (
|
|
505
|
+
localInfo &&
|
|
506
|
+
op !== ":=" &&
|
|
507
|
+
this._needsStableStore(l, localInfo.goType)
|
|
508
|
+
) {
|
|
509
|
+
this._emitStableStore(localInfo.goType, r, () => {
|
|
510
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
511
|
+
if (localInfo.isBoxed)
|
|
512
|
+
this.pushInstruction({
|
|
513
|
+
op: "struct.get",
|
|
514
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
515
|
+
fieldIndex: 0,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const globalStable =
|
|
521
|
+
!localInfo && op !== ":=" && this.mod.resolveGlobal(l.name);
|
|
522
|
+
if (globalStable && this._needsStableStore(l, globalStable.goType)) {
|
|
523
|
+
this._emitStableStore(globalStable.goType, r, () => {
|
|
524
|
+
this.pushInstruction({ op: "global.get", index: globalStable.index });
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
247
528
|
if (localInfo) {
|
|
529
|
+
if (localInfo.isBoxed && this._isValueArray(localInfo.goType)) {
|
|
530
|
+
// Pointers to the array (`&a`) hold its reference: write in place.
|
|
531
|
+
this._emitArrayStoreInto(localInfo.goType, r, () => {
|
|
532
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
533
|
+
this.pushInstruction({
|
|
534
|
+
op: "struct.get",
|
|
535
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
536
|
+
fieldIndex: 0,
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
248
541
|
if (localInfo.isBoxed) {
|
|
249
542
|
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
250
543
|
this.emitExpr(r, localInfo.boxInfo.wType);
|
|
544
|
+
if (isArrayType(localInfo.goType, this.mod.checker))
|
|
545
|
+
this._emitCopyIfValue(r, localInfo.goType, localInfo.boxInfo.wType);
|
|
251
546
|
this.pushInstruction({
|
|
252
547
|
op: "struct.set",
|
|
253
548
|
typeIndex: localInfo.boxInfo.typeIndex,
|
|
@@ -256,13 +551,13 @@ export class StmtsEmitter {
|
|
|
256
551
|
return;
|
|
257
552
|
}
|
|
258
553
|
this.emitExpr(r, localInfo.type);
|
|
259
|
-
this.
|
|
554
|
+
this._emitCopyIfValue(r, localInfo.goType, localInfo.type);
|
|
260
555
|
this.pushInstruction({ op: "local.set", index: localInfo.index });
|
|
261
556
|
} else {
|
|
262
557
|
const globalInfo = this.mod.resolveGlobal(l.name);
|
|
263
558
|
if (globalInfo) {
|
|
264
559
|
this.emitExpr(r, globalInfo.type);
|
|
265
|
-
this.
|
|
560
|
+
this._emitCopyIfValue(r, globalInfo.goType, globalInfo.type);
|
|
266
561
|
this.pushInstruction({
|
|
267
562
|
op: "global.set",
|
|
268
563
|
index: globalInfo.index,
|
|
@@ -292,7 +587,20 @@ export class StmtsEmitter {
|
|
|
292
587
|
this._resolveExprGoType(l.expr);
|
|
293
588
|
const baseWType = this.toWasmType(exprGoType);
|
|
294
589
|
|
|
295
|
-
this.
|
|
590
|
+
if (this._needsStableStore(l, lastStep.field.goType)) {
|
|
591
|
+
this._emitStableStore(lastStep.field.goType, r, () => {
|
|
592
|
+
this.emitSelectorBase(l.expr, baseWType, exprGoType);
|
|
593
|
+
for (const step of path) {
|
|
594
|
+
this.pushInstruction({
|
|
595
|
+
op: "struct.get",
|
|
596
|
+
typeIndex: step.structInfo.typeIndex,
|
|
597
|
+
fieldIndex: step.fieldIndex,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
this.emitSelectorBase(l.expr, baseWType, exprGoType);
|
|
296
604
|
for (let i = 0; i < path.length - 1; i++) {
|
|
297
605
|
const step = path[i];
|
|
298
606
|
this.pushInstruction({
|
|
@@ -302,11 +610,7 @@ export class StmtsEmitter {
|
|
|
302
610
|
});
|
|
303
611
|
}
|
|
304
612
|
this.emitExpr(r, lastStep.field.wType);
|
|
305
|
-
this.
|
|
306
|
-
r,
|
|
307
|
-
lastStep.field.goType,
|
|
308
|
-
lastStep.field.wType,
|
|
309
|
-
);
|
|
613
|
+
this._emitCopyIfValue(r, lastStep.field.goType, lastStep.field.wType);
|
|
310
614
|
this.pushInstruction({
|
|
311
615
|
op: "struct.set",
|
|
312
616
|
typeIndex: lastStep.structInfo.typeIndex,
|
|
@@ -315,57 +619,8 @@ export class StmtsEmitter {
|
|
|
315
619
|
return;
|
|
316
620
|
}
|
|
317
621
|
|
|
318
|
-
if (l.kind === "UnaryExpr" && l.op === "*") {
|
|
319
|
-
|
|
320
|
-
if (structInfo) {
|
|
321
|
-
const baseWType = this.toWasmType(l.operand._type);
|
|
322
|
-
const tmpL = this.acquireTemp(baseWType);
|
|
323
|
-
const tmpR = this.acquireTemp(baseWType);
|
|
324
|
-
this.emitExpr(l.operand, baseWType);
|
|
325
|
-
this.pushInstruction({ op: "local.set", index: tmpL });
|
|
326
|
-
this.emitExpr(r, baseWType);
|
|
327
|
-
this.pushInstruction({ op: "local.set", index: tmpR });
|
|
328
|
-
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
329
|
-
this.pushInstruction({ op: "local.get", index: tmpL });
|
|
330
|
-
this.pushInstruction({ op: "local.get", index: tmpR });
|
|
331
|
-
this.pushInstruction({
|
|
332
|
-
op: "struct.get",
|
|
333
|
-
typeIndex: structInfo.typeIndex,
|
|
334
|
-
fieldIndex: i,
|
|
335
|
-
});
|
|
336
|
-
this.pushInstruction({
|
|
337
|
-
op: "struct.set",
|
|
338
|
-
typeIndex: structInfo.typeIndex,
|
|
339
|
-
fieldIndex: i,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
this.releaseTemp(tmpR, baseWType);
|
|
343
|
-
this.releaseTemp(tmpL, baseWType);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const operandGoType =
|
|
347
|
-
l.operand._type ??
|
|
348
|
-
(l.operand.kind === "Ident"
|
|
349
|
-
? this.resolveLocal(l.operand.name)?.goType
|
|
350
|
-
: null) ??
|
|
351
|
-
this._resolveExprGoType(l.operand);
|
|
352
|
-
const box = this.mod.getBoxType(operandGoType?.base ?? operandGoType);
|
|
353
|
-
const baseWType = {
|
|
354
|
-
kind: "ref",
|
|
355
|
-
nullable: true,
|
|
356
|
-
typeIndex: box.typeIndex,
|
|
357
|
-
};
|
|
358
|
-
const boxTmp = this.acquireTemp(baseWType);
|
|
359
|
-
this.emitExpr(l.operand, baseWType);
|
|
360
|
-
this.pushInstruction({ op: "local.set", index: boxTmp });
|
|
361
|
-
this.pushInstruction({ op: "local.get", index: boxTmp });
|
|
362
|
-
this.emitExpr(r, box.wType);
|
|
363
|
-
this.pushInstruction({
|
|
364
|
-
op: "struct.set",
|
|
365
|
-
typeIndex: box.typeIndex,
|
|
366
|
-
fieldIndex: 0,
|
|
367
|
-
});
|
|
368
|
-
this.releaseTemp(boxTmp, baseWType);
|
|
622
|
+
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
623
|
+
this._emitDerefAssign(l, r);
|
|
369
624
|
return;
|
|
370
625
|
}
|
|
371
626
|
|
|
@@ -381,7 +636,7 @@ export class StmtsEmitter {
|
|
|
381
636
|
this.emitExpr(baseNode, mapInfo.wType);
|
|
382
637
|
this.emitExpr(l.index, mapInfo.keyWType);
|
|
383
638
|
this.emitExpr(r, mapInfo.valWType);
|
|
384
|
-
this.
|
|
639
|
+
this._emitCopyIfValue(r, valType, mapInfo.valWType);
|
|
385
640
|
this.pushInstruction({
|
|
386
641
|
op: "call",
|
|
387
642
|
funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
|
|
@@ -400,18 +655,29 @@ export class StmtsEmitter {
|
|
|
400
655
|
baseType?.kind === "pointer" &&
|
|
401
656
|
isArrayType(baseType.base, this.mod.checker);
|
|
402
657
|
|
|
403
|
-
const elemGoType =
|
|
404
|
-
? baseType.
|
|
405
|
-
|
|
406
|
-
? baseType.base.elem
|
|
407
|
-
: baseType.elem;
|
|
658
|
+
const elemGoType = this._getArrayElemType(
|
|
659
|
+
isPtrToArr ? baseType.base : baseType,
|
|
660
|
+
);
|
|
408
661
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
409
662
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
410
663
|
|
|
664
|
+
// A whole struct or array stored into an element whose address is
|
|
665
|
+
// taken writes into the existing element, so `p := &xs[0]` stays valid.
|
|
666
|
+
if (this._needsStableStore(l, elemGoType)) {
|
|
667
|
+
this._emitStableStore(elemGoType, r, () => {
|
|
668
|
+
this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
|
|
669
|
+
this.pushInstruction({
|
|
670
|
+
op: "array.get",
|
|
671
|
+
typeIndex: arrInfo.typeIndex,
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
411
677
|
this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
|
|
412
678
|
|
|
413
679
|
this.emitExpr(r, arrInfo.elemWType);
|
|
414
|
-
this.
|
|
680
|
+
this._emitCopyIfValue(r, elemGoType, arrInfo.elemWType);
|
|
415
681
|
|
|
416
682
|
this.pushInstruction({
|
|
417
683
|
op: "array.set",
|
|
@@ -421,7 +687,325 @@ export class StmtsEmitter {
|
|
|
421
687
|
}
|
|
422
688
|
}
|
|
423
689
|
|
|
690
|
+
// A range variable that a closure captures and mutates lives in a box,
|
|
691
|
+
// but the loop writes the plain value each iteration. It gets a raw
|
|
692
|
+
// local here, and `_emitRangeBoxes` (at the top of the body) wraps the
|
|
693
|
+
// value in a fresh box per iteration, so each iteration's closures keep
|
|
694
|
+
// their own variable (Go 1.22) and the loop never stores a raw value
|
|
695
|
+
// into a box local.
|
|
696
|
+
_allocRangeVar(name, wType, goType) {
|
|
697
|
+
if (!this.mutatedCaptures.has(name))
|
|
698
|
+
return this.allocLocal(name, wType, goType);
|
|
699
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
700
|
+
if (!this._pendingRangeBoxes) this._pendingRangeBoxes = [];
|
|
701
|
+
this._pendingRangeBoxes.push({ name, idx, wType, goType });
|
|
702
|
+
return idx;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
_emitRangeBoxes() {
|
|
706
|
+
const pending = this._pendingRangeBoxes ?? [];
|
|
707
|
+
this._pendingRangeBoxes = [];
|
|
708
|
+
for (const { name, idx, wType, goType } of pending) {
|
|
709
|
+
const boxIdx = this.allocLocal(name, wType, goType);
|
|
710
|
+
const info = this.locals.get(name);
|
|
711
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
712
|
+
this.pushInstruction({
|
|
713
|
+
op: "struct.new",
|
|
714
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
715
|
+
});
|
|
716
|
+
this.pushInstruction({ op: "local.set", index: boxIdx });
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// The struct that holds the last field of `path`: a promoted field
|
|
721
|
+
// (`o.N` on an embedded `Inner`) lives on the embedded struct.
|
|
722
|
+
_fieldHolderWType(path, baseWType) {
|
|
723
|
+
if (path.length <= 1) return baseWType;
|
|
724
|
+
const holder = path[path.length - 1].structInfo;
|
|
725
|
+
return { kind: "ref", nullable: true, typeIndex: holder.typeIndex };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Copy the array value `r` into the existing array that `emitDst` pushes,
|
|
729
|
+
// so every pointer to that array sees the new elements.
|
|
730
|
+
_emitArrayStoreInto(goType, r, emitDst) {
|
|
731
|
+
const elemGoType = this._getArrayElemType(goType);
|
|
732
|
+
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
733
|
+
const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
|
|
734
|
+
if (this._isValueStruct(elemGoType)) {
|
|
735
|
+
this._emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const dst = this.acquireTemp(wType);
|
|
739
|
+
emitDst();
|
|
740
|
+
this.pushInstruction({ op: "local.tee", index: dst });
|
|
741
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
742
|
+
this.emitExpr(r, wType);
|
|
743
|
+
this._emitCopyIfValue(r, goType, wType);
|
|
744
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
745
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
746
|
+
this.pushInstruction("array.len");
|
|
747
|
+
this.pushInstruction({
|
|
748
|
+
op: "array.copy",
|
|
749
|
+
typeIndex: arrInfo.typeIndex,
|
|
750
|
+
});
|
|
751
|
+
this.releaseTemp(dst, wType);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
_isValueStruct(goType) {
|
|
755
|
+
return (
|
|
756
|
+
!!goType &&
|
|
757
|
+
goType.kind !== "pointer" &&
|
|
758
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
759
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// An array of structs holds element references, so `array.copy` would
|
|
764
|
+
// swap them and leave `&a[i]` pointing at the old element. Copy each
|
|
765
|
+
// element's fields into the existing element struct instead.
|
|
766
|
+
_emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst) {
|
|
767
|
+
const elemInfo = this._resolveStructInfo({ _type: elemGoType });
|
|
768
|
+
const eType = arrInfo.elemWType;
|
|
769
|
+
const dst = this.acquireTemp(wType);
|
|
770
|
+
const src = this.acquireTemp(wType);
|
|
771
|
+
const i = this.acquireTemp("i32");
|
|
772
|
+
const d = this.acquireTemp(eType);
|
|
773
|
+
const s = this.acquireTemp(eType);
|
|
774
|
+
emitDst();
|
|
775
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
776
|
+
this.emitExpr(r, wType);
|
|
777
|
+
this.pushInstruction({ op: "local.set", index: src });
|
|
778
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
779
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
780
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
781
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
782
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
783
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
784
|
+
this.pushInstruction("array.len");
|
|
785
|
+
this.pushInstruction("i32.ge_s");
|
|
786
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
787
|
+
for (const [tmp, arr] of [
|
|
788
|
+
[d, dst],
|
|
789
|
+
[s, src],
|
|
790
|
+
]) {
|
|
791
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
792
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
793
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
794
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
795
|
+
}
|
|
796
|
+
this._emitStructFieldsInto(elemInfo, d, s);
|
|
797
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
798
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
799
|
+
this.pushInstruction("i32.add");
|
|
800
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
801
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
802
|
+
this.pushInstruction("end");
|
|
803
|
+
this.pushInstruction("end");
|
|
804
|
+
this.releaseTemp(s, eType);
|
|
805
|
+
this.releaseTemp(d, eType);
|
|
806
|
+
this.releaseTemp(i, "i32");
|
|
807
|
+
this.releaseTemp(src, wType);
|
|
808
|
+
this.releaseTemp(dst, wType);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Whole-value assignment to a variable (or a field of one) whose address
|
|
812
|
+
// is taken writes into the existing storage, so earlier pointers to it or
|
|
813
|
+
// to its parts see the new value (Task 0.33). Other variables keep the
|
|
814
|
+
// plain local.set / struct.set path.
|
|
815
|
+
_needsStableStore(l, goType) {
|
|
816
|
+
if (!goType || goType.kind === "pointer") return false;
|
|
817
|
+
const isArr = this._isValueArray(goType);
|
|
818
|
+
if (
|
|
819
|
+
!isArr &&
|
|
820
|
+
!(
|
|
821
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
822
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
823
|
+
)
|
|
824
|
+
)
|
|
825
|
+
return false;
|
|
826
|
+
const root = rootIdentName(l);
|
|
827
|
+
if (!root) return false;
|
|
828
|
+
// Any code may hold the address of a package-level variable.
|
|
829
|
+
if (!this.resolveLocal(root)) return !!this.mod.resolveGlobal(root);
|
|
830
|
+
const decl = this.rootFuncDecl._sourceDecl ?? this.rootFuncDecl;
|
|
831
|
+
const ctx = this.mod.lowerResult?.ownership?.get(decl);
|
|
832
|
+
return Boolean(ctx?.fnMutates(root, true));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
_emitStableStore(goType, r, emitDst) {
|
|
836
|
+
if (this._isValueArray(goType)) {
|
|
837
|
+
this._emitArrayStoreInto(goType, r, emitDst);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const info = this._resolveStructInfo({ _type: goType });
|
|
841
|
+
const wType = this.toWasmType(goType);
|
|
842
|
+
const dst = this.acquireTemp(wType);
|
|
843
|
+
const src = this.acquireTemp(wType);
|
|
844
|
+
emitDst();
|
|
845
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
846
|
+
this.emitExpr(r, wType);
|
|
847
|
+
this.pushInstruction({ op: "local.set", index: src });
|
|
848
|
+
this._emitStructFieldsInto(info, dst, src);
|
|
849
|
+
this.releaseTemp(src, wType);
|
|
850
|
+
this.releaseTemp(dst, wType);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Copies every field of the struct in `src` into the struct in `dst`;
|
|
854
|
+
// array and value-struct fields are copied into the existing storage.
|
|
855
|
+
_emitStructFieldsInto(info, dst, src) {
|
|
856
|
+
const get = (tmp, i) => {
|
|
857
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
858
|
+
this.pushInstruction({
|
|
859
|
+
op: "struct.get",
|
|
860
|
+
typeIndex: info.typeIndex,
|
|
861
|
+
fieldIndex: i,
|
|
862
|
+
});
|
|
863
|
+
};
|
|
864
|
+
for (let i = 0; i < info.fields.length; i++) {
|
|
865
|
+
const f = info.fields[i];
|
|
866
|
+
const gt = f.goType;
|
|
867
|
+
if (gt && gt.kind !== "pointer" && this._isValueArray(gt)) {
|
|
868
|
+
const arrInfo = this.mod.getArrayType(this._getArrayElemType(gt));
|
|
869
|
+
get(dst, i);
|
|
870
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
871
|
+
get(src, i);
|
|
872
|
+
const elem = this._getArrayElemType(gt);
|
|
873
|
+
if (
|
|
874
|
+
isArrayType(elem, this.mod.checker) ||
|
|
875
|
+
isStructType(elem, this.mod.checker, this.mod)
|
|
876
|
+
)
|
|
877
|
+
this.emitCopyArray(gt);
|
|
878
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
879
|
+
get(dst, i);
|
|
880
|
+
this.pushInstruction("array.len");
|
|
881
|
+
this.pushInstruction({
|
|
882
|
+
op: "array.copy",
|
|
883
|
+
typeIndex: arrInfo.typeIndex,
|
|
884
|
+
});
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const inner =
|
|
888
|
+
gt &&
|
|
889
|
+
gt.kind !== "pointer" &&
|
|
890
|
+
isStructType(gt, this.mod.checker, this.mod) &&
|
|
891
|
+
!isPointerToStruct(gt, this.mod.checker, this.mod)
|
|
892
|
+
? this._resolveStructInfo({ _type: gt })
|
|
893
|
+
: null;
|
|
894
|
+
if (inner) {
|
|
895
|
+
const d = this.acquireTemp(f.wType);
|
|
896
|
+
const s = this.acquireTemp(f.wType);
|
|
897
|
+
get(dst, i);
|
|
898
|
+
this.pushInstruction({ op: "local.set", index: d });
|
|
899
|
+
get(src, i);
|
|
900
|
+
this.pushInstruction({ op: "local.set", index: s });
|
|
901
|
+
this._emitStructFieldsInto(inner, d, s);
|
|
902
|
+
this.releaseTemp(s, f.wType);
|
|
903
|
+
this.releaseTemp(d, f.wType);
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
907
|
+
get(src, i);
|
|
908
|
+
this.pushInstruction({
|
|
909
|
+
op: "struct.set",
|
|
910
|
+
typeIndex: info.typeIndex,
|
|
911
|
+
fieldIndex: i,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
_emitStructDerefAssign(operand, r, structInfo) {
|
|
917
|
+
const baseWType = this.toWasmType(operand._type);
|
|
918
|
+
const tmpL = this.acquireTemp(baseWType);
|
|
919
|
+
const tmpR = this.acquireTemp(baseWType);
|
|
920
|
+
this.emitExpr(operand, baseWType);
|
|
921
|
+
this.pushInstruction({ op: "local.set", index: tmpL });
|
|
922
|
+
this.emitExpr(r, baseWType);
|
|
923
|
+
this.pushInstruction({ op: "local.set", index: tmpR });
|
|
924
|
+
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
925
|
+
this.pushInstruction({ op: "local.get", index: tmpL });
|
|
926
|
+
this.pushInstruction({ op: "local.get", index: tmpR });
|
|
927
|
+
this.pushInstruction({
|
|
928
|
+
op: "struct.get",
|
|
929
|
+
typeIndex: structInfo.typeIndex,
|
|
930
|
+
fieldIndex: i,
|
|
931
|
+
});
|
|
932
|
+
this.pushInstruction({
|
|
933
|
+
op: "struct.set",
|
|
934
|
+
typeIndex: structInfo.typeIndex,
|
|
935
|
+
fieldIndex: i,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
this.releaseTemp(tmpR, baseWType);
|
|
939
|
+
this.releaseTemp(tmpL, baseWType);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
_emitDerefAssign(l, r) {
|
|
943
|
+
const operand = l.operand ?? l.expr;
|
|
944
|
+
const structInfo = this._derefStructInfo(operand);
|
|
945
|
+
if (structInfo) {
|
|
946
|
+
this._emitStructDerefAssign(operand, r, structInfo);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const operandGoType =
|
|
950
|
+
operand._type ??
|
|
951
|
+
(operand.kind === "Ident"
|
|
952
|
+
? this.resolveLocal(operand.name)?.goType
|
|
953
|
+
: null) ??
|
|
954
|
+
this._resolveExprGoType(operand);
|
|
955
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
956
|
+
if (this._isValueArray(baseGoType)) {
|
|
957
|
+
this._emitArrayStoreInto(baseGoType, r, () => {
|
|
958
|
+
this.emitExpr(operand);
|
|
959
|
+
this._emitNilCheck(operandGoType);
|
|
960
|
+
});
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
967
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
968
|
+
const ptrWType = {
|
|
969
|
+
kind: "ref",
|
|
970
|
+
nullable: true,
|
|
971
|
+
typeIndex: ptr.typeIndex,
|
|
972
|
+
typeName: ptr.name,
|
|
973
|
+
};
|
|
974
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
975
|
+
const valTmp = this.acquireTemp(box.wType);
|
|
976
|
+
this.emitExpr(operand, ptrWType);
|
|
977
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
978
|
+
this.emitExpr(r, box.wType);
|
|
979
|
+
this.pushInstruction({ op: "local.set", index: valTmp });
|
|
980
|
+
|
|
981
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
982
|
+
this.pushInstruction({
|
|
983
|
+
op: "ref.cast",
|
|
984
|
+
typeIndex: box.typeIndex,
|
|
985
|
+
typeName: box.name,
|
|
986
|
+
});
|
|
987
|
+
this.pushInstruction({ op: "local.get", index: valTmp });
|
|
988
|
+
this.pushInstruction({
|
|
989
|
+
op: "struct.set",
|
|
990
|
+
typeIndex: box.typeIndex,
|
|
991
|
+
typeName: box.name,
|
|
992
|
+
fieldIndex: 0,
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
this.releaseTemp(valTmp, box.wType);
|
|
996
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
997
|
+
}
|
|
998
|
+
|
|
424
999
|
_emitTupleAssign(lhsNodes, rhsNode, op) {
|
|
1000
|
+
if (op === "=" && lhsNodes.some((l) => !this._isPlainTupleTarget(l))) {
|
|
1001
|
+
this._emitTupleViaTemps(
|
|
1002
|
+
lhsNodes,
|
|
1003
|
+
rhsNode,
|
|
1004
|
+
(l, t) => this._emitAssignSingle(l, t, "="),
|
|
1005
|
+
true,
|
|
1006
|
+
);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
425
1009
|
const tupleTypes = rhsNode._type.types;
|
|
426
1010
|
const dests = [];
|
|
427
1011
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
@@ -509,12 +1093,20 @@ export class StmtsEmitter {
|
|
|
509
1093
|
this.releaseTemp(okTmp, "i32");
|
|
510
1094
|
}
|
|
511
1095
|
|
|
1096
|
+
// Pushes a comma-ok result (map value / asserted value). An array is a
|
|
1097
|
+
// value: the stored one must not alias the map entry or boxed interface.
|
|
1098
|
+
_emitGetTempValue(tempIdx, goType) {
|
|
1099
|
+
this.pushInstruction({ op: "local.get", index: tempIdx });
|
|
1100
|
+
if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
|
|
1101
|
+
this.emitCopyArray(goType);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
512
1104
|
_emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
|
|
513
1105
|
if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
|
|
514
1106
|
if (isDefine) {
|
|
515
1107
|
const idx = this.allocLocal(lhsNode.name, wType, goType);
|
|
516
1108
|
const local = this.locals.get(lhsNode.name);
|
|
517
|
-
this.
|
|
1109
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
518
1110
|
if (local?.isBoxed) {
|
|
519
1111
|
this.pushInstruction({
|
|
520
1112
|
op: "struct.new",
|
|
@@ -528,19 +1120,19 @@ export class StmtsEmitter {
|
|
|
528
1120
|
if (local) {
|
|
529
1121
|
if (local.isBoxed) {
|
|
530
1122
|
this.pushInstruction({ op: "local.get", index: local.index });
|
|
531
|
-
this.
|
|
1123
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
532
1124
|
this.pushInstruction({
|
|
533
1125
|
op: "struct.set",
|
|
534
1126
|
typeIndex: local.boxInfo.typeIndex,
|
|
535
1127
|
fieldIndex: 0,
|
|
536
1128
|
});
|
|
537
1129
|
} else {
|
|
538
|
-
this.
|
|
1130
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
539
1131
|
this.pushInstruction({ op: "local.set", index: local.index });
|
|
540
1132
|
}
|
|
541
1133
|
} else {
|
|
542
1134
|
const global = this.mod.resolveGlobal(lhsNode.name);
|
|
543
|
-
this.
|
|
1135
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
544
1136
|
this.pushInstruction({ op: "global.set", index: global.index });
|
|
545
1137
|
}
|
|
546
1138
|
}
|
|
@@ -591,6 +1183,15 @@ export class StmtsEmitter {
|
|
|
591
1183
|
return goType.name ?? null;
|
|
592
1184
|
}
|
|
593
1185
|
|
|
1186
|
+
// The struct a type assertion target refers to; an anonymous struct type
|
|
1187
|
+
// has no name to look up.
|
|
1188
|
+
_structInfoOfType(goType) {
|
|
1189
|
+
return (
|
|
1190
|
+
this.mod.getStructType(this._structNameOf(goType)) ??
|
|
1191
|
+
this._resolveStructInfo({ _type: goType })
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
594
1195
|
_emitTypeTest(targetGoType) {
|
|
595
1196
|
if (!targetGoType) {
|
|
596
1197
|
this.pushInstruction("drop");
|
|
@@ -638,7 +1239,9 @@ export class StmtsEmitter {
|
|
|
638
1239
|
return;
|
|
639
1240
|
}
|
|
640
1241
|
|
|
641
|
-
|
|
1242
|
+
// A named string type is boxed like any other scalar (see emitExpr); only
|
|
1243
|
+
// a plain `string` lives in `any` as the bare string.
|
|
1244
|
+
if (isBareStringType(targetGoType, this.mod.checker)) {
|
|
642
1245
|
const isStrIdx = this.mod.getIsStringImportIndex();
|
|
643
1246
|
this.pushInstruction({ op: "call", funcIndex: isStrIdx });
|
|
644
1247
|
return;
|
|
@@ -646,19 +1249,16 @@ export class StmtsEmitter {
|
|
|
646
1249
|
|
|
647
1250
|
if (isFuncType(targetGoType, this.mod.checker)) {
|
|
648
1251
|
const closureInfo = this.mod.getClosureType(targetGoType);
|
|
649
|
-
this.
|
|
650
|
-
op: "ref.test",
|
|
651
|
-
typeIndex: closureInfo.typeIndex,
|
|
652
|
-
});
|
|
1252
|
+
this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
|
|
653
1253
|
return;
|
|
654
1254
|
}
|
|
655
1255
|
|
|
656
1256
|
// `*T` lives in `any` as the bare struct ref; a `T` value as a boxed
|
|
657
1257
|
// clone (see emitExpr). Fall through to the scalar box for values.
|
|
658
1258
|
if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
|
|
659
|
-
const sInfo = this.
|
|
1259
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
660
1260
|
if (sInfo) {
|
|
661
|
-
this.
|
|
1261
|
+
this._emitRefTestOrNil(targetGoType, sInfo.typeIndex);
|
|
662
1262
|
return;
|
|
663
1263
|
}
|
|
664
1264
|
}
|
|
@@ -681,6 +1281,17 @@ export class StmtsEmitter {
|
|
|
681
1281
|
}
|
|
682
1282
|
}
|
|
683
1283
|
|
|
1284
|
+
// A map lives in `any` as its bare map struct.
|
|
1285
|
+
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1286
|
+
const { keyType, valType } = getMapKeyValTypes(
|
|
1287
|
+
targetGoType,
|
|
1288
|
+
this.mod.checker,
|
|
1289
|
+
);
|
|
1290
|
+
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1291
|
+
this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
684
1295
|
// Scalar types
|
|
685
1296
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
686
1297
|
if (box) {
|
|
@@ -692,37 +1303,65 @@ export class StmtsEmitter {
|
|
|
692
1303
|
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
693
1304
|
}
|
|
694
1305
|
|
|
1306
|
+
// `ref.test` for a map, func or `*T`, which also accepts the typed-nil
|
|
1307
|
+
// marker of the same Go type (see getNilBoxType).
|
|
1308
|
+
_emitRefTestOrNil(goType, typeIndex) {
|
|
1309
|
+
const nilBox = this.mod.getNilBoxType(goType);
|
|
1310
|
+
const tmp = this.acquireTemp("anyref");
|
|
1311
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
1312
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1313
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1314
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1315
|
+
this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
|
|
1316
|
+
this.pushInstruction("i32.or");
|
|
1317
|
+
this.releaseTemp(tmp, "anyref");
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// `ref.cast_null` to `typeIndex`, turning the typed-nil marker of `goType`
|
|
1321
|
+
// back into a nil value.
|
|
1322
|
+
_emitRefCastOrNil(goType, typeIndex) {
|
|
1323
|
+
const nilBox = this.mod.getNilBoxType(goType);
|
|
1324
|
+
const tmp = this.acquireTemp("anyref");
|
|
1325
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
1326
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1327
|
+
this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
|
|
1328
|
+
this.pushInstruction({
|
|
1329
|
+
op: "if",
|
|
1330
|
+
blockType: { kind: "ref", nullable: true, typeIndex },
|
|
1331
|
+
});
|
|
1332
|
+
this.pushInstruction({ op: "ref.null", heapType: typeIndex });
|
|
1333
|
+
this.pushInstruction("else");
|
|
1334
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1335
|
+
this.pushInstruction({ op: "ref.cast_null", typeIndex });
|
|
1336
|
+
this.pushInstruction("end");
|
|
1337
|
+
this.releaseTemp(tmp, "anyref");
|
|
1338
|
+
}
|
|
1339
|
+
|
|
695
1340
|
_emitTypeCast(targetGoType, targetWType) {
|
|
696
1341
|
if (isInterfaceType(targetGoType, this.mod.checker)) {
|
|
697
1342
|
return;
|
|
698
1343
|
}
|
|
699
1344
|
|
|
700
|
-
if (
|
|
1345
|
+
if (isBareStringType(targetGoType, this.mod.checker)) {
|
|
701
1346
|
this.pushInstruction("extern.convert_any");
|
|
702
1347
|
return;
|
|
703
1348
|
}
|
|
704
1349
|
|
|
705
1350
|
if (isFuncType(targetGoType, this.mod.checker)) {
|
|
706
1351
|
const closureInfo = this.mod.getClosureType(targetGoType);
|
|
707
|
-
this.
|
|
708
|
-
op: "ref.cast_null",
|
|
709
|
-
typeIndex: closureInfo.typeIndex,
|
|
710
|
-
});
|
|
1352
|
+
this._emitRefCastOrNil(targetGoType, closureInfo.typeIndex);
|
|
711
1353
|
return;
|
|
712
1354
|
}
|
|
713
1355
|
|
|
714
1356
|
if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
|
|
715
|
-
const sInfo = this.
|
|
1357
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
716
1358
|
if (sInfo) {
|
|
717
|
-
this.
|
|
718
|
-
op: "ref.cast_null",
|
|
719
|
-
typeIndex: sInfo.typeIndex,
|
|
720
|
-
});
|
|
1359
|
+
this._emitRefCastOrNil(targetGoType, sInfo.typeIndex);
|
|
721
1360
|
return;
|
|
722
1361
|
}
|
|
723
1362
|
}
|
|
724
1363
|
if (isStructType(targetGoType, this.mod.checker, this.mod)) {
|
|
725
|
-
const sInfo = this.
|
|
1364
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
726
1365
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
727
1366
|
if (sInfo && box) {
|
|
728
1367
|
this._emitUnbox(box);
|
|
@@ -755,6 +1394,16 @@ export class StmtsEmitter {
|
|
|
755
1394
|
}
|
|
756
1395
|
}
|
|
757
1396
|
|
|
1397
|
+
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1398
|
+
const { keyType, valType } = getMapKeyValTypes(
|
|
1399
|
+
targetGoType,
|
|
1400
|
+
this.mod.checker,
|
|
1401
|
+
);
|
|
1402
|
+
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1403
|
+
this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
758
1407
|
// Scalar types
|
|
759
1408
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
760
1409
|
if (box) {
|
|
@@ -780,6 +1429,44 @@ export class StmtsEmitter {
|
|
|
780
1429
|
const l = Array.isArray(lhs) ? lhs[0] : lhs;
|
|
781
1430
|
const r = Array.isArray(rhs) ? rhs[0] : rhs;
|
|
782
1431
|
|
|
1432
|
+
if (isComplex(l._type)) {
|
|
1433
|
+
const synthBinary = {
|
|
1434
|
+
kind: "BinaryExpr",
|
|
1435
|
+
op: baseOp,
|
|
1436
|
+
left: l,
|
|
1437
|
+
right: r,
|
|
1438
|
+
_type: l._type,
|
|
1439
|
+
};
|
|
1440
|
+
const is64 = isComplex64(l._type);
|
|
1441
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
1442
|
+
const cWType = {
|
|
1443
|
+
kind: "ref",
|
|
1444
|
+
nullable: true,
|
|
1445
|
+
typeIndex: info.typeIndex,
|
|
1446
|
+
typeName: info.name,
|
|
1447
|
+
};
|
|
1448
|
+
if (l.kind === "Ident") {
|
|
1449
|
+
const localInfo = this.resolveLocal(l.name);
|
|
1450
|
+
const globalInfo = !localInfo ? this.mod.resolveGlobal(l.name) : null;
|
|
1451
|
+
if (localInfo?.isBoxed) {
|
|
1452
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
1453
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1454
|
+
this.pushInstruction({
|
|
1455
|
+
op: "struct.set",
|
|
1456
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
1457
|
+
fieldIndex: 0,
|
|
1458
|
+
});
|
|
1459
|
+
} else if (localInfo) {
|
|
1460
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1461
|
+
this.pushInstruction({ op: "local.set", index: localInfo.index });
|
|
1462
|
+
} else if (globalInfo) {
|
|
1463
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1464
|
+
this.pushInstruction({ op: "global.set", index: globalInfo.index });
|
|
1465
|
+
}
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
783
1470
|
if (l.kind === "SelectorExpr") {
|
|
784
1471
|
const structInfo = this._resolveStructInfo(l.expr);
|
|
785
1472
|
if (!structInfo) {
|
|
@@ -796,9 +1483,10 @@ export class StmtsEmitter {
|
|
|
796
1483
|
const lastStep = path[path.length - 1];
|
|
797
1484
|
const targetType = lastStep.field.wType;
|
|
798
1485
|
const baseWType = this.toWasmType(l.expr._type);
|
|
1486
|
+
const holderWType = this._fieldHolderWType(path, baseWType);
|
|
799
1487
|
|
|
800
|
-
const baseTmp = this.acquireTemp(
|
|
801
|
-
this.
|
|
1488
|
+
const baseTmp = this.acquireTemp(holderWType);
|
|
1489
|
+
this.emitSelectorBase(l.expr, baseWType);
|
|
802
1490
|
for (let i = 0; i < path.length - 1; i++) {
|
|
803
1491
|
const step = path[i];
|
|
804
1492
|
this.pushInstruction({
|
|
@@ -825,7 +1513,7 @@ export class StmtsEmitter {
|
|
|
825
1513
|
typeIndex: lastStep.structInfo.typeIndex,
|
|
826
1514
|
fieldIndex: lastStep.fieldIndex,
|
|
827
1515
|
});
|
|
828
|
-
this.releaseTemp(baseTmp,
|
|
1516
|
+
this.releaseTemp(baseTmp, holderWType);
|
|
829
1517
|
return;
|
|
830
1518
|
}
|
|
831
1519
|
|
|
@@ -882,11 +1570,9 @@ export class StmtsEmitter {
|
|
|
882
1570
|
baseType?.kind === "pointer" &&
|
|
883
1571
|
isArrayType(baseType.base, this.mod.checker);
|
|
884
1572
|
|
|
885
|
-
const elemGoType =
|
|
886
|
-
? baseType.
|
|
887
|
-
|
|
888
|
-
? baseType.base.elem
|
|
889
|
-
: baseType.elem;
|
|
1573
|
+
const elemGoType = this._getArrayElemType(
|
|
1574
|
+
isPtrToArr ? baseType.base : baseType,
|
|
1575
|
+
);
|
|
890
1576
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
891
1577
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
892
1578
|
const targetType = arrInfo.elemWType;
|
|
@@ -925,32 +1611,56 @@ export class StmtsEmitter {
|
|
|
925
1611
|
|
|
926
1612
|
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
927
1613
|
const operand = l.operand ?? l.expr;
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
1614
|
+
const operandGoType =
|
|
1615
|
+
operand._type ??
|
|
1616
|
+
(operand.kind === "Ident"
|
|
1617
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1618
|
+
: null) ??
|
|
1619
|
+
this._resolveExprGoType(operand);
|
|
1620
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1621
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
|
|
1622
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1623
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1624
|
+
if (box && ptr) {
|
|
1625
|
+
const ptrWType = {
|
|
931
1626
|
kind: "ref",
|
|
932
1627
|
nullable: true,
|
|
933
|
-
typeIndex:
|
|
1628
|
+
typeIndex: ptr.typeIndex,
|
|
1629
|
+
typeName: ptr.name,
|
|
934
1630
|
};
|
|
935
|
-
const
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
938
|
-
this.pushInstruction({ op: "local.
|
|
939
|
-
|
|
1631
|
+
const targetType = box.wType;
|
|
1632
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1633
|
+
this.emitExpr(operand, ptrWType);
|
|
1634
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1635
|
+
|
|
1636
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1637
|
+
this.pushInstruction({
|
|
1638
|
+
op: "ref.cast",
|
|
1639
|
+
typeIndex: box.typeIndex,
|
|
1640
|
+
typeName: box.name,
|
|
1641
|
+
});
|
|
1642
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1643
|
+
this.pushInstruction({
|
|
1644
|
+
op: "ref.cast",
|
|
1645
|
+
typeIndex: box.typeIndex,
|
|
1646
|
+
typeName: box.name,
|
|
1647
|
+
});
|
|
940
1648
|
this.pushInstruction({
|
|
941
1649
|
op: "struct.get",
|
|
942
1650
|
typeIndex: box.typeIndex,
|
|
1651
|
+
typeName: box.name,
|
|
943
1652
|
fieldIndex: 0,
|
|
944
1653
|
});
|
|
945
|
-
const targetType = box.wType;
|
|
946
1654
|
this.emitExpr(r, targetType);
|
|
947
|
-
this.emitBinaryOp(baseOp, targetType, l._type ??
|
|
1655
|
+
this.emitBinaryOp(baseOp, targetType, l._type ?? baseGoType);
|
|
948
1656
|
this.pushInstruction({
|
|
949
1657
|
op: "struct.set",
|
|
950
1658
|
typeIndex: box.typeIndex,
|
|
1659
|
+
typeName: box.name,
|
|
951
1660
|
fieldIndex: 0,
|
|
952
1661
|
});
|
|
953
|
-
|
|
1662
|
+
|
|
1663
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
954
1664
|
return;
|
|
955
1665
|
}
|
|
956
1666
|
}
|
|
@@ -1038,36 +1748,56 @@ export class StmtsEmitter {
|
|
|
1038
1748
|
expr.kind === "StarExpr"
|
|
1039
1749
|
) {
|
|
1040
1750
|
const operand = expr.operand ?? expr.expr;
|
|
1041
|
-
const structInfo = this.
|
|
1751
|
+
const structInfo = this._derefStructInfo(operand);
|
|
1042
1752
|
if (!structInfo) {
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1753
|
+
const operandGoType =
|
|
1754
|
+
operand._type ??
|
|
1755
|
+
(operand.kind === "Ident"
|
|
1756
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1757
|
+
: null) ??
|
|
1758
|
+
this._resolveExprGoType(operand);
|
|
1759
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1760
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
|
|
1761
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1762
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1763
|
+
if (box && ptr) {
|
|
1764
|
+
const ptrWType = {
|
|
1046
1765
|
kind: "ref",
|
|
1047
1766
|
nullable: true,
|
|
1048
|
-
typeIndex:
|
|
1767
|
+
typeIndex: ptr.typeIndex,
|
|
1768
|
+
typeName: ptr.name,
|
|
1049
1769
|
};
|
|
1050
|
-
const
|
|
1051
|
-
this.emitExpr(operand,
|
|
1052
|
-
this.pushInstruction({ op: "local.set", index:
|
|
1053
|
-
|
|
1054
|
-
this.pushInstruction({ op: "local.get", index:
|
|
1770
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1771
|
+
this.emitExpr(operand, ptrWType);
|
|
1772
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1773
|
+
|
|
1774
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1775
|
+
this.pushInstruction({
|
|
1776
|
+
op: "ref.cast",
|
|
1777
|
+
typeIndex: box.typeIndex,
|
|
1778
|
+
typeName: box.name,
|
|
1779
|
+
});
|
|
1780
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1781
|
+
this.pushInstruction({
|
|
1782
|
+
op: "ref.cast",
|
|
1783
|
+
typeIndex: box.typeIndex,
|
|
1784
|
+
typeName: box.name,
|
|
1785
|
+
});
|
|
1055
1786
|
this.pushInstruction({
|
|
1056
1787
|
op: "struct.get",
|
|
1057
1788
|
typeIndex: box.typeIndex,
|
|
1789
|
+
typeName: box.name,
|
|
1058
1790
|
fieldIndex: 0,
|
|
1059
1791
|
});
|
|
1060
|
-
this._emitIncDecStep(
|
|
1061
|
-
op,
|
|
1062
|
-
box.wType,
|
|
1063
|
-
operand._type?.base ?? operand._type,
|
|
1064
|
-
);
|
|
1792
|
+
this._emitIncDecStep(op, box.wType, baseGoType);
|
|
1065
1793
|
this.pushInstruction({
|
|
1066
1794
|
op: "struct.set",
|
|
1067
1795
|
typeIndex: box.typeIndex,
|
|
1796
|
+
typeName: box.name,
|
|
1068
1797
|
fieldIndex: 0,
|
|
1069
1798
|
});
|
|
1070
|
-
|
|
1799
|
+
|
|
1800
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
1071
1801
|
return;
|
|
1072
1802
|
}
|
|
1073
1803
|
}
|
|
@@ -1081,9 +1811,10 @@ export class StmtsEmitter {
|
|
|
1081
1811
|
const lastStep = path[path.length - 1];
|
|
1082
1812
|
const targetType = lastStep.field.wType;
|
|
1083
1813
|
const baseWType = this.toWasmType(expr.expr._type);
|
|
1814
|
+
const holderWType = this._fieldHolderWType(path, baseWType);
|
|
1084
1815
|
|
|
1085
|
-
const baseTmp = this.acquireTemp(
|
|
1086
|
-
this.
|
|
1816
|
+
const baseTmp = this.acquireTemp(holderWType);
|
|
1817
|
+
this.emitSelectorBase(expr.expr, baseWType);
|
|
1087
1818
|
for (let i = 0; i < path.length - 1; i++) {
|
|
1088
1819
|
const step = path[i];
|
|
1089
1820
|
this.pushInstruction({
|
|
@@ -1170,11 +1901,9 @@ export class StmtsEmitter {
|
|
|
1170
1901
|
baseType?.kind === "pointer" &&
|
|
1171
1902
|
isArrayType(baseType.base, this.mod.checker);
|
|
1172
1903
|
|
|
1173
|
-
const elemGoType =
|
|
1174
|
-
? baseType.
|
|
1175
|
-
|
|
1176
|
-
? baseType.base.elem
|
|
1177
|
-
: baseType.elem;
|
|
1904
|
+
const elemGoType = this._getArrayElemType(
|
|
1905
|
+
isPtrToArr ? baseType.base : baseType,
|
|
1906
|
+
);
|
|
1178
1907
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
1179
1908
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
1180
1909
|
const targetType = arrInfo.elemWType;
|
|
@@ -1325,7 +2054,7 @@ export class StmtsEmitter {
|
|
|
1325
2054
|
!this.capturedNames?.includes(lhs[0].name) &&
|
|
1326
2055
|
!this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
|
|
1327
2056
|
const wType = isNonEscaping ? "i32" : "i64";
|
|
1328
|
-
const lIdx = this.
|
|
2057
|
+
const lIdx = this._allocRangeVar(lhs[0].name, wType, {
|
|
1329
2058
|
kind: "basic",
|
|
1330
2059
|
name: "int",
|
|
1331
2060
|
});
|
|
@@ -1378,6 +2107,7 @@ export class StmtsEmitter {
|
|
|
1378
2107
|
// Emit body
|
|
1379
2108
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1380
2109
|
if (bodyBlock) {
|
|
2110
|
+
this._emitRangeBoxes();
|
|
1381
2111
|
this.emitBlock(bodyBlock);
|
|
1382
2112
|
}
|
|
1383
2113
|
|
|
@@ -1425,7 +2155,7 @@ export class StmtsEmitter {
|
|
|
1425
2155
|
let idxLocalInfo = null;
|
|
1426
2156
|
if (lhs[0] && lhs[0].name !== "_") {
|
|
1427
2157
|
if (!isAssign) {
|
|
1428
|
-
const lIdx = this.
|
|
2158
|
+
const lIdx = this._allocRangeVar(lhs[0].name, "i64", {
|
|
1429
2159
|
kind: "basic",
|
|
1430
2160
|
name: "int",
|
|
1431
2161
|
});
|
|
@@ -1440,7 +2170,7 @@ export class StmtsEmitter {
|
|
|
1440
2170
|
let valLocalInfo = null;
|
|
1441
2171
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1442
2172
|
if (!isAssign) {
|
|
1443
|
-
const lIdx = this.
|
|
2173
|
+
const lIdx = this._allocRangeVar(lhs[1].name, "i32", {
|
|
1444
2174
|
kind: "basic",
|
|
1445
2175
|
name: "rune",
|
|
1446
2176
|
});
|
|
@@ -1519,6 +2249,7 @@ export class StmtsEmitter {
|
|
|
1519
2249
|
// Body
|
|
1520
2250
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1521
2251
|
if (bodyBlock) {
|
|
2252
|
+
this._emitRangeBoxes();
|
|
1522
2253
|
this.emitBlock(bodyBlock);
|
|
1523
2254
|
}
|
|
1524
2255
|
|
|
@@ -1596,7 +2327,11 @@ export class StmtsEmitter {
|
|
|
1596
2327
|
let keyLocalInfo = null;
|
|
1597
2328
|
if (lhs[0] && lhs[0].name !== "_") {
|
|
1598
2329
|
if (!isAssign) {
|
|
1599
|
-
const lIdx = this.
|
|
2330
|
+
const lIdx = this._allocRangeVar(
|
|
2331
|
+
lhs[0].name,
|
|
2332
|
+
mapInfo.keyWType,
|
|
2333
|
+
keyType,
|
|
2334
|
+
);
|
|
1600
2335
|
keyLocalInfo = { index: lIdx, type: mapInfo.keyWType };
|
|
1601
2336
|
} else {
|
|
1602
2337
|
keyLocalInfo =
|
|
@@ -1608,7 +2343,11 @@ export class StmtsEmitter {
|
|
|
1608
2343
|
let valLocalInfo = null;
|
|
1609
2344
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1610
2345
|
if (!isAssign) {
|
|
1611
|
-
const lIdx = this.
|
|
2346
|
+
const lIdx = this._allocRangeVar(
|
|
2347
|
+
lhs[1].name,
|
|
2348
|
+
mapInfo.valWType,
|
|
2349
|
+
valType,
|
|
2350
|
+
);
|
|
1612
2351
|
valLocalInfo = { index: lIdx, type: mapInfo.valWType };
|
|
1613
2352
|
} else {
|
|
1614
2353
|
valLocalInfo =
|
|
@@ -1707,6 +2446,11 @@ export class StmtsEmitter {
|
|
|
1707
2446
|
if (sInfo) {
|
|
1708
2447
|
this.emitCloneStruct(sInfo, mapInfo.valWType);
|
|
1709
2448
|
}
|
|
2449
|
+
} else if (
|
|
2450
|
+
valType?.kind !== "pointer" &&
|
|
2451
|
+
isArrayType(valType, this.mod.checker)
|
|
2452
|
+
) {
|
|
2453
|
+
this.emitCopyArray(valType);
|
|
1710
2454
|
}
|
|
1711
2455
|
if (this.resolveLocal(lhs[1].name)) {
|
|
1712
2456
|
this.pushInstruction({
|
|
@@ -1724,6 +2468,7 @@ export class StmtsEmitter {
|
|
|
1724
2468
|
// Body
|
|
1725
2469
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1726
2470
|
if (bodyBlock) {
|
|
2471
|
+
this._emitRangeBoxes();
|
|
1727
2472
|
this.emitBlock(bodyBlock);
|
|
1728
2473
|
}
|
|
1729
2474
|
|
|
@@ -1768,11 +2513,9 @@ export class StmtsEmitter {
|
|
|
1768
2513
|
iterType?.kind === "pointer" &&
|
|
1769
2514
|
isArrayType(iterType.base, this.mod.checker);
|
|
1770
2515
|
|
|
1771
|
-
const elemGoType =
|
|
1772
|
-
? iterType.
|
|
1773
|
-
|
|
1774
|
-
? iterType.base.elem
|
|
1775
|
-
: iterType.elem;
|
|
2516
|
+
const elemGoType = this._getArrayElemType(
|
|
2517
|
+
isPtrToArr ? iterType.base : iterType,
|
|
2518
|
+
);
|
|
1776
2519
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
1777
2520
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
1778
2521
|
|
|
@@ -1810,6 +2553,10 @@ export class StmtsEmitter {
|
|
|
1810
2553
|
this.releaseTemp(sliceTmp, sliceWType);
|
|
1811
2554
|
} else {
|
|
1812
2555
|
this.emitExpr(iterExpr, arrWType);
|
|
2556
|
+
// Go ranges over a copy of an array (the range expression is
|
|
2557
|
+
// evaluated once), so body writes to the array do not reach `v`.
|
|
2558
|
+
if (hasVal && !isPtrToArr)
|
|
2559
|
+
this._emitCopyIfValue(iterExpr, iterType, arrWType);
|
|
1813
2560
|
if (hasVal) {
|
|
1814
2561
|
this.pushInstruction({ op: "local.set", index: arrTmp });
|
|
1815
2562
|
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
@@ -1833,7 +2580,7 @@ export class StmtsEmitter {
|
|
|
1833
2580
|
!this.capturedNames?.includes(lhs[0].name) &&
|
|
1834
2581
|
!this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
|
|
1835
2582
|
const wType = isNonEscaping ? "i32" : "i64";
|
|
1836
|
-
const lIdx = this.
|
|
2583
|
+
const lIdx = this._allocRangeVar(lhs[0].name, wType, {
|
|
1837
2584
|
kind: "basic",
|
|
1838
2585
|
name: "int",
|
|
1839
2586
|
});
|
|
@@ -1847,7 +2594,7 @@ export class StmtsEmitter {
|
|
|
1847
2594
|
let valLocalInfo = null;
|
|
1848
2595
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1849
2596
|
if (!isAssign) {
|
|
1850
|
-
const lIdx = this.
|
|
2597
|
+
const lIdx = this._allocRangeVar(
|
|
1851
2598
|
lhs[1].name,
|
|
1852
2599
|
arrInfo.elemWType,
|
|
1853
2600
|
elemGoType,
|
|
@@ -1912,6 +2659,11 @@ export class StmtsEmitter {
|
|
|
1912
2659
|
if (sInfo) {
|
|
1913
2660
|
this.emitCloneStruct(sInfo, arrInfo.elemWType);
|
|
1914
2661
|
}
|
|
2662
|
+
} else if (
|
|
2663
|
+
elemGoType?.kind !== "pointer" &&
|
|
2664
|
+
isArrayType(elemGoType, this.mod.checker)
|
|
2665
|
+
) {
|
|
2666
|
+
this.emitCopyArray(elemGoType);
|
|
1915
2667
|
}
|
|
1916
2668
|
if (this.resolveLocal(lhs[1].name)) {
|
|
1917
2669
|
this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
|
|
@@ -1926,6 +2678,7 @@ export class StmtsEmitter {
|
|
|
1926
2678
|
// Body
|
|
1927
2679
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1928
2680
|
if (bodyBlock) {
|
|
2681
|
+
this._emitRangeBoxes();
|
|
1929
2682
|
this.emitBlock(bodyBlock);
|
|
1930
2683
|
}
|
|
1931
2684
|
|
|
@@ -1955,7 +2708,102 @@ export class StmtsEmitter {
|
|
|
1955
2708
|
restoreShadowed();
|
|
1956
2709
|
}
|
|
1957
2710
|
|
|
2711
|
+
_initIterState(stmt) {
|
|
2712
|
+
if (!stmt._iterStateName) return;
|
|
2713
|
+
let stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
2714
|
+
if (!stateLoc) {
|
|
2715
|
+
this.allocLocal(stmt._iterStateName, "i64", {
|
|
2716
|
+
kind: "basic",
|
|
2717
|
+
name: "int",
|
|
2718
|
+
});
|
|
2719
|
+
stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
2720
|
+
}
|
|
2721
|
+
if (stateLoc?.isBoxed) {
|
|
2722
|
+
this.pushInstruction({ op: "i64.const", value: 0n });
|
|
2723
|
+
this.pushInstruction({
|
|
2724
|
+
op: "struct.new",
|
|
2725
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
2726
|
+
});
|
|
2727
|
+
this.pushInstruction({ op: "local.set", index: stateLoc.index });
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
_emitIteratorRange(stmt, rangeExpr) {
|
|
2732
|
+
const funcLit = getOrCreateIteratorFuncLit(stmt);
|
|
2733
|
+
const iterExpr = rangeExpr.expr;
|
|
2734
|
+
const iterType = this._resolveExprGoType(iterExpr) ?? iterExpr._type;
|
|
2735
|
+
const iterInfo = this.mod.getClosureType(iterType);
|
|
2736
|
+
const iterWType = {
|
|
2737
|
+
kind: "ref",
|
|
2738
|
+
nullable: true,
|
|
2739
|
+
typeIndex: iterInfo.typeIndex,
|
|
2740
|
+
};
|
|
2741
|
+
|
|
2742
|
+
this._initIterState(stmt);
|
|
2743
|
+
|
|
2744
|
+
const iterTmp = this.acquireTemp(iterWType);
|
|
2745
|
+
this.emitExpr(iterExpr, iterWType);
|
|
2746
|
+
this.pushInstruction({ op: "local.set", index: iterTmp });
|
|
2747
|
+
|
|
2748
|
+
const yieldClosureInfo = this.mod.getClosureType(funcLit._type ?? funcLit);
|
|
2749
|
+
const yieldClosureWType = {
|
|
2750
|
+
kind: "ref",
|
|
2751
|
+
nullable: true,
|
|
2752
|
+
typeIndex: yieldClosureInfo.typeIndex,
|
|
2753
|
+
};
|
|
2754
|
+
const yieldTmp = this.acquireTemp(yieldClosureWType);
|
|
2755
|
+
this.emitFuncLit(funcLit);
|
|
2756
|
+
this.pushInstruction({ op: "local.set", index: yieldTmp });
|
|
2757
|
+
|
|
2758
|
+
// Outer block for break
|
|
2759
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2760
|
+
this.pushControl("break", stmt.label);
|
|
2761
|
+
|
|
2762
|
+
// Push env of iterTmp (param 0)
|
|
2763
|
+
this.pushInstruction({ op: "local.get", index: iterTmp });
|
|
2764
|
+
this.pushInstruction({
|
|
2765
|
+
op: "struct.get",
|
|
2766
|
+
typeIndex: iterInfo.typeIndex,
|
|
2767
|
+
fieldIndex: 1,
|
|
2768
|
+
});
|
|
2769
|
+
|
|
2770
|
+
// Push yield callback closure (param 1)
|
|
2771
|
+
this.pushInstruction({ op: "local.get", index: yieldTmp });
|
|
2772
|
+
|
|
2773
|
+
// Push funcref of iterTmp
|
|
2774
|
+
this.pushInstruction({ op: "local.get", index: iterTmp });
|
|
2775
|
+
this.pushInstruction({
|
|
2776
|
+
op: "struct.get",
|
|
2777
|
+
typeIndex: iterInfo.typeIndex,
|
|
2778
|
+
fieldIndex: 0,
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
// Call iterator via call_ref
|
|
2782
|
+
this.pushInstruction({
|
|
2783
|
+
op: "call_ref",
|
|
2784
|
+
typeIndex: iterInfo.funcTypeIndex,
|
|
2785
|
+
});
|
|
2786
|
+
|
|
2787
|
+
// If iterator returned any values, drop them
|
|
2788
|
+
for (let i = 0; i < (iterInfo.returnWTypes?.length ?? 0); i++) {
|
|
2789
|
+
this.pushInstruction("drop");
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
this.pushInstruction("end");
|
|
2793
|
+
|
|
2794
|
+
this.releaseTemp(yieldTmp, yieldClosureWType);
|
|
2795
|
+
this.releaseTemp(iterTmp, iterWType);
|
|
2796
|
+
|
|
2797
|
+
this._emitIteratorUnwind(stmt);
|
|
2798
|
+
}
|
|
2799
|
+
|
|
1958
2800
|
emitForStmt(stmt) {
|
|
2801
|
+
if (isIteratorRangeFor(stmt)) {
|
|
2802
|
+
const rangeExpr =
|
|
2803
|
+
stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
|
|
2804
|
+
this._emitIteratorRange(stmt, rangeExpr);
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
1959
2807
|
if (isRangeFor(stmt) || stmt.cond?.kind === "RangeExpr") {
|
|
1960
2808
|
this.emitRangeForStmt(stmt);
|
|
1961
2809
|
return;
|
|
@@ -2000,14 +2848,23 @@ export class StmtsEmitter {
|
|
|
2000
2848
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2001
2849
|
this.pushControl("continue", stmt.label);
|
|
2002
2850
|
|
|
2851
|
+
// The body is its own scope: `i := i` must not rebind the loop
|
|
2852
|
+
// variable that the post statement increments.
|
|
2003
2853
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
2004
2854
|
if (bodyBlock) {
|
|
2005
|
-
this.
|
|
2855
|
+
const outer = new Map(this.locals);
|
|
2856
|
+
try {
|
|
2857
|
+
this.emitBlock(bodyBlock);
|
|
2858
|
+
} finally {
|
|
2859
|
+
this.locals = outer;
|
|
2860
|
+
}
|
|
2006
2861
|
}
|
|
2007
2862
|
|
|
2008
2863
|
// End inner continue block
|
|
2009
2864
|
this.pushInstruction("end");
|
|
2010
2865
|
|
|
2866
|
+
this._emitPerIterationBoxes(stmt);
|
|
2867
|
+
|
|
2011
2868
|
if (iv) {
|
|
2012
2869
|
this.pushInstruction({ op: "local.get", index: ivLocalInfo.index });
|
|
2013
2870
|
this.pushInstruction({ op: "i32.const", value: iv.step });
|
|
@@ -2035,6 +2892,46 @@ export class StmtsEmitter {
|
|
|
2035
2892
|
}
|
|
2036
2893
|
}
|
|
2037
2894
|
|
|
2895
|
+
// Go 1.22 per-iteration loop variables. For each three-clause `for`
|
|
2896
|
+
// variable that a closure in this loop captures, give the next iteration a
|
|
2897
|
+
// fresh copy before the post statement: closures created in this
|
|
2898
|
+
// iteration keep their own variable, the post statement (and the next
|
|
2899
|
+
// iteration) work on the new one, which starts from the current value.
|
|
2900
|
+
// A boxed scalar gets a new box; a value struct or array (a ref) is also
|
|
2901
|
+
// cloned. Uncaptured variables stay plain locals with no allocation.
|
|
2902
|
+
_emitPerIterationBoxes(stmt) {
|
|
2903
|
+
for (const name of loopVarsCapturedInLoop(stmt)) {
|
|
2904
|
+
const info = this.locals.get(name);
|
|
2905
|
+
if (!info) continue;
|
|
2906
|
+
const goType = info.goType;
|
|
2907
|
+
const cloneValue = (wType) => {
|
|
2908
|
+
if (goType?.kind === "pointer") return;
|
|
2909
|
+
if (isArrayType(goType, this.mod.checker)) {
|
|
2910
|
+
this.emitCopyArray(goType);
|
|
2911
|
+
} else if (isStructType(goType, this.mod.checker, this.mod)) {
|
|
2912
|
+
const sInfo = this._resolveStructInfo({ _type: goType });
|
|
2913
|
+
if (sInfo) this.emitCloneStruct(sInfo, wType);
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
this.pushInstruction({ op: "local.get", index: info.index });
|
|
2917
|
+
if (info.isBoxed) {
|
|
2918
|
+
this.pushInstruction({
|
|
2919
|
+
op: "struct.get",
|
|
2920
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
2921
|
+
fieldIndex: 0,
|
|
2922
|
+
});
|
|
2923
|
+
cloneValue(info.boxInfo.wType);
|
|
2924
|
+
this.pushInstruction({
|
|
2925
|
+
op: "struct.new",
|
|
2926
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
2927
|
+
});
|
|
2928
|
+
} else {
|
|
2929
|
+
cloneValue(info.type);
|
|
2930
|
+
}
|
|
2931
|
+
this.pushInstruction({ op: "local.set", index: info.index });
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2038
2935
|
_detectInductionVar(stmt) {
|
|
2039
2936
|
if (stmt.init?.kind !== "DefineStmt") return null;
|
|
2040
2937
|
const lhs = stmt.init.lhs ?? [];
|
|
@@ -2193,7 +3090,91 @@ export class StmtsEmitter {
|
|
|
2193
3090
|
return false;
|
|
2194
3091
|
}
|
|
2195
3092
|
|
|
3093
|
+
// Each case clause is its own scope: a name declared in one clause must not
|
|
3094
|
+
// stay visible in the clauses after it.
|
|
3095
|
+
_emitClauseBody(stmts) {
|
|
3096
|
+
const outer = new Map(this.locals);
|
|
3097
|
+
try {
|
|
3098
|
+
for (const s of stmts ?? []) this.emitStmt(s);
|
|
3099
|
+
} finally {
|
|
3100
|
+
this.locals = outer;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// A switch with `fallthrough` dispatches to one block per clause, in source
|
|
3105
|
+
// order, so a clause that falls through simply runs into the next body.
|
|
3106
|
+
_emitFallthroughSwitch(stmt) {
|
|
3107
|
+
const getCaseVals = (c) => c.list ?? c.values ?? [];
|
|
3108
|
+
const cases = stmt.cases ?? [];
|
|
3109
|
+
const n = cases.length;
|
|
3110
|
+
const isFall = (s) =>
|
|
3111
|
+
s?.kind === "BranchStmt" && s.keyword === "fallthrough";
|
|
3112
|
+
|
|
3113
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3114
|
+
this.pushControl("break", stmt.label);
|
|
3115
|
+
if (stmt.init) this.emitStmt(stmt.init);
|
|
3116
|
+
|
|
3117
|
+
let tagGoType = null;
|
|
3118
|
+
let tagWType = null;
|
|
3119
|
+
let tagTmp = -1;
|
|
3120
|
+
if (stmt.tag) {
|
|
3121
|
+
tagGoType = stmt.tag._type ?? this._resolveExprGoType(stmt.tag);
|
|
3122
|
+
tagWType = this.toWasmType(tagGoType);
|
|
3123
|
+
tagTmp = this.acquireTemp(tagWType);
|
|
3124
|
+
this.emitExpr(stmt.tag, tagWType);
|
|
3125
|
+
this.pushInstruction({ op: "local.set", index: tagTmp });
|
|
3126
|
+
}
|
|
3127
|
+
for (let i = 0; i < n; i++) {
|
|
3128
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3129
|
+
}
|
|
3130
|
+
let defaultIdx = -1;
|
|
3131
|
+
for (let i = 0; i < n; i++) {
|
|
3132
|
+
const vals = getCaseVals(cases[i]);
|
|
3133
|
+
if (vals.length === 0) {
|
|
3134
|
+
defaultIdx = i;
|
|
3135
|
+
continue;
|
|
3136
|
+
}
|
|
3137
|
+
for (let j = 0; j < vals.length; j++) {
|
|
3138
|
+
if (stmt.tag) {
|
|
3139
|
+
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
3140
|
+
this.emitExpr(vals[j], tagWType);
|
|
3141
|
+
this.emitBinaryOp("==", tagWType, tagGoType);
|
|
3142
|
+
} else {
|
|
3143
|
+
this.emitExpr(vals[j], "i32");
|
|
3144
|
+
}
|
|
3145
|
+
if (j > 0) this.pushInstruction("i32.or");
|
|
3146
|
+
}
|
|
3147
|
+
// The innermost block ends the first clause, so clause i is depth i.
|
|
3148
|
+
this.pushInstruction({ op: "br_if", depth: i });
|
|
3149
|
+
}
|
|
3150
|
+
this.pushInstruction({ op: "br", depth: defaultIdx >= 0 ? defaultIdx : n });
|
|
3151
|
+
if (stmt.tag) this.releaseTemp(tagTmp, tagWType);
|
|
3152
|
+
|
|
3153
|
+
for (let i = 0; i < n; i++) {
|
|
3154
|
+
this.pushInstruction("end");
|
|
3155
|
+
const body = cases[i].stmts ?? [];
|
|
3156
|
+
const falls = isFall(body[body.length - 1]);
|
|
3157
|
+
this._emitClauseBody(falls ? body.slice(0, -1) : body);
|
|
3158
|
+
if (!falls && i < n - 1) {
|
|
3159
|
+
this.pushInstruction({ op: "br", depth: n - 1 - i });
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
this.pushInstruction("end");
|
|
3163
|
+
}
|
|
3164
|
+
|
|
2196
3165
|
emitSwitchStmt(stmt) {
|
|
3166
|
+
if (
|
|
3167
|
+
(stmt.cases ?? []).some((c) => {
|
|
3168
|
+
const b = c.stmts ?? [];
|
|
3169
|
+
return (
|
|
3170
|
+
b[b.length - 1]?.kind === "BranchStmt" &&
|
|
3171
|
+
b[b.length - 1].keyword === "fallthrough"
|
|
3172
|
+
);
|
|
3173
|
+
})
|
|
3174
|
+
) {
|
|
3175
|
+
this._emitFallthroughSwitch(stmt);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
2197
3178
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2198
3179
|
this.pushControl("break", stmt.label);
|
|
2199
3180
|
|
|
@@ -2234,12 +3215,12 @@ export class StmtsEmitter {
|
|
|
2234
3215
|
this.pushInstruction({ op: "if", blockType: "void" });
|
|
2235
3216
|
ifCount++;
|
|
2236
3217
|
|
|
2237
|
-
|
|
3218
|
+
this._emitClauseBody(c.stmts);
|
|
2238
3219
|
this.pushInstruction("else");
|
|
2239
3220
|
}
|
|
2240
3221
|
|
|
2241
3222
|
if (defaultCase) {
|
|
2242
|
-
|
|
3223
|
+
this._emitClauseBody(defaultCase.stmts);
|
|
2243
3224
|
}
|
|
2244
3225
|
|
|
2245
3226
|
for (let i = 0; i < ifCount; i++) {
|
|
@@ -2285,8 +3266,13 @@ export class StmtsEmitter {
|
|
|
2285
3266
|
ifCount++;
|
|
2286
3267
|
|
|
2287
3268
|
if (stmt.assign) {
|
|
2288
|
-
|
|
2289
|
-
|
|
3269
|
+
// `case nil` binds the switch expression's own (null) value.
|
|
3270
|
+
const isNilCase =
|
|
3271
|
+
types.length === 1 &&
|
|
3272
|
+
(types[0].name === "nil" || types[0]._type?.name === "nil");
|
|
3273
|
+
const targetGoType = isNilCase
|
|
3274
|
+
? (stmt.expr._type ?? { kind: "basic", name: "any" })
|
|
3275
|
+
: types.length === 1
|
|
2290
3276
|
? (types[0]._type ?? types[0])
|
|
2291
3277
|
: { kind: "basic", name: "any" };
|
|
2292
3278
|
const targetWType = this.toWasmType(targetGoType);
|
|
@@ -2295,18 +3281,22 @@ export class StmtsEmitter {
|
|
|
2295
3281
|
targetWType,
|
|
2296
3282
|
targetGoType,
|
|
2297
3283
|
);
|
|
2298
|
-
if (types.length === 1) {
|
|
3284
|
+
if (types.length === 1 && !isNilCase) {
|
|
2299
3285
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
2300
3286
|
this._emitTypeCast(targetGoType, targetWType);
|
|
3287
|
+
// The bound array is a copy of the boxed value.
|
|
3288
|
+
if (
|
|
3289
|
+
targetGoType?.kind !== "pointer" &&
|
|
3290
|
+
isArrayType(targetGoType, this.mod.checker)
|
|
3291
|
+
)
|
|
3292
|
+
this.emitCopyArray(targetGoType);
|
|
2301
3293
|
} else {
|
|
2302
3294
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
2303
3295
|
}
|
|
2304
3296
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
2305
3297
|
}
|
|
2306
3298
|
|
|
2307
|
-
|
|
2308
|
-
this.emitStmt(s);
|
|
2309
|
-
}
|
|
3299
|
+
this._emitClauseBody(c.stmts);
|
|
2310
3300
|
|
|
2311
3301
|
this.pushInstruction("else");
|
|
2312
3302
|
}
|
|
@@ -2320,9 +3310,7 @@ export class StmtsEmitter {
|
|
|
2320
3310
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
2321
3311
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
2322
3312
|
}
|
|
2323
|
-
|
|
2324
|
-
this.emitStmt(s);
|
|
2325
|
-
}
|
|
3313
|
+
this._emitClauseBody(defaultCase.stmts);
|
|
2326
3314
|
}
|
|
2327
3315
|
|
|
2328
3316
|
for (let i = 0; i < ifCount; i++) {
|
|
@@ -2340,13 +3328,199 @@ export class StmtsEmitter {
|
|
|
2340
3328
|
this.pushInstruction("end");
|
|
2341
3329
|
}
|
|
2342
3330
|
|
|
3331
|
+
_propagateYieldExit(tag) {
|
|
3332
|
+
if (this.funcDecl._iterStateName) {
|
|
3333
|
+
const outerLoc = this.resolveLocal(this.funcDecl._iterStateName);
|
|
3334
|
+
if (outerLoc?.isBoxed) {
|
|
3335
|
+
this.pushInstruction({ op: "local.get", index: outerLoc.index });
|
|
3336
|
+
this.pushInstruction({ op: "i64.const", value: tag });
|
|
3337
|
+
this.pushInstruction({
|
|
3338
|
+
op: "struct.set",
|
|
3339
|
+
typeIndex: outerLoc.boxInfo.typeIndex,
|
|
3340
|
+
fieldIndex: 0,
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3345
|
+
this.pushInstruction({ op: "return" });
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
_unwindIteratorReturn() {
|
|
3349
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3350
|
+
this._propagateYieldExit(2n);
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
if (this.hasDefer) {
|
|
3354
|
+
const depth = this.resolveBranchDepthToRole("runDefers");
|
|
3355
|
+
this.pushInstruction({ op: "br", depth });
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const rootNamed = this.funcDecl.returnType?._namedReturns ?? [];
|
|
3359
|
+
for (const nr of rootNamed) {
|
|
3360
|
+
const loc = this.locals.get(nr.name);
|
|
3361
|
+
if (loc?.isBoxed) {
|
|
3362
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
3363
|
+
this.pushInstruction({
|
|
3364
|
+
op: "struct.get",
|
|
3365
|
+
typeIndex: loc.boxInfo.typeIndex,
|
|
3366
|
+
fieldIndex: 0,
|
|
3367
|
+
});
|
|
3368
|
+
} else if (loc) {
|
|
3369
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
this.pushInstruction({ op: "return" });
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
_unwindIteratorTargets(stmt, stateTmp, labelTargets) {
|
|
3376
|
+
for (const target of labelTargets) {
|
|
3377
|
+
this.pushInstruction({ op: "local.get", index: stateTmp });
|
|
3378
|
+
this.pushInstruction({ op: "i64.const", value: BigInt(target.tag) });
|
|
3379
|
+
this.pushInstruction("i64.eq");
|
|
3380
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3381
|
+
if (target.role !== "break" || stmt.label !== target.label) {
|
|
3382
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3383
|
+
this._propagateYieldExit(BigInt(target.tag));
|
|
3384
|
+
} else {
|
|
3385
|
+
const depth = this.resolveBranchDepth(
|
|
3386
|
+
target.label,
|
|
3387
|
+
target.role === "continue",
|
|
3388
|
+
);
|
|
3389
|
+
this.pushInstruction({ op: "br", depth });
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
this.pushInstruction("end");
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
_emitIteratorUnwind(stmt) {
|
|
3397
|
+
if (!stmt._iterStateName) return;
|
|
3398
|
+
const stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
3399
|
+
if (!stateLoc?.isBoxed) return;
|
|
3400
|
+
|
|
3401
|
+
const funcLit = getOrCreateIteratorFuncLit(stmt);
|
|
3402
|
+
const hasRet = hasReturnInBlock(funcLit?.body);
|
|
3403
|
+
const labelTargets = Array.from(
|
|
3404
|
+
this.rootFuncDecl?._labelTargets?.values() ?? [],
|
|
3405
|
+
);
|
|
3406
|
+
if (!hasRet && labelTargets.length === 0) return;
|
|
3407
|
+
|
|
3408
|
+
const stateTmp = this.acquireTemp("i64");
|
|
3409
|
+
this.pushInstruction({ op: "local.get", index: stateLoc.index });
|
|
3410
|
+
this.pushInstruction({
|
|
3411
|
+
op: "struct.get",
|
|
3412
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
3413
|
+
fieldIndex: 0,
|
|
3414
|
+
});
|
|
3415
|
+
this.pushInstruction({ op: "local.set", index: stateTmp });
|
|
3416
|
+
|
|
3417
|
+
if (hasRet) {
|
|
3418
|
+
this.pushInstruction({ op: "local.get", index: stateTmp });
|
|
3419
|
+
this.pushInstruction({ op: "i64.const", value: 2n });
|
|
3420
|
+
this.pushInstruction("i64.eq");
|
|
3421
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3422
|
+
this._unwindIteratorReturn();
|
|
3423
|
+
this.pushInstruction("end");
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
this._unwindIteratorTargets(stmt, stateTmp, labelTargets);
|
|
3427
|
+
this.releaseTemp(stateTmp, "i64");
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
_tryEmitYieldBranch(stmt) {
|
|
3431
|
+
if (!this.funcDecl._isYieldClosure) return false;
|
|
3432
|
+
const isContinue = stmt.keyword === "continue";
|
|
3433
|
+
const targetRole = isContinue ? "continue" : "break";
|
|
3434
|
+
const hasInnerTarget = this.controlStack.some((c) =>
|
|
3435
|
+
stmt.label
|
|
3436
|
+
? c.label === stmt.label && c.role === targetRole
|
|
3437
|
+
: c.role === targetRole,
|
|
3438
|
+
);
|
|
3439
|
+
if (hasInnerTarget) return false;
|
|
3440
|
+
|
|
3441
|
+
if (!stmt.label) {
|
|
3442
|
+
if (stmt.keyword === "break") {
|
|
3443
|
+
this._propagateYieldExit(1n);
|
|
3444
|
+
return true;
|
|
3445
|
+
}
|
|
3446
|
+
if (stmt.keyword === "continue") {
|
|
3447
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3448
|
+
this.pushInstruction({ op: "return" });
|
|
3449
|
+
return true;
|
|
3450
|
+
}
|
|
3451
|
+
return false;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
if (
|
|
3455
|
+
stmt.keyword === "continue" &&
|
|
3456
|
+
stmt.label === this.funcDecl._forStmt?.label
|
|
3457
|
+
) {
|
|
3458
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3459
|
+
this.pushInstruction({ op: "return" });
|
|
3460
|
+
return true;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
const target = this._findLabelTarget(stmt.label, targetRole);
|
|
3464
|
+
const tag = target ? BigInt(target.tag) : 1n;
|
|
3465
|
+
this._propagateYieldExit(tag);
|
|
3466
|
+
return true;
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
_emitYieldReturn(stmt) {
|
|
3470
|
+
const values = stmt.values ?? [];
|
|
3471
|
+
const rootNamed = this.rootFuncDecl?.returnType?._namedReturns ?? [];
|
|
3472
|
+
if (values.length === 1 && rootNamed.length > 1) {
|
|
3473
|
+
this.emitExpr(values[0]);
|
|
3474
|
+
for (let i = rootNamed.length - 1; i >= 0; i--) {
|
|
3475
|
+
this._assignToNamedReturnVar(rootNamed[i]);
|
|
3476
|
+
}
|
|
3477
|
+
} else if (values.length > 0 && rootNamed.length > 0) {
|
|
3478
|
+
const temps = [];
|
|
3479
|
+
for (let i = 0; i < values.length; i++) {
|
|
3480
|
+
const val = values[i];
|
|
3481
|
+
const nr = rootNamed[i];
|
|
3482
|
+
const targetWType = this.toWasmType(nr?.type ?? val._type);
|
|
3483
|
+
this.emitExpr(val, targetWType);
|
|
3484
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
3485
|
+
const tmp = this.acquireTemp(targetWType);
|
|
3486
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
3487
|
+
temps.push({ tmp, type: targetWType });
|
|
3488
|
+
}
|
|
3489
|
+
for (let i = 0; i < temps.length; i++) {
|
|
3490
|
+
this.pushInstruction({ op: "local.get", index: temps[i].tmp });
|
|
3491
|
+
this._assignToNamedReturnVar(rootNamed[i]);
|
|
3492
|
+
this.releaseTemp(temps[i].tmp, temps[i].type);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
if (this.funcDecl._iterStateName) {
|
|
3497
|
+
const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
|
|
3498
|
+
if (stateLoc?.isBoxed) {
|
|
3499
|
+
this.pushInstruction({ op: "local.get", index: stateLoc.index });
|
|
3500
|
+
this.pushInstruction({ op: "i64.const", value: 2n });
|
|
3501
|
+
this.pushInstruction({
|
|
3502
|
+
op: "struct.set",
|
|
3503
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
3504
|
+
fieldIndex: 0,
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3509
|
+
this.pushInstruction({ op: "return" });
|
|
3510
|
+
}
|
|
3511
|
+
|
|
2343
3512
|
emitBranchStmt(stmt) {
|
|
3513
|
+
if (this._tryEmitYieldBranch(stmt)) return;
|
|
2344
3514
|
const isContinue = stmt.keyword === "continue";
|
|
2345
3515
|
const depth = this.resolveBranchDepth(stmt.label, isContinue);
|
|
2346
3516
|
this.pushInstruction({ op: "br", depth });
|
|
2347
3517
|
}
|
|
2348
3518
|
|
|
2349
3519
|
emitReturnStmt(stmt) {
|
|
3520
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3521
|
+
this._emitYieldReturn(stmt);
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
2350
3524
|
const values = stmt.values ?? [];
|
|
2351
3525
|
if (this.hasDefer) {
|
|
2352
3526
|
if (this.namedReturnVars?.length > 0) {
|
|
@@ -2365,7 +3539,7 @@ export class StmtsEmitter {
|
|
|
2365
3539
|
const val = values[i];
|
|
2366
3540
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2367
3541
|
this.emitExpr(val, targetWType);
|
|
2368
|
-
this.
|
|
3542
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2369
3543
|
const tmp = this.acquireTemp(targetWType);
|
|
2370
3544
|
this.pushInstruction({ op: "local.set", index: tmp });
|
|
2371
3545
|
temps.push({ tmp, type: targetWType });
|
|
@@ -2390,7 +3564,7 @@ export class StmtsEmitter {
|
|
|
2390
3564
|
const val = values[i];
|
|
2391
3565
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2392
3566
|
this.emitExpr(val, targetWType);
|
|
2393
|
-
this.
|
|
3567
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2394
3568
|
this.pushInstruction({
|
|
2395
3569
|
op: "local.set",
|
|
2396
3570
|
index: this.returnTempLocals[i],
|
|
@@ -2421,17 +3595,38 @@ export class StmtsEmitter {
|
|
|
2421
3595
|
return;
|
|
2422
3596
|
}
|
|
2423
3597
|
|
|
3598
|
+
if (values.length === 1 && this.returnTypes.length > 1) {
|
|
3599
|
+
this.emitExpr(values[0]);
|
|
3600
|
+
this.pushInstruction("return");
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
2424
3604
|
for (let i = 0; i < values.length; i++) {
|
|
2425
3605
|
const val = values[i];
|
|
2426
3606
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2427
3607
|
this.emitExpr(val, targetWType);
|
|
2428
|
-
this.
|
|
3608
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2429
3609
|
}
|
|
2430
3610
|
this.pushInstruction("return");
|
|
2431
3611
|
}
|
|
2432
3612
|
|
|
3613
|
+
// A value returned as an interface was copied when it was boxed; copying it
|
|
3614
|
+
// again by its own type would treat the box as the struct.
|
|
3615
|
+
_emitCopyReturnValue(val, targetWType) {
|
|
3616
|
+
if (targetWType === "anyref") return;
|
|
3617
|
+
this._emitCopyIfValue(val, val._type, targetWType);
|
|
3618
|
+
}
|
|
3619
|
+
|
|
2433
3620
|
_assignToNamedReturnVar(nr) {
|
|
3621
|
+
if (!nr?.name || nr.name === "_") {
|
|
3622
|
+
this.pushInstruction("drop");
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
2434
3625
|
const loc = this.locals.get(nr.name);
|
|
3626
|
+
if (!loc) {
|
|
3627
|
+
this.pushInstruction("drop");
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
2435
3630
|
if (loc.isBoxed) {
|
|
2436
3631
|
const valTmp = this.acquireTemp(loc.boxInfo.wType);
|
|
2437
3632
|
this.pushInstruction({ op: "local.set", index: valTmp });
|