gofront 1.6.1 → 1.8.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 +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -1,21 +1,51 @@
|
|
|
1
1
|
// src/backend/wasm/emit-exprs.js
|
|
2
2
|
// FunctionEmitter mixin: expression emission (literals, operators, indexing, conversions).
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
constFromLit,
|
|
6
|
+
constIntValue,
|
|
7
|
+
constToLit,
|
|
8
|
+
foldConstExpr,
|
|
9
|
+
} from "../../typechecker/const-fold.js";
|
|
10
|
+
import {
|
|
11
|
+
compositeLitIndices,
|
|
12
|
+
compositeLitMaxIndex,
|
|
13
|
+
} from "../../typechecker/types.js";
|
|
5
14
|
import {
|
|
6
15
|
getMapKeyValTypes,
|
|
7
16
|
isAnyType,
|
|
8
17
|
isArrayType,
|
|
18
|
+
isComplex,
|
|
19
|
+
isComplex64,
|
|
9
20
|
isFuncType,
|
|
10
21
|
isMapType,
|
|
11
22
|
isPointerToStruct,
|
|
23
|
+
isRuneSlice,
|
|
24
|
+
isScalarType,
|
|
12
25
|
isSigned,
|
|
13
26
|
isSliceType,
|
|
14
27
|
isStringType,
|
|
15
28
|
isStructType,
|
|
29
|
+
isTestingT,
|
|
16
30
|
toWasmType,
|
|
17
31
|
} from "./types.js";
|
|
18
32
|
|
|
33
|
+
// Engine limit on `array.new_fixed` operands.
|
|
34
|
+
const MAX_NEW_FIXED = 10000;
|
|
35
|
+
|
|
36
|
+
// An untyped integral float constant (`1e18 + 1`) in an integer context is
|
|
37
|
+
// emitted from its exact integer value.
|
|
38
|
+
function constLitFor(val, targetWasmType) {
|
|
39
|
+
const i = constIntValue(val);
|
|
40
|
+
if (
|
|
41
|
+
val.k === "FLOAT" &&
|
|
42
|
+
i !== null &&
|
|
43
|
+
(targetWasmType === "i64" || targetWasmType === "i32")
|
|
44
|
+
)
|
|
45
|
+
return constToLit({ k: "INT", v: i });
|
|
46
|
+
return constToLit(val);
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
export class ExprsEmitter {
|
|
20
50
|
emitExpr(expr, targetWasmType = null) {
|
|
21
51
|
if (!expr) return;
|
|
@@ -36,7 +66,7 @@ export class ExprsEmitter {
|
|
|
36
66
|
}
|
|
37
67
|
|
|
38
68
|
if (
|
|
39
|
-
isStringType(goType, this.mod.checker) ||
|
|
69
|
+
(isStringType(goType, this.mod.checker) && goType?.kind !== "named") ||
|
|
40
70
|
(expr.kind === "BasicLit" && expr.litKind === "STRING")
|
|
41
71
|
) {
|
|
42
72
|
this._emitRawExpr(expr, "externref");
|
|
@@ -50,6 +80,7 @@ export class ExprsEmitter {
|
|
|
50
80
|
// for type switches and assertions.
|
|
51
81
|
if (isPointerToStruct(goType, this.mod.checker, this.mod)) {
|
|
52
82
|
this._emitRawExpr(expr, this.toWasmType(goType));
|
|
83
|
+
this._emitNilAwareAny(goType, expr);
|
|
53
84
|
return;
|
|
54
85
|
}
|
|
55
86
|
if (isStructType(goType, this.mod.checker, this.mod)) {
|
|
@@ -78,6 +109,14 @@ export class ExprsEmitter {
|
|
|
78
109
|
) {
|
|
79
110
|
const refWType = this.toWasmType(goType);
|
|
80
111
|
this._emitRawExpr(expr, refWType);
|
|
112
|
+
// An array stored in an interface is a copy.
|
|
113
|
+
if (isArrayType(goType, this.mod.checker))
|
|
114
|
+
this._emitCopyIfValue(expr, goType, refWType);
|
|
115
|
+
if (
|
|
116
|
+
isFuncType(goType, this.mod.checker) ||
|
|
117
|
+
isMapType(goType, this.mod.checker)
|
|
118
|
+
)
|
|
119
|
+
this._emitNilAwareAny(goType, expr);
|
|
81
120
|
return;
|
|
82
121
|
}
|
|
83
122
|
|
|
@@ -92,6 +131,23 @@ export class ExprsEmitter {
|
|
|
92
131
|
this._emitRawExpr(expr, targetWasmType);
|
|
93
132
|
}
|
|
94
133
|
|
|
134
|
+
// The nullable ref on the stack becomes an interface value: a nil map, func
|
|
135
|
+
// or pointer turns into the marker of its type (never a nil interface).
|
|
136
|
+
_emitNilAwareAny(goType, expr) {
|
|
137
|
+
if (expr.kind === "UnaryExpr" && expr.op === "&") return;
|
|
138
|
+
if (expr.kind === "CompositeLit" || expr.kind === "FuncLit") return;
|
|
139
|
+
const nilBox = this.mod.getNilBoxType(goType);
|
|
140
|
+
const tmp = this.acquireTemp(this.toWasmType(goType));
|
|
141
|
+
this.pushInstruction({ op: "local.tee", index: tmp });
|
|
142
|
+
this.pushInstruction("ref.is_null");
|
|
143
|
+
this.pushInstruction({ op: "if", blockType: "anyref" });
|
|
144
|
+
this.pushInstruction({ op: "global.get", index: nilBox.globalIndex });
|
|
145
|
+
this.pushInstruction("else");
|
|
146
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
147
|
+
this.pushInstruction("end");
|
|
148
|
+
this.releaseTemp(tmp, this.toWasmType(goType));
|
|
149
|
+
}
|
|
150
|
+
|
|
95
151
|
_emitBoxValue(box) {
|
|
96
152
|
this.pushInstruction({
|
|
97
153
|
op: "ref.null",
|
|
@@ -116,6 +172,9 @@ export class ExprsEmitter {
|
|
|
116
172
|
return { kind: "nil", name: "nil" };
|
|
117
173
|
}
|
|
118
174
|
}
|
|
175
|
+
if (expr.kind === "ImagLit") {
|
|
176
|
+
return { kind: "untyped", base: "complex128" };
|
|
177
|
+
}
|
|
119
178
|
if (expr.kind === "Ident") {
|
|
120
179
|
const local = this.resolveLocal(expr.name);
|
|
121
180
|
if (local) return local.goType;
|
|
@@ -128,23 +187,46 @@ export class ExprsEmitter {
|
|
|
128
187
|
return null;
|
|
129
188
|
}
|
|
130
189
|
|
|
190
|
+
_emitLocalConst(ident, targetWasmType) {
|
|
191
|
+
const lit = constLitFor(ident._const, targetWasmType);
|
|
192
|
+
if (!lit) return false;
|
|
193
|
+
lit._type = ident._type;
|
|
194
|
+
this.emitBasicLit(lit, targetWasmType);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
131
198
|
_emitRawExpr(expr, targetWasmType = null) {
|
|
132
199
|
if (expr.kind === "BinaryExpr" || expr.kind === "UnaryExpr") {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
200
|
+
// The checker's exact value (src/typechecker/const-fold.js) when it has one.
|
|
201
|
+
const folded = expr._const
|
|
202
|
+
? constLitFor(expr._const, targetWasmType)
|
|
203
|
+
: foldConstExpr(expr, (name) => this.mod.pkgConsts?.get(name));
|
|
136
204
|
if (folded) {
|
|
137
205
|
folded._type = expr._type;
|
|
138
206
|
this.emitBasicLit(folded, targetWasmType);
|
|
139
207
|
return;
|
|
140
208
|
}
|
|
141
209
|
}
|
|
210
|
+
// A function-local constant is a local slot (so closures can capture it)
|
|
211
|
+
// but reads as the checker's exact value, which adapts to the context.
|
|
212
|
+
if (
|
|
213
|
+
expr.kind === "Ident" &&
|
|
214
|
+
expr._const &&
|
|
215
|
+
this.resolveLocal(expr.name) &&
|
|
216
|
+
this._emitLocalConst(expr, targetWasmType)
|
|
217
|
+
)
|
|
218
|
+
return;
|
|
219
|
+
if (this._emitFoldedIntConversion(expr, targetWasmType)) return;
|
|
142
220
|
|
|
143
221
|
switch (expr.kind) {
|
|
144
222
|
case "BasicLit":
|
|
145
223
|
this.emitBasicLit(expr, targetWasmType);
|
|
146
224
|
break;
|
|
147
225
|
|
|
226
|
+
case "ImagLit":
|
|
227
|
+
this.emitImagLit(expr, targetWasmType);
|
|
228
|
+
break;
|
|
229
|
+
|
|
148
230
|
case "Ident":
|
|
149
231
|
this.emitIdent(expr, targetWasmType);
|
|
150
232
|
break;
|
|
@@ -172,20 +254,7 @@ export class ExprsEmitter {
|
|
|
172
254
|
case "SelectorExpr": {
|
|
173
255
|
const deq = this._dequalify(expr);
|
|
174
256
|
if (deq) this.emitIdent(deq, targetWasmType);
|
|
175
|
-
else if (this.
|
|
176
|
-
this.emitMathConst(expr.field, targetWasmType);
|
|
177
|
-
else if (this._isUtf8Const(expr))
|
|
178
|
-
this.emitUtf8Const(expr.field, targetWasmType);
|
|
179
|
-
else if (this._isStrconvConst(expr))
|
|
180
|
-
this.emitStrconvConst(expr.field, targetWasmType);
|
|
181
|
-
else if (this._isPathConst(expr))
|
|
182
|
-
this.emitPathConst(expr.field, targetWasmType);
|
|
183
|
-
else if (this._isIoConst(expr))
|
|
184
|
-
this.emitIoConst(expr.field, targetWasmType);
|
|
185
|
-
else if (this._isTimeConst(expr))
|
|
186
|
-
this.emitTimeConst(expr.field, targetWasmType);
|
|
187
|
-
else if (this._isExtendedPkgConst?.(expr))
|
|
188
|
-
this.emitExtendedPkgConst(expr, targetWasmType);
|
|
257
|
+
else if (this._emitPkgConst(expr, targetWasmType)) break;
|
|
189
258
|
else this.emitSelectorExpr(expr);
|
|
190
259
|
break;
|
|
191
260
|
}
|
|
@@ -275,6 +344,17 @@ export class ExprsEmitter {
|
|
|
275
344
|
|
|
276
345
|
emitCompositeLit(lit) {
|
|
277
346
|
const litType = lit._type ?? lit.typeExpr?._type ?? lit.typeExpr;
|
|
347
|
+
if (
|
|
348
|
+
litType?.kind === "simd" ||
|
|
349
|
+
litType?.name === "simd.F32x4" ||
|
|
350
|
+
litType?.underlying?.kind === "simd" ||
|
|
351
|
+
(lit.typeExpr?.kind === "SelectorExpr" &&
|
|
352
|
+
lit.typeExpr.field === "F32x4" &&
|
|
353
|
+
lit.typeExpr.expr?.name === "simd")
|
|
354
|
+
) {
|
|
355
|
+
this.emitSimdCompositeLit(lit);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
278
358
|
if (isMapType(litType, this.mod.checker)) {
|
|
279
359
|
const { keyType, valType } = getMapKeyValTypes(litType, this.mod.checker);
|
|
280
360
|
const mapInfo = this.mod.getMapType(keyType, valType);
|
|
@@ -291,7 +371,7 @@ export class ExprsEmitter {
|
|
|
291
371
|
this.pushInstruction({ op: "local.get", index: mTmp });
|
|
292
372
|
this.emitExpr(elem.key, mapInfo.keyWType);
|
|
293
373
|
this.emitExpr(elem.value, mapInfo.valWType);
|
|
294
|
-
this.
|
|
374
|
+
this._emitCopyIfValue(elem.value, valType, mapInfo.valWType);
|
|
295
375
|
this.pushInstruction({
|
|
296
376
|
op: "call",
|
|
297
377
|
funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
|
|
@@ -304,33 +384,11 @@ export class ExprsEmitter {
|
|
|
304
384
|
}
|
|
305
385
|
|
|
306
386
|
if (isSliceType(litType, this.mod.checker)) {
|
|
307
|
-
const elemType = litType
|
|
387
|
+
const elemType = this._getArrayElemType(litType);
|
|
308
388
|
const sliceInfo = this.mod.getSliceType(elemType);
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
this.pushInstruction({
|
|
313
|
-
op: "array.new_default",
|
|
314
|
-
typeIndex: sliceInfo.arrInfo.typeIndex,
|
|
315
|
-
});
|
|
316
|
-
this.pushInstruction({ op: "i32.const", value: 0 }); // offset
|
|
317
|
-
this.pushInstruction({ op: "i32.const", value: 0 }); // len
|
|
318
|
-
this.pushInstruction({ op: "i32.const", value: 0 }); // cap
|
|
319
|
-
this.pushInstruction({
|
|
320
|
-
op: "struct.new",
|
|
321
|
-
typeIndex: sliceInfo.typeIndex,
|
|
322
|
-
});
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
for (const elem of lit.elems) {
|
|
326
|
-
this.emitExpr(elem, sliceInfo.elemWType);
|
|
327
|
-
this._emitCopyIfValueStruct(elem, elem._type, sliceInfo.elemWType);
|
|
328
|
-
}
|
|
329
|
-
this.pushInstruction({
|
|
330
|
-
op: "array.new_fixed",
|
|
331
|
-
typeIndex: sliceInfo.arrInfo.typeIndex,
|
|
332
|
-
size: count,
|
|
333
|
-
});
|
|
389
|
+
const elems = lit.elems ?? [];
|
|
390
|
+
const count = compositeLitMaxIndex(elems) + 1;
|
|
391
|
+
this._emitArrayLitElems(elems, elemType, sliceInfo.arrInfo, count);
|
|
334
392
|
this.pushInstruction({ op: "i32.const", value: 0 }); // offset
|
|
335
393
|
this.pushInstruction({ op: "i32.const", value: count }); // len
|
|
336
394
|
this.pushInstruction({ op: "i32.const", value: count }); // cap
|
|
@@ -342,22 +400,12 @@ export class ExprsEmitter {
|
|
|
342
400
|
}
|
|
343
401
|
|
|
344
402
|
if (isArrayType(litType, this.mod.checker)) {
|
|
345
|
-
const elemType = litType
|
|
403
|
+
const elemType = this._getArrayElemType(litType);
|
|
346
404
|
const arrInfo = this.mod.getArrayType(elemType);
|
|
347
|
-
const
|
|
405
|
+
const elems = lit.elems ?? [];
|
|
406
|
+
const count = compositeLitMaxIndex(elems) + 1;
|
|
348
407
|
const targetSize = this._getArraySize(litType, count);
|
|
349
|
-
|
|
350
|
-
this.emitExpr(elem, arrInfo.elemWType);
|
|
351
|
-
this._emitCopyIfValueStruct(elem, elem._type, arrInfo.elemWType);
|
|
352
|
-
}
|
|
353
|
-
for (let i = count; i < targetSize; i++) {
|
|
354
|
-
this.emitZeroValue(elemType, arrInfo.elemWType);
|
|
355
|
-
}
|
|
356
|
-
this.pushInstruction({
|
|
357
|
-
op: "array.new_fixed",
|
|
358
|
-
typeIndex: arrInfo.typeIndex,
|
|
359
|
-
size: targetSize,
|
|
360
|
-
});
|
|
408
|
+
this._emitArrayLitElems(elems, elemType, arrInfo, targetSize);
|
|
361
409
|
return;
|
|
362
410
|
}
|
|
363
411
|
|
|
@@ -387,13 +435,13 @@ export class ExprsEmitter {
|
|
|
387
435
|
const f = fields[i];
|
|
388
436
|
if (isKeyed) {
|
|
389
437
|
if (map.has(f.name)) {
|
|
390
|
-
this.
|
|
438
|
+
this._emitFieldInit(map.get(f.name), f);
|
|
391
439
|
} else {
|
|
392
440
|
this.emitZeroValue(f.goType, f.wType);
|
|
393
441
|
}
|
|
394
442
|
} else {
|
|
395
443
|
if (i < (lit.elems?.length ?? 0)) {
|
|
396
|
-
this.
|
|
444
|
+
this._emitFieldInit(lit.elems[i], f);
|
|
397
445
|
} else {
|
|
398
446
|
this.emitZeroValue(f.goType, f.wType);
|
|
399
447
|
}
|
|
@@ -406,9 +454,169 @@ export class ExprsEmitter {
|
|
|
406
454
|
});
|
|
407
455
|
}
|
|
408
456
|
|
|
457
|
+
// Pushes a new array of `size` elements built from literal `elems`.
|
|
458
|
+
// Keyed elements (`{2: 7}`) go to their index; every other slot is the
|
|
459
|
+
// element zero value. With ascending keys and at most
|
|
460
|
+
// MAX_NEW_FIXED elements this is one `array.new_fixed`; otherwise the
|
|
461
|
+
// array is allocated first and the elements are stored in source order
|
|
462
|
+
// (Go evaluates them left to right whatever their keys).
|
|
463
|
+
_emitArrayLitElems(elems, elemType, arrInfo, size) {
|
|
464
|
+
const indices = compositeLitIndices(elems);
|
|
465
|
+
const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
|
|
466
|
+
const value = (e) => (e.kind === "KeyValueExpr" ? e.value : e);
|
|
467
|
+
const emitElem = (e) => {
|
|
468
|
+
this.emitExpr(e, arrInfo.elemWType);
|
|
469
|
+
// Copy by the element type: a struct stored in an `any` element
|
|
470
|
+
// is already boxed, and a keyed element wraps its value.
|
|
471
|
+
this._emitCopyIfValue(e, elemType, arrInfo.elemWType);
|
|
472
|
+
};
|
|
473
|
+
if (ascending && size <= MAX_NEW_FIXED) {
|
|
474
|
+
const slots = new Array(size).fill(null);
|
|
475
|
+
elems.forEach((e, i) => {
|
|
476
|
+
slots[indices[i]] = value(e);
|
|
477
|
+
});
|
|
478
|
+
for (const e of slots) {
|
|
479
|
+
if (e) emitElem(e);
|
|
480
|
+
else this.emitZeroValue(elemType, arrInfo.elemWType);
|
|
481
|
+
}
|
|
482
|
+
this.pushInstruction({
|
|
483
|
+
op: "array.new_fixed",
|
|
484
|
+
typeIndex: arrInfo.typeIndex,
|
|
485
|
+
size,
|
|
486
|
+
});
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
|
|
490
|
+
const arr = this.acquireTemp(wType);
|
|
491
|
+
const set = (idx, emit) => {
|
|
492
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
493
|
+
this.pushInstruction({ op: "i32.const", value: idx });
|
|
494
|
+
emit();
|
|
495
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
496
|
+
};
|
|
497
|
+
this.pushInstruction({ op: "i32.const", value: size });
|
|
498
|
+
this.pushInstruction({
|
|
499
|
+
op: "array.new_default",
|
|
500
|
+
typeIndex: arrInfo.typeIndex,
|
|
501
|
+
});
|
|
502
|
+
this.pushInstruction({ op: "local.set", index: arr });
|
|
503
|
+
// Numeric zero values are the array default; others are stored.
|
|
504
|
+
if (typeof arrInfo.elemWType !== "string") {
|
|
505
|
+
const given = new Set(indices);
|
|
506
|
+
for (let i = 0; i < size; i++)
|
|
507
|
+
if (!given.has(i))
|
|
508
|
+
set(i, () => this.emitZeroValue(elemType, arrInfo.elemWType));
|
|
509
|
+
}
|
|
510
|
+
elems.forEach((e, i) => {
|
|
511
|
+
set(indices[i], () => emitElem(value(e)));
|
|
512
|
+
});
|
|
513
|
+
this.pushInstruction({ op: "local.get", index: arr });
|
|
514
|
+
this.releaseTemp(arr, wType);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// A struct literal field initialiser; array values are copied in.
|
|
518
|
+
_emitFieldInit(value, f) {
|
|
519
|
+
this.emitExpr(value, f.wType);
|
|
520
|
+
if (f.goType?.kind !== "pointer" && isArrayType(f.goType, this.mod.checker))
|
|
521
|
+
this._emitCopyIfValue(value, f.goType, f.wType);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// With a pointer on the stack, branches to the function's `nil` block
|
|
525
|
+
// (_openNilBlock) when it is null. Outside one, the access traps.
|
|
526
|
+
_emitNilCheck(goType) {
|
|
527
|
+
if (goType?.kind !== "pointer") return;
|
|
528
|
+
this._emitNilBranch();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
_emitNilBranch() {
|
|
532
|
+
const depth = this.resolveBranchDepthToRole("nil");
|
|
533
|
+
if (depth == null) return;
|
|
534
|
+
this.pushInstruction({ op: "br_on_null", depth });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Emits the struct (or struct pointer) a field access or store goes
|
|
538
|
+
// through, nil-checked. A `*p` base is the pointee itself, not the copy a
|
|
539
|
+
// value read of `*p` makes, so a store through it lands in the pointee.
|
|
540
|
+
// Every selector read and every field store/op-assign/inc-dec starts here.
|
|
541
|
+
emitSelectorBase(node, wType = null, goType = node._type) {
|
|
542
|
+
const pointer = this._structDerefPointer(node);
|
|
543
|
+
if (pointer) {
|
|
544
|
+
this.emitSelectorBase(pointer);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.emitExpr(node, wType ?? this.toWasmType(goType));
|
|
548
|
+
this._emitNilCheck(goType);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// For `*p` (possibly parenthesised) with `p` pointing at a struct, the
|
|
552
|
+
// pointer `p`, which is the struct's own reference; otherwise null.
|
|
553
|
+
_structDerefPointer(node) {
|
|
554
|
+
let base = node;
|
|
555
|
+
while (base?.kind === "ParenExpr") base = base.expr;
|
|
556
|
+
if (
|
|
557
|
+
(base?.kind !== "UnaryExpr" || base.op !== "*") &&
|
|
558
|
+
base?.kind !== "StarExpr"
|
|
559
|
+
)
|
|
560
|
+
return null;
|
|
561
|
+
const operand = base.operand ?? base.expr;
|
|
562
|
+
return this._derefStructInfo(operand) ? operand : null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_emitPkgConst(expr, targetWasmType) {
|
|
566
|
+
if (this._isMathConst(expr)) {
|
|
567
|
+
this.emitMathConst(expr.field, targetWasmType);
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (this._isUtf8Const(expr)) {
|
|
571
|
+
this.emitUtf8Const(expr.field, targetWasmType);
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
if (this._isStrconvConst(expr)) {
|
|
575
|
+
this.emitStrconvConst(expr.field, targetWasmType);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
if (this._isPathConst(expr)) {
|
|
579
|
+
this.emitPathConst(expr.field, targetWasmType);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
if (this._isIoConst(expr)) {
|
|
583
|
+
this.emitIoConst(expr.field, targetWasmType);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (this._isOsConst?.(expr)) {
|
|
587
|
+
this.emitOsConst(expr.field, targetWasmType);
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
if (this._isTimeConst(expr)) {
|
|
591
|
+
this.emitTimeConst(expr.field, targetWasmType);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
if (this._isBitsConst?.(expr)) {
|
|
595
|
+
this.emitBitsConst(expr.field, targetWasmType);
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
if (this._isExtendedPkgConst?.(expr)) {
|
|
599
|
+
this.emitExtendedPkgConst(expr, targetWasmType);
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
|
|
409
605
|
emitSelectorExpr(expr) {
|
|
606
|
+
if (isTestingT(expr.expr?._type) && expr.field === "N") {
|
|
607
|
+
this.emitExpr(expr.expr, "externref");
|
|
608
|
+
this.pushInstruction({
|
|
609
|
+
op: "call",
|
|
610
|
+
funcIndex: this.mod.getStdlibImportIndex("testing_n"),
|
|
611
|
+
});
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
410
614
|
const structInfo = this._resolveStructInfo(expr.expr);
|
|
411
615
|
if (!structInfo) {
|
|
616
|
+
if (expr.expr?.kind === "Ident" && expr.expr._type?.kind === "namespace")
|
|
617
|
+
throw new Error(
|
|
618
|
+
`${expr.expr.name}.${expr.field} cannot be used as a value in WASM`,
|
|
619
|
+
);
|
|
412
620
|
throw new Error(`Cannot resolve struct for selector: ${expr.field}`);
|
|
413
621
|
}
|
|
414
622
|
const path = this._resolveFieldPath(structInfo, expr.field);
|
|
@@ -418,8 +626,7 @@ export class ExprsEmitter {
|
|
|
418
626
|
);
|
|
419
627
|
}
|
|
420
628
|
|
|
421
|
-
|
|
422
|
-
this.emitExpr(expr.expr, baseWType);
|
|
629
|
+
this.emitSelectorBase(expr.expr);
|
|
423
630
|
|
|
424
631
|
for (let i = 0; i < path.length; i++) {
|
|
425
632
|
const step = path[i];
|
|
@@ -495,11 +702,9 @@ export class ExprsEmitter {
|
|
|
495
702
|
);
|
|
496
703
|
}
|
|
497
704
|
|
|
498
|
-
const elemGoType =
|
|
499
|
-
? baseType.
|
|
500
|
-
|
|
501
|
-
? baseType.base.elem
|
|
502
|
-
: baseType.elem;
|
|
705
|
+
const elemGoType = this._getArrayElemType(
|
|
706
|
+
isPtrToArr ? baseType.base : baseType,
|
|
707
|
+
);
|
|
503
708
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
504
709
|
const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
|
|
505
710
|
|
|
@@ -593,11 +798,9 @@ export class ExprsEmitter {
|
|
|
593
798
|
);
|
|
594
799
|
}
|
|
595
800
|
|
|
596
|
-
const elemGoType =
|
|
597
|
-
? baseType.
|
|
598
|
-
|
|
599
|
-
? baseType.base.elem
|
|
600
|
-
: baseType.elem;
|
|
801
|
+
const elemGoType = this._getArrayElemType(
|
|
802
|
+
isPtrToArr ? baseType.base : baseType,
|
|
803
|
+
);
|
|
601
804
|
const arrInfo = this.mod.getArrayType(elemGoType);
|
|
602
805
|
const sliceInfo = this.mod.getSliceType(elemGoType);
|
|
603
806
|
|
|
@@ -746,8 +949,11 @@ export class ExprsEmitter {
|
|
|
746
949
|
const isValStruct =
|
|
747
950
|
isStructType(elemGoType, this.mod.checker, this.mod) &&
|
|
748
951
|
!isPointerToStruct(elemGoType, this.mod.checker, this.mod);
|
|
952
|
+
const isValArray =
|
|
953
|
+
elemGoType?.kind !== "pointer" &&
|
|
954
|
+
isArrayType(elemGoType, this.mod.checker);
|
|
749
955
|
|
|
750
|
-
if (!isValStruct) {
|
|
956
|
+
if (!isValStruct && !isValArray) {
|
|
751
957
|
this.pushInstruction({
|
|
752
958
|
op: "array.copy",
|
|
753
959
|
typeIndexDst: arrInfo.typeIndex,
|
|
@@ -820,7 +1026,9 @@ export class ExprsEmitter {
|
|
|
820
1026
|
this.pushInstruction("i32.add");
|
|
821
1027
|
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
822
1028
|
|
|
823
|
-
if (
|
|
1029
|
+
if (isValArray) {
|
|
1030
|
+
this.emitCopyArray(elemGoType);
|
|
1031
|
+
} else if (sInfo) {
|
|
824
1032
|
this.emitCloneStruct(sInfo, arrInfo.elemWType);
|
|
825
1033
|
}
|
|
826
1034
|
|
|
@@ -858,7 +1066,9 @@ export class ExprsEmitter {
|
|
|
858
1066
|
this.pushInstruction("i32.add");
|
|
859
1067
|
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
860
1068
|
|
|
861
|
-
if (
|
|
1069
|
+
if (isValArray) {
|
|
1070
|
+
this.emitCopyArray(elemGoType);
|
|
1071
|
+
} else if (sInfo) {
|
|
862
1072
|
this.emitCloneStruct(sInfo, arrInfo.elemWType);
|
|
863
1073
|
}
|
|
864
1074
|
|
|
@@ -931,53 +1141,95 @@ export class ExprsEmitter {
|
|
|
931
1141
|
return this.toWasmType(expr._type) === "i32";
|
|
932
1142
|
}
|
|
933
1143
|
|
|
1144
|
+
// Panics with the generic out-of-range message (shared buffers, SIMD
|
|
1145
|
+
// loads) when the condition on the stack is true.
|
|
934
1146
|
_emitBoundsBranch() {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1147
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1148
|
+
this.pushInstruction({
|
|
1149
|
+
op: "call",
|
|
1150
|
+
funcIndex: this.mod.getBoundsPanicFuncIndex(),
|
|
1151
|
+
});
|
|
1152
|
+
this.pushInstruction("unreachable");
|
|
1153
|
+
this.pushInstruction("end");
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// If the condition on the stack is true, panics with Go's index message;
|
|
1157
|
+
// `pushIdx64` and `emitLen` push the i64 index and the i32 length.
|
|
1158
|
+
_emitIndexPanicIf(pushIdx64, emitLen) {
|
|
1159
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1160
|
+
pushIdx64();
|
|
1161
|
+
emitLen();
|
|
1162
|
+
this.pushInstruction({
|
|
1163
|
+
op: "call",
|
|
1164
|
+
funcIndex: this.mod.getIndexPanicFuncIndex(),
|
|
1165
|
+
});
|
|
1166
|
+
this.pushInstruction("unreachable");
|
|
1167
|
+
this.pushInstruction("end");
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Registers an index-check site and pushes its id: a failing check
|
|
1171
|
+
// carries the index and this id to the handler, which re-emits `emitLen`
|
|
1172
|
+
// for that site (its temps still hold the operand at the branch).
|
|
1173
|
+
_pushOobSite(emitLen) {
|
|
1174
|
+
this.oobSites ??= [];
|
|
1175
|
+
this.oobSites.push(emitLen);
|
|
1176
|
+
this.pushInstruction({ op: "i32.const", value: this.oobSites.length - 1 });
|
|
947
1177
|
}
|
|
948
1178
|
|
|
949
1179
|
// Pushes the bounds-checked i32 index onto the stack. `emitLen` must push
|
|
950
|
-
// the i32 length.
|
|
951
|
-
//
|
|
1180
|
+
// the i32 length. Inside a function body a failing check branches to the
|
|
1181
|
+
// shared handler (`_closeOobBlocks`) with the index and a site id, and the
|
|
1182
|
+
// handler reports Go's message; elsewhere the check panics inline.
|
|
952
1183
|
_emitCheckedIndex(indexNode, emitLen) {
|
|
953
|
-
const
|
|
954
|
-
(indexNode.kind === "BasicLit" && indexNode.litKind === "INT") ||
|
|
955
|
-
(indexNode.kind === "Ident" && this.resolveLocal(indexNode.name));
|
|
1184
|
+
const depth32 = this.resolveBranchDepthToOob(32);
|
|
956
1185
|
if (this._isI32IndexExpr(indexNode)) {
|
|
1186
|
+
const idx = this.acquireTemp("i32");
|
|
957
1187
|
this.emitExpr(indexNode, "i32");
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
this.
|
|
1188
|
+
this.pushInstruction({ op: "local.tee", index: idx });
|
|
1189
|
+
if (depth32 !== null) {
|
|
1190
|
+
this._pushOobSite(emitLen);
|
|
1191
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
1192
|
+
emitLen();
|
|
1193
|
+
this.pushInstruction("i32.ge_u");
|
|
1194
|
+
this.pushInstruction({ op: "br_if", depth: depth32 });
|
|
1195
|
+
this.pushInstruction("drop");
|
|
961
1196
|
} else {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
this.pushInstruction(
|
|
1197
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
1198
|
+
emitLen();
|
|
1199
|
+
this.pushInstruction("i32.ge_u");
|
|
1200
|
+
this._emitIndexPanicIf(() => {
|
|
1201
|
+
this.pushInstruction({ op: "local.get", index: idx });
|
|
1202
|
+
this.pushInstruction("i64.extend_i32_s");
|
|
1203
|
+
}, emitLen);
|
|
965
1204
|
}
|
|
966
|
-
|
|
967
|
-
this.pushInstruction("i32.ge_u");
|
|
968
|
-
this._emitBoundsBranch();
|
|
969
|
-
if (tmp !== null) this.releaseTemp(tmp, "i32");
|
|
1205
|
+
this.releaseTemp(idx, "i32");
|
|
970
1206
|
return;
|
|
971
1207
|
}
|
|
972
1208
|
|
|
973
1209
|
const idx64 = this.acquireTemp("i64");
|
|
974
1210
|
this.emitExpr(indexNode, "i64");
|
|
975
1211
|
this.pushInstruction({ op: "local.tee", index: idx64 });
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1212
|
+
if (depth32 !== null) {
|
|
1213
|
+
this._pushOobSite(emitLen);
|
|
1214
|
+
this.pushInstruction({ op: "local.get", index: idx64 });
|
|
1215
|
+
emitLen();
|
|
1216
|
+
this.pushInstruction("i64.extend_i32_u");
|
|
1217
|
+
this.pushInstruction("i64.ge_u");
|
|
1218
|
+
this.pushInstruction({
|
|
1219
|
+
op: "br_if",
|
|
1220
|
+
depth: this.resolveBranchDepthToOob(64),
|
|
1221
|
+
});
|
|
1222
|
+
this.pushInstruction("drop");
|
|
1223
|
+
} else {
|
|
1224
|
+
emitLen();
|
|
1225
|
+
this.pushInstruction("i64.extend_i32_u");
|
|
1226
|
+
this.pushInstruction("i64.ge_u");
|
|
1227
|
+
this._emitIndexPanicIf(
|
|
1228
|
+
() => this.pushInstruction({ op: "local.get", index: idx64 }),
|
|
1229
|
+
emitLen,
|
|
1230
|
+
);
|
|
1231
|
+
this.pushInstruction({ op: "local.get", index: idx64 });
|
|
1232
|
+
}
|
|
981
1233
|
this.pushInstruction("i32.wrap_i64");
|
|
982
1234
|
this.releaseTemp(idx64, "i64");
|
|
983
1235
|
}
|
|
@@ -1075,7 +1327,83 @@ export class ExprsEmitter {
|
|
|
1075
1327
|
}
|
|
1076
1328
|
}
|
|
1077
1329
|
|
|
1330
|
+
// Integer conversions of constants (`int64(1e18 + 1)`) keep the exact value.
|
|
1331
|
+
_emitFoldedIntConversion(expr, targetWasmType) {
|
|
1332
|
+
if (expr.kind !== "TypeConversion" && expr.kind !== "CallExpr")
|
|
1333
|
+
return false;
|
|
1334
|
+
if (expr._const?.k !== "INT") return false;
|
|
1335
|
+
if (
|
|
1336
|
+
targetWasmType != null &&
|
|
1337
|
+
targetWasmType !== "i32" &&
|
|
1338
|
+
targetWasmType !== "i64"
|
|
1339
|
+
)
|
|
1340
|
+
return false;
|
|
1341
|
+
const lit = constToLit(expr._const);
|
|
1342
|
+
lit._type = expr._type;
|
|
1343
|
+
this.emitBasicLit(lit, targetWasmType);
|
|
1344
|
+
return true;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// A float literal; an integral one in an integer context (`var y int = 2.0`)
|
|
1348
|
+
// emits its exact integer value.
|
|
1349
|
+
_emitFloatLit(lit, wType) {
|
|
1350
|
+
const i =
|
|
1351
|
+
wType === "i32" || wType === "i64"
|
|
1352
|
+
? constIntValue(constFromLit(lit) ?? {})
|
|
1353
|
+
: null;
|
|
1354
|
+
if (i !== null) {
|
|
1355
|
+
this.pushInstruction(
|
|
1356
|
+
wType === "i32"
|
|
1357
|
+
? { op: "i32.const", value: Number(BigInt.asIntN(32, i)) }
|
|
1358
|
+
: { op: "i64.const", value: BigInt.asIntN(64, i) },
|
|
1359
|
+
);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const n = Number(lit.value);
|
|
1363
|
+
if (wType === "f32") {
|
|
1364
|
+
this.pushInstruction({ op: "f32.const", value: n });
|
|
1365
|
+
} else {
|
|
1366
|
+
this.pushInstruction({ op: "f64.const", value: n });
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// "complex64" | "complex128" when `w` is the struct type of a complex value.
|
|
1371
|
+
_complexKindOfWType(w) {
|
|
1372
|
+
if (typeof w?.typeIndex !== "number") return null;
|
|
1373
|
+
for (const [kind, info] of this.mod.complexTypes) {
|
|
1374
|
+
if (info.typeIndex === w.typeIndex) return kind;
|
|
1375
|
+
}
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1078
1379
|
emitBasicLit(lit, targetWasmType) {
|
|
1380
|
+
const targetComplex = this._complexKindOfWType(targetWasmType);
|
|
1381
|
+
const isTargetComplex64 =
|
|
1382
|
+
lit._type?.name === "complex64" ||
|
|
1383
|
+
targetWasmType?.typeName === "$complex64" ||
|
|
1384
|
+
targetComplex === "complex64";
|
|
1385
|
+
const isTargetComplex128 =
|
|
1386
|
+
lit._type?.name === "complex128" ||
|
|
1387
|
+
lit._type?.base === "complex128" ||
|
|
1388
|
+
targetWasmType?.typeName === "$complex128" ||
|
|
1389
|
+
targetComplex === "complex128";
|
|
1390
|
+
if (isTargetComplex64 || isTargetComplex128) {
|
|
1391
|
+
const info = this.mod.getComplexType(
|
|
1392
|
+
isTargetComplex64 ? "complex64" : "complex128",
|
|
1393
|
+
);
|
|
1394
|
+
const s = String(lit.value);
|
|
1395
|
+
const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
|
|
1396
|
+
const n = Number(clean);
|
|
1397
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: n });
|
|
1398
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
|
|
1399
|
+
this.pushInstruction({
|
|
1400
|
+
op: "struct.new",
|
|
1401
|
+
typeIndex: info.typeIndex,
|
|
1402
|
+
typeName: info.name,
|
|
1403
|
+
});
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1079
1407
|
const wType =
|
|
1080
1408
|
targetWasmType ??
|
|
1081
1409
|
toWasmType(lit._type, this.mod.checker) ??
|
|
@@ -1097,12 +1425,7 @@ export class ExprsEmitter {
|
|
|
1097
1425
|
}
|
|
1098
1426
|
|
|
1099
1427
|
if (lit.litKind === "FLOAT") {
|
|
1100
|
-
|
|
1101
|
-
if (wType === "f32") {
|
|
1102
|
-
this.pushInstruction({ op: "f32.const", value: n });
|
|
1103
|
-
} else {
|
|
1104
|
-
this.pushInstruction({ op: "f64.const", value: n });
|
|
1105
|
-
}
|
|
1428
|
+
this._emitFloatLit(lit, wType);
|
|
1106
1429
|
return;
|
|
1107
1430
|
}
|
|
1108
1431
|
|
|
@@ -1130,6 +1453,22 @@ export class ExprsEmitter {
|
|
|
1130
1453
|
}
|
|
1131
1454
|
}
|
|
1132
1455
|
|
|
1456
|
+
emitImagLit(lit, targetWasmType) {
|
|
1457
|
+
const is64 =
|
|
1458
|
+
lit._type?.name === "complex64" ||
|
|
1459
|
+
targetWasmType?.typeName === "$complex64";
|
|
1460
|
+
const complexName = is64 ? "complex64" : "complex128";
|
|
1461
|
+
const info = this.mod.getComplexType(complexName);
|
|
1462
|
+
const imVal = Number(lit.value);
|
|
1463
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
|
|
1464
|
+
this.pushInstruction({ op: `${info.elemWType}.const`, value: imVal });
|
|
1465
|
+
this.pushInstruction({
|
|
1466
|
+
op: "struct.new",
|
|
1467
|
+
typeIndex: info.typeIndex,
|
|
1468
|
+
typeName: info.name,
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1133
1472
|
_emitNilValue(goType, targetWasmType) {
|
|
1134
1473
|
const effectiveType = targetWasmType ?? this.toWasmType(goType);
|
|
1135
1474
|
if (
|
|
@@ -1253,6 +1592,11 @@ export class ExprsEmitter {
|
|
|
1253
1592
|
emitUnaryExpr(expr, targetWasmType = null) {
|
|
1254
1593
|
const { op, operand } = expr;
|
|
1255
1594
|
|
|
1595
|
+
if (isComplex(expr._type) || isComplex(operand._type)) {
|
|
1596
|
+
this._emitComplexUnary(expr, op, operand, targetWasmType);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1256
1600
|
// Optimize negative numeric literals directly
|
|
1257
1601
|
if (op === "-" && operand.kind === "BasicLit") {
|
|
1258
1602
|
if (operand.litKind === "INT" || operand.litKind === "FLOAT") {
|
|
@@ -1310,102 +1654,480 @@ export class ExprsEmitter {
|
|
|
1310
1654
|
}
|
|
1311
1655
|
break;
|
|
1312
1656
|
|
|
1313
|
-
case "&":
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1657
|
+
case "&":
|
|
1658
|
+
return this._emitAddressOf(operand);
|
|
1659
|
+
|
|
1660
|
+
case "*":
|
|
1661
|
+
return this._emitDerefExpr(operand);
|
|
1662
|
+
|
|
1663
|
+
default:
|
|
1664
|
+
throw new Error(`Unsupported unary operator: ${op}`);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
_emitComplexUnary(expr, op, operand, targetWasmType) {
|
|
1669
|
+
if (op === "+") {
|
|
1670
|
+
this.emitExpr(operand, targetWasmType);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (op === "-") {
|
|
1674
|
+
if (operand.kind === "ImagLit") {
|
|
1675
|
+
this.emitImagLit(
|
|
1676
|
+
{ ...operand, value: `-${operand.value}` },
|
|
1677
|
+
targetWasmType,
|
|
1678
|
+
);
|
|
1334
1679
|
return;
|
|
1335
1680
|
}
|
|
1681
|
+
const is64 =
|
|
1682
|
+
isComplex64(expr._type) ||
|
|
1683
|
+
isComplex64(operand._type) ||
|
|
1684
|
+
targetWasmType?.typeName === "$complex64";
|
|
1685
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
1686
|
+
const cWType = {
|
|
1687
|
+
kind: "ref",
|
|
1688
|
+
nullable: true,
|
|
1689
|
+
typeIndex: info.typeIndex,
|
|
1690
|
+
typeName: info.name,
|
|
1691
|
+
};
|
|
1692
|
+
const opTmp = this.acquireTemp(cWType);
|
|
1693
|
+
this.emitExpr(operand, cWType);
|
|
1694
|
+
this.pushInstruction({ op: "local.set", index: opTmp });
|
|
1336
1695
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
if (structInfo) {
|
|
1340
|
-
const baseWType = this.toWasmType(operand._type);
|
|
1341
|
-
this.emitExpr(operand, baseWType);
|
|
1342
|
-
this.emitCloneStruct(structInfo, baseWType);
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
|
|
1346
|
-
const baseWType = {
|
|
1347
|
-
kind: "ref",
|
|
1348
|
-
nullable: true,
|
|
1349
|
-
typeIndex: box.typeIndex,
|
|
1350
|
-
};
|
|
1351
|
-
this.emitExpr(operand, baseWType);
|
|
1696
|
+
for (let i = 0; i < 2; i++) {
|
|
1697
|
+
this.pushInstruction({ op: "local.get", index: opTmp });
|
|
1352
1698
|
this.pushInstruction({
|
|
1353
1699
|
op: "struct.get",
|
|
1354
|
-
typeIndex:
|
|
1355
|
-
|
|
1700
|
+
typeIndex: info.typeIndex,
|
|
1701
|
+
typeName: info.name,
|
|
1702
|
+
fieldIndex: i,
|
|
1356
1703
|
});
|
|
1357
|
-
|
|
1704
|
+
this.pushInstruction(`${info.elemWType}.neg`);
|
|
1358
1705
|
}
|
|
1359
1706
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1707
|
+
this.pushInstruction({
|
|
1708
|
+
op: "struct.new",
|
|
1709
|
+
typeIndex: info.typeIndex,
|
|
1710
|
+
typeName: info.name,
|
|
1711
|
+
});
|
|
1712
|
+
this.releaseTemp(opTmp, cWType);
|
|
1362
1713
|
}
|
|
1363
1714
|
}
|
|
1364
1715
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1716
|
+
_emitAddressOf(operand) {
|
|
1717
|
+
if (operand.kind === "Ident") {
|
|
1718
|
+
const loc = this.resolveLocal(operand.name);
|
|
1719
|
+
if (loc?.isBoxed) {
|
|
1720
|
+
this.pushInstruction({ op: "local.get", index: loc.index });
|
|
1721
|
+
// A pointer to an array or struct is its reference itself.
|
|
1722
|
+
const t = loc.goType;
|
|
1723
|
+
if (
|
|
1724
|
+
this._isValueArray(t) ||
|
|
1725
|
+
(t?.kind !== "pointer" &&
|
|
1726
|
+
isStructType(t, this.mod.checker, this.mod) &&
|
|
1727
|
+
!isPointerToStruct(t, this.mod.checker, this.mod))
|
|
1728
|
+
)
|
|
1729
|
+
this.pushInstruction({
|
|
1730
|
+
op: "struct.get",
|
|
1731
|
+
typeIndex: loc.boxInfo.typeIndex,
|
|
1732
|
+
fieldIndex: 0,
|
|
1733
|
+
});
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (operand.kind === "CompositeLit") {
|
|
1738
|
+
if (isStructType(operand._type, this.mod.checker, this.mod)) {
|
|
1739
|
+
this.emitExpr(operand);
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
const structInfo = this._resolveStructInfo(operand);
|
|
1744
|
+
if (structInfo) {
|
|
1745
|
+
this.emitExpr(operand);
|
|
1376
1746
|
return;
|
|
1377
1747
|
}
|
|
1378
|
-
|
|
1379
|
-
if (
|
|
1380
|
-
this.emitExpr(
|
|
1381
|
-
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
1382
|
-
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
1383
|
-
this.pushInstruction("else");
|
|
1384
|
-
this.emitExpr(right, "i32");
|
|
1385
|
-
this.pushInstruction("end");
|
|
1748
|
+
const opWType = this.toWasmType(operand._type);
|
|
1749
|
+
if (!isScalarType(operand._type, this.mod.checker, this.mod)) {
|
|
1750
|
+
this.emitExpr(operand, opWType);
|
|
1386
1751
|
return;
|
|
1387
1752
|
}
|
|
1753
|
+
const box = this.mod.getBoxType(operand._type);
|
|
1754
|
+
this.emitExpr(operand, opWType);
|
|
1755
|
+
this.pushInstruction({
|
|
1756
|
+
op: "struct.new",
|
|
1757
|
+
typeIndex: box.typeIndex,
|
|
1758
|
+
typeName: box.name,
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1388
1761
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1762
|
+
_isPointerGoType(t) {
|
|
1763
|
+
let x = t;
|
|
1764
|
+
if (x?.kind === "named" && x.underlying) x = x.underlying;
|
|
1765
|
+
return x?.kind === "pointer" || x?.kind === "PointerType";
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
_ptrBaseType(t) {
|
|
1769
|
+
let x = t;
|
|
1770
|
+
if (x?.kind === "named" && x.underlying) x = x.underlying;
|
|
1771
|
+
return x?.kind === "pointer" ? x.base : null;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// The struct a `*operand` reads or writes, or null when the operand points
|
|
1775
|
+
// at something else: `*pp` with `pp **T` loads a pointer, not a struct.
|
|
1776
|
+
_derefStructInfo(operand) {
|
|
1777
|
+
if (this._isPointerGoType(this._ptrBaseType(operand._type))) return null;
|
|
1778
|
+
return this._resolveStructInfo(operand);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
_emitDerefExpr(operand) {
|
|
1782
|
+
const structInfo = this._derefStructInfo(operand);
|
|
1783
|
+
if (structInfo) {
|
|
1784
|
+
const baseWType = this.toWasmType(operand._type);
|
|
1785
|
+
this.emitExpr(operand, baseWType);
|
|
1786
|
+
this._emitNilCheck(operand._type);
|
|
1787
|
+
this.emitCloneStruct(structInfo, baseWType);
|
|
1392
1788
|
return;
|
|
1393
1789
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1790
|
+
const operandGoType =
|
|
1791
|
+
operand._type ??
|
|
1792
|
+
(operand.kind === "Ident"
|
|
1793
|
+
? this.resolveLocal(operand.name)?.goType
|
|
1794
|
+
: null) ??
|
|
1795
|
+
this._resolveExprGoType(operand);
|
|
1796
|
+
const baseGoType = operandGoType?.base ?? operandGoType;
|
|
1797
|
+
if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
|
|
1798
|
+
const baseWType = this.toWasmType(baseGoType);
|
|
1799
|
+
this.emitExpr(operand, baseWType);
|
|
1398
1800
|
return;
|
|
1399
1801
|
}
|
|
1802
|
+
const box = this.mod.getBoxType(baseGoType);
|
|
1803
|
+
const ptr = this.mod.getPtrType(baseGoType);
|
|
1804
|
+
const ptrWType = {
|
|
1805
|
+
kind: "ref",
|
|
1806
|
+
nullable: true,
|
|
1807
|
+
typeIndex: ptr.typeIndex,
|
|
1808
|
+
typeName: ptr.name,
|
|
1809
|
+
};
|
|
1810
|
+
this.emitExpr(operand, ptrWType);
|
|
1811
|
+
this._emitNilCheck(operandGoType);
|
|
1812
|
+
this.pushInstruction({
|
|
1813
|
+
op: "ref.cast",
|
|
1814
|
+
typeIndex: box.typeIndex,
|
|
1815
|
+
typeName: box.name,
|
|
1816
|
+
});
|
|
1817
|
+
this.pushInstruction({
|
|
1818
|
+
op: "struct.get",
|
|
1819
|
+
typeIndex: box.typeIndex,
|
|
1820
|
+
typeName: box.name,
|
|
1821
|
+
fieldIndex: 0,
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1400
1824
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
op
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1825
|
+
_emitComplexEq(aTmp, bTmp, info, f, op) {
|
|
1826
|
+
for (let i = 0; i < 2; i++) {
|
|
1827
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1828
|
+
this.pushInstruction({
|
|
1829
|
+
op: "struct.get",
|
|
1830
|
+
typeIndex: info.typeIndex,
|
|
1831
|
+
typeName: info.name,
|
|
1832
|
+
fieldIndex: i,
|
|
1833
|
+
});
|
|
1834
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1835
|
+
this.pushInstruction({
|
|
1836
|
+
op: "struct.get",
|
|
1837
|
+
typeIndex: info.typeIndex,
|
|
1838
|
+
typeName: info.name,
|
|
1839
|
+
fieldIndex: i,
|
|
1840
|
+
});
|
|
1841
|
+
this.pushInstruction(`${f}.eq`);
|
|
1842
|
+
}
|
|
1843
|
+
this.pushInstruction("i32.and");
|
|
1844
|
+
if (op === "!=") this.pushInstruction("i32.eqz");
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
_emitComplexAddSub(aTmp, bTmp, info, f, op) {
|
|
1848
|
+
const wasmOp = op === "+" ? `${f}.add` : `${f}.sub`;
|
|
1849
|
+
for (let i = 0; i < 2; i++) {
|
|
1850
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1851
|
+
this.pushInstruction({
|
|
1852
|
+
op: "struct.get",
|
|
1853
|
+
typeIndex: info.typeIndex,
|
|
1854
|
+
typeName: info.name,
|
|
1855
|
+
fieldIndex: i,
|
|
1856
|
+
});
|
|
1857
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1858
|
+
this.pushInstruction({
|
|
1859
|
+
op: "struct.get",
|
|
1860
|
+
typeIndex: info.typeIndex,
|
|
1861
|
+
typeName: info.name,
|
|
1862
|
+
fieldIndex: i,
|
|
1863
|
+
});
|
|
1864
|
+
this.pushInstruction(wasmOp);
|
|
1865
|
+
}
|
|
1866
|
+
this.pushInstruction({
|
|
1867
|
+
op: "struct.new",
|
|
1868
|
+
typeIndex: info.typeIndex,
|
|
1869
|
+
typeName: info.name,
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
_emitComplexMul(aTmp, bTmp, info, f) {
|
|
1874
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1875
|
+
this.pushInstruction({
|
|
1876
|
+
op: "struct.get",
|
|
1877
|
+
typeIndex: info.typeIndex,
|
|
1878
|
+
typeName: info.name,
|
|
1879
|
+
fieldIndex: 0,
|
|
1880
|
+
});
|
|
1881
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1882
|
+
this.pushInstruction({
|
|
1883
|
+
op: "struct.get",
|
|
1884
|
+
typeIndex: info.typeIndex,
|
|
1885
|
+
typeName: info.name,
|
|
1886
|
+
fieldIndex: 0,
|
|
1887
|
+
});
|
|
1888
|
+
this.pushInstruction(`${f}.mul`);
|
|
1889
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1890
|
+
this.pushInstruction({
|
|
1891
|
+
op: "struct.get",
|
|
1892
|
+
typeIndex: info.typeIndex,
|
|
1893
|
+
typeName: info.name,
|
|
1894
|
+
fieldIndex: 1,
|
|
1895
|
+
});
|
|
1896
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1897
|
+
this.pushInstruction({
|
|
1898
|
+
op: "struct.get",
|
|
1899
|
+
typeIndex: info.typeIndex,
|
|
1900
|
+
typeName: info.name,
|
|
1901
|
+
fieldIndex: 1,
|
|
1902
|
+
});
|
|
1903
|
+
this.pushInstruction(`${f}.mul`);
|
|
1904
|
+
this.pushInstruction(`${f}.sub`);
|
|
1905
|
+
|
|
1906
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1907
|
+
this.pushInstruction({
|
|
1908
|
+
op: "struct.get",
|
|
1909
|
+
typeIndex: info.typeIndex,
|
|
1910
|
+
typeName: info.name,
|
|
1911
|
+
fieldIndex: 0,
|
|
1912
|
+
});
|
|
1913
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1914
|
+
this.pushInstruction({
|
|
1915
|
+
op: "struct.get",
|
|
1916
|
+
typeIndex: info.typeIndex,
|
|
1917
|
+
typeName: info.name,
|
|
1918
|
+
fieldIndex: 1,
|
|
1919
|
+
});
|
|
1920
|
+
this.pushInstruction(`${f}.mul`);
|
|
1921
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1922
|
+
this.pushInstruction({
|
|
1923
|
+
op: "struct.get",
|
|
1924
|
+
typeIndex: info.typeIndex,
|
|
1925
|
+
typeName: info.name,
|
|
1926
|
+
fieldIndex: 1,
|
|
1927
|
+
});
|
|
1928
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1929
|
+
this.pushInstruction({
|
|
1930
|
+
op: "struct.get",
|
|
1931
|
+
typeIndex: info.typeIndex,
|
|
1932
|
+
typeName: info.name,
|
|
1933
|
+
fieldIndex: 0,
|
|
1934
|
+
});
|
|
1935
|
+
this.pushInstruction(`${f}.mul`);
|
|
1936
|
+
this.pushInstruction(`${f}.add`);
|
|
1937
|
+
|
|
1938
|
+
this.pushInstruction({
|
|
1939
|
+
op: "struct.new",
|
|
1940
|
+
typeIndex: info.typeIndex,
|
|
1941
|
+
typeName: info.name,
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
_emitComplexDiv(aTmp, bTmp, info, f) {
|
|
1946
|
+
const dTmp = this.acquireTemp(f);
|
|
1947
|
+
for (let i = 0; i < 2; i++) {
|
|
1948
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1949
|
+
this.pushInstruction({
|
|
1950
|
+
op: "struct.get",
|
|
1951
|
+
typeIndex: info.typeIndex,
|
|
1952
|
+
typeName: info.name,
|
|
1953
|
+
fieldIndex: i,
|
|
1954
|
+
});
|
|
1955
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1956
|
+
this.pushInstruction({
|
|
1957
|
+
op: "struct.get",
|
|
1958
|
+
typeIndex: info.typeIndex,
|
|
1959
|
+
typeName: info.name,
|
|
1960
|
+
fieldIndex: i,
|
|
1961
|
+
});
|
|
1962
|
+
this.pushInstruction(`${f}.mul`);
|
|
1963
|
+
if (i > 0) this.pushInstruction(`${f}.add`);
|
|
1964
|
+
}
|
|
1965
|
+
this.pushInstruction({ op: "local.set", index: dTmp });
|
|
1966
|
+
|
|
1967
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1968
|
+
this.pushInstruction({
|
|
1969
|
+
op: "struct.get",
|
|
1970
|
+
typeIndex: info.typeIndex,
|
|
1971
|
+
typeName: info.name,
|
|
1972
|
+
fieldIndex: 0,
|
|
1973
|
+
});
|
|
1974
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1975
|
+
this.pushInstruction({
|
|
1976
|
+
op: "struct.get",
|
|
1977
|
+
typeIndex: info.typeIndex,
|
|
1978
|
+
typeName: info.name,
|
|
1979
|
+
fieldIndex: 0,
|
|
1980
|
+
});
|
|
1981
|
+
this.pushInstruction(`${f}.mul`);
|
|
1982
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
1983
|
+
this.pushInstruction({
|
|
1984
|
+
op: "struct.get",
|
|
1985
|
+
typeIndex: info.typeIndex,
|
|
1986
|
+
typeName: info.name,
|
|
1987
|
+
fieldIndex: 1,
|
|
1988
|
+
});
|
|
1989
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
1990
|
+
this.pushInstruction({
|
|
1991
|
+
op: "struct.get",
|
|
1992
|
+
typeIndex: info.typeIndex,
|
|
1993
|
+
typeName: info.name,
|
|
1994
|
+
fieldIndex: 1,
|
|
1995
|
+
});
|
|
1996
|
+
this.pushInstruction(`${f}.mul`);
|
|
1997
|
+
this.pushInstruction(`${f}.add`);
|
|
1998
|
+
this.pushInstruction({ op: "local.get", index: dTmp });
|
|
1999
|
+
this.pushInstruction(`${f}.div`);
|
|
2000
|
+
|
|
2001
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
2002
|
+
this.pushInstruction({
|
|
2003
|
+
op: "struct.get",
|
|
2004
|
+
typeIndex: info.typeIndex,
|
|
2005
|
+
typeName: info.name,
|
|
2006
|
+
fieldIndex: 1,
|
|
2007
|
+
});
|
|
2008
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
2009
|
+
this.pushInstruction({
|
|
2010
|
+
op: "struct.get",
|
|
2011
|
+
typeIndex: info.typeIndex,
|
|
2012
|
+
typeName: info.name,
|
|
2013
|
+
fieldIndex: 0,
|
|
2014
|
+
});
|
|
2015
|
+
this.pushInstruction(`${f}.mul`);
|
|
2016
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
2017
|
+
this.pushInstruction({
|
|
2018
|
+
op: "struct.get",
|
|
2019
|
+
typeIndex: info.typeIndex,
|
|
2020
|
+
typeName: info.name,
|
|
2021
|
+
fieldIndex: 0,
|
|
2022
|
+
});
|
|
2023
|
+
this.pushInstruction({ op: "local.get", index: bTmp });
|
|
2024
|
+
this.pushInstruction({
|
|
2025
|
+
op: "struct.get",
|
|
2026
|
+
typeIndex: info.typeIndex,
|
|
2027
|
+
typeName: info.name,
|
|
2028
|
+
fieldIndex: 1,
|
|
2029
|
+
});
|
|
2030
|
+
this.pushInstruction(`${f}.mul`);
|
|
2031
|
+
this.pushInstruction(`${f}.sub`);
|
|
2032
|
+
this.pushInstruction({ op: "local.get", index: dTmp });
|
|
2033
|
+
this.pushInstruction(`${f}.div`);
|
|
2034
|
+
|
|
2035
|
+
this.pushInstruction({
|
|
2036
|
+
op: "struct.new",
|
|
2037
|
+
typeIndex: info.typeIndex,
|
|
2038
|
+
typeName: info.name,
|
|
2039
|
+
});
|
|
2040
|
+
this.releaseTemp(dTmp, f);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
emitComplexBinary(expr, targetWasmType) {
|
|
2044
|
+
const { op, left, right } = expr;
|
|
2045
|
+
const is64 =
|
|
2046
|
+
isComplex64(expr._type) ||
|
|
2047
|
+
isComplex64(left._type) ||
|
|
2048
|
+
isComplex64(right._type) ||
|
|
2049
|
+
targetWasmType?.typeName === "$complex64";
|
|
2050
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
2051
|
+
const f = info.elemWType;
|
|
2052
|
+
const cWType = {
|
|
2053
|
+
kind: "ref",
|
|
2054
|
+
nullable: true,
|
|
2055
|
+
typeIndex: info.typeIndex,
|
|
2056
|
+
typeName: info.name,
|
|
2057
|
+
};
|
|
2058
|
+
const aTmp = this.acquireTemp(cWType);
|
|
2059
|
+
const bTmp = this.acquireTemp(cWType);
|
|
2060
|
+
this.emitExpr(left, cWType);
|
|
2061
|
+
this.pushInstruction({ op: "local.set", index: aTmp });
|
|
2062
|
+
this.emitExpr(right, cWType);
|
|
2063
|
+
this.pushInstruction({ op: "local.set", index: bTmp });
|
|
2064
|
+
|
|
2065
|
+
if (op === "==" || op === "!=") {
|
|
2066
|
+
this._emitComplexEq(aTmp, bTmp, info, f, op);
|
|
2067
|
+
} else if (op === "+" || op === "-") {
|
|
2068
|
+
this._emitComplexAddSub(aTmp, bTmp, info, f, op);
|
|
2069
|
+
} else if (op === "*") {
|
|
2070
|
+
this._emitComplexMul(aTmp, bTmp, info, f);
|
|
2071
|
+
} else if (op === "/") {
|
|
2072
|
+
this._emitComplexDiv(aTmp, bTmp, info, f);
|
|
2073
|
+
}
|
|
2074
|
+
this.releaseTemp(bTmp, cWType);
|
|
2075
|
+
this.releaseTemp(aTmp, cWType);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
emitBinaryExpr(expr, targetWasmType = null) {
|
|
2079
|
+
const { op, left, right } = expr;
|
|
2080
|
+
|
|
2081
|
+
if (
|
|
2082
|
+
isComplex(expr._type) ||
|
|
2083
|
+
isComplex(left._type) ||
|
|
2084
|
+
isComplex(right._type)
|
|
2085
|
+
) {
|
|
2086
|
+
this.emitComplexBinary(expr, targetWasmType);
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// Short-circuiting logical operators
|
|
2091
|
+
if (op === "&&") {
|
|
2092
|
+
this.emitExpr(left, "i32");
|
|
2093
|
+
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
2094
|
+
this.emitExpr(right, "i32");
|
|
2095
|
+
this.pushInstruction("else");
|
|
2096
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
2097
|
+
this.pushInstruction("end");
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (op === "||") {
|
|
2102
|
+
this.emitExpr(left, "i32");
|
|
2103
|
+
this.pushInstruction({ op: "if", blockType: "i32" });
|
|
2104
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
2105
|
+
this.pushInstruction("else");
|
|
2106
|
+
this.emitExpr(right, "i32");
|
|
2107
|
+
this.pushInstruction("end");
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// Division & Modulo with zero-guard and overflow-guard
|
|
2112
|
+
if (op === "/" || op === "%") {
|
|
2113
|
+
this.emitDivRem(op, left, right, targetWasmType);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// Shifts with Go semantics
|
|
2118
|
+
if (op === "<<" || op === ">>") {
|
|
2119
|
+
this.emitShift(op, left, right, targetWasmType);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// Standard binary operators
|
|
2124
|
+
const isCmp =
|
|
2125
|
+
op === "==" ||
|
|
2126
|
+
op === "!=" ||
|
|
2127
|
+
op === "<" ||
|
|
2128
|
+
op === "<=" ||
|
|
2129
|
+
op === ">" ||
|
|
2130
|
+
op === ">=";
|
|
1409
2131
|
|
|
1410
2132
|
const isNil = (n) =>
|
|
1411
2133
|
(n.kind === "BasicLit" && n.litKind === "NIL") ||
|
|
@@ -1442,10 +2164,17 @@ export class ExprsEmitter {
|
|
|
1442
2164
|
return;
|
|
1443
2165
|
}
|
|
1444
2166
|
|
|
1445
|
-
|
|
2167
|
+
// An untyped constant operand takes the type of the typed one
|
|
2168
|
+
// (`'a' < rune(b)`), so both sides share one wasm representation.
|
|
2169
|
+
const leftType =
|
|
2170
|
+
left._type?.kind === "untyped" &&
|
|
2171
|
+
right._type &&
|
|
2172
|
+
right._type.kind !== "untyped"
|
|
2173
|
+
? right._type
|
|
2174
|
+
: left._type;
|
|
1446
2175
|
const isLeftI32 = this._isI32IndexExpr(left);
|
|
1447
2176
|
const isRightI32 = this._isI32IndexExpr(right);
|
|
1448
|
-
const leftGoType =
|
|
2177
|
+
const leftGoType = leftType ?? this._resolveExprGoType(left);
|
|
1449
2178
|
const isIntType =
|
|
1450
2179
|
!leftGoType ||
|
|
1451
2180
|
(leftGoType.kind === "basic" &&
|
|
@@ -1486,6 +2215,72 @@ export class ExprsEmitter {
|
|
|
1486
2215
|
this.emitBinaryOp(op, wType, leftType);
|
|
1487
2216
|
}
|
|
1488
2217
|
|
|
2218
|
+
_findScalarPtrByWType(wType, map) {
|
|
2219
|
+
if (!map) return null;
|
|
2220
|
+
for (const info of map.values()) {
|
|
2221
|
+
if (info.typeIndex === wType.typeIndex || info.name === wType.typeName) {
|
|
2222
|
+
return info;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return null;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
_getScalarPtrFromWType(wType) {
|
|
2229
|
+
if (!wType || typeof wType !== "object") return null;
|
|
2230
|
+
const ptr = this._findScalarPtrByWType(wType, this.mod.ptrTypes);
|
|
2231
|
+
if (ptr) {
|
|
2232
|
+
return {
|
|
2233
|
+
ptr,
|
|
2234
|
+
box: this.mod.getBoxType(ptr.goType),
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
const box = this._findScalarPtrByWType(wType, this.mod.boxTypes);
|
|
2238
|
+
if (box) {
|
|
2239
|
+
return {
|
|
2240
|
+
ptr: this.mod.getPtrType(box.goType),
|
|
2241
|
+
box,
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
return null;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
_getScalarPtrFromGoType(goType) {
|
|
2248
|
+
let g = goType;
|
|
2249
|
+
if (g?.kind === "TypeName" && this.mod.checker?.types?.has(g.name)) {
|
|
2250
|
+
g = this.mod.checker.types.get(g.name);
|
|
2251
|
+
}
|
|
2252
|
+
while (g?.kind === "named" && g.underlying) {
|
|
2253
|
+
g = g.underlying;
|
|
2254
|
+
}
|
|
2255
|
+
const isPtr =
|
|
2256
|
+
g?.kind === "pointer" ||
|
|
2257
|
+
g?.kind === "PointerType" ||
|
|
2258
|
+
g?.kind === "StarExpr";
|
|
2259
|
+
if (!isPtr || (!g.base && !g.elem)) return null;
|
|
2260
|
+
const base = g.base || g.elem;
|
|
2261
|
+
if (!isScalarType(base, this.mod.checker, this.mod)) {
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
return {
|
|
2265
|
+
ptr: this.mod.getPtrType(base),
|
|
2266
|
+
box: this.mod.getBoxType(base),
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
_getScalarPtrInfo(wType, goType = null) {
|
|
2271
|
+
return (
|
|
2272
|
+
this._getScalarPtrFromGoType(goType) ?? this._getScalarPtrFromWType(wType)
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
_isScalarPtrType(goType, wType = null) {
|
|
2277
|
+
return this._getScalarPtrInfo(wType, goType) !== null;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
_emitScalarPtrEq(_ptrInfo) {
|
|
2281
|
+
this.pushInstruction("ref.eq");
|
|
2282
|
+
}
|
|
2283
|
+
|
|
1489
2284
|
_emitStructEq(structInfo) {
|
|
1490
2285
|
const bTmp = this.acquireTemp({
|
|
1491
2286
|
kind: "ref",
|
|
@@ -1532,11 +2327,18 @@ export class ExprsEmitter {
|
|
|
1532
2327
|
(f.goType?.kind === "named" ? f.goType.name : null),
|
|
1533
2328
|
);
|
|
1534
2329
|
this._emitStructEq(sInfo);
|
|
2330
|
+
} else if (this._isValueArray(f.goType)) {
|
|
2331
|
+
this._emitArrayEq(f.goType);
|
|
1535
2332
|
} else if (isStringType(f.goType, this.mod.checker)) {
|
|
1536
2333
|
const cmpIdx = this.mod.getStringCmpImportIndex("==");
|
|
1537
2334
|
this.pushInstruction({ op: "call", funcIndex: cmpIdx });
|
|
2335
|
+
} else if (f.wType === "v128") {
|
|
2336
|
+
this._emitSimdEq(f.goType);
|
|
1538
2337
|
} else if (f.wType === "anyref") {
|
|
1539
2338
|
this._emitAnyEq();
|
|
2339
|
+
} else if (this._isScalarPtrType(f.goType, f.wType)) {
|
|
2340
|
+
const ptrInfo = this._getScalarPtrInfo(f.wType, f.goType);
|
|
2341
|
+
this._emitScalarPtrEq(ptrInfo);
|
|
1540
2342
|
} else if (typeof f.wType === "object" && f.wType !== null) {
|
|
1541
2343
|
this.pushInstruction("ref.eq");
|
|
1542
2344
|
} else {
|
|
@@ -1567,7 +2369,110 @@ export class ExprsEmitter {
|
|
|
1567
2369
|
});
|
|
1568
2370
|
}
|
|
1569
2371
|
|
|
2372
|
+
_isValueArray(goType) {
|
|
2373
|
+
return goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// Go array equality: element-wise (arrays are values). Pops two arrays
|
|
2377
|
+
// and pushes an i32. A null array (a never-written slot) is the zero
|
|
2378
|
+
// array.
|
|
2379
|
+
_emitArrayEq(goType) {
|
|
2380
|
+
const elemType = this._getArrayElemType(goType);
|
|
2381
|
+
const arrInfo = this.mod.getArrayType(elemType);
|
|
2382
|
+
const arrWType = {
|
|
2383
|
+
kind: "ref",
|
|
2384
|
+
nullable: true,
|
|
2385
|
+
typeIndex: arrInfo.typeIndex,
|
|
2386
|
+
};
|
|
2387
|
+
const bTmp = this.acquireTemp(arrWType);
|
|
2388
|
+
const aTmp = this.acquireTemp(arrWType);
|
|
2389
|
+
for (const tmp of [bTmp, aTmp]) {
|
|
2390
|
+
this.pushInstruction({ op: "local.tee", index: tmp });
|
|
2391
|
+
this.pushInstruction("ref.is_null");
|
|
2392
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
2393
|
+
this.emitZeroValue(goType, arrWType);
|
|
2394
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
2395
|
+
this.pushInstruction("end");
|
|
2396
|
+
}
|
|
2397
|
+
const i = this.acquireTemp("i32");
|
|
2398
|
+
const resTmp = this.acquireTemp("i32");
|
|
2399
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
2400
|
+
this.pushInstruction({ op: "local.set", index: resTmp });
|
|
2401
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
2402
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
2403
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
2404
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
2405
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
2406
|
+
this.pushInstruction({ op: "local.get", index: aTmp });
|
|
2407
|
+
this.pushInstruction("array.len");
|
|
2408
|
+
this.pushInstruction("i32.ge_u");
|
|
2409
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
2410
|
+
for (const tmp of [aTmp, bTmp]) {
|
|
2411
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
2412
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
2413
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
2414
|
+
}
|
|
2415
|
+
this._emitElemEq(elemType, arrInfo.elemWType);
|
|
2416
|
+
this.pushInstruction("i32.eqz");
|
|
2417
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
2418
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
2419
|
+
this.pushInstruction({ op: "local.set", index: resTmp });
|
|
2420
|
+
this.pushInstruction({ op: "br", depth: 2 });
|
|
2421
|
+
this.pushInstruction("end");
|
|
2422
|
+
this.pushInstruction({ op: "local.get", index: i });
|
|
2423
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
2424
|
+
this.pushInstruction("i32.add");
|
|
2425
|
+
this.pushInstruction({ op: "local.set", index: i });
|
|
2426
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
2427
|
+
this.pushInstruction("end");
|
|
2428
|
+
this.pushInstruction("end");
|
|
2429
|
+
this.pushInstruction({ op: "local.get", index: resTmp });
|
|
2430
|
+
this.releaseTemp(resTmp, "i32");
|
|
2431
|
+
this.releaseTemp(i, "i32");
|
|
2432
|
+
this.releaseTemp(aTmp, arrWType);
|
|
2433
|
+
this.releaseTemp(bTmp, arrWType);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Equality of two array elements on the stack -> i32.
|
|
2437
|
+
_emitElemEq(goType, wType) {
|
|
2438
|
+
if (this._isValueArray(goType)) {
|
|
2439
|
+
this._emitArrayEq(goType);
|
|
2440
|
+
} else if (
|
|
2441
|
+
isStructType(goType, this.mod.checker, this.mod) &&
|
|
2442
|
+
!isPointerToStruct(goType, this.mod.checker, this.mod)
|
|
2443
|
+
) {
|
|
2444
|
+
this._emitStructEq(this._resolveStructInfo({ _type: goType }));
|
|
2445
|
+
} else {
|
|
2446
|
+
this.emitBinaryOp("==", wType, goType);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
1570
2450
|
emitBinaryOp(op, wType, goType) {
|
|
2451
|
+
if ((op === "==" || op === "!=") && this._isValueArray(goType)) {
|
|
2452
|
+
this._emitArrayEq(goType);
|
|
2453
|
+
if (op === "!=") this.pushInstruction("i32.eqz");
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (isComplex(goType)) {
|
|
2457
|
+
const is64 = isComplex64(goType);
|
|
2458
|
+
const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
|
|
2459
|
+
const f = info.elemWType;
|
|
2460
|
+
const bTmp = this.acquireTemp(wType);
|
|
2461
|
+
const aTmp = this.acquireTemp(wType);
|
|
2462
|
+
this.pushInstruction({ op: "local.set", index: bTmp });
|
|
2463
|
+
this.pushInstruction({ op: "local.set", index: aTmp });
|
|
2464
|
+
if (op === "+" || op === "-") {
|
|
2465
|
+
this._emitComplexAddSub(aTmp, bTmp, info, f, op);
|
|
2466
|
+
} else if (op === "*") {
|
|
2467
|
+
this._emitComplexMul(aTmp, bTmp, info, f);
|
|
2468
|
+
} else if (op === "/") {
|
|
2469
|
+
this._emitComplexDiv(aTmp, bTmp, info, f);
|
|
2470
|
+
}
|
|
2471
|
+
this.releaseTemp(aTmp, wType);
|
|
2472
|
+
this.releaseTemp(bTmp, wType);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
1571
2476
|
const signed = isSigned(goType);
|
|
1572
2477
|
|
|
1573
2478
|
switch (op) {
|
|
@@ -1635,8 +2540,13 @@ export class ExprsEmitter {
|
|
|
1635
2540
|
if (isStringType(goType, this.mod.checker) || wType === "externref") {
|
|
1636
2541
|
const cmpIdx = this.mod.getStringCmpImportIndex("==");
|
|
1637
2542
|
this.pushInstruction({ op: "call", funcIndex: cmpIdx });
|
|
2543
|
+
} else if (wType === "v128") {
|
|
2544
|
+
this._emitSimdEq(goType);
|
|
1638
2545
|
} else if (wType === "anyref") {
|
|
1639
2546
|
this._emitAnyEq();
|
|
2547
|
+
} else if (this._isScalarPtrType(goType, wType)) {
|
|
2548
|
+
const ptrInfo = this._getScalarPtrInfo(wType, goType);
|
|
2549
|
+
this._emitScalarPtrEq(ptrInfo);
|
|
1640
2550
|
} else if (typeof wType === "object" && wType !== null) {
|
|
1641
2551
|
this.pushInstruction("ref.eq");
|
|
1642
2552
|
} else {
|
|
@@ -1647,9 +2557,16 @@ export class ExprsEmitter {
|
|
|
1647
2557
|
if (isStringType(goType, this.mod.checker) || wType === "externref") {
|
|
1648
2558
|
const cmpIdx = this.mod.getStringCmpImportIndex("!=");
|
|
1649
2559
|
this.pushInstruction({ op: "call", funcIndex: cmpIdx });
|
|
2560
|
+
} else if (wType === "v128") {
|
|
2561
|
+
this._emitSimdEq(goType);
|
|
2562
|
+
this.pushInstruction("i32.eqz");
|
|
1650
2563
|
} else if (wType === "anyref") {
|
|
1651
2564
|
this._emitAnyEq();
|
|
1652
2565
|
this.pushInstruction("i32.eqz");
|
|
2566
|
+
} else if (this._isScalarPtrType(goType, wType)) {
|
|
2567
|
+
const ptrInfo = this._getScalarPtrInfo(wType, goType);
|
|
2568
|
+
this._emitScalarPtrEq(ptrInfo);
|
|
2569
|
+
this.pushInstruction("i32.eqz");
|
|
1653
2570
|
} else if (typeof wType === "object" && wType !== null) {
|
|
1654
2571
|
this.pushInstruction("ref.eq");
|
|
1655
2572
|
this.pushInstruction("i32.eqz");
|
|
@@ -1837,6 +2754,21 @@ export class ExprsEmitter {
|
|
|
1837
2754
|
this.pushInstruction({ op: "local.set", index: tmpVal });
|
|
1838
2755
|
|
|
1839
2756
|
this.emitExpr(right, rightWType);
|
|
2757
|
+
// A negative signed count panics before the shift is evaluated.
|
|
2758
|
+
if (isSigned(right._type) && right.kind !== "BasicLit") {
|
|
2759
|
+
const tmpCount = this.acquireTemp(rightWType);
|
|
2760
|
+
this.pushInstruction({ op: "local.tee", index: tmpCount });
|
|
2761
|
+
this.pushInstruction({
|
|
2762
|
+
op: `${rightWType}.const`,
|
|
2763
|
+
value: rightWType === "i64" ? 0n : 0,
|
|
2764
|
+
});
|
|
2765
|
+
this.pushInstruction(`${rightWType}.lt_s`);
|
|
2766
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
2767
|
+
this.emitPanic("runtime error: negative shift amount");
|
|
2768
|
+
this.pushInstruction("end");
|
|
2769
|
+
this.pushInstruction({ op: "local.get", index: tmpCount });
|
|
2770
|
+
this.releaseTemp(tmpCount, rightWType);
|
|
2771
|
+
}
|
|
1840
2772
|
if (wType === "i64" && rightWType === "i32") {
|
|
1841
2773
|
this.pushInstruction("i64.extend_i32_u");
|
|
1842
2774
|
} else if (wType === "i32" && rightWType === "i64") {
|
|
@@ -1879,32 +2811,91 @@ export class ExprsEmitter {
|
|
|
1879
2811
|
|
|
1880
2812
|
emitTypeConversion(conv) {
|
|
1881
2813
|
const { targetType, expr } = conv;
|
|
1882
|
-
const fromType = expr._type;
|
|
1883
|
-
const fromWType = toWasmType(fromType
|
|
1884
|
-
const toWType = toWasmType(targetType
|
|
2814
|
+
const fromType = expr._type ?? this._resolveExprGoType(expr);
|
|
2815
|
+
const fromWType = this.toWasmType(fromType);
|
|
2816
|
+
const toWType = this.toWasmType(targetType);
|
|
2817
|
+
|
|
2818
|
+
// `[]T(nil)` and `(*T)(nil)`: the target type's nil, not a converted value.
|
|
2819
|
+
if (
|
|
2820
|
+
(expr.kind === "Ident" && expr.name === "nil") ||
|
|
2821
|
+
(expr.kind === "BasicLit" && expr.litKind === "NIL")
|
|
2822
|
+
) {
|
|
2823
|
+
this._emitNilValue(targetType, toWType);
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// `any(x)` boxes x exactly like an assignment to an `any` variable.
|
|
2828
|
+
if (isAnyType(targetType, this.mod.checker)) {
|
|
2829
|
+
this.emitExpr(expr, "anyref");
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
1885
2832
|
|
|
1886
2833
|
this.emitExpr(expr, fromWType);
|
|
1887
2834
|
|
|
2835
|
+
if (isComplex(targetType)) {
|
|
2836
|
+
this._emitComplexConversion(targetType, fromType, fromWType);
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// Struct to a different defined struct type: a copy in the target's type.
|
|
2841
|
+
if (
|
|
2842
|
+
isStructType(fromType, this.mod.checker, this.mod) &&
|
|
2843
|
+
isStructType(targetType, this.mod.checker, this.mod) &&
|
|
2844
|
+
!isPointerToStruct(fromType, this.mod.checker, this.mod) &&
|
|
2845
|
+
!isPointerToStruct(targetType, this.mod.checker, this.mod)
|
|
2846
|
+
) {
|
|
2847
|
+
const src = this._resolveStructInfo({ _type: fromType });
|
|
2848
|
+
const dst = this._resolveStructInfo({ _type: targetType });
|
|
2849
|
+
if (src && dst && src.typeIndex !== dst.typeIndex) {
|
|
2850
|
+
this.emitCloneStruct(src, fromWType, dst);
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
1888
2855
|
if (fromWType === toWType) {
|
|
1889
2856
|
this.emitNarrowIntWrap(targetType);
|
|
1890
2857
|
return;
|
|
1891
2858
|
}
|
|
1892
2859
|
|
|
2860
|
+
// A named byte slice (`type MyBytes []byte`) converts like `[]byte`.
|
|
2861
|
+
const isByteElem = (t) => {
|
|
2862
|
+
const elem = this._getSliceElemType(t);
|
|
2863
|
+
return elem?.name === "byte" || elem?.name === "uint8";
|
|
2864
|
+
};
|
|
1893
2865
|
if (
|
|
1894
2866
|
fromWType === "externref" &&
|
|
1895
2867
|
isSliceType(targetType, this.mod.checker) &&
|
|
1896
|
-
(targetType
|
|
2868
|
+
isByteElem(targetType)
|
|
1897
2869
|
) {
|
|
1898
|
-
this._emitStringToByteSlice(
|
|
2870
|
+
this._emitStringToByteSlice({
|
|
2871
|
+
kind: "slice",
|
|
2872
|
+
elem: this._getSliceElemType(targetType),
|
|
2873
|
+
});
|
|
1899
2874
|
return;
|
|
1900
2875
|
}
|
|
1901
2876
|
|
|
1902
2877
|
if (
|
|
1903
2878
|
toWType === "externref" &&
|
|
1904
2879
|
isSliceType(fromType, this.mod.checker) &&
|
|
1905
|
-
(fromType
|
|
2880
|
+
isByteElem(fromType)
|
|
1906
2881
|
) {
|
|
1907
|
-
this._emitByteSliceToString(
|
|
2882
|
+
this._emitByteSliceToString({
|
|
2883
|
+
kind: "slice",
|
|
2884
|
+
elem: this._getSliceElemType(fromType),
|
|
2885
|
+
});
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
if (
|
|
2890
|
+
fromWType === "externref" &&
|
|
2891
|
+
isRuneSlice(targetType, this.mod.checker)
|
|
2892
|
+
) {
|
|
2893
|
+
this._emitStringToRuneSlice(targetType);
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
if (toWType === "externref" && isRuneSlice(fromType, this.mod.checker)) {
|
|
2898
|
+
this._emitRuneSliceToString(fromType);
|
|
1908
2899
|
return;
|
|
1909
2900
|
}
|
|
1910
2901
|
|
|
@@ -1949,6 +2940,64 @@ export class ExprsEmitter {
|
|
|
1949
2940
|
}
|
|
1950
2941
|
}
|
|
1951
2942
|
|
|
2943
|
+
_emitComplexConversion(targetType, fromType, fromWType) {
|
|
2944
|
+
const isTarget64 = isComplex64(targetType);
|
|
2945
|
+
const targetInfo = this.mod.getComplexType(
|
|
2946
|
+
isTarget64 ? "complex64" : "complex128",
|
|
2947
|
+
);
|
|
2948
|
+
if (isComplex(fromType)) {
|
|
2949
|
+
const isFrom64 = isComplex64(fromType);
|
|
2950
|
+
if (isTarget64 === isFrom64) return;
|
|
2951
|
+
const fromInfo = this.mod.getComplexType(
|
|
2952
|
+
isFrom64 ? "complex64" : "complex128",
|
|
2953
|
+
);
|
|
2954
|
+
const fromWTypeRef = {
|
|
2955
|
+
kind: "ref",
|
|
2956
|
+
nullable: true,
|
|
2957
|
+
typeIndex: fromInfo.typeIndex,
|
|
2958
|
+
typeName: fromInfo.name,
|
|
2959
|
+
};
|
|
2960
|
+
const cTmp = this.acquireTemp(fromWTypeRef);
|
|
2961
|
+
this.pushInstruction({ op: "local.set", index: cTmp });
|
|
2962
|
+
|
|
2963
|
+
for (let i = 0; i < 2; i++) {
|
|
2964
|
+
this.pushInstruction({ op: "local.get", index: cTmp });
|
|
2965
|
+
this.pushInstruction({
|
|
2966
|
+
op: "struct.get",
|
|
2967
|
+
typeIndex: fromInfo.typeIndex,
|
|
2968
|
+
typeName: fromInfo.name,
|
|
2969
|
+
fieldIndex: i,
|
|
2970
|
+
});
|
|
2971
|
+
this.pushInstruction(isTarget64 ? "f32.demote_f64" : "f64.promote_f32");
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
this.pushInstruction({
|
|
2975
|
+
op: "struct.new",
|
|
2976
|
+
typeIndex: targetInfo.typeIndex,
|
|
2977
|
+
typeName: targetInfo.name,
|
|
2978
|
+
});
|
|
2979
|
+
this.releaseTemp(cTmp, fromWTypeRef);
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
// From numeric scalar
|
|
2984
|
+
const targetF = targetInfo.elemWType;
|
|
2985
|
+
const srcSign = isSigned(fromType) ? "s" : "u";
|
|
2986
|
+
if (fromWType === "i32" || fromWType === "i64") {
|
|
2987
|
+
this.pushInstruction(`${targetF}.convert_${fromWType}_${srcSign}`);
|
|
2988
|
+
} else if (fromWType === "f32" && targetF === "f64") {
|
|
2989
|
+
this.pushInstruction("f64.promote_f32");
|
|
2990
|
+
} else if (fromWType === "f64" && targetF === "f32") {
|
|
2991
|
+
this.pushInstruction("f32.demote_f64");
|
|
2992
|
+
}
|
|
2993
|
+
this.pushInstruction({ op: `${targetF}.const`, value: 0.0 });
|
|
2994
|
+
this.pushInstruction({
|
|
2995
|
+
op: "struct.new",
|
|
2996
|
+
typeIndex: targetInfo.typeIndex,
|
|
2997
|
+
typeName: targetInfo.name,
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
|
|
1952
3001
|
_emitStringToByteSlice(targetType) {
|
|
1953
3002
|
const elemGo = targetType.elem;
|
|
1954
3003
|
const sliceInfo = this.mod.getSliceType(elemGo);
|
|
@@ -2019,6 +3068,162 @@ export class ExprsEmitter {
|
|
|
2019
3068
|
this.releaseTemp(strTmp, "externref");
|
|
2020
3069
|
}
|
|
2021
3070
|
|
|
3071
|
+
// Pushes the code point at UTF-16 index `iTmp` of `strTmp` into `cpTmp`
|
|
3072
|
+
// and advances `iTmp` past it (two units for a surrogate pair).
|
|
3073
|
+
_emitNextCodePoint(strTmp, iTmp, cpTmp) {
|
|
3074
|
+
this.pushInstruction({ op: "local.get", index: strTmp });
|
|
3075
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
3076
|
+
this.pushInstruction({
|
|
3077
|
+
op: "call",
|
|
3078
|
+
funcIndex: this.mod.getStringCodePointAtImportIndex(),
|
|
3079
|
+
});
|
|
3080
|
+
this.pushInstruction({ op: "local.set", index: cpTmp });
|
|
3081
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
3082
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3083
|
+
this.pushInstruction("i32.add");
|
|
3084
|
+
this.pushInstruction({ op: "local.get", index: cpTmp });
|
|
3085
|
+
this.pushInstruction({ op: "i32.const", value: 0xffff });
|
|
3086
|
+
this.pushInstruction("i32.gt_u");
|
|
3087
|
+
this.pushInstruction("i32.add");
|
|
3088
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// Emits `block loop (br_if 1 (i >= n)) body br 0 end end`.
|
|
3092
|
+
_emitCountedLoop(iTmp, nTmp, body) {
|
|
3093
|
+
this.pushInstruction({ op: "block", blockType: "void" });
|
|
3094
|
+
this.pushInstruction({ op: "loop", blockType: "void" });
|
|
3095
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
3096
|
+
this.pushInstruction({ op: "local.get", index: nTmp });
|
|
3097
|
+
this.pushInstruction("i32.ge_s");
|
|
3098
|
+
this.pushInstruction({ op: "br_if", depth: 1 });
|
|
3099
|
+
body();
|
|
3100
|
+
this.pushInstruction({ op: "br", depth: 0 });
|
|
3101
|
+
this.pushInstruction("end");
|
|
3102
|
+
this.pushInstruction("end");
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
// []rune(s): one pass counts the code points, a second fills an exactly
|
|
3106
|
+
// sized array (cap == len, as for every slice GoFront creates).
|
|
3107
|
+
_emitStringToRuneSlice(targetType) {
|
|
3108
|
+
const sliceInfo = this.mod.getSliceType(this._getSliceElemType(targetType));
|
|
3109
|
+
const arrInfo = sliceInfo.arrInfo;
|
|
3110
|
+
const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
|
|
3111
|
+
const strTmp = this.acquireTemp("externref");
|
|
3112
|
+
const unitsTmp = this.acquireTemp("i32");
|
|
3113
|
+
const iTmp = this.acquireTemp("i32");
|
|
3114
|
+
const kTmp = this.acquireTemp("i32");
|
|
3115
|
+
const cpTmp = this.acquireTemp("i32");
|
|
3116
|
+
const arrTmp = this.acquireTemp(arrW);
|
|
3117
|
+
this.pushInstruction({ op: "local.tee", index: strTmp });
|
|
3118
|
+
this.pushInstruction({
|
|
3119
|
+
op: "call",
|
|
3120
|
+
funcIndex: this.mod.getStringLenImportIndex(),
|
|
3121
|
+
});
|
|
3122
|
+
this.pushInstruction({ op: "local.set", index: unitsTmp });
|
|
3123
|
+
const resetIndex = () => {
|
|
3124
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3125
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
3126
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3127
|
+
this.pushInstruction({ op: "local.set", index: kTmp });
|
|
3128
|
+
};
|
|
3129
|
+
const bumpK = () => {
|
|
3130
|
+
this.pushInstruction({ op: "local.get", index: kTmp });
|
|
3131
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3132
|
+
this.pushInstruction("i32.add");
|
|
3133
|
+
this.pushInstruction({ op: "local.set", index: kTmp });
|
|
3134
|
+
};
|
|
3135
|
+
resetIndex();
|
|
3136
|
+
this._emitCountedLoop(iTmp, unitsTmp, () => {
|
|
3137
|
+
this._emitNextCodePoint(strTmp, iTmp, cpTmp);
|
|
3138
|
+
bumpK();
|
|
3139
|
+
});
|
|
3140
|
+
this.pushInstruction({ op: "local.get", index: kTmp });
|
|
3141
|
+
this.pushInstruction({
|
|
3142
|
+
op: "array.new_default",
|
|
3143
|
+
typeIndex: arrInfo.typeIndex,
|
|
3144
|
+
});
|
|
3145
|
+
this.pushInstruction({ op: "local.set", index: arrTmp });
|
|
3146
|
+
resetIndex();
|
|
3147
|
+
this._emitCountedLoop(iTmp, unitsTmp, () => {
|
|
3148
|
+
this._emitNextCodePoint(strTmp, iTmp, cpTmp);
|
|
3149
|
+
this.pushInstruction({ op: "local.get", index: arrTmp });
|
|
3150
|
+
this.pushInstruction({ op: "local.get", index: kTmp });
|
|
3151
|
+
this.pushInstruction({ op: "local.get", index: cpTmp });
|
|
3152
|
+
this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
|
|
3153
|
+
bumpK();
|
|
3154
|
+
});
|
|
3155
|
+
this.pushInstruction({ op: "local.get", index: arrTmp });
|
|
3156
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3157
|
+
this.pushInstruction({ op: "local.get", index: kTmp });
|
|
3158
|
+
this.pushInstruction({ op: "local.get", index: kTmp });
|
|
3159
|
+
this.pushInstruction({ op: "struct.new", typeIndex: sliceInfo.typeIndex });
|
|
3160
|
+
this.releaseTemp(arrTmp, arrW);
|
|
3161
|
+
this.releaseTemp(cpTmp, "i32");
|
|
3162
|
+
this.releaseTemp(kTmp, "i32");
|
|
3163
|
+
this.releaseTemp(iTmp, "i32");
|
|
3164
|
+
this.releaseTemp(unitsTmp, "i32");
|
|
3165
|
+
this.releaseTemp(strTmp, "externref");
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
// string(runes): concatenates `str_from_code_point` of each element.
|
|
3169
|
+
_emitRuneSliceToString(fromType) {
|
|
3170
|
+
const sliceInfo = this.mod.getSliceType(this._getSliceElemType(fromType));
|
|
3171
|
+
const arrInfo = sliceInfo.arrInfo;
|
|
3172
|
+
const sliceW = {
|
|
3173
|
+
kind: "ref",
|
|
3174
|
+
nullable: true,
|
|
3175
|
+
typeIndex: sliceInfo.typeIndex,
|
|
3176
|
+
};
|
|
3177
|
+
const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
|
|
3178
|
+
const sTmp = this.acquireTemp(sliceW);
|
|
3179
|
+
this.pushInstruction({ op: "local.set", index: sTmp });
|
|
3180
|
+
const arrTmp = this.acquireTemp(arrW);
|
|
3181
|
+
const offTmp = this.acquireTemp("i32");
|
|
3182
|
+
const lenTmp = this.acquireTemp("i32");
|
|
3183
|
+
const capTmp = this.acquireTemp("i32");
|
|
3184
|
+
this._emitSliceUnpack(sTmp, sliceInfo, {
|
|
3185
|
+
arr: arrTmp,
|
|
3186
|
+
off: offTmp,
|
|
3187
|
+
len: lenTmp,
|
|
3188
|
+
cap: capTmp,
|
|
3189
|
+
});
|
|
3190
|
+
this.releaseTemp(sTmp, sliceW);
|
|
3191
|
+
this.releaseTemp(capTmp, "i32");
|
|
3192
|
+
const accTmp = this.acquireTemp("externref");
|
|
3193
|
+
this.emitBasicLit({ kind: "BasicLit", litKind: "STRING", value: "" });
|
|
3194
|
+
this.pushInstruction({ op: "local.set", index: accTmp });
|
|
3195
|
+
const iTmp = this.acquireTemp("i32");
|
|
3196
|
+
this.pushInstruction({ op: "i32.const", value: 0 });
|
|
3197
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
3198
|
+
this._emitCountedLoop(iTmp, lenTmp, () => {
|
|
3199
|
+
this.pushInstruction({ op: "local.get", index: accTmp });
|
|
3200
|
+
this.pushInstruction({ op: "local.get", index: arrTmp });
|
|
3201
|
+
this.pushInstruction({ op: "local.get", index: offTmp });
|
|
3202
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
3203
|
+
this.pushInstruction("i32.add");
|
|
3204
|
+
this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
|
|
3205
|
+
this.pushInstruction({
|
|
3206
|
+
op: "call",
|
|
3207
|
+
funcIndex: this.mod.getStringFromCodePointImportIndex(),
|
|
3208
|
+
});
|
|
3209
|
+
this.pushInstruction({
|
|
3210
|
+
op: "call",
|
|
3211
|
+
funcIndex: this.mod.getStringConcatImportIndex(),
|
|
3212
|
+
});
|
|
3213
|
+
this.pushInstruction({ op: "local.set", index: accTmp });
|
|
3214
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
3215
|
+
this.pushInstruction({ op: "i32.const", value: 1 });
|
|
3216
|
+
this.pushInstruction("i32.add");
|
|
3217
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
3218
|
+
});
|
|
3219
|
+
this.pushInstruction({ op: "local.get", index: accTmp });
|
|
3220
|
+
this.releaseTemp(iTmp, "i32");
|
|
3221
|
+
this.releaseTemp(accTmp, "externref");
|
|
3222
|
+
this.releaseTemp(lenTmp, "i32");
|
|
3223
|
+
this.releaseTemp(offTmp, "i32");
|
|
3224
|
+
this.releaseTemp(arrTmp, arrW);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
2022
3227
|
_emitByteSliceToString(fromType) {
|
|
2023
3228
|
const elemGo = fromType.elem;
|
|
2024
3229
|
const sliceInfo = this.mod.getSliceType(elemGo);
|