From adb64adfd74470a0653dde9214a1acb0ddf72268 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 10 Dec 2025 23:16:24 +0700 Subject: [PATCH] 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Cuong Manh Le --- src/cmd/compile/internal/bloop/bloop.go | 227 +++++++++++------------- 1 file changed, 99 insertions(+), 128 deletions(-) diff --git a/src/cmd/compile/internal/bloop/bloop.go b/src/cmd/compile/internal/bloop/bloop.go index 7a376d292b..f2111cf952 100644 --- a/src/cmd/compile/internal/bloop/bloop.go +++ b/src/cmd/compile/internal/bloop/bloop.go @@ -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) { -- 2.52.0