mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-10-30 15:06:48 +00:00
* gcasm: call cross-chunk functions directly from asm, dropping the forwarding wrappers Cross-chunk calls from transformed asm went through a generated Go wrapper (gcasmFwdFnN) that forwarded through a //go:linkname alias: asm -> ABI0 bridge -> wrapper frame -> ABI bridge -> callee, several frames per call on hot kernel paths. The wrapper existed because a dotted import path was believed unspellable in plan 9 asm. It is spellable: the asm lexer accepts U+00B7 and U+2215 as identifier runes and folds them to "." and "/", so CALL github·com∕goccy∕...∕p1·Fn19(SB) links against the canonical remote symbol. calleeSig now emits that direct reference for remote fns captured on the same arch whenever the import path is spellable; hyphenated host paths keep the wrapper. A transformed remote resolves against its TEXT (ABI0, the same stack layout the call-site marshalling already stages). A fn that fell back to its pure Go body has no TEXT, so its own package now emits a gcasmABI0Keep anchor — an asm reference to each fallback fn, which makes the compiler emit the ABI0 wrapper the direct CALL links against. This keeps the direct-call decision local: no cross-package two-pass, every captured fn is callable either way. The owner map gating direct calls is now per arch, since a fn's TEXT/anchor exists per arch. The new cg_crosscall fixture (direct calls between padded functions, plus a wide-signature fn that sig-falls-back) splits across chunks under a tiny budget; gate4 runs it in single-package, multi-chunk and dotted-path multi-chunk layouts against the pure build, and the new crosschunk tests assert the emitted shapes (spelled CALLs, no gcasmFwdFn wrappers, the keep anchor, and the wrapper fallback for an unspellable path). Co-Authored-By: Claude Fable 5 <[email protected]> * gcasm: emit the ABI0 keep anchor only for plan-9-spellable import paths The gcasmABI0Keep anchor exists solely to force ABI0 wrappers for the pure-Go fallback fns that other chunks' asm CALLs directly by their spelled dotted symbol. An import path plan 9 asm cannot spell keeps the gcasmFwd linkname wrappers for every cross-chunk call instead, so no fallback fn is ever referenced directly and the anchor is pure dead weight there. Gate its emission on plan9AsmPathSafe(importPath); TestCrossChunkWrapperFallback now asserts no anchor is emitted on the unspellable path. Co-Authored-By: Claude Fable 5 <[email protected]> * gcasm: assert the multi-chunk bundle's amd64/arm64 file-set contract The direct-call path's parse economy rests on a file-set invariant that had no test: on the asm arches the codegen amd64||arm64 alias.go is deleted by the gcasm backend, its cross-chunk declarations fold into the decls_<arch>.go the asm bodies need anyway, and no separate linkname-only alias file is compiled there. TestPlan9SafeBundleFileStructure builds the cg_indirect fixture (whose elem table takes cross-chunk function addresses) in multi-chunk mode, merges the gcasm delta over the codegen tree exactly as the pipeline does, and asserts: no chunk keeps an amd64||arm64 alias.go; every chunk has an arch-guarded decls_amd64.go/decls_arm64.go; and every remaining //go:linkname-bearing alias file is guarded off the asm arches. It runs for both a spellable and a hyphenated import path — the file-set contract is identical; only the decls' CONTENT (direct symbols vs gcasmFwd wrappers) differs. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]>
79 lines
3.7 KiB
WebAssembly Text Format
79 lines
3.7 KiB
WebAssembly Text Format
(module
|
|
;; Direct cross-function calls between non-recursive functions.
|
|
;; Each body is padded past the smallest chunk budgets the tests
|
|
;; use, so under a tiny multi-package chunk budget every function
|
|
;; lands in its own chunk package and the callers' transformed asm
|
|
;; makes cross-chunk CALLs.
|
|
|
|
;; mixA: a long dependent i64 chain (~30 ops).
|
|
(func $mixA (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.rotl (local.get $a) (i64.const 17))))
|
|
(local.set $x (i64.xor (local.get $x) (i64.rotr (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))))
|
|
|
|
;; mixB: a different long chain.
|
|
(func $mixB (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.rotl (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; its own padding keeps it a separate
|
|
;; chunk too.
|
|
(func $mid (param $a i64) (param $b i64) (result i64)
|
|
(local $z i64)
|
|
(local.set $z (i64.add
|
|
(call $mixA (local.get $a) (local.get $b))
|
|
(call $mixB (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.rotr (local.get $b) (i64.const 21))))
|
|
(local.set $z (i64.sub (local.get $z) (i64.rotl (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 — the cross-chunk 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") (param $a i64) (param $b i64) (result i64)
|
|
(i64.add
|
|
(i64.add
|
|
(call $mid (local.get $a) (local.get $b))
|
|
(call $mixB (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))))
|
|
)
|