| // 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() |
| } |