mirror of
https://github.com/golang/go
synced 2024-11-17 19:34:50 -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:
parent
468bc94188
commit
0709f1bb00
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user