gofront 1.5.1 → 1.6.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 +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
|
@@ -10,15 +10,94 @@ import {
|
|
|
10
10
|
collectBoundaryMeta,
|
|
11
11
|
generateFacade,
|
|
12
12
|
scanBoundaryImports,
|
|
13
|
+
wTypeKey,
|
|
13
14
|
} from "./boundary.js";
|
|
14
15
|
import { FunctionEmitter } from "./emit.js";
|
|
16
|
+
import { BINARY_MATH_IMPORTS } from "./emit-stdlib.js";
|
|
15
17
|
import { encodeModule, isGoFrontWasm } from "./encode.js";
|
|
16
|
-
import {
|
|
18
|
+
import { hasGenerics, monomorphise } from "./monomorph.js";
|
|
19
|
+
import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
|
|
20
|
+
import {
|
|
21
|
+
ERROR_STRUCT,
|
|
22
|
+
getFuncSignature,
|
|
23
|
+
getMapKeyValTypes,
|
|
24
|
+
getReceiverTypeName,
|
|
25
|
+
isMapType,
|
|
26
|
+
isNonEmptyInterface,
|
|
27
|
+
isTestingT,
|
|
28
|
+
toWasmType,
|
|
29
|
+
} from "./types.js";
|
|
17
30
|
import { emitWat } from "./wat.js";
|
|
18
31
|
|
|
19
|
-
export { isGoFrontWasm };
|
|
32
|
+
export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
|
|
33
|
+
|
|
34
|
+
const ERROR_PRODUCING_PKGS = new Set(["errors", "fmt", "strconv"]);
|
|
35
|
+
// Packages whose calls lower to the `env.*` imports in STDLIB_IMPORTS (the
|
|
36
|
+
// matching JS lives in WASM_STDLIB_JS). Only modules importing one of them
|
|
37
|
+
// declare the imports and ship that JS block.
|
|
38
|
+
const STDLIB_ENV_PKGS = new Set([
|
|
39
|
+
"strings",
|
|
40
|
+
"strconv",
|
|
41
|
+
"unicode/utf8",
|
|
42
|
+
"fmt",
|
|
43
|
+
"testing",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function importsAnyOf(programs, pkgs) {
|
|
47
|
+
return (programs ?? []).some((p) =>
|
|
48
|
+
(p.imports ?? []).some((decl) =>
|
|
49
|
+
(decl.imports ?? [decl]).some((imp) => pkgs.has(imp.path)),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
// [name, params, results] for `env.*` imports backing stdlib calls; the JS
|
|
55
|
+
// side lives in WASM_STDLIB_JS (boundary.js). All are registered eagerly
|
|
56
|
+
// (when any STDLIB_ENV_PKGS package is imported) because the import table is
|
|
57
|
+
// locked before function bodies are emitted.
|
|
58
|
+
const S = "externref";
|
|
59
|
+
const STDLIB_IMPORTS = [
|
|
60
|
+
// strings
|
|
61
|
+
["str_trim", [S, S], [S]],
|
|
62
|
+
["str_trim_left", [S, S], [S]],
|
|
63
|
+
["str_trim_right", [S, S], [S]],
|
|
64
|
+
["str_trim_prefix", [S, S], [S]],
|
|
65
|
+
["str_trim_suffix", [S, S], [S]],
|
|
66
|
+
["str_replace", [S, S, S, "i32"], [S]],
|
|
67
|
+
["str_contains_rune", [S, "i32"], ["i32"]],
|
|
68
|
+
["str_contains_any", [S, S], ["i32"]],
|
|
69
|
+
["str_index_byte", [S, "i32"], ["i32"]],
|
|
70
|
+
["str_index_rune", [S, "i32"], ["i32"]],
|
|
71
|
+
["str_index_any", [S, S], ["i32"]],
|
|
72
|
+
["str_last_index_byte", [S, "i32"], ["i32"]],
|
|
73
|
+
["str_title", [S], [S]],
|
|
74
|
+
["str_split", [S, S], ["i32"]],
|
|
75
|
+
["str_fields", [S], ["i32"]],
|
|
76
|
+
["str_part", ["i32"], [S]],
|
|
77
|
+
// strconv
|
|
78
|
+
["strconv_atoi", [S], ["i64"]],
|
|
79
|
+
["strconv_parse_int", [S, "i32", "i32"], ["i64"]],
|
|
80
|
+
["strconv_parse_float", [S], ["f64"]],
|
|
81
|
+
["strconv_parse_bool", [S], ["i32"]],
|
|
82
|
+
["strconv_err", [], [S]],
|
|
83
|
+
["strconv_format_float", ["f64", "i32", "i32", "i32"], [S]],
|
|
84
|
+
["strconv_format_int", ["i64", "i32"], [S]],
|
|
85
|
+
["strconv_quote", [S], [S]],
|
|
86
|
+
// unicode/utf8
|
|
87
|
+
["utf8_rune_count", [S], ["i32"]],
|
|
88
|
+
["utf8_rune_len", ["i32"], ["i32"]],
|
|
89
|
+
["utf8_valid_string", [S], ["i32"]],
|
|
90
|
+
["utf8_decode_rune", [S, "i32"], ["i32"]],
|
|
91
|
+
["utf8_decode_size", [], ["i32"]],
|
|
92
|
+
["utf8_full_rune", [S], ["i32"]],
|
|
93
|
+
// fmt (arguments travel through the testing_arg_* buffer)
|
|
94
|
+
["fmt_sprintf", [S], [S]],
|
|
95
|
+
["fmt_printf", [S], []],
|
|
96
|
+
// testing.T.Run: the harness calls back into the exported __testing_run_cb
|
|
97
|
+
["testing_run", [S, S, "anyref"], ["i32"]],
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
class ModuleEmitter {
|
|
22
101
|
constructor(checker, lowerResult = null, programs = [], options = {}) {
|
|
23
102
|
this.checker = checker;
|
|
24
103
|
this.lowerResult = lowerResult;
|
|
@@ -37,6 +116,9 @@ export class ModuleEmitter {
|
|
|
37
116
|
this.arrayTypes = new Map(); // wType key -> array info
|
|
38
117
|
this.sliceTypes = new Map(); // wType key -> slice info
|
|
39
118
|
this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
|
|
119
|
+
this.mapTypes = new Map(); // wType key -> map info
|
|
120
|
+
this.mapTypesByIndex = new Map(); // typeIndex -> map info
|
|
121
|
+
this.mapBucketsType = null; // shared (array (mut i32))
|
|
40
122
|
this.closureTypes = new Map(); // key -> closure info
|
|
41
123
|
this.envTypes = new Map(); // key -> env info
|
|
42
124
|
|
|
@@ -54,11 +136,18 @@ export class ModuleEmitter {
|
|
|
54
136
|
this.funcMap = new Map(); // name -> global func index
|
|
55
137
|
this.funcParamTypes = new Map(); // name -> param types array
|
|
56
138
|
this.funcParamGoTypes = new Map(); // name -> param Go types array
|
|
139
|
+
this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
|
|
57
140
|
|
|
58
141
|
this.exports = []; // export entries
|
|
59
142
|
this.stringTable = []; // string literals
|
|
60
143
|
this.stringCache = new Map();
|
|
61
144
|
|
|
145
|
+
// `errors.New` / `fmt.Errorf` / strconv parse failures allocate the
|
|
146
|
+
// runtime's own error struct; it joins the struct rec group so the
|
|
147
|
+
// `error.Error` dispatcher can `ref.test` it like any user type.
|
|
148
|
+
this.hasErrorType = importsAnyOf(programs, ERROR_PRODUCING_PKGS);
|
|
149
|
+
this.usesStdlibEnv = importsAnyOf(programs, STDLIB_ENV_PKGS);
|
|
150
|
+
|
|
62
151
|
if (programs && programs.length > 0) {
|
|
63
152
|
this.initTypes(programs);
|
|
64
153
|
}
|
|
@@ -73,6 +162,20 @@ export class ModuleEmitter {
|
|
|
73
162
|
const seen = new Set();
|
|
74
163
|
const aliases = []; // qualified key (pkg.T) -> unqualified struct name
|
|
75
164
|
|
|
165
|
+
if (this.hasErrorType) {
|
|
166
|
+
seen.add(ERROR_STRUCT);
|
|
167
|
+
rawStructs.push({
|
|
168
|
+
name: ERROR_STRUCT,
|
|
169
|
+
structType: {
|
|
170
|
+
kind: "struct",
|
|
171
|
+
fields: new Map([
|
|
172
|
+
["msg", { kind: "basic", name: "string" }],
|
|
173
|
+
["cause", { kind: "basic", name: "any" }],
|
|
174
|
+
]),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
76
179
|
for (const p of progs) {
|
|
77
180
|
for (const d of p.decls ?? []) {
|
|
78
181
|
if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
|
|
@@ -136,7 +239,8 @@ export class ModuleEmitter {
|
|
|
136
239
|
for (let i = 0; i < rawStructs.length; i++) {
|
|
137
240
|
const s = rawStructs[i];
|
|
138
241
|
const info = this.structTypes.get(s.name);
|
|
139
|
-
const resolvedStruct =
|
|
242
|
+
const resolvedStruct =
|
|
243
|
+
s.structType ?? this.checker?.types?.get(s.name)?.underlying;
|
|
140
244
|
|
|
141
245
|
const fields = [];
|
|
142
246
|
const fieldIndexMap = new Map();
|
|
@@ -308,6 +412,107 @@ export class ModuleEmitter {
|
|
|
308
412
|
return this.sliceTypesByIndex?.get(typeIndex) ?? null;
|
|
309
413
|
}
|
|
310
414
|
|
|
415
|
+
getMapType(keyGoType, valGoType) {
|
|
416
|
+
const keyWType = toWasmType(keyGoType, this.checker, this);
|
|
417
|
+
const valWType = toWasmType(valGoType, this.checker, this);
|
|
418
|
+
const key = `${wTypeKey(keyWType)}:${wTypeKey(valWType)}`;
|
|
419
|
+
if (this.mapTypes.has(key)) {
|
|
420
|
+
return this.mapTypes.get(key);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!this.mapBucketsType) {
|
|
424
|
+
const typeIndex = this._getTotalTypeCount();
|
|
425
|
+
this._pushType({
|
|
426
|
+
form: "array",
|
|
427
|
+
elemType: "i32",
|
|
428
|
+
mutable: true,
|
|
429
|
+
});
|
|
430
|
+
this.mapBucketsType = { typeIndex };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const entryTypeIndex = this._getTotalTypeCount();
|
|
434
|
+
this._pushType({
|
|
435
|
+
form: "struct",
|
|
436
|
+
fields: [
|
|
437
|
+
{ type: keyWType, mutable: true }, // 0: key
|
|
438
|
+
{ type: valWType, mutable: true }, // 1: val
|
|
439
|
+
{ type: "i32", mutable: true }, // 2: next (bucket chain)
|
|
440
|
+
{ type: "i32", mutable: true }, // 3: order_prev (insertion order)
|
|
441
|
+
{ type: "i32", mutable: true }, // 4: order_next (insertion order)
|
|
442
|
+
{ type: "i32", mutable: true }, // 5: active (1 = active, 0 = deleted)
|
|
443
|
+
],
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const entriesTypeIndex = this._getTotalTypeCount();
|
|
447
|
+
this._pushType({
|
|
448
|
+
form: "array",
|
|
449
|
+
elemType: { kind: "ref", nullable: true, typeIndex: entryTypeIndex },
|
|
450
|
+
mutable: true,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const mapTypeIndex = this._getTotalTypeCount();
|
|
454
|
+
this._pushType({
|
|
455
|
+
form: "struct",
|
|
456
|
+
fields: [
|
|
457
|
+
{
|
|
458
|
+
type: {
|
|
459
|
+
kind: "ref",
|
|
460
|
+
nullable: true,
|
|
461
|
+
typeIndex: this.mapBucketsType.typeIndex,
|
|
462
|
+
},
|
|
463
|
+
mutable: true,
|
|
464
|
+
}, // 0: buckets
|
|
465
|
+
{
|
|
466
|
+
type: {
|
|
467
|
+
kind: "ref",
|
|
468
|
+
nullable: true,
|
|
469
|
+
typeIndex: entriesTypeIndex,
|
|
470
|
+
},
|
|
471
|
+
mutable: true,
|
|
472
|
+
}, // 1: entries
|
|
473
|
+
{ type: "i32", mutable: true }, // 2: len
|
|
474
|
+
{ type: "i32", mutable: true }, // 3: cap
|
|
475
|
+
{ type: "i32", mutable: true }, // 4: count
|
|
476
|
+
{ type: "i32", mutable: true }, // 5: head
|
|
477
|
+
{ type: "i32", mutable: true }, // 6: tail
|
|
478
|
+
{ type: "i32", mutable: true }, // 7: free_head
|
|
479
|
+
{ type: "i32", mutable: true }, // 8: num_buckets
|
|
480
|
+
],
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const suffix = key.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
484
|
+
const mapInfo = {
|
|
485
|
+
key,
|
|
486
|
+
suffix,
|
|
487
|
+
typeIndex: mapTypeIndex,
|
|
488
|
+
entryTypeIndex,
|
|
489
|
+
entriesTypeIndex,
|
|
490
|
+
bucketsTypeIndex: this.mapBucketsType.typeIndex,
|
|
491
|
+
keyWType,
|
|
492
|
+
valWType,
|
|
493
|
+
keyGoType,
|
|
494
|
+
valGoType,
|
|
495
|
+
wType: { kind: "ref", nullable: true, typeIndex: mapTypeIndex },
|
|
496
|
+
makeFuncName: `__map_make_${suffix}`,
|
|
497
|
+
getFuncName: `__map_get_${suffix}`,
|
|
498
|
+
getOkFuncName: `__map_get_ok_${suffix}`,
|
|
499
|
+
setFuncName: `__map_set_${suffix}`,
|
|
500
|
+
deleteFuncName: `__map_delete_${suffix}`,
|
|
501
|
+
lenFuncName: `__map_len_${suffix}`,
|
|
502
|
+
clearFuncName: `__map_clear_${suffix}`,
|
|
503
|
+
keysFuncName: `__map_keys_${suffix}`,
|
|
504
|
+
valuesFuncName: `__map_values_${suffix}`,
|
|
505
|
+
cloneFuncName: `__map_clone_${suffix}`,
|
|
506
|
+
};
|
|
507
|
+
this.mapTypes.set(key, mapInfo);
|
|
508
|
+
this.mapTypesByIndex.set(mapTypeIndex, mapInfo);
|
|
509
|
+
return mapInfo;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getMapTypeByIndex(typeIndex) {
|
|
513
|
+
return this.mapTypesByIndex?.get(typeIndex) ?? null;
|
|
514
|
+
}
|
|
515
|
+
|
|
311
516
|
getClosureType(goType) {
|
|
312
517
|
const sig = getFuncSignature(goType, this.checker);
|
|
313
518
|
const paramWTypes = sig.params
|
|
@@ -399,9 +604,11 @@ export class ModuleEmitter {
|
|
|
399
604
|
|
|
400
605
|
_initRuntimeImports() {
|
|
401
606
|
this._initPanicTag();
|
|
607
|
+
this._initPanicGlobal();
|
|
402
608
|
this.getPanicImportIndex(); // env.panic
|
|
403
609
|
this.internString("runtime error: index out of range");
|
|
404
610
|
this.internString("runtime error: slice bounds out of range");
|
|
611
|
+
this.internString("assignment to entry in nil map");
|
|
405
612
|
|
|
406
613
|
// String built-ins
|
|
407
614
|
this.getStringImportIndex(); // env.str
|
|
@@ -417,7 +624,35 @@ export class ModuleEmitter {
|
|
|
417
624
|
this.getStringSliceImportIndex(); // env.str_slice
|
|
418
625
|
this.getStringFromCodePointImportIndex(); // env.str_from_code_point
|
|
419
626
|
this.getStringCodePointAtImportIndex(); // env.str_code_point_at
|
|
627
|
+
this.getStringHashImportIndex(); // env.str_hash
|
|
628
|
+
this.getStringToUpperImportIndex();
|
|
629
|
+
this.getStringToLowerImportIndex();
|
|
630
|
+
this.getStringTrimSpaceImportIndex();
|
|
631
|
+
this.getStringContainsImportIndex();
|
|
632
|
+
this.getStringHasPrefixImportIndex();
|
|
633
|
+
this.getStringHasSuffixImportIndex();
|
|
634
|
+
this.getStringIndexImportIndex();
|
|
635
|
+
this.getStringLastIndexImportIndex();
|
|
636
|
+
this.getStringRepeatImportIndex();
|
|
637
|
+
this.getStringReplaceAllImportIndex();
|
|
638
|
+
this.getStringEqualFoldImportIndex();
|
|
639
|
+
this.getStringCountImportIndex();
|
|
640
|
+
this.getStrFromI64ImportIndex();
|
|
641
|
+
this.getStrFromI32ImportIndex();
|
|
642
|
+
this.getStrFromF64ImportIndex();
|
|
420
643
|
this.getIsStringImportIndex(); // env.is_string
|
|
644
|
+
this.getTestingCallImportIndex();
|
|
645
|
+
this.getTestingNameImportIndex();
|
|
646
|
+
this.getTestingFlagImportIndex();
|
|
647
|
+
for (const t of ["i32", "i64", "f32", "f64", "externref", "anyref"]) {
|
|
648
|
+
this.getTestingArgImportIndex(t, false);
|
|
649
|
+
}
|
|
650
|
+
this.getTestingArgImportIndex("i32", true);
|
|
651
|
+
if (this.usesStdlibEnv) {
|
|
652
|
+
for (const [name, params, results] of STDLIB_IMPORTS) {
|
|
653
|
+
this.getOrAddFuncImport("env", name, params, results);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
421
656
|
|
|
422
657
|
// Logging built-ins
|
|
423
658
|
this.getPrintlnEmptyIndex(); // env.println_empty
|
|
@@ -442,12 +677,12 @@ export class ModuleEmitter {
|
|
|
442
677
|
"log2",
|
|
443
678
|
"log10",
|
|
444
679
|
"round",
|
|
680
|
+
"cbrt",
|
|
445
681
|
];
|
|
446
682
|
for (const m of unaryMath) {
|
|
447
683
|
this.getMathImportIndex(m, false);
|
|
448
684
|
}
|
|
449
|
-
const
|
|
450
|
-
for (const m of binaryMath) {
|
|
685
|
+
for (const m of BINARY_MATH_IMPORTS) {
|
|
451
686
|
this.getMathImportIndex(m, true);
|
|
452
687
|
}
|
|
453
688
|
}
|
|
@@ -461,6 +696,114 @@ export class ModuleEmitter {
|
|
|
461
696
|
this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
|
|
462
697
|
}
|
|
463
698
|
|
|
699
|
+
getPanicNodeTypeIndex() {
|
|
700
|
+
if (this.panicNodeTypeIndex !== undefined) {
|
|
701
|
+
return this.panicNodeTypeIndex;
|
|
702
|
+
}
|
|
703
|
+
const typeIndex = this._getTotalTypeCount();
|
|
704
|
+
const typeEntry = {
|
|
705
|
+
form: "rec",
|
|
706
|
+
types: [
|
|
707
|
+
{
|
|
708
|
+
form: "struct",
|
|
709
|
+
fields: [
|
|
710
|
+
{
|
|
711
|
+
type: "anyref",
|
|
712
|
+
mutable: true,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
type: "i32",
|
|
716
|
+
mutable: true,
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
type: {
|
|
720
|
+
kind: "ref",
|
|
721
|
+
nullable: true,
|
|
722
|
+
typeIndex,
|
|
723
|
+
},
|
|
724
|
+
mutable: true,
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
};
|
|
730
|
+
this._pushType(typeEntry);
|
|
731
|
+
this.panicNodeTypeIndex = typeIndex;
|
|
732
|
+
return typeIndex;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
_initPanicGlobal() {
|
|
736
|
+
const pType = this.getPanicNodeTypeIndex();
|
|
737
|
+
const gIdx = this.globals.length;
|
|
738
|
+
this.globals.push({
|
|
739
|
+
type: { kind: "ref", nullable: true, typeIndex: pType },
|
|
740
|
+
mutable: true,
|
|
741
|
+
init: [{ op: "ref.null", typeIndex: pType }],
|
|
742
|
+
});
|
|
743
|
+
this.panicGlobalIndex = gIdx;
|
|
744
|
+
this.exports.push({ name: "__panic", kind: "global", index: gIdx });
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
getPanicGlobalIndex() {
|
|
748
|
+
return this.panicGlobalIndex;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// `__deferArmed` is set to 1 immediately before a deferred closure is
|
|
752
|
+
// invoked and cleared by that closure's prologue. recover() only takes
|
|
753
|
+
// effect when the current frame observed the flag, which gives Go's
|
|
754
|
+
// "recover must be called directly by the deferred function" rule.
|
|
755
|
+
getDeferArmedGlobalIndex() {
|
|
756
|
+
if (this.deferArmedGlobalIndex !== undefined) {
|
|
757
|
+
return this.deferArmedGlobalIndex;
|
|
758
|
+
}
|
|
759
|
+
const gIdx = this.globals.length;
|
|
760
|
+
this.globals.push({
|
|
761
|
+
type: "i32",
|
|
762
|
+
mutable: true,
|
|
763
|
+
init: [{ op: "i32.const", value: 0 }],
|
|
764
|
+
});
|
|
765
|
+
this.deferArmedGlobalIndex = gIdx;
|
|
766
|
+
return gIdx;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
getDeferNodeTypeIndex() {
|
|
770
|
+
if (this.deferNodeTypeIndex !== undefined) {
|
|
771
|
+
return this.deferNodeTypeIndex;
|
|
772
|
+
}
|
|
773
|
+
const deferClosureSig = { kind: "Signature", params: [], results: [] };
|
|
774
|
+
const deferClosureInfo = this.getClosureType(deferClosureSig);
|
|
775
|
+
const typeIndex = this._getTotalTypeCount();
|
|
776
|
+
const typeEntry = {
|
|
777
|
+
form: "rec",
|
|
778
|
+
types: [
|
|
779
|
+
{
|
|
780
|
+
form: "struct",
|
|
781
|
+
fields: [
|
|
782
|
+
{
|
|
783
|
+
type: {
|
|
784
|
+
kind: "ref",
|
|
785
|
+
nullable: true,
|
|
786
|
+
typeIndex: deferClosureInfo.typeIndex,
|
|
787
|
+
},
|
|
788
|
+
mutable: false,
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
type: {
|
|
792
|
+
kind: "ref",
|
|
793
|
+
nullable: true,
|
|
794
|
+
typeIndex,
|
|
795
|
+
},
|
|
796
|
+
mutable: true,
|
|
797
|
+
},
|
|
798
|
+
],
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
};
|
|
802
|
+
this._pushType(typeEntry);
|
|
803
|
+
this.deferNodeTypeIndex = typeIndex;
|
|
804
|
+
return typeIndex;
|
|
805
|
+
}
|
|
806
|
+
|
|
464
807
|
getTypeIndex(params, results) {
|
|
465
808
|
const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
|
|
466
809
|
if (this.typeCache.has(key)) {
|
|
@@ -518,6 +861,32 @@ export class ModuleEmitter {
|
|
|
518
861
|
return this.resolveFuncIndex("__bounds_panic");
|
|
519
862
|
}
|
|
520
863
|
|
|
864
|
+
// `gofront/shared` buffers are `(base i32, len i32)` views over the single
|
|
865
|
+
// exported linear memory, bump-allocated by `__shared_new` during start.
|
|
866
|
+
getSharedType() {
|
|
867
|
+
if (this.sharedType) return this.sharedType;
|
|
868
|
+
const typeIndex = this._getTotalTypeCount();
|
|
869
|
+
const typeEntry = {
|
|
870
|
+
form: "struct",
|
|
871
|
+
fields: [
|
|
872
|
+
{ type: "i32", mutable: false }, // byte base
|
|
873
|
+
{ type: "i32", mutable: false }, // element count
|
|
874
|
+
],
|
|
875
|
+
};
|
|
876
|
+
this._pushType(typeEntry);
|
|
877
|
+
this.memory = { min: 1 };
|
|
878
|
+
this.exports.push({ name: "memory", kind: "memory", index: 0 });
|
|
879
|
+
const topGlobalIndex = this.globals.length;
|
|
880
|
+
this.globals.push({
|
|
881
|
+
type: "i32",
|
|
882
|
+
mutable: true,
|
|
883
|
+
init: [{ op: "i32.const", value: 0 }],
|
|
884
|
+
});
|
|
885
|
+
const wType = { kind: "ref", nullable: true, typeIndex };
|
|
886
|
+
this.sharedType = { typeIndex, typeEntry, wType, topGlobalIndex };
|
|
887
|
+
return this.sharedType;
|
|
888
|
+
}
|
|
889
|
+
|
|
521
890
|
getSliceBoundsPanicFuncIndex() {
|
|
522
891
|
return this.resolveFuncIndex("__slice_bounds_panic");
|
|
523
892
|
}
|
|
@@ -535,7 +904,7 @@ export class ModuleEmitter {
|
|
|
535
904
|
wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
|
|
536
905
|
if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
|
|
537
906
|
throw new Error(
|
|
538
|
-
"only primitive, string and any arguments are supported for testing.T
|
|
907
|
+
"only primitive, string, error and any arguments are supported for fmt and testing.T calls in wasm packages (format slices/structs field by field)",
|
|
539
908
|
);
|
|
540
909
|
}
|
|
541
910
|
return this.getOrAddFuncImport(
|
|
@@ -594,9 +963,15 @@ export class ModuleEmitter {
|
|
|
594
963
|
["externref"],
|
|
595
964
|
[],
|
|
596
965
|
);
|
|
597
|
-
case "anyref":
|
|
598
|
-
return this.getOrAddFuncImport("env", `${prefix}_any`, ["anyref"], []);
|
|
599
966
|
default:
|
|
967
|
+
if (typeof wType === "object") {
|
|
968
|
+
return this.getOrAddFuncImport(
|
|
969
|
+
"env",
|
|
970
|
+
`${prefix}_any`,
|
|
971
|
+
["anyref"],
|
|
972
|
+
[],
|
|
973
|
+
);
|
|
974
|
+
}
|
|
600
975
|
return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
|
|
601
976
|
}
|
|
602
977
|
}
|
|
@@ -672,11 +1047,166 @@ export class ModuleEmitter {
|
|
|
672
1047
|
return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
|
|
673
1048
|
}
|
|
674
1049
|
|
|
1050
|
+
getStringHashImportIndex() {
|
|
1051
|
+
return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
getStringToUpperImportIndex() {
|
|
1055
|
+
return this.getOrAddFuncImport(
|
|
1056
|
+
"env",
|
|
1057
|
+
"str_to_upper",
|
|
1058
|
+
["externref"],
|
|
1059
|
+
["externref"],
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
getStringToLowerImportIndex() {
|
|
1064
|
+
return this.getOrAddFuncImport(
|
|
1065
|
+
"env",
|
|
1066
|
+
"str_to_lower",
|
|
1067
|
+
["externref"],
|
|
1068
|
+
["externref"],
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
getStringTrimSpaceImportIndex() {
|
|
1073
|
+
return this.getOrAddFuncImport(
|
|
1074
|
+
"env",
|
|
1075
|
+
"str_trim_space",
|
|
1076
|
+
["externref"],
|
|
1077
|
+
["externref"],
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
getStringContainsImportIndex() {
|
|
1082
|
+
return this.getOrAddFuncImport(
|
|
1083
|
+
"env",
|
|
1084
|
+
"str_contains",
|
|
1085
|
+
["externref", "externref"],
|
|
1086
|
+
["i32"],
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
getStringHasPrefixImportIndex() {
|
|
1091
|
+
return this.getOrAddFuncImport(
|
|
1092
|
+
"env",
|
|
1093
|
+
"str_has_prefix",
|
|
1094
|
+
["externref", "externref"],
|
|
1095
|
+
["i32"],
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
getStringHasSuffixImportIndex() {
|
|
1100
|
+
return this.getOrAddFuncImport(
|
|
1101
|
+
"env",
|
|
1102
|
+
"str_has_suffix",
|
|
1103
|
+
["externref", "externref"],
|
|
1104
|
+
["i32"],
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
getStringIndexImportIndex() {
|
|
1109
|
+
return this.getOrAddFuncImport(
|
|
1110
|
+
"env",
|
|
1111
|
+
"str_index",
|
|
1112
|
+
["externref", "externref"],
|
|
1113
|
+
["i32"],
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
getStringLastIndexImportIndex() {
|
|
1118
|
+
return this.getOrAddFuncImport(
|
|
1119
|
+
"env",
|
|
1120
|
+
"str_last_index",
|
|
1121
|
+
["externref", "externref"],
|
|
1122
|
+
["i32"],
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
getStringRepeatImportIndex() {
|
|
1127
|
+
return this.getOrAddFuncImport(
|
|
1128
|
+
"env",
|
|
1129
|
+
"str_repeat",
|
|
1130
|
+
["externref", "i32"],
|
|
1131
|
+
["externref"],
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
getStringReplaceAllImportIndex() {
|
|
1136
|
+
return this.getOrAddFuncImport(
|
|
1137
|
+
"env",
|
|
1138
|
+
"str_replace_all",
|
|
1139
|
+
["externref", "externref", "externref"],
|
|
1140
|
+
["externref"],
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
getStringEqualFoldImportIndex() {
|
|
1145
|
+
return this.getOrAddFuncImport(
|
|
1146
|
+
"env",
|
|
1147
|
+
"str_equal_fold",
|
|
1148
|
+
["externref", "externref"],
|
|
1149
|
+
["i32"],
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
getStringCountImportIndex() {
|
|
1154
|
+
return this.getOrAddFuncImport(
|
|
1155
|
+
"env",
|
|
1156
|
+
"str_count",
|
|
1157
|
+
["externref", "externref"],
|
|
1158
|
+
["i32"],
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
getStrFromI64ImportIndex() {
|
|
1163
|
+
return this.getOrAddFuncImport(
|
|
1164
|
+
"env",
|
|
1165
|
+
"str_from_i64",
|
|
1166
|
+
["i64"],
|
|
1167
|
+
["externref"],
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
getStrFromI32ImportIndex() {
|
|
1172
|
+
return this.getOrAddFuncImport(
|
|
1173
|
+
"env",
|
|
1174
|
+
"str_from_i32",
|
|
1175
|
+
["i32"],
|
|
1176
|
+
["externref"],
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
getStrFromF64ImportIndex() {
|
|
1181
|
+
return this.getOrAddFuncImport(
|
|
1182
|
+
"env",
|
|
1183
|
+
"str_from_f64",
|
|
1184
|
+
["f64"],
|
|
1185
|
+
["externref"],
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
675
1189
|
getMathImportIndex(jsName, isBinary = false) {
|
|
676
1190
|
const params = isBinary ? ["f64", "f64"] : ["f64"];
|
|
677
1191
|
return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
|
|
678
1192
|
}
|
|
679
1193
|
|
|
1194
|
+
// Imports from the STDLIB_IMPORTS table (strings/strconv/utf8/fmt/testing.Run).
|
|
1195
|
+
getStdlibImportIndex(name) {
|
|
1196
|
+
const idx = this.importCache.get(`env.${name}`);
|
|
1197
|
+
if (idx === undefined) throw new Error(`Unknown runtime import: ${name}`);
|
|
1198
|
+
return idx;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Closure type of `func(*testing.T)` (the t.Run body).
|
|
1202
|
+
getTestingRunClosureType() {
|
|
1203
|
+
return this.getClosureType({
|
|
1204
|
+
kind: "func",
|
|
1205
|
+
params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
|
|
1206
|
+
returns: [],
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
680
1210
|
resolveFuncIndex(name) {
|
|
681
1211
|
return this.funcMap.get(name) ?? null;
|
|
682
1212
|
}
|
|
@@ -700,9 +1230,220 @@ export class ModuleEmitter {
|
|
|
700
1230
|
isNonLiteralConst(name) {
|
|
701
1231
|
return this.nonLiteralConsts.has(name);
|
|
702
1232
|
}
|
|
1233
|
+
|
|
1234
|
+
// Interface method dispatch is a `ref.test` chain over every concrete type
|
|
1235
|
+
// that declares `methodName` (no itabs); the static interface type is not
|
|
1236
|
+
// needed to build the candidate list.
|
|
1237
|
+
findInterfaceCandidates(_ifaceType, methodName) {
|
|
1238
|
+
const candidates = [];
|
|
1239
|
+
const seenKeys = new Set();
|
|
1240
|
+
|
|
1241
|
+
// 1. Struct types
|
|
1242
|
+
for (const [sName, sInfo] of this.structTypes.entries()) {
|
|
1243
|
+
const directMethodName = `${sName}.${methodName}`;
|
|
1244
|
+
if (this.funcMap.has(directMethodName)) {
|
|
1245
|
+
const fIdx = this.resolveFuncIndex(directMethodName);
|
|
1246
|
+
const paramGoTypes = this.getFuncParamGoTypes(directMethodName);
|
|
1247
|
+
const paramWTypes = this.getFuncParamTypes(directMethodName);
|
|
1248
|
+
const recvGoType = paramGoTypes[0];
|
|
1249
|
+
const isPtrRecv = recvGoType?.kind === "pointer";
|
|
1250
|
+
|
|
1251
|
+
if (isPtrRecv) {
|
|
1252
|
+
const key = `ptr:${sName}`;
|
|
1253
|
+
if (!seenKeys.has(key)) {
|
|
1254
|
+
seenKeys.add(key);
|
|
1255
|
+
candidates.push({
|
|
1256
|
+
recvTypeName: sName,
|
|
1257
|
+
testTypeIndex: sInfo.typeIndex,
|
|
1258
|
+
isBoxedValue: false,
|
|
1259
|
+
needsValueDeref: false,
|
|
1260
|
+
embedPath: null,
|
|
1261
|
+
funcIndex: fIdx,
|
|
1262
|
+
targetParamTypes: paramWTypes,
|
|
1263
|
+
structInfo: sInfo,
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
// Value receiver: both *sName and sName match
|
|
1268
|
+
const ptrKey = `ptr:${sName}`;
|
|
1269
|
+
if (!seenKeys.has(ptrKey)) {
|
|
1270
|
+
seenKeys.add(ptrKey);
|
|
1271
|
+
candidates.push({
|
|
1272
|
+
recvTypeName: sName,
|
|
1273
|
+
testTypeIndex: sInfo.typeIndex,
|
|
1274
|
+
isBoxedValue: false,
|
|
1275
|
+
needsValueDeref: true,
|
|
1276
|
+
embedPath: null,
|
|
1277
|
+
funcIndex: fIdx,
|
|
1278
|
+
targetParamTypes: paramWTypes,
|
|
1279
|
+
targetRecvWType: paramWTypes[0],
|
|
1280
|
+
structInfo: sInfo,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
const box = this.getBoxType({ kind: "named", name: sName });
|
|
1284
|
+
const valKey = `val:${sName}`;
|
|
1285
|
+
if (box && !seenKeys.has(valKey)) {
|
|
1286
|
+
seenKeys.add(valKey);
|
|
1287
|
+
candidates.push({
|
|
1288
|
+
recvTypeName: sName,
|
|
1289
|
+
testTypeIndex: box.typeIndex,
|
|
1290
|
+
isBoxedValue: true,
|
|
1291
|
+
needsValueDeref: false,
|
|
1292
|
+
embedPath: null,
|
|
1293
|
+
funcIndex: fIdx,
|
|
1294
|
+
targetParamTypes: paramWTypes,
|
|
1295
|
+
targetRecvWType: paramWTypes[0],
|
|
1296
|
+
structInfo: sInfo,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Promoted methods on sName
|
|
1303
|
+
if (sInfo.embeds && sInfo.embeds.length > 0) {
|
|
1304
|
+
for (const embed of sInfo.embeds) {
|
|
1305
|
+
const embedMethodName = `${embed.name}.${methodName}`;
|
|
1306
|
+
if (this.funcMap.has(embedMethodName)) {
|
|
1307
|
+
const fIdx = this.resolveFuncIndex(embedMethodName);
|
|
1308
|
+
const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
|
|
1309
|
+
const paramWTypes = this.getFuncParamTypes(embedMethodName);
|
|
1310
|
+
const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
|
|
1311
|
+
const embedSInfo = this.getStructType(embed.name);
|
|
1312
|
+
|
|
1313
|
+
const ptrKey = `ptr:${sName}`;
|
|
1314
|
+
if (!seenKeys.has(ptrKey)) {
|
|
1315
|
+
seenKeys.add(ptrKey);
|
|
1316
|
+
candidates.push({
|
|
1317
|
+
recvTypeName: sName,
|
|
1318
|
+
testTypeIndex: sInfo.typeIndex,
|
|
1319
|
+
isBoxedValue: false,
|
|
1320
|
+
needsValueDeref: !isPtrRecv,
|
|
1321
|
+
embedPath: [
|
|
1322
|
+
{
|
|
1323
|
+
parentTypeIndex: sInfo.typeIndex,
|
|
1324
|
+
fieldIndex: embed.fieldIndex,
|
|
1325
|
+
},
|
|
1326
|
+
],
|
|
1327
|
+
funcIndex: fIdx,
|
|
1328
|
+
targetParamTypes: paramWTypes,
|
|
1329
|
+
targetRecvWType: paramWTypes[0],
|
|
1330
|
+
structInfo: embedSInfo,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
if (!isPtrRecv) {
|
|
1335
|
+
const box = this.getBoxType({ kind: "named", name: sName });
|
|
1336
|
+
const valKey = `val:${sName}`;
|
|
1337
|
+
if (box && !seenKeys.has(valKey)) {
|
|
1338
|
+
seenKeys.add(valKey);
|
|
1339
|
+
candidates.push({
|
|
1340
|
+
recvTypeName: sName,
|
|
1341
|
+
testTypeIndex: box.typeIndex,
|
|
1342
|
+
isBoxedValue: true,
|
|
1343
|
+
needsValueDeref: false,
|
|
1344
|
+
embedPath: [
|
|
1345
|
+
{
|
|
1346
|
+
parentTypeIndex: sInfo.typeIndex,
|
|
1347
|
+
fieldIndex: embed.fieldIndex,
|
|
1348
|
+
},
|
|
1349
|
+
],
|
|
1350
|
+
funcIndex: fIdx,
|
|
1351
|
+
targetParamTypes: paramWTypes,
|
|
1352
|
+
targetRecvWType: paramWTypes[0],
|
|
1353
|
+
structInfo: embedSInfo,
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// 2. Named non-struct types
|
|
1363
|
+
for (const [fnName, fIdx] of this.funcMap.entries()) {
|
|
1364
|
+
if (fnName.endsWith(`.${methodName}`)) {
|
|
1365
|
+
const rName = fnName.slice(0, fnName.length - methodName.length - 1);
|
|
1366
|
+
if (!this.structTypes.has(rName)) {
|
|
1367
|
+
const box = this.getBoxType({ kind: "named", name: rName });
|
|
1368
|
+
const key = `named:${rName}`;
|
|
1369
|
+
if (box && !seenKeys.has(key)) {
|
|
1370
|
+
seenKeys.add(key);
|
|
1371
|
+
const paramWTypes = this.getFuncParamTypes(fnName);
|
|
1372
|
+
candidates.push({
|
|
1373
|
+
recvTypeName: rName,
|
|
1374
|
+
testTypeIndex: box.typeIndex,
|
|
1375
|
+
isBoxedValue: true,
|
|
1376
|
+
needsValueDeref: false,
|
|
1377
|
+
embedPath: null,
|
|
1378
|
+
funcIndex: fIdx,
|
|
1379
|
+
targetParamTypes: paramWTypes,
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
return candidates;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
getTypesImplementingInterface(ifaceType) {
|
|
1390
|
+
const candidates = [];
|
|
1391
|
+
const seen = new Set();
|
|
1392
|
+
const ifaceResolved =
|
|
1393
|
+
this.checker?.resolveType?.(
|
|
1394
|
+
ifaceType?.kind === "named" ? ifaceType.underlying : ifaceType,
|
|
1395
|
+
) ?? ifaceType;
|
|
1396
|
+
|
|
1397
|
+
// 1. Struct types
|
|
1398
|
+
for (const [sName, sInfo] of this.structTypes.entries()) {
|
|
1399
|
+
const namedType = this.checker?.types?.get(sName) ?? {
|
|
1400
|
+
kind: "named",
|
|
1401
|
+
name: sName,
|
|
1402
|
+
};
|
|
1403
|
+
const ptrType = { kind: "pointer", base: namedType };
|
|
1404
|
+
|
|
1405
|
+
// Check pointer to struct
|
|
1406
|
+
if (this.checker?.implements?.(ptrType, ifaceResolved)) {
|
|
1407
|
+
if (!seen.has(sInfo.typeIndex)) {
|
|
1408
|
+
seen.add(sInfo.typeIndex);
|
|
1409
|
+
candidates.push({ name: `*${sName}`, typeIndex: sInfo.typeIndex });
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Check struct value (boxed)
|
|
1414
|
+
if (this.checker?.implements?.(namedType, ifaceResolved)) {
|
|
1415
|
+
const box = this.getBoxType(namedType);
|
|
1416
|
+
if (box && !seen.has(box.typeIndex)) {
|
|
1417
|
+
seen.add(box.typeIndex);
|
|
1418
|
+
candidates.push({ name: sName, typeIndex: box.typeIndex });
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// 2. Named non-struct types that have methods
|
|
1424
|
+
if (this.checker?.types) {
|
|
1425
|
+
for (const [tName, tVal] of this.checker.types.entries()) {
|
|
1426
|
+
if (
|
|
1427
|
+
tVal?.kind === "named" &&
|
|
1428
|
+
tVal.underlying?.kind !== "struct" &&
|
|
1429
|
+
tVal.underlying?.kind !== "interface"
|
|
1430
|
+
) {
|
|
1431
|
+
if (this.checker.implements(tVal, ifaceResolved)) {
|
|
1432
|
+
const box = this.getBoxType(tVal);
|
|
1433
|
+
if (box && !seen.has(box.typeIndex)) {
|
|
1434
|
+
seen.add(box.typeIndex);
|
|
1435
|
+
candidates.push({ name: tName, typeIndex: box.typeIndex });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return candidates;
|
|
1443
|
+
}
|
|
703
1444
|
}
|
|
704
1445
|
|
|
705
|
-
|
|
1446
|
+
function peepholeOptimize(instructions) {
|
|
706
1447
|
let changed = true;
|
|
707
1448
|
let current = instructions;
|
|
708
1449
|
while (changed) {
|
|
@@ -768,9 +1509,20 @@ export function compileWasmModule(
|
|
|
768
1509
|
lowerResult = null,
|
|
769
1510
|
options = {},
|
|
770
1511
|
) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1512
|
+
let progs = Array.isArray(programs) ? programs : [programs];
|
|
1513
|
+
let resolvedChecker = checker;
|
|
1514
|
+
let resolvedLowerResult = lowerResult;
|
|
1515
|
+
|
|
1516
|
+
if (hasGenerics(progs)) {
|
|
1517
|
+
const mono = monomorphise(progs, checker);
|
|
1518
|
+
progs = mono.programs;
|
|
1519
|
+
resolvedChecker = mono.checker;
|
|
1520
|
+
resolvedLowerResult = lower(progs, resolvedChecker);
|
|
1521
|
+
} else if (!resolvedLowerResult) {
|
|
1522
|
+
resolvedLowerResult = lower(progs, resolvedChecker);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
|
|
774
1526
|
bundledPackages: options.bundledPackages,
|
|
775
1527
|
});
|
|
776
1528
|
|
|
@@ -785,6 +1537,7 @@ export function compileWasmModule(
|
|
|
785
1537
|
if (d.kind === "FuncDecl") {
|
|
786
1538
|
d._pkgName = pkgName;
|
|
787
1539
|
d._pkgTarget = pkgTarget;
|
|
1540
|
+
d._srcFile = p._path ?? p._filename ?? null;
|
|
788
1541
|
if (d.name === "init" || d.name.startsWith("init$")) {
|
|
789
1542
|
const renamed = initCount === 0 ? "init" : `init$${initCount}`;
|
|
790
1543
|
d.name = renamed;
|
|
@@ -812,6 +1565,8 @@ export function compileWasmModule(
|
|
|
812
1565
|
_sourceDecl: d,
|
|
813
1566
|
_pkgName: pkgName,
|
|
814
1567
|
_pkgTarget: pkgTarget,
|
|
1568
|
+
_srcFile: p._path ?? p._filename ?? null,
|
|
1569
|
+
_line: d._line,
|
|
815
1570
|
};
|
|
816
1571
|
funcDecls.push(normFn);
|
|
817
1572
|
}
|
|
@@ -846,6 +1601,8 @@ export function compileWasmModule(
|
|
|
846
1601
|
_isClosure: true,
|
|
847
1602
|
_funcLit: node,
|
|
848
1603
|
_rootFuncDecl: rootFn,
|
|
1604
|
+
_srcFile: rootFn?._srcFile ?? null,
|
|
1605
|
+
_line: typeof node._line === "number" ? node._line : rootFn?._line,
|
|
849
1606
|
};
|
|
850
1607
|
node._liftedFn = liftedFn;
|
|
851
1608
|
closures.push(liftedFn);
|
|
@@ -956,7 +1713,352 @@ export function compileWasmModule(
|
|
|
956
1713
|
};
|
|
957
1714
|
funcDecls.push(sliceBoundsPanicFn);
|
|
958
1715
|
|
|
959
|
-
|
|
1716
|
+
const pushPanicFn = {
|
|
1717
|
+
kind: "FuncDecl",
|
|
1718
|
+
name: "__push_panic",
|
|
1719
|
+
params: [{ name: "msg", type: { kind: "basic", name: "string" } }],
|
|
1720
|
+
returnType: null,
|
|
1721
|
+
_isPushPanic: true,
|
|
1722
|
+
};
|
|
1723
|
+
funcDecls.push(pushPanicFn);
|
|
1724
|
+
|
|
1725
|
+
// 1d. Interface dispatchers
|
|
1726
|
+
_collectInterfaceDispatchers(progs, funcDecls, mod, checker);
|
|
1727
|
+
|
|
1728
|
+
// 1e. Map helpers
|
|
1729
|
+
function scanMapTypes(node) {
|
|
1730
|
+
if (!node || typeof node !== "object") return;
|
|
1731
|
+
if (Array.isArray(node)) {
|
|
1732
|
+
for (const item of node) scanMapTypes(item);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
if (node._type && isMapType(node._type, checker)) {
|
|
1736
|
+
const { keyType, valType } = getMapKeyValTypes(node._type, checker);
|
|
1737
|
+
if (keyType && valType) {
|
|
1738
|
+
mod.getMapType(keyType, valType);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (node.kind === "MapType") {
|
|
1742
|
+
const keyGoType = node.key;
|
|
1743
|
+
const valGoType = node.value ?? node.elem;
|
|
1744
|
+
if (keyGoType && valGoType) {
|
|
1745
|
+
mod.getMapType(keyGoType, valGoType);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
for (const key of Object.keys(node)) {
|
|
1749
|
+
if (key.startsWith("_")) continue;
|
|
1750
|
+
scanMapTypes(node[key]);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
for (const fn of funcDecls) {
|
|
1755
|
+
if (fn.params) scanMapTypes(fn.params);
|
|
1756
|
+
if (fn.returnType) scanMapTypes(fn.returnType);
|
|
1757
|
+
if (fn.body) scanMapTypes(fn.body);
|
|
1758
|
+
}
|
|
1759
|
+
for (const p of progs) {
|
|
1760
|
+
for (const d of p.decls ?? []) {
|
|
1761
|
+
scanMapTypes(d);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
for (const mapInfo of mod.mapTypes.values()) {
|
|
1766
|
+
// make
|
|
1767
|
+
funcDecls.push({
|
|
1768
|
+
kind: "FuncDecl",
|
|
1769
|
+
name: mapInfo.makeFuncName,
|
|
1770
|
+
params: [{ name: "cap", type: { kind: "basic", name: "int32" } }],
|
|
1771
|
+
returnType: mapInfo.wType,
|
|
1772
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1773
|
+
_isMapHelper: true,
|
|
1774
|
+
_mapHelperKind: "make",
|
|
1775
|
+
_mapInfo: mapInfo,
|
|
1776
|
+
_rootFuncDecl: null,
|
|
1777
|
+
});
|
|
1778
|
+
// get
|
|
1779
|
+
funcDecls.push({
|
|
1780
|
+
kind: "FuncDecl",
|
|
1781
|
+
name: mapInfo.getFuncName,
|
|
1782
|
+
params: [
|
|
1783
|
+
{ name: "m", type: mapInfo.wType },
|
|
1784
|
+
{ name: "k", type: mapInfo.keyGoType },
|
|
1785
|
+
],
|
|
1786
|
+
returnType: mapInfo.valGoType,
|
|
1787
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1788
|
+
_isMapHelper: true,
|
|
1789
|
+
_mapHelperKind: "get",
|
|
1790
|
+
_mapInfo: mapInfo,
|
|
1791
|
+
_rootFuncDecl: null,
|
|
1792
|
+
});
|
|
1793
|
+
// get_ok
|
|
1794
|
+
funcDecls.push({
|
|
1795
|
+
kind: "FuncDecl",
|
|
1796
|
+
name: mapInfo.getOkFuncName,
|
|
1797
|
+
params: [
|
|
1798
|
+
{ name: "m", type: mapInfo.wType },
|
|
1799
|
+
{ name: "k", type: mapInfo.keyGoType },
|
|
1800
|
+
],
|
|
1801
|
+
returnType: {
|
|
1802
|
+
kind: "tuple",
|
|
1803
|
+
types: [mapInfo.valGoType, { kind: "basic", name: "bool" }],
|
|
1804
|
+
},
|
|
1805
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1806
|
+
_isMapHelper: true,
|
|
1807
|
+
_mapHelperKind: "get_ok",
|
|
1808
|
+
_mapInfo: mapInfo,
|
|
1809
|
+
_rootFuncDecl: null,
|
|
1810
|
+
});
|
|
1811
|
+
// set
|
|
1812
|
+
funcDecls.push({
|
|
1813
|
+
kind: "FuncDecl",
|
|
1814
|
+
name: mapInfo.setFuncName,
|
|
1815
|
+
params: [
|
|
1816
|
+
{ name: "m", type: mapInfo.wType },
|
|
1817
|
+
{ name: "k", type: mapInfo.keyGoType },
|
|
1818
|
+
{ name: "v", type: mapInfo.valGoType },
|
|
1819
|
+
],
|
|
1820
|
+
returnType: null,
|
|
1821
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1822
|
+
_isMapHelper: true,
|
|
1823
|
+
_mapHelperKind: "set",
|
|
1824
|
+
_mapInfo: mapInfo,
|
|
1825
|
+
_rootFuncDecl: null,
|
|
1826
|
+
});
|
|
1827
|
+
// delete
|
|
1828
|
+
funcDecls.push({
|
|
1829
|
+
kind: "FuncDecl",
|
|
1830
|
+
name: mapInfo.deleteFuncName,
|
|
1831
|
+
params: [
|
|
1832
|
+
{ name: "m", type: mapInfo.wType },
|
|
1833
|
+
{ name: "k", type: mapInfo.keyGoType },
|
|
1834
|
+
],
|
|
1835
|
+
returnType: null,
|
|
1836
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1837
|
+
_isMapHelper: true,
|
|
1838
|
+
_mapHelperKind: "delete",
|
|
1839
|
+
_mapInfo: mapInfo,
|
|
1840
|
+
_rootFuncDecl: null,
|
|
1841
|
+
});
|
|
1842
|
+
// len
|
|
1843
|
+
funcDecls.push({
|
|
1844
|
+
kind: "FuncDecl",
|
|
1845
|
+
name: mapInfo.lenFuncName,
|
|
1846
|
+
params: [{ name: "m", type: mapInfo.wType }],
|
|
1847
|
+
returnType: { kind: "basic", name: "int32" },
|
|
1848
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1849
|
+
_isMapHelper: true,
|
|
1850
|
+
_mapHelperKind: "len",
|
|
1851
|
+
_mapInfo: mapInfo,
|
|
1852
|
+
_rootFuncDecl: null,
|
|
1853
|
+
});
|
|
1854
|
+
// clear
|
|
1855
|
+
funcDecls.push({
|
|
1856
|
+
kind: "FuncDecl",
|
|
1857
|
+
name: mapInfo.clearFuncName,
|
|
1858
|
+
params: [{ name: "m", type: mapInfo.wType }],
|
|
1859
|
+
returnType: null,
|
|
1860
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1861
|
+
_isMapHelper: true,
|
|
1862
|
+
_mapHelperKind: "clear",
|
|
1863
|
+
_mapInfo: mapInfo,
|
|
1864
|
+
_rootFuncDecl: null,
|
|
1865
|
+
});
|
|
1866
|
+
// keys
|
|
1867
|
+
funcDecls.push({
|
|
1868
|
+
kind: "FuncDecl",
|
|
1869
|
+
name: mapInfo.keysFuncName,
|
|
1870
|
+
params: [{ name: "m", type: mapInfo.wType }],
|
|
1871
|
+
returnType: { kind: "slice", elem: mapInfo.keyGoType },
|
|
1872
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1873
|
+
_isMapHelper: true,
|
|
1874
|
+
_mapHelperKind: "keys",
|
|
1875
|
+
_mapInfo: mapInfo,
|
|
1876
|
+
_rootFuncDecl: null,
|
|
1877
|
+
});
|
|
1878
|
+
// values
|
|
1879
|
+
funcDecls.push({
|
|
1880
|
+
kind: "FuncDecl",
|
|
1881
|
+
name: mapInfo.valuesFuncName,
|
|
1882
|
+
params: [{ name: "m", type: mapInfo.wType }],
|
|
1883
|
+
returnType: { kind: "slice", elem: mapInfo.valGoType },
|
|
1884
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1885
|
+
_isMapHelper: true,
|
|
1886
|
+
_mapHelperKind: "values",
|
|
1887
|
+
_mapInfo: mapInfo,
|
|
1888
|
+
_rootFuncDecl: null,
|
|
1889
|
+
});
|
|
1890
|
+
// clone
|
|
1891
|
+
funcDecls.push({
|
|
1892
|
+
kind: "FuncDecl",
|
|
1893
|
+
name: mapInfo.cloneFuncName,
|
|
1894
|
+
params: [{ name: "m", type: mapInfo.wType }],
|
|
1895
|
+
returnType: mapInfo.wType,
|
|
1896
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1897
|
+
_isMapHelper: true,
|
|
1898
|
+
_mapHelperKind: "clone",
|
|
1899
|
+
_mapInfo: mapInfo,
|
|
1900
|
+
_rootFuncDecl: null,
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// 1e2. `gofront/shared` linear-memory helpers (only when imported).
|
|
1905
|
+
const usesShared = progs.some((p) =>
|
|
1906
|
+
(p.imports ?? []).some((decl) =>
|
|
1907
|
+
(decl.imports ?? [decl]).some((imp) => imp.path === "gofront/shared"),
|
|
1908
|
+
),
|
|
1909
|
+
);
|
|
1910
|
+
if (usesShared) {
|
|
1911
|
+
const sharedWType = mod.getSharedType().wType;
|
|
1912
|
+
const i32 = { kind: "basic", name: "int32" };
|
|
1913
|
+
funcDecls.push({
|
|
1914
|
+
kind: "FuncDecl",
|
|
1915
|
+
name: "__shared_len_panic",
|
|
1916
|
+
params: [],
|
|
1917
|
+
returnType: null,
|
|
1918
|
+
_isRuntimePanic: true,
|
|
1919
|
+
_panicMsg: "runtime error: shared buffer length out of range",
|
|
1920
|
+
});
|
|
1921
|
+
funcDecls.push({
|
|
1922
|
+
kind: "FuncDecl",
|
|
1923
|
+
name: "__shared_new",
|
|
1924
|
+
params: [
|
|
1925
|
+
{ name: "len", type: i32 },
|
|
1926
|
+
{ name: "bytes", type: i32 },
|
|
1927
|
+
],
|
|
1928
|
+
returnType: sharedWType,
|
|
1929
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1930
|
+
_isSharedHelper: "new",
|
|
1931
|
+
_rootFuncDecl: null,
|
|
1932
|
+
});
|
|
1933
|
+
funcDecls.push({
|
|
1934
|
+
kind: "FuncDecl",
|
|
1935
|
+
name: "__shared_sub",
|
|
1936
|
+
params: [
|
|
1937
|
+
{ name: "buf", type: sharedWType },
|
|
1938
|
+
{ name: "lo", type: i32 },
|
|
1939
|
+
{ name: "hi", type: i32 },
|
|
1940
|
+
{ name: "bytes", type: i32 },
|
|
1941
|
+
],
|
|
1942
|
+
returnType: sharedWType,
|
|
1943
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1944
|
+
_isSharedHelper: "sub",
|
|
1945
|
+
_rootFuncDecl: null,
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// 1e3. Runtime helpers: error values, interface equality, t.Run callback.
|
|
1950
|
+
mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
|
|
1951
|
+
mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
|
|
1952
|
+
const ANY_T = { kind: "basic", name: "any" };
|
|
1953
|
+
const STR_T = { kind: "basic", name: "string" };
|
|
1954
|
+
const BOOL_T = { kind: "basic", name: "bool" };
|
|
1955
|
+
const helper = (name, params, returnType, kind, extra = {}) => ({
|
|
1956
|
+
kind: "FuncDecl",
|
|
1957
|
+
name,
|
|
1958
|
+
params,
|
|
1959
|
+
returnType,
|
|
1960
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
1961
|
+
_isRuntimeHelper: kind,
|
|
1962
|
+
_rootFuncDecl: null,
|
|
1963
|
+
...extra,
|
|
1964
|
+
});
|
|
1965
|
+
if (mod.usesAnyEq) {
|
|
1966
|
+
funcDecls.push(
|
|
1967
|
+
helper(
|
|
1968
|
+
"__any_eq",
|
|
1969
|
+
[
|
|
1970
|
+
{ name: "a", type: ANY_T },
|
|
1971
|
+
{ name: "b", type: ANY_T },
|
|
1972
|
+
],
|
|
1973
|
+
BOOL_T,
|
|
1974
|
+
"any_eq",
|
|
1975
|
+
),
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
if (mod.hasErrorType) {
|
|
1979
|
+
const errPtr = {
|
|
1980
|
+
kind: "pointer",
|
|
1981
|
+
base: { kind: "named", name: ERROR_STRUCT },
|
|
1982
|
+
};
|
|
1983
|
+
funcDecls.push(
|
|
1984
|
+
helper(
|
|
1985
|
+
`${ERROR_STRUCT}.Error`,
|
|
1986
|
+
[{ name: "e", type: errPtr }],
|
|
1987
|
+
STR_T,
|
|
1988
|
+
"error_msg",
|
|
1989
|
+
{
|
|
1990
|
+
_isMethod: true,
|
|
1991
|
+
_recvTypeName: ERROR_STRUCT,
|
|
1992
|
+
_methodName: "Error",
|
|
1993
|
+
},
|
|
1994
|
+
),
|
|
1995
|
+
helper(
|
|
1996
|
+
`${ERROR_STRUCT}.Unwrap`,
|
|
1997
|
+
[{ name: "e", type: errPtr }],
|
|
1998
|
+
ANY_T,
|
|
1999
|
+
"error_cause",
|
|
2000
|
+
{
|
|
2001
|
+
_isMethod: true,
|
|
2002
|
+
_recvTypeName: ERROR_STRUCT,
|
|
2003
|
+
_methodName: "Unwrap",
|
|
2004
|
+
},
|
|
2005
|
+
),
|
|
2006
|
+
helper(
|
|
2007
|
+
"__mk_error",
|
|
2008
|
+
[
|
|
2009
|
+
{ name: "msg", type: STR_T },
|
|
2010
|
+
{ name: "cause", type: ANY_T },
|
|
2011
|
+
],
|
|
2012
|
+
ANY_T,
|
|
2013
|
+
"mk_error",
|
|
2014
|
+
),
|
|
2015
|
+
helper("__error_str", [{ name: "e", type: ANY_T }], STR_T, "error_str"),
|
|
2016
|
+
{
|
|
2017
|
+
kind: "FuncDecl",
|
|
2018
|
+
name: "__errors_unwrap",
|
|
2019
|
+
params: [{ name: "__recv", type: ANY_T }],
|
|
2020
|
+
returnType: ANY_T,
|
|
2021
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
2022
|
+
_isInterfaceDispatcher: true,
|
|
2023
|
+
_softDispatch: true,
|
|
2024
|
+
_ifaceName: "error",
|
|
2025
|
+
_ifaceType: null,
|
|
2026
|
+
_methodName: "Unwrap",
|
|
2027
|
+
_rootFuncDecl: null,
|
|
2028
|
+
},
|
|
2029
|
+
helper(
|
|
2030
|
+
"__errors_is",
|
|
2031
|
+
[
|
|
2032
|
+
{ name: "err", type: ANY_T },
|
|
2033
|
+
{ name: "target", type: ANY_T },
|
|
2034
|
+
],
|
|
2035
|
+
BOOL_T,
|
|
2036
|
+
"errors_is",
|
|
2037
|
+
),
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
if (mod.usesTestingRun) {
|
|
2041
|
+
funcDecls.push(
|
|
2042
|
+
helper(
|
|
2043
|
+
"__testing_run_cb",
|
|
2044
|
+
[
|
|
2045
|
+
{ name: "fn", type: ANY_T },
|
|
2046
|
+
{
|
|
2047
|
+
name: "t",
|
|
2048
|
+
type: {
|
|
2049
|
+
kind: "pointer",
|
|
2050
|
+
base: { kind: "named", name: "testing.T" },
|
|
2051
|
+
},
|
|
2052
|
+
},
|
|
2053
|
+
],
|
|
2054
|
+
null,
|
|
2055
|
+
"testing_run_cb",
|
|
2056
|
+
{ _forceExport: true },
|
|
2057
|
+
),
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// 1f. Boundary metadata (exported surface of wasm packages)
|
|
960
2062
|
const boundaryMeta = options.boundary
|
|
961
2063
|
? collectBoundaryMeta(progs, funcDecls, mod)
|
|
962
2064
|
: null;
|
|
@@ -1009,14 +2111,19 @@ export function compileWasmModule(
|
|
|
1009
2111
|
const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
|
|
1010
2112
|
fn._typeIndex = typeIndex;
|
|
1011
2113
|
fn._globalFuncIndex = globalIdx;
|
|
2114
|
+
mod.funcReturnTypesByIdx.set(globalIdx, returnTypes);
|
|
1012
2115
|
|
|
1013
2116
|
// Export if public (capitalized) or main
|
|
1014
2117
|
if (
|
|
1015
|
-
|
|
1016
|
-
(
|
|
1017
|
-
fn.
|
|
1018
|
-
(
|
|
1019
|
-
|
|
2118
|
+
fn._forceExport ||
|
|
2119
|
+
(!fn._isGlobalInit &&
|
|
2120
|
+
!fn._isRuntimeHelper &&
|
|
2121
|
+
(options.exportAll ||
|
|
2122
|
+
fn.name === "main" ||
|
|
2123
|
+
(fn.name[0] >= "A" && fn.name[0] <= "Z") ||
|
|
2124
|
+
(fn._isMethod &&
|
|
2125
|
+
fn._methodName[0] >= "A" &&
|
|
2126
|
+
fn._methodName[0] <= "Z")))
|
|
1020
2127
|
) {
|
|
1021
2128
|
mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
|
|
1022
2129
|
if (fn._isMethod && fn._exportName) {
|
|
@@ -1027,6 +2134,16 @@ export function compileWasmModule(
|
|
|
1027
2134
|
});
|
|
1028
2135
|
}
|
|
1029
2136
|
}
|
|
2137
|
+
if (
|
|
2138
|
+
fn._isPushPanic &&
|
|
2139
|
+
!mod.exports.some((e) => e.name === "__push_panic")
|
|
2140
|
+
) {
|
|
2141
|
+
mod.exports.push({
|
|
2142
|
+
name: "__push_panic",
|
|
2143
|
+
kind: "func",
|
|
2144
|
+
index: globalIdx,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
1030
2147
|
}
|
|
1031
2148
|
|
|
1032
2149
|
// 4. Emit function bodies
|
|
@@ -1037,7 +2154,29 @@ export function compileWasmModule(
|
|
|
1037
2154
|
const funcIdx = mod.getStringImportIndex();
|
|
1038
2155
|
emitter.pushInstruction({ op: "i32.const", value: strIdx });
|
|
1039
2156
|
emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
|
|
2157
|
+
emitter.pushInstruction("any.convert_extern");
|
|
1040
2158
|
emitter.emitPanicThrow();
|
|
2159
|
+
} else if (fn._isPushPanic) {
|
|
2160
|
+
const panicNodeTypeIndex = mod.getPanicNodeTypeIndex();
|
|
2161
|
+
const panicGlobal = mod.getPanicGlobalIndex();
|
|
2162
|
+
emitter.pushInstruction({ op: "local.get", index: 0 });
|
|
2163
|
+
emitter.pushInstruction("any.convert_extern");
|
|
2164
|
+
emitter.pushInstruction({ op: "i32.const", value: 0 });
|
|
2165
|
+
emitter.pushInstruction({ op: "global.get", index: panicGlobal });
|
|
2166
|
+
emitter.pushInstruction({
|
|
2167
|
+
op: "struct.new",
|
|
2168
|
+
typeIndex: panicNodeTypeIndex,
|
|
2169
|
+
});
|
|
2170
|
+
emitter.pushInstruction({ op: "global.set", index: panicGlobal });
|
|
2171
|
+
emitter.pushInstruction("return");
|
|
2172
|
+
} else if (fn._isInterfaceDispatcher) {
|
|
2173
|
+
emitter.emitInterfaceDispatcher(fn);
|
|
2174
|
+
} else if (fn._isMapHelper) {
|
|
2175
|
+
emitter.emitMapHelper(fn);
|
|
2176
|
+
} else if (fn._isSharedHelper) {
|
|
2177
|
+
emitter.emitSharedHelper(fn);
|
|
2178
|
+
} else if (fn._isRuntimeHelper) {
|
|
2179
|
+
emitter.emitRuntimeHelper(fn);
|
|
1041
2180
|
} else if (fn.body) {
|
|
1042
2181
|
emitter.emitFunctionBody(fn.body);
|
|
1043
2182
|
}
|
|
@@ -1061,6 +2200,10 @@ export function compileWasmModule(
|
|
|
1061
2200
|
typeIndex: fn._typeIndex,
|
|
1062
2201
|
locals: emitter.localTypes,
|
|
1063
2202
|
body,
|
|
2203
|
+
src:
|
|
2204
|
+
fn._srcFile && typeof fn._line === "number"
|
|
2205
|
+
? { file: fn._srcFile, line: fn._line }
|
|
2206
|
+
: null,
|
|
1064
2207
|
});
|
|
1065
2208
|
}
|
|
1066
2209
|
|
|
@@ -1071,6 +2214,7 @@ export function compileWasmModule(
|
|
|
1071
2214
|
facade = generateFacade(boundaryMeta, {
|
|
1072
2215
|
stringTable: mod.stringTable,
|
|
1073
2216
|
callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
|
|
2217
|
+
stdlibEnv: mod.usesStdlibEnv,
|
|
1074
2218
|
});
|
|
1075
2219
|
}
|
|
1076
2220
|
|
|
@@ -1082,18 +2226,41 @@ export function compileWasmModule(
|
|
|
1082
2226
|
elements: mod.elements,
|
|
1083
2227
|
funcs: mod.funcs,
|
|
1084
2228
|
exports: mod.exports,
|
|
2229
|
+
memory: mod.memory ?? null,
|
|
1085
2230
|
start: globalInitFn ? globalInitFn._globalFuncIndex : null,
|
|
2231
|
+
sourceMappingURL: options.sourceMappingURL ?? null,
|
|
1086
2232
|
};
|
|
1087
2233
|
|
|
1088
2234
|
const wasmBytes = encodeModule(moduleIR);
|
|
1089
2235
|
const watText = options.emitWat ? emitWat(moduleIR) : null;
|
|
1090
2236
|
|
|
2237
|
+
// Function-level source positions: `sources` are the absolute Go files,
|
|
2238
|
+
// `mappings` pair each function body's byte offset with its 0-based line.
|
|
2239
|
+
const sources = [];
|
|
2240
|
+
const sourceIndex = new Map();
|
|
2241
|
+
const mappings = [];
|
|
2242
|
+
moduleIR.funcs.forEach((fn, i) => {
|
|
2243
|
+
if (!fn.src) return;
|
|
2244
|
+
let idx = sourceIndex.get(fn.src.file);
|
|
2245
|
+
if (idx === undefined) {
|
|
2246
|
+
idx = sources.length;
|
|
2247
|
+
sources.push(fn.src.file);
|
|
2248
|
+
sourceIndex.set(fn.src.file, idx);
|
|
2249
|
+
}
|
|
2250
|
+
mappings.push({
|
|
2251
|
+
offset: moduleIR.codeOffsets[i],
|
|
2252
|
+
srcFileIdx: idx,
|
|
2253
|
+
srcLine: Math.max(0, fn.src.line - 1),
|
|
2254
|
+
});
|
|
2255
|
+
});
|
|
2256
|
+
|
|
1091
2257
|
return {
|
|
1092
2258
|
wasm: wasmBytes,
|
|
1093
2259
|
wat: watText,
|
|
1094
2260
|
stringTable: mod.stringTable,
|
|
1095
2261
|
moduleIR,
|
|
1096
2262
|
facade,
|
|
2263
|
+
sourceMapInfo: { sources, mappings },
|
|
1097
2264
|
};
|
|
1098
2265
|
}
|
|
1099
2266
|
|
|
@@ -1122,6 +2289,50 @@ function _collectPackageConsts(progs, mod) {
|
|
|
1122
2289
|
}
|
|
1123
2290
|
}
|
|
1124
2291
|
|
|
2292
|
+
// Depth-first search over AST nodes (skipping `_`-prefixed annotations).
|
|
2293
|
+
function _someNode(node, pred) {
|
|
2294
|
+
if (!node || typeof node !== "object") return false;
|
|
2295
|
+
if (Array.isArray(node)) return node.some((n) => _someNode(n, pred));
|
|
2296
|
+
if (pred(node)) return true;
|
|
2297
|
+
for (const key of Object.keys(node)) {
|
|
2298
|
+
if (key.startsWith("_")) continue;
|
|
2299
|
+
if (_someNode(node[key], pred)) return true;
|
|
2300
|
+
}
|
|
2301
|
+
return false;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
function _usesTestingRun(node) {
|
|
2305
|
+
return _someNode(
|
|
2306
|
+
node,
|
|
2307
|
+
(n) =>
|
|
2308
|
+
n.kind === "CallExpr" &&
|
|
2309
|
+
n.func?.kind === "SelectorExpr" &&
|
|
2310
|
+
n.func.field === "Run" &&
|
|
2311
|
+
isTestingT(n.func.expr?._type),
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// `__any_eq` is only needed when some `==`/`!=` (or switch case) compares
|
|
2316
|
+
// anyref operands, a struct with an anyref field is compared, or the error
|
|
2317
|
+
// runtime (`errors.Is`) is present. `_emitAnyEq` throws if this misses a
|
|
2318
|
+
// site, so a gap surfaces as a compile error rather than a bad module.
|
|
2319
|
+
function _usesAnyEq(funcDecls, mod) {
|
|
2320
|
+
if (mod.hasErrorType) return true;
|
|
2321
|
+
for (const info of mod.structTypes.values()) {
|
|
2322
|
+
if (info.fields?.some((f) => f.wType === "anyref")) return true;
|
|
2323
|
+
}
|
|
2324
|
+
const isAnyRef = (n) =>
|
|
2325
|
+
Boolean(n?._type) && toWasmType(n._type, mod.checker, mod) === "anyref";
|
|
2326
|
+
return funcDecls.some((fn) =>
|
|
2327
|
+
_someNode(fn.body, (n) => {
|
|
2328
|
+
if (n.kind === "BinaryExpr" && (n.op === "==" || n.op === "!="))
|
|
2329
|
+
return isAnyRef(n.left) || isAnyRef(n.right);
|
|
2330
|
+
if (n.kind === "SwitchStmt" && n.tag) return isAnyRef(n.tag);
|
|
2331
|
+
return false;
|
|
2332
|
+
}),
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
1125
2336
|
function _scanImportsInBody(node, mod) {
|
|
1126
2337
|
if (!node || typeof node !== "object") return;
|
|
1127
2338
|
|
|
@@ -1158,6 +2369,9 @@ function _scanImportsInBody(node, mod) {
|
|
|
1158
2369
|
Atan: "atan",
|
|
1159
2370
|
Atan2: "atan2",
|
|
1160
2371
|
Pow: "pow",
|
|
2372
|
+
Mod: "mod",
|
|
2373
|
+
Hypot: "hypot",
|
|
2374
|
+
Cbrt: "cbrt",
|
|
1161
2375
|
Exp: "exp",
|
|
1162
2376
|
Log: "log",
|
|
1163
2377
|
Log2: "log2",
|
|
@@ -1165,7 +2379,7 @@ function _scanImportsInBody(node, mod) {
|
|
|
1165
2379
|
Round: "round",
|
|
1166
2380
|
};
|
|
1167
2381
|
if (jsMathMap[func.field]) {
|
|
1168
|
-
const isBinary =
|
|
2382
|
+
const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
|
|
1169
2383
|
mod.getMathImportIndex(jsMathMap[func.field], isBinary);
|
|
1170
2384
|
}
|
|
1171
2385
|
} else if (
|
|
@@ -1177,7 +2391,10 @@ function _scanImportsInBody(node, mod) {
|
|
|
1177
2391
|
if (func.field === "Name") mod.getTestingNameImportIndex();
|
|
1178
2392
|
else if (func.field === "Failed" || func.field === "Skipped")
|
|
1179
2393
|
mod.getTestingFlagImportIndex();
|
|
1180
|
-
else if (func.field
|
|
2394
|
+
else if (func.field === "Run") {
|
|
2395
|
+
// closure type for func(*testing.T) must exist before bodies are emitted
|
|
2396
|
+
mod.getTestingRunClosureType();
|
|
2397
|
+
} else {
|
|
1181
2398
|
mod.getTestingCallImportIndex();
|
|
1182
2399
|
for (const arg of args) {
|
|
1183
2400
|
mod.getTestingArgImportIndex(
|
|
@@ -1231,6 +2448,46 @@ export function compileWasm(source, options = {}) {
|
|
|
1231
2448
|
return { ...res, errors: [] };
|
|
1232
2449
|
}
|
|
1233
2450
|
|
|
2451
|
+
function _isPointerGoType(t, mod) {
|
|
2452
|
+
if (!t) return false;
|
|
2453
|
+
if (t.kind === "pointer" || t.kind === "PointerType" || t.kind === "StarExpr")
|
|
2454
|
+
return true;
|
|
2455
|
+
// `type P *T` used as a field type: look through the name.
|
|
2456
|
+
const named =
|
|
2457
|
+
t.kind === "named" || t.kind === "TypeName" || t.kind === "Ident";
|
|
2458
|
+
const under = named
|
|
2459
|
+
? (t.underlying ?? mod.checker?.types?.get(t.name)?.underlying)
|
|
2460
|
+
: null;
|
|
2461
|
+
return under ? _isPointerGoType(under, mod) : false;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
// Constant-expression zero value of a struct global. Nested struct values are
|
|
2465
|
+
// built in place: a null ref would trap on the first field access.
|
|
2466
|
+
function _zeroStructConst(structInfo, mod) {
|
|
2467
|
+
const out = [];
|
|
2468
|
+
for (const f of structInfo.fields) out.push(..._zeroFieldConst(f, mod));
|
|
2469
|
+
out.push({ op: "struct.new", typeIndex: structInfo.typeIndex });
|
|
2470
|
+
return out;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
function _zeroFieldConst(f, mod) {
|
|
2474
|
+
const w = f.wType;
|
|
2475
|
+
if (w === "i64") return [{ op: "i64.const", value: 0n }];
|
|
2476
|
+
if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
|
|
2477
|
+
if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
|
|
2478
|
+
if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
|
|
2479
|
+
if (typeof w !== "object" || w === null)
|
|
2480
|
+
return [{ op: "i32.const", value: 0 }];
|
|
2481
|
+
const ti = w.typeIndex;
|
|
2482
|
+
const slice = typeof ti === "number" ? mod.getSliceTypeByIndex(ti) : null;
|
|
2483
|
+
if (slice) return [{ op: "global.get", index: slice.emptyGlobalIndex }];
|
|
2484
|
+
if (typeof ti === "number" && !_isPointerGoType(f.goType, mod)) {
|
|
2485
|
+
for (const s of mod.structTypes.values())
|
|
2486
|
+
if (s.typeIndex === ti) return _zeroStructConst(s, mod);
|
|
2487
|
+
}
|
|
2488
|
+
return [{ op: "ref.null", heapType: ti ?? "any" }];
|
|
2489
|
+
}
|
|
2490
|
+
|
|
1234
2491
|
function _collectPackageGlobals(progs, mod, checker) {
|
|
1235
2492
|
const pending = []; // non-constant initializers, run by the start function
|
|
1236
2493
|
for (const p of progs) {
|
|
@@ -1270,34 +2527,7 @@ function _collectPackageGlobals(progs, mod, checker) {
|
|
|
1270
2527
|
(s) => s.typeIndex === wType.typeIndex,
|
|
1271
2528
|
);
|
|
1272
2529
|
if (structInfo) {
|
|
1273
|
-
initInsts =
|
|
1274
|
-
for (const f of structInfo.fields) {
|
|
1275
|
-
if (f.wType === "i64")
|
|
1276
|
-
initInsts.push({ op: "i64.const", value: 0n });
|
|
1277
|
-
else if (f.wType === "f32" || f.wType === "f64")
|
|
1278
|
-
initInsts.push({ op: `${f.wType}.const`, value: 0.0 });
|
|
1279
|
-
else if (typeof f.wType === "object") {
|
|
1280
|
-
const fieldSlice =
|
|
1281
|
-
typeof f.wType.typeIndex === "number"
|
|
1282
|
-
? mod.getSliceTypeByIndex(f.wType.typeIndex)
|
|
1283
|
-
: null;
|
|
1284
|
-
if (fieldSlice) {
|
|
1285
|
-
initInsts.push({
|
|
1286
|
-
op: "global.get",
|
|
1287
|
-
index: fieldSlice.emptyGlobalIndex,
|
|
1288
|
-
});
|
|
1289
|
-
} else {
|
|
1290
|
-
initInsts.push({
|
|
1291
|
-
op: "ref.null",
|
|
1292
|
-
heapType: f.wType.typeIndex ?? "any",
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
} else initInsts.push({ op: "i32.const", value: 0 });
|
|
1296
|
-
}
|
|
1297
|
-
initInsts.push({
|
|
1298
|
-
op: "struct.new",
|
|
1299
|
-
typeIndex: structInfo.typeIndex,
|
|
1300
|
-
});
|
|
2530
|
+
initInsts = _zeroStructConst(structInfo, mod);
|
|
1301
2531
|
} else {
|
|
1302
2532
|
initInsts = [
|
|
1303
2533
|
{ op: "ref.null", heapType: wType.typeIndex ?? "any" },
|
|
@@ -1411,3 +2641,150 @@ function _makeGlobalInitFunc(pending, initNames = []) {
|
|
|
1411
2641
|
_isGlobalInit: true,
|
|
1412
2642
|
};
|
|
1413
2643
|
}
|
|
2644
|
+
|
|
2645
|
+
function _scanInterfaceCallNodes(node, interfaceCalls, checker) {
|
|
2646
|
+
if (!node || typeof node !== "object") return;
|
|
2647
|
+
if (Array.isArray(node)) {
|
|
2648
|
+
for (const item of node)
|
|
2649
|
+
_scanInterfaceCallNodes(item, interfaceCalls, checker);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
|
|
2653
|
+
const recvType = node.func.expr?._type;
|
|
2654
|
+
if (isNonEmptyInterface(recvType, checker)) {
|
|
2655
|
+
const ifaceName = getReceiverTypeName(recvType, node.func.expr) || "anon";
|
|
2656
|
+
const methodName = node.func.field;
|
|
2657
|
+
const key = `${ifaceName}.${methodName}`;
|
|
2658
|
+
if (!interfaceCalls.has(key)) {
|
|
2659
|
+
interfaceCalls.set(key, {
|
|
2660
|
+
ifaceName,
|
|
2661
|
+
ifaceType: recvType,
|
|
2662
|
+
methodName,
|
|
2663
|
+
callNode: node,
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
for (const key of Object.keys(node)) {
|
|
2669
|
+
if (!key.startsWith("_"))
|
|
2670
|
+
_scanInterfaceCallNodes(node[key], interfaceCalls, checker);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function _scanInterfaceTypeDecls(progs, interfaceCalls, checker) {
|
|
2675
|
+
for (const p of progs) {
|
|
2676
|
+
for (const d of p.decls ?? []) {
|
|
2677
|
+
if (d.kind !== "TypeDecl") continue;
|
|
2678
|
+
const resolved = checker?.types?.get(d.name) ?? d.type;
|
|
2679
|
+
const underlying =
|
|
2680
|
+
resolved?.kind === "named"
|
|
2681
|
+
? resolved.underlying
|
|
2682
|
+
: d.type?.kind === "InterfaceType"
|
|
2683
|
+
? d.type
|
|
2684
|
+
: resolved;
|
|
2685
|
+
if (!isNonEmptyInterface(underlying, checker)) continue;
|
|
2686
|
+
const methods =
|
|
2687
|
+
underlying.methods instanceof Map
|
|
2688
|
+
? Array.from(underlying.methods.entries())
|
|
2689
|
+
: Array.isArray(underlying.methods)
|
|
2690
|
+
? underlying.methods.map((m) => [m.name ?? m, m])
|
|
2691
|
+
: [];
|
|
2692
|
+
for (const [mName, mSig] of methods) {
|
|
2693
|
+
const key = `${d.name}.${mName}`;
|
|
2694
|
+
if (!interfaceCalls.has(key)) {
|
|
2695
|
+
interfaceCalls.set(key, {
|
|
2696
|
+
ifaceName: d.name,
|
|
2697
|
+
ifaceType: resolved,
|
|
2698
|
+
methodName: mName,
|
|
2699
|
+
methodSig: mSig,
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function _makeInterfaceDispatcher(info, checker) {
|
|
2708
|
+
let methodSig = info.methodSig;
|
|
2709
|
+
if (!methodSig) {
|
|
2710
|
+
const ifaceResolved =
|
|
2711
|
+
checker?.resolveType?.(
|
|
2712
|
+
info.ifaceType?.kind === "named"
|
|
2713
|
+
? info.ifaceType.underlying
|
|
2714
|
+
: info.ifaceType,
|
|
2715
|
+
) ?? info.ifaceType;
|
|
2716
|
+
if (ifaceResolved?.methods instanceof Map) {
|
|
2717
|
+
methodSig = ifaceResolved.methods.get(info.methodName);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
const params = [{ name: "__recv", type: { kind: "basic", name: "any" } }];
|
|
2722
|
+
if (methodSig?.params) {
|
|
2723
|
+
for (let p = 0; p < methodSig.params.length; p++) {
|
|
2724
|
+
params.push({ name: `__arg${p}`, type: methodSig.params[p] });
|
|
2725
|
+
}
|
|
2726
|
+
} else if (info.callNode?.args) {
|
|
2727
|
+
for (let p = 0; p < info.callNode.args.length; p++) {
|
|
2728
|
+
params.push({
|
|
2729
|
+
name: `__arg${p}`,
|
|
2730
|
+
type: info.callNode.args[p]._type ?? {
|
|
2731
|
+
kind: "basic",
|
|
2732
|
+
name: "any",
|
|
2733
|
+
},
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
let returnType = info.callNode?._type ?? null;
|
|
2739
|
+
if (methodSig?.returns) {
|
|
2740
|
+
returnType =
|
|
2741
|
+
methodSig.returns.length === 1
|
|
2742
|
+
? methodSig.returns[0]
|
|
2743
|
+
: methodSig.returns.length > 1
|
|
2744
|
+
? { kind: "tuple", types: methodSig.returns }
|
|
2745
|
+
: null;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
return {
|
|
2749
|
+
kind: "FuncDecl",
|
|
2750
|
+
name: `__dispatch_${info.ifaceName}_${info.methodName}`,
|
|
2751
|
+
params,
|
|
2752
|
+
returnType,
|
|
2753
|
+
body: { kind: "Block", stmts: [], list: [] },
|
|
2754
|
+
_isInterfaceDispatcher: true,
|
|
2755
|
+
_ifaceName: info.ifaceName,
|
|
2756
|
+
_ifaceType: info.ifaceType,
|
|
2757
|
+
_methodName: info.methodName,
|
|
2758
|
+
_rootFuncDecl: null,
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
|
|
2763
|
+
const interfaceCalls = new Map();
|
|
2764
|
+
for (const fn of funcDecls) {
|
|
2765
|
+
if (fn.body) _scanInterfaceCallNodes(fn.body, interfaceCalls, checker);
|
|
2766
|
+
}
|
|
2767
|
+
_scanInterfaceTypeDecls(progs, interfaceCalls, checker);
|
|
2768
|
+
|
|
2769
|
+
// `error.Error()` is reached implicitly by fmt verbs and errors.Is, so
|
|
2770
|
+
// the dispatcher must exist even when user code never calls it.
|
|
2771
|
+
if (mod.hasErrorType && !interfaceCalls.has("error.Error")) {
|
|
2772
|
+
interfaceCalls.set("error.Error", {
|
|
2773
|
+
ifaceName: "error",
|
|
2774
|
+
ifaceType: checker?.types?.get("error") ?? {
|
|
2775
|
+
kind: "interface",
|
|
2776
|
+
name: "error",
|
|
2777
|
+
},
|
|
2778
|
+
methodName: "Error",
|
|
2779
|
+
methodSig: {
|
|
2780
|
+
kind: "func",
|
|
2781
|
+
params: [],
|
|
2782
|
+
returns: [{ kind: "basic", name: "string" }],
|
|
2783
|
+
},
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
for (const info of interfaceCalls.values()) {
|
|
2788
|
+
funcDecls.push(_makeInterfaceDispatcher(info, checker));
|
|
2789
|
+
}
|
|
2790
|
+
}
|