Repository: go
Issue: cmd/compile: avoid always escaping the receivers in interface method calls
Labels: NeedsFix,compiler/runtime

### Background

Currently, interface arguments to functions frequently escape due to subsequent use of the interface in an interface method call.

This affects many APIs, including things like marshalling/unmarshalling APIs, logging APIs, things like fmt.Sprintf, and many other flavors of APIs that take interfaces.

For example, in Go 1.21, the `val` here escapes and is heap allocated for this reason (and other for reasons as well):

```go
 val := 1000
 fmt.Sprintf("%d", val)
```

If we consider an extremely simplified implementation of Sprintf:

```go
  func Print(input any) {
    if v, ok := input.(Stringer); ok {
       println(v.String())
    }
  }
```

When `v.String` is called as an interface method in that example, `v` might contain a type like `Leaking`:

```go
var global any
type Leaking struct {a, b int}

 // The receiver l cannot be stack allocated because String leaks l to a global variable.
func (l *Leaking) String() string { global = l; return "" } 
```

The current compiler knows this is possible, and as a result, the `input` interface argument to our simple Print function is conservatively marked as escaping. This is part of the reason the real fmt.Sprintf causes `val` to be allocated above.

Frequently, though, `v` contains a type like `Nice`:

```go
type Nice struct {a, b int}

// The receiver n could in theory be stack allocated in n := Nice{}; Print(n)
func (n Nice) String() string { return "something" } 
```

### Suggestion

The suggestion is for escape analysis to propagate what it knows about the use of interfaces in method calls to then prove (when it can) whether it is dealing with a type like Leaking or Nice to then avoid allocating the `n` in ` n := Nice{}; Print(n)`.

In particular, escape analysis has had the concept of pseudo locations for things like the heap, and the escape analysis data-flow graph in the current compiler contains an edge leading to a heap pseudo location for each interface method call observed.

We could instead track the interface method use flows separately from other flows to the heap, including propagating across function call boundaries by tagging the associated function & method parameters. When a concrete value is later converted to an interface type (e.g., `Print(n)` in our simple example above), we look at what we know about the type and its methods to see if we can prove that it is incapable of leaking if used as a method receiver in an interface method call.

I sent [CL 524945](https://go.dev/cl/524945) with implementation of the idea, along with some related CLs like [524937](https://go.dev/cl/524937) and [524944](https://go.dev/cl/524944).

Those are a part of a larger stack targeting the fmt print functions. By the end of my stack (as of [CL 528538](https://go.dev/cl/528538)), this no longer allocates the Point struct on the heap:

```go
  type Point struct {x, y int}
  func (p Point) X() int { return p.x }
  ...
  p := Point{1, 2}
  fmt.Sprintf("%v", p)
```

(**Edit**: to better illustrate, also added an example method to Point, which still avoids allocating with [CL 528538](https://go.dev/cl/528538)).

The primary [CL 524945](https://go.dev/cl/524945) is still WIP, but at least passes all.bash and the TryBots, including passing the more specific interface receiver tests I added. (It passes the older TryBots, but currently fails the new LUCI TryBots for LUCI-specific reasons). The first cut does not differentiate between a type that only has some methods with leaking receivers vs. the specific method in question, but [CL 528539](https://go.dev/cl/528539) is also a small refinement I had also been thinking about that attempts to address that. (That is not needed for many cases, and that CL is currently in much rougher shape & more exploratory).

In the initial discussion in Gerrit on [CL 524945](https://go.dev/cl/524945), Matthew Dempsky said he liked the idea. He also suggested opening an issue to help discussion, which is now this issue.

Matthew also later sent [CL 526520](https://go.dev/cl/526520) with an alternative implementation of the core idea.

CC @mdempsky, @ianlancetaylor