Skip to content

cmd/compile: PGO does not devirtualize methods in another package (log: "no function body") #60561

Closed
@thepudds

Description

@thepudds

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions