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.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -3,12 +3,24 @@
3
3
  // math/bits, maps, slices, strings, strconv, fmt, errors, sort, unicode/utf8,
4
4
  // testing) plus the runtime helper bodies they rely on.
5
5
 
6
+ import {
7
+ fmtDescHasFloat,
8
+ fmtDescIsSeq,
9
+ fmtTypeDesc,
10
+ fmtWantsTypes,
11
+ } from "../../shared/fmt-types.js";
6
12
  import {
7
13
  ERROR_STRUCT,
8
14
  getMapKeyValTypes,
15
+ getSliceElemType,
9
16
  goTypeKey,
17
+ isAnyType,
10
18
  isArrayType,
19
+ isBoolGoType,
20
+ isComplex,
21
+ isComplex64,
11
22
  isErrorType,
23
+ isFuncType,
12
24
  isMapType,
13
25
  isPointerToStruct,
14
26
  isSigned,
@@ -18,6 +30,45 @@ import {
18
30
  toWasmType,
19
31
  } from "../types.js";
20
32
 
33
+ function _isPointerKind(t) {
34
+ return (
35
+ t?.kind === "pointer" || t?.kind === "PointerType" || t?.kind === "StarExpr"
36
+ );
37
+ }
38
+
39
+ // Whether `sig` is `Error() string` / `String() string` and in the method set
40
+ // of the operand (a pointer receiver needs a pointer operand).
41
+ function _isFmtMethodSig(sig, isPtr) {
42
+ if (!sig || sig.params?.length || sig.variadic) return false;
43
+ if (sig._ptrRecv && !isPtr) return false;
44
+ const ret = sig.returns ?? [];
45
+ return ret.length === 1 && ret[0]?.name === "string";
46
+ }
47
+
48
+ function _isUint32(goType) {
49
+ let t = goType;
50
+ while (t && (t.kind === "named" || t.kind === "TypeName") && t.underlying) {
51
+ t = t.underlying;
52
+ }
53
+ return t?.name === "uint32";
54
+ }
55
+
56
+ function _isSimplePrimitive(t) {
57
+ const n = t?.name ?? t;
58
+ return n === "string" || n === "bool";
59
+ }
60
+
61
+ function _getStructOrComplexInfo(mod, emitter, goType) {
62
+ if (isComplex(goType)) {
63
+ const is64 = isComplex64(goType);
64
+ return mod.getComplexType(is64 ? "complex64" : "complex128");
65
+ }
66
+ return (
67
+ emitter._resolveStructInfo({ _type: goType }) ??
68
+ (goType?.name ? mod.getStructType(goType.name) : null)
69
+ );
70
+ }
71
+
21
72
  // `unicode/utf8` package constants.
22
73
  const UTF8_CONSTS = {
23
74
  RuneError: 0xfffd,
@@ -86,6 +137,20 @@ export class StdlibCoreEmitter {
86
137
  );
87
138
  }
88
139
 
140
+ _isBitsConst(expr) {
141
+ return this._isPkgExpr(expr, "bits") && expr.field === "UintSize";
142
+ }
143
+
144
+ emitBitsConst(name, targetWasmType) {
145
+ if (name === "UintSize") {
146
+ if (targetWasmType === "i32") {
147
+ this.pushInstruction({ op: "i32.const", value: 64 });
148
+ } else {
149
+ this.pushInstruction({ op: "i64.const", value: 64n });
150
+ }
151
+ }
152
+ }
153
+
89
154
  emitUtf8Const(name, targetWasmType) {
90
155
  const v = UTF8_CONSTS[name];
91
156
  if ((targetWasmType ?? "i32") === "i64") {
@@ -337,8 +402,10 @@ export class StdlibCoreEmitter {
337
402
  Cbrt: "cbrt",
338
403
  Exp: "exp",
339
404
  Log: "log",
405
+ Log1p: "log1p",
340
406
  Log2: "log2",
341
407
  Log10: "log10",
408
+ Expm1: "expm1",
342
409
  Round: "round",
343
410
  Sinh: "sinh",
344
411
  Cosh: "cosh",
@@ -364,7 +431,7 @@ export class StdlibCoreEmitter {
364
431
  emitTestingCall(func, args) {
365
432
  const method = func.field;
366
433
  const nameIdx = this.mod.internString(method);
367
- if (method === "Run") {
434
+ if (method === "Run" && args.length > 0) {
368
435
  this.emitExpr(func.expr, "externref");
369
436
  this.emitExpr(args[0], "externref");
370
437
  this.emitExpr(args[1], "anyref");
@@ -374,6 +441,15 @@ export class StdlibCoreEmitter {
374
441
  });
375
442
  return;
376
443
  }
444
+ if (method === "Cleanup") {
445
+ this.emitExpr(func.expr, "externref");
446
+ this.emitExpr(args[0], "anyref");
447
+ this.pushInstruction({
448
+ op: "call",
449
+ funcIndex: this.mod.getStdlibImportIndex("testing_cleanup"),
450
+ });
451
+ return;
452
+ }
377
453
  if (method === "Name") {
378
454
  this.emitExpr(func.expr, "externref");
379
455
  this.pushInstruction({
@@ -382,6 +458,60 @@ export class StdlibCoreEmitter {
382
458
  });
383
459
  return;
384
460
  }
461
+ if (method === "Loop") {
462
+ this.emitExpr(func.expr, "externref");
463
+ this.pushInstruction({ op: "i32.const", value: nameIdx });
464
+ this.pushInstruction({
465
+ op: "call",
466
+ funcIndex: this.mod.getTestingFlagImportIndex(),
467
+ });
468
+ return;
469
+ }
470
+ if (method === "Run" && args.length === 0) {
471
+ this.emitExpr(func.expr, "externref");
472
+ this.pushInstruction({ op: "i32.const", value: nameIdx });
473
+ this.pushInstruction({
474
+ op: "call",
475
+ funcIndex: this.mod.getTestingFlagImportIndex(),
476
+ });
477
+ this.pushInstruction("i64.extend_i32_s");
478
+ return;
479
+ }
480
+ if (method === "Elapsed") {
481
+ this.emitExpr(func.expr, "externref");
482
+ this.pushInstruction({
483
+ op: "call",
484
+ funcIndex: this.mod.getStdlibImportIndex("testing_elapsed"),
485
+ });
486
+ return;
487
+ }
488
+ if (method === "Context") {
489
+ this.emitExpr(func.expr, "externref");
490
+ this.pushInstruction({
491
+ op: "call",
492
+ funcIndex: this.mod.getStdlibImportIndex("testing_context"),
493
+ });
494
+ this.pushInstruction("any.convert_extern");
495
+ return;
496
+ }
497
+ if (method === "Output") {
498
+ this.emitExpr(func.expr, "externref");
499
+ this.pushInstruction({
500
+ op: "call",
501
+ funcIndex: this.mod.getStdlibImportIndex("testing_output"),
502
+ });
503
+ this.pushInstruction("any.convert_extern");
504
+ return;
505
+ }
506
+ if (method === "Deadline") {
507
+ this.emitExpr(func.expr, "externref");
508
+ this.pushInstruction("drop");
509
+ const timeType = this.mod.checker?.types?.get("time.Time");
510
+ const timeWType = this.toWasmType(timeType);
511
+ this.emitZeroValue(timeType, timeWType);
512
+ this.pushInstruction({ op: "i32.const", value: 0 });
513
+ return;
514
+ }
385
515
  if (method === "Failed" || method === "Skipped") {
386
516
  this.emitExpr(func.expr, "externref");
387
517
  this.pushInstruction({ op: "i32.const", value: nameIdx });
@@ -406,29 +536,133 @@ export class StdlibCoreEmitter {
406
536
  // argument itself, or `Error()` on an `error` operand) happens in the first
407
537
  // phase: such code can call Sprintf and reset the JS-side argument buffer,
408
538
  _isCompositeGoType(goType) {
409
- if (!goType) return false;
539
+ if (!goType || _isSimplePrimitive(goType)) return false;
410
540
  if (isErrorType(goType) && this.mod.hasErrorType) return false;
411
- if (goType.name === "string" || goType === "string") return false;
412
- if (goType.name === "bool" || goType === "bool") return false;
413
541
  return (
542
+ isComplex(goType) ||
543
+ toWasmType(goType, this.mod.checker, this.mod) === "v128" ||
414
544
  isSliceType(goType, this.mod.checker) ||
415
545
  isArrayType(goType, this.mod.checker) ||
416
546
  isMapType(goType, this.mod.checker) ||
417
547
  isStructType(goType, this.mod.checker, this.mod) ||
418
548
  isPointerToStruct(goType, this.mod.checker, this.mod) ||
419
- goType.kind === "pointer" ||
420
- goType.kind === "PointerType" ||
421
- goType.kind === "StarExpr"
549
+ _isPointerKind(goType)
550
+ );
551
+ }
552
+
553
+ _isFmtExternOperand(goType) {
554
+ const name = goType?.name;
555
+ if (name === "time.Duration") {
556
+ return true;
557
+ }
558
+ return (
559
+ isFuncType(goType, this.mod.checker) ||
560
+ isAnyType(goType, this.mod.checker) ||
561
+ this._isCompositeGoType(goType) ||
562
+ this._fmtMethodOf(goType) !== null
422
563
  );
423
564
  }
424
565
 
566
+ // Pushes `arg` as the externref the fmt host helpers format.
567
+ _emitOperandExternref(arg, goType, allowNil = false) {
568
+ if (goType?.name === "time.Duration") {
569
+ // The raw count stays available for %d; %v and %s use the string.
570
+ const durTmp = this.acquireTemp("i64");
571
+ this.emitExpr(arg, "i64");
572
+ this.pushInstruction({ op: "local.set", index: durTmp });
573
+ this.pushInstruction({ op: "local.get", index: durTmp });
574
+ this._emitToExternrefPlain({ kind: "basic", name: "int64" });
575
+ this.pushInstruction({ op: "local.get", index: durTmp });
576
+ this._callStdlib("time_duration_string");
577
+ this.pushInstruction({ op: "i32.const", value: 0 });
578
+ this.pushInstruction({
579
+ op: "call",
580
+ funcIndex: this.mod.getFmtNewStringerIndex(),
581
+ });
582
+ this.releaseTemp(durTmp, "i64");
583
+ return;
584
+ }
585
+ if (isAnyType(goType, this.mod.checker)) {
586
+ this.emitExpr(arg, "anyref");
587
+ this._emitAnyToExternref();
588
+ return;
589
+ }
590
+ if (isFuncType(goType, this.mod.checker)) {
591
+ // A func value has no address in WebAssembly: a nil one prints
592
+ // <nil>, any other prints a fixed address-style text.
593
+ this.emitExpr(arg, toWasmType(goType, this.mod.checker, this.mod));
594
+ this.pushInstruction("ref.is_null");
595
+ this.pushInstruction({ op: "if", blockType: "externref" });
596
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
597
+ this.pushInstruction("else");
598
+ this.pushInstruction({
599
+ op: "i32.const",
600
+ value: this.mod.internString("0x1"),
601
+ });
602
+ this.pushInstruction({
603
+ op: "call",
604
+ funcIndex: this.mod.getStringImportIndex(),
605
+ });
606
+ this.pushInstruction("end");
607
+ return;
608
+ }
609
+ this.emitExpr(arg, toWasmType(goType, this.mod.checker, this.mod));
610
+ this.emitToExternref(goType, false, new Set(), allowNil);
611
+ }
612
+
613
+ // Turns the `any` on the stack into the externref the fmt host helpers
614
+ // format: strings pass through, scalars are boxed by their dynamic type,
615
+ // anything else (and nil) becomes a null externref.
616
+ _emitAnyToExternref() {
617
+ const helper = this.mod.resolveFuncIndex("__any_fmt");
618
+ if (helper !== null && helper !== undefined) {
619
+ this.pushInstruction({ op: "call", funcIndex: helper });
620
+ return;
621
+ }
622
+ this._emitAnyScalarsToExternref();
623
+ }
624
+
625
+ _emitAnyScalarsToExternref() {
626
+ const tmp = this.acquireTemp("anyref");
627
+ this.pushInstruction({ op: "local.set", index: tmp });
628
+ const kinds = [
629
+ "string",
630
+ "bool",
631
+ "int",
632
+ "int8",
633
+ "int16",
634
+ "int32",
635
+ "int64",
636
+ "uint",
637
+ "uint8",
638
+ "uint16",
639
+ "uint32",
640
+ "uint64",
641
+ "float32",
642
+ "float64",
643
+ ];
644
+ for (const name of kinds) {
645
+ const t = { kind: "basic", name };
646
+ this.pushInstruction({ op: "local.get", index: tmp });
647
+ this._emitTypeTest(t);
648
+ this.pushInstruction({ op: "if", blockType: "externref" });
649
+ this.pushInstruction({ op: "local.get", index: tmp });
650
+ this._emitTypeCast(t, toWasmType(t, this.mod.checker, this.mod));
651
+ this.emitToExternref(t, false);
652
+ this.pushInstruction("else");
653
+ }
654
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
655
+ for (let i = 0; i < kinds.length; i++) this.pushInstruction("end");
656
+ this.releaseTemp(tmp, "anyref");
657
+ }
658
+
425
659
  _emitScalarToExternref(goType, valTmp, valW) {
426
660
  if (goType?.name === "string" || goType === "string") {
427
661
  this.pushInstruction({ op: "local.get", index: valTmp });
428
662
  this.releaseTemp(valTmp, valW);
429
663
  return true;
430
664
  }
431
- if (goType?.name === "bool" || goType === "bool") {
665
+ if (isBoolGoType(goType) || goType === "bool") {
432
666
  this.pushInstruction({ op: "local.get", index: valTmp });
433
667
  this.pushInstruction({
434
668
  op: "call",
@@ -446,6 +680,32 @@ export class StdlibCoreEmitter {
446
680
  this.releaseTemp(valTmp, valW);
447
681
  return true;
448
682
  }
683
+ if ((valW === "i32" || valW === "i64") && !isSigned(goType)) {
684
+ // Unsigned operands are widened (uint32) or reinterpreted by the
685
+ // host (uint64), so the top bit is not read as a sign.
686
+ this.pushInstruction({ op: "local.get", index: valTmp });
687
+ if (valW === "i32") {
688
+ if (_isUint32(goType)) {
689
+ this.pushInstruction("i64.extend_i32_u");
690
+ this.pushInstruction({
691
+ op: "call",
692
+ funcIndex: this.mod.getFmtBoxI64Index(),
693
+ });
694
+ } else {
695
+ this.pushInstruction({
696
+ op: "call",
697
+ funcIndex: this.mod.getFmtBoxI32Index(),
698
+ });
699
+ }
700
+ } else {
701
+ this.pushInstruction({
702
+ op: "call",
703
+ funcIndex: this.mod.getFmtBoxU64Index(),
704
+ });
705
+ }
706
+ this.releaseTemp(valTmp, valW);
707
+ return true;
708
+ }
449
709
  if (valW === "i32" || valW === "i64") {
450
710
  this.pushInstruction({ op: "local.get", index: valTmp });
451
711
  const funcIndex =
@@ -469,8 +729,15 @@ export class StdlibCoreEmitter {
469
729
  return false;
470
730
  }
471
731
 
472
- _emitSliceToExternref(goType, valTmp, valW, inCycle, nextSeen) {
473
- const elemGoType = goType.elem ?? goType.underlying?.elem;
732
+ _emitSliceToExternref(
733
+ goType,
734
+ valTmp,
735
+ valW,
736
+ inCycle,
737
+ nextSeen,
738
+ allowNil = false,
739
+ ) {
740
+ const elemGoType = this._getSliceElemType(goType);
474
741
  const sliceInfo = this.mod.getSliceType(elemGoType);
475
742
  const arrInfo = this.mod.getArrayType(elemGoType);
476
743
  const arrWType = {
@@ -504,6 +771,10 @@ export class StdlibCoreEmitter {
504
771
  this.pushInstruction({ op: "local.get", index: valTmp });
505
772
  this.pushInstruction("ref.is_null");
506
773
  this.pushInstruction({ op: "if", blockType: "void" });
774
+ if (allowNil) {
775
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
776
+ this.pushInstruction({ op: "local.set", index: resTmp });
777
+ }
507
778
  this.pushInstruction("else");
508
779
 
509
780
  this._emitSliceUnpack(valTmp, sliceInfo, {
@@ -512,6 +783,15 @@ export class StdlibCoreEmitter {
512
783
  len: lenTmp,
513
784
  });
514
785
 
786
+ if (allowNil) {
787
+ this.pushInstruction({ op: "local.get", index: arrTmp });
788
+ this.pushInstruction("ref.is_null");
789
+ this.pushInstruction({ op: "if", blockType: "void" });
790
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
791
+ this.pushInstruction({ op: "local.set", index: resTmp });
792
+ this.pushInstruction("end");
793
+ }
794
+
515
795
  this.pushInstruction({ op: "local.get", index: arrTmp });
516
796
  this.pushInstruction("ref.is_null");
517
797
  this.pushInstruction({ op: "local.get", index: lenTmp });
@@ -572,8 +852,49 @@ export class StdlibCoreEmitter {
572
852
  this.releaseTemp(valTmp, valW);
573
853
  }
574
854
 
855
+ // F32x4 and Mask32x4 print like a [4]float32 / [4]bool array.
856
+ _emitSimdToExternref(goType, valTmp, valW) {
857
+ const isMask =
858
+ goType?.name === "simd.Mask32x4" ||
859
+ goType?.simd === "Mask32x4" ||
860
+ goType?.underlying?.simd === "Mask32x4";
861
+ const resTmp = this.acquireTemp("externref");
862
+ this.pushInstruction({
863
+ op: "call",
864
+ funcIndex: this.mod.getFmtNewArrayIndex(),
865
+ });
866
+ this.pushInstruction({ op: "local.set", index: resTmp });
867
+ for (let lane = 0; lane < 4; lane++) {
868
+ this.pushInstruction({ op: "local.get", index: resTmp });
869
+ this.pushInstruction({ op: "local.get", index: valTmp });
870
+ if (isMask) {
871
+ this.pushInstruction({ op: "i32x4.extract_lane", lane });
872
+ this.pushInstruction({ op: "i32.const", value: 0 });
873
+ this.pushInstruction("i32.ne");
874
+ this.pushInstruction({
875
+ op: "call",
876
+ funcIndex: this.mod.getFmtBoxBoolIndex(),
877
+ });
878
+ } else {
879
+ this.pushInstruction({ op: "f32x4.extract_lane", lane });
880
+ this.pushInstruction("f64.promote_f32");
881
+ this.pushInstruction({
882
+ op: "call",
883
+ funcIndex: this.mod.getFmtBoxF64Index(),
884
+ });
885
+ }
886
+ this.pushInstruction({
887
+ op: "call",
888
+ funcIndex: this.mod.getFmtArrayPushIndex(),
889
+ });
890
+ }
891
+ this.pushInstruction({ op: "local.get", index: resTmp });
892
+ this.releaseTemp(resTmp, "externref");
893
+ this.releaseTemp(valTmp, valW);
894
+ }
895
+
575
896
  _emitArrayToExternref(goType, valTmp, valW, inCycle, nextSeen) {
576
- const elemGoType = goType.elem ?? goType.underlying?.elem;
897
+ const elemGoType = this._getArrayElemType(goType);
577
898
  const arrInfo = this.mod.getArrayType(elemGoType);
578
899
  const resTmp = this.acquireTemp("externref");
579
900
  const lenTmp = this.acquireTemp("i32");
@@ -597,6 +918,8 @@ export class StdlibCoreEmitter {
597
918
  this.pushInstruction({ op: "local.get", index: valTmp });
598
919
  this.pushInstruction("ref.is_null");
599
920
  this.pushInstruction({ op: "if", blockType: "void" });
921
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
922
+ this.pushInstruction({ op: "local.set", index: resTmp });
600
923
  this.pushInstruction("else");
601
924
 
602
925
  this.pushInstruction({ op: "local.get", index: valTmp });
@@ -649,7 +972,14 @@ export class StdlibCoreEmitter {
649
972
  this.releaseTemp(valTmp, valW);
650
973
  }
651
974
 
652
- _emitMapToExternref(goType, valTmp, valW, inCycle, nextSeen) {
975
+ _emitMapToExternref(
976
+ goType,
977
+ valTmp,
978
+ valW,
979
+ inCycle,
980
+ nextSeen,
981
+ allowNil = false,
982
+ ) {
653
983
  const { keyType, valType } = getMapKeyValTypes(goType, this.mod.checker);
654
984
  const mapInfo = this.mod.getMapType(keyType, valType);
655
985
  const resTmp = this.acquireTemp("externref");
@@ -682,6 +1012,10 @@ export class StdlibCoreEmitter {
682
1012
  this.pushInstruction({ op: "local.get", index: valTmp });
683
1013
  this.pushInstruction("ref.is_null");
684
1014
  this.pushInstruction({ op: "if", blockType: "void" });
1015
+ if (allowNil) {
1016
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
1017
+ this.pushInstruction({ op: "local.set", index: resTmp });
1018
+ }
685
1019
  this.pushInstruction("else");
686
1020
 
687
1021
  // currIdx = map.head (field 5)
@@ -781,9 +1115,13 @@ export class StdlibCoreEmitter {
781
1115
  this.pushInstruction({ op: "ref.null", heapType: "extern" });
782
1116
  this.pushInstruction({ op: "local.set", index: resTmp });
783
1117
  this.pushInstruction("else");
1118
+ // Kind 2 is a pointer to a scalar: the host keeps one box per target,
1119
+ // so the same pointer formatted twice (%p) gets the same id.
1120
+ this.pushInstruction({ op: "local.get", index: valTmp });
1121
+ this.pushInstruction({ op: "i32.const", value: 2 });
784
1122
  this.pushInstruction({
785
1123
  op: "call",
786
- funcIndex: this.mod.getFmtNewBoxIndex(),
1124
+ funcIndex: this.mod.getFmtNewStructIndex(),
787
1125
  });
788
1126
  this.pushInstruction({ op: "local.set", index: resTmp });
789
1127
  this.pushInstruction("end");
@@ -802,9 +1140,7 @@ export class StdlibCoreEmitter {
802
1140
  inCycle,
803
1141
  nextSeen,
804
1142
  ) {
805
- const structInfo =
806
- this._resolveStructInfo({ _type: goType }) ??
807
- (goType?.name ? this.mod.getStructType(goType.name) : null);
1143
+ const structInfo = _getStructOrComplexInfo(this.mod, this, goType);
808
1144
  const resTmp = this.acquireTemp("externref");
809
1145
 
810
1146
  this.pushInstruction({ op: "local.get", index: valTmp });
@@ -831,6 +1167,22 @@ export class StdlibCoreEmitter {
831
1167
  funcIndex: this.mod.getFmtNewStructIndex(),
832
1168
  });
833
1169
  this.pushInstruction({ op: "local.set", index: resTmp });
1170
+ // The Go type name, for `%T` and `%#v` of an `any` holding the struct.
1171
+ const tdesc = fmtTypeDesc(
1172
+ isPtr ? (goType.base ?? goType.elem) : goType,
1173
+ (t) => this.mod.checker?.resolveType?.(t) ?? t,
1174
+ );
1175
+ if (tdesc?.k === "struct" && tdesc.n) {
1176
+ this.pushInstruction({ op: "local.get", index: resTmp });
1177
+ this.pushInstruction({
1178
+ op: "i32.const",
1179
+ value: this.mod.internString(tdesc.n),
1180
+ });
1181
+ this.pushInstruction({
1182
+ op: "call",
1183
+ funcIndex: this.mod.getFmtStructTypeImportIndex(),
1184
+ });
1185
+ }
834
1186
 
835
1187
  if ((!isPtr || !isNested) && !inCycle && structInfo?.fields) {
836
1188
  for (let i = 0; i < structInfo.fields.length; i++) {
@@ -865,47 +1217,259 @@ export class StdlibCoreEmitter {
865
1217
  this.releaseTemp(valTmp, valW);
866
1218
  }
867
1219
 
868
- emitToExternref(goType, isNested = false, seen = new Set()) {
869
- const valW = toWasmType(goType, this.mod.checker, this.mod);
870
- const valTmp = this.acquireTemp(valW);
871
- this.pushInstruction({ op: "local.set", index: valTmp });
872
-
873
- if (this._emitScalarToExternref(goType, valTmp, valW)) return;
1220
+ // The `String() string` / `Error() string` method fmt calls on a value of
1221
+ // static type `goType` (Error first, as in Go), or null. A pointer-receiver
1222
+ // method applies to pointers only; interfaces are dispatched on the dynamic
1223
+ // type (`__any_fmt`).
1224
+ _fmtMethodOf(goType) {
1225
+ if (!goType || isAnyType(goType, this.mod.checker)) return null;
1226
+ const checker = this.mod.checker;
1227
+ const isPtr = _isPointerKind(goType);
1228
+ const named = this._fmtNamed(isPtr ? (goType.base ?? goType.elem) : goType);
1229
+ if (!named || named.name === ERROR_STRUCT) return null;
1230
+ // Elements of an `any`-held container share a wasm type across Go types
1231
+ // (`[]Color` and `[]int`), so scalar-backed named types are not dispatched.
1232
+ if (
1233
+ this._fmtNoScalarMethods &&
1234
+ !isPtr &&
1235
+ !isStructType(goType, checker, this.mod)
1236
+ )
1237
+ return null;
1238
+ // Linked projects use a stub checker (no `implements`), so look the
1239
+ // method up on the named type directly.
1240
+ const methodMap = this._fmtMethodMap(named);
1241
+ for (const method of ["Error", "String"]) {
1242
+ const sig = methodMap?.get(method);
1243
+ if (!_isFmtMethodSig(sig, isPtr)) continue;
1244
+ const found = this._fmtMethodFunc(named, method);
1245
+ if (found) return { method, isPtr, ...found };
1246
+ }
1247
+ return null;
1248
+ }
874
1249
 
875
- const tKey = goType ? goTypeKey(goType) : null;
876
- const inCycle = tKey !== null && seen.has(tKey);
877
- const nextSeen = tKey !== null ? new Set(seen).add(tKey) : seen;
1250
+ // The named type behind `t` (type names resolve through the checker), or null.
1251
+ _fmtNamed(t) {
1252
+ if (t?.kind === "TypeName" || t?.kind === "Ident")
1253
+ t = this.mod.checker?.types?.get(t.name) ?? t;
1254
+ return t?.kind === "named" && t.name ? t : null;
1255
+ }
878
1256
 
879
- if (isSliceType(goType, this.mod.checker)) {
880
- this._emitSliceToExternref(goType, valTmp, valW, inCycle, nextSeen);
881
- return;
882
- }
1257
+ _fmtMethodMap(named) {
1258
+ const resolved = this.mod.checker?.types?.get(named.name) ?? named;
1259
+ const under = resolved?.kind === "named" ? resolved.underlying : resolved;
1260
+ if (under?.kind === "interface") return null;
1261
+ return resolved?.methods?.size
1262
+ ? resolved.methods
1263
+ : (under?.methods ?? null);
1264
+ }
883
1265
 
884
- if (isArrayType(goType, this.mod.checker)) {
885
- this._emitArrayToExternref(goType, valTmp, valW, inCycle, nextSeen);
886
- return;
1266
+ _fmtMethodFunc(named, method) {
1267
+ const names = [`${named.name}.${method}`];
1268
+ if (named.pkg) names.unshift(`${named.pkg}.${named.name}.${method}`);
1269
+ for (const n of names) {
1270
+ const funcIndex = this.mod.resolveFuncIndex(n);
1271
+ if (funcIndex === null || funcIndex === undefined) continue;
1272
+ const recv = this.mod.getFuncParamGoTypes(n)[0];
1273
+ return { funcIndex, valueRecv: !_isPointerKind(recv) };
887
1274
  }
1275
+ return null;
1276
+ }
888
1277
 
889
- if (isMapType(goType, this.mod.checker)) {
890
- this._emitMapToExternref(goType, valTmp, valW, inCycle, nextSeen);
891
- return;
892
- }
1278
+ // A struct that embeds `error` and declares no Error/String itself prints
1279
+ // the embedded error's text (the checker does not promote interface
1280
+ // methods, so only fmt sees this).
1281
+ _fmtEmbeddedError(goType) {
1282
+ if (!this.mod.hasErrorType) return null;
1283
+ let t = _isPointerKind(goType) ? (goType.base ?? goType.elem) : goType;
1284
+ if (t?.kind === "TypeName" || t?.kind === "Ident")
1285
+ t = this.mod.checker?.types?.get(t.name) ?? t;
1286
+ if (t?.kind !== "named" || t.name === ERROR_STRUCT) return null;
1287
+ const sInfo = this.mod.getStructType(t.name);
1288
+ const field = sInfo?.fields?.find(
1289
+ (f) => f.embedded && isErrorType(f.goType),
1290
+ );
1291
+ if (!field) return null;
1292
+ return {
1293
+ typeIndex: sInfo.typeIndex,
1294
+ fieldIndex: sInfo.fieldIndexMap.get(field.name),
1295
+ };
1296
+ }
893
1297
 
894
- const isStructPtr = isPointerToStruct(goType, this.mod.checker, this.mod);
895
- const isStruct =
896
- isStructType(goType, this.mod.checker, this.mod) || isStructPtr;
897
- const isScalarPtr =
898
- !isStructPtr &&
899
- (goType?.kind === "pointer" ||
900
- goType?.kind === "PointerType" ||
901
- goType?.kind === "StarExpr");
1298
+ _emitEmbeddedErrorToExternref(goType, emb, isNested, seen) {
1299
+ const valW = toWasmType(goType, this.mod.checker, this.mod);
1300
+ const valTmp = this.acquireTemp(valW);
1301
+ const rawTmp = this.acquireTemp("externref");
1302
+ const errTmp = this.acquireTemp("anyref");
1303
+ this.pushInstruction({ op: "local.tee", index: valTmp });
1304
+ this._emitToExternrefPlain(goType, isNested, seen);
1305
+ this.pushInstruction({ op: "local.set", index: rawTmp });
1306
+ // nil struct pointer or nil embedded error: print the struct as is.
1307
+ this.pushInstruction({ op: "local.get", index: valTmp });
1308
+ this.pushInstruction("ref.is_null");
1309
+ this.pushInstruction({ op: "if", blockType: "externref" });
1310
+ this.pushInstruction({ op: "local.get", index: rawTmp });
1311
+ this.pushInstruction("else");
1312
+ this.pushInstruction({ op: "local.get", index: valTmp });
1313
+ this.pushInstruction({
1314
+ op: "struct.get",
1315
+ typeIndex: emb.typeIndex,
1316
+ fieldIndex: emb.fieldIndex,
1317
+ });
1318
+ this.pushInstruction({ op: "local.tee", index: errTmp });
1319
+ this.pushInstruction("ref.is_null");
1320
+ this.pushInstruction({ op: "if", blockType: "externref" });
1321
+ this.pushInstruction({ op: "local.get", index: rawTmp });
1322
+ this.pushInstruction("else");
1323
+ this.pushInstruction({ op: "local.get", index: rawTmp });
1324
+ this.pushInstruction({ op: "local.get", index: errTmp });
1325
+ this.pushInstruction({
1326
+ op: "call",
1327
+ funcIndex: this.mod.resolveFuncIndex("__error_str"),
1328
+ });
1329
+ this.pushInstruction({ op: "i32.const", value: 1 });
1330
+ this.pushInstruction({
1331
+ op: "call",
1332
+ funcIndex: this.mod.getFmtNewStringerIndex(),
1333
+ });
1334
+ this.pushInstruction("end");
1335
+ this.pushInstruction("end");
1336
+ this.releaseTemp(errTmp, "anyref");
1337
+ this.releaseTemp(rawTmp, "externref");
1338
+ this.releaseTemp(valTmp, valW);
1339
+ }
902
1340
 
903
- if (isScalarPtr) {
904
- this._emitScalarPtrToExternref(valTmp, valW);
1341
+ emitToExternref(
1342
+ goType,
1343
+ isNested = false,
1344
+ seen = new Set(),
1345
+ allowNil = false,
1346
+ ) {
1347
+ const m = this._fmtMethodOf(goType);
1348
+ if (!m) {
1349
+ const emb = this._fmtEmbeddedError(goType);
1350
+ if (emb) {
1351
+ this._emitEmbeddedErrorToExternref(goType, emb, isNested, seen);
1352
+ return;
1353
+ }
1354
+ this._emitToExternrefPlain(goType, isNested, seen, allowNil);
905
1355
  return;
906
1356
  }
907
-
908
- if (isStruct) {
1357
+ const valW = toWasmType(goType, this.mod.checker, this.mod);
1358
+ const valTmp = this.acquireTemp(valW);
1359
+ const rawTmp = this.acquireTemp("externref");
1360
+ this.pushInstruction({ op: "local.tee", index: valTmp });
1361
+ this._emitToExternrefPlain(goType, isNested, seen, allowNil);
1362
+ this.pushInstruction({ op: "local.set", index: rawTmp });
1363
+ const nilGuard = m.isPtr && m.valueRecv;
1364
+ if (nilGuard) {
1365
+ // A nil pointer with a value receiver prints <nil>.
1366
+ this.pushInstruction({ op: "local.get", index: valTmp });
1367
+ this.pushInstruction("ref.is_null");
1368
+ this.pushInstruction({ op: "if", blockType: "externref" });
1369
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
1370
+ this.pushInstruction("else");
1371
+ }
1372
+ this.pushInstruction({ op: "local.get", index: rawTmp });
1373
+ this.pushInstruction({ op: "local.get", index: valTmp });
1374
+ if (nilGuard && !isPointerToStruct(goType, this.mod.checker, this.mod)) {
1375
+ // A pointer to a scalar is a box; the value receiver takes the scalar.
1376
+ const box = this.mod.getBoxType(goType.base ?? goType.elem);
1377
+ this.pushInstruction({
1378
+ op: "ref.cast",
1379
+ typeIndex: box.typeIndex,
1380
+ typeName: box.name,
1381
+ });
1382
+ this.pushInstruction({
1383
+ op: "struct.get",
1384
+ typeIndex: box.typeIndex,
1385
+ typeName: box.name,
1386
+ fieldIndex: 0,
1387
+ });
1388
+ }
1389
+ this.pushInstruction({ op: "call", funcIndex: m.funcIndex });
1390
+ this.pushInstruction({
1391
+ op: "i32.const",
1392
+ value: m.method === "Error" ? 1 : 0,
1393
+ });
1394
+ this.pushInstruction({
1395
+ op: "call",
1396
+ funcIndex: this.mod.getFmtNewStringerIndex(),
1397
+ });
1398
+ if (nilGuard) this.pushInstruction("end");
1399
+ this.releaseTemp(rawTmp, "externref");
1400
+ this.releaseTemp(valTmp, valW);
1401
+ }
1402
+
1403
+ _emitToExternrefPlain(
1404
+ goType,
1405
+ isNested = false,
1406
+ seen = new Set(),
1407
+ allowNil = false,
1408
+ ) {
1409
+ const valW = toWasmType(goType, this.mod.checker, this.mod);
1410
+ const valTmp = this.acquireTemp(valW);
1411
+ this.pushInstruction({ op: "local.set", index: valTmp });
1412
+
1413
+ if (isAnyType(goType, this.mod.checker)) {
1414
+ this.pushInstruction({ op: "local.get", index: valTmp });
1415
+ this.releaseTemp(valTmp, valW);
1416
+ this._emitAnyToExternref();
1417
+ return;
1418
+ }
1419
+
1420
+ if (this._emitScalarToExternref(goType, valTmp, valW)) return;
1421
+
1422
+ if (valW === "v128") {
1423
+ this._emitSimdToExternref(goType, valTmp, valW);
1424
+ return;
1425
+ }
1426
+
1427
+ const tKey = goType ? goTypeKey(goType) : null;
1428
+ const inCycle = tKey !== null && seen.has(tKey);
1429
+ const nextSeen = tKey !== null ? new Set(seen).add(tKey) : seen;
1430
+
1431
+ if (isSliceType(goType, this.mod.checker)) {
1432
+ this._emitSliceToExternref(
1433
+ goType,
1434
+ valTmp,
1435
+ valW,
1436
+ inCycle,
1437
+ nextSeen,
1438
+ allowNil,
1439
+ );
1440
+ return;
1441
+ }
1442
+
1443
+ if (isArrayType(goType, this.mod.checker)) {
1444
+ this._emitArrayToExternref(goType, valTmp, valW, inCycle, nextSeen);
1445
+ return;
1446
+ }
1447
+
1448
+ if (isMapType(goType, this.mod.checker)) {
1449
+ this._emitMapToExternref(
1450
+ goType,
1451
+ valTmp,
1452
+ valW,
1453
+ inCycle,
1454
+ nextSeen,
1455
+ allowNil,
1456
+ );
1457
+ return;
1458
+ }
1459
+
1460
+ const isStructPtr = isPointerToStruct(goType, this.mod.checker, this.mod);
1461
+ const isStruct =
1462
+ isStructType(goType, this.mod.checker, this.mod) ||
1463
+ isStructPtr ||
1464
+ isComplex(goType);
1465
+ const isScalarPtr = !isStructPtr && _isPointerKind(goType);
1466
+
1467
+ if (isScalarPtr) {
1468
+ this._emitScalarPtrToExternref(valTmp, valW);
1469
+ return;
1470
+ }
1471
+
1472
+ if (isStruct) {
909
1473
  this._emitStructToExternref(
910
1474
  goType,
911
1475
  valTmp,
@@ -918,10 +1482,34 @@ export class StdlibCoreEmitter {
918
1482
  return;
919
1483
  }
920
1484
 
921
- this.pushInstruction({ op: "local.get", index: valTmp });
1485
+ if (valW !== "externref") {
1486
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
1487
+ } else {
1488
+ this.pushInstruction({ op: "local.get", index: valTmp });
1489
+ }
922
1490
  this.releaseTemp(valTmp, valW);
923
1491
  }
924
1492
 
1493
+ // `xs...` (xs []any: the host appends each element) and a bare `nil`
1494
+ // operand; returns the buffer entry, or null for an ordinary operand.
1495
+ _emitSpecialFmtOperand(arg, goType) {
1496
+ const spread = arg._spread && isSliceType(goType, this.mod.checker);
1497
+ const isNil =
1498
+ (arg.kind === "Ident" && arg.name === "nil") ||
1499
+ goType?.name === "nil" ||
1500
+ goType?.kind === "nil";
1501
+ if (!spread && !isNil) return null;
1502
+ const tmp = this.acquireTemp("externref");
1503
+ if (spread) {
1504
+ this.emitExpr(arg, toWasmType(goType, this.mod.checker, this.mod));
1505
+ this.emitToExternref(goType, false);
1506
+ } else {
1507
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
1508
+ }
1509
+ this.pushInstruction({ op: "local.set", index: tmp });
1510
+ return { tmp, wType: "externref", isBool: false, errTmp: null, spread };
1511
+ }
1512
+
925
1513
  // Evaluates every argument into a temp first, then pushes them in order via
926
1514
  // the typed `testing_arg_*` imports. Anything that may run user code (the
927
1515
  // argument itself, or `Error()` on an `error` operand) happens in the first
@@ -929,12 +1517,17 @@ export class StdlibCoreEmitter {
929
1517
  // so nothing may be pushed until every operand is fully evaluated.
930
1518
  // Returns the temps so the caller can still read an argument (fmt.Errorf's
931
1519
  // %w cause keeps the original error value in `errTmp`).
932
- _emitArgBuffer(args) {
1520
+ // `markErrors` (Sprint/Print/Append) pushes error operands through
1521
+ // `testing_arg_err`, so the host sees a non-string like Go does.
1522
+ // `typed` (a Printf-style call whose format needs operand types, see
1523
+ // fmtWantsTypes) tags each operand of a concrete static type with that
1524
+ // type through `fmt_arg_type`, so the host can print `%T` and `%#v`.
1525
+ _emitArgBuffer(args, markErrors = false, typed = false) {
933
1526
  const temps = [];
934
1527
  for (const arg of args) {
935
1528
  const goType = arg._type ?? this._resolveExprGoType(arg);
936
1529
  const isBool =
937
- goType?.name === "bool" ||
1530
+ isBoolGoType(goType) ||
938
1531
  (arg.kind === "BasicLit" && arg.litKind === "BOOL");
939
1532
  const isErr =
940
1533
  this.mod.hasErrorType &&
@@ -945,6 +1538,11 @@ export class StdlibCoreEmitter {
945
1538
  this.mod.checker?.types?.get("error"),
946
1539
  ),
947
1540
  ));
1541
+ const special = this._emitSpecialFmtOperand(arg, goType);
1542
+ if (special) {
1543
+ temps.push(special);
1544
+ continue;
1545
+ }
948
1546
  const valW = toWasmType(goType, this.mod.checker, this.mod);
949
1547
  if (isErr) {
950
1548
  const errTmp = this.acquireTemp("anyref");
@@ -956,47 +1554,112 @@ export class StdlibCoreEmitter {
956
1554
  funcIndex: this.mod.resolveFuncIndex("__error_str"),
957
1555
  });
958
1556
  this.pushInstruction({ op: "local.set", index: tmp });
959
- temps.push({ tmp, wType: "externref", isBool: false, errTmp });
1557
+ temps.push({
1558
+ tmp,
1559
+ wType: "externref",
1560
+ isBool: false,
1561
+ errTmp,
1562
+ markErr: markErrors,
1563
+ });
960
1564
  continue;
961
1565
  }
962
- if (this._isCompositeGoType(goType)) {
1566
+ if (this._isFmtExternOperand(goType)) {
963
1567
  const tmp = this.acquireTemp("externref");
964
- this.emitExpr(arg, valW);
965
- this.emitToExternref(goType, false);
1568
+ this._emitOperandExternref(arg, goType, typed);
966
1569
  this.pushInstruction({ op: "local.set", index: tmp });
967
1570
  temps.push({ tmp, wType: "externref", isBool: false, errTmp: null });
968
1571
  continue;
969
1572
  }
1573
+ // An unsigned 32-bit operand widens to i64; an unsigned 64-bit one
1574
+ // stays i64 and is reinterpreted by the host (`testing_arg_u64`), as
1575
+ // f64 cannot hold every uint64 exactly.
970
1576
  let wType = valW;
971
- if (!isSigned(goType)) {
972
- if (wType === "i32") {
973
- wType = "i64";
974
- } else if (wType === "i64") {
975
- wType = "f64";
976
- }
977
- }
1577
+ const u64 = !isSigned(goType) && valW === "i64";
1578
+ if (!isSigned(goType) && valW === "i32") wType = "i64";
978
1579
  const tmp = this.acquireTemp(wType);
979
1580
  this.emitExpr(arg, valW);
980
- if (!isSigned(goType)) {
981
- if (valW === "i32") {
982
- this.pushInstruction("i64.extend_i32_u");
983
- } else if (valW === "i64") {
984
- this.pushInstruction("f64.convert_i64_u");
985
- }
1581
+ if (!isSigned(goType) && valW === "i32") {
1582
+ this.pushInstruction("i64.extend_i32_u");
986
1583
  }
987
1584
  this.pushInstruction({ op: "local.set", index: tmp });
988
- temps.push({ tmp, wType, isBool, errTmp: null });
1585
+ temps.push({ tmp, wType, isBool, errTmp: null, u64 });
989
1586
  }
990
- for (const { tmp, wType, isBool } of temps) {
1587
+ for (const [i, temp] of temps.entries()) {
1588
+ const { tmp, wType, isBool, markErr, spread, u64 } = temp;
991
1589
  this.pushInstruction({ op: "local.get", index: tmp });
992
- this.pushInstruction({
993
- op: "call",
994
- funcIndex: this.mod.getTestingArgImportIndex(wType, isBool),
995
- });
1590
+ let funcIndex = this.mod.getTestingArgImportIndex(wType, isBool);
1591
+ if (u64) funcIndex = this.mod.getTestingArgU64ImportIndex();
1592
+ if (markErr) funcIndex = this.mod.getTestingArgErrImportIndex();
1593
+ if (spread) funcIndex = this.mod.getTestingArgSpreadImportIndex();
1594
+ this.pushInstruction({ op: "call", funcIndex });
1595
+ if (!spread && (typed || this._fmtOperandHasFloat(args[i])))
1596
+ this._emitFmtArgType(args[i]);
996
1597
  }
997
1598
  return temps;
998
1599
  }
999
1600
 
1601
+ // Operands the per-argument Println imports cannot print: spreads, a bare
1602
+ // nil, floats (static type), and unsigned 32/64-bit integers, whose
1603
+ // signedness only the argument buffer preserves.
1604
+ _fmtOperandNeedsBuffer(arg) {
1605
+ if (arg._spread || this._fmtOperandHasFloat(arg)) return true;
1606
+ const goType = arg._type ?? this._resolveExprGoType(arg);
1607
+ if (
1608
+ (arg.kind === "Ident" && arg.name === "nil") ||
1609
+ goType?.name === "nil" ||
1610
+ goType?.kind === "nil"
1611
+ ) {
1612
+ return true;
1613
+ }
1614
+ if (isSigned(goType)) return false;
1615
+ const valW = toWasmType(goType, this.mod.checker, this.mod);
1616
+ return valW === "i64" || (valW === "i32" && _isUint32(goType));
1617
+ }
1618
+
1619
+ // Whether the operand's static type mentions a float: those travel with
1620
+ // their type so the host prints float32 and Go's %v exponent forms.
1621
+ _fmtOperandHasFloat(arg) {
1622
+ const goType = arg._type ?? this._resolveExprGoType(arg);
1623
+ return fmtDescHasFloat(
1624
+ fmtTypeDesc(goType, (t) => this.mod.checker?.resolveType?.(t) ?? t),
1625
+ );
1626
+ }
1627
+
1628
+ // Tags the operand just pushed with its static type (see _emitArgBuffer).
1629
+ // Interface-typed operands carry their dynamic type and stay untagged.
1630
+ _emitFmtArgType(arg) {
1631
+ const goType = arg._type ?? this._resolveExprGoType(arg);
1632
+ const desc = fmtTypeDesc(
1633
+ goType,
1634
+ (t) => this.mod.checker?.resolveType?.(t) ?? t,
1635
+ );
1636
+ if (!desc || desc.k === "i") return;
1637
+ this.pushInstruction({
1638
+ op: "i32.const",
1639
+ value: this.mod.internString(JSON.stringify(desc)),
1640
+ });
1641
+ this.pushInstruction({
1642
+ op: "call",
1643
+ funcIndex: this.mod.getFmtArgTypeImportIndex(),
1644
+ });
1645
+ }
1646
+
1647
+ // Whether a Printf-style call's operands need their static types: the
1648
+ // format must be a literal that uses `%T`, `%#v` or has surplus operands.
1649
+ _fmtNeedsTypes(format, operands) {
1650
+ if (format?.kind !== "BasicLit") return false;
1651
+ const spread = operands.some((a) => a._spread);
1652
+ const seqArgs = operands.some((a) =>
1653
+ fmtDescIsSeq(
1654
+ fmtTypeDesc(
1655
+ a._type ?? this._resolveExprGoType(a),
1656
+ (t) => this.mod.checker?.resolveType?.(t) ?? t,
1657
+ ),
1658
+ ),
1659
+ );
1660
+ return fmtWantsTypes(format.value, spread ? -1 : operands.length, seqArgs);
1661
+ }
1662
+
1000
1663
  _releaseArgBuffer(temps) {
1001
1664
  for (let i = temps.length - 1; i >= 0; i--) {
1002
1665
  this.releaseTemp(temps[i].tmp, temps[i].wType);
@@ -1004,23 +1667,443 @@ export class StdlibCoreEmitter {
1004
1667
  }
1005
1668
  }
1006
1669
 
1007
- emitBitsCall(name, args) {
1670
+ _emitBitsArith(name, args) {
1008
1671
  switch (name) {
1672
+ case "Add32": {
1673
+ const sumTmp = this.acquireTemp("i64");
1674
+ this.emitExpr(args[0], "i32");
1675
+ this.pushInstruction("i64.extend_i32_u");
1676
+ this.emitExpr(args[1], "i32");
1677
+ this.pushInstruction("i64.extend_i32_u");
1678
+ this.pushInstruction("i64.add");
1679
+ this.emitExpr(args[2], "i32");
1680
+ this.pushInstruction("i64.extend_i32_u");
1681
+ this.pushInstruction("i64.add");
1682
+ this.pushInstruction({ op: "local.set", index: sumTmp });
1683
+ this.pushInstruction({ op: "local.get", index: sumTmp });
1684
+ this.pushInstruction("i32.wrap_i64");
1685
+ this.pushInstruction({ op: "local.get", index: sumTmp });
1686
+ this.pushInstruction({ op: "i64.const", value: 32n });
1687
+ this.pushInstruction("i64.shr_u");
1688
+ this.pushInstruction("i32.wrap_i64");
1689
+ this.releaseTemp(sumTmp, "i64");
1690
+ return true;
1691
+ }
1692
+ case "Add":
1693
+ case "Add64": {
1694
+ const xTmp = this.acquireTemp("i64");
1695
+ const yTmp = this.acquireTemp("i64");
1696
+ const cTmp = this.acquireTemp("i64");
1697
+ const s1Tmp = this.acquireTemp("i64");
1698
+ const sTmp = this.acquireTemp("i64");
1699
+ this.emitExpr(args[0], "i64");
1700
+ this.pushInstruction({ op: "local.set", index: xTmp });
1701
+ this.emitExpr(args[1], "i64");
1702
+ this.pushInstruction({ op: "local.set", index: yTmp });
1703
+ this.emitExpr(args[2], "i64");
1704
+ this.pushInstruction({ op: "local.set", index: cTmp });
1705
+ this.pushInstruction({ op: "local.get", index: xTmp });
1706
+ this.pushInstruction({ op: "local.get", index: yTmp });
1707
+ this.pushInstruction("i64.add");
1708
+ this.pushInstruction({ op: "local.set", index: s1Tmp });
1709
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1710
+ this.pushInstruction({ op: "local.get", index: cTmp });
1711
+ this.pushInstruction("i64.add");
1712
+ this.pushInstruction({ op: "local.set", index: sTmp });
1713
+ this.pushInstruction({ op: "local.get", index: sTmp });
1714
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1715
+ this.pushInstruction({ op: "local.get", index: xTmp });
1716
+ this.pushInstruction("i64.lt_u");
1717
+ this.pushInstruction({ op: "local.get", index: sTmp });
1718
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1719
+ this.pushInstruction("i64.lt_u");
1720
+ this.pushInstruction("i32.or");
1721
+ this.pushInstruction("i64.extend_i32_u");
1722
+ this.releaseTemp(xTmp, "i64");
1723
+ this.releaseTemp(yTmp, "i64");
1724
+ this.releaseTemp(cTmp, "i64");
1725
+ this.releaseTemp(s1Tmp, "i64");
1726
+ this.releaseTemp(sTmp, "i64");
1727
+ return true;
1728
+ }
1729
+ case "Sub32": {
1730
+ const diffTmp = this.acquireTemp("i64");
1731
+ this.emitExpr(args[0], "i32");
1732
+ this.pushInstruction("i64.extend_i32_u");
1733
+ this.emitExpr(args[1], "i32");
1734
+ this.pushInstruction("i64.extend_i32_u");
1735
+ this.pushInstruction("i64.sub");
1736
+ this.emitExpr(args[2], "i32");
1737
+ this.pushInstruction("i64.extend_i32_u");
1738
+ this.pushInstruction("i64.sub");
1739
+ this.pushInstruction({ op: "local.set", index: diffTmp });
1740
+ this.pushInstruction({ op: "local.get", index: diffTmp });
1741
+ this.pushInstruction("i32.wrap_i64");
1742
+ this.pushInstruction({ op: "local.get", index: diffTmp });
1743
+ this.pushInstruction({ op: "i64.const", value: 63n });
1744
+ this.pushInstruction("i64.shr_u");
1745
+ this.pushInstruction("i32.wrap_i64");
1746
+ this.releaseTemp(diffTmp, "i64");
1747
+ return true;
1748
+ }
1749
+ case "Sub":
1750
+ case "Sub64": {
1751
+ const xTmp = this.acquireTemp("i64");
1752
+ const yTmp = this.acquireTemp("i64");
1753
+ const bTmp = this.acquireTemp("i64");
1754
+ const d1Tmp = this.acquireTemp("i64");
1755
+ const diffTmp = this.acquireTemp("i64");
1756
+ this.emitExpr(args[0], "i64");
1757
+ this.pushInstruction({ op: "local.set", index: xTmp });
1758
+ this.emitExpr(args[1], "i64");
1759
+ this.pushInstruction({ op: "local.set", index: yTmp });
1760
+ this.emitExpr(args[2], "i64");
1761
+ this.pushInstruction({ op: "local.set", index: bTmp });
1762
+ this.pushInstruction({ op: "local.get", index: xTmp });
1763
+ this.pushInstruction({ op: "local.get", index: yTmp });
1764
+ this.pushInstruction("i64.sub");
1765
+ this.pushInstruction({ op: "local.set", index: d1Tmp });
1766
+ this.pushInstruction({ op: "local.get", index: d1Tmp });
1767
+ this.pushInstruction({ op: "local.get", index: bTmp });
1768
+ this.pushInstruction("i64.sub");
1769
+ this.pushInstruction({ op: "local.set", index: diffTmp });
1770
+ this.pushInstruction({ op: "local.get", index: diffTmp });
1771
+ this.pushInstruction({ op: "local.get", index: xTmp });
1772
+ this.pushInstruction({ op: "local.get", index: yTmp });
1773
+ this.pushInstruction("i64.lt_u");
1774
+ this.pushInstruction({ op: "local.get", index: d1Tmp });
1775
+ this.pushInstruction({ op: "local.get", index: bTmp });
1776
+ this.pushInstruction("i64.lt_u");
1777
+ this.pushInstruction("i32.or");
1778
+ this.pushInstruction("i64.extend_i32_u");
1779
+ this.releaseTemp(xTmp, "i64");
1780
+ this.releaseTemp(yTmp, "i64");
1781
+ this.releaseTemp(bTmp, "i64");
1782
+ this.releaseTemp(d1Tmp, "i64");
1783
+ this.releaseTemp(diffTmp, "i64");
1784
+ return true;
1785
+ }
1786
+ case "Mul32": {
1787
+ const pTmp = this.acquireTemp("i64");
1788
+ this.emitExpr(args[0], "i32");
1789
+ this.pushInstruction("i64.extend_i32_u");
1790
+ this.emitExpr(args[1], "i32");
1791
+ this.pushInstruction("i64.extend_i32_u");
1792
+ this.pushInstruction("i64.mul");
1793
+ this.pushInstruction({ op: "local.set", index: pTmp });
1794
+ this.pushInstruction({ op: "local.get", index: pTmp });
1795
+ this.pushInstruction({ op: "i64.const", value: 32n });
1796
+ this.pushInstruction("i64.shr_u");
1797
+ this.pushInstruction("i32.wrap_i64");
1798
+ this.pushInstruction({ op: "local.get", index: pTmp });
1799
+ this.pushInstruction("i32.wrap_i64");
1800
+ this.releaseTemp(pTmp, "i64");
1801
+ return true;
1802
+ }
1803
+ case "Mul":
1804
+ case "Mul64": {
1805
+ const xTmp = this.acquireTemp("i64");
1806
+ const yTmp = this.acquireTemp("i64");
1807
+ const x0Tmp = this.acquireTemp("i64");
1808
+ const x1Tmp = this.acquireTemp("i64");
1809
+ const y0Tmp = this.acquireTemp("i64");
1810
+ const y1Tmp = this.acquireTemp("i64");
1811
+ const w0Tmp = this.acquireTemp("i64");
1812
+ const tTmp = this.acquireTemp("i64");
1813
+ const w1Tmp = this.acquireTemp("i64");
1814
+ this.emitExpr(args[0], "i64");
1815
+ this.pushInstruction({ op: "local.set", index: xTmp });
1816
+ this.emitExpr(args[1], "i64");
1817
+ this.pushInstruction({ op: "local.set", index: yTmp });
1818
+ this.pushInstruction({ op: "local.get", index: xTmp });
1819
+ this.pushInstruction({ op: "i64.const", value: 0xffffffffn });
1820
+ this.pushInstruction("i64.and");
1821
+ this.pushInstruction({ op: "local.set", index: x0Tmp });
1822
+ this.pushInstruction({ op: "local.get", index: xTmp });
1823
+ this.pushInstruction({ op: "i64.const", value: 32n });
1824
+ this.pushInstruction("i64.shr_u");
1825
+ this.pushInstruction({ op: "local.set", index: x1Tmp });
1826
+ this.pushInstruction({ op: "local.get", index: yTmp });
1827
+ this.pushInstruction({ op: "i64.const", value: 0xffffffffn });
1828
+ this.pushInstruction("i64.and");
1829
+ this.pushInstruction({ op: "local.set", index: y0Tmp });
1830
+ this.pushInstruction({ op: "local.get", index: yTmp });
1831
+ this.pushInstruction({ op: "i64.const", value: 32n });
1832
+ this.pushInstruction("i64.shr_u");
1833
+ this.pushInstruction({ op: "local.set", index: y1Tmp });
1834
+ this.pushInstruction({ op: "local.get", index: x0Tmp });
1835
+ this.pushInstruction({ op: "local.get", index: y0Tmp });
1836
+ this.pushInstruction("i64.mul");
1837
+ this.pushInstruction({ op: "local.set", index: w0Tmp });
1838
+ this.pushInstruction({ op: "local.get", index: x1Tmp });
1839
+ this.pushInstruction({ op: "local.get", index: y0Tmp });
1840
+ this.pushInstruction("i64.mul");
1841
+ this.pushInstruction({ op: "local.get", index: w0Tmp });
1842
+ this.pushInstruction({ op: "i64.const", value: 32n });
1843
+ this.pushInstruction("i64.shr_u");
1844
+ this.pushInstruction("i64.add");
1845
+ this.pushInstruction({ op: "local.set", index: tTmp });
1846
+ this.pushInstruction({ op: "local.get", index: tTmp });
1847
+ this.pushInstruction({ op: "i64.const", value: 0xffffffffn });
1848
+ this.pushInstruction("i64.and");
1849
+ this.pushInstruction({ op: "local.get", index: x0Tmp });
1850
+ this.pushInstruction({ op: "local.get", index: y1Tmp });
1851
+ this.pushInstruction("i64.mul");
1852
+ this.pushInstruction("i64.add");
1853
+ this.pushInstruction({ op: "local.set", index: w1Tmp });
1854
+ this.pushInstruction({ op: "local.get", index: x1Tmp });
1855
+ this.pushInstruction({ op: "local.get", index: y1Tmp });
1856
+ this.pushInstruction("i64.mul");
1857
+ this.pushInstruction({ op: "local.get", index: tTmp });
1858
+ this.pushInstruction({ op: "i64.const", value: 32n });
1859
+ this.pushInstruction("i64.shr_u");
1860
+ this.pushInstruction("i64.add");
1861
+ this.pushInstruction({ op: "local.get", index: w1Tmp });
1862
+ this.pushInstruction({ op: "i64.const", value: 32n });
1863
+ this.pushInstruction("i64.shr_u");
1864
+ this.pushInstruction("i64.add");
1865
+ this.pushInstruction({ op: "local.get", index: xTmp });
1866
+ this.pushInstruction({ op: "local.get", index: yTmp });
1867
+ this.pushInstruction("i64.mul");
1868
+ this.releaseTemp(xTmp, "i64");
1869
+ this.releaseTemp(yTmp, "i64");
1870
+ this.releaseTemp(x0Tmp, "i64");
1871
+ this.releaseTemp(x1Tmp, "i64");
1872
+ this.releaseTemp(y0Tmp, "i64");
1873
+ this.releaseTemp(y1Tmp, "i64");
1874
+ this.releaseTemp(w0Tmp, "i64");
1875
+ this.releaseTemp(tTmp, "i64");
1876
+ this.releaseTemp(w1Tmp, "i64");
1877
+ return true;
1878
+ }
1879
+ case "Div32":
1880
+ this.emitExpr(args[0], "i32");
1881
+ this.emitExpr(args[1], "i32");
1882
+ this.emitExpr(args[2], "i32");
1883
+ this.pushInstruction({
1884
+ op: "call",
1885
+ funcIndex: this.mod.getStdlibImportIndex("bits_div32"),
1886
+ });
1887
+ this.pushInstruction({
1888
+ op: "call",
1889
+ funcIndex: this.mod.getStdlibImportIndex("bits_div32_rem"),
1890
+ });
1891
+ return true;
1892
+ case "Rem32":
1893
+ this.emitExpr(args[0], "i32");
1894
+ this.emitExpr(args[1], "i32");
1895
+ this.emitExpr(args[2], "i32");
1896
+ this.pushInstruction({
1897
+ op: "call",
1898
+ funcIndex: this.mod.getStdlibImportIndex("bits_rem32"),
1899
+ });
1900
+ return true;
1901
+ case "Div":
1902
+ case "Div64":
1903
+ this.emitExpr(args[0], "i64");
1904
+ this.emitExpr(args[1], "i64");
1905
+ this.emitExpr(args[2], "i64");
1906
+ this.pushInstruction({
1907
+ op: "call",
1908
+ funcIndex: this.mod.getStdlibImportIndex("bits_div64"),
1909
+ });
1910
+ this.pushInstruction({
1911
+ op: "call",
1912
+ funcIndex: this.mod.getStdlibImportIndex("bits_div64_rem"),
1913
+ });
1914
+ return true;
1915
+ case "Rem":
1916
+ case "Rem64":
1917
+ this.emitExpr(args[0], "i64");
1918
+ this.emitExpr(args[1], "i64");
1919
+ this.emitExpr(args[2], "i64");
1920
+ this.pushInstruction({
1921
+ op: "call",
1922
+ funcIndex: this.mod.getStdlibImportIndex("bits_rem64"),
1923
+ });
1924
+ return true;
1925
+ default:
1926
+ return false;
1927
+ }
1928
+ }
1929
+
1930
+ _emitBitsCount(name, args) {
1931
+ switch (name) {
1932
+ case "LeadingZeros8":
1933
+ this.emitExpr(args[0], "i32");
1934
+ this.pushInstruction({ op: "i32.const", value: 0xff });
1935
+ this.pushInstruction("i32.and");
1936
+ this.pushInstruction("i32.clz");
1937
+ this.pushInstruction({ op: "i32.const", value: 24 });
1938
+ this.pushInstruction("i32.sub");
1939
+ this.pushInstruction("i64.extend_i32_u");
1940
+ return true;
1941
+ case "LeadingZeros16":
1942
+ this.emitExpr(args[0], "i32");
1943
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
1944
+ this.pushInstruction("i32.and");
1945
+ this.pushInstruction("i32.clz");
1946
+ this.pushInstruction({ op: "i32.const", value: 16 });
1947
+ this.pushInstruction("i32.sub");
1948
+ this.pushInstruction("i64.extend_i32_u");
1949
+ return true;
1009
1950
  case "LeadingZeros32":
1010
1951
  this.emitExpr(args[0], "i32");
1011
1952
  this.pushInstruction("i32.clz");
1012
1953
  this.pushInstruction("i64.extend_i32_u");
1013
- return;
1954
+ return true;
1955
+ case "LeadingZeros":
1956
+ case "LeadingZeros64":
1957
+ this.emitExpr(args[0], "i64");
1958
+ this.pushInstruction("i64.clz");
1959
+ return true;
1960
+ case "TrailingZeros8":
1961
+ this.emitExpr(args[0], "i32");
1962
+ this.pushInstruction({ op: "i32.const", value: 0xff });
1963
+ this.pushInstruction("i32.and");
1964
+ this.pushInstruction({ op: "i32.const", value: 0x100 });
1965
+ this.pushInstruction("i32.or");
1966
+ this.pushInstruction("i32.ctz");
1967
+ this.pushInstruction("i64.extend_i32_u");
1968
+ return true;
1969
+ case "TrailingZeros16":
1970
+ this.emitExpr(args[0], "i32");
1971
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
1972
+ this.pushInstruction("i32.and");
1973
+ this.pushInstruction({ op: "i32.const", value: 0x10000 });
1974
+ this.pushInstruction("i32.or");
1975
+ this.pushInstruction("i32.ctz");
1976
+ this.pushInstruction("i64.extend_i32_u");
1977
+ return true;
1014
1978
  case "TrailingZeros32":
1015
1979
  this.emitExpr(args[0], "i32");
1016
1980
  this.pushInstruction("i32.ctz");
1017
1981
  this.pushInstruction("i64.extend_i32_u");
1018
- return;
1982
+ return true;
1983
+ case "TrailingZeros":
1984
+ case "TrailingZeros64":
1985
+ this.emitExpr(args[0], "i64");
1986
+ this.pushInstruction("i64.ctz");
1987
+ return true;
1988
+ case "OnesCount8":
1989
+ this.emitExpr(args[0], "i32");
1990
+ this.pushInstruction({ op: "i32.const", value: 0xff });
1991
+ this.pushInstruction("i32.and");
1992
+ this.pushInstruction("i32.popcnt");
1993
+ this.pushInstruction("i64.extend_i32_u");
1994
+ return true;
1995
+ case "OnesCount16":
1996
+ this.emitExpr(args[0], "i32");
1997
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
1998
+ this.pushInstruction("i32.and");
1999
+ this.pushInstruction("i32.popcnt");
2000
+ this.pushInstruction("i64.extend_i32_u");
2001
+ return true;
1019
2002
  case "OnesCount32":
1020
2003
  this.emitExpr(args[0], "i32");
1021
2004
  this.pushInstruction("i32.popcnt");
1022
2005
  this.pushInstruction("i64.extend_i32_u");
1023
- return;
2006
+ return true;
2007
+ case "OnesCount":
2008
+ case "OnesCount64":
2009
+ this.emitExpr(args[0], "i64");
2010
+ this.pushInstruction("i64.popcnt");
2011
+ return true;
2012
+ case "Len8":
2013
+ this.pushInstruction({ op: "i32.const", value: 32 });
2014
+ this.emitExpr(args[0], "i32");
2015
+ this.pushInstruction({ op: "i32.const", value: 0xff });
2016
+ this.pushInstruction("i32.and");
2017
+ this.pushInstruction("i32.clz");
2018
+ this.pushInstruction("i32.sub");
2019
+ this.pushInstruction("i64.extend_i32_u");
2020
+ return true;
2021
+ case "Len16":
2022
+ this.pushInstruction({ op: "i32.const", value: 32 });
2023
+ this.emitExpr(args[0], "i32");
2024
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
2025
+ this.pushInstruction("i32.and");
2026
+ this.pushInstruction("i32.clz");
2027
+ this.pushInstruction("i32.sub");
2028
+ this.pushInstruction("i64.extend_i32_u");
2029
+ return true;
2030
+ case "Len32":
2031
+ this.pushInstruction({ op: "i32.const", value: 32 });
2032
+ this.emitExpr(args[0], "i32");
2033
+ this.pushInstruction("i32.clz");
2034
+ this.pushInstruction("i32.sub");
2035
+ this.pushInstruction("i64.extend_i32_u");
2036
+ return true;
2037
+ case "Len":
2038
+ case "Len64":
2039
+ this.pushInstruction({ op: "i64.const", value: 64n });
2040
+ this.emitExpr(args[0], "i64");
2041
+ this.pushInstruction("i64.clz");
2042
+ this.pushInstruction("i64.sub");
2043
+ return true;
2044
+ default:
2045
+ return false;
2046
+ }
2047
+ }
2048
+
2049
+ _emitBitsRotate(name, args) {
2050
+ switch (name) {
2051
+ case "RotateLeft8": {
2052
+ const xTmp = this.acquireTemp("i32");
2053
+ const sTmp = this.acquireTemp("i32");
2054
+ this.emitExpr(args[0], "i32");
2055
+ this.pushInstruction({ op: "i32.const", value: 0xff });
2056
+ this.pushInstruction("i32.and");
2057
+ this.pushInstruction({ op: "local.set", index: xTmp });
2058
+ this.emitExpr(args[1], "i32");
2059
+ this.pushInstruction({ op: "i32.const", value: 7 });
2060
+ this.pushInstruction("i32.and");
2061
+ this.pushInstruction({ op: "local.set", index: sTmp });
2062
+ this.pushInstruction({ op: "local.get", index: xTmp });
2063
+ this.pushInstruction({ op: "local.get", index: sTmp });
2064
+ this.pushInstruction("i32.shl");
2065
+ this.pushInstruction({ op: "local.get", index: xTmp });
2066
+ this.pushInstruction({ op: "i32.const", value: 8 });
2067
+ this.pushInstruction({ op: "local.get", index: sTmp });
2068
+ this.pushInstruction("i32.sub");
2069
+ this.pushInstruction({ op: "i32.const", value: 7 });
2070
+ this.pushInstruction("i32.and");
2071
+ this.pushInstruction("i32.shr_u");
2072
+ this.pushInstruction("i32.or");
2073
+ this.pushInstruction({ op: "i32.const", value: 0xff });
2074
+ this.pushInstruction("i32.and");
2075
+ this.releaseTemp(sTmp, "i32");
2076
+ this.releaseTemp(xTmp, "i32");
2077
+ return true;
2078
+ }
2079
+ case "RotateLeft16": {
2080
+ const xTmp = this.acquireTemp("i32");
2081
+ const sTmp = this.acquireTemp("i32");
2082
+ this.emitExpr(args[0], "i32");
2083
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
2084
+ this.pushInstruction("i32.and");
2085
+ this.pushInstruction({ op: "local.set", index: xTmp });
2086
+ this.emitExpr(args[1], "i32");
2087
+ this.pushInstruction({ op: "i32.const", value: 15 });
2088
+ this.pushInstruction("i32.and");
2089
+ this.pushInstruction({ op: "local.set", index: sTmp });
2090
+ this.pushInstruction({ op: "local.get", index: xTmp });
2091
+ this.pushInstruction({ op: "local.get", index: sTmp });
2092
+ this.pushInstruction("i32.shl");
2093
+ this.pushInstruction({ op: "local.get", index: xTmp });
2094
+ this.pushInstruction({ op: "i32.const", value: 16 });
2095
+ this.pushInstruction({ op: "local.get", index: sTmp });
2096
+ this.pushInstruction("i32.sub");
2097
+ this.pushInstruction({ op: "i32.const", value: 15 });
2098
+ this.pushInstruction("i32.and");
2099
+ this.pushInstruction("i32.shr_u");
2100
+ this.pushInstruction("i32.or");
2101
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
2102
+ this.pushInstruction("i32.and");
2103
+ this.releaseTemp(sTmp, "i32");
2104
+ this.releaseTemp(xTmp, "i32");
2105
+ return true;
2106
+ }
1024
2107
  case "RotateLeft32": {
1025
2108
  this.emitExpr(args[0], "i32");
1026
2109
  const arg1WType = toWasmType(args[1]?._type, this.mod.checker);
@@ -1030,22 +2113,10 @@ export class StdlibCoreEmitter {
1030
2113
  } else {
1031
2114
  this.emitExpr(args[1], "i32");
1032
2115
  }
1033
- this.pushInstruction("i32.rotl");
1034
- return;
1035
- }
1036
-
1037
- case "LeadingZeros64":
1038
- this.emitExpr(args[0], "i64");
1039
- this.pushInstruction("i64.clz");
1040
- return;
1041
- case "TrailingZeros64":
1042
- this.emitExpr(args[0], "i64");
1043
- this.pushInstruction("i64.ctz");
1044
- return;
1045
- case "OnesCount64":
1046
- this.emitExpr(args[0], "i64");
1047
- this.pushInstruction("i64.popcnt");
1048
- return;
2116
+ this.pushInstruction("i32.rotl");
2117
+ return true;
2118
+ }
2119
+ case "RotateLeft":
1049
2120
  case "RotateLeft64": {
1050
2121
  this.emitExpr(args[0], "i64");
1051
2122
  const arg1WType = toWasmType(args[1]?._type, this.mod.checker);
@@ -1056,111 +2127,103 @@ export class StdlibCoreEmitter {
1056
2127
  this.emitExpr(args[1], "i64");
1057
2128
  }
1058
2129
  this.pushInstruction("i64.rotl");
1059
- return;
2130
+ return true;
1060
2131
  }
1061
-
1062
- case "Len32":
1063
- this.pushInstruction({ op: "i32.const", value: 32 });
1064
- this.emitExpr(args[0], "i32");
1065
- this.pushInstruction("i32.clz");
1066
- this.pushInstruction("i32.sub");
1067
- this.pushInstruction("i64.extend_i32_u");
1068
- return;
1069
-
1070
- case "Len64":
1071
- this.pushInstruction({ op: "i64.const", value: 64n });
1072
- this.emitExpr(args[0], "i64");
1073
- this.pushInstruction("i64.clz");
1074
- this.pushInstruction("i64.sub");
1075
- return;
1076
-
1077
- case "ReverseBytes16": {
1078
- const xTmp = this.acquireTemp("i32");
2132
+ case "Reverse8":
1079
2133
  this.emitExpr(args[0], "i32");
1080
- this.pushInstruction({ op: "local.tee", index: xTmp });
1081
- this.pushInstruction({ op: "i32.const", value: 8 });
1082
- this.pushInstruction("i32.shr_u");
1083
2134
  this.pushInstruction({ op: "i32.const", value: 0xff });
1084
2135
  this.pushInstruction("i32.and");
1085
- this.pushInstruction({ op: "local.get", index: xTmp });
1086
- this.pushInstruction({ op: "i32.const", value: 0xff });
2136
+ this._emitReverse32Sequence();
2137
+ this.pushInstruction({ op: "i32.const", value: 24 });
2138
+ this.pushInstruction("i32.shr_u");
2139
+ return true;
2140
+ case "Reverse16":
2141
+ this.emitExpr(args[0], "i32");
2142
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
1087
2143
  this.pushInstruction("i32.and");
1088
- this.pushInstruction({ op: "i32.const", value: 8 });
1089
- this.pushInstruction("i32.shl");
1090
- this.pushInstruction("i32.or");
1091
- this.releaseTemp(xTmp, "i32");
1092
- return;
1093
- }
1094
-
1095
- case "ReverseBytes32": {
2144
+ this._emitReverse32Sequence();
2145
+ this.pushInstruction({ op: "i32.const", value: 16 });
2146
+ this.pushInstruction("i32.shr_u");
2147
+ return true;
2148
+ case "Reverse32":
1096
2149
  this.emitExpr(args[0], "i32");
1097
- this._emitReverseBytes32Sequence();
1098
- return;
1099
- }
1100
-
1101
- case "ReverseBytes64": {
2150
+ this._emitReverse32Sequence();
2151
+ return true;
2152
+ case "Reverse":
2153
+ case "Reverse64": {
1102
2154
  const xTmp = this.acquireTemp("i64");
1103
2155
  this.emitExpr(args[0], "i64");
1104
2156
  this.pushInstruction({ op: "local.set", index: xTmp });
1105
-
1106
- // lo 32 bits -> ReverseBytes32 -> extend to i64 -> shl 32
1107
2157
  this.pushInstruction({ op: "local.get", index: xTmp });
1108
2158
  this.pushInstruction("i32.wrap_i64");
1109
- this._emitReverseBytes32Sequence();
2159
+ this._emitReverse32Sequence();
1110
2160
  this.pushInstruction("i64.extend_i32_u");
1111
2161
  this.pushInstruction({ op: "i64.const", value: 32n });
1112
2162
  this.pushInstruction("i64.shl");
1113
-
1114
- // hi 32 bits -> ReverseBytes32 -> extend to i64
1115
2163
  this.pushInstruction({ op: "local.get", index: xTmp });
1116
2164
  this.pushInstruction({ op: "i64.const", value: 32n });
1117
2165
  this.pushInstruction("i64.shr_u");
1118
2166
  this.pushInstruction("i32.wrap_i64");
1119
- this._emitReverseBytes32Sequence();
2167
+ this._emitReverse32Sequence();
1120
2168
  this.pushInstruction("i64.extend_i32_u");
1121
-
1122
2169
  this.pushInstruction("i64.or");
1123
2170
  this.releaseTemp(xTmp, "i64");
1124
- return;
2171
+ return true;
1125
2172
  }
1126
-
1127
- case "Reverse32": {
2173
+ case "ReverseBytes16": {
2174
+ const xTmp = this.acquireTemp("i32");
1128
2175
  this.emitExpr(args[0], "i32");
1129
- this._emitReverse32Sequence();
1130
- return;
2176
+ this.pushInstruction({ op: "local.tee", index: xTmp });
2177
+ this.pushInstruction({ op: "i32.const", value: 8 });
2178
+ this.pushInstruction("i32.shr_u");
2179
+ this.pushInstruction({ op: "i32.const", value: 0xff });
2180
+ this.pushInstruction("i32.and");
2181
+ this.pushInstruction({ op: "local.get", index: xTmp });
2182
+ this.pushInstruction({ op: "i32.const", value: 0xff });
2183
+ this.pushInstruction("i32.and");
2184
+ this.pushInstruction({ op: "i32.const", value: 8 });
2185
+ this.pushInstruction("i32.shl");
2186
+ this.pushInstruction("i32.or");
2187
+ this.releaseTemp(xTmp, "i32");
2188
+ return true;
1131
2189
  }
1132
-
1133
- case "Reverse64": {
2190
+ case "ReverseBytes32":
2191
+ this.emitExpr(args[0], "i32");
2192
+ this._emitReverseBytes32Sequence();
2193
+ return true;
2194
+ case "ReverseBytes":
2195
+ case "ReverseBytes64": {
1134
2196
  const xTmp = this.acquireTemp("i64");
1135
2197
  this.emitExpr(args[0], "i64");
1136
2198
  this.pushInstruction({ op: "local.set", index: xTmp });
1137
-
1138
- // lo 32 bits -> Reverse32 -> extend to i64 -> shl 32
1139
2199
  this.pushInstruction({ op: "local.get", index: xTmp });
1140
2200
  this.pushInstruction("i32.wrap_i64");
1141
- this._emitReverse32Sequence();
2201
+ this._emitReverseBytes32Sequence();
1142
2202
  this.pushInstruction("i64.extend_i32_u");
1143
2203
  this.pushInstruction({ op: "i64.const", value: 32n });
1144
2204
  this.pushInstruction("i64.shl");
1145
-
1146
- // hi 32 bits -> Reverse32 -> extend to i64
1147
2205
  this.pushInstruction({ op: "local.get", index: xTmp });
1148
2206
  this.pushInstruction({ op: "i64.const", value: 32n });
1149
2207
  this.pushInstruction("i64.shr_u");
1150
2208
  this.pushInstruction("i32.wrap_i64");
1151
- this._emitReverse32Sequence();
2209
+ this._emitReverseBytes32Sequence();
1152
2210
  this.pushInstruction("i64.extend_i32_u");
1153
-
1154
2211
  this.pushInstruction("i64.or");
1155
2212
  this.releaseTemp(xTmp, "i64");
1156
- return;
2213
+ return true;
1157
2214
  }
1158
-
1159
2215
  default:
1160
- throw new Error(`Unsupported bits function: bits.${name}`);
2216
+ return false;
1161
2217
  }
1162
2218
  }
1163
2219
 
2220
+ emitBitsCall(name, args) {
2221
+ if (this._emitBitsArith(name, args)) return;
2222
+ if (this._emitBitsCount(name, args)) return;
2223
+ if (this._emitBitsRotate(name, args)) return;
2224
+ throw new Error(`Unsupported bits function: bits.${name}`);
2225
+ }
2226
+
1164
2227
  _emitReverseBytes32Sequence() {
1165
2228
  const v = this.acquireTemp("i32");
1166
2229
  this.pushInstruction({ op: "local.set", index: v });
@@ -1333,30 +2396,6 @@ export class StdlibCoreEmitter {
1333
2396
 
1334
2397
  emitMapsCall(name, args, _targetWasmType) {
1335
2398
  switch (name) {
1336
- case "Keys": {
1337
- const mArg = args[0];
1338
- const mType = mArg._type ?? this._resolveExprGoType(mArg);
1339
- const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
1340
- const mapInfo = this.mod.getMapType(keyType, valType);
1341
- this.emitExpr(mArg, mapInfo.wType);
1342
- this.pushInstruction({
1343
- op: "call",
1344
- funcIndex: this.mod.resolveFuncIndex(mapInfo.keysFuncName),
1345
- });
1346
- return;
1347
- }
1348
- case "Values": {
1349
- const mArg = args[0];
1350
- const mType = mArg._type ?? this._resolveExprGoType(mArg);
1351
- const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
1352
- const mapInfo = this.mod.getMapType(keyType, valType);
1353
- this.emitExpr(mArg, mapInfo.wType);
1354
- this.pushInstruction({
1355
- op: "call",
1356
- funcIndex: this.mod.resolveFuncIndex(mapInfo.valuesFuncName),
1357
- });
1358
- return;
1359
- }
1360
2399
  case "Clone": {
1361
2400
  const mArg = args[0];
1362
2401
  const mType = mArg._type ?? this._resolveExprGoType(mArg);
@@ -4938,7 +5977,7 @@ export class StdlibCoreEmitter {
4938
5977
  const vTmps = vNodes.map((vNode) => {
4939
5978
  const tmp = this.acquireTemp(arrInfo.elemWType);
4940
5979
  this.emitExpr(vNode, arrInfo.elemWType);
4941
- this._emitCopyIfValueStruct(vNode, elemGoType, arrInfo.elemWType);
5980
+ this._emitCopyIfValue(vNode, elemGoType, arrInfo.elemWType);
4942
5981
  this.pushInstruction({ op: "local.set", index: tmp });
4943
5982
  return tmp;
4944
5983
  });
@@ -5195,7 +6234,7 @@ export class StdlibCoreEmitter {
5195
6234
  const vTmps = vNodes.map((vNode) => {
5196
6235
  const tmp = this.acquireTemp(arrInfo.elemWType);
5197
6236
  this.emitExpr(vNode, arrInfo.elemWType);
5198
- this._emitCopyIfValueStruct(vNode, elemGoType, arrInfo.elemWType);
6237
+ this._emitCopyIfValue(vNode, elemGoType, arrInfo.elemWType);
5199
6238
  this.pushInstruction({ op: "local.set", index: tmp });
5200
6239
  return tmp;
5201
6240
  });
@@ -5638,6 +6677,15 @@ export class StdlibCoreEmitter {
5638
6677
 
5639
6678
  emitStringsCall(name, args, targetWasmType) {
5640
6679
  switch (name) {
6680
+ case "ToValidUTF8": {
6681
+ this.emitExpr(args[0], "externref");
6682
+ this.emitExpr(args[1], "externref");
6683
+ this.pushInstruction({
6684
+ op: "call",
6685
+ funcIndex: this.mod.getStringToValidUTF8ImportIndex(),
6686
+ });
6687
+ return;
6688
+ }
5641
6689
  case "ToUpper": {
5642
6690
  this.emitExpr(args[0], "externref");
5643
6691
  this.pushInstruction({
@@ -6204,11 +7252,14 @@ export class StdlibCoreEmitter {
6204
7252
  return;
6205
7253
  }
6206
7254
  case "ParseUint": {
7255
+ const sTmp = this.acquireTemp("externref");
6207
7256
  this.emitExpr(args[0], "externref");
7257
+ this.pushInstruction({ op: "local.tee", index: sTmp });
6208
7258
  this._emitI32Arg(args[1]);
6209
7259
  this._emitI32Arg(args[2]);
6210
7260
  this._callStdlib("strconv_parse_uint");
6211
- this._emitStrconvError();
7261
+ this._emitStrconvError("ParseUint", sTmp);
7262
+ this.releaseTemp(sTmp, "externref");
6212
7263
  return;
6213
7264
  }
6214
7265
  case "Atoi":
@@ -6217,7 +7268,9 @@ export class StdlibCoreEmitter {
6217
7268
  case "ParseBool": {
6218
7269
  // (value, error): the JS side records a failure message that
6219
7270
  // `strconv_err` returns (null on success) → runtime error value.
7271
+ const sTmp = this.acquireTemp("externref");
6220
7272
  this.emitExpr(args[0], "externref");
7273
+ this.pushInstruction({ op: "local.tee", index: sTmp });
6221
7274
  if (name === "Atoi") this._callStdlib("strconv_atoi");
6222
7275
  else if (name === "ParseInt") {
6223
7276
  this._emitI32Arg(args[1]);
@@ -6226,7 +7279,8 @@ export class StdlibCoreEmitter {
6226
7279
  } else if (name === "ParseFloat")
6227
7280
  this._callStdlib("strconv_parse_float");
6228
7281
  else this._callStdlib("strconv_parse_bool");
6229
- this._emitStrconvError();
7282
+ this._emitStrconvError(name, sTmp);
7283
+ this.releaseTemp(sTmp, "externref");
6230
7284
  return;
6231
7285
  }
6232
7286
  default:
@@ -6234,7 +7288,7 @@ export class StdlibCoreEmitter {
6234
7288
  }
6235
7289
  }
6236
7290
 
6237
- _emitStrconvError() {
7291
+ _emitStrconvError(fnName = null, sTmp = null) {
6238
7292
  this._callStdlib("strconv_err");
6239
7293
  const msgTmp = this.acquireTemp("externref");
6240
7294
  this.pushInstruction({ op: "local.set", index: msgTmp });
@@ -6263,12 +7317,38 @@ export class StdlibCoreEmitter {
6263
7317
  this.pushInstruction("end");
6264
7318
  this.pushInstruction("end");
6265
7319
 
7320
+ const numErrInfo =
7321
+ this.mod.getStructType("NumError$$strconv") ??
7322
+ this.mod.getStructType("strconv.NumError");
7323
+
6266
7324
  this.pushInstruction({ op: "local.get", index: msgTmp });
6267
- this.pushInstruction({ op: "local.get", index: causeTmp });
6268
- this.pushInstruction({
6269
- op: "call",
6270
- funcIndex: this.mod.resolveFuncIndex("__mk_error"),
6271
- });
7325
+ this.pushInstruction("any.convert_extern");
7326
+ this.pushInstruction("ref.is_null");
7327
+ this.pushInstruction({ op: "if", blockType: "anyref" });
7328
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
7329
+ this.pushInstruction({ op: "else" });
7330
+ if (numErrInfo && fnName && sTmp !== null) {
7331
+ const fnStrIdx = this.mod.internString(fnName);
7332
+ this.pushInstruction({ op: "i32.const", value: fnStrIdx });
7333
+ this.pushInstruction({
7334
+ op: "call",
7335
+ funcIndex: this.mod.getStringImportIndex(),
7336
+ });
7337
+ this.pushInstruction({ op: "local.get", index: sTmp });
7338
+ this.pushInstruction({ op: "local.get", index: causeTmp });
7339
+ this.pushInstruction({
7340
+ op: "struct.new",
7341
+ typeIndex: numErrInfo.typeIndex,
7342
+ });
7343
+ } else {
7344
+ this.pushInstruction({ op: "local.get", index: msgTmp });
7345
+ this.pushInstruction({ op: "local.get", index: causeTmp });
7346
+ this.pushInstruction({
7347
+ op: "call",
7348
+ funcIndex: this.mod.resolveFuncIndex("__mk_error"),
7349
+ });
7350
+ }
7351
+ this.pushInstruction("end");
6272
7352
 
6273
7353
  this.releaseTemp(causeTmp, "anyref");
6274
7354
  this.releaseTemp(kindTmp, "i32");
@@ -7206,6 +8286,14 @@ export class StdlibCoreEmitter {
7206
8286
  this.releaseTemp(rTmp, replacerW);
7207
8287
  return;
7208
8288
  }
8289
+ if (method === "WriteString") {
8290
+ const strTmp = this.acquireTemp("externref");
8291
+ this.emitReplacerMethodCall("Replace", recvExpr, [args[1]]);
8292
+ this.pushInstruction({ op: "local.set", index: strTmp });
8293
+ this._emitWriterWrite(args[0], strTmp);
8294
+ this.releaseTemp(strTmp, "externref");
8295
+ return;
8296
+ }
7209
8297
  throw new Error(`Unsupported Replacer method: ${method}`);
7210
8298
  }
7211
8299
 
@@ -7684,20 +8772,87 @@ export class StdlibCoreEmitter {
7684
8772
  }
7685
8773
  }
7686
8774
 
8775
+ // `fmt.Println(f())` with a multi-value `f`: every result becomes its own
8776
+ // operand, held in a hidden local so the usual operand paths apply.
8777
+ _expandTupleFmtArgs(args) {
8778
+ const tuple = args.length === 1 ? args[0]._type : null;
8779
+ if (tuple?.kind !== "tuple") return args;
8780
+ const types = tuple.types;
8781
+ this._fmtTupleSeq = (this._fmtTupleSeq ?? 0) + 1;
8782
+ const names = types.map((_, i) => `__fmt_tuple_${this._fmtTupleSeq}_${i}`);
8783
+ const idxs = types.map((t, i) =>
8784
+ this.allocLocal(names[i], this.toWasmType(t), t, true),
8785
+ );
8786
+ this.emitExpr(args[0]);
8787
+ for (let i = types.length - 1; i >= 0; i--) {
8788
+ this.pushInstruction({ op: "local.set", index: idxs[i] });
8789
+ }
8790
+ return types.map((t, i) => ({ kind: "Ident", name: names[i], _type: t }));
8791
+ }
8792
+
7687
8793
  emitFmtCall(name, args) {
7688
- if (name === "Println" || name === "Print") {
7689
- const isPrintln = name === "Println";
8794
+ args = this._expandTupleFmtArgs(args);
8795
+ if (name === "Print" || name === "Sprint" || name === "Sprintln") {
8796
+ // Operands travel through the argument buffer; the host joins them
8797
+ // with Go's spacing rule (a space only between two non-strings).
8798
+ const temps = this._emitArgBuffer(args, true);
8799
+ this.pushInstruction({
8800
+ op: "i32.const",
8801
+ value: name === "Sprintln" ? 1 : 0,
8802
+ });
8803
+ this._callStdlib(name === "Print" ? "fmt_print" : "fmt_sprint");
8804
+ this._releaseArgBuffer(temps);
8805
+ return;
8806
+ }
8807
+ if (name === "Append" || name === "Appendln" || name === "Appendf") {
8808
+ const [dst, ...rest] = args;
8809
+ const strTmp = this.acquireTemp("externref");
8810
+ if (name === "Appendf") {
8811
+ const [format, ...operands] = rest;
8812
+ const temps = this._emitArgBuffer(
8813
+ operands,
8814
+ false,
8815
+ this._fmtNeedsTypes(format, operands),
8816
+ );
8817
+ this.emitExpr(format, "externref");
8818
+ this._callStdlib("fmt_sprintf");
8819
+ this._releaseArgBuffer(temps);
8820
+ } else {
8821
+ const temps = this._emitArgBuffer(rest, true);
8822
+ this.pushInstruction({
8823
+ op: "i32.const",
8824
+ value: name === "Appendln" ? 1 : 0,
8825
+ });
8826
+ this._callStdlib("fmt_sprint");
8827
+ this._releaseArgBuffer(temps);
8828
+ }
8829
+ this.pushInstruction({ op: "local.set", index: strTmp });
8830
+ this._emitAppendStringToByteSlice(dst, strTmp);
8831
+ this.releaseTemp(strTmp, "externref");
8832
+ return;
8833
+ }
8834
+ if (name === "Println") {
8835
+ const isPrintln = true;
7690
8836
  if (args.length === 0 && isPrintln) {
7691
8837
  const logIdx = this.mod.getPrintlnEmptyIndex();
7692
8838
  this.pushInstruction({ op: "call", funcIndex: logIdx });
7693
8839
  return;
7694
8840
  }
8841
+ // Float operands need their static type (float32, Go's exponent
8842
+ // forms), which only the argument buffer carries.
8843
+ if (args.some((a) => this._fmtOperandNeedsBuffer(a))) {
8844
+ const temps = this._emitArgBuffer(args, true);
8845
+ this.pushInstruction({ op: "i32.const", value: 1 });
8846
+ this._callStdlib("fmt_print");
8847
+ this._releaseArgBuffer(temps);
8848
+ return;
8849
+ }
7695
8850
  for (let i = 0; i < args.length; i++) {
7696
8851
  const arg = args[i];
7697
8852
  const isLast = i === args.length - 1 && isPrintln;
7698
8853
  const goType = arg._type ?? this._resolveExprGoType(arg);
7699
8854
  const isBool =
7700
- goType?.name === "bool" ||
8855
+ isBoolGoType(goType) ||
7701
8856
  (arg.kind === "BasicLit" && arg.litKind === "BOOL");
7702
8857
  if (isErrorType(goType) && this.mod.hasErrorType) {
7703
8858
  this.emitExpr(arg, "anyref");
@@ -7711,10 +8866,8 @@ export class StdlibCoreEmitter {
7711
8866
  });
7712
8867
  continue;
7713
8868
  }
7714
- if (this._isCompositeGoType(goType)) {
7715
- const valW = toWasmType(goType, this.mod.checker, this.mod);
7716
- this.emitExpr(arg, valW);
7717
- this.emitToExternref(goType, false);
8869
+ if (this._isFmtExternOperand(goType)) {
8870
+ this._emitOperandExternref(arg, goType);
7718
8871
  this._callStdlib("fmt_format_v");
7719
8872
  this.pushInstruction({
7720
8873
  op: "call",
@@ -7731,7 +8884,11 @@ export class StdlibCoreEmitter {
7731
8884
  }
7732
8885
  if (name === "Sprintf" || name === "Printf" || name === "Errorf") {
7733
8886
  const [format, ...rest] = args;
7734
- const temps = this._emitArgBuffer(rest);
8887
+ const temps = this._emitArgBuffer(
8888
+ rest,
8889
+ false,
8890
+ this._fmtNeedsTypes(format, rest),
8891
+ );
7735
8892
  this.emitExpr(format, "externref");
7736
8893
  if (name === "Printf") {
7737
8894
  this._callStdlib("fmt_printf");
@@ -8549,6 +9706,193 @@ export class StdlibCoreEmitter {
8549
9706
  this.pushInstruction({ op: "call", funcIndex: idx });
8550
9707
  }
8551
9708
 
9709
+ // Whether `emitToExternref` can turn a value of this type into a host value.
9710
+ _isExternConvertible(goType, depth = 0) {
9711
+ if (!goType || depth > 4) return false;
9712
+ const checker = this.mod.checker;
9713
+ if (isAnyType(goType, checker)) return true;
9714
+ if (isSliceType(goType, checker) || isArrayType(goType, checker)) {
9715
+ return this._isExternConvertible(
9716
+ getSliceElemType(goType, checker),
9717
+ depth + 1,
9718
+ );
9719
+ }
9720
+ if (isMapType(goType, checker)) {
9721
+ const { keyType, valType } = getMapKeyValTypes(goType, checker);
9722
+ return (
9723
+ this._isExternConvertible(keyType, depth + 1) &&
9724
+ this._isExternConvertible(valType, depth + 1)
9725
+ );
9726
+ }
9727
+ return this._isExternLeaf(goType);
9728
+ }
9729
+
9730
+ _isExternLeaf(goType) {
9731
+ const checker = this.mod.checker;
9732
+ if (
9733
+ isStructType(goType, checker, this.mod) ||
9734
+ isPointerToStruct(goType, checker, this.mod) ||
9735
+ isComplex(goType) ||
9736
+ _isPointerKind(goType)
9737
+ )
9738
+ return true;
9739
+ return ["i32", "i64", "f32", "f64", "externref", "v128"].includes(
9740
+ toWasmType(goType, checker, this.mod),
9741
+ );
9742
+ }
9743
+
9744
+ // Body of `__any_fmt(a any) externref`: the dynamic type of `a` is found
9745
+ // with a `ref.test` chain over every type the module knows (like
9746
+ // `__any_eq`), and the value goes through `emitToExternref`, so
9747
+ // String/Error dispatch and nesting work as for statically typed operands.
9748
+ _emitAnyFmtHelper() {
9749
+ const done = new Set();
9750
+ this.pushInstruction({ op: "local.get", index: 0 });
9751
+ this.pushInstruction("ref.is_null");
9752
+ this.pushInstruction({ op: "if", blockType: "void" });
9753
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
9754
+ this.pushInstruction("return");
9755
+ this.pushInstruction("end");
9756
+
9757
+ // Strings are host values; scalars are boxed by their dynamic type.
9758
+ this.pushInstruction({ op: "local.get", index: 0 });
9759
+ this._emitTypeTest({ kind: "basic", name: "string" });
9760
+ this.pushInstruction({ op: "if", blockType: "void" });
9761
+ this.pushInstruction({ op: "local.get", index: 0 });
9762
+ this.pushInstruction("extern.convert_any");
9763
+ this.pushInstruction("return");
9764
+ this.pushInstruction("end");
9765
+
9766
+ this._emitAnyFmtError(done);
9767
+ for (let round = 0; round < 3; round++) {
9768
+ this._emitAnyFmtBoxes(done);
9769
+ this._emitAnyFmtStructs(done);
9770
+ // Containers share a wasm type across Go element types, so their
9771
+ // elements are formatted without scalar-backed Stringer dispatch.
9772
+ this._fmtNoScalarMethods = true;
9773
+ this._emitAnyFmtContainers(done);
9774
+ this._fmtNoScalarMethods = false;
9775
+ }
9776
+ this._emitAnyFmtNilMaps();
9777
+ this.pushInstruction({ op: "ref.null", heapType: "extern" });
9778
+ this.pushInstruction("return");
9779
+ }
9780
+
9781
+ // A nil map in an interface prints as an empty map; nil pointers and funcs
9782
+ // fall through to `<nil>`.
9783
+ _emitAnyFmtNilMaps() {
9784
+ for (const nb of [...this.mod.nilBoxTypes.values()]) {
9785
+ if (!isMapType(nb.goType, this.mod.checker)) continue;
9786
+ const { keyType, valType } = getMapKeyValTypes(
9787
+ nb.goType,
9788
+ this.mod.checker,
9789
+ );
9790
+ const mapInfo = this.mod.getMapType(keyType, valType);
9791
+ this.pushInstruction({ op: "local.get", index: 0 });
9792
+ this.pushInstruction({ op: "ref.test", typeIndex: nb.typeIndex });
9793
+ this.pushInstruction({ op: "if", blockType: "void" });
9794
+ this.pushInstruction({ op: "ref.null", heapType: mapInfo.typeIndex });
9795
+ this.emitToExternref(nb.goType);
9796
+ this.pushInstruction("return");
9797
+ this.pushInstruction("end");
9798
+ }
9799
+ }
9800
+
9801
+ // One arm of the `__any_fmt` chain: if `a` is a `typeIndex`, format it as
9802
+ // `goType` (a box is unwrapped first) and return.
9803
+ _emitAnyFmtArm(done, typeIndex, goType, unbox) {
9804
+ if (done.has(typeIndex)) return;
9805
+ done.add(typeIndex);
9806
+ this.pushInstruction({ op: "local.get", index: 0 });
9807
+ this.pushInstruction({ op: "ref.test", typeIndex });
9808
+ this.pushInstruction({ op: "if", blockType: "void" });
9809
+ this.pushInstruction({ op: "local.get", index: 0 });
9810
+ this.pushInstruction({ op: "ref.cast", typeIndex });
9811
+ if (unbox) {
9812
+ this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 0 });
9813
+ }
9814
+ this.emitToExternref(goType);
9815
+ this.pushInstruction("return");
9816
+ this.pushInstruction("end");
9817
+ }
9818
+
9819
+ // errors.New / fmt.Errorf values print their message.
9820
+ _emitAnyFmtError(done) {
9821
+ const errInfo = this.mod.hasErrorType
9822
+ ? this.mod.getStructType(ERROR_STRUCT)
9823
+ : null;
9824
+ if (!errInfo) return;
9825
+ const msgTmp = this.acquireTemp("externref");
9826
+ this.pushInstruction({ op: "local.get", index: 0 });
9827
+ this.pushInstruction({ op: "ref.test", typeIndex: errInfo.typeIndex });
9828
+ this.pushInstruction({ op: "if", blockType: "void" });
9829
+ this.pushInstruction({ op: "local.get", index: 0 });
9830
+ this.pushInstruction({
9831
+ op: "call",
9832
+ funcIndex: this.mod.resolveFuncIndex("__error_str"),
9833
+ });
9834
+ this.pushInstruction({ op: "local.set", index: msgTmp });
9835
+ this.pushInstruction({ op: "local.get", index: msgTmp });
9836
+ this.pushInstruction({ op: "local.get", index: msgTmp });
9837
+ this.pushInstruction({ op: "i32.const", value: 1 });
9838
+ this.pushInstruction({
9839
+ op: "call",
9840
+ funcIndex: this.mod.getFmtNewStringerIndex(),
9841
+ });
9842
+ this.pushInstruction("return");
9843
+ this.pushInstruction("end");
9844
+ this.releaseTemp(msgTmp, "externref");
9845
+ done.add(errInfo.typeIndex);
9846
+ }
9847
+
9848
+ _emitAnyFmtBoxes(done) {
9849
+ for (const box of [...this.mod.ifaceBoxTypes.values()]) {
9850
+ // `any(5)` boxes the untyped constant type; format it as its default type.
9851
+ const goType =
9852
+ box.goType?.kind === "untyped"
9853
+ ? {
9854
+ kind: "basic",
9855
+ name:
9856
+ { float: "float64", rune: "int32" }[box.goType.base] ??
9857
+ box.goType.base,
9858
+ }
9859
+ : box.goType;
9860
+ if (this._isExternConvertible(goType))
9861
+ this._emitAnyFmtArm(done, box.typeIndex, goType, true);
9862
+ }
9863
+ }
9864
+
9865
+ // A bare struct ref in an `any` is a pointer.
9866
+ _emitAnyFmtStructs(done) {
9867
+ const checker = this.mod.checker;
9868
+ for (const [name, sInfo] of [...this.mod.structTypes.entries()]) {
9869
+ const named = name === ERROR_STRUCT ? null : checker?.types?.get(name);
9870
+ if (!named) continue;
9871
+ const ptrType = { kind: "pointer", base: named };
9872
+ const w = toWasmType(ptrType, checker, this.mod);
9873
+ if (w?.typeIndex === sInfo.typeIndex)
9874
+ this._emitAnyFmtArm(done, sInfo.typeIndex, ptrType, false);
9875
+ }
9876
+ }
9877
+
9878
+ _emitAnyFmtContainers(done) {
9879
+ const groups = [
9880
+ [this.mod.sliceTypes, (i) => ({ kind: "slice", elem: i.elemGoType })],
9881
+ [this.mod.arrayTypes, (i) => ({ kind: "array", elem: i.elemGoType })],
9882
+ [
9883
+ this.mod.mapTypes,
9884
+ (i) => ({ kind: "map", key: i.keyGoType, value: i.valGoType }),
9885
+ ],
9886
+ ];
9887
+ for (const [infos, toGoType] of groups) {
9888
+ for (const info of [...infos.values()]) {
9889
+ const goType = toGoType(info);
9890
+ if (this._isExternConvertible(goType))
9891
+ this._emitAnyFmtArm(done, info.typeIndex, goType, false);
9892
+ }
9893
+ }
9894
+ }
9895
+
8552
9896
  // Bodies of the synthetic runtime helpers declared in compileWasmModule.
8553
9897
  emitRuntimeHelper(fn) {
8554
9898
  const errInfo = this.mod.getStructType(ERROR_STRUCT);
@@ -8773,6 +10117,9 @@ export class StdlibCoreEmitter {
8773
10117
  });
8774
10118
  this.pushInstruction("return");
8775
10119
  return;
10120
+ case "any_fmt":
10121
+ this._emitAnyFmtHelper();
10122
+ return;
8776
10123
  case "error_str":
8777
10124
  this.pushInstruction({ op: "local.get", index: 0 });
8778
10125
  this.pushInstruction("ref.is_null");
@@ -8880,6 +10227,36 @@ export class StdlibCoreEmitter {
8880
10227
  this.pushInstruction("return");
8881
10228
  return;
8882
10229
  }
10230
+ case "testing_cleanup_cb": {
10231
+ // (fn anyref): fn is a func() closure
10232
+ const closureInfo = this.mod.getTestingCleanupClosureType();
10233
+ this.pushInstruction({ op: "local.get", index: 0 });
10234
+ this.pushInstruction({
10235
+ op: "ref.cast",
10236
+ typeIndex: closureInfo.typeIndex,
10237
+ });
10238
+ this.pushInstruction({
10239
+ op: "struct.get",
10240
+ typeIndex: closureInfo.typeIndex,
10241
+ fieldIndex: 1,
10242
+ });
10243
+ this.pushInstruction({ op: "local.get", index: 0 });
10244
+ this.pushInstruction({
10245
+ op: "ref.cast",
10246
+ typeIndex: closureInfo.typeIndex,
10247
+ });
10248
+ this.pushInstruction({
10249
+ op: "struct.get",
10250
+ typeIndex: closureInfo.typeIndex,
10251
+ fieldIndex: 0,
10252
+ });
10253
+ this.pushInstruction({
10254
+ op: "call_ref",
10255
+ typeIndex: closureInfo.funcTypeIndex,
10256
+ });
10257
+ this.pushInstruction("return");
10258
+ return;
10259
+ }
8883
10260
  default:
8884
10261
  throw new Error(`Unknown runtime helper: ${fn._isRuntimeHelper}`);
8885
10262
  }
@@ -8899,5 +10276,3 @@ function _wrapVerbIndex(format) {
8899
10276
  }
8900
10277
  return -1;
8901
10278
  }
8902
-
8903
- export { StdlibCoreEmitter as StdlibEmitter };