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
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
// FunctionEmitter mixin: panic/recover, Go builtins (len, append, make, …) and
|
|
3
3
|
// call expression dispatch (static, method, closure, package).
|
|
4
4
|
|
|
5
|
+
import { hasCallOrReceive } from "../../lower/walk.js";
|
|
6
|
+
import { typeStr } from "../../typechecker/types.js";
|
|
7
|
+
import { goTypeName } from "../shared/fmt-types.js";
|
|
5
8
|
import {
|
|
9
|
+
anonIfaceName,
|
|
10
|
+
ERROR_STRUCT,
|
|
6
11
|
getMapKeyValTypes,
|
|
7
12
|
isArrayType,
|
|
13
|
+
isComplex,
|
|
14
|
+
isComplex64,
|
|
8
15
|
isMapType,
|
|
9
16
|
isNonEmptyInterface,
|
|
17
|
+
isScalarType,
|
|
18
|
+
isSigned,
|
|
10
19
|
isSliceType,
|
|
11
20
|
isStringType,
|
|
12
21
|
isStructType,
|
|
13
|
-
|
|
22
|
+
isUnhashableGoType,
|
|
23
|
+
printOperandKind,
|
|
14
24
|
toWasmType,
|
|
15
25
|
} from "./types.js";
|
|
16
26
|
|
|
@@ -20,13 +30,32 @@ export class BuiltinsEmitter {
|
|
|
20
30
|
const funcIdx = this.mod.getStringImportIndex();
|
|
21
31
|
this.pushInstruction({ op: "i32.const", value: strIdx });
|
|
22
32
|
this.pushInstruction({ op: "call", funcIndex: funcIdx });
|
|
23
|
-
this.
|
|
24
|
-
|
|
33
|
+
this.emitRuntimePanicThrow();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Raises a Go runtime error from the externref message on the stack. A
|
|
37
|
+
// program that uses `error` recovers it as an `error` value (as Go's
|
|
38
|
+
// `runtime.Error`); the host still reports the message text.
|
|
39
|
+
emitRuntimePanicThrow() {
|
|
40
|
+
if (!this.mod.hasErrorType) {
|
|
41
|
+
this.pushInstruction("any.convert_extern");
|
|
42
|
+
this.emitPanicThrow();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const msgTmp = this.acquireTemp("externref");
|
|
46
|
+
this.pushInstruction({ op: "local.tee", index: msgTmp });
|
|
47
|
+
this.pushInstruction({
|
|
48
|
+
op: "call",
|
|
49
|
+
funcIndex: this.mod.resolveFuncIndex("__mk_error"),
|
|
50
|
+
});
|
|
51
|
+
this.emitPanicThrow(msgTmp);
|
|
52
|
+
this.releaseTemp(msgTmp, "externref");
|
|
25
53
|
}
|
|
26
54
|
|
|
27
|
-
// Consumes the anyref
|
|
28
|
-
// `env.panic
|
|
29
|
-
|
|
55
|
+
// Consumes the anyref value on the stack, pushes a PanicNode, and raises the
|
|
56
|
+
// panic via `env.panic` (with the value, or the externref message in
|
|
57
|
+
// `hostMsgTmp`); `unreachable` tells the validator control never returns.
|
|
58
|
+
emitPanicThrow(hostMsgTmp = null) {
|
|
30
59
|
const panicNodeTypeIndex = this.mod.getPanicNodeTypeIndex();
|
|
31
60
|
const panicGlobal = this.mod.getPanicGlobalIndex();
|
|
32
61
|
const tmpVal = this.acquireTemp("anyref");
|
|
@@ -43,8 +72,12 @@ export class BuiltinsEmitter {
|
|
|
43
72
|
this.pushInstruction({ op: "global.set", index: panicGlobal });
|
|
44
73
|
|
|
45
74
|
// Prepare externref for env.panic
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
if (hostMsgTmp !== null) {
|
|
76
|
+
this.pushInstruction({ op: "local.get", index: hostMsgTmp });
|
|
77
|
+
} else {
|
|
78
|
+
this.pushInstruction({ op: "local.get", index: tmpVal });
|
|
79
|
+
this.pushInstruction("extern.convert_any");
|
|
80
|
+
}
|
|
48
81
|
this.pushInstruction({
|
|
49
82
|
op: "call",
|
|
50
83
|
funcIndex: this.mod.getPanicImportIndex(),
|
|
@@ -65,9 +98,32 @@ export class BuiltinsEmitter {
|
|
|
65
98
|
return null;
|
|
66
99
|
}
|
|
67
100
|
|
|
101
|
+
// `len`/`cap` of an array or array pointer: the constant length. An
|
|
102
|
+
// operand containing a call or receive is still evaluated, and its value
|
|
103
|
+
// (a nil array pointer included) is dropped without being dereferenced.
|
|
104
|
+
_emitConstArrayLen(call, targetWasmType) {
|
|
105
|
+
const arg = call.args[0];
|
|
106
|
+
if (hasCallOrReceive(arg)) {
|
|
107
|
+
this.emitExpr(
|
|
108
|
+
arg,
|
|
109
|
+
this.toWasmType(arg._type ?? this._resolveExprGoType(arg)),
|
|
110
|
+
);
|
|
111
|
+
this.pushInstruction("drop");
|
|
112
|
+
}
|
|
113
|
+
this.pushInstruction({ op: "i32.const", value: call._constLen });
|
|
114
|
+
if ((targetWasmType ?? "i64") === "i64")
|
|
115
|
+
this.pushInstruction("i64.extend_i32_u");
|
|
116
|
+
}
|
|
117
|
+
|
|
68
118
|
_emitBuiltinLen(call, targetWasmType) {
|
|
69
119
|
const arg = call.args[0];
|
|
70
120
|
const argType = arg._type ?? this._resolveExprGoType(arg);
|
|
121
|
+
// `len` of an array (or a pointer to one) is a constant, and the
|
|
122
|
+
// operand is not evaluated unless it contains a call or a receive.
|
|
123
|
+
if (call._constLen != null) {
|
|
124
|
+
this._emitConstArrayLen(call, targetWasmType);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
71
127
|
if (this._sharedInfo(argType)) {
|
|
72
128
|
this.emitSharedLen(arg, targetWasmType);
|
|
73
129
|
return;
|
|
@@ -135,6 +191,10 @@ export class BuiltinsEmitter {
|
|
|
135
191
|
_emitBuiltinCap(call, targetWasmType) {
|
|
136
192
|
const arg = call.args[0];
|
|
137
193
|
const argType = arg._type;
|
|
194
|
+
if (call._constLen != null) {
|
|
195
|
+
this._emitConstArrayLen(call, targetWasmType);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
138
198
|
if (isSliceType(argType, this.mod.checker)) {
|
|
139
199
|
const elemType = this._getSliceElemType(argType);
|
|
140
200
|
const sliceInfo = this.mod.getSliceType(elemType);
|
|
@@ -217,18 +277,26 @@ export class BuiltinsEmitter {
|
|
|
217
277
|
this.pushInstruction({ op: "local.set", index: capTmp });
|
|
218
278
|
}
|
|
219
279
|
|
|
280
|
+
// Go: a negative len is a len panic; otherwise cap < len is a cap panic.
|
|
220
281
|
this.pushInstruction({ op: "local.get", index: lenTmp });
|
|
221
|
-
this.pushInstruction({ op: "local.get", index: capTmp });
|
|
222
|
-
this.pushInstruction("i32.gt_u");
|
|
223
|
-
this.pushInstruction({ op: "local.get", index: capTmp });
|
|
224
282
|
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
225
283
|
this.pushInstruction("i32.lt_s");
|
|
226
|
-
this.pushInstruction("i32.or");
|
|
227
284
|
this.pushInstruction({ op: "if", blockType: "void" });
|
|
228
285
|
this.emitPanic("runtime error: makeslice: len out of range");
|
|
229
286
|
this.pushInstruction("end");
|
|
287
|
+
this.pushInstruction({ op: "local.get", index: capTmp });
|
|
288
|
+
this.pushInstruction({ op: "local.get", index: lenTmp });
|
|
289
|
+
this.pushInstruction("i32.lt_s");
|
|
290
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
291
|
+
this.emitPanic("runtime error: makeslice: cap out of range");
|
|
292
|
+
this.pushInstruction("end");
|
|
230
293
|
|
|
231
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
isStructType(elemGoType, this.mod.checker, this.mod) ||
|
|
296
|
+
isComplex(elemGoType) ||
|
|
297
|
+
(elemGoType?.kind !== "pointer" &&
|
|
298
|
+
isArrayType(elemGoType, this.mod.checker))
|
|
299
|
+
) {
|
|
232
300
|
const arrTmp = this.acquireTemp({
|
|
233
301
|
kind: "ref",
|
|
234
302
|
nullable: true,
|
|
@@ -283,11 +351,9 @@ export class BuiltinsEmitter {
|
|
|
283
351
|
typeIndex: arrInfo.typeIndex,
|
|
284
352
|
});
|
|
285
353
|
} else {
|
|
286
|
-
this.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
typeIndex: arrInfo.typeIndex,
|
|
290
|
-
});
|
|
354
|
+
this._emitNewArray(elemGoType, arrInfo, () =>
|
|
355
|
+
this.pushInstruction({ op: "local.get", index: capTmp }),
|
|
356
|
+
);
|
|
291
357
|
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
292
358
|
this.pushInstruction({ op: "local.get", index: lenTmp });
|
|
293
359
|
this.pushInstruction({ op: "local.get", index: capTmp });
|
|
@@ -302,6 +368,49 @@ export class BuiltinsEmitter {
|
|
|
302
368
|
}
|
|
303
369
|
}
|
|
304
370
|
|
|
371
|
+
_emitBuiltinNew(call) {
|
|
372
|
+
const targetType =
|
|
373
|
+
call._type?.base ??
|
|
374
|
+
(call.args[0]?.kind === "TypeExpr"
|
|
375
|
+
? call.args[0].type
|
|
376
|
+
: call.args[0]?._type) ??
|
|
377
|
+
call.args[0];
|
|
378
|
+
if (isStructType(targetType, this.mod.checker, this.mod)) {
|
|
379
|
+
let structInfo = this._resolveStructInfo({ _type: targetType });
|
|
380
|
+
if (!structInfo) {
|
|
381
|
+
const typeName =
|
|
382
|
+
targetType.kind === "named" ? targetType.name : targetType.name;
|
|
383
|
+
if (typeName) structInfo = this.mod.getStructType(typeName);
|
|
384
|
+
}
|
|
385
|
+
if (!structInfo) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Unknown struct in new: ${targetType.name ?? targetType}`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
for (const f of structInfo.fields) {
|
|
391
|
+
this.emitZeroValue(f.goType, f.wType);
|
|
392
|
+
}
|
|
393
|
+
this.pushInstruction({
|
|
394
|
+
op: "struct.new",
|
|
395
|
+
typeIndex: structInfo.typeIndex,
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (isArrayType(targetType, this.mod.checker)) {
|
|
400
|
+
const wType = this.toWasmType(targetType);
|
|
401
|
+
this.emitZeroValue(targetType, wType);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const box = this.mod.getBoxType(targetType);
|
|
405
|
+
const valWType = this.toWasmType(targetType);
|
|
406
|
+
this.emitZeroValue(targetType, valWType);
|
|
407
|
+
this.pushInstruction({
|
|
408
|
+
op: "struct.new",
|
|
409
|
+
typeIndex: box.typeIndex,
|
|
410
|
+
typeName: box.name,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
305
414
|
// With `newLen` already computed: when it exceeds the old capacity, allocate
|
|
306
415
|
// a doubled (min 2, at least newLen) backing array and copy the old elements
|
|
307
416
|
// into it at offset 0; otherwise reuse the old array, offset and capacity.
|
|
@@ -343,11 +452,9 @@ export class BuiltinsEmitter {
|
|
|
343
452
|
this.pushInstruction({ op: "local.set", index: newCapTmp });
|
|
344
453
|
this.pushInstruction("end");
|
|
345
454
|
|
|
346
|
-
this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
typeIndex: arrInfo.typeIndex,
|
|
350
|
-
});
|
|
455
|
+
this._emitNewArray(elemGoType, arrInfo, () =>
|
|
456
|
+
this.pushInstruction({ op: "local.get", index: newCapTmp }),
|
|
457
|
+
);
|
|
351
458
|
this.pushInstruction({ op: "local.set", index: newArrTmp });
|
|
352
459
|
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
353
460
|
this.pushInstruction({ op: "local.set", index: newOffTmp });
|
|
@@ -374,6 +481,17 @@ export class BuiltinsEmitter {
|
|
|
374
481
|
this.pushInstruction("end");
|
|
375
482
|
}
|
|
376
483
|
|
|
484
|
+
// The source operand of `append(b, x...)` and `copy(b, x)`: a string
|
|
485
|
+
// (the only non-slice form Go allows) is converted to a fresh byte slice.
|
|
486
|
+
_emitSliceOperand(node, elemGoType, sliceWType) {
|
|
487
|
+
if (isStringType(node._type, this.mod.checker)) {
|
|
488
|
+
this.emitExpr(node, "externref");
|
|
489
|
+
this._emitStringToByteSlice({ kind: "slice", elem: elemGoType });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
this.emitExpr(node, sliceWType);
|
|
493
|
+
}
|
|
494
|
+
|
|
377
495
|
_emitBuiltinAppend(call) {
|
|
378
496
|
const { args } = call;
|
|
379
497
|
const sNode = args[0];
|
|
@@ -428,7 +546,7 @@ export class BuiltinsEmitter {
|
|
|
428
546
|
|
|
429
547
|
if (args.length === 2 && args[1]._spread) {
|
|
430
548
|
const s2Tmp = this.acquireTemp(sliceWType);
|
|
431
|
-
this.
|
|
549
|
+
this._emitSliceOperand(args[1], elemGoType, sliceWType);
|
|
432
550
|
this.pushInstruction({ op: "local.set", index: s2Tmp });
|
|
433
551
|
|
|
434
552
|
const s2ArrTmp = this.acquireTemp(arrWType);
|
|
@@ -487,7 +605,7 @@ export class BuiltinsEmitter {
|
|
|
487
605
|
this.pushInstruction("i32.add");
|
|
488
606
|
}
|
|
489
607
|
this.emitExpr(elemNode, arrInfo.elemWType);
|
|
490
|
-
this.
|
|
608
|
+
this._emitCopyIfValue(elemNode, elemGoType, arrInfo.elemWType);
|
|
491
609
|
this.pushInstruction({
|
|
492
610
|
op: "array.set",
|
|
493
611
|
typeIndex: arrInfo.typeIndex,
|
|
@@ -538,7 +656,7 @@ export class BuiltinsEmitter {
|
|
|
538
656
|
this.pushInstruction({ op: "local.set", index: dstTmp });
|
|
539
657
|
|
|
540
658
|
const srcTmp = this.acquireTemp(sliceWType);
|
|
541
|
-
this.
|
|
659
|
+
this._emitSliceOperand(srcNode, elemGoType, sliceWType);
|
|
542
660
|
this.pushInstruction({ op: "local.set", index: srcTmp });
|
|
543
661
|
|
|
544
662
|
const nTmp = this.acquireTemp("i32");
|
|
@@ -666,13 +784,21 @@ export class BuiltinsEmitter {
|
|
|
666
784
|
for (let i = 0; i < args.length; i++) {
|
|
667
785
|
const arg = args[i];
|
|
668
786
|
const isLast = i === args.length - 1 && isPrintln;
|
|
669
|
-
const isBool =
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
787
|
+
const { wType, isBool, widen } = printOperandKind(arg, this.mod.checker);
|
|
788
|
+
this.emitExpr(
|
|
789
|
+
arg,
|
|
790
|
+
widen ? "i32" : toWasmType(arg._type, this.mod.checker),
|
|
791
|
+
);
|
|
792
|
+
if (widen) this.pushInstruction("i64.extend_i32_u");
|
|
674
793
|
const logFuncIdx = this.mod.getLogImportIndex(wType, isLast, isBool);
|
|
675
794
|
this.pushInstruction({ op: "call", funcIndex: logFuncIdx });
|
|
795
|
+
// `print` never separates operands; `println` always does.
|
|
796
|
+
if (!isPrintln) {
|
|
797
|
+
this.pushInstruction({
|
|
798
|
+
op: "call",
|
|
799
|
+
funcIndex: this.mod.getPrintResetIndex(),
|
|
800
|
+
});
|
|
801
|
+
}
|
|
676
802
|
}
|
|
677
803
|
}
|
|
678
804
|
|
|
@@ -688,7 +814,11 @@ export class BuiltinsEmitter {
|
|
|
688
814
|
} else {
|
|
689
815
|
const goType = this._resolveExprGoType(arg);
|
|
690
816
|
const wType = toWasmType(goType, this.mod.checker);
|
|
691
|
-
if (
|
|
817
|
+
if (goType?.kind === "named" && wType === "externref") {
|
|
818
|
+
// A defined string type keeps its identity (and methods) in the
|
|
819
|
+
// panic value, so `recover()` returns it, not the bare string.
|
|
820
|
+
this.emitExpr(arg, "anyref");
|
|
821
|
+
} else if (
|
|
692
822
|
isStringType(goType, this.mod.checker) ||
|
|
693
823
|
(arg.kind === "BasicLit" && arg.litKind === "STRING") ||
|
|
694
824
|
wType === "externref"
|
|
@@ -795,6 +925,136 @@ export class BuiltinsEmitter {
|
|
|
795
925
|
}
|
|
796
926
|
}
|
|
797
927
|
|
|
928
|
+
_emitBuiltinComplex(call, targetWasmType) {
|
|
929
|
+
const arg0 = call.args[0];
|
|
930
|
+
const arg1 = call.args[1];
|
|
931
|
+
const callType = call._type;
|
|
932
|
+
const is64 =
|
|
933
|
+
callType?.name === "complex64" ||
|
|
934
|
+
arg0._type?.name === "float32" ||
|
|
935
|
+
arg1._type?.name === "float32" ||
|
|
936
|
+
this._isComplex64Target(targetWasmType);
|
|
937
|
+
const complexName = is64 ? "complex64" : "complex128";
|
|
938
|
+
const info = this.mod.getComplexType(complexName);
|
|
939
|
+
this.emitExpr(arg0, info.elemWType);
|
|
940
|
+
this.emitExpr(arg1, info.elemWType);
|
|
941
|
+
this.pushInstruction({
|
|
942
|
+
op: "struct.new",
|
|
943
|
+
typeIndex: info.typeIndex,
|
|
944
|
+
typeName: info.name,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
_emitBuiltinReal(call, targetWasmType) {
|
|
949
|
+
const arg = call.args[0];
|
|
950
|
+
const argType = arg._type ?? this._resolveExprGoType(arg);
|
|
951
|
+
const is64 = isComplex64(argType);
|
|
952
|
+
const complexName = is64 ? "complex64" : "complex128";
|
|
953
|
+
const info = this.mod.getComplexType(complexName);
|
|
954
|
+
const complexWType = {
|
|
955
|
+
kind: "ref",
|
|
956
|
+
nullable: true,
|
|
957
|
+
typeIndex: info.typeIndex,
|
|
958
|
+
typeName: info.name,
|
|
959
|
+
};
|
|
960
|
+
this.emitExpr(arg, complexWType);
|
|
961
|
+
this.pushInstruction({
|
|
962
|
+
op: "struct.get",
|
|
963
|
+
typeIndex: info.typeIndex,
|
|
964
|
+
typeName: info.name,
|
|
965
|
+
fieldIndex: 0,
|
|
966
|
+
});
|
|
967
|
+
if (targetWasmType && targetWasmType !== info.elemWType) {
|
|
968
|
+
if (info.elemWType === "f32" && targetWasmType === "f64") {
|
|
969
|
+
this.pushInstruction("f64.promote_f32");
|
|
970
|
+
} else if (info.elemWType === "f64" && targetWasmType === "f32") {
|
|
971
|
+
this.pushInstruction("f32.demote_f64");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
_emitBuiltinImag(call, targetWasmType) {
|
|
977
|
+
const arg = call.args[0];
|
|
978
|
+
const argType = arg._type ?? this._resolveExprGoType(arg);
|
|
979
|
+
const is64 = isComplex64(argType);
|
|
980
|
+
const complexName = is64 ? "complex64" : "complex128";
|
|
981
|
+
const info = this.mod.getComplexType(complexName);
|
|
982
|
+
const complexWType = {
|
|
983
|
+
kind: "ref",
|
|
984
|
+
nullable: true,
|
|
985
|
+
typeIndex: info.typeIndex,
|
|
986
|
+
typeName: info.name,
|
|
987
|
+
};
|
|
988
|
+
this.emitExpr(arg, complexWType);
|
|
989
|
+
this.pushInstruction({
|
|
990
|
+
op: "struct.get",
|
|
991
|
+
typeIndex: info.typeIndex,
|
|
992
|
+
typeName: info.name,
|
|
993
|
+
fieldIndex: 1,
|
|
994
|
+
});
|
|
995
|
+
if (targetWasmType && targetWasmType !== info.elemWType) {
|
|
996
|
+
if (info.elemWType === "f32" && targetWasmType === "f64") {
|
|
997
|
+
this.pushInstruction("f64.promote_f32");
|
|
998
|
+
} else if (info.elemWType === "f64" && targetWasmType === "f32") {
|
|
999
|
+
this.pushInstruction("f32.demote_f64");
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// min/max fold left over the operands: floats use f32/f64.min/max (NaN
|
|
1005
|
+
// propagates, -0 < +0), integers `select` on lt/gt, strings compare via the
|
|
1006
|
+
// string import. A single operand is the result itself.
|
|
1007
|
+
_emitBuiltinMinMax(call, targetWasmType) {
|
|
1008
|
+
const isMin = call.func.name === "min";
|
|
1009
|
+
const goType = call._type ?? this._resolveExprGoType(call.args[0]);
|
|
1010
|
+
const wType =
|
|
1011
|
+
goType?.kind === "untyped" && typeof targetWasmType === "string"
|
|
1012
|
+
? targetWasmType
|
|
1013
|
+
: toWasmType(goType, this.mod.checker);
|
|
1014
|
+
const [first, ...rest] = call.args;
|
|
1015
|
+
this.emitExpr(first, wType);
|
|
1016
|
+
if (rest.length === 0) return;
|
|
1017
|
+
const acc = this.acquireTemp(wType);
|
|
1018
|
+
const next = this.acquireTemp(wType);
|
|
1019
|
+
const isStr = isStringType(goType, this.mod.checker);
|
|
1020
|
+
const isFloatType = wType === "f32" || wType === "f64";
|
|
1021
|
+
const cmp = isMin ? "lt" : "gt";
|
|
1022
|
+
for (const arg of rest) {
|
|
1023
|
+
this.pushInstruction({ op: "local.set", index: acc });
|
|
1024
|
+
this.emitExpr(arg, wType);
|
|
1025
|
+
this.pushInstruction({ op: "local.set", index: next });
|
|
1026
|
+
if (isFloatType) {
|
|
1027
|
+
this.pushInstruction({ op: "local.get", index: acc });
|
|
1028
|
+
this.pushInstruction({ op: "local.get", index: next });
|
|
1029
|
+
this.pushInstruction(`${wType}.${isMin ? "min" : "max"}`);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
if (isStr) {
|
|
1033
|
+
// next < acc (min) / next > acc (max) ? next : acc
|
|
1034
|
+
this.pushInstruction({ op: "local.get", index: next });
|
|
1035
|
+
this.pushInstruction({ op: "local.get", index: acc });
|
|
1036
|
+
this.pushInstruction({
|
|
1037
|
+
op: "call",
|
|
1038
|
+
funcIndex: this.mod.getStringCmpImportIndex(isMin ? "<" : ">"),
|
|
1039
|
+
});
|
|
1040
|
+
this.pushInstruction({ op: "if", blockType: wType });
|
|
1041
|
+
this.pushInstruction({ op: "local.get", index: next });
|
|
1042
|
+
this.pushInstruction("else");
|
|
1043
|
+
this.pushInstruction({ op: "local.get", index: acc });
|
|
1044
|
+
this.pushInstruction("end");
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
this.pushInstruction({ op: "local.get", index: next });
|
|
1048
|
+
this.pushInstruction({ op: "local.get", index: acc });
|
|
1049
|
+
this.pushInstruction({ op: "local.get", index: next });
|
|
1050
|
+
this.pushInstruction({ op: "local.get", index: acc });
|
|
1051
|
+
this.pushInstruction(`${wType}.${cmp}_${isSigned(goType) ? "s" : "u"}`);
|
|
1052
|
+
this.pushInstruction("select");
|
|
1053
|
+
}
|
|
1054
|
+
this.releaseTemp(next, wType);
|
|
1055
|
+
this.releaseTemp(acc, wType);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
798
1058
|
_emitBuiltinCall(call, targetWasmType) {
|
|
799
1059
|
const { func } = call;
|
|
800
1060
|
if (func.kind !== "Ident") return false;
|
|
@@ -808,6 +1068,9 @@ export class BuiltinsEmitter {
|
|
|
808
1068
|
case "cap":
|
|
809
1069
|
this._emitBuiltinCap(call, targetWasmType);
|
|
810
1070
|
return true;
|
|
1071
|
+
case "new":
|
|
1072
|
+
this._emitBuiltinNew(call);
|
|
1073
|
+
return true;
|
|
811
1074
|
case "make":
|
|
812
1075
|
this._emitBuiltinMake(call);
|
|
813
1076
|
return true;
|
|
@@ -833,6 +1096,19 @@ export class BuiltinsEmitter {
|
|
|
833
1096
|
case "recover":
|
|
834
1097
|
this._emitBuiltinRecover(call, targetWasmType);
|
|
835
1098
|
return true;
|
|
1099
|
+
case "complex":
|
|
1100
|
+
this._emitBuiltinComplex(call, targetWasmType);
|
|
1101
|
+
return true;
|
|
1102
|
+
case "real":
|
|
1103
|
+
this._emitBuiltinReal(call, targetWasmType);
|
|
1104
|
+
return true;
|
|
1105
|
+
case "imag":
|
|
1106
|
+
this._emitBuiltinImag(call, targetWasmType);
|
|
1107
|
+
return true;
|
|
1108
|
+
case "min":
|
|
1109
|
+
case "max":
|
|
1110
|
+
this._emitBuiltinMinMax(call, targetWasmType);
|
|
1111
|
+
return true;
|
|
836
1112
|
default:
|
|
837
1113
|
return false;
|
|
838
1114
|
}
|
|
@@ -841,95 +1117,17 @@ export class BuiltinsEmitter {
|
|
|
841
1117
|
_emitPackageCall(call, targetWasmType) {
|
|
842
1118
|
const { func, args } = call;
|
|
843
1119
|
if (func.kind !== "SelectorExpr") return false;
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1120
|
+
if (
|
|
1121
|
+
func.expr?.name &&
|
|
1122
|
+
(this.resolveLocal(func.expr.name) ||
|
|
1123
|
+
this.mod.resolveGlobal(func.expr.name))
|
|
1124
|
+
)
|
|
1125
|
+
return false;
|
|
848
1126
|
const pkgName = func.expr?.name;
|
|
849
1127
|
switch (pkgName) {
|
|
850
|
-
case "math":
|
|
851
|
-
this.emitMathCall(func.field, args);
|
|
852
|
-
return true;
|
|
853
|
-
case "bits":
|
|
854
|
-
this.emitBitsCall(func.field, args);
|
|
855
|
-
return true;
|
|
856
|
-
case "maps":
|
|
857
|
-
this.emitMapsCall(func.field, args, targetWasmType);
|
|
858
|
-
return true;
|
|
859
|
-
case "slices":
|
|
860
|
-
this.emitSlicesCall(func.field, args, targetWasmType);
|
|
861
|
-
return true;
|
|
862
|
-
case "strings":
|
|
863
|
-
if (func.field === "NewReader") {
|
|
864
|
-
this.emitStringsNewReader(call, targetWasmType);
|
|
865
|
-
return true;
|
|
866
|
-
}
|
|
867
|
-
this.emitStringsCall(func.field, args, targetWasmType);
|
|
868
|
-
return true;
|
|
869
|
-
case "strconv":
|
|
870
|
-
this.emitStrconvCall(func.field, args);
|
|
871
|
-
return true;
|
|
872
|
-
case "fmt":
|
|
873
|
-
if (func.field.startsWith("Fprint")) {
|
|
874
|
-
this.emitFprintCall(func.field, call, targetWasmType);
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
if (func.field.startsWith("Sscan")) {
|
|
878
|
-
this.emitSscanCall(func.field, call, targetWasmType);
|
|
879
|
-
return true;
|
|
880
|
-
}
|
|
881
|
-
this.emitFmtCall(func.field, args);
|
|
882
|
-
return true;
|
|
883
|
-
case "errors":
|
|
884
|
-
this.emitErrorsCall(func.field, args);
|
|
885
|
-
return true;
|
|
886
|
-
case "utf8":
|
|
887
|
-
this.emitUtf8Call(func.field, args, targetWasmType);
|
|
888
|
-
return true;
|
|
889
|
-
case "sort":
|
|
890
|
-
this.emitSortCall(func.field, args, targetWasmType);
|
|
891
|
-
return true;
|
|
892
1128
|
case "shared":
|
|
893
1129
|
this.emitSharedNew(func.field, args);
|
|
894
1130
|
return true;
|
|
895
|
-
case "unicode":
|
|
896
|
-
this.emitUnicodeCall(func.field, args, targetWasmType);
|
|
897
|
-
return true;
|
|
898
|
-
case "rand":
|
|
899
|
-
this.emitRandCall(func.field, args, targetWasmType);
|
|
900
|
-
return true;
|
|
901
|
-
case "bytes":
|
|
902
|
-
if (func.field === "NewReader") {
|
|
903
|
-
this.emitBytesNewReader(call, targetWasmType);
|
|
904
|
-
return true;
|
|
905
|
-
}
|
|
906
|
-
this.emitBytesCall(func.field, args, targetWasmType);
|
|
907
|
-
return true;
|
|
908
|
-
case "path":
|
|
909
|
-
case "filepath":
|
|
910
|
-
this.emitPathCall(func.field, args);
|
|
911
|
-
return true;
|
|
912
|
-
case "html":
|
|
913
|
-
this.emitHtmlCall(func.field, args);
|
|
914
|
-
return true;
|
|
915
|
-
case "io":
|
|
916
|
-
this.emitIoCall(func.field, call, targetWasmType);
|
|
917
|
-
return true;
|
|
918
|
-
case "time":
|
|
919
|
-
this.emitTimeCall(func.field, call, targetWasmType);
|
|
920
|
-
return true;
|
|
921
|
-
case "binary":
|
|
922
|
-
this.emitBinaryCall(func.field, call, targetWasmType);
|
|
923
|
-
return true;
|
|
924
|
-
case "sha256":
|
|
925
|
-
this.emitSha256Call(func.field, call, targetWasmType);
|
|
926
|
-
return true;
|
|
927
|
-
case "hex":
|
|
928
|
-
this.emitHexCall(func.field, call, targetWasmType);
|
|
929
|
-
return true;
|
|
930
|
-
case "base64":
|
|
931
|
-
this.emitBase64Call(func.field, call, targetWasmType);
|
|
932
|
-
return true;
|
|
933
1131
|
case "simd":
|
|
934
1132
|
this.emitSimdPackageCall(func.field, args, targetWasmType);
|
|
935
1133
|
return true;
|
|
@@ -961,29 +1159,13 @@ export class BuiltinsEmitter {
|
|
|
961
1159
|
func.expr,
|
|
962
1160
|
args,
|
|
963
1161
|
targetWasmType,
|
|
1162
|
+
recvTypeName === "simd.Mask32x4" ||
|
|
1163
|
+
recvType?.underlying?.simd === "Mask32x4" ||
|
|
1164
|
+
recvType?.simd === "Mask32x4",
|
|
964
1165
|
);
|
|
965
1166
|
}
|
|
966
|
-
if (
|
|
967
|
-
this._emitStdlibReceiverMethod(
|
|
968
|
-
recvTypeName,
|
|
969
|
-
func.field,
|
|
970
|
-
func.expr,
|
|
971
|
-
args,
|
|
972
|
-
targetWasmType,
|
|
973
|
-
)
|
|
974
|
-
) {
|
|
975
|
-
return true;
|
|
976
|
-
}
|
|
977
|
-
if (recvTypeName === "strings.Builder") {
|
|
978
|
-
this.emitBuilderMethodCall(func.field, func.expr, args);
|
|
979
|
-
return true;
|
|
980
|
-
}
|
|
981
|
-
if (recvTypeName === "strings.Replacer") {
|
|
982
|
-
this.emitReplacerMethodCall(func.field, func.expr, args);
|
|
983
|
-
return true;
|
|
984
|
-
}
|
|
985
1167
|
if (isNonEmptyInterface(recvType, this.mod.checker)) {
|
|
986
|
-
const ifaceName = recvTypeName ??
|
|
1168
|
+
const ifaceName = recvTypeName ?? anonIfaceName(recvType);
|
|
987
1169
|
const dispatchName = `__dispatch_${ifaceName}_${func.field}`;
|
|
988
1170
|
const targetFuncIdx = this.mod.resolveFuncIndex(dispatchName);
|
|
989
1171
|
if (targetFuncIdx !== null) {
|
|
@@ -994,7 +1176,7 @@ export class BuiltinsEmitter {
|
|
|
994
1176
|
const pType = targetParamTypes[i + 1] ?? null;
|
|
995
1177
|
const pGoType = paramGoTypes[i + 1] ?? null;
|
|
996
1178
|
this.emitExpr(args[i], pType);
|
|
997
|
-
this.
|
|
1179
|
+
this._emitCopyIfValue(args[i], pGoType, pType);
|
|
998
1180
|
}
|
|
999
1181
|
this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
|
|
1000
1182
|
return true;
|
|
@@ -1016,14 +1198,20 @@ export class BuiltinsEmitter {
|
|
|
1016
1198
|
if (targetFuncIdx !== null) {
|
|
1017
1199
|
const recvWType = targetParamTypes[0] ?? null;
|
|
1018
1200
|
const recvGoType = this.mod.getFuncParamGoTypes(methodName)[0] ?? null;
|
|
1019
|
-
this.emitExpr(func
|
|
1020
|
-
|
|
1201
|
+
this.emitExpr(this._recvOperand(func, recvGoType), recvWType);
|
|
1202
|
+
// `p.M()` with a value receiver reads `*p`: a nil `p` panics here.
|
|
1203
|
+
if (
|
|
1204
|
+
recvGoType?.kind !== "pointer" &&
|
|
1205
|
+
isStructType(recvGoType, this.mod.checker, this.mod)
|
|
1206
|
+
)
|
|
1207
|
+
this._emitNilCheck(func.expr._type);
|
|
1208
|
+
this._emitCopyIfValue(func.expr, recvGoType, recvWType);
|
|
1021
1209
|
const paramGoTypes = this.mod.getFuncParamGoTypes(methodName);
|
|
1022
1210
|
for (let i = 0; i < args.length; i++) {
|
|
1023
1211
|
const pType = targetParamTypes[i + 1] ?? null;
|
|
1024
1212
|
const pGoType = paramGoTypes[i + 1] ?? null;
|
|
1025
1213
|
this.emitExpr(args[i], pType);
|
|
1026
|
-
this.
|
|
1214
|
+
this._emitCopyIfValue(args[i], pGoType, pType);
|
|
1027
1215
|
}
|
|
1028
1216
|
this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
|
|
1029
1217
|
return true;
|
|
@@ -1032,111 +1220,97 @@ export class BuiltinsEmitter {
|
|
|
1032
1220
|
return false;
|
|
1033
1221
|
}
|
|
1034
1222
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
expr
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
this.emitBuilderMethodCall(field, expr, args, targetWasmType);
|
|
1044
|
-
return true;
|
|
1045
|
-
}
|
|
1046
|
-
if (recvTypeName === "strings.Replacer") {
|
|
1047
|
-
this.emitReplacerMethodCall(field, expr, args);
|
|
1048
|
-
return true;
|
|
1049
|
-
}
|
|
1050
|
-
if (recvTypeName === "bytes.Buffer") {
|
|
1051
|
-
this.emitBytesBufferMethodCall(field, expr, args, targetWasmType);
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
if (recvTypeName === "strings.Reader") {
|
|
1055
|
-
this.emitStringsReaderMethodCall(field, expr, args, targetWasmType);
|
|
1056
|
-
return true;
|
|
1057
|
-
}
|
|
1058
|
-
if (recvTypeName === "bytes.Reader") {
|
|
1059
|
-
this.emitBytesReaderMethodCall(field, expr, args, targetWasmType);
|
|
1060
|
-
return true;
|
|
1061
|
-
}
|
|
1062
|
-
if (recvTypeName === "time.Time") {
|
|
1063
|
-
this.emitTimeMethodCall(field, expr, args, targetWasmType);
|
|
1064
|
-
return true;
|
|
1065
|
-
}
|
|
1066
|
-
if (recvTypeName === "time.Duration") {
|
|
1067
|
-
this.emitDurationMethodCall(field, expr, args, targetWasmType);
|
|
1068
|
-
return true;
|
|
1069
|
-
}
|
|
1070
|
-
if (recvTypeName === "io.LimitedReader") {
|
|
1071
|
-
this.emitIoLimitedReaderMethodCall(field, expr, args, targetWasmType);
|
|
1072
|
-
return true;
|
|
1073
|
-
}
|
|
1074
|
-
if (recvTypeName === "sync.Mutex" || recvTypeName === "sync.RWMutex") {
|
|
1075
|
-
this.emitSyncMutexMethodCall(field, expr, args, targetWasmType);
|
|
1076
|
-
return true;
|
|
1077
|
-
}
|
|
1078
|
-
if (recvTypeName === "sync.Once") {
|
|
1079
|
-
this.emitSyncOnceMethodCall(field, expr, args, targetWasmType);
|
|
1080
|
-
return true;
|
|
1081
|
-
}
|
|
1082
|
-
if (recvTypeName === "sync.WaitGroup") {
|
|
1083
|
-
this.emitSyncWaitGroupMethodCall(field, expr, args, targetWasmType);
|
|
1084
|
-
return true;
|
|
1223
|
+
// The receiver operand of a method call: `p.Get()` with a value receiver
|
|
1224
|
+
// and `p *N` (a named scalar or slice) passes `*p`.
|
|
1225
|
+
_recvOperand(func, recvGoType) {
|
|
1226
|
+
const t = func.expr._type;
|
|
1227
|
+
// A pointer receiver on `*p` gets `p` itself, so the method mutates *p.
|
|
1228
|
+
if (recvGoType?.kind === "pointer") {
|
|
1229
|
+
const pointer = this._structDerefPointer(func.expr);
|
|
1230
|
+
if (pointer) return pointer;
|
|
1085
1231
|
}
|
|
1086
1232
|
if (
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
)
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
return true;
|
|
1102
|
-
}
|
|
1233
|
+
t?.kind === "pointer" &&
|
|
1234
|
+
recvGoType?.kind !== "pointer" &&
|
|
1235
|
+
(isScalarType(t.base, this.mod.checker, this.mod) ||
|
|
1236
|
+
isSliceType(t.base, this.mod.checker))
|
|
1237
|
+
)
|
|
1238
|
+
return { kind: "UnaryExpr", op: "*", operand: func.expr, _type: t.base };
|
|
1239
|
+
return this._ptrRecvOperand(func);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// `x.M()` with a pointer receiver on a named scalar value passes `&x`
|
|
1243
|
+
// (the typechecker boxed `x`).
|
|
1244
|
+
_ptrRecvOperand(func) {
|
|
1245
|
+
const t = func.expr._type;
|
|
1246
|
+
if (!func._type?._ptrRecv || t?.kind !== "named") return func.expr;
|
|
1103
1247
|
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
)
|
|
1107
|
-
|
|
1108
|
-
|
|
1248
|
+
!isScalarType(t, this.mod.checker, this.mod) &&
|
|
1249
|
+
!isSliceType(t, this.mod.checker)
|
|
1250
|
+
)
|
|
1251
|
+
return func.expr;
|
|
1252
|
+
return {
|
|
1253
|
+
kind: "UnaryExpr",
|
|
1254
|
+
op: "&",
|
|
1255
|
+
operand: func.expr,
|
|
1256
|
+
_type: { kind: "pointer", base: t },
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// The embedded struct (possibly several levels down) that declares or
|
|
1261
|
+
// forwards `method`: the chain of embeds to follow and the method's name.
|
|
1262
|
+
_findEmbeddedMethod(structInfo, method, seen = new Set()) {
|
|
1263
|
+
if (seen.has(structInfo)) return null;
|
|
1264
|
+
seen.add(structInfo);
|
|
1265
|
+
for (const embed of structInfo.embeds ?? []) {
|
|
1266
|
+
const typeName = embed.typeName ?? embed.name;
|
|
1267
|
+
const methodName = `${typeName}.${method}`;
|
|
1268
|
+
if (this.mod.resolveFuncIndex(methodName) !== null)
|
|
1269
|
+
return { chain: [{ structInfo, embed }], methodName };
|
|
1270
|
+
const inner = this.mod.getStructType(typeName);
|
|
1271
|
+
const deeper = inner && this._findEmbeddedMethod(inner, method, seen);
|
|
1272
|
+
if (deeper)
|
|
1273
|
+
return {
|
|
1274
|
+
chain: [{ structInfo, embed }, ...deeper.chain],
|
|
1275
|
+
methodName: deeper.methodName,
|
|
1276
|
+
};
|
|
1109
1277
|
}
|
|
1110
|
-
return
|
|
1278
|
+
return null;
|
|
1111
1279
|
}
|
|
1112
1280
|
|
|
1113
1281
|
_emitEmbeddedMethodCall(recvTypeName, func, args, recvType) {
|
|
1114
1282
|
const structInfo = this.mod.getStructType(recvTypeName);
|
|
1115
1283
|
if (!structInfo) return false;
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
return true;
|
|
1137
|
-
}
|
|
1284
|
+
const found = this._findEmbeddedMethod(structInfo, func.field);
|
|
1285
|
+
if (!found) return false;
|
|
1286
|
+
const { chain, methodName } = found;
|
|
1287
|
+
const subIdx = this.mod.resolveFuncIndex(methodName);
|
|
1288
|
+
const targetParamTypes = this.mod.getFuncParamTypes(methodName);
|
|
1289
|
+
const valueRecv =
|
|
1290
|
+
this.mod.getFuncParamGoTypes(methodName)[0]?.kind !== "pointer";
|
|
1291
|
+
this.emitExpr(func.expr, this.toWasmType(recvType));
|
|
1292
|
+
this._emitNilCheck(func.expr._type);
|
|
1293
|
+
for (const [i, { structInfo: owner, embed }] of chain.entries()) {
|
|
1294
|
+
this.pushInstruction({
|
|
1295
|
+
op: "struct.get",
|
|
1296
|
+
typeIndex: owner.typeIndex,
|
|
1297
|
+
fieldIndex: embed.fieldIndex,
|
|
1298
|
+
});
|
|
1299
|
+
// Reading through an embedded nil pointer (to the next embedded
|
|
1300
|
+
// struct, or as a value receiver) is a recoverable nil dereference.
|
|
1301
|
+
const ft = owner.fields[embed.fieldIndex]?.goType;
|
|
1302
|
+
const isPtr = ft?.kind === "pointer" || ft?.kind === "PointerType";
|
|
1303
|
+
if (isPtr && (i < chain.length - 1 || valueRecv)) this._emitNilBranch();
|
|
1138
1304
|
}
|
|
1139
|
-
|
|
1305
|
+
for (let i = 0; i < args.length; i++) {
|
|
1306
|
+
const pType = targetParamTypes[i + 1] ?? null;
|
|
1307
|
+
this.emitExpr(args[i], pType);
|
|
1308
|
+
}
|
|
1309
|
+
this.pushInstruction({
|
|
1310
|
+
op: "call",
|
|
1311
|
+
funcIndex: subIdx,
|
|
1312
|
+
});
|
|
1313
|
+
return true;
|
|
1140
1314
|
}
|
|
1141
1315
|
|
|
1142
1316
|
_emitStaticCall(call) {
|
|
@@ -1149,12 +1323,18 @@ export class BuiltinsEmitter {
|
|
|
1149
1323
|
if (targetFuncIdx !== null) {
|
|
1150
1324
|
const paramTypes = this.mod.getFuncParamTypes(func.name);
|
|
1151
1325
|
const paramGoTypes = this.mod.getFuncParamGoTypes(func.name);
|
|
1326
|
+
if (call._multiForward) {
|
|
1327
|
+
// `f(g())`: g's results are f's arguments as they come.
|
|
1328
|
+
this.emitExpr(args[0], null);
|
|
1329
|
+
this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
|
|
1330
|
+
return true;
|
|
1331
|
+
}
|
|
1152
1332
|
for (let i = 0; i < args.length; i++) {
|
|
1153
1333
|
const arg = args[i];
|
|
1154
1334
|
const pType = paramTypes[i] ?? null;
|
|
1155
1335
|
const pGoType = paramGoTypes[i] ?? null;
|
|
1156
1336
|
this.emitExpr(arg, pType);
|
|
1157
|
-
this.
|
|
1337
|
+
this._emitCopyIfValue(arg, pGoType, pType);
|
|
1158
1338
|
}
|
|
1159
1339
|
this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
|
|
1160
1340
|
return true;
|
|
@@ -1175,6 +1355,8 @@ export class BuiltinsEmitter {
|
|
|
1175
1355
|
|
|
1176
1356
|
const closureTmp = this.acquireTemp(closureWType);
|
|
1177
1357
|
this.emitExpr(func, closureWType);
|
|
1358
|
+
// Calling a nil func value panics like a nil pointer dereference.
|
|
1359
|
+
this._emitNilBranch();
|
|
1178
1360
|
this.pushInstruction({ op: "local.set", index: closureTmp });
|
|
1179
1361
|
|
|
1180
1362
|
// Push param 0: env (anyref)
|
|
@@ -1191,7 +1373,7 @@ export class BuiltinsEmitter {
|
|
|
1191
1373
|
const pWType = closureInfo.paramWTypes[i] ?? null;
|
|
1192
1374
|
const pGoType = closureInfo.sig.params[i] ?? null;
|
|
1193
1375
|
this.emitExpr(arg, pWType);
|
|
1194
|
-
this.
|
|
1376
|
+
this._emitCopyIfValue(arg, pGoType, pWType);
|
|
1195
1377
|
}
|
|
1196
1378
|
|
|
1197
1379
|
// Push funcref
|
|
@@ -1244,4 +1426,499 @@ export class BuiltinsEmitter {
|
|
|
1244
1426
|
|
|
1245
1427
|
this._emitClosureCall(call);
|
|
1246
1428
|
}
|
|
1429
|
+
|
|
1430
|
+
emitSliceClear(arg, argType) {
|
|
1431
|
+
const elemGoType = this._getSliceElemType(argType);
|
|
1432
|
+
const sliceInfo = this.mod.getSliceType(elemGoType);
|
|
1433
|
+
const arrInfo = sliceInfo.arrInfo;
|
|
1434
|
+
|
|
1435
|
+
const sliceTmp = this.acquireTemp({
|
|
1436
|
+
kind: "ref",
|
|
1437
|
+
nullable: true,
|
|
1438
|
+
typeIndex: sliceInfo.typeIndex,
|
|
1439
|
+
});
|
|
1440
|
+
this.emitExpr(arg, {
|
|
1441
|
+
kind: "ref",
|
|
1442
|
+
nullable: true,
|
|
1443
|
+
typeIndex: sliceInfo.typeIndex,
|
|
1444
|
+
});
|
|
1445
|
+
this.pushInstruction({ op: "local.set", index: sliceTmp });
|
|
1446
|
+
|
|
1447
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1448
|
+
this.pushInstruction({ op: "local.get", index: sliceTmp });
|
|
1449
|
+
this.pushInstruction("ref.is_null");
|
|
1450
|
+
this.pushInstruction({ op: "br_if", depth: 0 });
|
|
1451
|
+
|
|
1452
|
+
const arrTmp = this.acquireTemp({
|
|
1453
|
+
kind: "ref",
|
|
1454
|
+
nullable: true,
|
|
1455
|
+
typeIndex: arrInfo.typeIndex,
|
|
1456
|
+
});
|
|
1457
|
+
const offTmp = this.acquireTemp("i32");
|
|
1458
|
+
const lenTmp = this.acquireTemp("i32");
|
|
1459
|
+
const iLoc = this.acquireTemp("i32");
|
|
1460
|
+
|
|
1461
|
+
this._emitSliceUnpack(sliceTmp, sliceInfo, {
|
|
1462
|
+
arr: arrTmp,
|
|
1463
|
+
off: offTmp,
|
|
1464
|
+
len: lenTmp,
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1468
|
+
this.pushInstruction({ op: "local.set", index: iLoc });
|
|
1469
|
+
|
|
1470
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
1471
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
1472
|
+
this.pushInstruction({ op: "local.get", index: iLoc });
|
|
1473
|
+
this.pushInstruction({ op: "local.get", index: lenTmp });
|
|
1474
|
+
this.pushInstruction("i32.ge_s");
|
|
1475
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
1476
|
+
|
|
1477
|
+
this.pushInstruction({ op: "local.get", index: arrTmp });
|
|
1478
|
+
this.pushInstruction({ op: "local.get", index: offTmp });
|
|
1479
|
+
this.pushInstruction({ op: "local.get", index: iLoc });
|
|
1480
|
+
this.pushInstruction("i32.add");
|
|
1481
|
+
this.emitZeroValue(elemGoType, arrInfo.elemWType);
|
|
1482
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
1483
|
+
|
|
1484
|
+
this.pushInstruction({ op: "local.get", index: iLoc });
|
|
1485
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
1486
|
+
this.pushInstruction("i32.add");
|
|
1487
|
+
this.pushInstruction({ op: "local.set", index: iLoc });
|
|
1488
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
1489
|
+
this.pushInstruction("end");
|
|
1490
|
+
this.pushInstruction("end");
|
|
1491
|
+
|
|
1492
|
+
this.releaseTemp(iLoc, "i32");
|
|
1493
|
+
this.releaseTemp(lenTmp, "i32");
|
|
1494
|
+
this.releaseTemp(offTmp, "i32");
|
|
1495
|
+
this.releaseTemp(arrTmp, {
|
|
1496
|
+
kind: "ref",
|
|
1497
|
+
nullable: true,
|
|
1498
|
+
typeIndex: arrInfo.typeIndex,
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
this.pushInstruction("end");
|
|
1502
|
+
this.releaseTemp(sliceTmp, {
|
|
1503
|
+
kind: "ref",
|
|
1504
|
+
nullable: true,
|
|
1505
|
+
typeIndex: sliceInfo.typeIndex,
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Pushes an integer-ish argument as i32 (Go ints are i64 in this backend).
|
|
1510
|
+
_emitI32Arg(arg) {
|
|
1511
|
+
const wType = toWasmType(arg?._type, this.mod.checker);
|
|
1512
|
+
if (wType === "i64") {
|
|
1513
|
+
this.emitExpr(arg, "i64");
|
|
1514
|
+
this.pushInstruction("i32.wrap_i64");
|
|
1515
|
+
} else {
|
|
1516
|
+
this.emitExpr(arg, "i32");
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// `a == b` for two anyref operands (interface values). `ref.eq` only
|
|
1521
|
+
// accepts eqref, so host strings (internalised externs) are compared via
|
|
1522
|
+
// `str_eq` and anything else by identity.
|
|
1523
|
+
_emitAnyEq() {
|
|
1524
|
+
const idx = this.mod.resolveFuncIndex("__any_eq");
|
|
1525
|
+
if (idx === null) {
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
"internal: anyref equality emitted but __any_eq was not declared (usesAnyEq scan missed a site)",
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
this.pushInstruction({ op: "call", funcIndex: idx });
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// Every `[wasm type index, Go type name]` an `any` can hold in this module,
|
|
1534
|
+
// more specific types last so they overwrite their supertypes.
|
|
1535
|
+
_dynamicTypeNames() {
|
|
1536
|
+
const named = (name) => {
|
|
1537
|
+
const i = name.indexOf("$$");
|
|
1538
|
+
return i < 0
|
|
1539
|
+
? goTypeName({ kind: "named", name })
|
|
1540
|
+
: `${name.slice(i + 2)}.${name.slice(0, i)}`;
|
|
1541
|
+
};
|
|
1542
|
+
const mod = this.mod;
|
|
1543
|
+
const names = [];
|
|
1544
|
+
for (const s of mod.sliceTypes.values())
|
|
1545
|
+
names.push([
|
|
1546
|
+
s.typeIndex,
|
|
1547
|
+
goTypeName({ kind: "slice", elem: s.elemGoType }),
|
|
1548
|
+
]);
|
|
1549
|
+
for (const m of mod.mapTypes.values())
|
|
1550
|
+
names.push([
|
|
1551
|
+
m.typeIndex,
|
|
1552
|
+
goTypeName({ kind: "map", key: m.keyGoType, value: m.valGoType }),
|
|
1553
|
+
]);
|
|
1554
|
+
for (const info of new Set(mod.structTypes.values())) {
|
|
1555
|
+
if (info.isAnon) continue;
|
|
1556
|
+
names.push([
|
|
1557
|
+
info.typeIndex,
|
|
1558
|
+
info.name === ERROR_STRUCT
|
|
1559
|
+
? "*errors.errorString"
|
|
1560
|
+
: `*${named(info.name)}`,
|
|
1561
|
+
]);
|
|
1562
|
+
}
|
|
1563
|
+
for (const box of mod.ifaceBoxTypes.values())
|
|
1564
|
+
names.push([box.typeIndex, goTypeName(box.goType)]);
|
|
1565
|
+
for (const info of mod.taggedSliceTypes.values())
|
|
1566
|
+
names.push([info.typeIndex, goTypeName(info.goType)]);
|
|
1567
|
+
for (const info of mod.nilBoxTypes.values())
|
|
1568
|
+
names.push([info.typeIndex, goTypeName(info.goType)]);
|
|
1569
|
+
return names;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Body of `__ta_fail(v any, src string, target string, iface int32)`: panics
|
|
1573
|
+
// with Go's failed type assertion message, naming the dynamic type of `v`
|
|
1574
|
+
// from the types the module knows (`src` is the static interface type, or
|
|
1575
|
+
// the first method the target interface needs when `iface` is 1).
|
|
1576
|
+
_emitTypeAssertFail() {
|
|
1577
|
+
const strConst = (text) => {
|
|
1578
|
+
this.pushInstruction({
|
|
1579
|
+
op: "i32.const",
|
|
1580
|
+
value: this.mod.internString(text),
|
|
1581
|
+
});
|
|
1582
|
+
this.pushInstruction({
|
|
1583
|
+
op: "call",
|
|
1584
|
+
funcIndex: this.mod.getStringImportIndex(),
|
|
1585
|
+
});
|
|
1586
|
+
};
|
|
1587
|
+
const concat = () =>
|
|
1588
|
+
this.pushInstruction({
|
|
1589
|
+
op: "call",
|
|
1590
|
+
funcIndex: this.mod.getStringConcatImportIndex(),
|
|
1591
|
+
});
|
|
1592
|
+
const dyn = this.allocLocal(null, "externref");
|
|
1593
|
+
strConst("interface {}");
|
|
1594
|
+
this.pushInstruction({ op: "local.set", index: dyn });
|
|
1595
|
+
const when = (typeIndex, text) => {
|
|
1596
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1597
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1598
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1599
|
+
strConst(text);
|
|
1600
|
+
this.pushInstruction({ op: "local.set", index: dyn });
|
|
1601
|
+
this.pushInstruction("end");
|
|
1602
|
+
};
|
|
1603
|
+
for (const [typeIndex, text] of this._dynamicTypeNames())
|
|
1604
|
+
when(typeIndex, text);
|
|
1605
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1606
|
+
this.pushInstruction({
|
|
1607
|
+
op: "call",
|
|
1608
|
+
funcIndex: this.mod.getIsStringImportIndex(),
|
|
1609
|
+
});
|
|
1610
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1611
|
+
strConst("string");
|
|
1612
|
+
this.pushInstruction({ op: "local.set", index: dyn });
|
|
1613
|
+
this.pushInstruction("end");
|
|
1614
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1615
|
+
this.pushInstruction("ref.is_null");
|
|
1616
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1617
|
+
strConst("nil");
|
|
1618
|
+
this.pushInstruction({ op: "local.set", index: dyn });
|
|
1619
|
+
this.pushInstruction("end");
|
|
1620
|
+
|
|
1621
|
+
// An interface target: `<dyn> is not <target>: missing method <M>`.
|
|
1622
|
+
this.pushInstruction({ op: "local.get", index: 3 });
|
|
1623
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1624
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1625
|
+
this.pushInstruction("ref.is_null");
|
|
1626
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1627
|
+
strConst("interface conversion: interface is nil, not ");
|
|
1628
|
+
this.pushInstruction({ op: "local.get", index: 2 });
|
|
1629
|
+
concat();
|
|
1630
|
+
this.emitRuntimePanicThrow();
|
|
1631
|
+
this.pushInstruction("end");
|
|
1632
|
+
strConst("interface conversion: ");
|
|
1633
|
+
this.pushInstruction({ op: "local.get", index: dyn });
|
|
1634
|
+
concat();
|
|
1635
|
+
strConst(" is not ");
|
|
1636
|
+
concat();
|
|
1637
|
+
this.pushInstruction({ op: "local.get", index: 2 });
|
|
1638
|
+
concat();
|
|
1639
|
+
strConst(": missing method ");
|
|
1640
|
+
concat();
|
|
1641
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1642
|
+
concat();
|
|
1643
|
+
this.emitRuntimePanicThrow();
|
|
1644
|
+
this.pushInstruction("end");
|
|
1645
|
+
|
|
1646
|
+
strConst("interface conversion: ");
|
|
1647
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1648
|
+
concat();
|
|
1649
|
+
strConst(" is ");
|
|
1650
|
+
concat();
|
|
1651
|
+
this.pushInstruction({ op: "local.get", index: dyn });
|
|
1652
|
+
concat();
|
|
1653
|
+
strConst(", not ");
|
|
1654
|
+
concat();
|
|
1655
|
+
this.pushInstruction({ op: "local.get", index: 2 });
|
|
1656
|
+
concat();
|
|
1657
|
+
this.emitRuntimePanicThrow();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
emitRuntimeHelper(fn) {
|
|
1661
|
+
const errInfo = this.mod.getStructType(ERROR_STRUCT);
|
|
1662
|
+
switch (fn._isRuntimeHelper) {
|
|
1663
|
+
case "any_eq": {
|
|
1664
|
+
const bothNull = () => {
|
|
1665
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1666
|
+
this.pushInstruction("ref.is_null");
|
|
1667
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1668
|
+
this.pushInstruction("ref.is_null");
|
|
1669
|
+
};
|
|
1670
|
+
bothNull();
|
|
1671
|
+
this.pushInstruction("i32.and");
|
|
1672
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1673
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
1674
|
+
this.pushInstruction("return");
|
|
1675
|
+
this.pushInstruction("end");
|
|
1676
|
+
bothNull();
|
|
1677
|
+
this.pushInstruction("i32.or");
|
|
1678
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1679
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1680
|
+
this.pushInstruction("return");
|
|
1681
|
+
this.pushInstruction("end");
|
|
1682
|
+
|
|
1683
|
+
// Check unhashable / uncomparable types
|
|
1684
|
+
const checkUncomparable = (typeIndex) => {
|
|
1685
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1686
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1687
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1688
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1689
|
+
this.pushInstruction("i32.or");
|
|
1690
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1691
|
+
this.emitPanic("runtime error: comparing uncomparable type");
|
|
1692
|
+
this.pushInstruction("end");
|
|
1693
|
+
};
|
|
1694
|
+
for (const s of this.mod.sliceTypes.values())
|
|
1695
|
+
checkUncomparable(s.typeIndex);
|
|
1696
|
+
for (const m of this.mod.mapTypes.values())
|
|
1697
|
+
checkUncomparable(m.typeIndex);
|
|
1698
|
+
for (const c of this.mod.closureTypes.values())
|
|
1699
|
+
checkUncomparable(c.typeIndex);
|
|
1700
|
+
for (const b of this.mod.ifaceBoxTypes.values()) {
|
|
1701
|
+
if (isUnhashableGoType(b.goType, this.mod.checker))
|
|
1702
|
+
checkUncomparable(b.typeIndex);
|
|
1703
|
+
}
|
|
1704
|
+
for (const a of this.mod.arrayTypes.values()) {
|
|
1705
|
+
if (isUnhashableGoType(a.elemGoType, this.mod.checker))
|
|
1706
|
+
checkUncomparable(a.typeIndex);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Boxed values compare by value (Go: any(3) == any(3), struct values, etc.).
|
|
1710
|
+
for (const box of this.mod.ifaceBoxTypes.values()) {
|
|
1711
|
+
if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
|
|
1712
|
+
for (const idx of [0, 1]) {
|
|
1713
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
1714
|
+
this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
|
|
1715
|
+
}
|
|
1716
|
+
this.pushInstruction("i32.and");
|
|
1717
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1718
|
+
const f1 = this.allocLocal(null, box.wType);
|
|
1719
|
+
const f2 = this.allocLocal(null, box.wType);
|
|
1720
|
+
for (const [src, dst] of [
|
|
1721
|
+
[0, f1],
|
|
1722
|
+
[1, f2],
|
|
1723
|
+
]) {
|
|
1724
|
+
this.pushInstruction({ op: "local.get", index: src });
|
|
1725
|
+
this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
|
|
1726
|
+
this.pushInstruction({
|
|
1727
|
+
op: "struct.get",
|
|
1728
|
+
typeIndex: box.typeIndex,
|
|
1729
|
+
fieldIndex: 0,
|
|
1730
|
+
});
|
|
1731
|
+
this.pushInstruction({ op: "local.set", index: dst });
|
|
1732
|
+
}
|
|
1733
|
+
this.emitKeyEq(f1, f2, box.goType);
|
|
1734
|
+
this.pushInstruction("return");
|
|
1735
|
+
this.pushInstruction("end");
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// Strings
|
|
1739
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1740
|
+
this.pushInstruction({
|
|
1741
|
+
op: "call",
|
|
1742
|
+
funcIndex: this.mod.getIsStringImportIndex(),
|
|
1743
|
+
});
|
|
1744
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1745
|
+
this.pushInstruction({
|
|
1746
|
+
op: "call",
|
|
1747
|
+
funcIndex: this.mod.getIsStringImportIndex(),
|
|
1748
|
+
});
|
|
1749
|
+
this.pushInstruction("i32.and");
|
|
1750
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1751
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1752
|
+
this.pushInstruction("extern.convert_any");
|
|
1753
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1754
|
+
this.pushInstruction("extern.convert_any");
|
|
1755
|
+
this.pushInstruction({
|
|
1756
|
+
op: "call",
|
|
1757
|
+
funcIndex: this.mod.getStringCmpImportIndex("=="),
|
|
1758
|
+
});
|
|
1759
|
+
this.pushInstruction("return");
|
|
1760
|
+
this.pushInstruction("end");
|
|
1761
|
+
|
|
1762
|
+
// Heap reference equality
|
|
1763
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1764
|
+
this.pushInstruction({ op: "ref.test", heapType: "eq" });
|
|
1765
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1766
|
+
this.pushInstruction({ op: "ref.test", heapType: "eq" });
|
|
1767
|
+
this.pushInstruction("i32.and");
|
|
1768
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1769
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1770
|
+
this.pushInstruction({ op: "ref.cast", heapType: "eq" });
|
|
1771
|
+
this.pushInstruction({ op: "local.get", index: 1 });
|
|
1772
|
+
this.pushInstruction({ op: "ref.cast", heapType: "eq" });
|
|
1773
|
+
this.pushInstruction("ref.eq");
|
|
1774
|
+
this.pushInstruction("return");
|
|
1775
|
+
this.pushInstruction("end");
|
|
1776
|
+
|
|
1777
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1778
|
+
this.pushInstruction("return");
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
case "any_hash": {
|
|
1782
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1783
|
+
this.pushInstruction("ref.is_null");
|
|
1784
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1785
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1786
|
+
this.pushInstruction("return");
|
|
1787
|
+
this.pushInstruction("end");
|
|
1788
|
+
|
|
1789
|
+
// Check unhashable types
|
|
1790
|
+
const checkUnhashable = (typeIndex, goType) => {
|
|
1791
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1792
|
+
this.pushInstruction({ op: "ref.test", typeIndex });
|
|
1793
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1794
|
+
this.emitPanic(
|
|
1795
|
+
goType
|
|
1796
|
+
? `runtime error: hash of unhashable type ${typeStr(goType)}`
|
|
1797
|
+
: "runtime error: hash of unhashable type",
|
|
1798
|
+
);
|
|
1799
|
+
this.pushInstruction("end");
|
|
1800
|
+
};
|
|
1801
|
+
// A slice type first built from source syntax (a `TypeName`) names
|
|
1802
|
+
// its element as written.
|
|
1803
|
+
const spelled = (t) =>
|
|
1804
|
+
t?.kind === "TypeName" ? { kind: "basic", name: t.name } : t;
|
|
1805
|
+
for (const sliceInfo of this.mod.sliceTypes.values())
|
|
1806
|
+
checkUnhashable(sliceInfo.typeIndex, {
|
|
1807
|
+
kind: "slice",
|
|
1808
|
+
elem: spelled(sliceInfo.elemGoType),
|
|
1809
|
+
});
|
|
1810
|
+
for (const mapInfo of this.mod.mapTypes.values())
|
|
1811
|
+
checkUnhashable(mapInfo.typeIndex, {
|
|
1812
|
+
kind: "map",
|
|
1813
|
+
key: mapInfo.keyGoType,
|
|
1814
|
+
value: mapInfo.valGoType,
|
|
1815
|
+
});
|
|
1816
|
+
for (const closureInfo of this.mod.closureTypes.values())
|
|
1817
|
+
checkUnhashable(closureInfo.typeIndex);
|
|
1818
|
+
for (const box of this.mod.ifaceBoxTypes.values()) {
|
|
1819
|
+
if (isUnhashableGoType(box.goType, this.mod.checker))
|
|
1820
|
+
checkUnhashable(box.typeIndex);
|
|
1821
|
+
}
|
|
1822
|
+
for (const arrInfo of this.mod.arrayTypes.values()) {
|
|
1823
|
+
if (isUnhashableGoType(arrInfo.elemGoType, this.mod.checker))
|
|
1824
|
+
checkUnhashable(arrInfo.typeIndex);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Check boxed values (scalars, struct values)
|
|
1828
|
+
for (const box of this.mod.ifaceBoxTypes.values()) {
|
|
1829
|
+
if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
|
|
1830
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1831
|
+
this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
|
|
1832
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1833
|
+
const valLoc = this.allocLocal(null, box.wType);
|
|
1834
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1835
|
+
this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
|
|
1836
|
+
this.pushInstruction({
|
|
1837
|
+
op: "struct.get",
|
|
1838
|
+
typeIndex: box.typeIndex,
|
|
1839
|
+
fieldIndex: 0,
|
|
1840
|
+
});
|
|
1841
|
+
this.pushInstruction({ op: "local.set", index: valLoc });
|
|
1842
|
+
this.pushInstruction({ op: "i32.const", value: box.typeIndex });
|
|
1843
|
+
this.pushInstruction({ op: "i32.const", value: 31 });
|
|
1844
|
+
this.pushInstruction("i32.mul");
|
|
1845
|
+
this.emitKeyHash(valLoc, box.goType);
|
|
1846
|
+
this.pushInstruction("i32.add");
|
|
1847
|
+
this.pushInstruction("return");
|
|
1848
|
+
this.pushInstruction("end");
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// Check string
|
|
1852
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1853
|
+
this.pushInstruction({
|
|
1854
|
+
op: "call",
|
|
1855
|
+
funcIndex: this.mod.getIsStringImportIndex(),
|
|
1856
|
+
});
|
|
1857
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1858
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1859
|
+
this.pushInstruction("extern.convert_any");
|
|
1860
|
+
this.pushInstruction({
|
|
1861
|
+
op: "call",
|
|
1862
|
+
funcIndex: this.mod.getStringHashImportIndex(),
|
|
1863
|
+
});
|
|
1864
|
+
this.pushInstruction("return");
|
|
1865
|
+
this.pushInstruction("end");
|
|
1866
|
+
|
|
1867
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1868
|
+
this.pushInstruction("return");
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
case "ta_fail":
|
|
1872
|
+
this._emitTypeAssertFail();
|
|
1873
|
+
return;
|
|
1874
|
+
case "error_msg":
|
|
1875
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1876
|
+
this.pushInstruction({
|
|
1877
|
+
op: "struct.get",
|
|
1878
|
+
typeIndex: errInfo.typeIndex,
|
|
1879
|
+
fieldIndex: 0,
|
|
1880
|
+
});
|
|
1881
|
+
this.pushInstruction("return");
|
|
1882
|
+
return;
|
|
1883
|
+
case "mk_error":
|
|
1884
|
+
// nil message (strconv success) → nil error
|
|
1885
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1886
|
+
this.pushInstruction("any.convert_extern");
|
|
1887
|
+
this.pushInstruction("ref.is_null");
|
|
1888
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1889
|
+
this.pushInstruction({ op: "ref.null", heapType: "any" });
|
|
1890
|
+
this.pushInstruction("return");
|
|
1891
|
+
this.pushInstruction("end");
|
|
1892
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1893
|
+
this.pushInstruction({
|
|
1894
|
+
op: "struct.new",
|
|
1895
|
+
typeIndex: errInfo.typeIndex,
|
|
1896
|
+
});
|
|
1897
|
+
this.pushInstruction("return");
|
|
1898
|
+
return;
|
|
1899
|
+
case "error_str":
|
|
1900
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1901
|
+
this.pushInstruction("ref.is_null");
|
|
1902
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1903
|
+
this.pushInstruction({
|
|
1904
|
+
op: "i32.const",
|
|
1905
|
+
value: this.mod.internString("<nil>"),
|
|
1906
|
+
});
|
|
1907
|
+
this.pushInstruction({
|
|
1908
|
+
op: "call",
|
|
1909
|
+
funcIndex: this.mod.getStringImportIndex(),
|
|
1910
|
+
});
|
|
1911
|
+
this.pushInstruction("return");
|
|
1912
|
+
this.pushInstruction("end");
|
|
1913
|
+
this.pushInstruction({ op: "local.get", index: 0 });
|
|
1914
|
+
this.pushInstruction({
|
|
1915
|
+
op: "call",
|
|
1916
|
+
funcIndex: this.mod.resolveFuncIndex("__dispatch_error_Error"),
|
|
1917
|
+
});
|
|
1918
|
+
this.pushInstruction("return");
|
|
1919
|
+
return;
|
|
1920
|
+
default:
|
|
1921
|
+
throw new Error(`Unknown runtime helper: ${fn._isRuntimeHelper}`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1247
1924
|
}
|