Source file src/cmd/compile/internal/bloop/bloop.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package bloop
     6  
     7  // This file contains support routines for keeping
     8  // statements alive
     9  // in such loops (example):
    10  //
    11  //	for b.Loop() {
    12  //		var a, b int
    13  //		a = 5
    14  //		b = 6
    15  //		f(a, b)
    16  //	}
    17  //
    18  // The results of a, b and f(a, b) will be kept alive.
    19  //
    20  // Formally, the lhs (if they are [ir.Name]-s) of
    21  // [ir.AssignStmt], [ir.AssignListStmt],
    22  // [ir.AssignOpStmt], and the results of [ir.CallExpr]
    23  // or its args if it doesn't return a value will be kept
    24  // alive.
    25  //
    26  // The keep alive logic is implemented with as wrapping a
    27  // runtime.KeepAlive around the Name.
    28  //
    29  // TODO: currently this is implemented with KeepAlive
    30  // because it will prevent DSE and DCE which is probably
    31  // what we want right now. And KeepAlive takes an ssa
    32  // value instead of a symbol, which is easier to manage.
    33  // But since KeepAlive's context was mainly in the runtime
    34  // and GC, should we implement a new intrinsic that lowers
    35  // to OpVarLive? Peeling out the symbols is a bit tricky
    36  // and also VarLive seems to assume that there exists a
    37  // VarDef on the same symbol that dominates it.
    38  
    39  import (
    40  	"cmd/compile/internal/base"
    41  	"cmd/compile/internal/ir"
    42  	"cmd/compile/internal/reflectdata"
    43  	"cmd/compile/internal/typecheck"
    44  	"cmd/compile/internal/types"
    45  	"cmd/internal/src"
    46  )
    47  
    48  // getNameFromNode tries to iteratively peel down the node to
    49  // get the name.
    50  func getNameFromNode(n ir.Node) *ir.Name {
    51  	// Tries to iteratively peel down the node to get the names.
    52  	for n != nil {
    53  		switch n.Op() {
    54  		case ir.ONAME:
    55  			// Found the name, stop the loop.
    56  			return n.(*ir.Name)
    57  		case ir.OSLICE, ir.OSLICE3:
    58  			n = n.(*ir.SliceExpr).X
    59  		case ir.ODOT:
    60  			n = n.(*ir.SelectorExpr).X
    61  		case ir.OCONV, ir.OCONVIFACE, ir.OCONVNOP:
    62  			n = n.(*ir.ConvExpr).X
    63  		case ir.OADDR:
    64  			n = n.(*ir.AddrExpr).X
    65  		case ir.ODOTPTR:
    66  			n = n.(*ir.SelectorExpr).X
    67  		case ir.OINDEX, ir.OINDEXMAP:
    68  			n = n.(*ir.IndexExpr).X
    69  		default:
    70  			n = nil
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  // getAddressableNameFromNode is like getNameFromNode but returns nil if the node is not addressable.
    77  func getAddressableNameFromNode(n ir.Node) *ir.Name {
    78  	if name := getNameFromNode(n); name != nil && ir.IsAddressable(name) {
    79  		return name
    80  	}
    81  	return nil
    82  }
    83  
    84  // keepAliveAt returns a statement that is either curNode, or a
    85  // block containing curNode followed by a call to runtime.KeepAlive for each
    86  // node in ns. These calls ensure that nodes in ns will be live until
    87  // after curNode's execution.
    88  func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node {
    89  	if len(ns) == 0 {
    90  		return curNode
    91  	}
    92  
    93  	pos := curNode.Pos()
    94  	calls := []ir.Node{curNode}
    95  	for _, n := range ns {
    96  		if n == nil {
    97  			continue
    98  		}
    99  		if n.Sym() == nil {
   100  			continue
   101  		}
   102  		if n.Sym().IsBlank() {
   103  			continue
   104  		}
   105  		if !ir.IsAddressable(n) {
   106  			base.FatalfAt(n.Pos(), "keepAliveAt: node %v is not addressable", n)
   107  		}
   108  		arg := ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TUNSAFEPTR], typecheck.NodAddr(n))
   109  		if !n.Type().IsInterface() {
   110  			srcRType0 := reflectdata.TypePtrAt(pos, n.Type())
   111  			arg.TypeWord = srcRType0
   112  			arg.SrcRType = srcRType0
   113  		}
   114  		callExpr := typecheck.Call(pos,
   115  			typecheck.LookupRuntime("KeepAlive"),
   116  			[]ir.Node{arg}, false).(*ir.CallExpr)
   117  		callExpr.IsCompilerVarLive = true
   118  		callExpr.NoInline = true
   119  		calls = append(calls, callExpr)
   120  	}
   121  
   122  	return ir.NewBlockStmt(pos, calls)
   123  }
   124  
   125  func debugName(name *ir.Name, pos src.XPos) {
   126  	if base.Flag.LowerM > 1 {
   127  		if name.Linksym() != nil {
   128  			base.WarnfAt(pos, "%s will be kept alive", name.Linksym().Name)
   129  		} else {
   130  			base.WarnfAt(pos, "expr will be kept alive")
   131  		}
   132  	}
   133  }
   134  
   135  // preserveStmt transforms stmt so that any names defined/assigned within it
   136  // are used after stmt's execution, preventing their dead code elimination
   137  // and dead store elimination. The return value is the transformed statement.
   138  func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) {
   139  	ret = stmt
   140  	switch n := stmt.(type) {
   141  	case *ir.AssignStmt:
   142  		// If the left hand side is blank, we need to assign it to a temp
   143  		// so that it can be kept alive.
   144  		if ir.IsBlank(n.X) {
   145  			tmp := typecheck.TempAt(n.Pos(), curFn, n.Y.Type())
   146  			n.X = tmp
   147  			n.Def = true
   148  			n.PtrInit().Append(typecheck.Stmt(ir.NewDecl(n.Pos(), ir.ODCL, tmp)))
   149  			stmt = typecheck.AssignExpr(n)
   150  			n = stmt.(*ir.AssignStmt)
   151  		}
   152  		// Peel down struct and slice indexing to get the names
   153  		name := getAddressableNameFromNode(n.X)
   154  		if name != nil {
   155  			debugName(name, n.Pos())
   156  			ret = keepAliveAt([]ir.Node{name}, n)
   157  		} else if deref, ok := n.X.(*ir.StarExpr); ok && deref != nil {
   158  			ret = keepAliveAt([]ir.Node{deref}, n)
   159  			if base.Flag.LowerM > 1 {
   160  				base.WarnfAt(n.Pos(), "dereference will be kept alive")
   161  			}
   162  		} else if base.Flag.LowerM > 1 {
   163  			base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
   164  		}
   165  	case *ir.AssignListStmt:
   166  		ns := []ir.Node{}
   167  		hasBlank := false
   168  		for i, lhs := range n.Lhs {
   169  			if ir.IsBlank(lhs) {
   170  				// If the left hand side has blanks, we need to assign them to temps
   171  				// so that they can be kept alive.
   172  				var typ *types.Type
   173  				// AssignListStmt can have tuple or a list of expressions on the right hand side.
   174  				if len(n.Rhs) == 1 && n.Rhs[0].Type() != nil &&
   175  					n.Rhs[0].Type().IsTuple() &&
   176  					len(n.Lhs) == n.Rhs[0].Type().NumFields() {
   177  					typ = n.Rhs[0].Type().Field(i).Type
   178  				} else if len(n.Rhs) == len(n.Lhs) {
   179  					typ = n.Rhs[i].Type()
   180  				} else {
   181  					// Unrecognized shapes, skip?
   182  					base.WarnfAt(n.Pos(), "unrecognized shape for assign list stmt for blank assignment")
   183  					continue
   184  				}
   185  				tmp := typecheck.TempAt(n.Pos(), curFn, typ)
   186  				n.Lhs[i] = tmp
   187  				n.PtrInit().Append(typecheck.Stmt(ir.NewDecl(n.Pos(), ir.ODCL, tmp)))
   188  				hasBlank = true
   189  				lhs = tmp
   190  			}
   191  			name := getAddressableNameFromNode(lhs)
   192  			if name != nil {
   193  				debugName(name, n.Pos())
   194  				ns = append(ns, name)
   195  			} else if deref, ok := lhs.(*ir.StarExpr); ok && deref != nil {
   196  				ns = append(ns, deref)
   197  				if base.Flag.LowerM > 1 {
   198  					base.WarnfAt(n.Pos(), "dereference will be kept alive")
   199  				}
   200  			} else if base.Flag.LowerM > 1 {
   201  				base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
   202  			}
   203  		}
   204  		if hasBlank {
   205  			// blank nodes are rewritten to temps, we need to typecheck the node again.
   206  			n.Def = true
   207  			stmt = typecheck.AssignExpr(n)
   208  			n = stmt.(*ir.AssignListStmt)
   209  		}
   210  		ret = keepAliveAt(ns, n)
   211  	case *ir.AssignOpStmt:
   212  		name := getAddressableNameFromNode(n.X)
   213  		if name != nil {
   214  			debugName(name, n.Pos())
   215  			ret = keepAliveAt([]ir.Node{name}, n)
   216  		} else if deref, ok := n.X.(*ir.StarExpr); ok && deref != nil {
   217  			ret = keepAliveAt([]ir.Node{deref}, n)
   218  			if base.Flag.LowerM > 1 {
   219  				base.WarnfAt(n.Pos(), "dereference will be kept alive")
   220  			}
   221  		} else if base.Flag.LowerM > 1 {
   222  			base.WarnfAt(n.Pos(), "expr is unknown to bloop pass")
   223  		}
   224  	case *ir.CallExpr:
   225  		curNode := stmt
   226  		if n.Fun != nil && n.Fun.Type() != nil && n.Fun.Type().NumResults() != 0 {
   227  			ns := []ir.Node{}
   228  			// This function's results are not assigned, assign them to
   229  			// auto tmps and then keepAliveAt these autos.
   230  			// Note: markStmt assumes the context that it's called - this CallExpr is
   231  			// not within another OAS2, which is guaranteed by the case above.
   232  			results := n.Fun.Type().Results()
   233  			lhs := make([]ir.Node, len(results))
   234  			for i, res := range results {
   235  				tmp := typecheck.TempAt(n.Pos(), curFn, res.Type)
   236  				lhs[i] = tmp
   237  				ns = append(ns, tmp)
   238  			}
   239  
   240  			// Create an assignment statement.
   241  			assign := typecheck.AssignExpr(
   242  				ir.NewAssignListStmt(n.Pos(), ir.OAS2, lhs,
   243  					[]ir.Node{n})).(*ir.AssignListStmt)
   244  			assign.Def = true
   245  			for _, tmp := range lhs {
   246  				// Place temp declarations in the loop body to help escape analysis.
   247  				assign.PtrInit().Append(typecheck.Stmt(ir.NewDecl(assign.Pos(), ir.ODCL, tmp.(*ir.Name))))
   248  			}
   249  			curNode = assign
   250  			plural := ""
   251  			if len(results) > 1 {
   252  				plural = "s"
   253  			}
   254  			if base.Flag.LowerM > 1 {
   255  				base.WarnfAt(n.Pos(), "function result%s will be kept alive", plural)
   256  			}
   257  			ret = keepAliveAt(ns, curNode)
   258  		} else {
   259  			// This function probably doesn't return anything, keep its args alive.
   260  			argTmps := []ir.Node{}
   261  			names := []ir.Node{}
   262  			for i, a := range n.Args {
   263  				if name := getAddressableNameFromNode(a); name != nil {
   264  					// If they are name, keep them alive directly.
   265  					debugName(name, n.Pos())
   266  					names = append(names, name)
   267  				} else if a.Op() == ir.OSLICELIT {
   268  					// variadic args are encoded as slice literal.
   269  					s := a.(*ir.CompLitExpr)
   270  					ns := []ir.Node{}
   271  					for i, elem := range s.List {
   272  						if name := getAddressableNameFromNode(elem); name != nil {
   273  							debugName(name, n.Pos())
   274  							ns = append(ns, name)
   275  						} else {
   276  							// We need a temporary to save this arg.
   277  							tmp := typecheck.TempAt(elem.Pos(), curFn, elem.Type())
   278  							assign := ir.NewAssignStmt(elem.Pos(), tmp, elem)
   279  							assign.Def = true
   280  							// Place temp declarations in the loop body to help escape analysis.
   281  							assign.PtrInit().Append(typecheck.Stmt(ir.NewDecl(assign.Pos(), ir.ODCL, tmp)))
   282  							argTmps = append(argTmps, typecheck.AssignExpr(assign))
   283  							names = append(names, tmp)
   284  							s.List[i] = tmp
   285  							if base.Flag.LowerM > 1 {
   286  								base.WarnfAt(n.Pos(), "function arg will be kept alive")
   287  							}
   288  						}
   289  					}
   290  					names = append(names, ns...)
   291  				} else {
   292  					// expressions, we need to assign them to temps and change the original arg to reference
   293  					// them.
   294  					tmp := typecheck.TempAt(n.Pos(), curFn, a.Type())
   295  					assign := ir.NewAssignStmt(n.Pos(), tmp, a)
   296  					assign.Def = true
   297  					// Place temp declarations in the loop body to help escape analysis.
   298  					assign.PtrInit().Append(typecheck.Stmt(ir.NewDecl(assign.Pos(), ir.ODCL, tmp)))
   299  					argTmps = append(argTmps, typecheck.AssignExpr(assign))
   300  					names = append(names, tmp)
   301  					n.Args[i] = tmp
   302  					if base.Flag.LowerM > 1 {
   303  						base.WarnfAt(n.Pos(), "function arg will be kept alive")
   304  					}
   305  				}
   306  			}
   307  			if len(argTmps) > 0 {
   308  				argTmps = append(argTmps, n)
   309  				curNode = ir.NewBlockStmt(n.Pos(), argTmps)
   310  			}
   311  			ret = keepAliveAt(names, curNode)
   312  		}
   313  	}
   314  	return
   315  }
   316  
   317  func preserveStmts(curFn *ir.Func, list ir.Nodes) {
   318  	for i := range list {
   319  		list[i] = preserveStmt(curFn, list[i])
   320  	}
   321  }
   322  
   323  // isTestingBLoop returns true if it matches the node as a
   324  // testing.(*B).Loop. See issue #61515.
   325  func isTestingBLoop(t ir.Node) bool {
   326  	if t.Op() != ir.OFOR {
   327  		return false
   328  	}
   329  	nFor, ok := t.(*ir.ForStmt)
   330  	if !ok || nFor.Cond == nil || nFor.Cond.Op() != ir.OCALLFUNC {
   331  		return false
   332  	}
   333  	n, ok := nFor.Cond.(*ir.CallExpr)
   334  	if !ok || n.Fun == nil || n.Fun.Op() != ir.OMETHEXPR {
   335  		return false
   336  	}
   337  	name := ir.MethodExprName(n.Fun)
   338  	if name == nil {
   339  		return false
   340  	}
   341  	if fSym := name.Sym(); fSym != nil && name.Class == ir.PFUNC && fSym.Pkg != nil &&
   342  		fSym.Name == "(*B).Loop" && fSym.Pkg.Path == "testing" {
   343  		// Attempting to match a function call to testing.(*B).Loop
   344  		return true
   345  	}
   346  	return false
   347  }
   348  
   349  type editor struct {
   350  	inBloop bool
   351  	curFn   *ir.Func
   352  }
   353  
   354  func (e editor) edit(n ir.Node) ir.Node {
   355  	e.inBloop = isTestingBLoop(n) || e.inBloop
   356  	// It's in bloop, mark the stmts with bodies.
   357  	ir.EditChildren(n, e.edit)
   358  	if e.inBloop {
   359  		switch n := n.(type) {
   360  		case *ir.ForStmt:
   361  			preserveStmts(e.curFn, n.Body)
   362  		case *ir.IfStmt:
   363  			preserveStmts(e.curFn, n.Body)
   364  			preserveStmts(e.curFn, n.Else)
   365  		case *ir.BlockStmt:
   366  			preserveStmts(e.curFn, n.List)
   367  		case *ir.CaseClause:
   368  			preserveStmts(e.curFn, n.List)
   369  			preserveStmts(e.curFn, n.Body)
   370  		case *ir.CommClause:
   371  			preserveStmts(e.curFn, n.Body)
   372  		case *ir.RangeStmt:
   373  			preserveStmts(e.curFn, n.Body)
   374  		}
   375  	}
   376  	return n
   377  }
   378  
   379  // BloopWalk performs a walk on all functions in the package
   380  // if it imports testing and wrap the results of all qualified
   381  // statements in a runtime.KeepAlive intrinsic call. See package
   382  // doc for more details.
   383  //
   384  //	for b.Loop() {...}
   385  //
   386  // loop's body.
   387  func BloopWalk(pkg *ir.Package) {
   388  	hasTesting := false
   389  	for _, i := range pkg.Imports {
   390  		if i.Path == "testing" {
   391  			hasTesting = true
   392  			break
   393  		}
   394  	}
   395  	if !hasTesting {
   396  		return
   397  	}
   398  	for _, fn := range pkg.Funcs {
   399  		e := editor{false, fn}
   400  		ir.EditChildren(fn, e.edit)
   401  	}
   402  }
   403  

View as plain text