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
package/src/backend/wasm/emit.js
CHANGED
|
@@ -9,17 +9,12 @@ import { ExprsEmitter } from "./emit-exprs.js";
|
|
|
9
9
|
import { MapsEmitter } from "./emit-maps.js";
|
|
10
10
|
import { SharedEmitter } from "./emit-shared.js";
|
|
11
11
|
import { StmtsEmitter } from "./emit-stmts.js";
|
|
12
|
-
import {
|
|
13
|
-
BytesEmitter,
|
|
14
|
-
ExtendedPackagesEmitter,
|
|
15
|
-
IoEmitter,
|
|
16
|
-
PurePackagesEmitter,
|
|
17
|
-
SimdEmitter,
|
|
18
|
-
StdlibCoreEmitter,
|
|
19
|
-
TimeEmitter,
|
|
20
|
-
} from "./stdlib/index.js";
|
|
12
|
+
import { stdlibEmitters } from "./stdlib/index.js";
|
|
21
13
|
import {
|
|
22
14
|
isArrayType,
|
|
15
|
+
isComplex,
|
|
16
|
+
isComplex64,
|
|
17
|
+
isFuncType,
|
|
23
18
|
isPointerToStruct,
|
|
24
19
|
isSliceType,
|
|
25
20
|
isStringType,
|
|
@@ -30,6 +25,48 @@ import {
|
|
|
30
25
|
// The emitter is one class split over several files by concern. Methods from
|
|
31
26
|
// the emit-*.js modules are assigned to FunctionEmitter.prototype below; `this`
|
|
32
27
|
// is shared, so cross-file calls are plain method calls.
|
|
28
|
+
const SCOPED_STMTS = new Set([
|
|
29
|
+
"Block",
|
|
30
|
+
"IfStmt",
|
|
31
|
+
"ForStmt",
|
|
32
|
+
"SwitchStmt",
|
|
33
|
+
"TypeSwitchStmt",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// A same-named method with another arity (strings.Reader.Reset(s) vs
|
|
37
|
+
// hash.Hash.Reset()) cannot satisfy this interface method. Neither can one
|
|
38
|
+
// whose parameters or results have other types (csv.Writer.Write(record
|
|
39
|
+
// []string) error vs io.Writer.Write(p []byte) (int, error)).
|
|
40
|
+
function sameTypes(a, b) {
|
|
41
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sameParams(c, params, paramTypes) {
|
|
45
|
+
if (c.targetParamTypes.length !== params.length) return false;
|
|
46
|
+
if (paramTypes?.length !== params.length) return true;
|
|
47
|
+
return c.targetParamTypes.every(
|
|
48
|
+
(t, i) => i === 0 || sameTypes(t, paramTypes[i]),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sameSignature(c, params, paramTypes, want, got) {
|
|
53
|
+
if (!Array.isArray(c.targetParamTypes)) return true;
|
|
54
|
+
if (!sameParams(c, params, paramTypes)) return false;
|
|
55
|
+
return want === undefined || got === undefined || sameTypes(got, want);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// True when a struct type embeds a pointer, directly or through an embedded
|
|
59
|
+
// struct: promoted method calls read through it.
|
|
60
|
+
function _embedsPointer(t, seen = new Set()) {
|
|
61
|
+
const base = t?.kind === "pointer" ? t.base : t;
|
|
62
|
+
const st = base?.underlying ?? base;
|
|
63
|
+
if (!st || seen.has(st)) return false;
|
|
64
|
+
seen.add(st);
|
|
65
|
+
return (st._embeds ?? []).some(
|
|
66
|
+
(e) => e.kind === "pointer" || _embedsPointer(e, seen),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
33
70
|
export class FunctionEmitter {
|
|
34
71
|
constructor(moduleEmitter, funcDecl, funcIndex) {
|
|
35
72
|
this.mod = moduleEmitter;
|
|
@@ -118,7 +155,75 @@ export class FunctionEmitter {
|
|
|
118
155
|
this.recoverOkLocalIndex = local;
|
|
119
156
|
}
|
|
120
157
|
|
|
158
|
+
_checkYieldClosureReentry() {
|
|
159
|
+
if (!this.funcDecl._isYieldClosure || !this.funcDecl._iterStateName) return;
|
|
160
|
+
const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
|
|
161
|
+
if (!stateLoc?.isBoxed) return;
|
|
162
|
+
this.pushInstruction({ op: "local.get", index: stateLoc.index });
|
|
163
|
+
this.pushInstruction({
|
|
164
|
+
op: "struct.get",
|
|
165
|
+
typeIndex: stateLoc.boxInfo.typeIndex,
|
|
166
|
+
fieldIndex: 0,
|
|
167
|
+
});
|
|
168
|
+
this.pushInstruction({ op: "i64.const", value: 0n });
|
|
169
|
+
this.pushInstruction("i64.ne");
|
|
170
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
171
|
+
this.emitPanic(
|
|
172
|
+
"range function continued iteration after function for loop body returned false",
|
|
173
|
+
);
|
|
174
|
+
this.pushInstruction("end");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_finishYieldClosureBody() {
|
|
178
|
+
if (!this.funcDecl._isYieldClosure) return false;
|
|
179
|
+
const lastOp = this.body[this.body.length - 1]?.op;
|
|
180
|
+
if (lastOp !== "return" && lastOp !== "unreachable") {
|
|
181
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
182
|
+
this.pushInstruction({ op: "return" });
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_collectLabelTargets(body) {
|
|
188
|
+
const targets = new Map();
|
|
189
|
+
let nextTag = 3;
|
|
190
|
+
const scan = (node) => {
|
|
191
|
+
if (!node || typeof node !== "object") return;
|
|
192
|
+
if (Array.isArray(node)) {
|
|
193
|
+
for (const item of node) scan(item);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (node.kind === "LabeledStmt" && node.label) {
|
|
197
|
+
const bTag = nextTag++;
|
|
198
|
+
const cTag = nextTag++;
|
|
199
|
+
targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
|
|
200
|
+
targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
|
|
201
|
+
} else if (node.kind === "ForStmt" && node.label) {
|
|
202
|
+
const bTag = nextTag++;
|
|
203
|
+
const cTag = nextTag++;
|
|
204
|
+
targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
|
|
205
|
+
targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
|
|
206
|
+
}
|
|
207
|
+
for (const k of Object.keys(node)) {
|
|
208
|
+
if (k.startsWith("_")) continue;
|
|
209
|
+
scan(node[k]);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
scan(body);
|
|
213
|
+
return targets;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_findLabelTarget(label, role) {
|
|
217
|
+
for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
|
|
218
|
+
if (target.label === label && target.role === role) return target;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
121
223
|
emitFunctionBody(body) {
|
|
224
|
+
if (!this.funcDecl._isClosure && !this.funcDecl._labelTargets) {
|
|
225
|
+
this.funcDecl._labelTargets = this._collectLabelTargets(body);
|
|
226
|
+
}
|
|
122
227
|
if (
|
|
123
228
|
this.funcDecl._isClosure &&
|
|
124
229
|
!this.funcDecl._isTrampoline &&
|
|
@@ -132,6 +237,7 @@ export class FunctionEmitter {
|
|
|
132
237
|
this._cacheScratchGlobals();
|
|
133
238
|
|
|
134
239
|
this._emitRecoverArmPrologue(body);
|
|
240
|
+
this._checkYieldClosureReentry();
|
|
135
241
|
|
|
136
242
|
const hasDef = hasDefer(body);
|
|
137
243
|
this.hasDefer = hasDef;
|
|
@@ -143,31 +249,139 @@ export class FunctionEmitter {
|
|
|
143
249
|
|
|
144
250
|
this._initNamedReturns();
|
|
145
251
|
|
|
146
|
-
const
|
|
147
|
-
if (needsOobBlock) {
|
|
148
|
-
this.pushInstruction({ op: "block", blockType: "void" });
|
|
149
|
-
this.pushControl("oob");
|
|
150
|
-
}
|
|
252
|
+
const closeChecks = this._openCheckBlocks(body);
|
|
151
253
|
|
|
152
254
|
this.emitBlock(body);
|
|
255
|
+
if (this._finishYieldClosureBody()) {
|
|
256
|
+
closeChecks?.(() => {});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
153
259
|
|
|
154
|
-
|
|
260
|
+
closeChecks?.(() =>
|
|
261
|
+
this.pushInstruction({
|
|
262
|
+
op: this.returnTypes.length === 0 ? "return" : "unreachable",
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Opens the nil and index-check blocks `body` needs and returns the
|
|
268
|
+
// function that closes them (null when none are needed). The closer runs
|
|
269
|
+
// `terminate` first unless the body already ends in a return or trap.
|
|
270
|
+
_openCheckBlocks(body) {
|
|
271
|
+
const nil = this._needsNilBlock(body);
|
|
272
|
+
const oob = this._needsOobBlock(body);
|
|
273
|
+
if (!nil && !oob) return null;
|
|
274
|
+
if (nil) this._openNilBlock();
|
|
275
|
+
if (oob) this._openOobBlocks();
|
|
276
|
+
return (terminate) => {
|
|
155
277
|
const lastOp = this.body[this.body.length - 1]?.op;
|
|
156
|
-
if (lastOp !== "return" && lastOp !== "unreachable")
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
278
|
+
if (lastOp !== "return" && lastOp !== "unreachable") terminate();
|
|
279
|
+
if (oob) this._closeOobBlocks();
|
|
280
|
+
if (nil) this._closeNilBlock();
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Field access through a nil struct pointer branches (`br_on_null`) out
|
|
285
|
+
// of the body to a block wrapped around it, whose handler raises Go's
|
|
286
|
+
// panic, so `recover()` sees it where a trap would skip the defers.
|
|
287
|
+
_openNilBlock() {
|
|
288
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
289
|
+
this.pushControl("nil");
|
|
290
|
+
}
|
|
164
291
|
|
|
292
|
+
// Closes the block opened by _openNilBlock; the body must already end in
|
|
293
|
+
// a branch, return or unreachable.
|
|
294
|
+
_closeNilBlock() {
|
|
295
|
+
this.pushInstruction("end");
|
|
296
|
+
this.pushInstruction({
|
|
297
|
+
op: "call",
|
|
298
|
+
funcIndex: this.mod.getNilPanicFuncIndex(),
|
|
299
|
+
});
|
|
300
|
+
this.pushInstruction("unreachable");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Index checks branch out of the body to one of two blocks wrapped around
|
|
304
|
+
// it, `oob32` for i32 indexes and `oob64` for i64 indexes, carrying the
|
|
305
|
+
// index and the check's site id as block results (`_pushOobSite`). The
|
|
306
|
+
// handlers after the blocks recompute that site's length and raise Go's
|
|
307
|
+
// `index out of range [i] with length n`. Nothing extra stays live in
|
|
308
|
+
// the hot path: a site id is an immediate.
|
|
309
|
+
_openOobBlocks() {
|
|
310
|
+
this.pushInstruction({
|
|
311
|
+
op: "block",
|
|
312
|
+
blockType: this.mod.getTypeIndex([], ["i64", "i32"]),
|
|
313
|
+
});
|
|
314
|
+
this.pushControl("oob64");
|
|
315
|
+
this.pushInstruction({
|
|
316
|
+
op: "block",
|
|
317
|
+
blockType: this.mod.getTypeIndex([], ["i32", "i32"]),
|
|
318
|
+
});
|
|
319
|
+
this.pushControl("oob32");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Closes the blocks opened by _openOobBlocks; the body must already end
|
|
323
|
+
// in a branch, return or unreachable.
|
|
324
|
+
_closeOobBlocks() {
|
|
325
|
+
const site = this.acquireTemp("i32");
|
|
326
|
+
const idx = this.acquireTemp("i64");
|
|
327
|
+
const len = this.acquireTemp("i32");
|
|
328
|
+
// oob32 lands here with [i32 index, site]: widen the index and fall
|
|
329
|
+
// through to the oob64 handler.
|
|
330
|
+
this.pushInstruction("end");
|
|
331
|
+
this.pushInstruction({ op: "local.set", index: site });
|
|
332
|
+
this.pushInstruction("i64.extend_i32_s");
|
|
333
|
+
this.pushInstruction({ op: "local.get", index: site });
|
|
334
|
+
this.pushInstruction("end");
|
|
335
|
+
this.pushInstruction({ op: "local.set", index: site });
|
|
336
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
337
|
+
// Sites whose length code is identical (same temp, same field) share
|
|
338
|
+
// one br_table target.
|
|
339
|
+
const codes = [];
|
|
340
|
+
const target = (this.oobSites ?? []).map((emitLen) => {
|
|
341
|
+
const saved = this.body;
|
|
342
|
+
this.body = [];
|
|
343
|
+
emitLen();
|
|
344
|
+
const code = this.body;
|
|
345
|
+
this.body = saved;
|
|
346
|
+
const key = JSON.stringify(code);
|
|
347
|
+
let k = codes.findIndex((c) => c.key === key);
|
|
348
|
+
if (k < 0) k = codes.push({ key, code }) - 1;
|
|
349
|
+
return k;
|
|
350
|
+
});
|
|
351
|
+
const sites = codes.map(({ code }) => () => {
|
|
352
|
+
for (const inst of code) this.pushInstruction(inst);
|
|
353
|
+
});
|
|
354
|
+
if (sites.length === 0) {
|
|
355
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
356
|
+
} else {
|
|
357
|
+
// block $len (result i32) { block … block { br_table } emitLen_0; br $len … }
|
|
358
|
+
this.pushInstruction({ op: "block", blockType: "i32" });
|
|
359
|
+
for (let k = 0; k < sites.length; k++)
|
|
360
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
361
|
+
this.pushInstruction({ op: "local.get", index: site });
|
|
165
362
|
this.pushInstruction({
|
|
166
|
-
op: "
|
|
167
|
-
|
|
363
|
+
op: "br_table",
|
|
364
|
+
targets: target,
|
|
365
|
+
defaultTarget: sites.length - 1,
|
|
168
366
|
});
|
|
169
|
-
|
|
367
|
+
for (let k = 0; k < sites.length; k++) {
|
|
368
|
+
this.pushInstruction("end");
|
|
369
|
+
sites[k]();
|
|
370
|
+
this.pushInstruction({ op: "br", depth: sites.length - 1 - k });
|
|
371
|
+
}
|
|
372
|
+
this.pushInstruction("end");
|
|
170
373
|
}
|
|
374
|
+
this.pushInstruction({ op: "local.set", index: len });
|
|
375
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
376
|
+
this.pushInstruction({ op: "local.get", index: len });
|
|
377
|
+
this.pushInstruction({
|
|
378
|
+
op: "call",
|
|
379
|
+
funcIndex: this.mod.getIndexPanicFuncIndex(),
|
|
380
|
+
});
|
|
381
|
+
this.pushInstruction("unreachable");
|
|
382
|
+
this.releaseTemp(site, "i32");
|
|
383
|
+
this.releaseTemp(idx, "i64");
|
|
384
|
+
this.releaseTemp(len, "i32");
|
|
171
385
|
}
|
|
172
386
|
|
|
173
387
|
_initNamedReturns() {
|
|
@@ -180,13 +394,6 @@ export class FunctionEmitter {
|
|
|
180
394
|
const varName = isBlank ? `__ret_blank$${i}` : r.name;
|
|
181
395
|
const wType = this.toWasmType(r.type);
|
|
182
396
|
const localIdx = this.allocLocal(varName, wType, r.type, isBlank);
|
|
183
|
-
this.namedReturnVars.push({
|
|
184
|
-
name: varName,
|
|
185
|
-
localIdx,
|
|
186
|
-
type: wType,
|
|
187
|
-
goType: r.type,
|
|
188
|
-
isBlank,
|
|
189
|
-
});
|
|
190
397
|
this.emitZeroValue(r.type, wType);
|
|
191
398
|
const localInfo = this.locals.get(varName);
|
|
192
399
|
if (localInfo?.isBoxed) {
|
|
@@ -196,6 +403,13 @@ export class FunctionEmitter {
|
|
|
196
403
|
});
|
|
197
404
|
}
|
|
198
405
|
this.pushInstruction({ op: "local.set", index: localIdx });
|
|
406
|
+
this.namedReturnVars.push({
|
|
407
|
+
name: varName,
|
|
408
|
+
localIdx,
|
|
409
|
+
type: wType,
|
|
410
|
+
goType: r.type,
|
|
411
|
+
isBlank,
|
|
412
|
+
});
|
|
199
413
|
}
|
|
200
414
|
return true;
|
|
201
415
|
}
|
|
@@ -256,30 +470,16 @@ export class FunctionEmitter {
|
|
|
256
470
|
});
|
|
257
471
|
this.pushControl("try_table");
|
|
258
472
|
|
|
259
|
-
const
|
|
260
|
-
if (needsOobBlock) {
|
|
261
|
-
this.pushInstruction({ op: "block", blockType: "void" });
|
|
262
|
-
this.pushControl("oob");
|
|
263
|
-
}
|
|
473
|
+
const closeChecks = this._openCheckBlocks(body);
|
|
264
474
|
|
|
265
475
|
this.emitBlock(body);
|
|
266
476
|
|
|
267
|
-
|
|
268
|
-
const lastOp = this.body[this.body.length - 1]?.op;
|
|
269
|
-
if (lastOp !== "return" && lastOp !== "unreachable") {
|
|
270
|
-
this.pushInstruction({
|
|
271
|
-
op: "br",
|
|
272
|
-
depth: this.resolveBranchDepthToRole("runDefers"),
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
this.pushInstruction("end");
|
|
276
|
-
|
|
477
|
+
closeChecks?.(() =>
|
|
277
478
|
this.pushInstruction({
|
|
278
|
-
op: "
|
|
279
|
-
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
}
|
|
479
|
+
op: "br",
|
|
480
|
+
depth: this.resolveBranchDepthToRole("runDefers"),
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
283
483
|
|
|
284
484
|
// Close try_table
|
|
285
485
|
this.pushInstruction("end");
|
|
@@ -575,6 +775,67 @@ export class FunctionEmitter {
|
|
|
575
775
|
return false;
|
|
576
776
|
}
|
|
577
777
|
|
|
778
|
+
// True when the body reads or writes a field or the target of a pointer.
|
|
779
|
+
_needsNilBlock(node) {
|
|
780
|
+
if (!node || typeof node !== "object") return false;
|
|
781
|
+
if (Array.isArray(node)) return node.some((n) => this._needsNilBlock(n));
|
|
782
|
+
if (node.kind === "FuncLit") return false;
|
|
783
|
+
if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "pointer")
|
|
784
|
+
return true;
|
|
785
|
+
if (this._isNilTrapSite(node)) return true;
|
|
786
|
+
for (const key of Object.keys(node)) {
|
|
787
|
+
if (key.startsWith("_")) continue;
|
|
788
|
+
if (this._needsNilBlock(node[key])) return true;
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// A pointer dereference or a call through a func value: sites whose nil
|
|
794
|
+
// operand raises Go's panic through the `nil` block.
|
|
795
|
+
_isNilTrapSite(node) {
|
|
796
|
+
if (node.kind === "CallExpr")
|
|
797
|
+
return (
|
|
798
|
+
this._isFuncValueCall(node) ||
|
|
799
|
+
(node.func?.kind === "SelectorExpr" &&
|
|
800
|
+
_embedsPointer(node.func.expr?._type))
|
|
801
|
+
);
|
|
802
|
+
// Binding a method value on an interface checks it for nil.
|
|
803
|
+
if (node.kind === "NilCheckStmt") return true;
|
|
804
|
+
// A pointer to an array: indexing, slicing and ranging with a value
|
|
805
|
+
// read the array.
|
|
806
|
+
if (
|
|
807
|
+
node.kind === "IndexExpr" ||
|
|
808
|
+
node.kind === "SliceExpr" ||
|
|
809
|
+
node.kind === "RangeExpr"
|
|
810
|
+
)
|
|
811
|
+
return node.expr?._type?.kind === "pointer";
|
|
812
|
+
const star =
|
|
813
|
+
(node.kind === "UnaryExpr" && node.op === "*") ||
|
|
814
|
+
node.kind === "StarExpr";
|
|
815
|
+
return star && (node.operand ?? node.expr)?._type?.kind === "pointer";
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// A call through a func variable, parameter or expression (not a declared
|
|
819
|
+
// function or a method), which panics when the value is nil.
|
|
820
|
+
_isFuncValueCall(call) {
|
|
821
|
+
const f = call.func;
|
|
822
|
+
if (!f || call._isTypeConversion) return false;
|
|
823
|
+
if (f.kind === "Ident")
|
|
824
|
+
return (
|
|
825
|
+
isFuncType(f._type, this.mod.checker) &&
|
|
826
|
+
this.mod.resolveFuncIndex(f.name) === null
|
|
827
|
+
);
|
|
828
|
+
if (f.kind === "SelectorExpr") {
|
|
829
|
+
const t = f.expr?._type;
|
|
830
|
+
const base = t?.kind === "pointer" ? t.base : t;
|
|
831
|
+
const fields = (base?.underlying ?? base)?.fields;
|
|
832
|
+
return fields instanceof Map && fields.has(f.field);
|
|
833
|
+
}
|
|
834
|
+
return (
|
|
835
|
+
f.kind === "IndexExpr" || f.kind === "CallExpr" || f.kind === "ParenExpr"
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
578
839
|
_needsOobBlock(node) {
|
|
579
840
|
if (!node || typeof node !== "object") return false;
|
|
580
841
|
if (Array.isArray(node)) {
|
|
@@ -640,6 +901,10 @@ export class FunctionEmitter {
|
|
|
640
901
|
for (let i = 0; i < lhs.length; i++) {
|
|
641
902
|
if (lhs[i].name === varName) {
|
|
642
903
|
if (lhs[i]._type) return lhs[i]._type;
|
|
904
|
+
// `for k, v := range x`: the range expression is a tuple.
|
|
905
|
+
const rt = node.rhs?.[0]?._type;
|
|
906
|
+
if (node.rhs?.[0]?.kind === "RangeExpr" && rt?.kind === "tuple")
|
|
907
|
+
return rt.types?.[i] ?? null;
|
|
643
908
|
if (node.rhs?.[i]?._type) return node.rhs[i]._type;
|
|
644
909
|
}
|
|
645
910
|
}
|
|
@@ -655,11 +920,12 @@ export class FunctionEmitter {
|
|
|
655
920
|
return null;
|
|
656
921
|
}
|
|
657
922
|
|
|
658
|
-
_buildEnvFields(capturedNames) {
|
|
923
|
+
_buildEnvFields(capturedNames, funcLit) {
|
|
659
924
|
const envFields = [];
|
|
925
|
+
const types = this.captureAnalysis?.captureTypes?.get(funcLit);
|
|
660
926
|
for (const name of capturedNames) {
|
|
661
927
|
const isBoxed = this.mutatedCaptures.has(name);
|
|
662
|
-
const goType = this._findCapturedVarGoType(name);
|
|
928
|
+
const goType = types?.get(name) ?? this._findCapturedVarGoType(name);
|
|
663
929
|
const boxInfo = isBoxed ? this.mod.getBoxType(goType) : null;
|
|
664
930
|
envFields.push({
|
|
665
931
|
name,
|
|
@@ -675,7 +941,10 @@ export class FunctionEmitter {
|
|
|
675
941
|
}
|
|
676
942
|
|
|
677
943
|
_unpackClosureEnv() {
|
|
678
|
-
const envFields = this._buildEnvFields(
|
|
944
|
+
const envFields = this._buildEnvFields(
|
|
945
|
+
this.capturedNames,
|
|
946
|
+
this.funcDecl._funcLit,
|
|
947
|
+
);
|
|
679
948
|
const envInfo = this.mod.getEnvType(envFields);
|
|
680
949
|
this.envInfo = envInfo;
|
|
681
950
|
this.envFields = envFields;
|
|
@@ -868,7 +1137,7 @@ export class FunctionEmitter {
|
|
|
868
1137
|
for (const n of names) rawFields.push({ name: n, goType: f.type });
|
|
869
1138
|
}
|
|
870
1139
|
}
|
|
871
|
-
if (
|
|
1140
|
+
if (this.mod.getAnonStructType) {
|
|
872
1141
|
return this.mod.getAnonStructType(rawFields);
|
|
873
1142
|
}
|
|
874
1143
|
}
|
|
@@ -882,7 +1151,13 @@ export class FunctionEmitter {
|
|
|
882
1151
|
return [{ structInfo, fieldIndex: idx, field: structInfo.fields[idx] }];
|
|
883
1152
|
}
|
|
884
1153
|
for (const embed of structInfo.embeds) {
|
|
885
|
-
|
|
1154
|
+
// A generic instance is registered under its specialised name
|
|
1155
|
+
// (`Inner$int`), which the field's type carries.
|
|
1156
|
+
const ft = structInfo.fields[embed.fieldIndex]?.goType;
|
|
1157
|
+
const instName = (ft?.kind === "PointerType" ? ft.base : ft)?.name;
|
|
1158
|
+
const embedInfo =
|
|
1159
|
+
(instName && this.mod.structTypes.get(instName)) ??
|
|
1160
|
+
this.mod.structTypes.get(embed.typeName ?? embed.name);
|
|
886
1161
|
if (embedInfo) {
|
|
887
1162
|
const sub = this._resolveFieldPath(embedInfo, fieldName);
|
|
888
1163
|
if (sub) {
|
|
@@ -900,7 +1175,7 @@ export class FunctionEmitter {
|
|
|
900
1175
|
return null;
|
|
901
1176
|
}
|
|
902
1177
|
|
|
903
|
-
emitCloneStruct(structInfo, baseWType) {
|
|
1178
|
+
emitCloneStruct(structInfo, baseWType, dstInfo = structInfo) {
|
|
904
1179
|
const tmp = this.acquireTemp(baseWType);
|
|
905
1180
|
this.pushInstruction({ op: "local.set", index: tmp });
|
|
906
1181
|
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
@@ -910,18 +1185,117 @@ export class FunctionEmitter {
|
|
|
910
1185
|
typeIndex: structInfo.typeIndex,
|
|
911
1186
|
fieldIndex: i,
|
|
912
1187
|
});
|
|
1188
|
+
// Array and value-struct fields are values: copy them with the struct.
|
|
1189
|
+
const f = structInfo.fields[i];
|
|
1190
|
+
if (!f.goType || f.goType.kind === "pointer") continue;
|
|
1191
|
+
if (isArrayType(f.goType, this.mod.checker)) {
|
|
1192
|
+
this.emitCopyArray(f.goType);
|
|
1193
|
+
} else if (
|
|
1194
|
+
isStructType(f.goType, this.mod.checker, this.mod) &&
|
|
1195
|
+
!isPointerToStruct(f.goType, this.mod.checker, this.mod)
|
|
1196
|
+
) {
|
|
1197
|
+
const inner = this._resolveStructInfo({ _type: f.goType });
|
|
1198
|
+
if (inner) this.emitCloneStruct(inner, f.wType);
|
|
1199
|
+
}
|
|
913
1200
|
}
|
|
914
1201
|
this.pushInstruction({
|
|
915
1202
|
op: "struct.new",
|
|
916
|
-
typeIndex:
|
|
1203
|
+
typeIndex: dstInfo.typeIndex,
|
|
917
1204
|
});
|
|
918
1205
|
this.releaseTemp(tmp, baseWType);
|
|
919
1206
|
}
|
|
920
1207
|
|
|
921
|
-
//
|
|
922
|
-
//
|
|
923
|
-
// (
|
|
924
|
-
|
|
1208
|
+
// Copies the fixed-size array on top of the stack into a fresh array.
|
|
1209
|
+
// Elements that are themselves arrays or value structs are copied deeply.
|
|
1210
|
+
// A null source (an array slot that was never written, e.g. an element of
|
|
1211
|
+
// `make([][2]int, n)`) yields the zero array, so every copy is non-null.
|
|
1212
|
+
emitCopyArray(goType) {
|
|
1213
|
+
const elemType = this._getArrayElemType(goType);
|
|
1214
|
+
const arrInfo = this.mod.getArrayType(elemType);
|
|
1215
|
+
const arrWType = {
|
|
1216
|
+
kind: "ref",
|
|
1217
|
+
nullable: true,
|
|
1218
|
+
typeIndex: arrInfo.typeIndex,
|
|
1219
|
+
};
|
|
1220
|
+
const src = this.acquireTemp(arrWType);
|
|
1221
|
+
const dst = this.acquireTemp(arrWType);
|
|
1222
|
+
this.pushInstruction({ op: "local.tee", index: src });
|
|
1223
|
+
this.pushInstruction("ref.is_null");
|
|
1224
|
+
this.pushInstruction({ op: "if", blockType: arrWType });
|
|
1225
|
+
this.emitZeroValue(goType, arrWType);
|
|
1226
|
+
this.pushInstruction("else");
|
|
1227
|
+
this.pushInstruction({ op: "local.get", index: src });
|
|
1228
|
+
this.pushInstruction("array.len");
|
|
1229
|
+
this.pushInstruction({
|
|
1230
|
+
op: "array.new_default",
|
|
1231
|
+
typeIndex: arrInfo.typeIndex,
|
|
1232
|
+
});
|
|
1233
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
1234
|
+
const elemIsArray =
|
|
1235
|
+
elemType?.kind !== "pointer" && isArrayType(elemType, this.mod.checker);
|
|
1236
|
+
const elemStruct =
|
|
1237
|
+
!elemIsArray &&
|
|
1238
|
+
isStructType(elemType, this.mod.checker, this.mod) &&
|
|
1239
|
+
!isPointerToStruct(elemType, this.mod.checker, this.mod)
|
|
1240
|
+
? (this._resolveStructInfo({ _type: elemType }) ??
|
|
1241
|
+
this.mod.getStructType(elemType?.name))
|
|
1242
|
+
: null;
|
|
1243
|
+
if (elemIsArray || elemStruct) {
|
|
1244
|
+
const i = this.acquireTemp("i32");
|
|
1245
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1246
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
1247
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1248
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
1249
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
1250
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
1251
|
+
this.pushInstruction("array.len");
|
|
1252
|
+
this.pushInstruction("i32.ge_u");
|
|
1253
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
1254
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
1255
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
1256
|
+
this.pushInstruction({ op: "local.get", index: src });
|
|
1257
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
1258
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
1259
|
+
if (elemIsArray) this.emitCopyArray(elemType);
|
|
1260
|
+
else this.emitCloneStruct(elemStruct, arrInfo.elemWType);
|
|
1261
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
1262
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
1263
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
1264
|
+
this.pushInstruction("i32.add");
|
|
1265
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
1266
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
1267
|
+
this.pushInstruction("end");
|
|
1268
|
+
this.pushInstruction("end");
|
|
1269
|
+
this.releaseTemp(i, "i32");
|
|
1270
|
+
} else {
|
|
1271
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
1272
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1273
|
+
this.pushInstruction({ op: "local.get", index: src });
|
|
1274
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1275
|
+
this.pushInstruction({ op: "local.get", index: src });
|
|
1276
|
+
this.pushInstruction("array.len");
|
|
1277
|
+
this.pushInstruction({
|
|
1278
|
+
op: "array.copy",
|
|
1279
|
+
typeIndex: arrInfo.typeIndex,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
this.pushInstruction({ op: "local.get", index: dst });
|
|
1283
|
+
this.pushInstruction("end");
|
|
1284
|
+
this.releaseTemp(dst, arrWType);
|
|
1285
|
+
this.releaseTemp(src, arrWType);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Go copies structs and arrays on assignment. After `node` has been
|
|
1289
|
+
// emitted (value on the stack), copy it unless it is already a fresh
|
|
1290
|
+
// temporary (composite literal, call result, or a struct `*p` deref) or not
|
|
1291
|
+
// a value type at all.
|
|
1292
|
+
_emitCopyIfValue(node, goType, wType) {
|
|
1293
|
+
if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker)) {
|
|
1294
|
+
if (node.kind === "CompositeLit" || node._freshArray) return;
|
|
1295
|
+
if (node.kind === "CallExpr" && !node._isTypeConversion) return;
|
|
1296
|
+
this.emitCopyArray(goType);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
925
1299
|
if (
|
|
926
1300
|
!isStructType(goType, this.mod.checker, this.mod) ||
|
|
927
1301
|
isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
@@ -963,7 +1337,8 @@ export class FunctionEmitter {
|
|
|
963
1337
|
const pkg = t.expr?.name;
|
|
964
1338
|
if (pkg) return `${pkg}.${t.field}`;
|
|
965
1339
|
}
|
|
966
|
-
if (t.kind === "simd")
|
|
1340
|
+
if (t.kind === "simd")
|
|
1341
|
+
return t.simd === "Mask32x4" ? "simd.Mask32x4" : "simd.F32x4";
|
|
967
1342
|
if (t.name) return t.name;
|
|
968
1343
|
if (t.kind === "named") return t.name;
|
|
969
1344
|
if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
|
|
@@ -981,11 +1356,23 @@ export class FunctionEmitter {
|
|
|
981
1356
|
return null;
|
|
982
1357
|
}
|
|
983
1358
|
|
|
984
|
-
|
|
985
|
-
|
|
1359
|
+
// Concrete methods the dispatcher `fn` can call: same name, same signature.
|
|
1360
|
+
_dispatchCandidates(fn) {
|
|
1361
|
+
const candidates = this.mod.findInterfaceCandidates(
|
|
986
1362
|
fn._ifaceType,
|
|
987
1363
|
fn._methodName,
|
|
988
1364
|
);
|
|
1365
|
+
if (!Array.isArray(fn.params)) return candidates;
|
|
1366
|
+
const paramTypes = this.mod.getFuncParamTypes(fn.name);
|
|
1367
|
+
const results = this.mod.funcReturnTypesByIdx;
|
|
1368
|
+
const want = results.get(fn._globalFuncIndex);
|
|
1369
|
+
return candidates.filter((c) =>
|
|
1370
|
+
sameSignature(c, fn.params, paramTypes, want, results.get(c.funcIndex)),
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
emitInterfaceDispatcher(fn) {
|
|
1375
|
+
let candidates = this._dispatchCandidates(fn);
|
|
989
1376
|
const recvLocal = 0; // __recv is local 0
|
|
990
1377
|
// Soft dispatchers (errors.Unwrap) return nil instead of panicking when
|
|
991
1378
|
// the receiver is nil or has no such method.
|
|
@@ -1025,12 +1412,15 @@ export class FunctionEmitter {
|
|
|
1025
1412
|
|
|
1026
1413
|
// 2. Iterate candidates
|
|
1027
1414
|
for (const cand of candidates) {
|
|
1415
|
+
this._emitNilPtrCandidate(cand, fn, recvLocal);
|
|
1028
1416
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1029
1417
|
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1030
1418
|
this.pushInstruction({ op: "ref.test", typeIndex: cand.testTypeIndex });
|
|
1031
1419
|
this.pushInstruction("i32.eqz");
|
|
1032
1420
|
this.pushInstruction({ op: "br_if", depth: 0 });
|
|
1033
1421
|
|
|
1422
|
+
this._emitNilPtrBoxPanic(cand, recvLocal);
|
|
1423
|
+
|
|
1034
1424
|
// Matched! Cast receiver
|
|
1035
1425
|
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1036
1426
|
this.pushInstruction({
|
|
@@ -1046,6 +1436,12 @@ export class FunctionEmitter {
|
|
|
1046
1436
|
});
|
|
1047
1437
|
if (cand.embedPath) {
|
|
1048
1438
|
for (const step of cand.embedPath) {
|
|
1439
|
+
if (step.castTypeIndex) {
|
|
1440
|
+
this.pushInstruction({
|
|
1441
|
+
op: "ref.cast",
|
|
1442
|
+
typeIndex: step.castTypeIndex,
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1049
1445
|
this.pushInstruction({
|
|
1050
1446
|
op: "struct.get",
|
|
1051
1447
|
typeIndex: step.parentTypeIndex,
|
|
@@ -1081,6 +1477,58 @@ export class FunctionEmitter {
|
|
|
1081
1477
|
emitMiss("interface conversion: nil or unmatched type for method call");
|
|
1082
1478
|
}
|
|
1083
1479
|
|
|
1480
|
+
// A typed nil `*S` inside the interface is a marker object (see
|
|
1481
|
+
// getNilBoxType): a pointer-receiver method runs with a nil receiver, as in
|
|
1482
|
+
// Go; a value-receiver or promoted method must read through the nil pointer.
|
|
1483
|
+
_emitNilPtrCandidate(cand, fn, recvLocal) {
|
|
1484
|
+
if (cand.isBoxedValue || !cand.structInfo) return;
|
|
1485
|
+
if (cand.testTypeIndex !== cand.structInfo.typeIndex) return;
|
|
1486
|
+
const nilBox = this.mod.getNilBoxType({
|
|
1487
|
+
kind: "pointer",
|
|
1488
|
+
base: { kind: "named", name: cand.recvTypeName },
|
|
1489
|
+
});
|
|
1490
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1491
|
+
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1492
|
+
this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
|
|
1493
|
+
this.pushInstruction("i32.eqz");
|
|
1494
|
+
this.pushInstruction({ op: "br_if", depth: 0 });
|
|
1495
|
+
if (cand.needsValueDeref || cand.embedPath) {
|
|
1496
|
+
this.emitPanic(
|
|
1497
|
+
"runtime error: invalid memory address or nil pointer dereference",
|
|
1498
|
+
);
|
|
1499
|
+
} else {
|
|
1500
|
+
this.pushInstruction({ op: "ref.null", heapType: cand.testTypeIndex });
|
|
1501
|
+
for (let p = 1; p < fn.params.length; p++) {
|
|
1502
|
+
this.pushInstruction({ op: "local.get", index: p });
|
|
1503
|
+
}
|
|
1504
|
+
this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
|
|
1505
|
+
this.pushInstruction("return");
|
|
1506
|
+
}
|
|
1507
|
+
this.pushInstruction("end");
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// A value-receiver method reached through a pointer box reads the pointee
|
|
1511
|
+
// first, so a nil pointer inside the interface is a catchable panic.
|
|
1512
|
+
_emitNilPtrBoxPanic(cand, recvLocal) {
|
|
1513
|
+
if (!cand.nilPtrPanic) return;
|
|
1514
|
+
this.pushInstruction({ op: "local.get", index: recvLocal });
|
|
1515
|
+
this.pushInstruction({
|
|
1516
|
+
op: "ref.cast_null",
|
|
1517
|
+
typeIndex: cand.testTypeIndex,
|
|
1518
|
+
});
|
|
1519
|
+
this.pushInstruction({
|
|
1520
|
+
op: "struct.get",
|
|
1521
|
+
typeIndex: cand.testTypeIndex,
|
|
1522
|
+
fieldIndex: 0,
|
|
1523
|
+
});
|
|
1524
|
+
this.pushInstruction("ref.is_null");
|
|
1525
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1526
|
+
this.emitPanic(
|
|
1527
|
+
"runtime error: invalid memory address or nil pointer dereference",
|
|
1528
|
+
);
|
|
1529
|
+
this.pushInstruction("end");
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1084
1532
|
_getArraySize(t, defaultSize = 0) {
|
|
1085
1533
|
if (!t) return defaultSize;
|
|
1086
1534
|
if (typeof t.size === "number") return t.size;
|
|
@@ -1088,6 +1536,10 @@ export class FunctionEmitter {
|
|
|
1088
1536
|
if (typeof t.len === "number") return t.len;
|
|
1089
1537
|
if (t.len?.value !== undefined) return Number(t.len.value);
|
|
1090
1538
|
if (t.underlying) return this._getArraySize(t.underlying, defaultSize);
|
|
1539
|
+
if (t.name && this.mod.checker?.types?.has(t.name)) {
|
|
1540
|
+
const resolved = this.mod.checker.types.get(t.name);
|
|
1541
|
+
if (resolved !== t) return this._getArraySize(resolved, defaultSize);
|
|
1542
|
+
}
|
|
1091
1543
|
return defaultSize;
|
|
1092
1544
|
}
|
|
1093
1545
|
|
|
@@ -1119,6 +1571,22 @@ export class FunctionEmitter {
|
|
|
1119
1571
|
wType !== null &&
|
|
1120
1572
|
wType.kind === "ref"
|
|
1121
1573
|
) {
|
|
1574
|
+
if (
|
|
1575
|
+
(goType && isComplex(goType)) ||
|
|
1576
|
+
wType.typeName === "$complex64" ||
|
|
1577
|
+
wType.typeName === "$complex128"
|
|
1578
|
+
) {
|
|
1579
|
+
const is64 = isComplex64(goType) || wType.typeName === "$complex64";
|
|
1580
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
1581
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
|
|
1582
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
|
|
1583
|
+
this.pushInstruction({
|
|
1584
|
+
op: "struct.new",
|
|
1585
|
+
typeIndex: info.typeIndex,
|
|
1586
|
+
typeName: info.name,
|
|
1587
|
+
});
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1122
1590
|
if (
|
|
1123
1591
|
goType &&
|
|
1124
1592
|
goType.kind !== "pointer" &&
|
|
@@ -1126,7 +1594,10 @@ export class FunctionEmitter {
|
|
|
1126
1594
|
) {
|
|
1127
1595
|
const sName =
|
|
1128
1596
|
goType.name ?? (goType.kind === "named" ? goType.name : null);
|
|
1129
|
-
|
|
1597
|
+
// An anonymous struct type has no name to look up.
|
|
1598
|
+
const structInfo =
|
|
1599
|
+
this.mod.getStructType(sName) ??
|
|
1600
|
+
this._resolveStructInfo({ _type: goType });
|
|
1130
1601
|
if (structInfo) {
|
|
1131
1602
|
for (const f of structInfo.fields) {
|
|
1132
1603
|
this.emitZeroValue(f.goType, f.wType);
|
|
@@ -1143,10 +1614,15 @@ export class FunctionEmitter {
|
|
|
1143
1614
|
goType.kind !== "pointer" &&
|
|
1144
1615
|
isArrayType(goType, this.mod.checker)
|
|
1145
1616
|
) {
|
|
1146
|
-
const elemType = goType
|
|
1617
|
+
const elemType = this._getArrayElemType(goType);
|
|
1147
1618
|
const arrInfo = this.mod.getArrayType(elemType);
|
|
1148
1619
|
const size = this._getArraySize(goType);
|
|
1149
|
-
if (
|
|
1620
|
+
if (
|
|
1621
|
+
isStructType(elemType, this.mod.checker, this.mod) ||
|
|
1622
|
+
isComplex(elemType) ||
|
|
1623
|
+
(elemType?.kind !== "pointer" &&
|
|
1624
|
+
isArrayType(elemType, this.mod.checker))
|
|
1625
|
+
) {
|
|
1150
1626
|
for (let i = 0; i < size; i++) {
|
|
1151
1627
|
this.emitZeroValue(elemType, arrInfo.elemWType);
|
|
1152
1628
|
}
|
|
@@ -1156,11 +1632,9 @@ export class FunctionEmitter {
|
|
|
1156
1632
|
size,
|
|
1157
1633
|
});
|
|
1158
1634
|
} else {
|
|
1159
|
-
this.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
typeIndex: arrInfo.typeIndex,
|
|
1163
|
-
});
|
|
1635
|
+
this._emitNewArray(elemType, arrInfo, () =>
|
|
1636
|
+
this.pushInstruction({ op: "i32.const", value: size }),
|
|
1637
|
+
);
|
|
1164
1638
|
}
|
|
1165
1639
|
return;
|
|
1166
1640
|
}
|
|
@@ -1202,8 +1676,24 @@ export class FunctionEmitter {
|
|
|
1202
1676
|
}
|
|
1203
1677
|
}
|
|
1204
1678
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1679
|
+
// Pushes a new array of `arrInfo` with the length `emitLen` pushes. Elements
|
|
1680
|
+
// start at their zero value: strings are the empty string, not a null
|
|
1681
|
+
// reference, so the array reads, compares and prints like Go's.
|
|
1682
|
+
_emitNewArray(elemType, arrInfo, emitLen) {
|
|
1683
|
+
if (
|
|
1684
|
+
arrInfo.elemWType === "externref" &&
|
|
1685
|
+
isStringType(elemType, this.mod.checker)
|
|
1686
|
+
) {
|
|
1687
|
+
this.emitZeroValue(elemType, "externref");
|
|
1688
|
+
emitLen();
|
|
1689
|
+
this.pushInstruction({ op: "array.new", typeIndex: arrInfo.typeIndex });
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
emitLen();
|
|
1693
|
+
this.pushInstruction({
|
|
1694
|
+
op: "array.new_default",
|
|
1695
|
+
typeIndex: arrInfo.typeIndex,
|
|
1696
|
+
});
|
|
1207
1697
|
}
|
|
1208
1698
|
|
|
1209
1699
|
// Temps are keyed by a canonical string so ref-typed temps (fresh object
|
|
@@ -1286,8 +1776,9 @@ export class FunctionEmitter {
|
|
|
1286
1776
|
return 0;
|
|
1287
1777
|
}
|
|
1288
1778
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1779
|
+
// Depth of the index-check block for i32 or i64 indexes, or null.
|
|
1780
|
+
resolveBranchDepthToOob(width = 64) {
|
|
1781
|
+
return this.resolveBranchDepthToRole(width === 32 ? "oob32" : "oob64");
|
|
1291
1782
|
}
|
|
1292
1783
|
|
|
1293
1784
|
resolveBranchDepthToRole(targetRole) {
|
|
@@ -1308,17 +1799,43 @@ export class FunctionEmitter {
|
|
|
1308
1799
|
}
|
|
1309
1800
|
}
|
|
1310
1801
|
|
|
1802
|
+
// Go scopes: a block, `if`, `for` and `switch` (with their init
|
|
1803
|
+
// statements) end the names they declare, so an inner `x := 5` does not
|
|
1804
|
+
// rebind the outer `x` for the code after it.
|
|
1311
1805
|
emitStmt(stmt) {
|
|
1312
1806
|
if (!stmt) return;
|
|
1807
|
+
if (!SCOPED_STMTS.has(stmt.kind)) {
|
|
1808
|
+
this._emitStmtKind(stmt);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const saved = new Map(this.locals);
|
|
1812
|
+
try {
|
|
1813
|
+
this._emitStmtKind(stmt);
|
|
1814
|
+
} finally {
|
|
1815
|
+
this.locals = saved;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
_emitStmtKind(stmt) {
|
|
1313
1820
|
switch (stmt.kind) {
|
|
1314
1821
|
case "Block":
|
|
1315
1822
|
this.emitBlock(stmt);
|
|
1316
1823
|
break;
|
|
1317
1824
|
|
|
1318
1825
|
case "VarDecl":
|
|
1826
|
+
case "ConstDecl":
|
|
1319
1827
|
this.emitVarDecl(stmt);
|
|
1320
1828
|
break;
|
|
1321
1829
|
|
|
1830
|
+
// A package-level array or struct too large to build as a constant
|
|
1831
|
+
// expression gets its zero value when the module starts.
|
|
1832
|
+
case "GlobalZeroInit": {
|
|
1833
|
+
const g = this.mod.resolveGlobal(stmt.name);
|
|
1834
|
+
this.emitZeroValue(stmt.goType, g.type);
|
|
1835
|
+
this.pushInstruction({ op: "global.set", index: g.index });
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1322
1839
|
case "DefineStmt":
|
|
1323
1840
|
this.emitDefineStmt(stmt);
|
|
1324
1841
|
break;
|
|
@@ -1355,10 +1872,25 @@ export class FunctionEmitter {
|
|
|
1355
1872
|
this.emitExprStmt(stmt);
|
|
1356
1873
|
break;
|
|
1357
1874
|
|
|
1875
|
+
// Evaluates `stmt.expr` and panics (recoverably) when it is nil.
|
|
1876
|
+
case "NilCheckStmt":
|
|
1877
|
+
this.emitExpr(stmt.expr, "anyref");
|
|
1878
|
+
this._emitNilBranch();
|
|
1879
|
+
this.pushInstruction("drop");
|
|
1880
|
+
break;
|
|
1881
|
+
|
|
1358
1882
|
case "LabeledStmt":
|
|
1359
1883
|
if (stmt.body) {
|
|
1360
|
-
|
|
1884
|
+
// Stacked labels (`a: b: for`) all target the loop: outer
|
|
1885
|
+
// labels alias the innermost one.
|
|
1886
|
+
if (stmt.body.kind !== "LabeledStmt") {
|
|
1361
1887
|
stmt.body.label = stmt.label;
|
|
1888
|
+
this._labelAliases?.delete(stmt.label);
|
|
1889
|
+
} else {
|
|
1890
|
+
let inner = stmt.body;
|
|
1891
|
+
while (inner.body?.kind === "LabeledStmt") inner = inner.body;
|
|
1892
|
+
this._labelAliases ??= new Map();
|
|
1893
|
+
this._labelAliases.set(stmt.label, inner.label);
|
|
1362
1894
|
}
|
|
1363
1895
|
this.emitStmt(stmt.body);
|
|
1364
1896
|
}
|
|
@@ -1386,7 +1918,7 @@ export class FunctionEmitter {
|
|
|
1386
1918
|
}
|
|
1387
1919
|
}
|
|
1388
1920
|
|
|
1389
|
-
function copyMethods(target, source) {
|
|
1921
|
+
export function copyMethods(target, source) {
|
|
1390
1922
|
const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
|
|
1391
1923
|
delete descriptors.constructor;
|
|
1392
1924
|
for (const key of Object.keys(descriptors)) {
|
|
@@ -1403,11 +1935,5 @@ copyMethods(FunctionEmitter, MapsEmitter);
|
|
|
1403
1935
|
copyMethods(FunctionEmitter, StmtsEmitter);
|
|
1404
1936
|
copyMethods(FunctionEmitter, ExprsEmitter);
|
|
1405
1937
|
copyMethods(FunctionEmitter, BuiltinsEmitter);
|
|
1406
|
-
copyMethods(FunctionEmitter,
|
|
1407
|
-
copyMethods(FunctionEmitter, BytesEmitter);
|
|
1408
|
-
copyMethods(FunctionEmitter, PurePackagesEmitter);
|
|
1409
|
-
copyMethods(FunctionEmitter, IoEmitter);
|
|
1410
|
-
copyMethods(FunctionEmitter, TimeEmitter);
|
|
1411
|
-
copyMethods(FunctionEmitter, ExtendedPackagesEmitter);
|
|
1938
|
+
for (const emitter of stdlibEmitters) copyMethods(FunctionEmitter, emitter);
|
|
1412
1939
|
copyMethods(FunctionEmitter, SharedEmitter);
|
|
1413
|
-
copyMethods(FunctionEmitter, SimdEmitter);
|