cmd/compile: refactor preserveStmt

By breaking it into smaller functions with duplicated logic removed.

Change-Id: I710414e8d13d91c19fb4bf4da1a31b00ee88ccee
Reviewed-on: https://go-review.googlesource.com/c/go/+/728961
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Cuong Manh Le
2025-12-10 23:16:24 +07:00
committed by Gopher Robot
parent 540ccca0a7
commit adb64adfd7

View File

@@ -81,25 +81,36 @@ func getAddressableNameFromNode(n ir.Node) *ir.Name {
return nil
}
// getKeepAliveNodes analyzes an IR node and returns a list of nodes that must be kept alive.
func getKeepAliveNodes(pos src.XPos, n ir.Node) ir.Nodes {
name := getAddressableNameFromNode(n)
if name != nil {
debugName(name, pos)
return ir.Nodes{name}
} else if deref := n.(*ir.StarExpr); deref != nil {
if base.Flag.LowerM > 1 {
base.WarnfAt(pos, "dereference will be kept alive")
}
return ir.Nodes{deref}
} else if base.Flag.LowerM > 1 {
base.WarnfAt(pos, "expr is unknown to bloop pass")
}
return nil
}
// keepAliveAt returns a statement that is either curNode, or a
// block containing curNode followed by a call to runtime.KeepAlive for each
// node in ns. These calls ensure that nodes in ns will be live until
// after curNode's execution.
func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node {
func keepAliveAt(ns ir.Nodes, curNode ir.Node) ir.Node {
if len(ns) == 0 {
return curNode
}
pos := curNode.Pos()
calls := []ir.Node{curNode}
calls := ir.Nodes{curNode}
for _, n := range ns {
if n == nil {
continue
}
if n.Sym() == nil {
continue
}
if n.Sym().IsBlank() {
if n == nil || n.Sym() == nil || n.Sym().IsBlank() {
continue
}
if !ir.IsAddressable(n) {
@@ -111,9 +122,7 @@ func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node {
arg.TypeWord = srcRType0
arg.SrcRType = srcRType0
}
callExpr := typecheck.Call(pos,
typecheck.LookupRuntime("KeepAlive"),
[]ir.Node{arg}, false).(*ir.CallExpr)
callExpr := typecheck.Call(pos, typecheck.LookupRuntime("KeepAlive"), ir.Nodes{arg}, false).(*ir.CallExpr)
callExpr.IsCompilerVarLive = true
callExpr.NoInline = true
calls = append(calls, callExpr)
@@ -132,135 +141,97 @@ func debugName(name *ir.Name, pos src.XPos) {
}
}
// preserveCallResults assigns the results of a call statement to temporary variables to ensure they remain alive.
func preserveCallResults(curFn *ir.Func, call *ir.CallExpr) ir.Node {
var ns ir.Nodes
lhs := make(ir.Nodes, call.Fun.Type().NumResults())
for i, res := range call.Fun.Type().Results() {
tmp := typecheck.TempAt(call.Pos(), curFn, res.Type)
lhs[i] = tmp
ns = append(ns, tmp)
}
if base.Flag.LowerM > 1 {
plural := ""
if call.Fun.Type().NumResults() > 1 {
plural = "s"
}
base.WarnfAt(call.Pos(), "function result%s will be kept alive", plural)
}
assign := typecheck.AssignExpr(ir.NewAssignListStmt(call.Pos(), ir.OAS2, lhs, ir.Nodes{call})).(*ir.AssignListStmt)
assign.Def = true
return keepAliveAt(ns, assign)
}
// preserveCallArgs ensures the arguments of a call statement are kept alive by transforming them into temporaries if necessary.
func preserveCallArgs(curFn *ir.Func, call *ir.CallExpr) ir.Node {
var argTmps ir.Nodes
var names ir.Nodes
preserveTmp := func(pos src.XPos, n ir.Node) ir.Node {
tmp := typecheck.TempAt(pos, curFn, n.Type())
argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(pos, tmp, n)))
names = append(names, tmp)
if base.Flag.LowerM > 1 {
base.WarnfAt(call.Pos(), "function arg will be kept alive")
}
return tmp
}
for i, a := range call.Args {
if name := getAddressableNameFromNode(a); name != nil {
// If they are name, keep them alive directly.
debugName(name, call.Pos())
names = append(names, name)
} else if a.Op() == ir.OSLICELIT {
// variadic args are encoded as slice literal.
s := a.(*ir.CompLitExpr)
var ns ir.Nodes
for i, elem := range s.List {
if name := getAddressableNameFromNode(elem); name != nil {
debugName(name, call.Pos())
ns = append(ns, name)
} else {
// We need a temporary to save this arg.
s.List[i] = preserveTmp(elem.Pos(), elem)
}
}
names = append(names, ns...)
} else {
// expressions, we need to assign them to temps and change the original arg to reference them.
call.Args[i] = preserveTmp(call.Pos(), a)
}
}
if len(argTmps) > 0 {
argTmps = append(argTmps, call)
return keepAliveAt(names, ir.NewBlockStmt(call.Pos(), argTmps))
}
return keepAliveAt(names, call)
}
// preserveStmt transforms stmt so that any names defined/assigned within it
// are used after stmt's execution, preventing their dead code elimination
// and dead store elimination. The return value is the transformed statement.
func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) {
ret = stmt
func preserveStmt(curFn *ir.Func, stmt ir.Node) ir.Node {
switch n := stmt.(type) {
case *ir.AssignStmt:
// Peel down struct and slice indexing to get the names
name := getAddressableNameFromNode(n.X)
if name != nil {
debugName(name, n.Pos())
ret = keepAliveAt([]ir.Node{name}, n)
} else if deref := n.X.(*ir.StarExpr); deref != nil {
ret = keepAliveAt([]ir.Node{deref}, n)
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "dereference will be kept alive")
}
} else if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
}
return keepAliveAt(getKeepAliveNodes(n.Pos(), n.X), n)
case *ir.AssignListStmt:
ns := []ir.Node{}
var ns ir.Nodes
for _, lhs := range n.Lhs {
name := getAddressableNameFromNode(lhs)
if name != nil {
debugName(name, n.Pos())
ns = append(ns, name)
} else if deref := lhs.(*ir.StarExpr); deref != nil {
ns = append(ns, deref)
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "dereference will be kept alive")
}
} else if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
}
ns = append(ns, getKeepAliveNodes(n.Pos(), lhs)...)
}
ret = keepAliveAt(ns, n)
return keepAliveAt(ns, n)
case *ir.AssignOpStmt:
name := getAddressableNameFromNode(n.X)
if name != nil {
debugName(name, n.Pos())
ret = keepAliveAt([]ir.Node{name}, n)
} else if deref := n.X.(*ir.StarExpr); deref != nil {
ret = keepAliveAt([]ir.Node{deref}, n)
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "dereference will be kept alive")
}
} else if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
}
return keepAliveAt(getKeepAliveNodes(n.Pos(), n.X), n)
case *ir.CallExpr:
curNode := stmt
// The function's results are not assigned, preserve them.
if n.Fun != nil && n.Fun.Type() != nil && n.Fun.Type().NumResults() != 0 {
ns := []ir.Node{}
// This function's results are not assigned, assign them to
// auto tmps and then keepAliveAt these autos.
// Note: markStmt assumes the context that it's called - this CallExpr is
// not within another OAS2, which is guaranteed by the case above.
results := n.Fun.Type().Results()
lhs := make([]ir.Node, len(results))
for i, res := range results {
tmp := typecheck.TempAt(n.Pos(), curFn, res.Type)
lhs[i] = tmp
ns = append(ns, tmp)
}
// Create an assignment statement.
assign := typecheck.AssignExpr(
ir.NewAssignListStmt(n.Pos(), ir.OAS2, lhs,
[]ir.Node{n})).(*ir.AssignListStmt)
assign.Def = true
curNode = assign
plural := ""
if len(results) > 1 {
plural = "s"
}
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "function result%s will be kept alive", plural)
}
ret = keepAliveAt(ns, curNode)
} else {
// This function probably doesn't return anything, keep its args alive.
argTmps := []ir.Node{}
names := []ir.Node{}
for i, a := range n.Args {
if name := getAddressableNameFromNode(a); name != nil {
// If they are name, keep them alive directly.
debugName(name, n.Pos())
names = append(names, name)
} else if a.Op() == ir.OSLICELIT {
// variadic args are encoded as slice literal.
s := a.(*ir.CompLitExpr)
ns := []ir.Node{}
for i, elem := range s.List {
if name := getAddressableNameFromNode(elem); name != nil {
debugName(name, n.Pos())
ns = append(ns, name)
} else {
// We need a temporary to save this arg.
tmp := typecheck.TempAt(elem.Pos(), curFn, elem.Type())
argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(elem.Pos(), tmp, elem)))
names = append(names, tmp)
s.List[i] = tmp
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "function arg will be kept alive")
}
}
}
names = append(names, ns...)
} else {
// expressions, we need to assign them to temps and change the original arg to reference
// them.
tmp := typecheck.TempAt(n.Pos(), curFn, a.Type())
argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(n.Pos(), tmp, a)))
names = append(names, tmp)
n.Args[i] = tmp
if base.Flag.LowerM > 1 {
base.WarnfAt(n.Pos(), "function arg will be kept alive")
}
}
}
if len(argTmps) > 0 {
argTmps = append(argTmps, n)
curNode = ir.NewBlockStmt(n.Pos(), argTmps)
}
ret = keepAliveAt(names, curNode)
return preserveCallResults(curFn, n)
}
// This function doesn't return anything, keep its args alive.
return preserveCallArgs(curFn, n)
}
return
return stmt
}
func preserveStmts(curFn *ir.Func, list ir.Nodes) {