Skip to content

Commit 6cbe522

Browse files
prattmicthanm
authored andcommitted
[release-branch.go1.22] cmd/compile: fail noder.LookupFunc gracefully if function generic
PGO uses noder.LookupFunc to look for devirtualization targets in export data. LookupFunc does not support type-parameterized functions, and will currently fail the build when attempting to lookup a type-parameterized function because objIdx is passed the wrong number of type arguments. This doesn't usually come up, as a PGO profile will report a generic function with a symbol name like Func[.go.shape.foo]. In export data, this is just Func, so when we do LookupFunc("Func[.go.shape.foo]") lookup simply fails because the name doesn't exist. However, if Func is not generic when the profile is collected, but the source has since changed to make Func generic, then LookupFunc("Func") will find the object successfully, only to fail the build because we failed to provide type arguments. Handle this with a objIdxMayFail, which allows graceful failure if the object requires type arguments. Bumping the language version to 1.21 in pgo_devirtualize_test.go is required for type inference of the uses of mult.MultFn in cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go. For #65615. Fixes #65618. Change-Id: I84d9344840b851182f5321b8f7a29a591221b29f Reviewed-on: https://go-review.googlesource.com/c/go/+/562737 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> (cherry picked from commit 532c6f1) Reviewed-on: https://go-review.googlesource.com/c/go/+/563016
1 parent fb86598 commit 6cbe522

File tree

3 files changed

+180
-66
lines changed

3 files changed

+180
-66
lines changed

src/cmd/compile/internal/noder/reader.go

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -663,9 +663,24 @@ func (pr *pkgReader) objInstIdx(info objInfo, dict *readerDict, shaped bool) ir.
663663
}
664664

665665
// objIdx returns the specified object, instantiated with the given
666-
// type arguments, if any. If shaped is true, then the shaped variant
667-
// of the object is returned instead.
666+
// type arguments, if any.
667+
// If shaped is true, then the shaped variant of the object is returned
668+
// instead.
668669
func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) ir.Node {
670+
n, err := pr.objIdxMayFail(idx, implicits, explicits, shaped)
671+
if err != nil {
672+
base.Fatalf("%v", err)
673+
}
674+
return n
675+
}
676+
677+
// objIdxMayFail is equivalent to objIdx, but returns an error rather than
678+
// failing the build if this object requires type arguments and the incorrect
679+
// number of type arguments were passed.
680+
//
681+
// Other sources of internal failure (such as duplicate definitions) still fail
682+
// the build.
683+
func (pr *pkgReader) objIdxMayFail(idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) (ir.Node, error) {
669684
rname := pr.newReader(pkgbits.RelocName, idx, pkgbits.SyncObject1)
670685
_, sym := rname.qualifiedIdent()
671686
tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj))
@@ -674,22 +689,25 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
674689
assert(!sym.IsBlank())
675690
switch sym.Pkg {
676691
case types.BuiltinPkg, types.UnsafePkg:
677-
return sym.Def.(ir.Node)
692+
return sym.Def.(ir.Node), nil
678693
}
679694
if pri, ok := objReader[sym]; ok {
680-
return pri.pr.objIdx(pri.idx, nil, explicits, shaped)
695+
return pri.pr.objIdxMayFail(pri.idx, nil, explicits, shaped)
681696
}
682697
if sym.Pkg.Path == "runtime" {
683-
return typecheck.LookupRuntime(sym.Name)
698+
return typecheck.LookupRuntime(sym.Name), nil
684699
}
685700
base.Fatalf("unresolved stub: %v", sym)
686701
}
687702

688-
dict := pr.objDictIdx(sym, idx, implicits, explicits, shaped)
703+
dict, err := pr.objDictIdx(sym, idx, implicits, explicits, shaped)
704+
if err != nil {
705+
return nil, err
706+
}
689707

690708
sym = dict.baseSym
691709
if !sym.IsBlank() && sym.Def != nil {
692-
return sym.Def.(*ir.Name)
710+
return sym.Def.(*ir.Name), nil
693711
}
694712

695713
r := pr.newReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1)
@@ -725,15 +743,15 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
725743
name := do(ir.OTYPE, false)
726744
setType(name, r.typ())
727745
name.SetAlias(true)
728-
return name
746+
return name, nil
729747

730748
case pkgbits.ObjConst:
731749
name := do(ir.OLITERAL, false)
732750
typ := r.typ()
733751
val := FixValue(typ, r.Value())
734752
setType(name, typ)
735753
setValue(name, val)
736-
return name
754+
return name, nil
737755

738756
case pkgbits.ObjFunc:
739757
if sym.Name == "init" {
@@ -768,7 +786,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
768786
}
769787

770788
rext.funcExt(name, nil)
771-
return name
789+
return name, nil
772790

773791
case pkgbits.ObjType:
774792
name := do(ir.OTYPE, true)
@@ -805,13 +823,13 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
805823
r.needWrapper(typ)
806824
}
807825

808-
return name
826+
return name, nil
809827

810828
case pkgbits.ObjVar:
811829
name := do(ir.ONAME, false)
812830
setType(name, r.typ())
813831
rext.varExt(name)
814-
return name
832+
return name, nil
815833
}
816834
}
817835

@@ -908,7 +926,7 @@ func shapify(targ *types.Type, basic bool) *types.Type {
908926
}
909927

910928
// objDictIdx reads and returns the specified object dictionary.
911-
func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) *readerDict {
929+
func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) (*readerDict, error) {
912930
r := pr.newReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1)
913931

914932
dict := readerDict{
@@ -919,7 +937,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
919937
nexplicits := r.Len()
920938

921939
if nimplicits > len(implicits) || nexplicits != len(explicits) {
922-
base.Fatalf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits))
940+
return nil, fmt.Errorf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits))
923941
}
924942

925943
dict.targs = append(implicits[:nimplicits:nimplicits], explicits...)
@@ -984,7 +1002,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
9841002
dict.itabs[i] = itabInfo{typ: r.typInfo(), iface: r.typInfo()}
9851003
}
9861004

987-
return &dict
1005+
return &dict, nil
9881006
}
9891007

9901008
func (r *reader) typeParamNames() {
@@ -2529,7 +2547,10 @@ func (pr *pkgReader) objDictName(idx pkgbits.Index, implicits, explicits []*type
25292547
base.Fatalf("unresolved stub: %v", sym)
25302548
}
25312549

2532-
dict := pr.objDictIdx(sym, idx, implicits, explicits, false)
2550+
dict, err := pr.objDictIdx(sym, idx, implicits, explicits, false)
2551+
if err != nil {
2552+
base.Fatalf("%v", err)
2553+
}
25332554

25342555
return pr.dictNameOf(dict)
25352556
}

src/cmd/compile/internal/noder/unified.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ func lookupFunction(pkg *types.Pkg, symName string) (*ir.Func, error) {
8080
return nil, fmt.Errorf("func sym %v missing objReader", sym)
8181
}
8282

83-
name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name)
83+
node, err := pri.pr.objIdxMayFail(pri.idx, nil, nil, false)
84+
if err != nil {
85+
return nil, fmt.Errorf("func sym %v lookup error: %w", sym, err)
86+
}
87+
name := node.(*ir.Name)
8488
if name.Op() != ir.ONAME || name.Class != ir.PFUNC {
8589
return nil, fmt.Errorf("func sym %v refers to non-function name: %v", sym, name)
8690
}
@@ -105,7 +109,11 @@ func lookupMethod(pkg *types.Pkg, symName string) (*ir.Func, error) {
105109
return nil, fmt.Errorf("type sym %v missing objReader", typ)
106110
}
107111

108-
name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name)
112+
node, err := pri.pr.objIdxMayFail(pri.idx, nil, nil, false)
113+
if err != nil {
114+
return nil, fmt.Errorf("func sym %v lookup error: %w", typ, err)
115+
}
116+
name := node.(*ir.Name)
109117
if name.Op() != ir.OTYPE {
110118
return nil, fmt.Errorf("type sym %v refers to non-type name: %v", typ, name)
111119
}

src/cmd/compile/internal/test/pgo_devirtualize_test.go

Lines changed: 133 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@ import (
1414
"testing"
1515
)
1616

17+
type devirtualization struct {
18+
pos string
19+
callee string
20+
}
21+
1722
// testPGODevirtualize tests that specific PGO devirtualize rewrites are performed.
18-
func testPGODevirtualize(t *testing.T, dir string) {
23+
func testPGODevirtualize(t *testing.T, dir string, want []devirtualization) {
1924
testenv.MustHaveGoRun(t)
2025
t.Parallel()
2126

2227
const pkg = "example.com/pgo/devirtualize"
2328

2429
// Add a go.mod so we have a consistent symbol names in this temp dir.
2530
goMod := fmt.Sprintf(`module %s
26-
go 1.19
31+
go 1.21
2732
`, pkg)
2833
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
2934
t.Fatalf("error writing go.mod: %v", err)
@@ -60,51 +65,6 @@ go 1.19
6065
t.Fatalf("error starting go test: %v", err)
6166
}
6267

63-
type devirtualization struct {
64-
pos string
65-
callee string
66-
}
67-
68-
want := []devirtualization{
69-
// ExerciseIface
70-
{
71-
pos: "./devirt.go:101:20",
72-
callee: "mult.Mult.Multiply",
73-
},
74-
{
75-
pos: "./devirt.go:101:39",
76-
callee: "Add.Add",
77-
},
78-
// ExerciseFuncConcrete
79-
{
80-
pos: "./devirt.go:173:36",
81-
callee: "AddFn",
82-
},
83-
{
84-
pos: "./devirt.go:173:15",
85-
callee: "mult.MultFn",
86-
},
87-
// ExerciseFuncField
88-
{
89-
pos: "./devirt.go:207:35",
90-
callee: "AddFn",
91-
},
92-
{
93-
pos: "./devirt.go:207:19",
94-
callee: "mult.MultFn",
95-
},
96-
// ExerciseFuncClosure
97-
// TODO(prattmic): Closure callees not implemented.
98-
//{
99-
// pos: "./devirt.go:249:27",
100-
// callee: "AddClosure.func1",
101-
//},
102-
//{
103-
// pos: "./devirt.go:249:15",
104-
// callee: "mult.MultClosure.func1",
105-
//},
106-
}
107-
10868
got := make(map[devirtualization]struct{})
10969

11070
devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
@@ -172,5 +132,130 @@ func TestPGODevirtualize(t *testing.T) {
172132
}
173133
}
174134

175-
testPGODevirtualize(t, dir)
135+
want := []devirtualization{
136+
// ExerciseIface
137+
{
138+
pos: "./devirt.go:101:20",
139+
callee: "mult.Mult.Multiply",
140+
},
141+
{
142+
pos: "./devirt.go:101:39",
143+
callee: "Add.Add",
144+
},
145+
// ExerciseFuncConcrete
146+
{
147+
pos: "./devirt.go:173:36",
148+
callee: "AddFn",
149+
},
150+
{
151+
pos: "./devirt.go:173:15",
152+
callee: "mult.MultFn",
153+
},
154+
// ExerciseFuncField
155+
{
156+
pos: "./devirt.go:207:35",
157+
callee: "AddFn",
158+
},
159+
{
160+
pos: "./devirt.go:207:19",
161+
callee: "mult.MultFn",
162+
},
163+
// ExerciseFuncClosure
164+
// TODO(prattmic): Closure callees not implemented.
165+
//{
166+
// pos: "./devirt.go:249:27",
167+
// callee: "AddClosure.func1",
168+
//},
169+
//{
170+
// pos: "./devirt.go:249:15",
171+
// callee: "mult.MultClosure.func1",
172+
//},
173+
}
174+
175+
testPGODevirtualize(t, dir, want)
176+
}
177+
178+
// Regression test for https://go.dev/issue/65615. If a target function changes
179+
// from non-generic to generic we can't devirtualize it (don't know the type
180+
// parameters), but the compiler should not crash.
181+
func TestLookupFuncGeneric(t *testing.T) {
182+
wd, err := os.Getwd()
183+
if err != nil {
184+
t.Fatalf("error getting wd: %v", err)
185+
}
186+
srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
187+
188+
// Copy the module to a scratch location so we can add a go.mod.
189+
dir := t.TempDir()
190+
if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
191+
t.Fatalf("error creating dir: %v", err)
192+
}
193+
for _, file := range []string{"devirt.go", "devirt_test.go", "devirt.pprof", filepath.Join("mult.pkg", "mult.go")} {
194+
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
195+
t.Fatalf("error copying %s: %v", file, err)
196+
}
197+
}
198+
199+
// Change MultFn from a concrete function to a parameterized function.
200+
if err := convertMultToGeneric(filepath.Join(dir, "mult.pkg", "mult.go")); err != nil {
201+
t.Fatalf("error editing mult.go: %v", err)
202+
}
203+
204+
// Same as TestPGODevirtualize except for MultFn, which we cannot
205+
// devirtualize to because it has become generic.
206+
//
207+
// Note that the important part of this test is that the build is
208+
// successful, not the specific devirtualizations.
209+
want := []devirtualization{
210+
// ExerciseIface
211+
{
212+
pos: "./devirt.go:101:20",
213+
callee: "mult.Mult.Multiply",
214+
},
215+
{
216+
pos: "./devirt.go:101:39",
217+
callee: "Add.Add",
218+
},
219+
// ExerciseFuncConcrete
220+
{
221+
pos: "./devirt.go:173:36",
222+
callee: "AddFn",
223+
},
224+
// ExerciseFuncField
225+
{
226+
pos: "./devirt.go:207:35",
227+
callee: "AddFn",
228+
},
229+
// ExerciseFuncClosure
230+
// TODO(prattmic): Closure callees not implemented.
231+
//{
232+
// pos: "./devirt.go:249:27",
233+
// callee: "AddClosure.func1",
234+
//},
235+
//{
236+
// pos: "./devirt.go:249:15",
237+
// callee: "mult.MultClosure.func1",
238+
//},
239+
}
240+
241+
testPGODevirtualize(t, dir, want)
242+
}
243+
244+
var multFnRe = regexp.MustCompile(`func MultFn\(a, b int64\) int64`)
245+
246+
func convertMultToGeneric(path string) error {
247+
content, err := os.ReadFile(path)
248+
if err != nil {
249+
return fmt.Errorf("error opening: %w", err)
250+
}
251+
252+
if !multFnRe.Match(content) {
253+
return fmt.Errorf("MultFn not found; update regexp?")
254+
}
255+
256+
// Users of MultFn shouldn't need adjustment, type inference should
257+
// work OK.
258+
content = multFnRe.ReplaceAll(content, []byte(`func MultFn[T int32|int64](a, b T) T`))
259+
260+
return os.WriteFile(path, content, 0644)
176261
}

0 commit comments

Comments
 (0)