gofront 1.6.1 → 1.7.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 +35 -39
- package/package.json +37 -4
- package/src/backend/js/expressions.js +323 -37
- package/src/backend/js/index.js +137 -49
- package/src/backend/js/runtime.js +570 -6
- package/src/backend/js/statements.js +36 -6
- package/src/backend/js/stdlib/errors.js +5 -0
- package/src/backend/js/stdlib/index.js +12 -0
- package/src/backend/js/stdlib/json.js +972 -0
- package/src/backend/js/stdlib/simd.js +24 -0
- package/src/backend/wasm/boundary.js +18 -0
- package/src/backend/wasm/emit-builtins.js +17 -0
- package/src/backend/wasm/emit-exprs.js +11 -0
- package/src/backend/wasm/emit.js +9 -0
- package/src/backend/wasm/encode.js +85 -0
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/stdlib/index.js +3 -0
- package/src/backend/wasm/stdlib/simd.js +234 -0
- package/src/backend/wasm/types.js +21 -0
- package/src/backend/wasm/wat.js +37 -0
- package/src/dts-parser.js +71 -14
- package/src/lower/boxing.js +1 -6
- package/src/lower/embedding.js +2 -1
- package/src/parser/struct-tag.js +144 -0
- package/src/parser/types.js +63 -6
- package/src/typechecker/assignability.js +45 -0
- package/src/typechecker/expressions.js +66 -14
- package/src/typechecker/index.js +35 -8
- package/src/typechecker/resolve.js +151 -2
- package/src/typechecker/statements.js +68 -9
- package/src/typechecker/stdlib/extended.js +37 -0
- package/src/typechecker/stdlib/simd.js +73 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +18 -3
- package/src/vendor.js +86 -11
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/backend/js/stdlib/simd.js
|
|
2
|
+
// CodeGen for the `gofront/simd` package in JS packages.
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../index.js').CodeGen} CodeGen */
|
|
5
|
+
|
|
6
|
+
/** @type {ThisType<CodeGen>} */
|
|
7
|
+
export const simdMethods = {
|
|
8
|
+
_genSimd(fn, a) {
|
|
9
|
+
this.useHelper("simd");
|
|
10
|
+
const args = a();
|
|
11
|
+
switch (fn) {
|
|
12
|
+
case "Splat":
|
|
13
|
+
return `__simd_splat(${args[0]})`;
|
|
14
|
+
case "NewF32x4":
|
|
15
|
+
return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
|
|
16
|
+
case "LoadF32x4":
|
|
17
|
+
return `__simd_load(${args[0]}, ${args[1]})`;
|
|
18
|
+
case "StoreF32x4":
|
|
19
|
+
return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
|
|
20
|
+
default:
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -147,6 +147,11 @@ function classifyType(goType, mod) {
|
|
|
147
147
|
return classifyFunc(t, mod);
|
|
148
148
|
case "interface":
|
|
149
149
|
return classifyInterface(t, mod);
|
|
150
|
+
case "simd":
|
|
151
|
+
return {
|
|
152
|
+
k: "unsupported",
|
|
153
|
+
what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
|
|
154
|
+
};
|
|
150
155
|
default:
|
|
151
156
|
return {
|
|
152
157
|
k: "unsupported",
|
|
@@ -211,6 +216,16 @@ function classifyBasic(n) {
|
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
function classifyNamed(t, mod) {
|
|
219
|
+
if (
|
|
220
|
+
t.name === "simd.F32x4" ||
|
|
221
|
+
t.name === "F32x4" ||
|
|
222
|
+
t.underlying?.kind === "simd"
|
|
223
|
+
) {
|
|
224
|
+
return {
|
|
225
|
+
k: "unsupported",
|
|
226
|
+
what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
214
229
|
if (t.name === "error") return { k: "error", wType: "anyref" };
|
|
215
230
|
if (t.name === "time.Duration")
|
|
216
231
|
return { k: "i64", unsigned: false, wType: "i64" };
|
|
@@ -506,6 +521,9 @@ function literalConst(spec, i) {
|
|
|
506
521
|
}
|
|
507
522
|
|
|
508
523
|
function unsupportedMsg(where, what) {
|
|
524
|
+
if (typeof what === "string" && what.includes("boundary")) {
|
|
525
|
+
return `${where}: ${what}`;
|
|
526
|
+
}
|
|
509
527
|
return `${where}: ${what} is not yet supported across the wasm boundary (planned)`;
|
|
510
528
|
}
|
|
511
529
|
|
|
@@ -930,6 +930,9 @@ export class BuiltinsEmitter {
|
|
|
930
930
|
case "base64":
|
|
931
931
|
this.emitBase64Call(func.field, call, targetWasmType);
|
|
932
932
|
return true;
|
|
933
|
+
case "simd":
|
|
934
|
+
this.emitSimdPackageCall(func.field, args, targetWasmType);
|
|
935
|
+
return true;
|
|
933
936
|
default:
|
|
934
937
|
return false;
|
|
935
938
|
}
|
|
@@ -946,6 +949,20 @@ export class BuiltinsEmitter {
|
|
|
946
949
|
return true;
|
|
947
950
|
}
|
|
948
951
|
const recvTypeName = this._getReceiverTypeName(recvType, func.expr);
|
|
952
|
+
if (
|
|
953
|
+
recvTypeName === "simd.F32x4" ||
|
|
954
|
+
recvTypeName === "gofront/simd.F32x4" ||
|
|
955
|
+
recvType?.kind === "simd" ||
|
|
956
|
+
recvType?.underlying?.kind === "simd" ||
|
|
957
|
+
recvType?.name === "simd.F32x4"
|
|
958
|
+
) {
|
|
959
|
+
return this.emitSimdMethodCall(
|
|
960
|
+
func.field,
|
|
961
|
+
func.expr,
|
|
962
|
+
args,
|
|
963
|
+
targetWasmType,
|
|
964
|
+
);
|
|
965
|
+
}
|
|
949
966
|
if (
|
|
950
967
|
this._emitStdlibReceiverMethod(
|
|
951
968
|
recvTypeName,
|
|
@@ -275,6 +275,17 @@ export class ExprsEmitter {
|
|
|
275
275
|
|
|
276
276
|
emitCompositeLit(lit) {
|
|
277
277
|
const litType = lit._type ?? lit.typeExpr?._type ?? lit.typeExpr;
|
|
278
|
+
if (
|
|
279
|
+
litType?.kind === "simd" ||
|
|
280
|
+
litType?.name === "simd.F32x4" ||
|
|
281
|
+
litType?.underlying?.kind === "simd" ||
|
|
282
|
+
(lit.typeExpr?.kind === "SelectorExpr" &&
|
|
283
|
+
lit.typeExpr.field === "F32x4" &&
|
|
284
|
+
lit.typeExpr.expr?.name === "simd")
|
|
285
|
+
) {
|
|
286
|
+
this.emitSimdCompositeLit(lit);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
278
289
|
if (isMapType(litType, this.mod.checker)) {
|
|
279
290
|
const { keyType, valType } = getMapKeyValTypes(litType, this.mod.checker);
|
|
280
291
|
const mapInfo = this.mod.getMapType(keyType, valType);
|
package/src/backend/wasm/emit.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ExtendedPackagesEmitter,
|
|
15
15
|
IoEmitter,
|
|
16
16
|
PurePackagesEmitter,
|
|
17
|
+
SimdEmitter,
|
|
17
18
|
StdlibCoreEmitter,
|
|
18
19
|
TimeEmitter,
|
|
19
20
|
} from "./stdlib/index.js";
|
|
@@ -958,6 +959,11 @@ export class FunctionEmitter {
|
|
|
958
959
|
if (recvType) {
|
|
959
960
|
let t = recvType;
|
|
960
961
|
while (t.kind === "pointer") t = t.base;
|
|
962
|
+
if (t.kind === "SelectorExpr") {
|
|
963
|
+
const pkg = t.expr?.name;
|
|
964
|
+
if (pkg) return `${pkg}.${t.field}`;
|
|
965
|
+
}
|
|
966
|
+
if (t.kind === "simd") return "simd.F32x4";
|
|
961
967
|
if (t.name) return t.name;
|
|
962
968
|
if (t.kind === "named") return t.name;
|
|
963
969
|
if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
|
|
@@ -1106,6 +1112,8 @@ export class FunctionEmitter {
|
|
|
1106
1112
|
this.pushInstruction({ op: "f32.const", value: 0.0 });
|
|
1107
1113
|
} else if (wType === "f64") {
|
|
1108
1114
|
this.pushInstruction({ op: "f64.const", value: 0.0 });
|
|
1115
|
+
} else if (wType === "v128") {
|
|
1116
|
+
this.pushInstruction({ op: "v128.const", bytes: new Array(16).fill(0) });
|
|
1109
1117
|
} else if (
|
|
1110
1118
|
typeof wType === "object" &&
|
|
1111
1119
|
wType !== null &&
|
|
@@ -1402,3 +1410,4 @@ copyMethods(FunctionEmitter, IoEmitter);
|
|
|
1402
1410
|
copyMethods(FunctionEmitter, TimeEmitter);
|
|
1403
1411
|
copyMethods(FunctionEmitter, ExtendedPackagesEmitter);
|
|
1404
1412
|
copyMethods(FunctionEmitter, SharedEmitter);
|
|
1413
|
+
copyMethods(FunctionEmitter, SimdEmitter);
|
|
@@ -393,6 +393,49 @@ const GC_OPCODES = {
|
|
|
393
393
|
"extern.convert_any": 0x1b,
|
|
394
394
|
};
|
|
395
395
|
|
|
396
|
+
// 0xFD prefixed SIMD instructions (Wasm SIMD / v128)
|
|
397
|
+
const SIMD_OPCODES = {
|
|
398
|
+
"v128.load": 0,
|
|
399
|
+
"v128.store": 11,
|
|
400
|
+
"v128.const": 12,
|
|
401
|
+
"i8x16.shuffle": 13,
|
|
402
|
+
"i8x16.swizzle": 14,
|
|
403
|
+
"i8x16.splat": 15,
|
|
404
|
+
"i16x8.splat": 16,
|
|
405
|
+
"i32x4.splat": 17,
|
|
406
|
+
"i64x2.splat": 18,
|
|
407
|
+
"f32x4.splat": 19,
|
|
408
|
+
"f64x2.splat": 20,
|
|
409
|
+
"i8x16.extract_lane_s": 21,
|
|
410
|
+
"i8x16.extract_lane_u": 22,
|
|
411
|
+
"i8x16.replace_lane": 23,
|
|
412
|
+
"i16x8.extract_lane_s": 24,
|
|
413
|
+
"i16x8.extract_lane_u": 25,
|
|
414
|
+
"i16x8.replace_lane": 26,
|
|
415
|
+
"i32x4.extract_lane": 27,
|
|
416
|
+
"i32x4.replace_lane": 28,
|
|
417
|
+
"i64x2.extract_lane": 29,
|
|
418
|
+
"i64x2.replace_lane": 30,
|
|
419
|
+
"f32x4.extract_lane": 31,
|
|
420
|
+
"f32x4.replace_lane": 32,
|
|
421
|
+
"v128.not": 77,
|
|
422
|
+
"v128.and": 78,
|
|
423
|
+
"v128.andnot": 79,
|
|
424
|
+
"v128.or": 80,
|
|
425
|
+
"v128.xor": 81,
|
|
426
|
+
"f32x4.abs": 224,
|
|
427
|
+
"f32x4.neg": 225,
|
|
428
|
+
"f32x4.sqrt": 227,
|
|
429
|
+
"f32x4.add": 228,
|
|
430
|
+
"f32x4.sub": 229,
|
|
431
|
+
"f32x4.mul": 230,
|
|
432
|
+
"f32x4.div": 231,
|
|
433
|
+
"f32x4.min": 232,
|
|
434
|
+
"f32x4.max": 233,
|
|
435
|
+
"f32x4.pmin": 234,
|
|
436
|
+
"f32x4.pmax": 235,
|
|
437
|
+
};
|
|
438
|
+
|
|
396
439
|
// Natural alignment (log2) per load/store opcode; memarg = align, offset.
|
|
397
440
|
const MEM_ALIGN = {
|
|
398
441
|
"i32.load": 2,
|
|
@@ -433,6 +476,48 @@ function encodeInstruction(inst) {
|
|
|
433
476
|
return [0xfc, ...encodeU32LEB(SAT_TRUNC_OPCODES[op])];
|
|
434
477
|
}
|
|
435
478
|
|
|
479
|
+
// 2. Check SIMD opcodes (0xFD prefix)
|
|
480
|
+
if (SIMD_OPCODES[op] !== undefined) {
|
|
481
|
+
const subOp = SIMD_OPCODES[op];
|
|
482
|
+
const bytes = [0xfd, ...encodeU32LEB(subOp)];
|
|
483
|
+
if (op === "v128.load" || op === "v128.store") {
|
|
484
|
+
bytes.push(...encodeU32LEB(inst.align ?? 2));
|
|
485
|
+
bytes.push(...encodeU32LEB(inst.offset ?? 0));
|
|
486
|
+
return bytes;
|
|
487
|
+
}
|
|
488
|
+
if (op === "v128.const") {
|
|
489
|
+
const prefixLen = bytes.length;
|
|
490
|
+
if (inst.bytes) {
|
|
491
|
+
for (let i = 0; i < 16; i++) {
|
|
492
|
+
bytes.push(inst.bytes[i] ?? 0);
|
|
493
|
+
}
|
|
494
|
+
} else if (inst.values) {
|
|
495
|
+
const vals = inst.values.slice(0, 4);
|
|
496
|
+
for (const val of vals) {
|
|
497
|
+
bytes.push(...encodeF32(val));
|
|
498
|
+
}
|
|
499
|
+
while (bytes.length < prefixLen + 16) {
|
|
500
|
+
bytes.push(0);
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
for (let i = 0; i < 16; i++) bytes.push(0);
|
|
504
|
+
}
|
|
505
|
+
return bytes;
|
|
506
|
+
}
|
|
507
|
+
if (op === "i8x16.shuffle") {
|
|
508
|
+
const lanes = inst.lanes ?? inst.indices ?? [];
|
|
509
|
+
for (let i = 0; i < 16; i++) {
|
|
510
|
+
bytes.push(lanes[i] ?? 0);
|
|
511
|
+
}
|
|
512
|
+
return bytes;
|
|
513
|
+
}
|
|
514
|
+
if (op.includes("extract_lane") || op.includes("replace_lane")) {
|
|
515
|
+
bytes.push((inst.lane ?? inst.index ?? 0) & 0xff);
|
|
516
|
+
return bytes;
|
|
517
|
+
}
|
|
518
|
+
return bytes;
|
|
519
|
+
}
|
|
520
|
+
|
|
436
521
|
// 2. Check WasmGC opcodes (0xFB prefix)
|
|
437
522
|
if (GC_OPCODES[op] !== undefined) {
|
|
438
523
|
const subOp = GC_OPCODES[op];
|
|
@@ -24,8 +24,8 @@ import { join } from "node:path";
|
|
|
24
24
|
import { GOFRONT_SECTION_BYTES, isGoFrontWasm } from "./encode.js";
|
|
25
25
|
|
|
26
26
|
// Binaryen feature bitmask: GC (1024) | ReferenceTypes (256) | BulkMemory (16) | Multivalue (512) |
|
|
27
|
-
// ExceptionHandling (64) | MutableGlobals (2) | NontrappingFPToInt (4) | TailCall (128) =
|
|
28
|
-
export const DEFAULT_WASM_FEATURES =
|
|
27
|
+
// ExceptionHandling (64) | MutableGlobals (2) | NontrappingFPToInt (4) | TailCall (128) | SIMD (8) = 2014
|
|
28
|
+
export const DEFAULT_WASM_FEATURES = 2014;
|
|
29
29
|
|
|
30
30
|
let _nativeWasmOptAvailable = null;
|
|
31
31
|
let _npmWasmOptPath = null;
|
|
@@ -98,6 +98,7 @@ function runWasmOpt(
|
|
|
98
98
|
"--enable-mutable-globals",
|
|
99
99
|
"--enable-nontrapping-float-to-int",
|
|
100
100
|
"--enable-tail-call",
|
|
101
|
+
"--enable-simd",
|
|
101
102
|
];
|
|
102
103
|
if (options.gufa !== false) {
|
|
103
104
|
args.push("--gufa");
|
|
@@ -141,6 +142,7 @@ function runWasmOpt(
|
|
|
141
142
|
"--enable-mutable-globals",
|
|
142
143
|
"--enable-nontrapping-float-to-int",
|
|
143
144
|
"--enable-tail-call",
|
|
145
|
+
"--enable-simd",
|
|
144
146
|
output,
|
|
145
147
|
"-S",
|
|
146
148
|
"-o",
|
|
@@ -13,6 +13,7 @@ import { BINARY_MATH_IMPORTS, StdlibCoreEmitter } from "./core.js";
|
|
|
13
13
|
import { IoEmitter } from "./io.js";
|
|
14
14
|
import { ExtendedPackagesEmitter } from "./packages.js";
|
|
15
15
|
import { PurePackagesEmitter } from "./pure.js";
|
|
16
|
+
import { SimdEmitter } from "./simd.js";
|
|
16
17
|
import { TimeEmitter } from "./time.js";
|
|
17
18
|
|
|
18
19
|
// Aliases for backwards compatibility
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
IoEmitter as Phase7Emitter,
|
|
25
26
|
PurePackagesEmitter,
|
|
26
27
|
PurePackagesEmitter as Phase6Emitter,
|
|
28
|
+
SimdEmitter,
|
|
27
29
|
StdlibCoreEmitter,
|
|
28
30
|
StdlibCoreEmitter as StdlibEmitter,
|
|
29
31
|
TimeEmitter,
|
|
@@ -37,4 +39,5 @@ export const stdlibEmitters = [
|
|
|
37
39
|
IoEmitter,
|
|
38
40
|
TimeEmitter,
|
|
39
41
|
ExtendedPackagesEmitter,
|
|
42
|
+
SimdEmitter,
|
|
40
43
|
];
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/backend/wasm/stdlib/simd.js
|
|
2
|
+
// FunctionEmitter mixin: gofront/simd (F32x4 vector math, load/store, composite literals).
|
|
3
|
+
|
|
4
|
+
const SIMD_BINARY_OPS = {
|
|
5
|
+
Add: "f32x4.add",
|
|
6
|
+
Sub: "f32x4.sub",
|
|
7
|
+
Mul: "f32x4.mul",
|
|
8
|
+
Div: "f32x4.div",
|
|
9
|
+
Min: "f32x4.min",
|
|
10
|
+
Max: "f32x4.max",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class SimdEmitter {
|
|
14
|
+
emitSimdCompositeLit(lit) {
|
|
15
|
+
const elems = lit.elems ?? [];
|
|
16
|
+
if (elems.length === 0) {
|
|
17
|
+
this.pushInstruction({ op: "v128.const", bytes: new Array(16).fill(0) });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const constVals = elems.map((e) => this._getConstFloatValue(e));
|
|
22
|
+
const allConst = constVals.every((v) => v !== null);
|
|
23
|
+
|
|
24
|
+
if (allConst) {
|
|
25
|
+
const vals = [0, 0, 0, 0];
|
|
26
|
+
for (let i = 0; i < Math.min(elems.length, 4); i++) {
|
|
27
|
+
vals[i] = constVals[i];
|
|
28
|
+
}
|
|
29
|
+
this.pushInstruction({ op: "v128.const", values: vals });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Runtime elements: splat lane 0, replace lanes 1, 2, 3
|
|
34
|
+
this.emitExpr(elems[0], "f32");
|
|
35
|
+
this.pushInstruction("f32x4.splat");
|
|
36
|
+
for (let i = 1; i < 4; i++) {
|
|
37
|
+
if (elems[i]) {
|
|
38
|
+
this.emitExpr(elems[i], "f32");
|
|
39
|
+
} else {
|
|
40
|
+
this.pushInstruction({ op: "f32.const", value: 0.0 });
|
|
41
|
+
}
|
|
42
|
+
this.pushInstruction({ op: "f32x4.replace_lane", lane: i });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_emitSimdDot(recvExpr, otherExpr, count) {
|
|
47
|
+
this.emitExpr(recvExpr, "v128");
|
|
48
|
+
this.emitExpr(otherExpr, "v128");
|
|
49
|
+
this.pushInstruction("f32x4.mul");
|
|
50
|
+
const tmp = this.acquireTemp("v128");
|
|
51
|
+
this.pushInstruction({ op: "local.set", index: tmp });
|
|
52
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
53
|
+
this.pushInstruction({ op: "f32x4.extract_lane", lane: 0 });
|
|
54
|
+
for (let i = 1; i < count; i++) {
|
|
55
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
56
|
+
this.pushInstruction({ op: "f32x4.extract_lane", lane: i });
|
|
57
|
+
this.pushInstruction("f32.add");
|
|
58
|
+
}
|
|
59
|
+
this.releaseTemp(tmp, "v128");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
emitSimdMethodCall(method, recvExpr, args, _targetWasmType) {
|
|
63
|
+
const op = SIMD_BINARY_OPS[method];
|
|
64
|
+
if (op) {
|
|
65
|
+
this.emitExpr(recvExpr, "v128");
|
|
66
|
+
this.emitExpr(args[0], "v128");
|
|
67
|
+
this.pushInstruction(op);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (method === "Dot3" || method === "Dot4") {
|
|
71
|
+
this._emitSimdDot(recvExpr, args[0], method === "Dot3" ? 3 : 4);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (method === "Lane") {
|
|
75
|
+
const laneIdx = this._constIntValue(args[0]) ?? 0;
|
|
76
|
+
this.emitExpr(recvExpr, "v128");
|
|
77
|
+
this.pushInstruction({ op: "f32x4.extract_lane", lane: laneIdx });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (method === "WithLane") {
|
|
81
|
+
const laneIdx = this._constIntValue(args[0]) ?? 0;
|
|
82
|
+
this.emitExpr(recvExpr, "v128");
|
|
83
|
+
this.emitExpr(args[1], "f32");
|
|
84
|
+
this.pushInstruction({ op: "f32x4.replace_lane", lane: laneIdx });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (method === "Splat") {
|
|
88
|
+
this.emitExpr(args[0], "f32");
|
|
89
|
+
this.pushInstruction("f32x4.splat");
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
emitSimdPackageCall(fn, args, _targetWasmType) {
|
|
96
|
+
switch (fn) {
|
|
97
|
+
case "Splat":
|
|
98
|
+
this.emitExpr(args[0], "f32");
|
|
99
|
+
this.pushInstruction("f32x4.splat");
|
|
100
|
+
return true;
|
|
101
|
+
case "NewF32x4":
|
|
102
|
+
this.emitExpr(args[0], "f32");
|
|
103
|
+
this.pushInstruction("f32x4.splat");
|
|
104
|
+
this.emitExpr(args[1], "f32");
|
|
105
|
+
this.pushInstruction({ op: "f32x4.replace_lane", lane: 1 });
|
|
106
|
+
this.emitExpr(args[2], "f32");
|
|
107
|
+
this.pushInstruction({ op: "f32x4.replace_lane", lane: 2 });
|
|
108
|
+
this.emitExpr(args[3], "f32");
|
|
109
|
+
this.pushInstruction({ op: "f32x4.replace_lane", lane: 3 });
|
|
110
|
+
return true;
|
|
111
|
+
case "LoadF32x4":
|
|
112
|
+
this.emitSimdLoad(args[0], args[1]);
|
|
113
|
+
return true;
|
|
114
|
+
case "StoreF32x4":
|
|
115
|
+
this.emitSimdStore(args[0], args[1], args[2]);
|
|
116
|
+
return true;
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_emitSharedFloat32Address(bufNode, indexNode) {
|
|
123
|
+
const { wType, typeIndex } = this.mod.getSharedType();
|
|
124
|
+
const bufTmp = this.acquireTemp(wType);
|
|
125
|
+
const iTmp = this.acquireTemp("i32");
|
|
126
|
+
|
|
127
|
+
this.emitExpr(bufNode, wType);
|
|
128
|
+
this.pushInstruction({ op: "local.set", index: bufTmp });
|
|
129
|
+
|
|
130
|
+
// Bounds check 1: if unsigned(len) < 4 branch to OOB
|
|
131
|
+
this.pushInstruction({ op: "local.get", index: bufTmp });
|
|
132
|
+
this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
|
|
133
|
+
this.pushInstruction({ op: "i32.const", value: 4 });
|
|
134
|
+
this.pushInstruction("i32.lt_u");
|
|
135
|
+
this._emitBoundsBranch();
|
|
136
|
+
|
|
137
|
+
if (this._isI32IndexExpr(indexNode)) {
|
|
138
|
+
this.emitExpr(indexNode, "i32");
|
|
139
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
140
|
+
|
|
141
|
+
// Bounds check: if unsigned(i) > len - 4 branch to OOB
|
|
142
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
143
|
+
this.pushInstruction({ op: "local.get", index: bufTmp });
|
|
144
|
+
this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
|
|
145
|
+
this.pushInstruction({ op: "i32.const", value: 4 });
|
|
146
|
+
this.pushInstruction("i32.sub");
|
|
147
|
+
this.pushInstruction("i32.gt_u");
|
|
148
|
+
this._emitBoundsBranch();
|
|
149
|
+
} else {
|
|
150
|
+
const idx64 = this.acquireTemp("i64");
|
|
151
|
+
this.emitExpr(indexNode, "i64");
|
|
152
|
+
this.pushInstruction({ op: "local.set", index: idx64 });
|
|
153
|
+
|
|
154
|
+
// Bounds check in 64-bit: if unsigned(idx64) > unsigned(len - 4) branch to OOB
|
|
155
|
+
this.pushInstruction({ op: "local.get", index: idx64 });
|
|
156
|
+
this.pushInstruction({ op: "local.get", index: bufTmp });
|
|
157
|
+
this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
|
|
158
|
+
this.pushInstruction({ op: "i32.const", value: 4 });
|
|
159
|
+
this.pushInstruction("i32.sub");
|
|
160
|
+
this.pushInstruction("i64.extend_i32_u");
|
|
161
|
+
this.pushInstruction("i64.gt_u");
|
|
162
|
+
this._emitBoundsBranch();
|
|
163
|
+
|
|
164
|
+
this.pushInstruction({ op: "local.get", index: idx64 });
|
|
165
|
+
this.pushInstruction("i32.wrap_i64");
|
|
166
|
+
this.pushInstruction({ op: "local.set", index: iTmp });
|
|
167
|
+
this.releaseTemp(idx64, "i64");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Address: buf.base + (i << 2)
|
|
171
|
+
this.pushInstruction({ op: "local.get", index: bufTmp });
|
|
172
|
+
this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 0 });
|
|
173
|
+
this.pushInstruction({ op: "local.get", index: iTmp });
|
|
174
|
+
this.pushInstruction({ op: "i32.const", value: 2 });
|
|
175
|
+
this.pushInstruction("i32.shl");
|
|
176
|
+
this.pushInstruction("i32.add");
|
|
177
|
+
|
|
178
|
+
this.releaseTemp(bufTmp, wType);
|
|
179
|
+
this.releaseTemp(iTmp, "i32");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
emitSimdLoad(bufNode, indexNode) {
|
|
183
|
+
this._emitSharedFloat32Address(bufNode, indexNode);
|
|
184
|
+
this.pushInstruction({ op: "v128.load", align: 2, offset: 0 });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
emitSimdStore(bufNode, indexNode, valNode) {
|
|
188
|
+
this._emitSharedFloat32Address(bufNode, indexNode);
|
|
189
|
+
this.emitExpr(valNode, "v128");
|
|
190
|
+
this.pushInstruction({ op: "v128.store", align: 2, offset: 0 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_constIntValue(node) {
|
|
194
|
+
if (!node) return null;
|
|
195
|
+
if (node.kind === "BasicLit" && node.litKind === "INT") {
|
|
196
|
+
return Number(node.value);
|
|
197
|
+
}
|
|
198
|
+
if (node.kind === "Ident") {
|
|
199
|
+
const c = this.mod?.resolveConst?.(node.name);
|
|
200
|
+
if (c && c.kind === "BasicLit") {
|
|
201
|
+
return Number(c.value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (node.kind === "ParenExpr") {
|
|
205
|
+
return this._constIntValue(node.expr);
|
|
206
|
+
}
|
|
207
|
+
if (node.kind === "UnaryExpr" && node.op === "+") {
|
|
208
|
+
return this._constIntValue(node.operand);
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_getConstFloatValue(e) {
|
|
214
|
+
if (!e) return null;
|
|
215
|
+
if (
|
|
216
|
+
e.kind === "BasicLit" &&
|
|
217
|
+
(e.litKind === "FLOAT" || e.litKind === "INT")
|
|
218
|
+
) {
|
|
219
|
+
return Number(e.value);
|
|
220
|
+
}
|
|
221
|
+
if (e.kind === "UnaryExpr" && (e.op === "-" || e.op === "+")) {
|
|
222
|
+
const v = this._getConstFloatValue(e.operand);
|
|
223
|
+
return v !== null ? (e.op === "-" ? -v : v) : null;
|
|
224
|
+
}
|
|
225
|
+
if (e.kind === "Ident") {
|
|
226
|
+
const c = this.mod?.resolveConst?.(e.name);
|
|
227
|
+
if (c) return this._getConstFloatValue(c);
|
|
228
|
+
}
|
|
229
|
+
if (e.kind === "ParenExpr") {
|
|
230
|
+
return this._getConstFloatValue(e.expr);
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -27,6 +27,7 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
27
27
|
goType === "i64" ||
|
|
28
28
|
goType === "f32" ||
|
|
29
29
|
goType === "f64" ||
|
|
30
|
+
goType === "v128" ||
|
|
30
31
|
goType === "anyref" ||
|
|
31
32
|
goType === "externref" ||
|
|
32
33
|
goType === "funcref"
|
|
@@ -39,6 +40,16 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
39
40
|
}
|
|
40
41
|
if (isTestingT(goType)) return "externref";
|
|
41
42
|
|
|
43
|
+
if (
|
|
44
|
+
goType.kind === "simd" ||
|
|
45
|
+
(goType.kind === "named" &&
|
|
46
|
+
(goType.name === "simd.F32x4" ||
|
|
47
|
+
goType.name === "F32x4" ||
|
|
48
|
+
goType.underlying?.kind === "simd"))
|
|
49
|
+
) {
|
|
50
|
+
return "v128";
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
// Pointer AST nodes
|
|
43
54
|
if (goType.kind === "PointerType") {
|
|
44
55
|
return toWasmType({ kind: "pointer", base: goType.base }, checker, mod);
|
|
@@ -68,6 +79,9 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
68
79
|
|
|
69
80
|
// TypeName AST node
|
|
70
81
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
82
|
+
if (goType.name === "simd.F32x4" || goType.name === "F32x4") {
|
|
83
|
+
return "v128";
|
|
84
|
+
}
|
|
71
85
|
if (mod?.getStructType(goType.name)) {
|
|
72
86
|
return {
|
|
73
87
|
kind: "ref",
|
|
@@ -84,6 +98,13 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
84
98
|
|
|
85
99
|
// Unwrap named types
|
|
86
100
|
if (goType.kind === "named") {
|
|
101
|
+
if (
|
|
102
|
+
goType.name === "simd.F32x4" ||
|
|
103
|
+
goType.name === "F32x4" ||
|
|
104
|
+
goType.underlying?.kind === "simd"
|
|
105
|
+
) {
|
|
106
|
+
return "v128";
|
|
107
|
+
}
|
|
87
108
|
if (mod?.getStructType(goType.name)) {
|
|
88
109
|
return {
|
|
89
110
|
kind: "ref",
|
package/src/backend/wasm/wat.js
CHANGED
|
@@ -98,6 +98,43 @@ function formatInstruction(inst) {
|
|
|
98
98
|
case "i32.store8":
|
|
99
99
|
case "i32.store16":
|
|
100
100
|
return inst.offset ? `${op} offset=${inst.offset}` : op;
|
|
101
|
+
case "v128.load":
|
|
102
|
+
case "v128.store": {
|
|
103
|
+
const parts = [op];
|
|
104
|
+
if (inst.offset) parts.push(`offset=${inst.offset}`);
|
|
105
|
+
if (inst.align !== undefined) parts.push(`align=${1 << inst.align}`);
|
|
106
|
+
return parts.join(" ");
|
|
107
|
+
}
|
|
108
|
+
case "v128.const": {
|
|
109
|
+
if (inst.values && inst.values.length === 4) {
|
|
110
|
+
return `${op} f32x4 ${inst.values.join(" ")}`;
|
|
111
|
+
}
|
|
112
|
+
if (inst.bytes) {
|
|
113
|
+
const b = new Uint8Array(16);
|
|
114
|
+
b.set(inst.bytes.slice(0, 16));
|
|
115
|
+
const dv = new DataView(b.buffer);
|
|
116
|
+
return `${op} i32x4 ${dv.getInt32(0, true)} ${dv.getInt32(4, true)} ${dv.getInt32(8, true)} ${dv.getInt32(12, true)}`;
|
|
117
|
+
}
|
|
118
|
+
return `${op} i32x4 0 0 0 0`;
|
|
119
|
+
}
|
|
120
|
+
case "i8x16.shuffle": {
|
|
121
|
+
const lanes = (inst.lanes ?? inst.indices ?? []).slice(0, 16);
|
|
122
|
+
while (lanes.length < 16) lanes.push(0);
|
|
123
|
+
return `${op} ${lanes.join(" ")}`;
|
|
124
|
+
}
|
|
125
|
+
case "i8x16.extract_lane_s":
|
|
126
|
+
case "i8x16.extract_lane_u":
|
|
127
|
+
case "i8x16.replace_lane":
|
|
128
|
+
case "i16x8.extract_lane_s":
|
|
129
|
+
case "i16x8.extract_lane_u":
|
|
130
|
+
case "i16x8.replace_lane":
|
|
131
|
+
case "i32x4.extract_lane":
|
|
132
|
+
case "i32x4.replace_lane":
|
|
133
|
+
case "i64x2.extract_lane":
|
|
134
|
+
case "i64x2.replace_lane":
|
|
135
|
+
case "f32x4.extract_lane":
|
|
136
|
+
case "f32x4.replace_lane":
|
|
137
|
+
return `${op} ${inst.lane ?? inst.index ?? 0}`;
|
|
101
138
|
case "i32.const":
|
|
102
139
|
case "i64.const":
|
|
103
140
|
return `${op} ${inst.value ?? 0}`;
|