[dev.typeparams] cmd/compile: desugar ORECOVER into ORECOVERFP

Currently ORECOVER is a single operation that both (1) calculates
the (logical) caller frame pointer and (2) calls runtime.gorecover.
This is normally fine, but it's inconvenient for regabi, which wants
to wrap "defer recover()" into "defer func() { recover() }" and
needs (1) and (2) to happen at different times.

The current solution is to apply walkRecover early to split it into
the two steps, but calling it during order is a minor layering
violation. It works well today because the order and walk phases are
closely related anyway and walkRecover is relatively simple, but it
won't work for go/defer wrapping earlier into the frontend.

This CL adds a new, lower-level ORECOVERFP primitive, which represents
just part (2); and OGETCALLER{PC,SP} primitives, which provide a way
to compute (1) in the frontend too.

OGETCALLERPC isn't needed/used today, but it seems worth including for
completeness. Maybe it will be useful at some point for intrinsifying
runtime.getcaller{pc,sp}, like we already do for runtime.getg.

Change-Id: Iaa8ae51e09306c45c147b6759a5b7c24dcc317ca
Reviewed-on: https://go-review.googlesource.com/c/go/+/330192
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2021-06-22 13:44:18 -07:00
parent 9be8303df9
commit 574ec1c645
12 changed files with 94 additions and 32 deletions

View file

@ -123,11 +123,14 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
call := call.(*ir.BinaryExpr) call := call.(*ir.BinaryExpr)
argument(e.discardHole(), &call.X) argument(e.discardHole(), &call.X)
argument(e.discardHole(), &call.Y) argument(e.discardHole(), &call.Y)
case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
call := call.(*ir.CallExpr) call := call.(*ir.CallExpr)
fixRecoverCall(call)
for i := range call.Args { for i := range call.Args {
argument(e.discardHole(), &call.Args[i]) argument(e.discardHole(), &call.Args[i])
} }
case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
call := call.(*ir.UnaryExpr) call := call.(*ir.UnaryExpr)
argument(e.discardHole(), &call.X) argument(e.discardHole(), &call.X)

View file

@ -0,0 +1,37 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package escape
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
)
// TODO(mdempsky): Desugaring doesn't belong during escape analysis,
// but for now it's the most convenient place for some rewrites.
// fixRecoverCall rewrites an ORECOVER call into ORECOVERFP,
// adding an explicit frame pointer argument.
// If call is not an ORECOVER call, it's left unmodified.
func fixRecoverCall(call *ir.CallExpr) {
if call.Op() != ir.ORECOVER {
return
}
pos := call.Pos()
// FP is equal to caller's SP plus FixedFrameSize().
var fp ir.Node = ir.NewCallExpr(pos, ir.OGETCALLERSP, nil, nil)
if off := base.Ctxt.FixedFrameSize(); off != 0 {
fp = ir.NewBinaryExpr(fp.Pos(), ir.OADD, fp, ir.NewInt(off))
}
// TODO(mdempsky): Replace *int32 with unsafe.Pointer, without upsetting checkptr.
fp = ir.NewConvExpr(pos, ir.OCONVNOP, types.NewPtr(types.Types[types.TINT32]), fp)
call.SetOp(ir.ORECOVERFP)
call.Args = []ir.Node{typecheck.Expr(fp)}
}

View file

@ -43,7 +43,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
default: default:
base.Fatalf("unexpected expr: %s %v", n.Op().String(), n) base.Fatalf("unexpected expr: %s %v", n.Op().String(), n)
case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OTYPE, ir.OMETHEXPR, ir.OLINKSYMOFFSET: case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OGETCALLERPC, ir.OGETCALLERSP, ir.OTYPE, ir.OMETHEXPR, ir.OLINKSYMOFFSET:
// nop // nop
case ir.ONAME: case ir.ONAME:
@ -138,7 +138,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
e.discard(n.X) e.discard(n.X)
case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.OUNSAFEADD, ir.OUNSAFESLICE: case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.ORECOVER, ir.OUNSAFEADD, ir.OUNSAFESLICE:
e.call([]hole{k}, n) e.call([]hole{k}, n)
case ir.ONEW: case ir.ONEW:
@ -158,9 +158,6 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
e.spill(k, n) e.spill(k, n)
e.discard(n.Len) e.discard(n.Len)
case ir.ORECOVER:
// nop
case ir.OCALLPART: case ir.OCALLPART:
// Flow the receiver argument to both the closure and // Flow the receiver argument to both the closure and
// to the receiver parameter. // to the receiver parameter.

View file

@ -180,8 +180,12 @@ func (n *CallExpr) SetOp(op Op) {
switch op { switch op {
default: default:
panic(n.no("SetOp " + op.String())) panic(n.no("SetOp " + op.String()))
case OCALL, OCALLFUNC, OCALLINTER, OCALLMETH, case OAPPEND,
OAPPEND, ODELETE, OGETG, OMAKE, OPRINT, OPRINTN, ORECOVER: OCALL, OCALLFUNC, OCALLINTER, OCALLMETH,
ODELETE,
OGETG, OGETCALLERPC, OGETCALLERSP,
OMAKE, OPRINT, OPRINTN,
ORECOVER, ORECOVERFP:
n.op = op n.op = op
} }
} }

View file

@ -3135,6 +3135,14 @@ func (s *state) expr(n ir.Node) *ssa.Value {
n := n.(*ir.CallExpr) n := n.(*ir.CallExpr)
return s.newValue1(ssa.OpGetG, n.Type(), s.mem()) return s.newValue1(ssa.OpGetG, n.Type(), s.mem())
case ir.OGETCALLERPC:
n := n.(*ir.CallExpr)
return s.newValue0(ssa.OpGetCallerPC, n.Type())
case ir.OGETCALLERSP:
n := n.(*ir.CallExpr)
return s.newValue0(ssa.OpGetCallerSP, n.Type())
case ir.OAPPEND: case ir.OAPPEND:
return s.append(n.(*ir.CallExpr), false) return s.append(n.(*ir.CallExpr), false)

View file

@ -967,6 +967,21 @@ func tcRecover(n *ir.CallExpr) ir.Node {
return n return n
} }
// tcRecoverFP typechecks an ORECOVERFP node.
func tcRecoverFP(n *ir.CallExpr) ir.Node {
if len(n.Args) != 1 {
base.FatalfAt(n.Pos(), "wrong number of arguments: %v", n)
}
n.Args[0] = Expr(n.Args[0])
if !n.Args[0].Type().IsPtrShaped() {
base.FatalfAt(n.Pos(), "%L is not pointer shaped", n.Args[0])
}
n.SetType(types.Types[types.TINTER])
return n
}
// tcUnsafeAdd typechecks an OUNSAFEADD node. // tcUnsafeAdd typechecks an OUNSAFEADD node.
func tcUnsafeAdd(n *ir.BinaryExpr) *ir.BinaryExpr { func tcUnsafeAdd(n *ir.BinaryExpr) *ir.BinaryExpr {
if !types.AllowsGoVersion(curpkg(), 1, 17) { if !types.AllowsGoVersion(curpkg(), 1, 17) {

View file

@ -776,6 +776,10 @@ func typecheck1(n ir.Node, top int) ir.Node {
n := n.(*ir.CallExpr) n := n.(*ir.CallExpr)
return tcRecover(n) return tcRecover(n)
case ir.ORECOVERFP:
n := n.(*ir.CallExpr)
return tcRecoverFP(n)
case ir.OUNSAFEADD: case ir.OUNSAFEADD:
n := n.(*ir.BinaryExpr) n := n.(*ir.BinaryExpr)
return tcUnsafeAdd(n) return tcUnsafeAdd(n)
@ -809,6 +813,14 @@ func typecheck1(n ir.Node, top int) ir.Node {
n.SetType(types.Types[types.TUINTPTR]) n.SetType(types.Types[types.TUINTPTR])
return n return n
case ir.OGETCALLERPC, ir.OGETCALLERSP:
n := n.(*ir.CallExpr)
if len(n.Args) != 0 {
base.FatalfAt(n.Pos(), "unexpected arguments: %v", n)
}
n.SetType(types.Types[types.TUINTPTR])
return n
case ir.OCONVNOP: case ir.OCONVNOP:
n := n.(*ir.ConvExpr) n := n.(*ir.ConvExpr)
n.X = Expr(n.X) n.X = Expr(n.X)

View file

@ -641,16 +641,9 @@ func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
return walkStmt(typecheck.Stmt(r)) return walkStmt(typecheck.Stmt(r))
} }
// walkRecover walks an ORECOVER node. // walkRecover walks an ORECOVERFP node.
func walkRecover(nn *ir.CallExpr, init *ir.Nodes) ir.Node { func walkRecoverFP(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
// Call gorecover with the FP of this frame. return mkcall("gorecover", nn.Type(), init, walkExpr(nn.Args[0], init))
// FP is equal to caller's SP plus FixedFrameSize().
var fp ir.Node = mkcall("getcallersp", types.Types[types.TUINTPTR], init)
if off := base.Ctxt.FixedFrameSize(); off != 0 {
fp = ir.NewBinaryExpr(fp.Pos(), ir.OADD, fp, ir.NewInt(off))
}
fp = ir.NewConvExpr(fp.Pos(), ir.OCONVNOP, types.NewPtr(types.Types[types.TINT32]), fp)
return mkcall("gorecover", nn.Type(), init, fp)
} }
func walkUnsafeSlice(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { func walkUnsafeSlice(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {

View file

@ -82,7 +82,7 @@ func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
base.Fatalf("walkExpr: switch 1 unknown op %+v", n.Op()) base.Fatalf("walkExpr: switch 1 unknown op %+v", n.Op())
panic("unreachable") panic("unreachable")
case ir.ONONAME, ir.OGETG: case ir.OGETG, ir.OGETCALLERPC, ir.OGETCALLERSP:
return n return n
case ir.OTYPE, ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET: case ir.OTYPE, ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OLINKSYMOFFSET:
@ -161,8 +161,8 @@ func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
return mkcall("gopanic", nil, init, n.X) return mkcall("gopanic", nil, init, n.X)
case ir.ORECOVER: case ir.ORECOVERFP:
return walkRecover(n.(*ir.CallExpr), init) return walkRecoverFP(n.(*ir.CallExpr), init)
case ir.OCFUNC: case ir.OCFUNC:
return n return n

View file

@ -777,10 +777,10 @@ func (o *orderState) stmt(n ir.Node) {
o.out = append(o.out, n) o.out = append(o.out, n)
o.cleanTemp(t) o.cleanTemp(t)
case ir.OPRINT, ir.OPRINTN, ir.ORECOVER: case ir.OPRINT, ir.OPRINTN, ir.ORECOVERFP:
n := n.(*ir.CallExpr) n := n.(*ir.CallExpr)
t := o.markTemp() t := o.markTemp()
o.exprList(n.Args) o.call(n)
o.out = append(o.out, n) o.out = append(o.out, n)
o.cleanTemp(t) o.cleanTemp(t)
@ -790,13 +790,6 @@ func (o *orderState) stmt(n ir.Node) {
t := o.markTemp() t := o.markTemp()
o.init(n.Call) o.init(n.Call)
o.call(n.Call) o.call(n.Call)
if n.Call.Op() == ir.ORECOVER {
// Special handling of "defer recover()". We need to evaluate the FP
// argument before wrapping.
var init ir.Nodes
n.Call = walkRecover(n.Call.(*ir.CallExpr), &init)
o.stmtList(init)
}
o.wrapGoDefer(n) o.wrapGoDefer(n)
o.out = append(o.out, n) o.out = append(o.out, n)
o.cleanTemp(t) o.cleanTemp(t)
@ -1270,7 +1263,7 @@ func (o *orderState) expr1(n, lhs ir.Node) ir.Node {
ir.OMAKESLICECOPY, ir.OMAKESLICECOPY,
ir.ONEW, ir.ONEW,
ir.OREAL, ir.OREAL,
ir.ORECOVER, ir.ORECOVERFP,
ir.OSTR2BYTES, ir.OSTR2BYTES,
ir.OSTR2BYTESTMP, ir.OSTR2BYTESTMP,
ir.OSTR2RUNES: ir.OSTR2RUNES:

View file

@ -49,7 +49,7 @@ func walkStmt(n ir.Node) ir.Node {
ir.OPRINT, ir.OPRINT,
ir.OPRINTN, ir.OPRINTN,
ir.OPANIC, ir.OPANIC,
ir.ORECOVER, ir.ORECOVERFP,
ir.OGETG: ir.OGETG:
if n.Typecheck() == 0 { if n.Typecheck() == 0 {
base.Fatalf("missing typecheck: %+v", n) base.Fatalf("missing typecheck: %+v", n)

View file

@ -343,7 +343,7 @@ func mayCall(n ir.Node) bool {
ir.OCAP, ir.OIMAG, ir.OLEN, ir.OREAL, ir.OCAP, ir.OIMAG, ir.OLEN, ir.OREAL,
ir.OCONVNOP, ir.ODOT, ir.OCONVNOP, ir.ODOT,
ir.OCFUNC, ir.OIDATA, ir.OITAB, ir.OSPTR, ir.OCFUNC, ir.OIDATA, ir.OITAB, ir.OSPTR,
ir.OBYTES2STRTMP, ir.OGETG, ir.OSLICEHEADER: ir.OBYTES2STRTMP, ir.OGETG, ir.OGETCALLERPC, ir.OGETCALLERSP, ir.OSLICEHEADER:
// ok: operations that don't require function calls. // ok: operations that don't require function calls.
// Expand as needed. // Expand as needed.
} }