gofront 1.7.0 → 1.9.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /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
|
@@ -1,20 +1,60 @@
|
|
|
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 { hasCallOrReceive, 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,
|
|
27
|
+
isTaggedSliceType,
|
|
16
28
|
} from "./types.js";
|
|
17
29
|
|
|
30
|
+
function hasReturnInBlock(n) {
|
|
31
|
+
let found = false;
|
|
32
|
+
const check = (x) => {
|
|
33
|
+
if (!x || typeof x !== "object" || found) return;
|
|
34
|
+
if (Array.isArray(x)) {
|
|
35
|
+
for (const elem of x) check(elem);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (x.kind === "ReturnStmt") {
|
|
39
|
+
found = true;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (x.kind === "FuncLit" && !x._isYieldClosure) return;
|
|
43
|
+
for (const k of Object.keys(x)) {
|
|
44
|
+
if (k.startsWith("_")) continue;
|
|
45
|
+
check(x[k]);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
check(n);
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A plain `string`: the one string type that lives in `any` as the bare
|
|
53
|
+
// string. A named string type is boxed like any other scalar (see emitExpr).
|
|
54
|
+
function isBareStringType(t, checker) {
|
|
55
|
+
return isStringType(t, checker) && t.kind !== "named";
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
export class StmtsEmitter {
|
|
19
59
|
emitVarDecl(stmt) {
|
|
20
60
|
const decls = stmt.decls ?? stmt.specs ?? [stmt];
|
|
@@ -22,6 +62,15 @@ export class StmtsEmitter {
|
|
|
22
62
|
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
23
63
|
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
24
64
|
|
|
65
|
+
// `var a, b = f()` / `var v, ok = m[k]` bind like `a, b := ...`.
|
|
66
|
+
if (names.length > 1 && values.length === 1) {
|
|
67
|
+
this.emitDefineStmt({
|
|
68
|
+
lhs: names.map((name) => ({ kind: "Ident", name })),
|
|
69
|
+
rhs: values,
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
25
74
|
for (let i = 0; i < names.length; i++) {
|
|
26
75
|
const name = names[i];
|
|
27
76
|
const rawType = spec.type ?? values[i]?._type;
|
|
@@ -31,7 +80,7 @@ export class StmtsEmitter {
|
|
|
31
80
|
if (values?.[i]) {
|
|
32
81
|
const r = values[i];
|
|
33
82
|
this.emitExpr(r, wType);
|
|
34
|
-
this.
|
|
83
|
+
this._emitCopyIfValue(r, rawType, wType);
|
|
35
84
|
if (localInfo?.isBoxed) {
|
|
36
85
|
this.pushInstruction({
|
|
37
86
|
op: "struct.new",
|
|
@@ -88,9 +137,11 @@ export class StmtsEmitter {
|
|
|
88
137
|
return;
|
|
89
138
|
}
|
|
90
139
|
|
|
140
|
+
const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes, true);
|
|
91
141
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
92
|
-
this._emitDefineSingle(lhsNodes[i],
|
|
142
|
+
this._emitDefineSingle(lhsNodes[i], rhsVals[i]);
|
|
93
143
|
}
|
|
144
|
+
this._releaseParallelTemps(rhsVals, rhsNodes);
|
|
94
145
|
}
|
|
95
146
|
|
|
96
147
|
_emitDefineSingle(l, r) {
|
|
@@ -100,11 +151,17 @@ export class StmtsEmitter {
|
|
|
100
151
|
this.pushInstruction("drop");
|
|
101
152
|
return;
|
|
102
153
|
}
|
|
154
|
+
if (l._redecl && this.resolveLocal(l.name)) {
|
|
155
|
+
this._emitAssignSingle(l, r, "=");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
103
158
|
const wType = this.toWasmType(r._type);
|
|
159
|
+
// The value is computed before the new name exists: `x := x * 2`
|
|
160
|
+
// in an inner block reads the outer `x`.
|
|
161
|
+
this.emitExpr(r, wType);
|
|
162
|
+
this._emitCopyIfValue(r, r._type, wType);
|
|
104
163
|
const idx = this.allocLocal(l.name, wType, r._type);
|
|
105
164
|
const localInfo = this.locals.get(l.name);
|
|
106
|
-
this.emitExpr(r, wType);
|
|
107
|
-
this._emitCopyIfValueStruct(r, r._type, wType);
|
|
108
165
|
if (localInfo?.isBoxed) {
|
|
109
166
|
this.pushInstruction({
|
|
110
167
|
op: "struct.new",
|
|
@@ -114,7 +171,38 @@ export class StmtsEmitter {
|
|
|
114
171
|
this.pushInstruction({ op: "local.set", index: idx });
|
|
115
172
|
}
|
|
116
173
|
|
|
174
|
+
// Stores a multi-value call into temporaries (last result first), then
|
|
175
|
+
// assigns each one to its target. Used when a target is not a plain new
|
|
176
|
+
// local: an index, field or dereference, or a name redeclared by `:=`.
|
|
177
|
+
_emitTupleViaTemps(lhsNodes, rhsNode, assignOne, hoist) {
|
|
178
|
+
const tupleTypes = rhsNode._type.types;
|
|
179
|
+
this.emitExpr(rhsNode);
|
|
180
|
+
const temps = [];
|
|
181
|
+
for (let i = lhsNodes.length - 1; i >= 0; i--) {
|
|
182
|
+
const goType = tupleTypes[i];
|
|
183
|
+
const wType = this.toWasmType(goType);
|
|
184
|
+
const name = `__tup_tmp_${this.tempCounter++}`;
|
|
185
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
186
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
187
|
+
temps[i] = { kind: "Ident", name, _type: goType, _parTmp: true };
|
|
188
|
+
}
|
|
189
|
+
const undo = hoist ? this._hoistLhsOperands(lhsNodes) : null;
|
|
190
|
+
for (let i = 0; i < lhsNodes.length; i++) assignOne(lhsNodes[i], temps[i]);
|
|
191
|
+
undo?.();
|
|
192
|
+
for (const t of temps) this.locals.delete(t.name);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_isPlainTupleTarget(l) {
|
|
196
|
+
return l.kind === "Ident";
|
|
197
|
+
}
|
|
198
|
+
|
|
117
199
|
_emitTupleDefine(lhsNodes, rhsNode) {
|
|
200
|
+
if (lhsNodes.some((l) => !this._isPlainTupleTarget(l) || l._redecl)) {
|
|
201
|
+
this._emitTupleViaTemps(lhsNodes, rhsNode, (l, t) =>
|
|
202
|
+
this._emitDefineSingle(l, t),
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
118
206
|
const tupleTypes = rhsNode._type.types;
|
|
119
207
|
const localIndices = [];
|
|
120
208
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
@@ -200,9 +288,192 @@ export class StmtsEmitter {
|
|
|
200
288
|
return;
|
|
201
289
|
}
|
|
202
290
|
|
|
291
|
+
const hoisted = op === "=" ? this._hoistLhsOperands(lhsNodes) : null;
|
|
292
|
+
const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes);
|
|
203
293
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
204
|
-
this._emitAssignSingle(lhsNodes[i],
|
|
294
|
+
this._emitAssignSingle(lhsNodes[i], rhsVals[i], op);
|
|
205
295
|
}
|
|
296
|
+
this._releaseParallelTemps(rhsVals, rhsNodes);
|
|
297
|
+
hoisted?.();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
|
|
301
|
+
// target before any assignment. An operand that mentions a variable
|
|
302
|
+
// assigned by the same statement is evaluated into a temporary first.
|
|
303
|
+
// Returns a function that restores the AST and releases the temporaries.
|
|
304
|
+
_hoistLhsOperands(lhsNodes) {
|
|
305
|
+
if (lhsNodes.length < 2) return null;
|
|
306
|
+
const assigned = new Set(
|
|
307
|
+
lhsNodes
|
|
308
|
+
.filter((l) => l.kind === "Ident" && l.name !== "_")
|
|
309
|
+
.map((l) => l.name),
|
|
310
|
+
);
|
|
311
|
+
if (assigned.size === 0) return null;
|
|
312
|
+
const undo = [];
|
|
313
|
+
const names = [];
|
|
314
|
+
const visit = (l) => {
|
|
315
|
+
let keys;
|
|
316
|
+
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
317
|
+
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
318
|
+
else return;
|
|
319
|
+
for (const key of keys) {
|
|
320
|
+
const operand = l[key];
|
|
321
|
+
if (!this._mentions(operand, assigned)) continue;
|
|
322
|
+
const goType = operand._type ?? this._resolveExprGoType(operand);
|
|
323
|
+
// A struct or array value names the variable's storage in Go: hoist
|
|
324
|
+
// the operands inside it (`ps[n].A`) instead of the value.
|
|
325
|
+
if (
|
|
326
|
+
key === "expr" &&
|
|
327
|
+
(this._isValueStruct(goType) || this._isValueArray(goType))
|
|
328
|
+
) {
|
|
329
|
+
visit(operand);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const wType = this.toWasmType(goType);
|
|
333
|
+
const name = `__lhs_tmp_${this.tempCounter++}`;
|
|
334
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
335
|
+
this.emitExpr(operand, wType);
|
|
336
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
337
|
+
undo.push([l, key, operand]);
|
|
338
|
+
names.push(name);
|
|
339
|
+
l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
for (const l of lhsNodes) visit(l);
|
|
343
|
+
if (undo.length === 0) return null;
|
|
344
|
+
return () => {
|
|
345
|
+
for (const [node, key, v] of undo) node[key] = v;
|
|
346
|
+
for (const n of names) this.locals.delete(n);
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// `x, y = y, x`: Go evaluates every right-hand side before assigning.
|
|
351
|
+
// When a later value could see an earlier assignment, every value that
|
|
352
|
+
// is not pure is stored in a temporary first (in source order, so side
|
|
353
|
+
// effects keep their order). Pure values stay inline, so `i, n = 0,
|
|
354
|
+
// len(xs)` is unchanged. A definition only exposes its redeclared
|
|
355
|
+
// names: a new name is not visible to the values being assigned to it.
|
|
356
|
+
_parallelAssignValues(lhsNodes, rhsNodes, define = false) {
|
|
357
|
+
if (lhsNodes.length < 2 || rhsNodes.length !== lhsNodes.length)
|
|
358
|
+
return rhsNodes;
|
|
359
|
+
const roots = new Set(
|
|
360
|
+
lhsNodes.map((l) => rootIdentName(l)).filter((n) => n && n !== "_"),
|
|
361
|
+
);
|
|
362
|
+
const pure = rhsNodes.map((r) => this._independentOf(r, roots));
|
|
363
|
+
const redecl = lhsNodes.some((l) => l._redecl);
|
|
364
|
+
const observes = (r, i) =>
|
|
365
|
+
define ? this._mentions(r, roots) || (redecl && !pure[i]) : !pure[i];
|
|
366
|
+
if (!rhsNodes.some((r, i) => i > 0 && observes(r, i))) return rhsNodes;
|
|
367
|
+
const inPlace = lhsNodes.some(
|
|
368
|
+
(l) => this._isInPlaceArrayStore(l) || this._isStableTarget(l),
|
|
369
|
+
);
|
|
370
|
+
return rhsNodes.map((r, i) => {
|
|
371
|
+
if (pure[i]) return r;
|
|
372
|
+
const goType = r._type ?? this._resolveExprGoType(r);
|
|
373
|
+
const wType = this.toWasmType(goType);
|
|
374
|
+
const name = `__par_tmp_${this.tempCounter++}`;
|
|
375
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
376
|
+
this.emitExpr(r, wType);
|
|
377
|
+
// An array temp must snapshot the value when a target is stored in
|
|
378
|
+
// place (`a, b = b, a` with `p := &a`): the store would otherwise
|
|
379
|
+
// change what the temp holds. The snapshot is already a fresh
|
|
380
|
+
// copy, so assigning from the temp does not copy it again.
|
|
381
|
+
const isArr = this._isValueArray(goType);
|
|
382
|
+
const snapshot = inPlace && (isArr || this._isValueStruct(goType));
|
|
383
|
+
if (snapshot) this._emitCopyIfValue(r, goType, wType);
|
|
384
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
385
|
+
return {
|
|
386
|
+
kind: "Ident",
|
|
387
|
+
name,
|
|
388
|
+
_type: goType,
|
|
389
|
+
_parTmp: true,
|
|
390
|
+
_freshArray: snapshot && isArr,
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// True when assigning to `l` writes into an existing array object (so an
|
|
396
|
+
// earlier-read array value sharing that object would see the write): a
|
|
397
|
+
// boxed (address-taken) array local, `*p` on an array pointer, or an
|
|
398
|
+
// element of an array.
|
|
399
|
+
_isInPlaceArrayStore(l) {
|
|
400
|
+
if (l.kind === "Ident") {
|
|
401
|
+
const info = this.resolveLocal(l.name);
|
|
402
|
+
return !!info?.isBoxed && this._isValueArray(info.goType);
|
|
403
|
+
}
|
|
404
|
+
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
405
|
+
const t = (l.operand ?? l.expr)?._type;
|
|
406
|
+
return this._isValueArray(t?.base ?? t);
|
|
407
|
+
}
|
|
408
|
+
if (l.kind === "IndexExpr") {
|
|
409
|
+
const t = l.expr._type ?? this._resolveExprGoType(l.expr);
|
|
410
|
+
return (
|
|
411
|
+
this._isValueArray(t) ||
|
|
412
|
+
(t?.kind === "pointer" && this._isValueArray(t.base))
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// True when `l` is an array or struct target that a whole-value
|
|
419
|
+
// assignment writes in place (see `_needsStableStore`).
|
|
420
|
+
_isStableTarget(l) {
|
|
421
|
+
const t = l._type ?? this._resolveExprGoType(l);
|
|
422
|
+
return this._needsStableStore(l, t);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
_mentions(node, names) {
|
|
426
|
+
if (!node || typeof node !== "object") return false;
|
|
427
|
+
if (node.kind === "Ident" && names.has(node.name)) return true;
|
|
428
|
+
return someChild(node, (child) => this._mentions(child, names));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
_releaseParallelTemps(rhsVals, rhsNodes) {
|
|
432
|
+
if (rhsVals === rhsNodes) return;
|
|
433
|
+
for (const r of rhsVals) if (r?._parTmp) this.locals.delete(r.name);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// True when evaluating `node` cannot observe an assignment to `roots`
|
|
437
|
+
// (or through a pointer) and has no side effects.
|
|
438
|
+
_independentOf(node, roots) {
|
|
439
|
+
if (!node || typeof node !== "object") return true;
|
|
440
|
+
switch (node.kind) {
|
|
441
|
+
case "BasicLit":
|
|
442
|
+
return true;
|
|
443
|
+
case "Ident":
|
|
444
|
+
return this._independentIdent(node, roots);
|
|
445
|
+
case "BinaryExpr":
|
|
446
|
+
return (
|
|
447
|
+
this._independentOf(node.left, roots) &&
|
|
448
|
+
this._independentOf(node.right, roots)
|
|
449
|
+
);
|
|
450
|
+
case "UnaryExpr":
|
|
451
|
+
return (
|
|
452
|
+
node.op !== "*" &&
|
|
453
|
+
node.op !== "&" &&
|
|
454
|
+
this._independentOf(node.operand, roots)
|
|
455
|
+
);
|
|
456
|
+
case "CallExpr":
|
|
457
|
+
return this._isPureLenCall(node, roots);
|
|
458
|
+
default:
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
_independentIdent(node, roots) {
|
|
464
|
+
if (node._const || node.name === "nil") return true;
|
|
465
|
+
if (roots.has(node.name)) return false;
|
|
466
|
+
const local = this.resolveLocal(node.name);
|
|
467
|
+
return !!local && !local.isBoxed;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// `len`/`cap` of a pure operand.
|
|
471
|
+
_isPureLenCall(node, roots) {
|
|
472
|
+
const fn = node.func;
|
|
473
|
+
if (fn?.kind !== "Ident" || (fn.name !== "len" && fn.name !== "cap"))
|
|
474
|
+
return false;
|
|
475
|
+
if (this.resolveLocal(fn.name) || node.args?.length !== 1) return false;
|
|
476
|
+
return this._independentOf(node.args[0], roots);
|
|
206
477
|
}
|
|
207
478
|
|
|
208
479
|
_emitAssignToTarget(target, wasmType, goType, emitValue) {
|
|
@@ -244,10 +515,48 @@ export class StmtsEmitter {
|
|
|
244
515
|
this.allocLocal(l.name, wType, r._type);
|
|
245
516
|
localInfo = this.locals.get(l.name);
|
|
246
517
|
}
|
|
518
|
+
if (
|
|
519
|
+
localInfo &&
|
|
520
|
+
op !== ":=" &&
|
|
521
|
+
this._needsStableStore(l, localInfo.goType)
|
|
522
|
+
) {
|
|
523
|
+
this._emitStableStore(localInfo.goType, r, () => {
|
|
524
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
525
|
+
if (localInfo.isBoxed)
|
|
526
|
+
this.pushInstruction({
|
|
527
|
+
op: "struct.get",
|
|
528
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
529
|
+
fieldIndex: 0,
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const globalStable =
|
|
535
|
+
!localInfo && op !== ":=" && this.mod.resolveGlobal(l.name);
|
|
536
|
+
if (globalStable && this._needsStableStore(l, globalStable.goType)) {
|
|
537
|
+
this._emitStableStore(globalStable.goType, r, () => {
|
|
538
|
+
this.pushInstruction({ op: "global.get", index: globalStable.index });
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
247
542
|
if (localInfo) {
|
|
543
|
+
if (localInfo.isBoxed && this._isValueArray(localInfo.goType)) {
|
|
544
|
+
// Pointers to the array (`&a`) hold its reference: write in place.
|
|
545
|
+
this._emitArrayStoreInto(localInfo.goType, r, () => {
|
|
546
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
547
|
+
this.pushInstruction({
|
|
548
|
+
op: "struct.get",
|
|
549
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
550
|
+
fieldIndex: 0,
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
248
555
|
if (localInfo.isBoxed) {
|
|
249
556
|
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
250
557
|
this.emitExpr(r, localInfo.boxInfo.wType);
|
|
558
|
+
if (isArrayType(localInfo.goType, this.mod.checker))
|
|
559
|
+
this._emitCopyIfValue(r, localInfo.goType, localInfo.boxInfo.wType);
|
|
251
560
|
this.pushInstruction({
|
|
252
561
|
op: "struct.set",
|
|
253
562
|
typeIndex: localInfo.boxInfo.typeIndex,
|
|
@@ -256,13 +565,13 @@ export class StmtsEmitter {
|
|
|
256
565
|
return;
|
|
257
566
|
}
|
|
258
567
|
this.emitExpr(r, localInfo.type);
|
|
259
|
-
this.
|
|
568
|
+
this._emitCopyIfValue(r, localInfo.goType, localInfo.type);
|
|
260
569
|
this.pushInstruction({ op: "local.set", index: localInfo.index });
|
|
261
570
|
} else {
|
|
262
571
|
const globalInfo = this.mod.resolveGlobal(l.name);
|
|
263
572
|
if (globalInfo) {
|
|
264
573
|
this.emitExpr(r, globalInfo.type);
|
|
265
|
-
this.
|
|
574
|
+
this._emitCopyIfValue(r, globalInfo.goType, globalInfo.type);
|
|
266
575
|
this.pushInstruction({
|
|
267
576
|
op: "global.set",
|
|
268
577
|
index: globalInfo.index,
|
|
@@ -292,7 +601,20 @@ export class StmtsEmitter {
|
|
|
292
601
|
this._resolveExprGoType(l.expr);
|
|
293
602
|
const baseWType = this.toWasmType(exprGoType);
|
|
294
603
|
|
|
295
|
-
this.
|
|
604
|
+
if (this._needsStableStore(l, lastStep.field.goType)) {
|
|
605
|
+
this._emitStableStore(lastStep.field.goType, r, () => {
|
|
606
|
+
this.emitSelectorBase(l.expr, baseWType, exprGoType);
|
|
607
|
+
for (const step of path) {
|
|
608
|
+
this.pushInstruction({
|
|
609
|
+
op: "struct.get",
|
|
610
|
+
typeIndex: step.structInfo.typeIndex,
|
|
611
|
+
fieldIndex: step.fieldIndex,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.emitSelectorBase(l.expr, baseWType, exprGoType);
|
|
296
618
|
for (let i = 0; i < path.length - 1; i++) {
|
|
297
619
|
const step = path[i];
|
|
298
620
|
this.pushInstruction({
|
|
@@ -302,11 +624,7 @@ export class StmtsEmitter {
|
|
|
302
624
|
});
|
|
303
625
|
}
|
|
304
626
|
this.emitExpr(r, lastStep.field.wType);
|
|
305
|
-
this.
|
|
306
|
-
r,
|
|
307
|
-
lastStep.field.goType,
|
|
308
|
-
lastStep.field.wType,
|
|
309
|
-
);
|
|
627
|
+
this._emitCopyIfValue(r, lastStep.field.goType, lastStep.field.wType);
|
|
310
628
|
this.pushInstruction({
|
|
311
629
|
op: "struct.set",
|
|
312
630
|
typeIndex: lastStep.structInfo.typeIndex,
|
|
@@ -315,57 +633,8 @@ export class StmtsEmitter {
|
|
|
315
633
|
return;
|
|
316
634
|
}
|
|
317
635
|
|
|
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);
|
|
636
|
+
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
637
|
+
this._emitDerefAssign(l, r);
|
|
369
638
|
return;
|
|
370
639
|
}
|
|
371
640
|
|
|
@@ -381,7 +650,7 @@ export class StmtsEmitter {
|
|
|
381
650
|
this.emitExpr(baseNode, mapInfo.wType);
|
|
382
651
|
this.emitExpr(l.index, mapInfo.keyWType);
|
|
383
652
|
this.emitExpr(r, mapInfo.valWType);
|
|
384
|
-
this.
|
|
653
|
+
this._emitCopyIfValue(r, valType, mapInfo.valWType);
|
|
385
654
|
this.pushInstruction({
|
|
386
655
|
op: "call",
|
|
387
656
|
funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
|
|
@@ -400,18 +669,33 @@ export class StmtsEmitter {
|
|
|
400
669
|
baseType?.kind === "pointer" &&
|
|
401
670
|
isArrayType(baseType.base, this.mod.checker);
|
|
402
671
|
|
|
403
|
-
const elemGoType =
|
|
404
|
-
? baseType.
|
|
405
|
-
|
|
406
|
-
? baseType.base.elem
|
|
407
|
-
: baseType.elem;
|
|
672
|
+
const elemGoType = this._getArrayElemType(
|
|
673
|
+
isPtrToArr ? baseType.base : baseType,
|
|
674
|
+
);
|
|
408
675
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
409
676
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
410
677
|
|
|
678
|
+
// A whole struct or array stored into an element whose address is
|
|
679
|
+
// taken writes into the existing element, so `p := &xs[0]` stays valid.
|
|
680
|
+
if (this._needsStableStore(l, elemGoType)) {
|
|
681
|
+
if (sliceInfo) {
|
|
682
|
+
this._emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
this._emitStableStore(elemGoType, r, () => {
|
|
686
|
+
this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
|
|
687
|
+
this.pushInstruction({
|
|
688
|
+
op: "array.get",
|
|
689
|
+
typeIndex: arrInfo.typeIndex,
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
411
695
|
this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
|
|
412
696
|
|
|
413
697
|
this.emitExpr(r, arrInfo.elemWType);
|
|
414
|
-
this.
|
|
698
|
+
this._emitCopyIfValue(r, elemGoType, arrInfo.elemWType);
|
|
415
699
|
|
|
416
700
|
this.pushInstruction({
|
|
417
701
|
op: "array.set",
|
|
@@ -421,7 +705,398 @@ export class StmtsEmitter {
|
|
|
421
705
|
}
|
|
422
706
|
}
|
|
423
707
|
|
|
708
|
+
// A range variable that a closure captures and mutates lives in a box,
|
|
709
|
+
// but the loop writes the plain value each iteration. It gets a raw
|
|
710
|
+
// local here, and `_emitRangeBoxes` (at the top of the body) wraps the
|
|
711
|
+
// value in a fresh box per iteration, so each iteration's closures keep
|
|
712
|
+
// their own variable (Go 1.22) and the loop never stores a raw value
|
|
713
|
+
// into a box local.
|
|
714
|
+
_allocRangeVar(name, wType, goType) {
|
|
715
|
+
if (!this.mutatedCaptures.has(name))
|
|
716
|
+
return this.allocLocal(name, wType, goType);
|
|
717
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
718
|
+
if (!this._pendingRangeBoxes) this._pendingRangeBoxes = [];
|
|
719
|
+
this._pendingRangeBoxes.push({ name, idx, wType, goType });
|
|
720
|
+
return idx;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
_emitRangeBoxes() {
|
|
724
|
+
const pending = this._pendingRangeBoxes ?? [];
|
|
725
|
+
this._pendingRangeBoxes = [];
|
|
726
|
+
for (const { name, idx, wType, goType } of pending) {
|
|
727
|
+
const boxIdx = this.allocLocal(name, wType, goType);
|
|
728
|
+
const info = this.locals.get(name);
|
|
729
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
730
|
+
this.pushInstruction({
|
|
731
|
+
op: "struct.new",
|
|
732
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
733
|
+
});
|
|
734
|
+
this.pushInstruction({ op: "local.set", index: boxIdx });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// The struct that holds the last field of `path`: a promoted field
|
|
739
|
+
// (`o.N` on an embedded `Inner`) lives on the embedded struct.
|
|
740
|
+
_fieldHolderWType(path, baseWType) {
|
|
741
|
+
if (path.length <= 1) return baseWType;
|
|
742
|
+
const holder = path[path.length - 1].structInfo;
|
|
743
|
+
return { kind: "ref", nullable: true, typeIndex: holder.typeIndex };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Copy the array value `r` into the existing array that `emitDst` pushes,
|
|
747
|
+
// so every pointer to that array sees the new elements.
|
|
748
|
+
_emitArrayStoreInto(goType, r, emitDst) {
|
|
749
|
+
const elemGoType = this._getArrayElemType(goType);
|
|
750
|
+
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
751
|
+
const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
|
|
752
|
+
if (this._isValueStruct(elemGoType)) {
|
|
753
|
+
this._emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const dst = this.acquireTemp(wType);
|
|
757
|
+
emitDst();
|
|
758
|
+
this.pushInstruction({ op: "local.tee", index: dst });
|
|
759
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
760
|
+
this.emitExpr(r, wType);
|
|
761
|
+
this._emitCopyIfValue(r, goType, wType);
|
|
762
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
763
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
764
|
+
this.pushInstruction("array.len");
|
|
765
|
+
this.pushInstruction({
|
|
766
|
+
op: "array.copy",
|
|
767
|
+
typeIndex: arrInfo.typeIndex,
|
|
768
|
+
});
|
|
769
|
+
this.releaseTemp(dst, wType);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
_isValueStruct(goType) {
|
|
773
|
+
return (
|
|
774
|
+
!!goType &&
|
|
775
|
+
goType.kind !== "pointer" &&
|
|
776
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
777
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// An array of structs holds element references, so `array.copy` would
|
|
782
|
+
// swap them and leave `&a[i]` pointing at the old element. Copy each
|
|
783
|
+
// element's fields into the existing element struct instead.
|
|
784
|
+
_emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst) {
|
|
785
|
+
const elemInfo = this._resolveStructInfo({ _type: elemGoType });
|
|
786
|
+
const eType = arrInfo.elemWType;
|
|
787
|
+
const dst = this.acquireTemp(wType);
|
|
788
|
+
const src = this.acquireTemp(wType);
|
|
789
|
+
const i = this.acquireTemp("i32");
|
|
790
|
+
const d = this.acquireTemp(eType);
|
|
791
|
+
const s = this.acquireTemp(eType);
|
|
792
|
+
emitDst();
|
|
793
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
794
|
+
this.emitExpr(r, wType);
|
|
795
|
+
this.pushInstruction({ op: "local.set", index: src });
|
|
796
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
797
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
798
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
799
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
800
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
801
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
802
|
+
this.pushInstruction("array.len");
|
|
803
|
+
this.pushInstruction("i32.ge_s");
|
|
804
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
805
|
+
for (const [tmp, arr] of [
|
|
806
|
+
[d, dst],
|
|
807
|
+
[s, src],
|
|
808
|
+
]) {
|
|
809
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
810
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
811
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
812
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
813
|
+
}
|
|
814
|
+
this._emitStructFieldsInto(elemInfo, d, s);
|
|
815
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
816
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
817
|
+
this.pushInstruction("i32.add");
|
|
818
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
819
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
820
|
+
this.pushInstruction("end");
|
|
821
|
+
this.pushInstruction("end");
|
|
822
|
+
this.releaseTemp(s, eType);
|
|
823
|
+
this.releaseTemp(d, eType);
|
|
824
|
+
this.releaseTemp(i, "i32");
|
|
825
|
+
this.releaseTemp(src, wType);
|
|
826
|
+
this.releaseTemp(dst, wType);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Whole-value assignment to a variable (or a field of one) whose address
|
|
830
|
+
// is taken writes into the existing storage, so earlier pointers to it or
|
|
831
|
+
// to its parts see the new value (Task 0.33). Other variables keep the
|
|
832
|
+
// plain local.set / struct.set path.
|
|
833
|
+
_needsStableStore(l, goType) {
|
|
834
|
+
if (!goType || goType.kind === "pointer") return false;
|
|
835
|
+
const isArr = this._isValueArray(goType);
|
|
836
|
+
if (
|
|
837
|
+
!isArr &&
|
|
838
|
+
!(
|
|
839
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
840
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
841
|
+
)
|
|
842
|
+
)
|
|
843
|
+
return false;
|
|
844
|
+
// Any callee may hold `&xs[i]` of a slice element, so when the program
|
|
845
|
+
// takes one, a whole-value store into an element writes in place.
|
|
846
|
+
if (this.mod.lowerResult?.elemAddrTaken && this._isSliceElemTarget(l))
|
|
847
|
+
return true;
|
|
848
|
+
const root = rootIdentName(l);
|
|
849
|
+
if (!root) return false;
|
|
850
|
+
// Any code may hold the address of a package-level variable.
|
|
851
|
+
if (!this.resolveLocal(root)) return !!this.mod.resolveGlobal(root);
|
|
852
|
+
const decl = this.rootFuncDecl._sourceDecl ?? this.rootFuncDecl;
|
|
853
|
+
const ctx = this.mod.lowerResult?.ownership?.get(decl);
|
|
854
|
+
return Boolean(ctx?.fnMutates(root, true));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// `xs[i] = v` into a slice element in place. Capacity beyond the length
|
|
858
|
+
// may hold null elements (a reslice exposes them), so a null element is
|
|
859
|
+
// first replaced by a zero value that the store then fills.
|
|
860
|
+
_emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo) {
|
|
861
|
+
const arrT = {
|
|
862
|
+
kind: "ref",
|
|
863
|
+
nullable: true,
|
|
864
|
+
typeIndex: arrInfo.typeIndex,
|
|
865
|
+
};
|
|
866
|
+
const arr = this.acquireTemp(arrT);
|
|
867
|
+
const idx = this.acquireTemp("i32");
|
|
868
|
+
this._emitElemAddr(l.expr, l.index, sliceInfo, arrInfo);
|
|
869
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
870
|
+
this.pushInstruction({ op: "local.set", index: arr });
|
|
871
|
+
const getElem = () => {
|
|
872
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
873
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
874
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
875
|
+
};
|
|
876
|
+
getElem();
|
|
877
|
+
this.pushInstruction("ref.is_null");
|
|
878
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
879
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
880
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
881
|
+
this.emitZeroValue(elemGoType, arrInfo.elemWType);
|
|
882
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
883
|
+
this.pushInstruction("end");
|
|
884
|
+
this._emitStableStore(elemGoType, r, getElem);
|
|
885
|
+
this.releaseTemp(idx, "i32");
|
|
886
|
+
this.releaseTemp(arr, arrT);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// True when `l` is `xs[i]` on a slice (not an array or map).
|
|
890
|
+
_isSliceElemTarget(l) {
|
|
891
|
+
if (l.kind !== "IndexExpr") return false;
|
|
892
|
+
const t = l.expr._type ?? this._resolveExprGoType(l.expr);
|
|
893
|
+
return isSliceType(t, this.mod.checker) && !this._sharedInfo(t);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
_emitStableStore(goType, r, emitDst) {
|
|
897
|
+
if (this._isValueArray(goType)) {
|
|
898
|
+
this._emitArrayStoreInto(goType, r, emitDst);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const info = this._resolveStructInfo({ _type: goType });
|
|
902
|
+
const wType = this.toWasmType(goType);
|
|
903
|
+
const dst = this.acquireTemp(wType);
|
|
904
|
+
const src = this.acquireTemp(wType);
|
|
905
|
+
emitDst();
|
|
906
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
907
|
+
this.emitExpr(r, wType);
|
|
908
|
+
this.pushInstruction({ op: "local.set", index: src });
|
|
909
|
+
this._emitStructFieldsInto(info, dst, src);
|
|
910
|
+
this.releaseTemp(src, wType);
|
|
911
|
+
this.releaseTemp(dst, wType);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Copies every field of the struct in `src` into the struct in `dst`;
|
|
915
|
+
// array and value-struct fields are copied into the existing storage.
|
|
916
|
+
_emitStructFieldsInto(info, dst, src) {
|
|
917
|
+
const get = (tmp, i) => {
|
|
918
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
919
|
+
this.pushInstruction({
|
|
920
|
+
op: "struct.get",
|
|
921
|
+
typeIndex: info.typeIndex,
|
|
922
|
+
fieldIndex: i,
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
for (let i = 0; i < info.fields.length; i++) {
|
|
926
|
+
const f = info.fields[i];
|
|
927
|
+
const gt = f.goType;
|
|
928
|
+
if (gt && gt.kind !== "pointer" && this._isValueArray(gt)) {
|
|
929
|
+
const arrInfo = this.mod.getArrayType(this._getArrayElemType(gt));
|
|
930
|
+
get(dst, i);
|
|
931
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
932
|
+
get(src, i);
|
|
933
|
+
const elem = this._getArrayElemType(gt);
|
|
934
|
+
if (
|
|
935
|
+
isArrayType(elem, this.mod.checker) ||
|
|
936
|
+
isStructType(elem, this.mod.checker, this.mod)
|
|
937
|
+
)
|
|
938
|
+
this.emitCopyArray(gt);
|
|
939
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
940
|
+
get(dst, i);
|
|
941
|
+
this.pushInstruction("array.len");
|
|
942
|
+
this.pushInstruction({
|
|
943
|
+
op: "array.copy",
|
|
944
|
+
typeIndex: arrInfo.typeIndex,
|
|
945
|
+
});
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const inner =
|
|
949
|
+
gt &&
|
|
950
|
+
gt.kind !== "pointer" &&
|
|
951
|
+
isStructType(gt, this.mod.checker, this.mod) &&
|
|
952
|
+
!isPointerToStruct(gt, this.mod.checker, this.mod)
|
|
953
|
+
? this._resolveStructInfo({ _type: gt })
|
|
954
|
+
: null;
|
|
955
|
+
if (inner) {
|
|
956
|
+
const d = this.acquireTemp(f.wType);
|
|
957
|
+
const s = this.acquireTemp(f.wType);
|
|
958
|
+
get(dst, i);
|
|
959
|
+
this.pushInstruction({ op: "local.set", index: d });
|
|
960
|
+
get(src, i);
|
|
961
|
+
this.pushInstruction({ op: "local.set", index: s });
|
|
962
|
+
this._emitStructFieldsInto(inner, d, s);
|
|
963
|
+
this.releaseTemp(s, f.wType);
|
|
964
|
+
this.releaseTemp(d, f.wType);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
968
|
+
get(src, i);
|
|
969
|
+
this.pushInstruction({
|
|
970
|
+
op: "struct.set",
|
|
971
|
+
typeIndex: info.typeIndex,
|
|
972
|
+
fieldIndex: i,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
_emitStructDerefAssign(operand, r, structInfo) {
|
|
978
|
+
const baseWType = this.toWasmType(operand._type);
|
|
979
|
+
const tmpL = this.acquireTemp(baseWType);
|
|
980
|
+
const tmpR = this.acquireTemp(baseWType);
|
|
981
|
+
this.emitExpr(operand, baseWType);
|
|
982
|
+
this.pushInstruction({ op: "local.set", index: tmpL });
|
|
983
|
+
this.emitExpr(r, baseWType);
|
|
984
|
+
this.pushInstruction({ op: "local.set", index: tmpR });
|
|
985
|
+
this._emitNilGuard(tmpL, operand._type);
|
|
986
|
+
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
987
|
+
this.pushInstruction({ op: "local.get", index: tmpL });
|
|
988
|
+
this.pushInstruction({ op: "local.get", index: tmpR });
|
|
989
|
+
this.pushInstruction({
|
|
990
|
+
op: "struct.get",
|
|
991
|
+
typeIndex: structInfo.typeIndex,
|
|
992
|
+
fieldIndex: i,
|
|
993
|
+
});
|
|
994
|
+
this.pushInstruction({
|
|
995
|
+
op: "struct.set",
|
|
996
|
+
typeIndex: structInfo.typeIndex,
|
|
997
|
+
fieldIndex: i,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
this.releaseTemp(tmpR, baseWType);
|
|
1001
|
+
this.releaseTemp(tmpL, baseWType);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Raises Go's nil-dereference panic when the pointer held in `tmp` is nil
|
|
1005
|
+
// (a plain cast or field write would trap, which `recover` cannot see).
|
|
1006
|
+
_emitNilGuard(tmp, goType) {
|
|
1007
|
+
if (goType?.kind !== "pointer") return;
|
|
1008
|
+
if (this.resolveBranchDepthToRole("nil") == null) return;
|
|
1009
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1010
|
+
this._emitNilBranch();
|
|
1011
|
+
this.pushInstruction("drop");
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
_emitDerefAssign(l, r) {
|
|
1015
|
+
const operand = l.operand ?? l.expr;
|
|
1016
|
+
const structInfo = this._derefStructInfo(operand);
|
|
1017
|
+
if (structInfo) {
|
|
1018
|
+
this._emitStructDerefAssign(operand, r, structInfo);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const operandGoType =
|
|
1022
|
+
operand._type ??
|
|
1023
|
+
(operand.kind === "Ident"
|
|
1024
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1025
|
+
: null) ??
|
|
1026
|
+
this._resolveExprGoType(operand);
|
|
1027
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1028
|
+
if (this._isValueArray(baseGoType)) {
|
|
1029
|
+
this._emitArrayStoreInto(baseGoType, r, () => {
|
|
1030
|
+
this.emitExpr(operand);
|
|
1031
|
+
this._emitNilCheck(operandGoType);
|
|
1032
|
+
});
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1039
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1040
|
+
const ptrWType = {
|
|
1041
|
+
kind: "ref",
|
|
1042
|
+
nullable: true,
|
|
1043
|
+
typeIndex: ptr.typeIndex,
|
|
1044
|
+
typeName: ptr.name,
|
|
1045
|
+
};
|
|
1046
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1047
|
+
const valTmp = this.acquireTemp(box.wType);
|
|
1048
|
+
this.emitExpr(operand, ptrWType);
|
|
1049
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1050
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
1051
|
+
this.emitExpr(r, box.wType);
|
|
1052
|
+
this.pushInstruction({ op: "local.set", index: valTmp });
|
|
1053
|
+
|
|
1054
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1055
|
+
this.pushInstruction({
|
|
1056
|
+
op: "ref.cast",
|
|
1057
|
+
typeIndex: box.typeIndex,
|
|
1058
|
+
typeName: box.name,
|
|
1059
|
+
});
|
|
1060
|
+
this.pushInstruction({ op: "local.get", index: valTmp });
|
|
1061
|
+
this.pushInstruction({
|
|
1062
|
+
op: "struct.set",
|
|
1063
|
+
typeIndex: box.typeIndex,
|
|
1064
|
+
typeName: box.name,
|
|
1065
|
+
fieldIndex: 0,
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
this.releaseTemp(valTmp, box.wType);
|
|
1069
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// A plain variable target of interface type receiving a concrete result
|
|
1073
|
+
// (`var x any; x, y = two()`) needs the value converted first.
|
|
1074
|
+
_needsTupleConversion(lhsNodes, tupleTypes) {
|
|
1075
|
+
return lhsNodes.some((l, i) => {
|
|
1076
|
+
if (l.kind !== "Ident" || l.name === "_") return false;
|
|
1077
|
+
const dest = this.resolveLocal(l.name) ?? this.mod.resolveGlobal(l.name);
|
|
1078
|
+
const checker = this.mod.checker;
|
|
1079
|
+
return (
|
|
1080
|
+
isInterfaceType(dest?.goType ?? l._type, checker) &&
|
|
1081
|
+
!isInterfaceType(tupleTypes[i], checker)
|
|
1082
|
+
);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
424
1086
|
_emitTupleAssign(lhsNodes, rhsNode, op) {
|
|
1087
|
+
if (
|
|
1088
|
+
op === "=" &&
|
|
1089
|
+
(lhsNodes.some((l) => !this._isPlainTupleTarget(l)) ||
|
|
1090
|
+
this._needsTupleConversion(lhsNodes, rhsNode._type.types))
|
|
1091
|
+
) {
|
|
1092
|
+
this._emitTupleViaTemps(
|
|
1093
|
+
lhsNodes,
|
|
1094
|
+
rhsNode,
|
|
1095
|
+
(l, t) => this._emitAssignSingle(l, t, "="),
|
|
1096
|
+
true,
|
|
1097
|
+
);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
425
1100
|
const tupleTypes = rhsNode._type.types;
|
|
426
1101
|
const dests = [];
|
|
427
1102
|
for (let i = 0; i < lhsNodes.length; i++) {
|
|
@@ -509,12 +1184,83 @@ export class StmtsEmitter {
|
|
|
509
1184
|
this.releaseTemp(okTmp, "i32");
|
|
510
1185
|
}
|
|
511
1186
|
|
|
1187
|
+
// Copies the struct or array a map entry holds (a range key or value is a
|
|
1188
|
+
// copy, not the stored value).
|
|
1189
|
+
_emitCopyMapEntry(goType, wType) {
|
|
1190
|
+
const isStruct =
|
|
1191
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
1192
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod);
|
|
1193
|
+
if (isStruct) {
|
|
1194
|
+
const sInfo = this._resolveStructInfo({ _type: goType });
|
|
1195
|
+
if (sInfo) this.emitCloneStruct(sInfo, wType);
|
|
1196
|
+
} else if (
|
|
1197
|
+
goType?.kind !== "pointer" &&
|
|
1198
|
+
isArrayType(goType, this.mod.checker)
|
|
1199
|
+
) {
|
|
1200
|
+
this.emitCopyArray(goType);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Pushes a comma-ok result (map value / asserted value). An array or
|
|
1205
|
+
// struct is a value: the stored one must not alias the map entry or boxed
|
|
1206
|
+
// interface.
|
|
1207
|
+
_emitGetTempValue(tempIdx, goType) {
|
|
1208
|
+
this.pushInstruction({ op: "local.get", index: tempIdx });
|
|
1209
|
+
if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
|
|
1210
|
+
this.emitCopyArray(goType);
|
|
1211
|
+
else if (
|
|
1212
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
1213
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
1214
|
+
) {
|
|
1215
|
+
// A struct read out of a map or an interface is a copy.
|
|
1216
|
+
const info =
|
|
1217
|
+
this._resolveStructInfo({ _type: goType }) ??
|
|
1218
|
+
this.mod.getStructType(goType?.name);
|
|
1219
|
+
if (info) this.emitCloneStruct(info, this.toWasmType(goType));
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
_storeTargetWType(lhsNode) {
|
|
1224
|
+
if (lhsNode.kind !== "Ident") return null;
|
|
1225
|
+
const local = this.resolveLocal(lhsNode.name);
|
|
1226
|
+
if (local)
|
|
1227
|
+
return local.isBoxed
|
|
1228
|
+
? local.boxInfo?.typeEntry?.fields?.[0]?.type
|
|
1229
|
+
: local.type;
|
|
1230
|
+
return this.mod.resolveGlobal(lhsNode.name)?.type ?? null;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
512
1233
|
_emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
|
|
513
|
-
if (lhsNode.kind
|
|
1234
|
+
if (lhsNode.kind === "Ident" && lhsNode.name === "_") return;
|
|
1235
|
+
const redeclared =
|
|
1236
|
+
isDefine && lhsNode._redecl && this.resolveLocal(lhsNode.name);
|
|
1237
|
+
// `var x any; x, ok = m[k]`: a scalar or struct result is boxed on the
|
|
1238
|
+
// way into the interface-typed variable.
|
|
1239
|
+
const target = isDefine ? null : this._storeTargetWType(lhsNode);
|
|
1240
|
+
const boxesIntoAny = target === "anyref" && wType !== "anyref";
|
|
1241
|
+
if (lhsNode.kind !== "Ident" || redeclared || boxesIntoAny) {
|
|
1242
|
+
// An index, field or dereference target (or a name `:=` redeclares):
|
|
1243
|
+
// assign from a named view of the temporary.
|
|
1244
|
+
const name = `__ok_tmp_${this.tempCounter++}`;
|
|
1245
|
+
this.locals.set(name, {
|
|
1246
|
+
index: tempIdx,
|
|
1247
|
+
type: wType,
|
|
1248
|
+
goType,
|
|
1249
|
+
isParam: false,
|
|
1250
|
+
isBoxed: false,
|
|
1251
|
+
});
|
|
1252
|
+
this._emitAssignSingle(
|
|
1253
|
+
lhsNode,
|
|
1254
|
+
{ kind: "Ident", name, _type: goType, _parTmp: true },
|
|
1255
|
+
"=",
|
|
1256
|
+
);
|
|
1257
|
+
this.locals.delete(name);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
514
1260
|
if (isDefine) {
|
|
515
1261
|
const idx = this.allocLocal(lhsNode.name, wType, goType);
|
|
516
1262
|
const local = this.locals.get(lhsNode.name);
|
|
517
|
-
this.
|
|
1263
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
518
1264
|
if (local?.isBoxed) {
|
|
519
1265
|
this.pushInstruction({
|
|
520
1266
|
op: "struct.new",
|
|
@@ -528,19 +1274,19 @@ export class StmtsEmitter {
|
|
|
528
1274
|
if (local) {
|
|
529
1275
|
if (local.isBoxed) {
|
|
530
1276
|
this.pushInstruction({ op: "local.get", index: local.index });
|
|
531
|
-
this.
|
|
1277
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
532
1278
|
this.pushInstruction({
|
|
533
1279
|
op: "struct.set",
|
|
534
1280
|
typeIndex: local.boxInfo.typeIndex,
|
|
535
1281
|
fieldIndex: 0,
|
|
536
1282
|
});
|
|
537
1283
|
} else {
|
|
538
|
-
this.
|
|
1284
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
539
1285
|
this.pushInstruction({ op: "local.set", index: local.index });
|
|
540
1286
|
}
|
|
541
1287
|
} else {
|
|
542
1288
|
const global = this.mod.resolveGlobal(lhsNode.name);
|
|
543
|
-
this.
|
|
1289
|
+
this._emitGetTempValue(tempIdx, goType);
|
|
544
1290
|
this.pushInstruction({ op: "global.set", index: global.index });
|
|
545
1291
|
}
|
|
546
1292
|
}
|
|
@@ -591,7 +1337,38 @@ export class StmtsEmitter {
|
|
|
591
1337
|
return goType.name ?? null;
|
|
592
1338
|
}
|
|
593
1339
|
|
|
1340
|
+
// The struct a type assertion target refers to; an anonymous struct type
|
|
1341
|
+
// has no name to look up.
|
|
1342
|
+
_structInfoOfType(goType) {
|
|
1343
|
+
return (
|
|
1344
|
+
this.mod.getStructType(this._structNameOf(goType)) ??
|
|
1345
|
+
this._resolveStructInfo({ _type: goType })
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// A bare type name in a case list or assertion (`case L:`, `case error:`)
|
|
1350
|
+
// is the declared type, not its underlying one: a defined string type is
|
|
1351
|
+
// boxed apart from `string`, and `error` carries a method set.
|
|
1352
|
+
_resolveTestTarget(t) {
|
|
1353
|
+
if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
|
|
1354
|
+
if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
|
|
1355
|
+
return t;
|
|
1356
|
+
return this.mod.checker?.types?.get(t.name) ?? t;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// A defined func type, or a defined map type that has methods, lives in
|
|
1360
|
+
// `any` as a box (so `iter.Seq[int]` is not a plain `func(func(int) bool)`).
|
|
1361
|
+
_isMethodNamedRef(goType) {
|
|
1362
|
+
const t = this._resolveTestTarget(goType);
|
|
1363
|
+
if (t?.kind !== "named") return false;
|
|
1364
|
+
return (
|
|
1365
|
+
isFuncType(t, this.mod.checker) ||
|
|
1366
|
+
(t.methods?.size > 0 && isMapType(t, this.mod.checker))
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
594
1370
|
_emitTypeTest(targetGoType) {
|
|
1371
|
+
targetGoType = this._resolveTestTarget(targetGoType);
|
|
595
1372
|
if (!targetGoType) {
|
|
596
1373
|
this.pushInstruction("drop");
|
|
597
1374
|
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
@@ -610,62 +1387,45 @@ export class StmtsEmitter {
|
|
|
610
1387
|
}
|
|
611
1388
|
|
|
612
1389
|
if (isNonEmptyInterface(targetGoType, this.mod.checker)) {
|
|
613
|
-
|
|
614
|
-
if (candidates.length === 0) {
|
|
615
|
-
this.pushInstruction("drop");
|
|
616
|
-
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
const anyTmp = this.acquireTemp("anyref");
|
|
620
|
-
this.pushInstruction({ op: "local.set", index: anyTmp });
|
|
621
|
-
|
|
622
|
-
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
623
|
-
this.pushInstruction("ref.is_null");
|
|
624
|
-
this.pushInstruction("i32.eqz");
|
|
625
|
-
|
|
626
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
627
|
-
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
628
|
-
this.pushInstruction({
|
|
629
|
-
op: "ref.test",
|
|
630
|
-
typeIndex: candidates[i].typeIndex,
|
|
631
|
-
});
|
|
632
|
-
if (i > 0) {
|
|
633
|
-
this.pushInstruction("i32.or");
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
this.pushInstruction("i32.and");
|
|
637
|
-
this.releaseTemp(anyTmp, "anyref");
|
|
1390
|
+
this._emitInterfaceTypeTest(targetGoType);
|
|
638
1391
|
return;
|
|
639
1392
|
}
|
|
640
1393
|
|
|
641
|
-
|
|
1394
|
+
// A named string type is boxed like any other scalar (see emitExpr); only
|
|
1395
|
+
// a plain `string` lives in `any` as the bare string.
|
|
1396
|
+
if (isBareStringType(targetGoType, this.mod.checker)) {
|
|
642
1397
|
const isStrIdx = this.mod.getIsStringImportIndex();
|
|
643
1398
|
this.pushInstruction({ op: "call", funcIndex: isStrIdx });
|
|
644
1399
|
return;
|
|
645
1400
|
}
|
|
646
1401
|
|
|
1402
|
+
if (this._isMethodNamedRef(targetGoType)) {
|
|
1403
|
+
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
1404
|
+
this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
647
1408
|
if (isFuncType(targetGoType, this.mod.checker)) {
|
|
648
1409
|
const closureInfo = this.mod.getClosureType(targetGoType);
|
|
649
|
-
this.
|
|
650
|
-
op: "ref.test",
|
|
651
|
-
typeIndex: closureInfo.typeIndex,
|
|
652
|
-
});
|
|
1410
|
+
this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
|
|
653
1411
|
return;
|
|
654
1412
|
}
|
|
655
1413
|
|
|
656
1414
|
// `*T` lives in `any` as the bare struct ref; a `T` value as a boxed
|
|
657
1415
|
// clone (see emitExpr). Fall through to the scalar box for values.
|
|
658
1416
|
if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
|
|
659
|
-
const sInfo = this.
|
|
1417
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
660
1418
|
if (sInfo) {
|
|
661
|
-
this.
|
|
1419
|
+
this._emitRefTestOrNil(targetGoType, sInfo.typeIndex);
|
|
662
1420
|
return;
|
|
663
1421
|
}
|
|
664
1422
|
}
|
|
665
1423
|
|
|
666
1424
|
if (isSliceType(targetGoType, this.mod.checker)) {
|
|
667
1425
|
const elem = this._getSliceElemType(targetGoType);
|
|
668
|
-
const sInfo = this.mod.
|
|
1426
|
+
const sInfo = isTaggedSliceType(targetGoType, this.mod.checker)
|
|
1427
|
+
? this.mod.getTaggedSliceType(targetGoType, elem)
|
|
1428
|
+
: this.mod.getSliceType(elem);
|
|
669
1429
|
if (sInfo) {
|
|
670
1430
|
this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
|
|
671
1431
|
return;
|
|
@@ -676,11 +1436,22 @@ export class StmtsEmitter {
|
|
|
676
1436
|
const elem = this._getArrayElemType(targetGoType);
|
|
677
1437
|
const aInfo = this.mod.getArrayType(elem);
|
|
678
1438
|
if (aInfo) {
|
|
679
|
-
this.
|
|
1439
|
+
this._emitArrayTypeTest(targetGoType, aInfo);
|
|
680
1440
|
return;
|
|
681
1441
|
}
|
|
682
1442
|
}
|
|
683
1443
|
|
|
1444
|
+
// A map lives in `any` as its bare map struct.
|
|
1445
|
+
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1446
|
+
const { keyType, valType } = getMapKeyValTypes(
|
|
1447
|
+
targetGoType,
|
|
1448
|
+
this.mod.checker,
|
|
1449
|
+
);
|
|
1450
|
+
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1451
|
+
this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
684
1455
|
// Scalar types
|
|
685
1456
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
686
1457
|
if (box) {
|
|
@@ -692,37 +1463,139 @@ export class StmtsEmitter {
|
|
|
692
1463
|
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
693
1464
|
}
|
|
694
1465
|
|
|
695
|
-
|
|
696
|
-
|
|
1466
|
+
// `[3]int` and `[2]int` share one wasm array type, so the test also compares
|
|
1467
|
+
// the length.
|
|
1468
|
+
_emitArrayTypeTest(goType, aInfo) {
|
|
1469
|
+
let t = goType?._assertType ?? goType;
|
|
1470
|
+
for (let i = 0; i < 16 && t && t.kind !== "array"; i++)
|
|
1471
|
+
t = t.underlying ?? this.mod.checker?.types?.get(t.name);
|
|
1472
|
+
const size = typeof t?.size === "number" ? t.size : null;
|
|
1473
|
+
if (size === null) {
|
|
1474
|
+
this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
|
|
697
1475
|
return;
|
|
698
1476
|
}
|
|
1477
|
+
const tmp = this.acquireTemp("anyref");
|
|
1478
|
+
this.pushInstruction({ op: "local.tee", index: tmp });
|
|
1479
|
+
this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
|
|
1480
|
+
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
1481
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1482
|
+
this.pushInstruction({ op: "ref.cast", typeIndex: aInfo.typeIndex });
|
|
1483
|
+
this.pushInstruction("array.len");
|
|
1484
|
+
this.pushInstruction({ op: "i32.const", value: size });
|
|
1485
|
+
this.pushInstruction("i32.eq");
|
|
1486
|
+
this.pushInstruction("else");
|
|
1487
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1488
|
+
this.pushInstruction("end");
|
|
1489
|
+
this.releaseTemp(tmp, "anyref");
|
|
1490
|
+
}
|
|
699
1491
|
|
|
700
|
-
|
|
1492
|
+
// Whether the interface value on the stack holds a type implementing the
|
|
1493
|
+
// non-empty interface: a `ref.test` over every implementing type (a typed
|
|
1494
|
+
// nil `*S` marker included).
|
|
1495
|
+
_emitInterfaceTypeTest(targetGoType) {
|
|
1496
|
+
const candidates = this.mod.getTypesImplementingInterface(targetGoType);
|
|
1497
|
+
if (candidates.length === 0) {
|
|
1498
|
+
this.pushInstruction("drop");
|
|
1499
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const anyTmp = this.acquireTemp("anyref");
|
|
1503
|
+
this.pushInstruction({ op: "local.set", index: anyTmp });
|
|
1504
|
+
|
|
1505
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1506
|
+
this.pushInstruction("ref.is_null");
|
|
1507
|
+
this.pushInstruction("i32.eqz");
|
|
1508
|
+
|
|
1509
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1510
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1511
|
+
this.pushInstruction({
|
|
1512
|
+
op: "ref.test",
|
|
1513
|
+
typeIndex: candidates[i].typeIndex,
|
|
1514
|
+
});
|
|
1515
|
+
if (candidates[i].nilTypeIndex !== undefined) {
|
|
1516
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1517
|
+
this.pushInstruction({
|
|
1518
|
+
op: "ref.test",
|
|
1519
|
+
typeIndex: candidates[i].nilTypeIndex,
|
|
1520
|
+
});
|
|
1521
|
+
this.pushInstruction("i32.or");
|
|
1522
|
+
}
|
|
1523
|
+
if (i > 0) {
|
|
1524
|
+
this.pushInstruction("i32.or");
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
this.pushInstruction("i32.and");
|
|
1528
|
+
this.releaseTemp(anyTmp, "anyref");
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// `ref.test` for a map, func or `*T`, which also accepts the typed-nil
|
|
1532
|
+
// marker of the same Go type (see getNilBoxType).
|
|
1533
|
+
_emitRefTestOrNil(goType, typeIndex) {
|
|
1534
|
+
const nilBox = this.mod.getNilBoxType(goType);
|
|
1535
|
+
const tmp = this.acquireTemp("anyref");
|
|
1536
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
1537
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1538
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1539
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1540
|
+
this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
|
|
1541
|
+
this.pushInstruction("i32.or");
|
|
1542
|
+
this.releaseTemp(tmp, "anyref");
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// `ref.cast_null` to `typeIndex`, turning the typed-nil marker of `goType`
|
|
1546
|
+
// back into a nil value.
|
|
1547
|
+
_emitRefCastOrNil(goType, typeIndex) {
|
|
1548
|
+
const nilBox = this.mod.getNilBoxType(goType);
|
|
1549
|
+
const tmp = this.acquireTemp("anyref");
|
|
1550
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
1551
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1552
|
+
this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
|
|
1553
|
+
this.pushInstruction({
|
|
1554
|
+
op: "if",
|
|
1555
|
+
blockType: { kind: "ref", nullable: true, typeIndex },
|
|
1556
|
+
});
|
|
1557
|
+
this.pushInstruction({ op: "ref.null", heapType: typeIndex });
|
|
1558
|
+
this.pushInstruction("else");
|
|
1559
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1560
|
+
this.pushInstruction({ op: "ref.cast_null", typeIndex });
|
|
1561
|
+
this.pushInstruction("end");
|
|
1562
|
+
this.releaseTemp(tmp, "anyref");
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// An interface target keeps the value as is; a boxed defined func or map
|
|
1566
|
+
// type is unboxed. True when the cast is done.
|
|
1567
|
+
_emitCastNoRepack(targetGoType) {
|
|
1568
|
+
if (isInterfaceType(targetGoType, this.mod.checker)) return true;
|
|
1569
|
+
if (!this._isMethodNamedRef(targetGoType)) return false;
|
|
1570
|
+
this._emitUnbox(
|
|
1571
|
+
this.mod.getIfaceBoxType(this._resolveTestTarget(targetGoType)),
|
|
1572
|
+
);
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
_emitTypeCast(targetGoType, targetWType) {
|
|
1577
|
+
if (this._emitCastNoRepack(targetGoType)) return;
|
|
1578
|
+
|
|
1579
|
+
if (isBareStringType(targetGoType, this.mod.checker)) {
|
|
701
1580
|
this.pushInstruction("extern.convert_any");
|
|
702
1581
|
return;
|
|
703
1582
|
}
|
|
704
1583
|
|
|
705
1584
|
if (isFuncType(targetGoType, this.mod.checker)) {
|
|
706
1585
|
const closureInfo = this.mod.getClosureType(targetGoType);
|
|
707
|
-
this.
|
|
708
|
-
op: "ref.cast_null",
|
|
709
|
-
typeIndex: closureInfo.typeIndex,
|
|
710
|
-
});
|
|
1586
|
+
this._emitRefCastOrNil(targetGoType, closureInfo.typeIndex);
|
|
711
1587
|
return;
|
|
712
1588
|
}
|
|
713
1589
|
|
|
714
1590
|
if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
|
|
715
|
-
const sInfo = this.
|
|
1591
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
716
1592
|
if (sInfo) {
|
|
717
|
-
this.
|
|
718
|
-
op: "ref.cast_null",
|
|
719
|
-
typeIndex: sInfo.typeIndex,
|
|
720
|
-
});
|
|
1593
|
+
this._emitRefCastOrNil(targetGoType, sInfo.typeIndex);
|
|
721
1594
|
return;
|
|
722
1595
|
}
|
|
723
1596
|
}
|
|
724
1597
|
if (isStructType(targetGoType, this.mod.checker, this.mod)) {
|
|
725
|
-
const sInfo = this.
|
|
1598
|
+
const sInfo = this._structInfoOfType(targetGoType);
|
|
726
1599
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
727
1600
|
if (sInfo && box) {
|
|
728
1601
|
this._emitUnbox(box);
|
|
@@ -755,6 +1628,16 @@ export class StmtsEmitter {
|
|
|
755
1628
|
}
|
|
756
1629
|
}
|
|
757
1630
|
|
|
1631
|
+
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1632
|
+
const { keyType, valType } = getMapKeyValTypes(
|
|
1633
|
+
targetGoType,
|
|
1634
|
+
this.mod.checker,
|
|
1635
|
+
);
|
|
1636
|
+
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1637
|
+
this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
758
1641
|
// Scalar types
|
|
759
1642
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
760
1643
|
if (box) {
|
|
@@ -780,6 +1663,44 @@ export class StmtsEmitter {
|
|
|
780
1663
|
const l = Array.isArray(lhs) ? lhs[0] : lhs;
|
|
781
1664
|
const r = Array.isArray(rhs) ? rhs[0] : rhs;
|
|
782
1665
|
|
|
1666
|
+
if (isComplex(l._type)) {
|
|
1667
|
+
const synthBinary = {
|
|
1668
|
+
kind: "BinaryExpr",
|
|
1669
|
+
op: baseOp,
|
|
1670
|
+
left: l,
|
|
1671
|
+
right: r,
|
|
1672
|
+
_type: l._type,
|
|
1673
|
+
};
|
|
1674
|
+
const is64 = isComplex64(l._type);
|
|
1675
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
1676
|
+
const cWType = {
|
|
1677
|
+
kind: "ref",
|
|
1678
|
+
nullable: true,
|
|
1679
|
+
typeIndex: info.typeIndex,
|
|
1680
|
+
typeName: info.name,
|
|
1681
|
+
};
|
|
1682
|
+
if (l.kind === "Ident") {
|
|
1683
|
+
const localInfo = this.resolveLocal(l.name);
|
|
1684
|
+
const globalInfo = !localInfo ? this.mod.resolveGlobal(l.name) : null;
|
|
1685
|
+
if (localInfo?.isBoxed) {
|
|
1686
|
+
this.pushInstruction({ op: "local.get", index: localInfo.index });
|
|
1687
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1688
|
+
this.pushInstruction({
|
|
1689
|
+
op: "struct.set",
|
|
1690
|
+
typeIndex: localInfo.boxInfo.typeIndex,
|
|
1691
|
+
fieldIndex: 0,
|
|
1692
|
+
});
|
|
1693
|
+
} else if (localInfo) {
|
|
1694
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1695
|
+
this.pushInstruction({ op: "local.set", index: localInfo.index });
|
|
1696
|
+
} else if (globalInfo) {
|
|
1697
|
+
this.emitComplexBinary(synthBinary, cWType);
|
|
1698
|
+
this.pushInstruction({ op: "global.set", index: globalInfo.index });
|
|
1699
|
+
}
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
783
1704
|
if (l.kind === "SelectorExpr") {
|
|
784
1705
|
const structInfo = this._resolveStructInfo(l.expr);
|
|
785
1706
|
if (!structInfo) {
|
|
@@ -796,9 +1717,10 @@ export class StmtsEmitter {
|
|
|
796
1717
|
const lastStep = path[path.length - 1];
|
|
797
1718
|
const targetType = lastStep.field.wType;
|
|
798
1719
|
const baseWType = this.toWasmType(l.expr._type);
|
|
1720
|
+
const holderWType = this._fieldHolderWType(path, baseWType);
|
|
799
1721
|
|
|
800
|
-
const baseTmp = this.acquireTemp(
|
|
801
|
-
this.
|
|
1722
|
+
const baseTmp = this.acquireTemp(holderWType);
|
|
1723
|
+
this.emitSelectorBase(l.expr, baseWType);
|
|
802
1724
|
for (let i = 0; i < path.length - 1; i++) {
|
|
803
1725
|
const step = path[i];
|
|
804
1726
|
this.pushInstruction({
|
|
@@ -825,7 +1747,7 @@ export class StmtsEmitter {
|
|
|
825
1747
|
typeIndex: lastStep.structInfo.typeIndex,
|
|
826
1748
|
fieldIndex: lastStep.fieldIndex,
|
|
827
1749
|
});
|
|
828
|
-
this.releaseTemp(baseTmp,
|
|
1750
|
+
this.releaseTemp(baseTmp, holderWType);
|
|
829
1751
|
return;
|
|
830
1752
|
}
|
|
831
1753
|
|
|
@@ -882,11 +1804,9 @@ export class StmtsEmitter {
|
|
|
882
1804
|
baseType?.kind === "pointer" &&
|
|
883
1805
|
isArrayType(baseType.base, this.mod.checker);
|
|
884
1806
|
|
|
885
|
-
const elemGoType =
|
|
886
|
-
? baseType.
|
|
887
|
-
|
|
888
|
-
? baseType.base.elem
|
|
889
|
-
: baseType.elem;
|
|
1807
|
+
const elemGoType = this._getArrayElemType(
|
|
1808
|
+
isPtrToArr ? baseType.base : baseType,
|
|
1809
|
+
);
|
|
890
1810
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
891
1811
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
892
1812
|
const targetType = arrInfo.elemWType;
|
|
@@ -925,32 +1845,57 @@ export class StmtsEmitter {
|
|
|
925
1845
|
|
|
926
1846
|
if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
|
|
927
1847
|
const operand = l.operand ?? l.expr;
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
1848
|
+
const operandGoType =
|
|
1849
|
+
operand._type ??
|
|
1850
|
+
(operand.kind === "Ident"
|
|
1851
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1852
|
+
: null) ??
|
|
1853
|
+
this._resolveExprGoType(operand);
|
|
1854
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1855
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
|
|
1856
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1857
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1858
|
+
if (box && ptr) {
|
|
1859
|
+
const ptrWType = {
|
|
931
1860
|
kind: "ref",
|
|
932
1861
|
nullable: true,
|
|
933
|
-
typeIndex:
|
|
1862
|
+
typeIndex: ptr.typeIndex,
|
|
1863
|
+
typeName: ptr.name,
|
|
934
1864
|
};
|
|
935
|
-
const
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
938
|
-
this.pushInstruction({ op: "local.
|
|
939
|
-
this.
|
|
1865
|
+
const targetType = box.wType;
|
|
1866
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1867
|
+
this.emitExpr(operand, ptrWType);
|
|
1868
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1869
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
1870
|
+
|
|
1871
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1872
|
+
this.pushInstruction({
|
|
1873
|
+
op: "ref.cast",
|
|
1874
|
+
typeIndex: box.typeIndex,
|
|
1875
|
+
typeName: box.name,
|
|
1876
|
+
});
|
|
1877
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1878
|
+
this.pushInstruction({
|
|
1879
|
+
op: "ref.cast",
|
|
1880
|
+
typeIndex: box.typeIndex,
|
|
1881
|
+
typeName: box.name,
|
|
1882
|
+
});
|
|
940
1883
|
this.pushInstruction({
|
|
941
1884
|
op: "struct.get",
|
|
942
1885
|
typeIndex: box.typeIndex,
|
|
1886
|
+
typeName: box.name,
|
|
943
1887
|
fieldIndex: 0,
|
|
944
1888
|
});
|
|
945
|
-
const targetType = box.wType;
|
|
946
1889
|
this.emitExpr(r, targetType);
|
|
947
|
-
this.emitBinaryOp(baseOp, targetType, l._type ??
|
|
1890
|
+
this.emitBinaryOp(baseOp, targetType, l._type ?? baseGoType);
|
|
948
1891
|
this.pushInstruction({
|
|
949
1892
|
op: "struct.set",
|
|
950
1893
|
typeIndex: box.typeIndex,
|
|
1894
|
+
typeName: box.name,
|
|
951
1895
|
fieldIndex: 0,
|
|
952
1896
|
});
|
|
953
|
-
|
|
1897
|
+
|
|
1898
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
954
1899
|
return;
|
|
955
1900
|
}
|
|
956
1901
|
}
|
|
@@ -1038,36 +1983,57 @@ export class StmtsEmitter {
|
|
|
1038
1983
|
expr.kind === "StarExpr"
|
|
1039
1984
|
) {
|
|
1040
1985
|
const operand = expr.operand ?? expr.expr;
|
|
1041
|
-
const structInfo = this.
|
|
1986
|
+
const structInfo = this._derefStructInfo(operand);
|
|
1042
1987
|
if (!structInfo) {
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1988
|
+
const operandGoType =
|
|
1989
|
+
operand._type ??
|
|
1990
|
+
(operand.kind === "Ident"
|
|
1991
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1992
|
+
: null) ??
|
|
1993
|
+
this._resolveExprGoType(operand);
|
|
1994
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1995
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
|
|
1996
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1997
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1998
|
+
if (box && ptr) {
|
|
1999
|
+
const ptrWType = {
|
|
1046
2000
|
kind: "ref",
|
|
1047
2001
|
nullable: true,
|
|
1048
|
-
typeIndex:
|
|
2002
|
+
typeIndex: ptr.typeIndex,
|
|
2003
|
+
typeName: ptr.name,
|
|
1049
2004
|
};
|
|
1050
|
-
const
|
|
1051
|
-
this.emitExpr(operand,
|
|
1052
|
-
this.pushInstruction({ op: "local.set", index:
|
|
1053
|
-
this.
|
|
1054
|
-
|
|
2005
|
+
const ptrTmp = this.acquireTemp(ptrWType);
|
|
2006
|
+
this.emitExpr(operand, ptrWType);
|
|
2007
|
+
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
2008
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
2009
|
+
|
|
2010
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
2011
|
+
this.pushInstruction({
|
|
2012
|
+
op: "ref.cast",
|
|
2013
|
+
typeIndex: box.typeIndex,
|
|
2014
|
+
typeName: box.name,
|
|
2015
|
+
});
|
|
2016
|
+
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
2017
|
+
this.pushInstruction({
|
|
2018
|
+
op: "ref.cast",
|
|
2019
|
+
typeIndex: box.typeIndex,
|
|
2020
|
+
typeName: box.name,
|
|
2021
|
+
});
|
|
1055
2022
|
this.pushInstruction({
|
|
1056
2023
|
op: "struct.get",
|
|
1057
2024
|
typeIndex: box.typeIndex,
|
|
2025
|
+
typeName: box.name,
|
|
1058
2026
|
fieldIndex: 0,
|
|
1059
2027
|
});
|
|
1060
|
-
this._emitIncDecStep(
|
|
1061
|
-
op,
|
|
1062
|
-
box.wType,
|
|
1063
|
-
operand._type?.base ?? operand._type,
|
|
1064
|
-
);
|
|
2028
|
+
this._emitIncDecStep(op, box.wType, baseGoType);
|
|
1065
2029
|
this.pushInstruction({
|
|
1066
2030
|
op: "struct.set",
|
|
1067
2031
|
typeIndex: box.typeIndex,
|
|
2032
|
+
typeName: box.name,
|
|
1068
2033
|
fieldIndex: 0,
|
|
1069
2034
|
});
|
|
1070
|
-
|
|
2035
|
+
|
|
2036
|
+
this.releaseTemp(ptrTmp, ptrWType);
|
|
1071
2037
|
return;
|
|
1072
2038
|
}
|
|
1073
2039
|
}
|
|
@@ -1081,9 +2047,10 @@ export class StmtsEmitter {
|
|
|
1081
2047
|
const lastStep = path[path.length - 1];
|
|
1082
2048
|
const targetType = lastStep.field.wType;
|
|
1083
2049
|
const baseWType = this.toWasmType(expr.expr._type);
|
|
2050
|
+
const holderWType = this._fieldHolderWType(path, baseWType);
|
|
1084
2051
|
|
|
1085
|
-
const baseTmp = this.acquireTemp(
|
|
1086
|
-
this.
|
|
2052
|
+
const baseTmp = this.acquireTemp(holderWType);
|
|
2053
|
+
this.emitSelectorBase(expr.expr, baseWType);
|
|
1087
2054
|
for (let i = 0; i < path.length - 1; i++) {
|
|
1088
2055
|
const step = path[i];
|
|
1089
2056
|
this.pushInstruction({
|
|
@@ -1170,11 +2137,9 @@ export class StmtsEmitter {
|
|
|
1170
2137
|
baseType?.kind === "pointer" &&
|
|
1171
2138
|
isArrayType(baseType.base, this.mod.checker);
|
|
1172
2139
|
|
|
1173
|
-
const elemGoType =
|
|
1174
|
-
? baseType.
|
|
1175
|
-
|
|
1176
|
-
? baseType.base.elem
|
|
1177
|
-
: baseType.elem;
|
|
2140
|
+
const elemGoType = this._getArrayElemType(
|
|
2141
|
+
isPtrToArr ? baseType.base : baseType,
|
|
2142
|
+
);
|
|
1178
2143
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
1179
2144
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
1180
2145
|
const targetType = arrInfo.elemWType;
|
|
@@ -1325,7 +2290,7 @@ export class StmtsEmitter {
|
|
|
1325
2290
|
!this.capturedNames?.includes(lhs[0].name) &&
|
|
1326
2291
|
!this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
|
|
1327
2292
|
const wType = isNonEscaping ? "i32" : "i64";
|
|
1328
|
-
const lIdx = this.
|
|
2293
|
+
const lIdx = this._allocRangeVar(lhs[0].name, wType, {
|
|
1329
2294
|
kind: "basic",
|
|
1330
2295
|
name: "int",
|
|
1331
2296
|
});
|
|
@@ -1378,6 +2343,7 @@ export class StmtsEmitter {
|
|
|
1378
2343
|
// Emit body
|
|
1379
2344
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1380
2345
|
if (bodyBlock) {
|
|
2346
|
+
this._emitRangeBoxes();
|
|
1381
2347
|
this.emitBlock(bodyBlock);
|
|
1382
2348
|
}
|
|
1383
2349
|
|
|
@@ -1425,7 +2391,7 @@ export class StmtsEmitter {
|
|
|
1425
2391
|
let idxLocalInfo = null;
|
|
1426
2392
|
if (lhs[0] && lhs[0].name !== "_") {
|
|
1427
2393
|
if (!isAssign) {
|
|
1428
|
-
const lIdx = this.
|
|
2394
|
+
const lIdx = this._allocRangeVar(lhs[0].name, "i64", {
|
|
1429
2395
|
kind: "basic",
|
|
1430
2396
|
name: "int",
|
|
1431
2397
|
});
|
|
@@ -1440,7 +2406,7 @@ export class StmtsEmitter {
|
|
|
1440
2406
|
let valLocalInfo = null;
|
|
1441
2407
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1442
2408
|
if (!isAssign) {
|
|
1443
|
-
const lIdx = this.
|
|
2409
|
+
const lIdx = this._allocRangeVar(lhs[1].name, "i32", {
|
|
1444
2410
|
kind: "basic",
|
|
1445
2411
|
name: "rune",
|
|
1446
2412
|
});
|
|
@@ -1519,6 +2485,7 @@ export class StmtsEmitter {
|
|
|
1519
2485
|
// Body
|
|
1520
2486
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1521
2487
|
if (bodyBlock) {
|
|
2488
|
+
this._emitRangeBoxes();
|
|
1522
2489
|
this.emitBlock(bodyBlock);
|
|
1523
2490
|
}
|
|
1524
2491
|
|
|
@@ -1596,7 +2563,11 @@ export class StmtsEmitter {
|
|
|
1596
2563
|
let keyLocalInfo = null;
|
|
1597
2564
|
if (lhs[0] && lhs[0].name !== "_") {
|
|
1598
2565
|
if (!isAssign) {
|
|
1599
|
-
const lIdx = this.
|
|
2566
|
+
const lIdx = this._allocRangeVar(
|
|
2567
|
+
lhs[0].name,
|
|
2568
|
+
mapInfo.keyWType,
|
|
2569
|
+
keyType,
|
|
2570
|
+
);
|
|
1600
2571
|
keyLocalInfo = { index: lIdx, type: mapInfo.keyWType };
|
|
1601
2572
|
} else {
|
|
1602
2573
|
keyLocalInfo =
|
|
@@ -1608,7 +2579,11 @@ export class StmtsEmitter {
|
|
|
1608
2579
|
let valLocalInfo = null;
|
|
1609
2580
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1610
2581
|
if (!isAssign) {
|
|
1611
|
-
const lIdx = this.
|
|
2582
|
+
const lIdx = this._allocRangeVar(
|
|
2583
|
+
lhs[1].name,
|
|
2584
|
+
mapInfo.valWType,
|
|
2585
|
+
valType,
|
|
2586
|
+
);
|
|
1612
2587
|
valLocalInfo = { index: lIdx, type: mapInfo.valWType };
|
|
1613
2588
|
} else {
|
|
1614
2589
|
valLocalInfo =
|
|
@@ -1678,6 +2653,7 @@ export class StmtsEmitter {
|
|
|
1678
2653
|
typeIndex: mapInfo.entryTypeIndex,
|
|
1679
2654
|
fieldIndex: 0,
|
|
1680
2655
|
});
|
|
2656
|
+
this._emitCopyMapEntry(keyType, mapInfo.keyWType);
|
|
1681
2657
|
if (this.resolveLocal(lhs[0].name)) {
|
|
1682
2658
|
this.pushInstruction({
|
|
1683
2659
|
op: "local.set",
|
|
@@ -1699,15 +2675,7 @@ export class StmtsEmitter {
|
|
|
1699
2675
|
typeIndex: mapInfo.entryTypeIndex,
|
|
1700
2676
|
fieldIndex: 1,
|
|
1701
2677
|
});
|
|
1702
|
-
|
|
1703
|
-
isStructType(valType, this.mod.checker, this.mod) &&
|
|
1704
|
-
!isPointerToStruct(valType, this.mod.checker, this.mod);
|
|
1705
|
-
if (isValStruct) {
|
|
1706
|
-
const sInfo = this._resolveStructInfo({ _type: valType });
|
|
1707
|
-
if (sInfo) {
|
|
1708
|
-
this.emitCloneStruct(sInfo, mapInfo.valWType);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
2678
|
+
this._emitCopyMapEntry(valType, mapInfo.valWType);
|
|
1711
2679
|
if (this.resolveLocal(lhs[1].name)) {
|
|
1712
2680
|
this.pushInstruction({
|
|
1713
2681
|
op: "local.set",
|
|
@@ -1724,6 +2692,7 @@ export class StmtsEmitter {
|
|
|
1724
2692
|
// Body
|
|
1725
2693
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1726
2694
|
if (bodyBlock) {
|
|
2695
|
+
this._emitRangeBoxes();
|
|
1727
2696
|
this.emitBlock(bodyBlock);
|
|
1728
2697
|
}
|
|
1729
2698
|
|
|
@@ -1768,11 +2737,9 @@ export class StmtsEmitter {
|
|
|
1768
2737
|
iterType?.kind === "pointer" &&
|
|
1769
2738
|
isArrayType(iterType.base, this.mod.checker);
|
|
1770
2739
|
|
|
1771
|
-
const elemGoType =
|
|
1772
|
-
? iterType.
|
|
1773
|
-
|
|
1774
|
-
? iterType.base.elem
|
|
1775
|
-
: iterType.elem;
|
|
2740
|
+
const elemGoType = this._getArrayElemType(
|
|
2741
|
+
isPtrToArr ? iterType.base : iterType,
|
|
2742
|
+
);
|
|
1776
2743
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
1777
2744
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
1778
2745
|
|
|
@@ -1808,8 +2775,21 @@ export class StmtsEmitter {
|
|
|
1808
2775
|
});
|
|
1809
2776
|
}
|
|
1810
2777
|
this.releaseTemp(sliceTmp, sliceWType);
|
|
2778
|
+
} else if (!hasVal && !hasCallOrReceive(iterExpr)) {
|
|
2779
|
+
// Ranging over an array for its indexes only does not evaluate it
|
|
2780
|
+
// (`range p` on a nil array pointer is valid): the length is the
|
|
2781
|
+
// type's.
|
|
2782
|
+
this.pushInstruction({
|
|
2783
|
+
op: "i32.const",
|
|
2784
|
+
value: this._getArraySize(isPtrToArr ? iterType.base : iterType),
|
|
2785
|
+
});
|
|
2786
|
+
this.pushInstruction({ op: "local.set", index: lenTmp });
|
|
1811
2787
|
} else {
|
|
1812
2788
|
this.emitExpr(iterExpr, arrWType);
|
|
2789
|
+
// Go ranges over a copy of an array (the range expression is
|
|
2790
|
+
// evaluated once), so body writes to the array do not reach `v`.
|
|
2791
|
+
if (hasVal && !isPtrToArr)
|
|
2792
|
+
this._emitCopyIfValue(iterExpr, iterType, arrWType);
|
|
1813
2793
|
if (hasVal) {
|
|
1814
2794
|
this.pushInstruction({ op: "local.set", index: arrTmp });
|
|
1815
2795
|
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
@@ -1833,7 +2813,7 @@ export class StmtsEmitter {
|
|
|
1833
2813
|
!this.capturedNames?.includes(lhs[0].name) &&
|
|
1834
2814
|
!this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
|
|
1835
2815
|
const wType = isNonEscaping ? "i32" : "i64";
|
|
1836
|
-
const lIdx = this.
|
|
2816
|
+
const lIdx = this._allocRangeVar(lhs[0].name, wType, {
|
|
1837
2817
|
kind: "basic",
|
|
1838
2818
|
name: "int",
|
|
1839
2819
|
});
|
|
@@ -1847,7 +2827,7 @@ export class StmtsEmitter {
|
|
|
1847
2827
|
let valLocalInfo = null;
|
|
1848
2828
|
if (lhs[1] && lhs[1].name !== "_") {
|
|
1849
2829
|
if (!isAssign) {
|
|
1850
|
-
const lIdx = this.
|
|
2830
|
+
const lIdx = this._allocRangeVar(
|
|
1851
2831
|
lhs[1].name,
|
|
1852
2832
|
arrInfo.elemWType,
|
|
1853
2833
|
elemGoType,
|
|
@@ -1912,6 +2892,11 @@ export class StmtsEmitter {
|
|
|
1912
2892
|
if (sInfo) {
|
|
1913
2893
|
this.emitCloneStruct(sInfo, arrInfo.elemWType);
|
|
1914
2894
|
}
|
|
2895
|
+
} else if (
|
|
2896
|
+
elemGoType?.kind !== "pointer" &&
|
|
2897
|
+
isArrayType(elemGoType, this.mod.checker)
|
|
2898
|
+
) {
|
|
2899
|
+
this.emitCopyArray(elemGoType);
|
|
1915
2900
|
}
|
|
1916
2901
|
if (this.resolveLocal(lhs[1].name)) {
|
|
1917
2902
|
this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
|
|
@@ -1926,6 +2911,7 @@ export class StmtsEmitter {
|
|
|
1926
2911
|
// Body
|
|
1927
2912
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
1928
2913
|
if (bodyBlock) {
|
|
2914
|
+
this._emitRangeBoxes();
|
|
1929
2915
|
this.emitBlock(bodyBlock);
|
|
1930
2916
|
}
|
|
1931
2917
|
|
|
@@ -1955,7 +2941,102 @@ export class StmtsEmitter {
|
|
|
1955
2941
|
restoreShadowed();
|
|
1956
2942
|
}
|
|
1957
2943
|
|
|
2944
|
+
_initIterState(stmt) {
|
|
2945
|
+
if (!stmt._iterStateName) return;
|
|
2946
|
+
let stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
2947
|
+
if (!stateLoc) {
|
|
2948
|
+
this.allocLocal(stmt._iterStateName, "i64", {
|
|
2949
|
+
kind: "basic",
|
|
2950
|
+
name: "int",
|
|
2951
|
+
});
|
|
2952
|
+
stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
2953
|
+
}
|
|
2954
|
+
if (stateLoc?.isBoxed) {
|
|
2955
|
+
this.pushInstruction({ op: "i64.const", value: 0n });
|
|
2956
|
+
this.pushInstruction({
|
|
2957
|
+
op: "struct.new",
|
|
2958
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
2959
|
+
});
|
|
2960
|
+
this.pushInstruction({ op: "local.set", index: stateLoc.index });
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
_emitIteratorRange(stmt, rangeExpr) {
|
|
2965
|
+
const funcLit = getOrCreateIteratorFuncLit(stmt);
|
|
2966
|
+
const iterExpr = rangeExpr.expr;
|
|
2967
|
+
const iterType = this._resolveExprGoType(iterExpr) ?? iterExpr._type;
|
|
2968
|
+
const iterInfo = this.mod.getClosureType(iterType);
|
|
2969
|
+
const iterWType = {
|
|
2970
|
+
kind: "ref",
|
|
2971
|
+
nullable: true,
|
|
2972
|
+
typeIndex: iterInfo.typeIndex,
|
|
2973
|
+
};
|
|
2974
|
+
|
|
2975
|
+
this._initIterState(stmt);
|
|
2976
|
+
|
|
2977
|
+
const iterTmp = this.acquireTemp(iterWType);
|
|
2978
|
+
this.emitExpr(iterExpr, iterWType);
|
|
2979
|
+
this.pushInstruction({ op: "local.set", index: iterTmp });
|
|
2980
|
+
|
|
2981
|
+
const yieldClosureInfo = this.mod.getClosureType(funcLit._type ?? funcLit);
|
|
2982
|
+
const yieldClosureWType = {
|
|
2983
|
+
kind: "ref",
|
|
2984
|
+
nullable: true,
|
|
2985
|
+
typeIndex: yieldClosureInfo.typeIndex,
|
|
2986
|
+
};
|
|
2987
|
+
const yieldTmp = this.acquireTemp(yieldClosureWType);
|
|
2988
|
+
this.emitFuncLit(funcLit);
|
|
2989
|
+
this.pushInstruction({ op: "local.set", index: yieldTmp });
|
|
2990
|
+
|
|
2991
|
+
// Outer block for break
|
|
2992
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2993
|
+
this.pushControl("break", stmt.label);
|
|
2994
|
+
|
|
2995
|
+
// Push env of iterTmp (param 0)
|
|
2996
|
+
this.pushInstruction({ op: "local.get", index: iterTmp });
|
|
2997
|
+
this.pushInstruction({
|
|
2998
|
+
op: "struct.get",
|
|
2999
|
+
typeIndex: iterInfo.typeIndex,
|
|
3000
|
+
fieldIndex: 1,
|
|
3001
|
+
});
|
|
3002
|
+
|
|
3003
|
+
// Push yield callback closure (param 1)
|
|
3004
|
+
this.pushInstruction({ op: "local.get", index: yieldTmp });
|
|
3005
|
+
|
|
3006
|
+
// Push funcref of iterTmp
|
|
3007
|
+
this.pushInstruction({ op: "local.get", index: iterTmp });
|
|
3008
|
+
this.pushInstruction({
|
|
3009
|
+
op: "struct.get",
|
|
3010
|
+
typeIndex: iterInfo.typeIndex,
|
|
3011
|
+
fieldIndex: 0,
|
|
3012
|
+
});
|
|
3013
|
+
|
|
3014
|
+
// Call iterator via call_ref
|
|
3015
|
+
this.pushInstruction({
|
|
3016
|
+
op: "call_ref",
|
|
3017
|
+
typeIndex: iterInfo.funcTypeIndex,
|
|
3018
|
+
});
|
|
3019
|
+
|
|
3020
|
+
// If iterator returned any values, drop them
|
|
3021
|
+
for (let i = 0; i < (iterInfo.returnWTypes?.length ?? 0); i++) {
|
|
3022
|
+
this.pushInstruction("drop");
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
this.pushInstruction("end");
|
|
3026
|
+
|
|
3027
|
+
this.releaseTemp(yieldTmp, yieldClosureWType);
|
|
3028
|
+
this.releaseTemp(iterTmp, iterWType);
|
|
3029
|
+
|
|
3030
|
+
this._emitIteratorUnwind(stmt);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
1958
3033
|
emitForStmt(stmt) {
|
|
3034
|
+
if (isIteratorRangeFor(stmt)) {
|
|
3035
|
+
const rangeExpr =
|
|
3036
|
+
stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
|
|
3037
|
+
this._emitIteratorRange(stmt, rangeExpr);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
1959
3040
|
if (isRangeFor(stmt) || stmt.cond?.kind === "RangeExpr") {
|
|
1960
3041
|
this.emitRangeForStmt(stmt);
|
|
1961
3042
|
return;
|
|
@@ -2000,14 +3081,23 @@ export class StmtsEmitter {
|
|
|
2000
3081
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2001
3082
|
this.pushControl("continue", stmt.label);
|
|
2002
3083
|
|
|
3084
|
+
// The body is its own scope: `i := i` must not rebind the loop
|
|
3085
|
+
// variable that the post statement increments.
|
|
2003
3086
|
const bodyBlock = stmt.body ?? stmt.block;
|
|
2004
3087
|
if (bodyBlock) {
|
|
2005
|
-
this.
|
|
3088
|
+
const outer = new Map(this.locals);
|
|
3089
|
+
try {
|
|
3090
|
+
this.emitBlock(bodyBlock);
|
|
3091
|
+
} finally {
|
|
3092
|
+
this.locals = outer;
|
|
3093
|
+
}
|
|
2006
3094
|
}
|
|
2007
3095
|
|
|
2008
3096
|
// End inner continue block
|
|
2009
3097
|
this.pushInstruction("end");
|
|
2010
3098
|
|
|
3099
|
+
this._emitPerIterationBoxes(stmt);
|
|
3100
|
+
|
|
2011
3101
|
if (iv) {
|
|
2012
3102
|
this.pushInstruction({ op: "local.get", index: ivLocalInfo.index });
|
|
2013
3103
|
this.pushInstruction({ op: "i32.const", value: iv.step });
|
|
@@ -2035,6 +3125,46 @@ export class StmtsEmitter {
|
|
|
2035
3125
|
}
|
|
2036
3126
|
}
|
|
2037
3127
|
|
|
3128
|
+
// Go 1.22 per-iteration loop variables. For each three-clause `for`
|
|
3129
|
+
// variable that a closure in this loop captures, give the next iteration a
|
|
3130
|
+
// fresh copy before the post statement: closures created in this
|
|
3131
|
+
// iteration keep their own variable, the post statement (and the next
|
|
3132
|
+
// iteration) work on the new one, which starts from the current value.
|
|
3133
|
+
// A boxed scalar gets a new box; a value struct or array (a ref) is also
|
|
3134
|
+
// cloned. Uncaptured variables stay plain locals with no allocation.
|
|
3135
|
+
_emitPerIterationBoxes(stmt) {
|
|
3136
|
+
for (const name of loopVarsCapturedInLoop(stmt)) {
|
|
3137
|
+
const info = this.locals.get(name);
|
|
3138
|
+
if (!info) continue;
|
|
3139
|
+
const goType = info.goType;
|
|
3140
|
+
const cloneValue = (wType) => {
|
|
3141
|
+
if (goType?.kind === "pointer") return;
|
|
3142
|
+
if (isArrayType(goType, this.mod.checker)) {
|
|
3143
|
+
this.emitCopyArray(goType);
|
|
3144
|
+
} else if (isStructType(goType, this.mod.checker, this.mod)) {
|
|
3145
|
+
const sInfo = this._resolveStructInfo({ _type: goType });
|
|
3146
|
+
if (sInfo) this.emitCloneStruct(sInfo, wType);
|
|
3147
|
+
}
|
|
3148
|
+
};
|
|
3149
|
+
this.pushInstruction({ op: "local.get", index: info.index });
|
|
3150
|
+
if (info.isBoxed) {
|
|
3151
|
+
this.pushInstruction({
|
|
3152
|
+
op: "struct.get",
|
|
3153
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
3154
|
+
fieldIndex: 0,
|
|
3155
|
+
});
|
|
3156
|
+
cloneValue(info.boxInfo.wType);
|
|
3157
|
+
this.pushInstruction({
|
|
3158
|
+
op: "struct.new",
|
|
3159
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
3160
|
+
});
|
|
3161
|
+
} else {
|
|
3162
|
+
cloneValue(info.type);
|
|
3163
|
+
}
|
|
3164
|
+
this.pushInstruction({ op: "local.set", index: info.index });
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
|
|
2038
3168
|
_detectInductionVar(stmt) {
|
|
2039
3169
|
if (stmt.init?.kind !== "DefineStmt") return null;
|
|
2040
3170
|
const lhs = stmt.init.lhs ?? [];
|
|
@@ -2193,7 +3323,102 @@ export class StmtsEmitter {
|
|
|
2193
3323
|
return false;
|
|
2194
3324
|
}
|
|
2195
3325
|
|
|
3326
|
+
// Each case clause is its own scope: a name declared in one clause must not
|
|
3327
|
+
// stay visible in the clauses after it.
|
|
3328
|
+
_emitClauseBody(stmts) {
|
|
3329
|
+
const outer = new Map(this.locals);
|
|
3330
|
+
try {
|
|
3331
|
+
for (const s of stmts ?? []) this.emitStmt(s);
|
|
3332
|
+
} finally {
|
|
3333
|
+
this.locals = outer;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// A switch with `fallthrough` dispatches to one block per clause, in source
|
|
3338
|
+
// order, so a clause that falls through simply runs into the next body.
|
|
3339
|
+
_emitFallthroughSwitch(stmt) {
|
|
3340
|
+
const getCaseVals = (c) => c.list ?? c.values ?? [];
|
|
3341
|
+
const cases = stmt.cases ?? [];
|
|
3342
|
+
const n = cases.length;
|
|
3343
|
+
const isFall = (s) =>
|
|
3344
|
+
s?.kind === "BranchStmt" && s.keyword === "fallthrough";
|
|
3345
|
+
|
|
3346
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3347
|
+
this.pushControl("break", stmt.label);
|
|
3348
|
+
if (stmt.init) this.emitStmt(stmt.init);
|
|
3349
|
+
|
|
3350
|
+
let tagGoType = null;
|
|
3351
|
+
let tagWType = null;
|
|
3352
|
+
let tagTmp = -1;
|
|
3353
|
+
if (stmt.tag) {
|
|
3354
|
+
tagGoType = stmt.tag._type ?? this._resolveExprGoType(stmt.tag);
|
|
3355
|
+
tagWType = this.toWasmType(tagGoType);
|
|
3356
|
+
tagTmp = this.acquireTemp(tagWType);
|
|
3357
|
+
this.emitExpr(stmt.tag, tagWType);
|
|
3358
|
+
this.pushInstruction({ op: "local.set", index: tagTmp });
|
|
3359
|
+
}
|
|
3360
|
+
for (let i = 0; i < n; i++) {
|
|
3361
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3362
|
+
}
|
|
3363
|
+
let defaultIdx = -1;
|
|
3364
|
+
for (let i = 0; i < n; i++) {
|
|
3365
|
+
const vals = getCaseVals(cases[i]);
|
|
3366
|
+
if (vals.length === 0) {
|
|
3367
|
+
defaultIdx = i;
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
|
|
3371
|
+
// The innermost block ends the first clause, so clause i is depth i.
|
|
3372
|
+
this.pushInstruction({ op: "br_if", depth: i });
|
|
3373
|
+
}
|
|
3374
|
+
this.pushInstruction({ op: "br", depth: defaultIdx >= 0 ? defaultIdx : n });
|
|
3375
|
+
if (stmt.tag) this.releaseTemp(tagTmp, tagWType);
|
|
3376
|
+
|
|
3377
|
+
for (let i = 0; i < n; i++) {
|
|
3378
|
+
this.pushInstruction("end");
|
|
3379
|
+
const body = cases[i].stmts ?? [];
|
|
3380
|
+
const falls = isFall(body[body.length - 1]);
|
|
3381
|
+
this._emitClauseBody(falls ? body.slice(0, -1) : body);
|
|
3382
|
+
if (!falls && i < n - 1) {
|
|
3383
|
+
this.pushInstruction({ op: "br", depth: n - 1 - i });
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
this.pushInstruction("end");
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// Leaves an i32 on the stack: whether any expression of a case list matches.
|
|
3390
|
+
// Later expressions are not evaluated once one matches, as in Go.
|
|
3391
|
+
_emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType) {
|
|
3392
|
+
for (let j = 0; j < vals.length; j++) {
|
|
3393
|
+
if (stmt.tag) {
|
|
3394
|
+
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
3395
|
+
this.emitExpr(vals[j], tagWType);
|
|
3396
|
+
this._emitElemEq(tagGoType, tagWType);
|
|
3397
|
+
} else {
|
|
3398
|
+
this.emitExpr(vals[j], "i32");
|
|
3399
|
+
}
|
|
3400
|
+
if (j < vals.length - 1) {
|
|
3401
|
+
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
3402
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3403
|
+
this.pushInstruction("else");
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
for (let j = 0; j < vals.length - 1; j++) this.pushInstruction("end");
|
|
3407
|
+
}
|
|
3408
|
+
|
|
2196
3409
|
emitSwitchStmt(stmt) {
|
|
3410
|
+
if (
|
|
3411
|
+
(stmt.cases ?? []).some((c) => {
|
|
3412
|
+
const b = c.stmts ?? [];
|
|
3413
|
+
return (
|
|
3414
|
+
b[b.length - 1]?.kind === "BranchStmt" &&
|
|
3415
|
+
b[b.length - 1].keyword === "fallthrough"
|
|
3416
|
+
);
|
|
3417
|
+
})
|
|
3418
|
+
) {
|
|
3419
|
+
this._emitFallthroughSwitch(stmt);
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
2197
3422
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2198
3423
|
this.pushControl("break", stmt.label);
|
|
2199
3424
|
|
|
@@ -2221,25 +3446,16 @@ export class StmtsEmitter {
|
|
|
2221
3446
|
let ifCount = 0;
|
|
2222
3447
|
for (const c of nonDefaultCases) {
|
|
2223
3448
|
const vals = getCaseVals(c);
|
|
2224
|
-
|
|
2225
|
-
if (stmt.tag) {
|
|
2226
|
-
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
2227
|
-
this.emitExpr(vals[i], tagWType);
|
|
2228
|
-
this.emitBinaryOp("==", tagWType, tagGoType);
|
|
2229
|
-
} else {
|
|
2230
|
-
this.emitExpr(vals[i], "i32");
|
|
2231
|
-
}
|
|
2232
|
-
if (i > 0) this.pushInstruction("i32.or");
|
|
2233
|
-
}
|
|
3449
|
+
this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
|
|
2234
3450
|
this.pushInstruction({ op: "if", blockType: "void" });
|
|
2235
3451
|
ifCount++;
|
|
2236
3452
|
|
|
2237
|
-
|
|
3453
|
+
this._emitClauseBody(c.stmts);
|
|
2238
3454
|
this.pushInstruction("else");
|
|
2239
3455
|
}
|
|
2240
3456
|
|
|
2241
3457
|
if (defaultCase) {
|
|
2242
|
-
|
|
3458
|
+
this._emitClauseBody(defaultCase.stmts);
|
|
2243
3459
|
}
|
|
2244
3460
|
|
|
2245
3461
|
for (let i = 0; i < ifCount; i++) {
|
|
@@ -2252,6 +3468,17 @@ export class StmtsEmitter {
|
|
|
2252
3468
|
this.pushInstruction("end");
|
|
2253
3469
|
}
|
|
2254
3470
|
|
|
3471
|
+
// Wraps the value on the stack in a box when the just-declared local `name`
|
|
3472
|
+
// is captured and mutated.
|
|
3473
|
+
_emitBoxIfCaptured(name) {
|
|
3474
|
+
const info = this.locals.get(name);
|
|
3475
|
+
if (info?.isBoxed)
|
|
3476
|
+
this.pushInstruction({
|
|
3477
|
+
op: "struct.new",
|
|
3478
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
|
|
2255
3482
|
emitTypeSwitchStmt(stmt) {
|
|
2256
3483
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2257
3484
|
this.pushControl("break", stmt.label);
|
|
@@ -2285,9 +3512,14 @@ export class StmtsEmitter {
|
|
|
2285
3512
|
ifCount++;
|
|
2286
3513
|
|
|
2287
3514
|
if (stmt.assign) {
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
3515
|
+
// `case nil` binds the switch expression's own (null) value.
|
|
3516
|
+
const isNilCase =
|
|
3517
|
+
types.length === 1 &&
|
|
3518
|
+
(types[0].name === "nil" || types[0]._type?.name === "nil");
|
|
3519
|
+
const targetGoType = isNilCase
|
|
3520
|
+
? (stmt.expr._type ?? { kind: "basic", name: "any" })
|
|
3521
|
+
: types.length === 1
|
|
3522
|
+
? (types[0]._type ?? this._resolveTestTarget(types[0]))
|
|
2291
3523
|
: { kind: "basic", name: "any" };
|
|
2292
3524
|
const targetWType = this.toWasmType(targetGoType);
|
|
2293
3525
|
const assignLocal = this.allocLocal(
|
|
@@ -2295,18 +3527,23 @@ export class StmtsEmitter {
|
|
|
2295
3527
|
targetWType,
|
|
2296
3528
|
targetGoType,
|
|
2297
3529
|
);
|
|
2298
|
-
if (types.length === 1) {
|
|
3530
|
+
if (types.length === 1 && !isNilCase) {
|
|
2299
3531
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
2300
3532
|
this._emitTypeCast(targetGoType, targetWType);
|
|
3533
|
+
// The bound array is a copy of the boxed value.
|
|
3534
|
+
if (
|
|
3535
|
+
targetGoType?.kind !== "pointer" &&
|
|
3536
|
+
isArrayType(targetGoType, this.mod.checker)
|
|
3537
|
+
)
|
|
3538
|
+
this.emitCopyArray(targetGoType);
|
|
2301
3539
|
} else {
|
|
2302
3540
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
2303
3541
|
}
|
|
3542
|
+
this._emitBoxIfCaptured(stmt.assign);
|
|
2304
3543
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
2305
3544
|
}
|
|
2306
3545
|
|
|
2307
|
-
|
|
2308
|
-
this.emitStmt(s);
|
|
2309
|
-
}
|
|
3546
|
+
this._emitClauseBody(c.stmts);
|
|
2310
3547
|
|
|
2311
3548
|
this.pushInstruction("else");
|
|
2312
3549
|
}
|
|
@@ -2318,11 +3555,10 @@ export class StmtsEmitter {
|
|
|
2318
3555
|
name: "any",
|
|
2319
3556
|
});
|
|
2320
3557
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
3558
|
+
this._emitBoxIfCaptured(stmt.assign);
|
|
2321
3559
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
2322
3560
|
}
|
|
2323
|
-
|
|
2324
|
-
this.emitStmt(s);
|
|
2325
|
-
}
|
|
3561
|
+
this._emitClauseBody(defaultCase.stmts);
|
|
2326
3562
|
}
|
|
2327
3563
|
|
|
2328
3564
|
for (let i = 0; i < ifCount; i++) {
|
|
@@ -2340,13 +3576,201 @@ export class StmtsEmitter {
|
|
|
2340
3576
|
this.pushInstruction("end");
|
|
2341
3577
|
}
|
|
2342
3578
|
|
|
3579
|
+
_propagateYieldExit(tag) {
|
|
3580
|
+
if (this.funcDecl._iterStateName) {
|
|
3581
|
+
const outerLoc = this.resolveLocal(this.funcDecl._iterStateName);
|
|
3582
|
+
if (outerLoc?.isBoxed) {
|
|
3583
|
+
this.pushInstruction({ op: "local.get", index: outerLoc.index });
|
|
3584
|
+
this.pushInstruction({ op: "i64.const", value: tag });
|
|
3585
|
+
this.pushInstruction({
|
|
3586
|
+
op: "struct.set",
|
|
3587
|
+
typeIndex: outerLoc.boxInfo.typeIndex,
|
|
3588
|
+
fieldIndex: 0,
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3593
|
+
this.pushInstruction({ op: "return" });
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
_unwindIteratorReturn() {
|
|
3597
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3598
|
+
this._propagateYieldExit(2n);
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
if (this.hasDefer) {
|
|
3602
|
+
const depth = this.resolveBranchDepthToRole("runDefers");
|
|
3603
|
+
this.pushInstruction({ op: "br", depth });
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
const rootNamed = this.funcDecl.returnType?._namedReturns ?? [];
|
|
3607
|
+
for (const nr of rootNamed) {
|
|
3608
|
+
const loc = this.locals.get(nr.name);
|
|
3609
|
+
if (loc?.isBoxed) {
|
|
3610
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
3611
|
+
this.pushInstruction({
|
|
3612
|
+
op: "struct.get",
|
|
3613
|
+
typeIndex: loc.boxInfo.typeIndex,
|
|
3614
|
+
fieldIndex: 0,
|
|
3615
|
+
});
|
|
3616
|
+
} else if (loc) {
|
|
3617
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
this.pushInstruction({ op: "return" });
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
_unwindIteratorTargets(stmt, stateTmp, labelTargets) {
|
|
3624
|
+
for (const target of labelTargets) {
|
|
3625
|
+
this.pushInstruction({ op: "local.get", index: stateTmp });
|
|
3626
|
+
this.pushInstruction({ op: "i64.const", value: BigInt(target.tag) });
|
|
3627
|
+
this.pushInstruction("i64.eq");
|
|
3628
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3629
|
+
if (target.role !== "break" || stmt.label !== target.label) {
|
|
3630
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3631
|
+
this._propagateYieldExit(BigInt(target.tag));
|
|
3632
|
+
} else {
|
|
3633
|
+
const depth = this.resolveBranchDepth(
|
|
3634
|
+
target.label,
|
|
3635
|
+
target.role === "continue",
|
|
3636
|
+
);
|
|
3637
|
+
this.pushInstruction({ op: "br", depth });
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
this.pushInstruction("end");
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
_emitIteratorUnwind(stmt) {
|
|
3645
|
+
if (!stmt._iterStateName) return;
|
|
3646
|
+
const stateLoc = this.resolveLocal(stmt._iterStateName);
|
|
3647
|
+
if (!stateLoc?.isBoxed) return;
|
|
3648
|
+
|
|
3649
|
+
const funcLit = getOrCreateIteratorFuncLit(stmt);
|
|
3650
|
+
const hasRet = hasReturnInBlock(funcLit?.body);
|
|
3651
|
+
const labelTargets = Array.from(
|
|
3652
|
+
this.rootFuncDecl?._labelTargets?.values() ?? [],
|
|
3653
|
+
);
|
|
3654
|
+
if (!hasRet && labelTargets.length === 0) return;
|
|
3655
|
+
|
|
3656
|
+
const stateTmp = this.acquireTemp("i64");
|
|
3657
|
+
this.pushInstruction({ op: "local.get", index: stateLoc.index });
|
|
3658
|
+
this.pushInstruction({
|
|
3659
|
+
op: "struct.get",
|
|
3660
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
3661
|
+
fieldIndex: 0,
|
|
3662
|
+
});
|
|
3663
|
+
this.pushInstruction({ op: "local.set", index: stateTmp });
|
|
3664
|
+
|
|
3665
|
+
if (hasRet) {
|
|
3666
|
+
this.pushInstruction({ op: "local.get", index: stateTmp });
|
|
3667
|
+
this.pushInstruction({ op: "i64.const", value: 2n });
|
|
3668
|
+
this.pushInstruction("i64.eq");
|
|
3669
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3670
|
+
this._unwindIteratorReturn();
|
|
3671
|
+
this.pushInstruction("end");
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
this._unwindIteratorTargets(stmt, stateTmp, labelTargets);
|
|
3675
|
+
this.releaseTemp(stateTmp, "i64");
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
_tryEmitYieldBranch(stmt) {
|
|
3679
|
+
if (!this.funcDecl._isYieldClosure) return false;
|
|
3680
|
+
const isContinue = stmt.keyword === "continue";
|
|
3681
|
+
const targetRole = isContinue ? "continue" : "break";
|
|
3682
|
+
const hasInnerTarget = this.controlStack.some((c) =>
|
|
3683
|
+
stmt.label
|
|
3684
|
+
? c.label === stmt.label && c.role === targetRole
|
|
3685
|
+
: c.role === targetRole,
|
|
3686
|
+
);
|
|
3687
|
+
if (hasInnerTarget) return false;
|
|
3688
|
+
|
|
3689
|
+
if (!stmt.label) {
|
|
3690
|
+
if (stmt.keyword === "break") {
|
|
3691
|
+
this._propagateYieldExit(1n);
|
|
3692
|
+
return true;
|
|
3693
|
+
}
|
|
3694
|
+
if (stmt.keyword === "continue") {
|
|
3695
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3696
|
+
this.pushInstruction({ op: "return" });
|
|
3697
|
+
return true;
|
|
3698
|
+
}
|
|
3699
|
+
return false;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
if (
|
|
3703
|
+
stmt.keyword === "continue" &&
|
|
3704
|
+
stmt.label === this.funcDecl._forStmt?.label
|
|
3705
|
+
) {
|
|
3706
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3707
|
+
this.pushInstruction({ op: "return" });
|
|
3708
|
+
return true;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
const target = this._findLabelTarget(stmt.label, targetRole);
|
|
3712
|
+
const tag = target ? BigInt(target.tag) : 1n;
|
|
3713
|
+
this._propagateYieldExit(tag);
|
|
3714
|
+
return true;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
_emitYieldReturn(stmt) {
|
|
3718
|
+
const values = stmt.values ?? [];
|
|
3719
|
+
const rootNamed = this.rootFuncDecl?.returnType?._namedReturns ?? [];
|
|
3720
|
+
if (values.length === 1 && rootNamed.length > 1) {
|
|
3721
|
+
this.emitExpr(values[0]);
|
|
3722
|
+
for (let i = rootNamed.length - 1; i >= 0; i--) {
|
|
3723
|
+
this._assignToNamedReturnVar(rootNamed[i]);
|
|
3724
|
+
}
|
|
3725
|
+
} else if (values.length > 0 && rootNamed.length > 0) {
|
|
3726
|
+
const temps = [];
|
|
3727
|
+
for (let i = 0; i < values.length; i++) {
|
|
3728
|
+
const val = values[i];
|
|
3729
|
+
const nr = rootNamed[i];
|
|
3730
|
+
const targetWType = this.toWasmType(nr?.type ?? val._type);
|
|
3731
|
+
this.emitExpr(val, targetWType);
|
|
3732
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
3733
|
+
const tmp = this.acquireTemp(targetWType);
|
|
3734
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
3735
|
+
temps.push({ tmp, type: targetWType });
|
|
3736
|
+
}
|
|
3737
|
+
for (let i = 0; i < temps.length; i++) {
|
|
3738
|
+
this.pushInstruction({ op: "local.get", index: temps[i].tmp });
|
|
3739
|
+
this._assignToNamedReturnVar(rootNamed[i]);
|
|
3740
|
+
this.releaseTemp(temps[i].tmp, temps[i].type);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
if (this.funcDecl._iterStateName) {
|
|
3745
|
+
const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
|
|
3746
|
+
if (stateLoc?.isBoxed) {
|
|
3747
|
+
this.pushInstruction({ op: "local.get", index: stateLoc.index });
|
|
3748
|
+
this.pushInstruction({ op: "i64.const", value: 2n });
|
|
3749
|
+
this.pushInstruction({
|
|
3750
|
+
op: "struct.set",
|
|
3751
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
3752
|
+
fieldIndex: 0,
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3757
|
+
this.pushInstruction({ op: "return" });
|
|
3758
|
+
}
|
|
3759
|
+
|
|
2343
3760
|
emitBranchStmt(stmt) {
|
|
3761
|
+
const alias = stmt.label && this._labelAliases?.get(stmt.label);
|
|
3762
|
+
if (alias) stmt = { ...stmt, label: alias };
|
|
3763
|
+
if (this._tryEmitYieldBranch(stmt)) return;
|
|
2344
3764
|
const isContinue = stmt.keyword === "continue";
|
|
2345
3765
|
const depth = this.resolveBranchDepth(stmt.label, isContinue);
|
|
2346
3766
|
this.pushInstruction({ op: "br", depth });
|
|
2347
3767
|
}
|
|
2348
3768
|
|
|
2349
3769
|
emitReturnStmt(stmt) {
|
|
3770
|
+
if (this.funcDecl._isYieldClosure) {
|
|
3771
|
+
this._emitYieldReturn(stmt);
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
2350
3774
|
const values = stmt.values ?? [];
|
|
2351
3775
|
if (this.hasDefer) {
|
|
2352
3776
|
if (this.namedReturnVars?.length > 0) {
|
|
@@ -2365,7 +3789,7 @@ export class StmtsEmitter {
|
|
|
2365
3789
|
const val = values[i];
|
|
2366
3790
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2367
3791
|
this.emitExpr(val, targetWType);
|
|
2368
|
-
this.
|
|
3792
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2369
3793
|
const tmp = this.acquireTemp(targetWType);
|
|
2370
3794
|
this.pushInstruction({ op: "local.set", index: tmp });
|
|
2371
3795
|
temps.push({ tmp, type: targetWType });
|
|
@@ -2390,7 +3814,7 @@ export class StmtsEmitter {
|
|
|
2390
3814
|
const val = values[i];
|
|
2391
3815
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2392
3816
|
this.emitExpr(val, targetWType);
|
|
2393
|
-
this.
|
|
3817
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2394
3818
|
this.pushInstruction({
|
|
2395
3819
|
op: "local.set",
|
|
2396
3820
|
index: this.returnTempLocals[i],
|
|
@@ -2421,17 +3845,38 @@ export class StmtsEmitter {
|
|
|
2421
3845
|
return;
|
|
2422
3846
|
}
|
|
2423
3847
|
|
|
3848
|
+
if (values.length === 1 && this.returnTypes.length > 1) {
|
|
3849
|
+
this.emitExpr(values[0]);
|
|
3850
|
+
this.pushInstruction("return");
|
|
3851
|
+
return;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
2424
3854
|
for (let i = 0; i < values.length; i++) {
|
|
2425
3855
|
const val = values[i];
|
|
2426
3856
|
const targetWType = this.returnTypes[i] ?? null;
|
|
2427
3857
|
this.emitExpr(val, targetWType);
|
|
2428
|
-
this.
|
|
3858
|
+
this._emitCopyReturnValue(val, targetWType);
|
|
2429
3859
|
}
|
|
2430
3860
|
this.pushInstruction("return");
|
|
2431
3861
|
}
|
|
2432
3862
|
|
|
3863
|
+
// A value returned as an interface was copied when it was boxed; copying it
|
|
3864
|
+
// again by its own type would treat the box as the struct.
|
|
3865
|
+
_emitCopyReturnValue(val, targetWType) {
|
|
3866
|
+
if (targetWType === "anyref") return;
|
|
3867
|
+
this._emitCopyIfValue(val, val._type, targetWType);
|
|
3868
|
+
}
|
|
3869
|
+
|
|
2433
3870
|
_assignToNamedReturnVar(nr) {
|
|
3871
|
+
if (!nr?.name || nr.name === "_") {
|
|
3872
|
+
this.pushInstruction("drop");
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
2434
3875
|
const loc = this.locals.get(nr.name);
|
|
3876
|
+
if (!loc) {
|
|
3877
|
+
this.pushInstruction("drop");
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
2435
3880
|
if (loc.isBoxed) {
|
|
2436
3881
|
const valTmp = this.acquireTemp(loc.boxInfo.wType);
|
|
2437
3882
|
this.pushInstruction({ op: "local.set", index: valTmp });
|