Description
What version of Go are you using (go version
)?
$ gotip version go version devel go1.21-c7b2f649 Tue May 30 17:30:28 2023
Does this issue reproduce with the latest release?
Feature only present in tip.
What did you do?
When experimenting with the new PGO-driven indirect call specialization from #59959, I did not see devirtualization happening on methods in another package, even when those methods are directly referenced in the current package.
CL 492436 notes that there are limitations in the first version of this, including:
- Callees not directly referenced in the current package can be missed (even if they are in the transitive dependences).
However, I do not see devirtualization even after attempting to take that into account, though I might be holding it wrong.
I created a simplified example (playground link), which includes this main.go:
import (
// ...
"example/speak"
)
func main() {
// configure profiling
// ...
s1 := &speak.Speaker1{}
s2 := &speak.Speaker2{}
println(s1.Speak())
println(s2.Speak())
for i := 0; i < 10_000; i++ {
_ = f()
}
}
func f() string {
var s speak.Speaker // interface
s = &speak.Speaker1{}
if rand.Int()%10 == 0 {
s = &speak.Speaker2{}
}
return s.Speak()
}
I then create a cpu profile:
$ gotip run . -cpuprofile=cpu.out
And use that cpu profile with pgo:
$ gotip build -pgo=cpu.out -gcflags='-m=99 -d=pgodebug=99' &> debug-build.out
The log shows should not PGO devirtualize (*Speaker1).Speak: no function body
:
./main.go:42:16: PGO devirtualize considering call s.Speak()
./main.go:42:16: edge main.f:6 -> example/speak.(*Speaker1).Speak (weight 186): hottest so far
./main.go:42:16: edge main.f:6 -> example/speak.(*Speaker2).Speak (weight 34): too cold (hottest 186)
./main.go:42:16 call main.f:6: hottest callee example/speak.(*Speaker1).Speak (weight 186)
./speak/speak.go:9:6: should not PGO devirtualize (*Speaker1).Speak: no function body
If I comment out the only explicit uses of Speaker1.Speak and Speaker2.Speak in package main:
// s1 := &speak.Speaker1{}
// s2 := &speak.Speaker2{}
// println(s1.Speak())
// println(s2.Speak())
I then get a different log message saying missing IR
for (*Speaker1).Speak:
$ gotip build -pgo=cpu.out -gcflags='-m=99 -d=pgodebug=99' &> debug-build-2.out
./main.go:42:16: PGO devirtualize considering call s.Speak()
./main.go:42:16: edge main.f:6 -> example/speak.(*Speaker1).Speak (weight 186) (missing IR): hottest so far
./main.go:42:16: edge main.f:6 -> example/speak.(*Speaker2).Speak (weight 34): too cold (hottest 186)
./main.go:42:16 call main.f:6: hottest callee example/speak.(*Speaker1).Speak (weight 186)
I suspect this second message of missing IR
might be expected given the limitations listed in the CL because commenting out those lines means there are no explicit references to the Speak callees in package main, but I'm not sure that the first message of no function body
is expected when those lines are not commented out.
The no function body
reason seems to be coming from inline.InlineImpossible(fn)
:
// If fn has no body (is defined outside of Go), cannot inline it.
if len(fn.Body) == 0 {
reason = "no function body"
return reason
}
Finally, regular inlining does appear to work on those methods (here using the original main.go, without having commented anything out):
$ gotip build -gcflags=-m
...
./main.go:28:18: inlining call to speak.(*Speaker1).Speak
./main.go:29:18: inlining call to speak.(*Speaker2).Speak
What did you expect to see?
PGO-based devirtualization on methods in another package when those methods are directly referenced in the current package.
What did you see instead?
No devirtualization, with the log messages above.
Sorry in advance if this is just not supported yet, or if I've made a mistake.
CC @prattmic