Files
wasm2go/testdata/cg_crosscall_directasm.wat
9b2c9ccddd gcasm: fix cross-chunk symbol gaps in the direct-call path (#64)
* gcasm: fix cross-chunk symbol gaps in the direct-call path

Follow-ups to the direct cross-chunk asm calls change (#61):

- The direct-asm resolvers (CalleeSymbol / MemHelperSymbol) trimmed
  calleeSig's leading "·" and let asmgen's goAsmSymbol re-derive the
  CALL operand. A spelled remote symbol ("github·com∕...∕p2·Fn0")
  has no ASCII "." for goAsmSymbol to split on, so it was re-wrapped
  as a current-package reference ("CALL ·github·com∕...(SB)") that
  can never link — any direct-asm-retained function making a
  resolved cross-chunk call produced an undefined symbol at consumer
  link time. The resolver contract now carries the exact plan 9
  operand (without the "(SB)" suffix) and asmgen emits it verbatim.

- plan9AsmPathSafe accepted digit-leading import paths ("4d63.com/...",
  "0xacab.org/..."), but the plan 9 asm lexer accepts digits only
  after the first identifier rune, so the spelled CALL would fail to
  assemble. The predicate now requires a letter or "_" at position 0.

- That predicate existed as two byte-for-byte copies (codegen's
  isPlan9AsmSafe, gcasm's plan9AsmPathSafe) under a comment claiming
  an import cycle prevents sharing; no such cycle exists. Both
  packages now consult one exported asmgen.Plan9AsmPathSafe, so the
  linkname alias shape and the direct-CALL / gcasmABI0Keep anchor
  gates can never disagree.

- buildPkg now verifies every captured fn leaves the emission loop
  either with a TEXT body or registered as a pure-Go fallback. A
  future degrade path that forgets fallbackNames would otherwise
  surface only as a link error in the consumer build, with no
  Build-time diagnostic; it is a Build error naming the fn now.

The new cg_crosscall_directasm fixture keeps every op native (no
rotl/rotr, which lower to helper CALLs the direct-asm emitter's
ForbidCalls refuses) so the retained functions actually emit via
asmgen; its regression test fails against the pre-fix resolvers with
the mangled current-package spelling in the emitted bundle.

Co-Authored-By: Claude Fable 5 <[email protected]>

* asmgen: restructure the first-rune check as a switch to satisfy staticcheck

golangci-lint's staticcheck (QF1001) flagged the negated compound
condition in Plan9AsmPathSafe; the positive-switch form matches the
per-byte loop below it. No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-09-02 20:02:20 +09:00

82 lines
3.9 KiB
WebAssembly Text Format

(module
;; Cross-chunk direct calls made from direct-asm-retained bodies.
;; Unlike cg_crosscall.wat, every op here lowers to native
;; instructions (no rotl/rotr/div, which become helper CALLs the
;; direct-asm emitter's ForbidCalls refuses), so the retained
;; functions actually emit via internal/asmgen and their
;; cross-chunk CALL operands are exercised. Bodies are padded past
;; the tiny multi-package chunk budgets the tests use, so each
;; function lands in its own chunk package.
;; leafA: a long dependent i64 chain of native-lowering ops.
(func $leafA (param $a i64) (param $b i64) (result i64)
(local $x i64)
(local.set $x (i64.add (local.get $a) (local.get $b)))
(local.set $x (i64.mul (local.get $x) (i64.const 31)))
(local.set $x (i64.xor (local.get $x) (i64.const 0x9e3779b97f4a7c15)))
(local.set $x (i64.add (local.get $x) (i64.shr_u (local.get $x) (i64.const 7))))
(local.set $x (i64.mul (local.get $x) (i64.const 0x100000001b3)))
(local.set $x (i64.xor (local.get $x) (i64.shl (local.get $x) (i64.const 13))))
(local.set $x (i64.add (local.get $x) (i64.const 12345)))
(local.set $x (i64.sub (local.get $x) (i64.shl (local.get $a) (i64.const 17))))
(local.set $x (i64.xor (local.get $x) (i64.shr_u (local.get $b) (i64.const 9))))
(local.set $x (i64.mul (local.get $x) (i64.const 7)))
(i64.add (local.get $x) (i64.shr_s (local.get $x) (i64.const 3))))
;; leafB: a different long chain.
(func $leafB (param $a i64) (result i64)
(local $y i64)
(local.set $y (i64.mul (local.get $a) (i64.const 0x5851f42d4c957f2d)))
(local.set $y (i64.add (local.get $y) (i64.const 0x14057b7ef767814f)))
(local.set $y (i64.xor (local.get $y) (i64.shr_u (local.get $y) (i64.const 33))))
(local.set $y (i64.mul (local.get $y) (i64.const 0xff51afd7ed558ccd)))
(local.set $y (i64.xor (local.get $y) (i64.shr_u (local.get $y) (i64.const 29))))
(local.set $y (i64.add (local.get $y) (i64.shl (local.get $a) (i64.const 5))))
(local.set $y (i64.sub (local.get $y) (i64.const 999)))
(local.set $y (i64.or (local.get $y) (i64.const 1)))
(i64.mul (local.get $y) (i64.const 3)))
;; mid combines both leaves; retained for direct-asm, so its two
;; cross-chunk calls come out of the asmgen emitter.
(func $mid (param $a i64) (param $b i64) (result i64)
(local $z i64)
(local.set $z (i64.add
(call $leafA (local.get $a) (local.get $b))
(call $leafB (local.get $a))))
(local.set $z (i64.xor (local.get $z) (i64.shr_u (local.get $z) (i64.const 11))))
(local.set $z (i64.mul (local.get $z) (i64.const 5)))
(local.set $z (i64.add (local.get $z) (i64.shr_u (local.get $b) (i64.const 21))))
(local.set $z (i64.sub (local.get $z) (i64.shl (local.get $a) (i64.const 2))))
(local.set $z (i64.xor (local.get $z) (i64.const 0x0f0f0f0f0f0f0f0f)))
(i64.add (local.get $z) (i64.const 42)))
;; wide: enough i64 params to overflow the register ABI, so this
;; function keeps its pure Go body (sig fallback) while its callers
;; are transformed — a direct-asm caller's CALL then resolves
;; against the ABI0 wrapper the gcasmABI0Keep anchor forces out.
(func $wide
(param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
(result i64)
(i64.add (local.get 0)
(i64.add (local.get 1)
(i64.add (local.get 2)
(i64.add (local.get 3)
(i64.add (local.get 4)
(i64.add (local.get 5)
(i64.add (local.get 6)
(i64.add (local.get 7)
(i64.add (local.get 8)
(i64.add (local.get 9)
(i64.add (local.get 10) (local.get 11)))))))))))))
(func (export "crosscall_da") (param $a i64) (param $b i64) (result i64)
(i64.add
(i64.add
(call $mid (local.get $a) (local.get $b))
(call $leafB (local.get $b)))
(call $wide
(local.get $a) (local.get $b) (local.get $a) (local.get $b)
(local.get $a) (local.get $b) (local.get $a) (local.get $b)
(local.get $a) (local.get $b) (local.get $a) (local.get $b))))
)