From 6efc23c7231e91f017f628dea5d6a5de18b688a0 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 25 Jul 2026 18:48:23 +0800 Subject: [PATCH 1/5] plan9asm: support Go aliases and local trampolines --- go_translate.go | 48 ++++++++++++++++++++++++++++++++------- go_translate_deep_test.go | 24 ++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/go_translate.go b/go_translate.go index 0e57225..c98f4b1 100644 --- a/go_translate.go +++ b/go_translate.go @@ -158,6 +158,7 @@ func goSigsForAsmFile(pkg GoPackage, file *File, resolve func(sym string) string } b := goSigBuilder{ sigs: make(map[string]FuncSig, len(file.Funcs)), + localSigs: make(map[string]bool), scope: pkg.Types.Scope(), sz: sz, linknames: goLinknameRemoteToLocal(pkg.Syntax), @@ -175,11 +176,24 @@ func goSigsForAsmFile(pkg GoPackage, file *File, resolve func(sym string) string if err := b.addReferencedFuncSigs(file); err != nil { return nil, err } + // File-local TEXT symbols (the Plan 9 `<>` form) are commonly used for + // assembly trampolines that are only reached through a raw function + // pointer. They intentionally have no Go declaration. Give any such + // symbols that could not be inferred from a tail jump a conservative + // zero-argument/void signature so they can still be emitted and referenced + // by DATA directives. A caller-provided ManualSig remains authoritative. + for resolved := range b.localSigs { + if _, ok := b.sigs[resolved]; ok { + continue + } + b.sigs[resolved] = FuncSig{Name: resolved, Ret: Void} + } return b.sigs, nil } type goSigBuilder struct { sigs map[string]FuncSig + localSigs map[string]bool scope *types.Scope sz types.Sizes linknames map[string]string @@ -191,7 +205,8 @@ type goSigBuilder struct { func (b *goSigBuilder) addDeclaredFuncSigs(file *File) error { for i := range file.Funcs { - sym := goStripABISuffix(file.Funcs[i].Sym) + textSym := file.Funcs[i].Sym + sym := goStripABISuffix(textSym) resolved := b.resolve(sym) if ms, ok := goLookupManualSig(b.manualSig, resolved); ok { b.sigs[resolved] = ms @@ -204,6 +219,13 @@ func (b *goSigBuilder) addDeclaredFuncSigs(file *File) error { } obj := b.scope.Lookup(declName) if obj == nil { + if strings.HasSuffix(textSym, "<>") { + if b.localSigs == nil { + b.localSigs = make(map[string]bool) + } + b.localSigs[resolved] = true + continue + } return fmt.Errorf("missing Go declaration for asm symbol %q", sym) } fn, ok := obj.(*types.Func) @@ -235,15 +257,20 @@ func (b *goSigBuilder) addReferencedFuncSigs(file *File) error { if _, ok := b.sigs[targetResolved]; ok { continue } - if !tailJump || !hasCallerSig { + if tailJump && hasCallerSig { + // Best-effort fallback for helper<> tail-jumps where the callee is an + // internal asm label with no Go declaration. Callers can override this + // via ManualSig when the inferred signature is not identical. + fs := callerSig + fs.Name = targetResolved + b.sigs[targetResolved] = fs continue } - // Best-effort fallback for helper<> tail-jumps where the callee is an - // internal asm label with no Go declaration. Callers can override this - // via ManualSig when the inferred signature is not identical. - fs := callerSig - fs.Name = targetResolved - b.sigs[targetResolved] = fs + // cgo_import_dynamic and other C symbols may be referenced from + // assembly without a Go declaration. Their ABI is deliberately opaque + // to the Go type checker; keep a conservative declaration so the + // backend can still emit the trampoline. A ManualSig takes precedence. + b.sigs[targetResolved] = FuncSig{Name: targetResolved, Ret: Void} } } return nil @@ -546,6 +573,11 @@ func goLLVMTypeForType(t types.Type, goarch string) (LLVMType, error) { return LLVMType("{ ptr, ptr }"), nil case *types.Named: return goLLVMTypeForType(tt.Underlying(), goarch) + case *types.Alias: + // Go 1.23+ materializes ordinary aliases (for example xxh3.u64 = + // uint64) as *types.Alias. They have the same calling convention as + // their RHS and must be lowered just like defined named types. + return goLLVMTypeForType(tt.Underlying(), goarch) default: return "", fmt.Errorf("unsupported type %s", t.String()) } diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go index c5b55d9..492fa21 100644 --- a/go_translate_deep_test.go +++ b/go_translate_deep_test.go @@ -109,6 +109,8 @@ func TestGoTranslateTypeCoverage(t *testing.T) { word32 := types.Typ[types.Int] namedObj := types.NewTypeName(token.NoPos, nil, "MyInt", nil) named := types.NewNamed(namedObj, types.Typ[types.Int32], nil) + aliasObj := types.NewTypeName(token.NoPos, nil, "MyUint64", nil) + alias := types.NewAlias(aliasObj, types.Typ[types.Uint64]) for _, tc := range []struct { typ types.Type goarch string @@ -130,6 +132,7 @@ func TestGoTranslateTypeCoverage(t *testing.T) { {types.NewSlice(types.Typ[types.Byte]), "arm64", LLVMType("{ ptr, i64, i64 }"), true}, {types.NewInterfaceType(nil, nil), "amd64", LLVMType("{ ptr, ptr }"), true}, {named, "amd64", I32, true}, + {alias, "arm64", I64, true}, {types.Typ[types.Complex64], "amd64", "", false}, {types.NewStruct(nil, nil), "amd64", "", false}, } { @@ -320,3 +323,24 @@ RET t.Fatalf("goExpandConsts() = %q", expanded) } } + +func TestTranslateGoModuleUndeclaredLocalTrampoline(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", `package testpkg +func F() {} +`) + tr, err := TranslateGoModule(pkg, []byte(`TEXT local_trampoline<>(SB),NOSPLIT,$0-0 +JMP external_symbol(SB) +`), GoModuleOptions{ + GOARCH: "arm64", + TargetTriple: "aarch64-unknown-linux-gnu", + ResolveSym: testResolveSym("test/pkg"), + }) + if err != nil { + t.Fatalf("TranslateGoModule(undeclared local trampoline) error = %v", err) + } + defer tr.Module.Dispose() + sig, ok := tr.Signatures["test/pkg.local_trampoline"] + if !ok || sig.Ret != Void || len(sig.Args) != 0 { + t.Fatalf("local trampoline signature = %#v, want void()", sig) + } +} From 58bdf5cdcbd4c30af692014b6950ef1dce08f2ba Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 25 Jul 2026 21:51:01 +0800 Subject: [PATCH 2/5] plan9asm: keep alias lowering compatible with Go 1.21 --- go_translate.go | 14 +++++++++----- go_translate_deep_test.go | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/go_translate.go b/go_translate.go index c98f4b1..a2fed62 100644 --- a/go_translate.go +++ b/go_translate.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/constant" "go/types" + "reflect" "regexp" "strconv" "strings" @@ -530,6 +531,14 @@ func goExpandConsts(src []byte, pkgTypes *types.Package, imports map[string]*typ } func goLLVMTypeForType(t types.Type, goarch string) (LLVMType, error) { + // *types.Alias was added after the oldest Go version supported by this + // module. Detect it by its stable reflect identity so the package still + // compiles with Go 1.21, while newer go/types implementations can lower an + // alias through its RHS just like a named type. + if rt := reflect.TypeOf(t); rt != nil && rt.Kind() == reflect.Ptr && + rt.Elem().PkgPath() == "go/types" && rt.Elem().Name() == "Alias" { + return goLLVMTypeForType(t.Underlying(), goarch) + } switch tt := t.(type) { case *types.Basic: switch tt.Kind() { @@ -573,11 +582,6 @@ func goLLVMTypeForType(t types.Type, goarch string) (LLVMType, error) { return LLVMType("{ ptr, ptr }"), nil case *types.Named: return goLLVMTypeForType(tt.Underlying(), goarch) - case *types.Alias: - // Go 1.23+ materializes ordinary aliases (for example xxh3.u64 = - // uint64) as *types.Alias. They have the same calling convention as - // their RHS and must be lowered just like defined named types. - return goLLVMTypeForType(tt.Underlying(), goarch) default: return "", fmt.Errorf("unsupported type %s", t.String()) } diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go index 492fa21..99a8a40 100644 --- a/go_translate_deep_test.go +++ b/go_translate_deep_test.go @@ -109,8 +109,10 @@ func TestGoTranslateTypeCoverage(t *testing.T) { word32 := types.Typ[types.Int] namedObj := types.NewTypeName(token.NoPos, nil, "MyInt", nil) named := types.NewNamed(namedObj, types.Typ[types.Int32], nil) - aliasObj := types.NewTypeName(token.NoPos, nil, "MyUint64", nil) - alias := types.NewAlias(aliasObj, types.Typ[types.Uint64]) + aliasPkg := mustGoPackage(t, "aliaspkg", `package aliaspkg +type MyUint64 = uint64 +`) + alias := aliasPkg.Types.Scope().Lookup("MyUint64").Type() for _, tc := range []struct { typ types.Type goarch string From 7ec3e08d287a7794840c2025c902e9674287ef63 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sun, 26 Jul 2026 12:15:30 +0800 Subject: [PATCH 3/5] test: cover alias and signature fallback branches --- alias_go123_test.go | 9 +++++++++ alias_legacy_test.go | 9 +++++++++ go_translate.go | 16 ++++++++-------- go_translate_deep_test.go | 26 ++++++++++++++++++++------ 4 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 alias_go123_test.go create mode 100644 alias_legacy_test.go diff --git a/alias_go123_test.go b/alias_go123_test.go new file mode 100644 index 0000000..8965f63 --- /dev/null +++ b/alias_go123_test.go @@ -0,0 +1,9 @@ +//go:build go1.23 + +package plan9asm + +import "go/types" + +func newAliasTypeForTest(obj *types.TypeName, rhs types.Type) types.Type { + return types.NewAlias(obj, rhs) +} diff --git a/alias_legacy_test.go b/alias_legacy_test.go new file mode 100644 index 0000000..1f7642e --- /dev/null +++ b/alias_legacy_test.go @@ -0,0 +1,9 @@ +//go:build !go1.23 + +package plan9asm + +import "go/types" + +func newAliasTypeForTest(_ *types.TypeName, rhs types.Type) types.Type { + return rhs +} diff --git a/go_translate.go b/go_translate.go index a2fed62..9a4cd4a 100644 --- a/go_translate.go +++ b/go_translate.go @@ -531,14 +531,6 @@ func goExpandConsts(src []byte, pkgTypes *types.Package, imports map[string]*typ } func goLLVMTypeForType(t types.Type, goarch string) (LLVMType, error) { - // *types.Alias was added after the oldest Go version supported by this - // module. Detect it by its stable reflect identity so the package still - // compiles with Go 1.21, while newer go/types implementations can lower an - // alias through its RHS just like a named type. - if rt := reflect.TypeOf(t); rt != nil && rt.Kind() == reflect.Ptr && - rt.Elem().PkgPath() == "go/types" && rt.Elem().Name() == "Alias" { - return goLLVMTypeForType(t.Underlying(), goarch) - } switch tt := t.(type) { case *types.Basic: switch tt.Kind() { @@ -583,6 +575,14 @@ func goLLVMTypeForType(t types.Type, goarch string) (LLVMType, error) { case *types.Named: return goLLVMTypeForType(tt.Underlying(), goarch) default: + // *types.Alias was added after the oldest Go version supported by this + // module. Detect it by its stable reflect identity so the package still + // compiles with Go 1.21, while newer go/types implementations can lower + // an alias through its RHS just like a named type. Keep this probe in the + // fallback path so common concrete types avoid reflection overhead. + if reflect.TypeOf(t).String() == "*types.Alias" { + return goLLVMTypeForType(t.Underlying(), goarch) + } return "", fmt.Errorf("unsupported type %s", t.String()) } } diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go index 99a8a40..3f83d60 100644 --- a/go_translate_deep_test.go +++ b/go_translate_deep_test.go @@ -109,10 +109,8 @@ func TestGoTranslateTypeCoverage(t *testing.T) { word32 := types.Typ[types.Int] namedObj := types.NewTypeName(token.NoPos, nil, "MyInt", nil) named := types.NewNamed(namedObj, types.Typ[types.Int32], nil) - aliasPkg := mustGoPackage(t, "aliaspkg", `package aliaspkg -type MyUint64 = uint64 -`) - alias := aliasPkg.Types.Scope().Lookup("MyUint64").Type() + aliasObj := types.NewTypeName(token.NoPos, nil, "MyUint64", nil) + alias := newAliasTypeForTest(aliasObj, types.Typ[types.Uint64]) for _, tc := range []struct { typ types.Type goarch string @@ -208,8 +206,9 @@ func cmp(a, b int) int { return a } file := &File{ Arch: ArchARM64, Funcs: []Func{ - {Sym: "·Plain", Instrs: []Instr{{Op: OpTEXT}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "runtime·cmp(SB)"}}}, {Op: OpRET}}}, + {Sym: "·Plain", Instrs: []Instr{{Op: OpTEXT}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "runtime·cmp(SB)"}}}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "localtarget<>(SB)"}}}, {Op: OpRET}}}, {Sym: "localhelper<>", Instrs: []Instr{{Op: OpTEXT}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "helper<>(SB)"}}}, {Op: OpRET}}}, + {Sym: "localtarget<>"}, }, } sigs, err := goSigsForAsmFile(pkg, file, testResolveSym("test/pkg"), "arm64", func(resolved string) (FuncSig, bool) { @@ -221,7 +220,7 @@ func cmp(a, b int) int { return a } if err != nil { t.Fatalf("goSigsForAsmFile() error = %v", err) } - for _, want := range []string{"test/pkg.Plain", "runtime.cmp", "test/pkg.localhelper"} { + for _, want := range []string{"test/pkg.Plain", "runtime.cmp", "test/pkg.localhelper", "test/pkg.localtarget"} { if _, ok := sigs[want]; !ok { t.Fatalf("missing signature %q", want) } @@ -264,6 +263,21 @@ var helper int if err := badBuilder.addDeclaredFuncSigs(badFile); err == nil { t.Fatalf("addDeclaredFuncSigs(non-func) unexpectedly succeeded") } + if err := builder.addDeclaredFuncSigs(&File{Funcs: []Func{{Sym: "·missing"}}}); err == nil { + t.Fatalf("addDeclaredFuncSigs(missing) unexpectedly succeeded") + } + nilLocalBuilder := goSigBuilder{ + sigs: map[string]FuncSig{}, + localSigs: nil, + scope: scope, + sz: types.SizesFor("gc", "arm64"), + pkgPath: "test/pkg", + resolve: testResolveSym("test/pkg"), + goarch: "arm64", + } + if err := nilLocalBuilder.addDeclaredFuncSigs(&File{Funcs: []Func{{Sym: "missing_local<>"}}}); err != nil { + t.Fatalf("addDeclaredFuncSigs(local with nil map) error = %v", err) + } if _, err := goSigsForAsmFile(pkg, file, testResolveSym("test/pkg"), "madeup", nil); err == nil { t.Fatalf("goSigsForAsmFile(madeup) unexpectedly succeeded") } From b22d3ad40c9c7be3e4f73b4d3b2d7c5828ae75b2 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sun, 26 Jul 2026 12:55:26 +0800 Subject: [PATCH 4/5] plan9asm: preserve errors for unresolved calls --- go_translate.go | 12 ++++++++---- go_translate_deep_test.go | 5 ++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/go_translate.go b/go_translate.go index 9a4cd4a..72548fd 100644 --- a/go_translate.go +++ b/go_translate.go @@ -267,10 +267,14 @@ func (b *goSigBuilder) addReferencedFuncSigs(file *File) error { b.sigs[targetResolved] = fs continue } - // cgo_import_dynamic and other C symbols may be referenced from - // assembly without a Go declaration. Their ABI is deliberately opaque - // to the Go type checker; keep a conservative declaration so the - // backend can still emit the trampoline. A ManualSig takes precedence. + if !tailJump { + // Keep ordinary unresolved calls untyped so the backend reports the + // missing declaration instead of silently dropping arguments/results. + continue + } + // An undeclared tail target can be an assembly trampoline reached + // through a raw function pointer. Its ABI is opaque to go/types; keep a + // conservative declaration so the trampoline can still be emitted. b.sigs[targetResolved] = FuncSig{Name: targetResolved, Ret: Void} } } diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go index 3f83d60..acc0f51 100644 --- a/go_translate_deep_test.go +++ b/go_translate_deep_test.go @@ -206,7 +206,7 @@ func cmp(a, b int) int { return a } file := &File{ Arch: ArchARM64, Funcs: []Func{ - {Sym: "·Plain", Instrs: []Instr{{Op: OpTEXT}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "runtime·cmp(SB)"}}}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "localtarget<>(SB)"}}}, {Op: OpRET}}}, + {Sym: "·Plain", Instrs: []Instr{{Op: OpTEXT}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "runtime·cmp(SB)"}}}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "undeclared_call(SB)"}}}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "localtarget<>(SB)"}}}, {Op: OpRET}}}, {Sym: "localhelper<>", Instrs: []Instr{{Op: OpTEXT}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "helper<>(SB)"}}}, {Op: OpRET}}}, {Sym: "localtarget<>"}, }, @@ -225,6 +225,9 @@ func cmp(a, b int) int { return a } t.Fatalf("missing signature %q", want) } } + if _, ok := sigs["test/pkg.undeclared_call"]; ok { + t.Fatalf("ordinary unresolved CALL unexpectedly received a synthesized signature") + } builder := goSigBuilder{ sigs: map[string]FuncSig{}, From c1ee44e6baaed4ddc495168cb4c71dac6c788fd1 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sun, 26 Jul 2026 15:52:39 +0800 Subject: [PATCH 5/5] ci: retry stdlib corpus job