blob: ce836cbd0720e8dbaf4003a06657d601dedd52ac [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package rewriteresults rewrites local variables returned directly by a
// function to use the corresponding result parameter's storage.
package rewriteresults
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/types"
"fmt"
"os"
)
// Funcs applies the rewriteresults pass to fns.
func Funcs(fns []*ir.Func) {
if base.Flag.N != 0 || base.Debug.RewriteResults == 0 {
return
}
for _, fn := range fns {
rewrite(fn)
}
}
func rewrite(fn *ir.Func) {
if fn == nil || len(fn.Body) == 0 {
return
}
var returns []*ir.ReturnStmt
hasDefer := false
ir.VisitList(fn.Body, func(n ir.Node) {
switch n := n.(type) {
case *ir.ReturnStmt:
returns = append(returns, n)
case *ir.GoDeferStmt:
if n.Op() == ir.ODEFER {
hasDefer = true
}
}
})
if hasDefer || len(returns) == 0 {
return
}
results := fn.Type().Results()
for _, ret := range returns {
if len(ret.Results) == 0 || len(ret.Results) != len(results) {
return
}
}
// candidates maps each local variable to the result slot whose storage
// it can use.
candidates := make(map[*ir.Name]*ir.Name)
conflicts := make(map[*ir.Name]bool)
for i, result := range results {
// If the result is already named, source already has access to
// its storage; leave those functions alone for now.
if !isAnonymousResult(result) {
continue
}
out := result.Nname.(*ir.Name)
var local *ir.Name
for _, ret := range returns {
n, ok := ret.Results[i].(*ir.Name)
if !ok {
continue
}
if !isCandidateLocal(n, result) {
continue
}
if local == nil {
local = n
} else if local != n {
local = nil
break
}
}
if local == nil {
continue
}
if prev, ok := candidates[local]; ok && prev != out {
conflicts[local] = true
continue
}
candidates[local] = out
}
if len(candidates) == 0 {
return
}
for local := range conflicts {
delete(candidates, local)
}
if len(candidates) == 0 {
return
}
captured := make(map[*ir.Name]bool)
ir.VisitList(fn.Body, func(n ir.Node) {
if n, ok := n.(*ir.ClosureExpr); ok {
for _, cv := range n.Func.ClosureVars {
captured[cv.Canonical()] = true
}
}
})
for local := range candidates {
if captured[local] {
delete(candidates, local)
}
}
if len(candidates) == 0 {
return
}
for local, out := range candidates {
if local.Addrtaken() {
out.SetAddrtaken(true)
}
out.SetUsed(true)
out.SetEsc(local.Esc())
}
if base.Debug.RewriteResults > 1 {
for local, out := range candidates {
fmt.Fprintf(os.Stderr, "rewriteresults: %v: %v => %v\n", ir.FuncName(fn), local, out)
}
}
var edit func(ir.Node) ir.Node
edit = func(n ir.Node) ir.Node {
switch n := n.(type) {
case nil:
return nil
case *ir.Name:
if out, ok := candidates[n]; ok {
return out
}
return n
}
ir.EditChildren(n, edit)
return n
}
for i, n := range fn.Body {
fn.Body[i] = edit(n)
}
}
func isCandidateLocal(n *ir.Name, result *types.Field) bool {
return n.Class == ir.PAUTO &&
!n.AutoTemp() &&
n.Esc() != ir.EscHeap &&
isBareDecl(n) &&
types.Identical(n.Type(), result.Type)
}
func isBareDecl(n *ir.Name) bool {
// Locals declared without an explicit initializer have no defining
// assignment; their nil-RHS assignment only supplies the zero value.
return n.Defn == nil
}
func isAnonymousResult(result *types.Field) bool {
return result.Sym == nil || result.Sym.IsBlank()
}