1
0
mirror of https://github.com/golang/go synced 2024-11-17 21:24:55 -07:00

cmd/compile/internal/ir: add CallExpr.GoDefer

The devirtualizer and inliner both want to recognize call expressions
that are part of a go or defer statement. This CL refactors them to
use a single CallExpr.GoDefer flag, which gets set during
normalization of go/defer statements during typecheck.

While here, drop some OCALLMETH assertions. Typecheck has been
responsible for desugaring them into OCALLFUNC for a while now, and
ssagen will check this again for us later anyway.

Change-Id: I3fc370f4417431aae97239313da6fe523f512a2e
Reviewed-on: https://go-review.googlesource.com/c/go/+/543657
Reviewed-by: Than McIntosh <thanm@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Matthew Dempsky 2023-11-19 20:18:50 -08:00 committed by Gopher Robot
parent 468bc94188
commit 0709f1bb00
4 changed files with 38 additions and 47 deletions

View File

@ -23,24 +23,10 @@ import (
func Static(fn *ir.Func) { func Static(fn *ir.Func) {
ir.CurFunc = fn ir.CurFunc = fn
// For promoted methods (including value-receiver methods promoted to pointer-receivers),
// the interface method wrapper may contain expressions that can panic (e.g., ODEREF, ODOTPTR, ODOTINTER).
// Devirtualization involves inlining these expressions (and possible panics) to the call site.
// This normally isn't a problem, but for go/defer statements it can move the panic from when/where
// the call executes to the go/defer statement itself, which is a visible change in semantics (e.g., #52072).
// To prevent this, we skip devirtualizing calls within go/defer statements altogether.
goDeferCall := make(map[*ir.CallExpr]bool)
ir.VisitList(fn.Body, func(n ir.Node) { ir.VisitList(fn.Body, func(n ir.Node) {
switch n := n.(type) { switch n := n.(type) {
case *ir.GoDeferStmt:
if call, ok := n.Call.(*ir.CallExpr); ok {
goDeferCall[call] = true
}
return
case *ir.CallExpr: case *ir.CallExpr:
if !goDeferCall[n] { staticCall(n)
staticCall(n)
}
} }
}) })
} }
@ -48,6 +34,20 @@ func Static(fn *ir.Func) {
// staticCall devirtualizes the given call if possible when the concrete callee // staticCall devirtualizes the given call if possible when the concrete callee
// is available statically. // is available statically.
func staticCall(call *ir.CallExpr) { func staticCall(call *ir.CallExpr) {
// For promoted methods (including value-receiver methods promoted
// to pointer-receivers), the interface method wrapper may contain
// expressions that can panic (e.g., ODEREF, ODOTPTR,
// ODOTINTER). Devirtualization involves inlining these expressions
// (and possible panics) to the call site. This normally isn't a
// problem, but for go/defer statements it can move the panic from
// when/where the call executes to the go/defer statement itself,
// which is a visible change in semantics (e.g., #52072). To prevent
// this, we skip devirtualizing calls within go/defer statements
// altogether.
if call.GoDefer {
return
}
if call.Op() != ir.OCALLINTER { if call.Op() != ir.OCALLINTER {
return return
} }

View File

@ -845,15 +845,6 @@ func inlnode(callerfn *ir.Func, n ir.Node, bigCaller bool, inlCalls *[]*ir.Inlin
} }
switch n.Op() { switch n.Op() {
case ir.ODEFER, ir.OGO:
n := n.(*ir.GoDeferStmt)
switch call := n.Call; call.Op() {
case ir.OCALLMETH:
base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
case ir.OCALLFUNC:
call := call.(*ir.CallExpr)
call.NoInline = true
}
case ir.OTAILCALL: case ir.OTAILCALL:
n := n.(*ir.TailCallStmt) n := n.(*ir.TailCallStmt)
n.Call.NoInline = true // Not inline a tail call for now. Maybe we could inline it just like RETURN fn(arg)? n.Call.NoInline = true // Not inline a tail call for now. Maybe we could inline it just like RETURN fn(arg)?
@ -862,8 +853,6 @@ func inlnode(callerfn *ir.Func, n ir.Node, bigCaller bool, inlCalls *[]*ir.Inlin
// so escape analysis can avoid more heapmoves. // so escape analysis can avoid more heapmoves.
case ir.OCLOSURE: case ir.OCLOSURE:
return n return n
case ir.OCALLMETH:
base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
case ir.OCALLFUNC: case ir.OCALLFUNC:
n := n.(*ir.CallExpr) n := n.(*ir.CallExpr)
if n.Fun.Op() == ir.OMETHEXPR { if n.Fun.Op() == ir.OMETHEXPR {
@ -889,12 +878,9 @@ func inlnode(callerfn *ir.Func, n ir.Node, bigCaller bool, inlCalls *[]*ir.Inlin
// transmogrify this node itself unless inhibited by the // transmogrify this node itself unless inhibited by the
// switch at the top of this function. // switch at the top of this function.
switch n.Op() { switch n.Op() {
case ir.OCALLMETH:
base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
case ir.OCALLFUNC: case ir.OCALLFUNC:
call := n.(*ir.CallExpr) call := n.(*ir.CallExpr)
if call.NoInline { if call.GoDefer || call.NoInline {
break break
} }
if base.Flag.LowerM > 3 { if base.Flag.LowerM > 3 {

View File

@ -190,7 +190,8 @@ type CallExpr struct {
RType Node `mknode:"-"` // see reflectdata/helpers.go RType Node `mknode:"-"` // see reflectdata/helpers.go
KeepAlive []*Name // vars to be kept alive until call returns KeepAlive []*Name // vars to be kept alive until call returns
IsDDD bool IsDDD bool
NoInline bool GoDefer bool // whether this call is part of a go or defer statement
NoInline bool // whether this call must not be inlined
} }
func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr { func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr {

View File

@ -198,32 +198,36 @@ func tcFor(n *ir.ForStmt) ir.Node {
return n return n
} }
// tcGoDefer typechecks an OGO/ODEFER statement. // tcGoDefer typechecks (normalizes) an OGO/ODEFER statement.
func tcGoDefer(n *ir.GoDeferStmt) {
call := normalizeGoDeferCall(n.Pos(), n.Op(), n.Call, n.PtrInit())
call.GoDefer = true
n.Call = call
}
// normalizeGoDeferCall normalizes call into a normal function call
// with no arguments and no results, suitable for use in an OGO/ODEFER
// statement.
// //
// Really, this means normalizing the statement to always use a simple // For example, it normalizes:
// function call with no arguments and no results. For example, it
// rewrites:
// //
// defer f(x, y) // f(x, y)
// //
// into: // into:
// //
// x1, y1 := x, y // x1, y1 := x, y // added to init
// defer func() { f(x1, y1) }() // func() { f(x1, y1) }() // result
func tcGoDefer(n *ir.GoDeferStmt) { func normalizeGoDeferCall(pos src.XPos, op ir.Op, call ir.Node, init *ir.Nodes) *ir.CallExpr {
call := n.Call
init := n.PtrInit()
init.Append(ir.TakeInit(call)...) init.Append(ir.TakeInit(call)...)
if call, ok := n.Call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC { if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() == 0 { if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() == 0 {
return // already in normal form return call // already in normal form
} }
} }
// Create a new wrapper function without parameters or results. // Create a new wrapper function without parameters or results.
wrapperFn := ir.NewClosureFunc(n.Pos(), n.Pos(), n.Op(), types.NewSignature(nil, nil, nil), ir.CurFunc, Target) wrapperFn := ir.NewClosureFunc(pos, pos, op, types.NewSignature(nil, nil, nil), ir.CurFunc, Target)
wrapperFn.DeclareParams(true) wrapperFn.DeclareParams(true)
wrapperFn.SetWrapper(true) wrapperFn.SetWrapper(true)
@ -372,8 +376,8 @@ func tcGoDefer(n *ir.GoDeferStmt) {
// evaluate there. // evaluate there.
wrapperFn.Body = []ir.Node{call} wrapperFn.Body = []ir.Node{call}
// Finally, rewrite the go/defer statement to call the wrapper. // Finally, construct a call to the wrapper.
n.Call = Call(call.Pos(), wrapperFn.OClosure, nil, false) return Call(call.Pos(), wrapperFn.OClosure, nil, false).(*ir.CallExpr)
} }
// tcIf typechecks an OIF node. // tcIf typechecks an OIF node.