Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: don't escape argument when all possible values of function pointer are known #73132

Open
dominikh opened this issue Apr 2, 2025 · 7 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance

Comments

@dominikh
Copy link
Member

dominikh commented Apr 2, 2025

Go version

go version go1.24.1 linux/amd64

Output of go env in your module/workspace:

-

What did you do?

package pkg

func Foo(arg int) {
	var x [4]int

	var fp func(*[4]int)
	if arg == 1 {
		fp = bar1
	} else {
		fp = bar2
	}
	fp(&x)
}

//go:noinline
func bar1(_ *[4]int) {}

//go:noinline
func bar2(_ *[4]int) {}

What did you see happen?

x gets allocated on the heap because the compiler cannot tell that the call to fp doesn't cause the argument to escape.

What did you expect to see?

All possible values of fp are statically known and known not to cause the argument to escape. I would expect the escape information for bar1 and bar2 to be merged and maintained with fp.

This comes up when providing multiple assembly implementations of a function for the same overall CPU architecture, for example both SSE and AVX ones. The implementation has to be chosen dynamically and I would like to pull that out of any loops, hence the function pointer.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Apr 2, 2025
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 2, 2025
@mateusz834
Copy link
Member

mateusz834 commented Apr 2, 2025

I think that this is doable (at least for function locals), see my work at CL 652036, that change tracks concrete types of interfaces, but I think it could be extended to analyze funcs, and this result could then be feed into the escape analysis (for cases where devirtualization was unsuccessful), and thus escape analysis could then be aware of all possible concrete types of an interface and all possible functions that can be called.

@mateusz834 mateusz834 added Performance and removed BugReport Issues describing a possible bug in the Go implementation. labels Apr 2, 2025
@mateusz834
Copy link
Member

CC @golang/compiler

@adonovan
Copy link
Member

adonovan commented Apr 2, 2025

I suspect many local func values could benefit from a sparse dataflow analysis that models the escape bits of each value of func type. An even more common and trivial case is that of a self-recursive local function (which requires the var trick): the dynamic call causes the current escape analysis to make very conservative judgments, even when the call could be made static with only local inferences, improving both the CALL instruction and the escape judgments.

func f() {
	var x int

	var v func(x *int) // self-recursive function
	v = func(x *int) {
		v(x)
	}

	v(&x) // (apparently) dynamic call -> conservative "x escapes" judgment
	v2(&x) // static call to self-recursive function -> no escape
}

func v2(x *int) {
	v2(x)
}

I think a good first step would be for the compiler to recognize the special case of var v func(); v = func(){...}; v() and treat it as a static call.

@mateusz834
Copy link
Member

mateusz834 commented Apr 2, 2025

I think a good first step would be for the compiler to recognize the special case of var v func(); v = func(){...}; v() and treat it as a static call.

Yep, i think that with mine approach described in #73132 (comment) that could be done entirely in the devirtualizer, unless there is some other edge-case that i am not aware of. For the implementation to be simple it will additionally require a nil check on the non-static func value (in your example v) at every devirtualized call to v.

@adonovan
Copy link
Member

adonovan commented Apr 2, 2025

For the implementation to be simple it will additionally require a nil check on the non-static func value (in your example v) at every devirtualized call to v.

The usual pattern is that v is assigned exactly once, before all uses, so it could (in effect) be turned from a variable into a function symbol.

@cherrymui
Copy link
Member

I suspect many local func values could benefit from a sparse dataflow analysis that models the escape bits of each value of func type.

Yes. Currently there is only very limited dataflow analysis in the escape analysis, essentially only a few forms of "assigned only once". A more general solution is to do escape analysis in SSA, where the dataflow analysis follows naturally. That would need a good amount of work, though.

It would probably not hard to recognize the var v func(); v = func(){...} pattern as "assigned only once". It wouldn't help the original issue, though.

@cagedmantis cagedmantis added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Apr 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
Development

No branches or pull requests

7 participants