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 0e57225..72548fd 100644 --- a/go_translate.go +++ b/go_translate.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/constant" "go/types" + "reflect" "regexp" "strconv" "strings" @@ -158,6 +159,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 +177,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 +206,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 +220,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 +258,24 @@ 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 + } + if !tailJump { + // Keep ordinary unresolved calls untyped so the backend reports the + // missing declaration instead of silently dropping arguments/results. 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 + // 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} } } return nil @@ -547,6 +579,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 c5b55d9..acc0f51 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 := newAliasTypeForTest(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}, } { @@ -203,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: "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<>"}, }, } sigs, err := goSigsForAsmFile(pkg, file, testResolveSym("test/pkg"), "arm64", func(resolved string) (FuncSig, bool) { @@ -216,11 +220,14 @@ 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) } } + if _, ok := sigs["test/pkg.undeclared_call"]; ok { + t.Fatalf("ordinary unresolved CALL unexpectedly received a synthesized signature") + } builder := goSigBuilder{ sigs: map[string]FuncSig{}, @@ -259,6 +266,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") } @@ -320,3 +342,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) + } +}