From b805e18fbf6dee945236159d89cf3d29fcd541c4 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 16 Aug 2023 15:16:19 -0700 Subject: cmd/compile/internal/escape: cleanup go/defer normalization cruft This CL removes the extra complexity from escape analysis that was only needed to support go/defer normalization. It does not affect analysis results at all. Change-Id: I75785e0cb4c4ce19bea3b8df0bf95821bd885291 Reviewed-on: https://go-review.googlesource.com/c/go/+/520261 TryBot-Result: Gopher Robot Run-TryBot: Matthew Dempsky Reviewed-by: Cuong Manh Le Auto-Submit: Matthew Dempsky Reviewed-by: Dmitri Shuralyov --- src/cmd/compile/internal/escape/call.go | 242 ++++++++++---------------------- 1 file changed, 71 insertions(+), 171 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go index eb87954299..2ba1955b55 100644 --- a/src/cmd/compile/internal/escape/call.go +++ b/src/cmd/compile/internal/escape/call.go @@ -16,38 +16,9 @@ import ( // should contain the holes representing where the function callee's // results flows. func (e *escape) call(ks []hole, call ir.Node) { - var init ir.Nodes - e.callCommon(ks, call, &init, nil) - if len(init) != 0 { - call.(ir.InitNode).PtrInit().Append(init...) - } -} - -func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir.Func) { - - // argumentPragma handles escape analysis of argument *argp to the - // given hole. If the function callee is known, pragma is the - // function's pragma flags; otherwise 0. - argumentFunc := func(fn *ir.Name, k hole, argp *ir.Node) { - e.rewriteArgument(argp, init, call, fn, wrapper) - - e.expr(k.note(call, "call parameter"), *argp) - } - - argument := func(k hole, argp *ir.Node) { - argumentFunc(nil, k, argp) - } - - argumentRType := func(rtypep *ir.Node) { - rtype := *rtypep - if rtype == nil { - return - } - // common case: static rtype/itab argument, which can be evaluated within the wrapper instead. - if addr, ok := rtype.(*ir.AddrExpr); ok && addr.Op() == ir.OADDR && addr.X.Op() == ir.OLINKSYMOFFSET { - return - } - e.wrapExpr(rtype.Pos(), rtypep, init, call, wrapper) + argument := func(k hole, arg ir.Node) { + // TODO(mdempsky): Should be "call argument". + e.expr(k.note(call, "call parameter"), arg) } switch call.Op() { @@ -83,13 +54,9 @@ func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir } } - var recvp *ir.Node + var recvArg ir.Node if call.Op() == ir.OCALLFUNC { // Evaluate callee function expression. - // - // Note: We use argument and not argumentFunc, because while - // call.X here may be an argument to runtime.{new,defer}proc, - // it's not an argument to fn itself. calleeK := e.discardHole() if fn == nil { // unknown callee for _, k := range ks { @@ -98,30 +65,36 @@ func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir // know the callee function. If a closure flows here, we // need to conservatively assume its results might flow to // the heap. - calleeK = e.calleeHole() + calleeK = e.calleeHole().note(call, "callee operand") break } } } - argument(calleeK, &call.X) + e.expr(calleeK, call.X) } else { - recvp = &call.X.(*ir.SelectorExpr).X + recvArg = call.X.(*ir.SelectorExpr).X + } + + // argumentParam handles escape analysis of assigning a call + // argument to its corresponding parameter. + argumentParam := func(param *types.Field, arg ir.Node) { + e.rewriteArgument(arg, call, fn) + argument(e.tagHole(ks, fn, param), arg) } args := call.Args - if recv := fntype.Recv(); recv != nil { - if recvp == nil { + if recvParam := fntype.Recv(); recvParam != nil { + if recvArg == nil { // Function call using method expression. Receiver argument is // at the front of the regular arguments list. - recvp = &args[0] - args = args[1:] + recvArg, args = args[0], args[1:] } - argumentFunc(fn, e.tagHole(ks, fn, recv), recvp) + argumentParam(recvParam, recvArg) } for i, param := range fntype.Params().FieldSlice() { - argumentFunc(fn, e.tagHole(ks, fn, param), &args[i]) + argumentParam(param, args[i]) } case ir.OINLCALL: @@ -147,65 +120,65 @@ func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir if args[0].Type().Elem().HasPointers() { appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) } - argument(appendeeK, &args[0]) + argument(appendeeK, args[0]) if call.IsDDD { appendedK := e.discardHole() if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() { appendedK = e.heapHole().deref(call, "appended slice...") } - argument(appendedK, &args[1]) + argument(appendedK, args[1]) } else { for i := 1; i < len(args); i++ { - argument(e.heapHole(), &args[i]) + argument(e.heapHole(), args[i]) } } - argumentRType(&call.RType) + e.discard(call.RType) case ir.OCOPY: call := call.(*ir.BinaryExpr) - argument(e.mutatorHole(), &call.X) + argument(e.mutatorHole(), call.X) copiedK := e.discardHole() if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() { copiedK = e.heapHole().deref(call, "copied slice") } - argument(copiedK, &call.Y) - argumentRType(&call.RType) + argument(copiedK, call.Y) + e.discard(call.RType) case ir.OPANIC: call := call.(*ir.UnaryExpr) - argument(e.heapHole(), &call.X) + argument(e.heapHole(), call.X) case ir.OCOMPLEX: call := call.(*ir.BinaryExpr) - argument(e.discardHole(), &call.X) - argument(e.discardHole(), &call.Y) + e.discard(call.X) + e.discard(call.Y) case ir.ODELETE, ir.OMAX, ir.OMIN, ir.OPRINT, ir.OPRINTN, ir.ORECOVERFP: call := call.(*ir.CallExpr) for i := range call.Args { - argument(e.discardHole(), &call.Args[i]) + e.discard(call.Args[i]) } - argumentRType(&call.RType) + e.discard(call.RType) case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: call := call.(*ir.UnaryExpr) - argument(e.discardHole(), &call.X) + e.discard(call.X) case ir.OCLEAR: call := call.(*ir.UnaryExpr) - argument(e.mutatorHole(), &call.X) + argument(e.mutatorHole(), call.X) case ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA: call := call.(*ir.UnaryExpr) - argument(ks[0], &call.X) + argument(ks[0], call.X) case ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING: call := call.(*ir.BinaryExpr) - argument(ks[0], &call.X) - argument(e.discardHole(), &call.Y) - argumentRType(&call.RType) + argument(ks[0], call.X) + e.discard(call.Y) + e.discard(call.RType) } } @@ -244,34 +217,31 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) { e.expr(k, call.X) } -// rewriteArgument rewrites the argument *argp of the given call expression. +// rewriteArgument rewrites the argument arg of the given call expression. // fn is the static callee function, if known. -// wrapper is the go/defer wrapper function for call, if any. -func (e *escape) rewriteArgument(argp *ir.Node, init *ir.Nodes, call ir.Node, fn *ir.Name, wrapper *ir.Func) { - var pragma ir.PragmaFlag - if fn != nil && fn.Func != nil { - pragma = fn.Func.Pragma +func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) { + if fn == nil || fn.Func == nil { + return + } + pragma := fn.Func.Pragma + if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 { + return } // unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like // functions, so that ptr is kept alive and/or escaped as // appropriate. unsafeUintptr also reports whether it modified arg0. - unsafeUintptr := func(arg0 ir.Node) bool { - if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 { - return false - } - + unsafeUintptr := func(arg ir.Node) { // If the argument is really a pointer being converted to uintptr, - // arrange for the pointer to be kept alive until the call returns, - // by copying it into a temp and marking that temp - // still alive when we pop the temp stack. - if arg0.Op() != ir.OCONVNOP || !arg0.Type().IsUintptr() { - return false + // arrange for the pointer to be kept alive until the call + // returns, by copying it into a temp and marking that temp still + // alive when we pop the temp stack. + conv, ok := arg.(*ir.ConvExpr) + if !ok || conv.Op() != ir.OCONVNOP { + return // not a conversion } - arg := arg0.(*ir.ConvExpr) - - if !arg.X.Type().IsUnsafePtr() { - return false + if !conv.X.Type().IsUnsafePtr() || !conv.Type().IsUintptr() { + return // not an unsafe.Pointer->uintptr conversion } // Create and declare a new pointer-typed temp variable. @@ -279,64 +249,21 @@ func (e *escape) rewriteArgument(argp *ir.Node, init *ir.Nodes, call ir.Node, fn // TODO(mdempsky): This potentially violates the Go spec's order // of evaluations, by evaluating arg.X before any other // operands. - tmp := e.wrapExpr(arg.Pos(), &arg.X, init, call, wrapper) + tmp := e.copyExpr(conv.Pos(), conv.X, call.PtrInit()) + conv.X = tmp k := e.mutatorHole() if pragma&ir.UintptrEscapes != 0 { - k = e.heapHole().note(arg, "//go:uintptrescapes") + k = e.heapHole().note(conv, "//go:uintptrescapes") } e.flow(k, e.oldLoc(tmp)) if pragma&ir.UintptrKeepAlive != 0 { - call := call.(*ir.CallExpr) - - // SSA implements CallExpr.KeepAlive using OpVarLive, which - // doesn't support PAUTOHEAP variables. I tried changing it to - // use OpKeepAlive, but that ran into issues of its own. - // For now, the easy solution is to explicitly copy to (yet - // another) new temporary variable. - keep := tmp - if keep.Class == ir.PAUTOHEAP { - keep = e.copyExpr(arg.Pos(), tmp, call.PtrInit(), wrapper, false) - } - - keep.SetAddrtaken(true) // ensure SSA keeps the tmp variable - call.KeepAlive = append(call.KeepAlive, keep) - } - - return true - } - - visit := func(pos src.XPos, argp *ir.Node) { - // Optimize a few common constant expressions. By leaving these - // untouched in the call expression, we let the wrapper handle - // evaluating them, rather than taking up closure context space. - switch arg := *argp; arg.Op() { - case ir.OLITERAL, ir.ONIL, ir.OMETHEXPR: - return - case ir.ONAME: - if arg.(*ir.Name).Class == ir.PFUNC { - return - } - } - - if unsafeUintptr(*argp) { - return - } - - if wrapper != nil { - e.wrapExpr(pos, argp, init, call, wrapper) + tmp.SetAddrtaken(true) // ensure SSA keeps the tmp variable + call.KeepAlive = append(call.KeepAlive, tmp) } } - // Peel away any slice literals for better escape analyze - // them. For example: - // - // go F([]int{a, b}) - // - // If F doesn't escape its arguments, then the slice can - // be allocated on the new goroutine's stack. - // // For variadic functions, the compiler has already rewritten: // // f(a, b, c) @@ -346,54 +273,29 @@ func (e *escape) rewriteArgument(argp *ir.Node, init *ir.Nodes, call ir.Node, fn // f([]T{a, b, c}...) // // So we need to look into slice elements to handle uintptr(ptr) - // arguments to syscall-like functions correctly. - if arg := *argp; arg.Op() == ir.OSLICELIT { + // arguments to variadic syscall-like functions correctly. + if arg.Op() == ir.OSLICELIT { list := arg.(*ir.CompLitExpr).List - for i := range list { - el := &list[i] - if list[i].Op() == ir.OKEY { - el = &list[i].(*ir.KeyExpr).Value + for _, el := range list { + if el.Op() == ir.OKEY { + el = el.(*ir.KeyExpr).Value } - visit(arg.Pos(), el) + unsafeUintptr(el) } } else { - visit(call.Pos(), argp) - } -} - -// wrapExpr replaces *exprp with a temporary variable copy. If wrapper -// is non-nil, the variable will be captured for use within that -// function. -func (e *escape) wrapExpr(pos src.XPos, exprp *ir.Node, init *ir.Nodes, call ir.Node, wrapper *ir.Func) *ir.Name { - tmp := e.copyExpr(pos, *exprp, init, e.curfn, true) - - if wrapper != nil { - // Currently for "defer i.M()" if i is nil it panics at the point - // of defer statement, not when deferred function is called. We - // need to do the nil check outside of the wrapper. - if call.Op() == ir.OCALLINTER && exprp == &call.(*ir.CallExpr).X.(*ir.SelectorExpr).X { - check := ir.NewUnaryExpr(pos, ir.OCHECKNIL, ir.NewUnaryExpr(pos, ir.OITAB, tmp)) - init.Append(typecheck.Stmt(check)) - } - - e.oldLoc(tmp).captured = true - - tmp = ir.NewClosureVar(pos, wrapper, tmp) + unsafeUintptr(arg) } - - *exprp = tmp - return tmp } // copyExpr creates and returns a new temporary variable within fn; // appends statements to init to declare and initialize it to expr; -// and escape analyzes the data flow if analyze is true. -func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes, fn *ir.Func, analyze bool) *ir.Name { +// and escape analyzes the data flow. +func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name { if ir.HasUniquePos(expr) { pos = expr.Pos() } - tmp := typecheck.TempAt(pos, fn, expr.Type()) + tmp := typecheck.TempAt(pos, e.curfn, expr.Type()) stmts := []ir.Node{ ir.NewDecl(pos, ir.ODCL, tmp), @@ -402,10 +304,8 @@ func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes, fn *ir.Fun typecheck.Stmts(stmts) init.Append(stmts...) - if analyze { - e.newLoc(tmp, true) - e.stmts(stmts) - } + e.newLoc(tmp, true) + e.stmts(stmts) return tmp } -- cgit v1.2.3