gofront 1.8.0 → 1.9.1
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 +3 -3
- package/package.json +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +287 -575
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -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/json.go +62 -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/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- 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 +391 -452
- 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 +235 -50
- 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 +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- 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 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- 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 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- 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 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isIteratorRangeFor,
|
|
10
10
|
isRangeFor,
|
|
11
11
|
} from "../../lower/range.js";
|
|
12
|
-
import { someChild } from "../../lower/walk.js";
|
|
12
|
+
import { hasCallOrReceive, someChild } from "../../lower/walk.js";
|
|
13
13
|
import {
|
|
14
14
|
getMapKeyValTypes,
|
|
15
15
|
isArrayType,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isSliceType,
|
|
25
25
|
isStringType,
|
|
26
26
|
isStructType,
|
|
27
|
+
isTaggedSliceType,
|
|
27
28
|
} from "./types.js";
|
|
28
29
|
|
|
29
30
|
function hasReturnInBlock(n) {
|
|
@@ -61,6 +62,15 @@ export class StmtsEmitter {
|
|
|
61
62
|
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
62
63
|
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
63
64
|
|
|
65
|
+
// `var a, b = f()` / `var v, ok = m[k]` bind like `a, b := ...`.
|
|
66
|
+
if (names.length > 1 && values.length === 1) {
|
|
67
|
+
this.emitDefineStmt({
|
|
68
|
+
lhs: names.map((name) => ({ kind: "Ident", name })),
|
|
69
|
+
rhs: values,
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
for (let i = 0; i < names.length; i++) {
|
|
65
75
|
const name = names[i];
|
|
66
76
|
const rawType = spec.type ?? values[i]?._type;
|
|
@@ -301,21 +311,24 @@ export class StmtsEmitter {
|
|
|
301
311
|
if (assigned.size === 0) return null;
|
|
302
312
|
const undo = [];
|
|
303
313
|
const names = [];
|
|
304
|
-
|
|
314
|
+
const visit = (l) => {
|
|
305
315
|
let keys;
|
|
306
316
|
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
307
317
|
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
308
|
-
else
|
|
318
|
+
else return;
|
|
309
319
|
for (const key of keys) {
|
|
310
320
|
const operand = l[key];
|
|
311
321
|
if (!this._mentions(operand, assigned)) continue;
|
|
312
322
|
const goType = operand._type ?? this._resolveExprGoType(operand);
|
|
313
|
-
// A struct or array value names the variable's storage in Go
|
|
323
|
+
// A struct or array value names the variable's storage in Go: hoist
|
|
324
|
+
// the operands inside it (`ps[n].A`) instead of the value.
|
|
314
325
|
if (
|
|
315
326
|
key === "expr" &&
|
|
316
327
|
(this._isValueStruct(goType) || this._isValueArray(goType))
|
|
317
|
-
)
|
|
328
|
+
) {
|
|
329
|
+
visit(operand);
|
|
318
330
|
continue;
|
|
331
|
+
}
|
|
319
332
|
const wType = this.toWasmType(goType);
|
|
320
333
|
const name = `__lhs_tmp_${this.tempCounter++}`;
|
|
321
334
|
const idx = this.allocLocal(name, wType, goType, true);
|
|
@@ -325,7 +338,8 @@ export class StmtsEmitter {
|
|
|
325
338
|
names.push(name);
|
|
326
339
|
l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
|
|
327
340
|
}
|
|
328
|
-
}
|
|
341
|
+
};
|
|
342
|
+
for (const l of lhsNodes) visit(l);
|
|
329
343
|
if (undo.length === 0) return null;
|
|
330
344
|
return () => {
|
|
331
345
|
for (const [node, key, v] of undo) node[key] = v;
|
|
@@ -664,6 +678,10 @@ export class StmtsEmitter {
|
|
|
664
678
|
// A whole struct or array stored into an element whose address is
|
|
665
679
|
// taken writes into the existing element, so `p := &xs[0]` stays valid.
|
|
666
680
|
if (this._needsStableStore(l, elemGoType)) {
|
|
681
|
+
if (sliceInfo) {
|
|
682
|
+
this._emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
667
685
|
this._emitStableStore(elemGoType, r, () => {
|
|
668
686
|
this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
|
|
669
687
|
this.pushInstruction({
|
|
@@ -702,7 +720,95 @@ export class StmtsEmitter {
|
|
|
702
720
|
return idx;
|
|
703
721
|
}
|
|
704
722
|
|
|
723
|
+
// Stores the value `emitValue` pushes into the variable `name` that a range
|
|
724
|
+
// assignment (`for k, v = range m`) targets: a plain local, a boxed local
|
|
725
|
+
// (its address is taken or a closure mutates it), or a global. When the
|
|
726
|
+
// target is an interface, `valueGoType` is the Go type of the ranged
|
|
727
|
+
// element and the value is boxed into the interface representation;
|
|
728
|
+
// `emitValue` is then called with the wasm type of `valueGoType`.
|
|
729
|
+
_emitRangeAssignStore(name, info, emitValue, valueGoType = null) {
|
|
730
|
+
const local = this.resolveLocal(name);
|
|
731
|
+
const boxIface =
|
|
732
|
+
valueGoType && this._rangeStoreWType(name, info) === "anyref";
|
|
733
|
+
const pushValue = () => {
|
|
734
|
+
if (boxIface) this._emitRangeValueToAny(emitValue, valueGoType);
|
|
735
|
+
else emitValue();
|
|
736
|
+
};
|
|
737
|
+
if (!local) {
|
|
738
|
+
pushValue();
|
|
739
|
+
this.pushInstruction({ op: "global.set", index: info.index });
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (local.isBoxed) {
|
|
743
|
+
this.pushInstruction({ op: "local.get", index: local.index });
|
|
744
|
+
pushValue();
|
|
745
|
+
this.pushInstruction({
|
|
746
|
+
op: "struct.set",
|
|
747
|
+
typeIndex: local.boxInfo.typeIndex,
|
|
748
|
+
fieldIndex: 0,
|
|
749
|
+
});
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
pushValue();
|
|
753
|
+
this.pushInstruction({ op: "local.set", index: info.index });
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Pushes a ranged element of Go type `goType` as an interface value.
|
|
757
|
+
// `emitValue(wType)` pushes the element in the wasm type of `goType`; an
|
|
758
|
+
// element that is already an interface needs no boxing.
|
|
759
|
+
_emitRangeValueToAny(emitValue, goType) {
|
|
760
|
+
const wType = this.toWasmType(goType);
|
|
761
|
+
if (wType === "anyref") {
|
|
762
|
+
emitValue(wType);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const name = `__rangeval$${this.tempCounter++}`;
|
|
766
|
+
const idx = this.allocLocal(name, wType, goType, true);
|
|
767
|
+
emitValue(wType);
|
|
768
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
769
|
+
this.emitExpr(
|
|
770
|
+
{ kind: "Ident", name, _type: goType, _parTmp: true },
|
|
771
|
+
"anyref",
|
|
772
|
+
);
|
|
773
|
+
this.locals.delete(name);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// The wasm type of the value a range assignment stores into `info`: the
|
|
777
|
+
// box's field type when the target local is boxed.
|
|
778
|
+
_rangeStoreWType(name, info) {
|
|
779
|
+
const local = this.resolveLocal(name);
|
|
780
|
+
return local?.isBoxed ? local.boxInfo.wType : info.type;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// `for _, out[0] = range xs`: an index, selector or dereference target is
|
|
784
|
+
// not a variable the loop can store into directly. Each such target gets
|
|
785
|
+
// a hidden local that the loop writes (so conversions and interface
|
|
786
|
+
// boxing work as for a variable), and `_emitRangeBoxes` assigns it to the
|
|
787
|
+
// real target at the top of the body. Returns `lhs` with those targets
|
|
788
|
+
// replaced by identifiers of the hidden locals.
|
|
789
|
+
_bindRangeAssignTargets(lhs) {
|
|
790
|
+
return lhs.map((node) => {
|
|
791
|
+
if (!node || node.kind === "Ident" || !node._type) return node;
|
|
792
|
+
const name = `__rangetarget$${this.tempCounter++}`;
|
|
793
|
+
const wType = this.toWasmType(node._type);
|
|
794
|
+
this.allocLocal(name, wType, node._type, true);
|
|
795
|
+
if (!this._pendingRangeTargets) this._pendingRangeTargets = [];
|
|
796
|
+
this._pendingRangeTargets.push({ node, name, goType: node._type });
|
|
797
|
+
return { kind: "Ident", name, _type: node._type };
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
705
801
|
_emitRangeBoxes() {
|
|
802
|
+
const targets = this._pendingRangeTargets ?? [];
|
|
803
|
+
this._pendingRangeTargets = [];
|
|
804
|
+
for (const { node, name, goType } of targets) {
|
|
805
|
+
this._emitAssignSingle(
|
|
806
|
+
node,
|
|
807
|
+
{ kind: "Ident", name, _type: goType, _parTmp: true },
|
|
808
|
+
"=",
|
|
809
|
+
);
|
|
810
|
+
this.locals.delete(name);
|
|
811
|
+
}
|
|
706
812
|
const pending = this._pendingRangeBoxes ?? [];
|
|
707
813
|
this._pendingRangeBoxes = [];
|
|
708
814
|
for (const { name, idx, wType, goType } of pending) {
|
|
@@ -823,6 +929,10 @@ export class StmtsEmitter {
|
|
|
823
929
|
)
|
|
824
930
|
)
|
|
825
931
|
return false;
|
|
932
|
+
// Any callee may hold `&xs[i]` of a slice element, so when the program
|
|
933
|
+
// takes one, a whole-value store into an element writes in place.
|
|
934
|
+
if (this.mod.lowerResult?.elemAddrTaken && this._isSliceElemTarget(l))
|
|
935
|
+
return true;
|
|
826
936
|
const root = rootIdentName(l);
|
|
827
937
|
if (!root) return false;
|
|
828
938
|
// Any code may hold the address of a package-level variable.
|
|
@@ -832,6 +942,45 @@ export class StmtsEmitter {
|
|
|
832
942
|
return Boolean(ctx?.fnMutates(root, true));
|
|
833
943
|
}
|
|
834
944
|
|
|
945
|
+
// `xs[i] = v` into a slice element in place. Capacity beyond the length
|
|
946
|
+
// may hold null elements (a reslice exposes them), so a null element is
|
|
947
|
+
// first replaced by a zero value that the store then fills.
|
|
948
|
+
_emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo) {
|
|
949
|
+
const arrT = {
|
|
950
|
+
kind: "ref",
|
|
951
|
+
nullable: true,
|
|
952
|
+
typeIndex: arrInfo.typeIndex,
|
|
953
|
+
};
|
|
954
|
+
const arr = this.acquireTemp(arrT);
|
|
955
|
+
const idx = this.acquireTemp("i32");
|
|
956
|
+
this._emitElemAddr(l.expr, l.index, sliceInfo, arrInfo);
|
|
957
|
+
this.pushInstruction({ op: "local.set", index: idx });
|
|
958
|
+
this.pushInstruction({ op: "local.set", index: arr });
|
|
959
|
+
const getElem = () => {
|
|
960
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
961
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
962
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
963
|
+
};
|
|
964
|
+
getElem();
|
|
965
|
+
this.pushInstruction("ref.is_null");
|
|
966
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
967
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
968
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
969
|
+
this.emitZeroValue(elemGoType, arrInfo.elemWType);
|
|
970
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
971
|
+
this.pushInstruction("end");
|
|
972
|
+
this._emitStableStore(elemGoType, r, getElem);
|
|
973
|
+
this.releaseTemp(idx, "i32");
|
|
974
|
+
this.releaseTemp(arr, arrT);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// True when `l` is `xs[i]` on a slice (not an array or map).
|
|
978
|
+
_isSliceElemTarget(l) {
|
|
979
|
+
if (l.kind !== "IndexExpr") return false;
|
|
980
|
+
const t = l.expr._type ?? this._resolveExprGoType(l.expr);
|
|
981
|
+
return isSliceType(t, this.mod.checker) && !this._sharedInfo(t);
|
|
982
|
+
}
|
|
983
|
+
|
|
835
984
|
_emitStableStore(goType, r, emitDst) {
|
|
836
985
|
if (this._isValueArray(goType)) {
|
|
837
986
|
this._emitArrayStoreInto(goType, r, emitDst);
|
|
@@ -921,6 +1070,7 @@ export class StmtsEmitter {
|
|
|
921
1070
|
this.pushInstruction({ op: "local.set", index: tmpL });
|
|
922
1071
|
this.emitExpr(r, baseWType);
|
|
923
1072
|
this.pushInstruction({ op: "local.set", index: tmpR });
|
|
1073
|
+
this._emitNilGuard(tmpL, operand._type);
|
|
924
1074
|
for (let i = 0; i < structInfo.fields.length; i++) {
|
|
925
1075
|
this.pushInstruction({ op: "local.get", index: tmpL });
|
|
926
1076
|
this.pushInstruction({ op: "local.get", index: tmpR });
|
|
@@ -939,6 +1089,16 @@ export class StmtsEmitter {
|
|
|
939
1089
|
this.releaseTemp(tmpL, baseWType);
|
|
940
1090
|
}
|
|
941
1091
|
|
|
1092
|
+
// Raises Go's nil-dereference panic when the pointer held in `tmp` is nil
|
|
1093
|
+
// (a plain cast or field write would trap, which `recover` cannot see).
|
|
1094
|
+
_emitNilGuard(tmp, goType) {
|
|
1095
|
+
if (goType?.kind !== "pointer") return;
|
|
1096
|
+
if (this.resolveBranchDepthToRole("nil") == null) return;
|
|
1097
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1098
|
+
this._emitNilBranch();
|
|
1099
|
+
this.pushInstruction("drop");
|
|
1100
|
+
}
|
|
1101
|
+
|
|
942
1102
|
_emitDerefAssign(l, r) {
|
|
943
1103
|
const operand = l.operand ?? l.expr;
|
|
944
1104
|
const structInfo = this._derefStructInfo(operand);
|
|
@@ -975,6 +1135,7 @@ export class StmtsEmitter {
|
|
|
975
1135
|
const valTmp = this.acquireTemp(box.wType);
|
|
976
1136
|
this.emitExpr(operand, ptrWType);
|
|
977
1137
|
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1138
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
978
1139
|
this.emitExpr(r, box.wType);
|
|
979
1140
|
this.pushInstruction({ op: "local.set", index: valTmp });
|
|
980
1141
|
|
|
@@ -996,8 +1157,26 @@ export class StmtsEmitter {
|
|
|
996
1157
|
this.releaseTemp(ptrTmp, ptrWType);
|
|
997
1158
|
}
|
|
998
1159
|
|
|
1160
|
+
// A plain variable target of interface type receiving a concrete result
|
|
1161
|
+
// (`var x any; x, y = two()`) needs the value converted first.
|
|
1162
|
+
_needsTupleConversion(lhsNodes, tupleTypes) {
|
|
1163
|
+
return lhsNodes.some((l, i) => {
|
|
1164
|
+
if (l.kind !== "Ident" || l.name === "_") return false;
|
|
1165
|
+
const dest = this.resolveLocal(l.name) ?? this.mod.resolveGlobal(l.name);
|
|
1166
|
+
const checker = this.mod.checker;
|
|
1167
|
+
return (
|
|
1168
|
+
isInterfaceType(dest?.goType ?? l._type, checker) &&
|
|
1169
|
+
!isInterfaceType(tupleTypes[i], checker)
|
|
1170
|
+
);
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
|
|
999
1174
|
_emitTupleAssign(lhsNodes, rhsNode, op) {
|
|
1000
|
-
if (
|
|
1175
|
+
if (
|
|
1176
|
+
op === "=" &&
|
|
1177
|
+
(lhsNodes.some((l) => !this._isPlainTupleTarget(l)) ||
|
|
1178
|
+
this._needsTupleConversion(lhsNodes, rhsNode._type.types))
|
|
1179
|
+
) {
|
|
1001
1180
|
this._emitTupleViaTemps(
|
|
1002
1181
|
lhsNodes,
|
|
1003
1182
|
rhsNode,
|
|
@@ -1093,16 +1272,79 @@ export class StmtsEmitter {
|
|
|
1093
1272
|
this.releaseTemp(okTmp, "i32");
|
|
1094
1273
|
}
|
|
1095
1274
|
|
|
1096
|
-
//
|
|
1097
|
-
//
|
|
1275
|
+
// Copies the struct or array a map entry holds (a range key or value is a
|
|
1276
|
+
// copy, not the stored value).
|
|
1277
|
+
_emitCopyMapEntry(goType, wType) {
|
|
1278
|
+
const isStruct =
|
|
1279
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
1280
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod);
|
|
1281
|
+
if (isStruct) {
|
|
1282
|
+
const sInfo = this._resolveStructInfo({ _type: goType });
|
|
1283
|
+
if (sInfo) this.emitCloneStruct(sInfo, wType);
|
|
1284
|
+
} else if (
|
|
1285
|
+
goType?.kind !== "pointer" &&
|
|
1286
|
+
isArrayType(goType, this.mod.checker)
|
|
1287
|
+
) {
|
|
1288
|
+
this.emitCopyArray(goType);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Pushes a comma-ok result (map value / asserted value). An array or
|
|
1293
|
+
// struct is a value: the stored one must not alias the map entry or boxed
|
|
1294
|
+
// interface.
|
|
1098
1295
|
_emitGetTempValue(tempIdx, goType) {
|
|
1099
1296
|
this.pushInstruction({ op: "local.get", index: tempIdx });
|
|
1100
1297
|
if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
|
|
1101
1298
|
this.emitCopyArray(goType);
|
|
1299
|
+
else if (
|
|
1300
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
1301
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
1302
|
+
) {
|
|
1303
|
+
// A struct read out of a map or an interface is a copy.
|
|
1304
|
+
const info =
|
|
1305
|
+
this._resolveStructInfo({ _type: goType }) ??
|
|
1306
|
+
this.mod.getStructType(goType?.name);
|
|
1307
|
+
if (info) this.emitCloneStruct(info, this.toWasmType(goType));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
_storeTargetWType(lhsNode) {
|
|
1312
|
+
if (lhsNode.kind !== "Ident") return null;
|
|
1313
|
+
const local = this.resolveLocal(lhsNode.name);
|
|
1314
|
+
if (local)
|
|
1315
|
+
return local.isBoxed
|
|
1316
|
+
? local.boxInfo?.typeEntry?.fields?.[0]?.type
|
|
1317
|
+
: local.type;
|
|
1318
|
+
return this.mod.resolveGlobal(lhsNode.name)?.type ?? null;
|
|
1102
1319
|
}
|
|
1103
1320
|
|
|
1104
1321
|
_emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
|
|
1105
|
-
if (lhsNode.kind
|
|
1322
|
+
if (lhsNode.kind === "Ident" && lhsNode.name === "_") return;
|
|
1323
|
+
const redeclared =
|
|
1324
|
+
isDefine && lhsNode._redecl && this.resolveLocal(lhsNode.name);
|
|
1325
|
+
// `var x any; x, ok = m[k]`: a scalar or struct result is boxed on the
|
|
1326
|
+
// way into the interface-typed variable.
|
|
1327
|
+
const target = isDefine ? null : this._storeTargetWType(lhsNode);
|
|
1328
|
+
const boxesIntoAny = target === "anyref" && wType !== "anyref";
|
|
1329
|
+
if (lhsNode.kind !== "Ident" || redeclared || boxesIntoAny) {
|
|
1330
|
+
// An index, field or dereference target (or a name `:=` redeclares):
|
|
1331
|
+
// assign from a named view of the temporary.
|
|
1332
|
+
const name = `__ok_tmp_${this.tempCounter++}`;
|
|
1333
|
+
this.locals.set(name, {
|
|
1334
|
+
index: tempIdx,
|
|
1335
|
+
type: wType,
|
|
1336
|
+
goType,
|
|
1337
|
+
isParam: false,
|
|
1338
|
+
isBoxed: false,
|
|
1339
|
+
});
|
|
1340
|
+
this._emitAssignSingle(
|
|
1341
|
+
lhsNode,
|
|
1342
|
+
{ kind: "Ident", name, _type: goType, _parTmp: true },
|
|
1343
|
+
"=",
|
|
1344
|
+
);
|
|
1345
|
+
this.locals.delete(name);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1106
1348
|
if (isDefine) {
|
|
1107
1349
|
const idx = this.allocLocal(lhsNode.name, wType, goType);
|
|
1108
1350
|
const local = this.locals.get(lhsNode.name);
|
|
@@ -1192,7 +1434,36 @@ export class StmtsEmitter {
|
|
|
1192
1434
|
);
|
|
1193
1435
|
}
|
|
1194
1436
|
|
|
1437
|
+
// A bare type name in a case list or assertion (`case L:`, `case error:`)
|
|
1438
|
+
// is the declared type, not its underlying one: a defined string type is
|
|
1439
|
+
// boxed apart from `string`, and `error` carries a method set.
|
|
1440
|
+
_resolveTestTarget(t) {
|
|
1441
|
+
// A composite type-switch case (`case func(int) string:`) carries the
|
|
1442
|
+
// checker's type, which keys typed-nil markers like the boxed value.
|
|
1443
|
+
if (t?._assertType) return t._assertType;
|
|
1444
|
+
if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
|
|
1445
|
+
if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
|
|
1446
|
+
return t;
|
|
1447
|
+
return this.mod.checker?.types?.get(t.name) ?? t;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// A defined func, map or array type lives in `any` as a box (so
|
|
1451
|
+
// `iter.Seq[int]` is not a plain `func(func(int) bool)`, `type M
|
|
1452
|
+
// map[string]int` keeps its name in `%T` and unhashable-key panics, and
|
|
1453
|
+
// `type A [2]int` is not a `[2]int` in a type switch).
|
|
1454
|
+
_isMethodNamedRef(goType) {
|
|
1455
|
+
const t = this._resolveTestTarget(goType);
|
|
1456
|
+
if (
|
|
1457
|
+
t &&
|
|
1458
|
+
(isArrayType(t, this.mod.checker) || isMapType(t, this.mod.checker))
|
|
1459
|
+
)
|
|
1460
|
+
return true;
|
|
1461
|
+
if (t?.kind !== "named") return false;
|
|
1462
|
+
return isFuncType(t, this.mod.checker);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1195
1465
|
_emitTypeTest(targetGoType) {
|
|
1466
|
+
targetGoType = this._resolveTestTarget(targetGoType);
|
|
1196
1467
|
if (!targetGoType) {
|
|
1197
1468
|
this.pushInstruction("drop");
|
|
1198
1469
|
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
@@ -1211,31 +1482,7 @@ export class StmtsEmitter {
|
|
|
1211
1482
|
}
|
|
1212
1483
|
|
|
1213
1484
|
if (isNonEmptyInterface(targetGoType, this.mod.checker)) {
|
|
1214
|
-
|
|
1215
|
-
if (candidates.length === 0) {
|
|
1216
|
-
this.pushInstruction("drop");
|
|
1217
|
-
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
const anyTmp = this.acquireTemp("anyref");
|
|
1221
|
-
this.pushInstruction({ op: "local.set", index: anyTmp });
|
|
1222
|
-
|
|
1223
|
-
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1224
|
-
this.pushInstruction("ref.is_null");
|
|
1225
|
-
this.pushInstruction("i32.eqz");
|
|
1226
|
-
|
|
1227
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
1228
|
-
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1229
|
-
this.pushInstruction({
|
|
1230
|
-
op: "ref.test",
|
|
1231
|
-
typeIndex: candidates[i].typeIndex,
|
|
1232
|
-
});
|
|
1233
|
-
if (i > 0) {
|
|
1234
|
-
this.pushInstruction("i32.or");
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
this.pushInstruction("i32.and");
|
|
1238
|
-
this.releaseTemp(anyTmp, "anyref");
|
|
1485
|
+
this._emitInterfaceTypeTest(targetGoType);
|
|
1239
1486
|
return;
|
|
1240
1487
|
}
|
|
1241
1488
|
|
|
@@ -1247,6 +1494,12 @@ export class StmtsEmitter {
|
|
|
1247
1494
|
return;
|
|
1248
1495
|
}
|
|
1249
1496
|
|
|
1497
|
+
if (this._isMethodNamedRef(targetGoType)) {
|
|
1498
|
+
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
1499
|
+
this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1250
1503
|
if (isFuncType(targetGoType, this.mod.checker)) {
|
|
1251
1504
|
const closureInfo = this.mod.getClosureType(targetGoType);
|
|
1252
1505
|
this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
|
|
@@ -1265,33 +1518,27 @@ export class StmtsEmitter {
|
|
|
1265
1518
|
|
|
1266
1519
|
if (isSliceType(targetGoType, this.mod.checker)) {
|
|
1267
1520
|
const elem = this._getSliceElemType(targetGoType);
|
|
1521
|
+
if (isTaggedSliceType(targetGoType, this.mod.checker)) {
|
|
1522
|
+
const tagged = this.mod.getTaggedSliceType(targetGoType, elem);
|
|
1523
|
+
this.pushInstruction({ op: "ref.test", typeIndex: tagged.typeIndex });
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1268
1526
|
const sInfo = this.mod.getSliceType(elem);
|
|
1269
1527
|
if (sInfo) {
|
|
1528
|
+
// The unnamed slice type is the bare one, not a tagged subtype.
|
|
1529
|
+
const root = this.mod.getTaggedSliceRoot(sInfo);
|
|
1530
|
+
const tmp = this.acquireTemp("anyref");
|
|
1531
|
+
this.pushInstruction({ op: "local.tee", index: tmp });
|
|
1270
1532
|
this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
|
|
1533
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1534
|
+
this.pushInstruction({ op: "ref.test", typeIndex: root.typeIndex });
|
|
1535
|
+
this.pushInstruction("i32.eqz");
|
|
1536
|
+
this.pushInstruction("i32.and");
|
|
1537
|
+
this.releaseTemp(tmp, "anyref");
|
|
1271
1538
|
return;
|
|
1272
1539
|
}
|
|
1273
1540
|
}
|
|
1274
1541
|
|
|
1275
|
-
if (isArrayType(targetGoType, this.mod.checker)) {
|
|
1276
|
-
const elem = this._getArrayElemType(targetGoType);
|
|
1277
|
-
const aInfo = this.mod.getArrayType(elem);
|
|
1278
|
-
if (aInfo) {
|
|
1279
|
-
this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// A map lives in `any` as its bare map struct.
|
|
1285
|
-
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1286
|
-
const { keyType, valType } = getMapKeyValTypes(
|
|
1287
|
-
targetGoType,
|
|
1288
|
-
this.mod.checker,
|
|
1289
|
-
);
|
|
1290
|
-
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1291
|
-
this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
1542
|
// Scalar types
|
|
1296
1543
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
1297
1544
|
if (box) {
|
|
@@ -1303,6 +1550,45 @@ export class StmtsEmitter {
|
|
|
1303
1550
|
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
1304
1551
|
}
|
|
1305
1552
|
|
|
1553
|
+
// Whether the interface value on the stack holds a type implementing the
|
|
1554
|
+
// non-empty interface: a `ref.test` over every implementing type (a typed
|
|
1555
|
+
// nil `*S` marker included).
|
|
1556
|
+
_emitInterfaceTypeTest(targetGoType) {
|
|
1557
|
+
const candidates = this.mod.getTypesImplementingInterface(targetGoType);
|
|
1558
|
+
if (candidates.length === 0) {
|
|
1559
|
+
this.pushInstruction("drop");
|
|
1560
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const anyTmp = this.acquireTemp("anyref");
|
|
1564
|
+
this.pushInstruction({ op: "local.set", index: anyTmp });
|
|
1565
|
+
|
|
1566
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1567
|
+
this.pushInstruction("ref.is_null");
|
|
1568
|
+
this.pushInstruction("i32.eqz");
|
|
1569
|
+
|
|
1570
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1571
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1572
|
+
this.pushInstruction({
|
|
1573
|
+
op: "ref.test",
|
|
1574
|
+
typeIndex: candidates[i].typeIndex,
|
|
1575
|
+
});
|
|
1576
|
+
if (candidates[i].nilTypeIndex !== undefined) {
|
|
1577
|
+
this.pushInstruction({ op: "local.get", index: anyTmp });
|
|
1578
|
+
this.pushInstruction({
|
|
1579
|
+
op: "ref.test",
|
|
1580
|
+
typeIndex: candidates[i].nilTypeIndex,
|
|
1581
|
+
});
|
|
1582
|
+
this.pushInstruction("i32.or");
|
|
1583
|
+
}
|
|
1584
|
+
if (i > 0) {
|
|
1585
|
+
this.pushInstruction("i32.or");
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
this.pushInstruction("i32.and");
|
|
1589
|
+
this.releaseTemp(anyTmp, "anyref");
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1306
1592
|
// `ref.test` for a map, func or `*T`, which also accepts the typed-nil
|
|
1307
1593
|
// marker of the same Go type (see getNilBoxType).
|
|
1308
1594
|
_emitRefTestOrNil(goType, typeIndex) {
|
|
@@ -1337,10 +1623,19 @@ export class StmtsEmitter {
|
|
|
1337
1623
|
this.releaseTemp(tmp, "anyref");
|
|
1338
1624
|
}
|
|
1339
1625
|
|
|
1626
|
+
// An interface target keeps the value as is; a boxed defined func or map
|
|
1627
|
+
// type is unboxed. True when the cast is done.
|
|
1628
|
+
_emitCastNoRepack(targetGoType) {
|
|
1629
|
+
if (isInterfaceType(targetGoType, this.mod.checker)) return true;
|
|
1630
|
+
if (!this._isMethodNamedRef(targetGoType)) return false;
|
|
1631
|
+
this._emitUnbox(
|
|
1632
|
+
this.mod.getIfaceBoxType(this._resolveTestTarget(targetGoType)),
|
|
1633
|
+
);
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1340
1637
|
_emitTypeCast(targetGoType, targetWType) {
|
|
1341
|
-
if (
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1638
|
+
if (this._emitCastNoRepack(targetGoType)) return;
|
|
1344
1639
|
|
|
1345
1640
|
if (isBareStringType(targetGoType, this.mod.checker)) {
|
|
1346
1641
|
this.pushInstruction("extern.convert_any");
|
|
@@ -1382,28 +1677,6 @@ export class StmtsEmitter {
|
|
|
1382
1677
|
}
|
|
1383
1678
|
}
|
|
1384
1679
|
|
|
1385
|
-
if (isArrayType(targetGoType, this.mod.checker)) {
|
|
1386
|
-
const elem = this._getArrayElemType(targetGoType);
|
|
1387
|
-
const aInfo = this.mod.getArrayType(elem);
|
|
1388
|
-
if (aInfo) {
|
|
1389
|
-
this.pushInstruction({
|
|
1390
|
-
op: "ref.cast_null",
|
|
1391
|
-
typeIndex: aInfo.typeIndex,
|
|
1392
|
-
});
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
if (isMapType(targetGoType, this.mod.checker)) {
|
|
1398
|
-
const { keyType, valType } = getMapKeyValTypes(
|
|
1399
|
-
targetGoType,
|
|
1400
|
-
this.mod.checker,
|
|
1401
|
-
);
|
|
1402
|
-
const mInfo = this.mod.getMapType(keyType, valType);
|
|
1403
|
-
this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
1680
|
// Scalar types
|
|
1408
1681
|
const box = this.mod.getIfaceBoxType(targetGoType);
|
|
1409
1682
|
if (box) {
|
|
@@ -1632,6 +1905,7 @@ export class StmtsEmitter {
|
|
|
1632
1905
|
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1633
1906
|
this.emitExpr(operand, ptrWType);
|
|
1634
1907
|
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
1908
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
1635
1909
|
|
|
1636
1910
|
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1637
1911
|
this.pushInstruction({
|
|
@@ -1770,6 +2044,7 @@ export class StmtsEmitter {
|
|
|
1770
2044
|
const ptrTmp = this.acquireTemp(ptrWType);
|
|
1771
2045
|
this.emitExpr(operand, ptrWType);
|
|
1772
2046
|
this.pushInstruction({ op: "local.set", index: ptrTmp });
|
|
2047
|
+
this._emitNilGuard(ptrTmp, operandGoType);
|
|
1773
2048
|
|
|
1774
2049
|
this.pushInstruction({ op: "local.get", index: ptrTmp });
|
|
1775
2050
|
this.pushInstruction({
|
|
@@ -2020,7 +2295,9 @@ export class StmtsEmitter {
|
|
|
2020
2295
|
const rangeExpr =
|
|
2021
2296
|
stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
|
|
2022
2297
|
const isAssign = stmt.init?.kind === "AssignStmt";
|
|
2023
|
-
const lhs =
|
|
2298
|
+
const lhs = isAssign
|
|
2299
|
+
? this._bindRangeAssignTargets(stmt.init.lhs)
|
|
2300
|
+
: (stmt.init?.lhs ?? []);
|
|
2024
2301
|
const iterExpr = rangeExpr.expr;
|
|
2025
2302
|
const iterType = iterExpr._type;
|
|
2026
2303
|
|
|
@@ -2087,21 +2364,21 @@ export class StmtsEmitter {
|
|
|
2087
2364
|
|
|
2088
2365
|
// Assign loop var if present
|
|
2089
2366
|
if (idxLocalInfo) {
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2367
|
+
const name = lhs[0].name;
|
|
2368
|
+
const wType = this._rangeStoreWType(name, idxLocalInfo);
|
|
2369
|
+
const idxGoType =
|
|
2370
|
+
iterType?.kind === "untyped"
|
|
2371
|
+
? { kind: "basic", name: "int" }
|
|
2372
|
+
: iterType;
|
|
2373
|
+
this._emitRangeAssignStore(
|
|
2374
|
+
name,
|
|
2375
|
+
idxLocalInfo,
|
|
2376
|
+
(want = wType) => {
|
|
2377
|
+
this.pushInstruction({ op: "local.get", index: idxTmp });
|
|
2378
|
+
if (want === "i64") this.pushInstruction("i64.extend_i32_s");
|
|
2379
|
+
},
|
|
2380
|
+
idxGoType,
|
|
2381
|
+
);
|
|
2105
2382
|
}
|
|
2106
2383
|
|
|
2107
2384
|
// Emit body
|
|
@@ -2212,38 +2489,30 @@ export class StmtsEmitter {
|
|
|
2212
2489
|
|
|
2213
2490
|
// Assign index variable
|
|
2214
2491
|
if (idxLocalInfo) {
|
|
2215
|
-
this.
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
op: "local.
|
|
2220
|
-
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
op: "global.set",
|
|
2225
|
-
index: idxLocalInfo.index,
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2492
|
+
this._emitRangeAssignStore(
|
|
2493
|
+
lhs[0].name,
|
|
2494
|
+
idxLocalInfo,
|
|
2495
|
+
() => {
|
|
2496
|
+
this.pushInstruction({ op: "local.get", index: idxTmp });
|
|
2497
|
+
this.pushInstruction("i64.extend_i32_s");
|
|
2498
|
+
},
|
|
2499
|
+
{ kind: "basic", name: "int" },
|
|
2500
|
+
);
|
|
2228
2501
|
}
|
|
2229
2502
|
|
|
2230
2503
|
// Assign rune variable
|
|
2231
2504
|
if (valLocalInfo) {
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
op: "local.
|
|
2239
|
-
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
op: "global.set",
|
|
2244
|
-
index: valLocalInfo.index,
|
|
2245
|
-
});
|
|
2246
|
-
}
|
|
2505
|
+
const name = lhs[1].name;
|
|
2506
|
+
const wType = this._rangeStoreWType(name, valLocalInfo);
|
|
2507
|
+
this._emitRangeAssignStore(
|
|
2508
|
+
name,
|
|
2509
|
+
valLocalInfo,
|
|
2510
|
+
(want = wType) => {
|
|
2511
|
+
this.pushInstruction({ op: "local.get", index: runeTmp });
|
|
2512
|
+
if (want === "i64") this.pushInstruction("i64.extend_i32_u");
|
|
2513
|
+
},
|
|
2514
|
+
{ kind: "basic", name: "int32" },
|
|
2515
|
+
);
|
|
2247
2516
|
}
|
|
2248
2517
|
|
|
2249
2518
|
// Body
|
|
@@ -2411,58 +2680,38 @@ export class StmtsEmitter {
|
|
|
2411
2680
|
|
|
2412
2681
|
// Assign key variable
|
|
2413
2682
|
if (keyLocalInfo) {
|
|
2414
|
-
this.
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
index: keyLocalInfo.index,
|
|
2429
|
-
});
|
|
2430
|
-
}
|
|
2683
|
+
this._emitRangeAssignStore(
|
|
2684
|
+
lhs[0].name,
|
|
2685
|
+
keyLocalInfo,
|
|
2686
|
+
() => {
|
|
2687
|
+
this.pushInstruction({ op: "local.get", index: entryTmp });
|
|
2688
|
+
this.pushInstruction({
|
|
2689
|
+
op: "struct.get",
|
|
2690
|
+
typeIndex: mapInfo.entryTypeIndex,
|
|
2691
|
+
fieldIndex: 0,
|
|
2692
|
+
});
|
|
2693
|
+
this._emitCopyMapEntry(keyType, mapInfo.keyWType);
|
|
2694
|
+
},
|
|
2695
|
+
keyType,
|
|
2696
|
+
);
|
|
2431
2697
|
}
|
|
2432
2698
|
|
|
2433
2699
|
// Assign val variable
|
|
2434
2700
|
if (valLocalInfo) {
|
|
2435
|
-
this.
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
} else if (
|
|
2450
|
-
valType?.kind !== "pointer" &&
|
|
2451
|
-
isArrayType(valType, this.mod.checker)
|
|
2452
|
-
) {
|
|
2453
|
-
this.emitCopyArray(valType);
|
|
2454
|
-
}
|
|
2455
|
-
if (this.resolveLocal(lhs[1].name)) {
|
|
2456
|
-
this.pushInstruction({
|
|
2457
|
-
op: "local.set",
|
|
2458
|
-
index: valLocalInfo.index,
|
|
2459
|
-
});
|
|
2460
|
-
} else {
|
|
2461
|
-
this.pushInstruction({
|
|
2462
|
-
op: "global.set",
|
|
2463
|
-
index: valLocalInfo.index,
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2701
|
+
this._emitRangeAssignStore(
|
|
2702
|
+
lhs[1].name,
|
|
2703
|
+
valLocalInfo,
|
|
2704
|
+
() => {
|
|
2705
|
+
this.pushInstruction({ op: "local.get", index: entryTmp });
|
|
2706
|
+
this.pushInstruction({
|
|
2707
|
+
op: "struct.get",
|
|
2708
|
+
typeIndex: mapInfo.entryTypeIndex,
|
|
2709
|
+
fieldIndex: 1,
|
|
2710
|
+
});
|
|
2711
|
+
this._emitCopyMapEntry(valType, mapInfo.valWType);
|
|
2712
|
+
},
|
|
2713
|
+
valType,
|
|
2714
|
+
);
|
|
2466
2715
|
}
|
|
2467
2716
|
|
|
2468
2717
|
// Body
|
|
@@ -2551,6 +2800,15 @@ export class StmtsEmitter {
|
|
|
2551
2800
|
});
|
|
2552
2801
|
}
|
|
2553
2802
|
this.releaseTemp(sliceTmp, sliceWType);
|
|
2803
|
+
} else if (!hasVal && !hasCallOrReceive(iterExpr)) {
|
|
2804
|
+
// Ranging over an array for its indexes only does not evaluate it
|
|
2805
|
+
// (`range p` on a nil array pointer is valid): the length is the
|
|
2806
|
+
// type's.
|
|
2807
|
+
this.pushInstruction({
|
|
2808
|
+
op: "i32.const",
|
|
2809
|
+
value: this._getArraySize(isPtrToArr ? iterType.base : iterType),
|
|
2810
|
+
});
|
|
2811
|
+
this.pushInstruction({ op: "local.set", index: lenTmp });
|
|
2554
2812
|
} else {
|
|
2555
2813
|
this.emitExpr(iterExpr, arrWType);
|
|
2556
2814
|
// Go ranges over a copy of an array (the range expression is
|
|
@@ -2627,52 +2885,50 @@ export class StmtsEmitter {
|
|
|
2627
2885
|
|
|
2628
2886
|
// Assign idx
|
|
2629
2887
|
if (idxLocalInfo) {
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
}
|
|
2888
|
+
const name = lhs[0].name;
|
|
2889
|
+
const wType = this._rangeStoreWType(name, idxLocalInfo);
|
|
2890
|
+
this._emitRangeAssignStore(
|
|
2891
|
+
name,
|
|
2892
|
+
idxLocalInfo,
|
|
2893
|
+
(want = wType) => {
|
|
2894
|
+
this.pushInstruction({ op: "local.get", index: idxTmp });
|
|
2895
|
+
if (want === "i64") this.pushInstruction("i64.extend_i32_s");
|
|
2896
|
+
},
|
|
2897
|
+
{ kind: "basic", name: "int" },
|
|
2898
|
+
);
|
|
2642
2899
|
}
|
|
2643
2900
|
|
|
2644
2901
|
// Assign val
|
|
2645
2902
|
if (valLocalInfo) {
|
|
2646
|
-
this.
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
}
|
|
2903
|
+
this._emitRangeAssignStore(
|
|
2904
|
+
lhs[1].name,
|
|
2905
|
+
valLocalInfo,
|
|
2906
|
+
() => {
|
|
2907
|
+
this.pushInstruction({ op: "local.get", index: arrTmp });
|
|
2908
|
+
this.pushInstruction({ op: "local.get", index: offTmp });
|
|
2909
|
+
this.pushInstruction({ op: "local.get", index: idxTmp });
|
|
2910
|
+
this.pushInstruction("i32.add");
|
|
2911
|
+
this.pushInstruction({
|
|
2912
|
+
op: "array.get",
|
|
2913
|
+
typeIndex: arrInfo.typeIndex,
|
|
2914
|
+
});
|
|
2915
|
+
if (
|
|
2916
|
+
isStructType(elemGoType, this.mod.checker, this.mod) &&
|
|
2917
|
+
!isPointerToStruct(elemGoType, this.mod.checker, this.mod)
|
|
2918
|
+
) {
|
|
2919
|
+
const sInfo = this._resolveStructInfo({ _type: elemGoType });
|
|
2920
|
+
if (sInfo) {
|
|
2921
|
+
this.emitCloneStruct(sInfo, arrInfo.elemWType);
|
|
2922
|
+
}
|
|
2923
|
+
} else if (
|
|
2924
|
+
elemGoType?.kind !== "pointer" &&
|
|
2925
|
+
isArrayType(elemGoType, this.mod.checker)
|
|
2926
|
+
) {
|
|
2927
|
+
this.emitCopyArray(elemGoType);
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
elemGoType,
|
|
2931
|
+
);
|
|
2676
2932
|
}
|
|
2677
2933
|
|
|
2678
2934
|
// Body
|
|
@@ -3134,16 +3390,7 @@ export class StmtsEmitter {
|
|
|
3134
3390
|
defaultIdx = i;
|
|
3135
3391
|
continue;
|
|
3136
3392
|
}
|
|
3137
|
-
|
|
3138
|
-
if (stmt.tag) {
|
|
3139
|
-
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
3140
|
-
this.emitExpr(vals[j], tagWType);
|
|
3141
|
-
this.emitBinaryOp("==", tagWType, tagGoType);
|
|
3142
|
-
} else {
|
|
3143
|
-
this.emitExpr(vals[j], "i32");
|
|
3144
|
-
}
|
|
3145
|
-
if (j > 0) this.pushInstruction("i32.or");
|
|
3146
|
-
}
|
|
3393
|
+
this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
|
|
3147
3394
|
// The innermost block ends the first clause, so clause i is depth i.
|
|
3148
3395
|
this.pushInstruction({ op: "br_if", depth: i });
|
|
3149
3396
|
}
|
|
@@ -3162,6 +3409,26 @@ export class StmtsEmitter {
|
|
|
3162
3409
|
this.pushInstruction("end");
|
|
3163
3410
|
}
|
|
3164
3411
|
|
|
3412
|
+
// Leaves an i32 on the stack: whether any expression of a case list matches.
|
|
3413
|
+
// Later expressions are not evaluated once one matches, as in Go.
|
|
3414
|
+
_emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType) {
|
|
3415
|
+
for (let j = 0; j < vals.length; j++) {
|
|
3416
|
+
if (stmt.tag) {
|
|
3417
|
+
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
3418
|
+
this.emitExpr(vals[j], tagWType);
|
|
3419
|
+
this._emitElemEq(tagGoType, tagWType);
|
|
3420
|
+
} else {
|
|
3421
|
+
this.emitExpr(vals[j], "i32");
|
|
3422
|
+
}
|
|
3423
|
+
if (j < vals.length - 1) {
|
|
3424
|
+
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
3425
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3426
|
+
this.pushInstruction("else");
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
for (let j = 0; j < vals.length - 1; j++) this.pushInstruction("end");
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3165
3432
|
emitSwitchStmt(stmt) {
|
|
3166
3433
|
if (
|
|
3167
3434
|
(stmt.cases ?? []).some((c) => {
|
|
@@ -3202,16 +3469,7 @@ export class StmtsEmitter {
|
|
|
3202
3469
|
let ifCount = 0;
|
|
3203
3470
|
for (const c of nonDefaultCases) {
|
|
3204
3471
|
const vals = getCaseVals(c);
|
|
3205
|
-
|
|
3206
|
-
if (stmt.tag) {
|
|
3207
|
-
this.pushInstruction({ op: "local.get", index: tagTmp });
|
|
3208
|
-
this.emitExpr(vals[i], tagWType);
|
|
3209
|
-
this.emitBinaryOp("==", tagWType, tagGoType);
|
|
3210
|
-
} else {
|
|
3211
|
-
this.emitExpr(vals[i], "i32");
|
|
3212
|
-
}
|
|
3213
|
-
if (i > 0) this.pushInstruction("i32.or");
|
|
3214
|
-
}
|
|
3472
|
+
this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
|
|
3215
3473
|
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3216
3474
|
ifCount++;
|
|
3217
3475
|
|
|
@@ -3233,6 +3491,17 @@ export class StmtsEmitter {
|
|
|
3233
3491
|
this.pushInstruction("end");
|
|
3234
3492
|
}
|
|
3235
3493
|
|
|
3494
|
+
// Wraps the value on the stack in a box when the just-declared local `name`
|
|
3495
|
+
// is captured and mutated.
|
|
3496
|
+
_emitBoxIfCaptured(name) {
|
|
3497
|
+
const info = this.locals.get(name);
|
|
3498
|
+
if (info?.isBoxed)
|
|
3499
|
+
this.pushInstruction({
|
|
3500
|
+
op: "struct.new",
|
|
3501
|
+
typeIndex: info.boxInfo.typeIndex,
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3236
3505
|
emitTypeSwitchStmt(stmt) {
|
|
3237
3506
|
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3238
3507
|
this.pushControl("break", stmt.label);
|
|
@@ -3273,7 +3542,7 @@ export class StmtsEmitter {
|
|
|
3273
3542
|
const targetGoType = isNilCase
|
|
3274
3543
|
? (stmt.expr._type ?? { kind: "basic", name: "any" })
|
|
3275
3544
|
: types.length === 1
|
|
3276
|
-
? (types[0]._type ?? types[0])
|
|
3545
|
+
? (types[0]._type ?? this._resolveTestTarget(types[0]))
|
|
3277
3546
|
: { kind: "basic", name: "any" };
|
|
3278
3547
|
const targetWType = this.toWasmType(targetGoType);
|
|
3279
3548
|
const assignLocal = this.allocLocal(
|
|
@@ -3293,6 +3562,7 @@ export class StmtsEmitter {
|
|
|
3293
3562
|
} else {
|
|
3294
3563
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
3295
3564
|
}
|
|
3565
|
+
this._emitBoxIfCaptured(stmt.assign);
|
|
3296
3566
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
3297
3567
|
}
|
|
3298
3568
|
|
|
@@ -3308,6 +3578,7 @@ export class StmtsEmitter {
|
|
|
3308
3578
|
name: "any",
|
|
3309
3579
|
});
|
|
3310
3580
|
this.pushInstruction({ op: "local.get", index: tswTmp });
|
|
3581
|
+
this._emitBoxIfCaptured(stmt.assign);
|
|
3311
3582
|
this.pushInstruction({ op: "local.set", index: assignLocal });
|
|
3312
3583
|
}
|
|
3313
3584
|
this._emitClauseBody(defaultCase.stmts);
|
|
@@ -3379,13 +3650,24 @@ export class StmtsEmitter {
|
|
|
3379
3650
|
this.pushInstruction("i64.eq");
|
|
3380
3651
|
this.pushInstruction({ op: "if", blockType: "void" });
|
|
3381
3652
|
if (target.role !== "break" || stmt.label !== target.label) {
|
|
3382
|
-
|
|
3653
|
+
const isContinue = target.role === "continue";
|
|
3654
|
+
const inScope = this.controlStack.some(
|
|
3655
|
+
(c) => c.label === target.label && c.role === target.role,
|
|
3656
|
+
);
|
|
3657
|
+
if (
|
|
3658
|
+
!inScope &&
|
|
3659
|
+
isContinue &&
|
|
3660
|
+
this.funcDecl._isYieldClosure &&
|
|
3661
|
+
target.label === this.funcDecl._forStmt?.label
|
|
3662
|
+
) {
|
|
3663
|
+
// `continue L` where L labels the iterator loop whose body this
|
|
3664
|
+
// yield closure is: resume that iterator.
|
|
3665
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3666
|
+
this.pushInstruction({ op: "return" });
|
|
3667
|
+
} else if (!inScope && this.funcDecl._isYieldClosure) {
|
|
3383
3668
|
this._propagateYieldExit(BigInt(target.tag));
|
|
3384
3669
|
} else {
|
|
3385
|
-
const depth = this.resolveBranchDepth(
|
|
3386
|
-
target.label,
|
|
3387
|
-
target.role === "continue",
|
|
3388
|
-
);
|
|
3670
|
+
const depth = this.resolveBranchDepth(target.label, isContinue);
|
|
3389
3671
|
this.pushInstruction({ op: "br", depth });
|
|
3390
3672
|
}
|
|
3391
3673
|
}
|
|
@@ -3510,6 +3792,8 @@ export class StmtsEmitter {
|
|
|
3510
3792
|
}
|
|
3511
3793
|
|
|
3512
3794
|
emitBranchStmt(stmt) {
|
|
3795
|
+
const alias = stmt.label && this._stackedLabelAlias(stmt.label);
|
|
3796
|
+
if (alias) stmt = { ...stmt, label: alias };
|
|
3513
3797
|
if (this._tryEmitYieldBranch(stmt)) return;
|
|
3514
3798
|
const isContinue = stmt.keyword === "continue";
|
|
3515
3799
|
const depth = this.resolveBranchDepth(stmt.label, isContinue);
|