[dev.regabi] cmd/compile: cleanup for concrete types - inl

An automated rewrite will add concrete type assertions after
a test of n.Op(), when n can be safely type-asserted
(meaning, n is not reassigned a different type, n is not reassigned
and then used outside the scope of the type assertion,
and so on).

This sequence of CLs handles the code that the automated
rewrite does not: adding specific types to function arguments,
adjusting code not to call n.Left() etc when n may have multiple
representations, and so on.

This CL focuses on inl.go.

Passes buildall w/ toolstash -cmp.

Change-Id: Iaaee7664cd43e264d9e49d252e3afa7cf719939b
Reviewed-on: https://go-review.googlesource.com/c/go/+/277926
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Russ Cox 2020-12-10 18:46:45 -05:00
parent 5fe64298a4
commit 389ae3d5ba

View file

@ -320,12 +320,15 @@ func (v *hairyVisitor) doNode(n ir.Node) error {
switch n.Op() { switch n.Op() {
// Call is okay if inlinable and we have the budget for the body. // Call is okay if inlinable and we have the budget for the body.
case ir.OCALLFUNC: case ir.OCALLFUNC:
n := n.(*ir.CallExpr)
// Functions that call runtime.getcaller{pc,sp} can not be inlined // Functions that call runtime.getcaller{pc,sp} can not be inlined
// because getcaller{pc,sp} expect a pointer to the caller's first argument. // because getcaller{pc,sp} expect a pointer to the caller's first argument.
// //
// runtime.throw is a "cheap call" like panic in normal code. // runtime.throw is a "cheap call" like panic in normal code.
if n.Left().Op() == ir.ONAME && n.Left().Class() == ir.PFUNC && isRuntimePkg(n.Left().Sym().Pkg) { if n.Left().Op() == ir.ONAME {
fn := n.Left().Sym().Name name := n.Left().(*ir.Name)
if name.Class() == ir.PFUNC && isRuntimePkg(name.Sym().Pkg) {
fn := name.Sym().Name
if fn == "getcallerpc" || fn == "getcallersp" { if fn == "getcallerpc" || fn == "getcallersp" {
return errors.New("call to " + fn) return errors.New("call to " + fn)
} }
@ -334,8 +337,9 @@ func (v *hairyVisitor) doNode(n ir.Node) error {
break break
} }
} }
}
if isIntrinsicCall(n.(*ir.CallExpr)) { if isIntrinsicCall(n) {
// Treat like any other node. // Treat like any other node.
break break
} }
@ -401,11 +405,15 @@ func (v *hairyVisitor) doNode(n ir.Node) error {
// These nodes don't produce code; omit from inlining budget. // These nodes don't produce code; omit from inlining budget.
return nil return nil
case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH: case ir.OFOR, ir.OFORUNTIL:
// ORANGE, OSELECT in "unhandled" above
if n.Sym() != nil { if n.Sym() != nil {
return errors.New("labeled control") return errors.New("labeled control")
} }
case ir.OSWITCH:
if n.Sym() != nil {
return errors.New("labeled control")
}
// case ir.ORANGE, ir.OSELECT in "unhandled" above
case ir.OBREAK, ir.OCONTINUE: case ir.OBREAK, ir.OCONTINUE:
if n.Sym() != nil { if n.Sym() != nil {
@ -488,7 +496,7 @@ func inlcalls(fn *ir.Func) {
} }
// Turn an OINLCALL into a statement. // Turn an OINLCALL into a statement.
func inlconv2stmt(inlcall ir.Node) ir.Node { func inlconv2stmt(inlcall *ir.InlinedCallExpr) ir.Node {
n := ir.NodAt(inlcall.Pos(), ir.OBLOCK, nil, nil) n := ir.NodAt(inlcall.Pos(), ir.OBLOCK, nil, nil)
n.SetList(inlcall.Init()) n.SetList(inlcall.Init())
n.PtrList().AppendNodes(inlcall.PtrBody()) n.PtrList().AppendNodes(inlcall.PtrBody())
@ -498,7 +506,7 @@ func inlconv2stmt(inlcall ir.Node) ir.Node {
// Turn an OINLCALL into a single valued expression. // Turn an OINLCALL into a single valued expression.
// The result of inlconv2expr MUST be assigned back to n, e.g. // The result of inlconv2expr MUST be assigned back to n, e.g.
// n.Left = inlconv2expr(n.Left) // n.Left = inlconv2expr(n.Left)
func inlconv2expr(n ir.Node) ir.Node { func inlconv2expr(n *ir.InlinedCallExpr) ir.Node {
r := n.Rlist().First() r := n.Rlist().First()
return initExpr(append(n.Init().Slice(), n.Body().Slice()...), r) return initExpr(append(n.Init().Slice(), n.Body().Slice()...), r)
} }
@ -508,7 +516,7 @@ func inlconv2expr(n ir.Node) ir.Node {
// containing the inlined statements on the first list element so // containing the inlined statements on the first list element so
// order will be preserved. Used in return, oas2func and call // order will be preserved. Used in return, oas2func and call
// statements. // statements.
func inlconv2list(n ir.Node) []ir.Node { func inlconv2list(n *ir.InlinedCallExpr) []ir.Node {
if n.Op() != ir.OINLCALL || n.Rlist().Len() == 0 { if n.Op() != ir.OINLCALL || n.Rlist().Len() == 0 {
base.Fatalf("inlconv2list %+v\n", n) base.Fatalf("inlconv2list %+v\n", n)
} }
@ -538,9 +546,9 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
switch n.Op() { switch n.Op() {
case ir.ODEFER, ir.OGO: case ir.ODEFER, ir.OGO:
switch n.Left().Op() { switch call := n.Left(); call.Op() {
case ir.OCALLFUNC, ir.OCALLMETH: case ir.OCALLFUNC, ir.OCALLMETH:
n.Left().SetNoInline(true) call.SetNoInline(true)
} }
// TODO do them here (or earlier), // TODO do them here (or earlier),
@ -559,11 +567,13 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
ir.EditChildren(n, edit) ir.EditChildren(n, edit)
if n.Op() == ir.OAS2FUNC && n.Rlist().First().Op() == ir.OINLCALL { if as := n; as.Op() == ir.OAS2FUNC {
n.PtrRlist().Set(inlconv2list(n.Rlist().First())) if as.Rlist().First().Op() == ir.OINLCALL {
n.SetOp(ir.OAS2) as.PtrRlist().Set(inlconv2list(as.Rlist().First().(*ir.InlinedCallExpr)))
n.SetTypecheck(0) as.SetOp(ir.OAS2)
n = typecheck(n, ctxStmt) as.SetTypecheck(0)
n = typecheck(as, ctxStmt)
}
} }
// with all the branches out of the way, it is now time to // with all the branches out of the way, it is now time to
@ -576,45 +586,46 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
} }
} }
var call ir.Node var call *ir.CallExpr
switch n.Op() { switch n.Op() {
case ir.OCALLFUNC: case ir.OCALLFUNC:
call = n call = n.(*ir.CallExpr)
if base.Flag.LowerM > 3 { if base.Flag.LowerM > 3 {
fmt.Printf("%v:call to func %+v\n", ir.Line(n), n.Left()) fmt.Printf("%v:call to func %+v\n", ir.Line(n), call.Left())
} }
if isIntrinsicCall(n.(*ir.CallExpr)) { if isIntrinsicCall(call) {
break break
} }
if fn := inlCallee(n.Left()); fn != nil && fn.Inl != nil { if fn := inlCallee(call.Left()); fn != nil && fn.Inl != nil {
n = mkinlcall(n, fn, maxCost, inlMap, edit) n = mkinlcall(call, fn, maxCost, inlMap, edit)
} }
case ir.OCALLMETH: case ir.OCALLMETH:
call = n call = n.(*ir.CallExpr)
if base.Flag.LowerM > 3 { if base.Flag.LowerM > 3 {
fmt.Printf("%v:call to meth %L\n", ir.Line(n), n.Left().Right()) fmt.Printf("%v:call to meth %v\n", ir.Line(n), call.Left().(*ir.SelectorExpr).Sel)
} }
// typecheck should have resolved ODOTMETH->type, whose nname points to the actual function. // typecheck should have resolved ODOTMETH->type, whose nname points to the actual function.
if n.Left().Type() == nil { if call.Left().Type() == nil {
base.Fatalf("no function type for [%p] %+v\n", n.Left(), n.Left()) base.Fatalf("no function type for [%p] %+v\n", call.Left(), call.Left())
} }
n = mkinlcall(n, methodExprName(n.Left()).Func(), maxCost, inlMap, edit) n = mkinlcall(call, methodExprName(call.Left()).Func(), maxCost, inlMap, edit)
} }
base.Pos = lno base.Pos = lno
if n.Op() == ir.OINLCALL { if n.Op() == ir.OINLCALL {
switch call.(*ir.CallExpr).Use { ic := n.(*ir.InlinedCallExpr)
switch call.Use {
default: default:
ir.Dump("call", call) ir.Dump("call", call)
base.Fatalf("call missing use") base.Fatalf("call missing use")
case ir.CallUseExpr: case ir.CallUseExpr:
n = inlconv2expr(n) n = inlconv2expr(ic)
case ir.CallUseStmt: case ir.CallUseStmt:
n = inlconv2stmt(n) n = inlconv2stmt(ic)
case ir.CallUseList: case ir.CallUseList:
// leave for caller to convert // leave for caller to convert
} }
@ -627,8 +638,8 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
// that it refers to if statically known. Otherwise, it returns nil. // that it refers to if statically known. Otherwise, it returns nil.
func inlCallee(fn ir.Node) *ir.Func { func inlCallee(fn ir.Node) *ir.Func {
fn = staticValue(fn) fn = staticValue(fn)
switch { switch fn.Op() {
case fn.Op() == ir.OMETHEXPR: case ir.OMETHEXPR:
n := methodExprName(fn) n := methodExprName(fn)
// Check that receiver type matches fn.Left. // Check that receiver type matches fn.Left.
// TODO(mdempsky): Handle implicit dereference // TODO(mdempsky): Handle implicit dereference
@ -637,9 +648,11 @@ func inlCallee(fn ir.Node) *ir.Func {
return nil return nil
} }
return n.Func() return n.Func()
case fn.Op() == ir.ONAME && fn.Class() == ir.PFUNC: case ir.ONAME:
if fn.Class() == ir.PFUNC {
return fn.Func() return fn.Func()
case fn.Op() == ir.OCLOSURE: }
case ir.OCLOSURE:
c := fn.Func() c := fn.Func()
caninl(c) caninl(c)
return c return c
@ -650,7 +663,7 @@ func inlCallee(fn ir.Node) *ir.Func {
func staticValue(n ir.Node) ir.Node { func staticValue(n ir.Node) ir.Node {
for { for {
if n.Op() == ir.OCONVNOP { if n.Op() == ir.OCONVNOP {
n = n.Left() n = n.(*ir.ConvExpr).Left()
continue continue
} }
@ -665,8 +678,12 @@ func staticValue(n ir.Node) ir.Node {
// staticValue1 implements a simple SSA-like optimization. If n is a local variable // staticValue1 implements a simple SSA-like optimization. If n is a local variable
// that is initialized and never reassigned, staticValue1 returns the initializer // that is initialized and never reassigned, staticValue1 returns the initializer
// expression. Otherwise, it returns nil. // expression. Otherwise, it returns nil.
func staticValue1(n ir.Node) ir.Node { func staticValue1(nn ir.Node) ir.Node {
if n.Op() != ir.ONAME || n.Class() != ir.PAUTO || n.Name().Addrtaken() { if nn.Op() != ir.ONAME {
return nil
}
n := nn.(*ir.Name)
if n.Class() != ir.PAUTO || n.Name().Addrtaken() {
return nil return nil
} }
@ -695,7 +712,7 @@ FindRHS:
base.Fatalf("RHS is nil: %v", defn) base.Fatalf("RHS is nil: %v", defn)
} }
if reassigned(n.(*ir.Name)) { if reassigned(n) {
return nil return nil
} }
@ -757,7 +774,7 @@ var inlgen int
// parameters. // parameters.
// The result of mkinlcall MUST be assigned back to n, e.g. // The result of mkinlcall MUST be assigned back to n, e.g.
// n.Left = mkinlcall(n.Left, fn, isddd) // n.Left = mkinlcall(n.Left, fn, isddd)
func mkinlcall(n ir.Node, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node { func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
if fn.Inl == nil { if fn.Inl == nil {
if logopt.Enabled() { if logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(Curfn), logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(Curfn),
@ -830,8 +847,9 @@ func mkinlcall(n ir.Node, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool,
if n.Op() == ir.OCALLFUNC { if n.Op() == ir.OCALLFUNC {
callee := n.Left() callee := n.Left()
for callee.Op() == ir.OCONVNOP { for callee.Op() == ir.OCONVNOP {
ninit.AppendNodes(callee.PtrInit()) conv := callee.(*ir.ConvExpr)
callee = callee.Left() ninit.AppendNodes(conv.PtrInit())
callee = conv.Left()
} }
if callee.Op() != ir.ONAME && callee.Op() != ir.OCLOSURE && callee.Op() != ir.OMETHEXPR { if callee.Op() != ir.ONAME && callee.Op() != ir.OCLOSURE && callee.Op() != ir.OMETHEXPR {
base.Fatalf("unexpected callee expression: %v", callee) base.Fatalf("unexpected callee expression: %v", callee)
@ -952,16 +970,17 @@ func mkinlcall(n ir.Node, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool,
as := ir.Nod(ir.OAS2, nil, nil) as := ir.Nod(ir.OAS2, nil, nil)
as.SetColas(true) as.SetColas(true)
if n.Op() == ir.OCALLMETH { if n.Op() == ir.OCALLMETH {
if n.Left().Left() == nil { sel := n.Left().(*ir.SelectorExpr)
if sel.Left() == nil {
base.Fatalf("method call without receiver: %+v", n) base.Fatalf("method call without receiver: %+v", n)
} }
as.PtrRlist().Append(n.Left().Left()) as.PtrRlist().Append(sel.Left())
} }
as.PtrRlist().Append(n.List().Slice()...) as.PtrRlist().Append(n.List().Slice()...)
// For non-dotted calls to variadic functions, we assign the // For non-dotted calls to variadic functions, we assign the
// variadic parameter's temp name separately. // variadic parameter's temp name separately.
var vas ir.Node var vas *ir.AssignStmt
if recv := fn.Type().Recv(); recv != nil { if recv := fn.Type().Recv(); recv != nil {
as.PtrList().Append(inlParam(recv, as, inlvars)) as.PtrList().Append(inlParam(recv, as, inlvars))
@ -984,14 +1003,15 @@ func mkinlcall(n ir.Node, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool,
} }
varargs := as.List().Slice()[x:] varargs := as.List().Slice()[x:]
vas = ir.Nod(ir.OAS, nil, nil) vas = ir.NewAssignStmt(base.Pos, nil, nil)
vas.SetLeft(inlParam(param, vas, inlvars)) vas.SetLeft(inlParam(param, vas, inlvars))
if len(varargs) == 0 { if len(varargs) == 0 {
vas.SetRight(nodnil()) vas.SetRight(nodnil())
vas.Right().SetType(param.Type) vas.Right().SetType(param.Type)
} else { } else {
vas.SetRight(ir.Nod(ir.OCOMPLIT, nil, ir.TypeNode(param.Type))) lit := ir.Nod(ir.OCOMPLIT, nil, ir.TypeNode(param.Type))
vas.Right().PtrList().Set(varargs) lit.PtrList().Set(varargs)
vas.SetRight(lit)
} }
} }
@ -1229,13 +1249,20 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
typecheckslice(init, ctxStmt) typecheckslice(init, ctxStmt)
return ir.NewBlockStmt(base.Pos, init) return ir.NewBlockStmt(base.Pos, init)
case ir.OGOTO, ir.OLABEL: case ir.OGOTO:
m := ir.Copy(n) m := ir.Copy(n).(*ir.BranchStmt)
m.SetPos(subst.updatedPos(m.Pos())) m.SetPos(subst.updatedPos(m.Pos()))
m.PtrInit().Set(nil) m.PtrInit().Set(nil)
p := fmt.Sprintf("%s·%d", n.Sym().Name, inlgen) p := fmt.Sprintf("%s·%d", n.Sym().Name, inlgen)
m.SetSym(lookup(p)) m.SetSym(lookup(p))
return m
case ir.OLABEL:
m := ir.Copy(n).(*ir.LabelStmt)
m.SetPos(subst.updatedPos(m.Pos()))
m.PtrInit().Set(nil)
p := fmt.Sprintf("%s·%d", n.Sym().Name, inlgen)
m.SetSym(lookup(p))
return m return m
} }
@ -1280,36 +1307,38 @@ func devirtualize(fn *ir.Func) {
Curfn = fn Curfn = fn
ir.VisitList(fn.Body(), func(n ir.Node) { ir.VisitList(fn.Body(), func(n ir.Node) {
if n.Op() == ir.OCALLINTER { if n.Op() == ir.OCALLINTER {
devirtualizeCall(n) devirtualizeCall(n.(*ir.CallExpr))
} }
}) })
} }
func devirtualizeCall(call ir.Node) { func devirtualizeCall(call *ir.CallExpr) {
recv := staticValue(call.Left().Left()) sel := call.Left().(*ir.SelectorExpr)
if recv.Op() != ir.OCONVIFACE { r := staticValue(sel.Left())
if r.Op() != ir.OCONVIFACE {
return return
} }
recv := r.(*ir.ConvExpr)
typ := recv.Left().Type() typ := recv.Left().Type()
if typ.IsInterface() { if typ.IsInterface() {
return return
} }
dt := ir.NodAt(call.Left().Pos(), ir.ODOTTYPE, call.Left().Left(), nil) dt := ir.NodAt(sel.Pos(), ir.ODOTTYPE, sel.Left(), nil)
dt.SetType(typ) dt.SetType(typ)
x := typecheck(nodlSym(call.Left().Pos(), ir.OXDOT, dt, call.Left().Sym()), ctxExpr|ctxCallee) x := typecheck(nodlSym(sel.Pos(), ir.OXDOT, dt, sel.Sym()), ctxExpr|ctxCallee)
switch x.Op() { switch x.Op() {
case ir.ODOTMETH: case ir.ODOTMETH:
if base.Flag.LowerM != 0 { if base.Flag.LowerM != 0 {
base.WarnfAt(call.Pos(), "devirtualizing %v to %v", call.Left(), typ) base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ)
} }
call.SetOp(ir.OCALLMETH) call.SetOp(ir.OCALLMETH)
call.SetLeft(x) call.SetLeft(x)
case ir.ODOTINTER: case ir.ODOTINTER:
// Promoted method from embedded interface-typed field (#42279). // Promoted method from embedded interface-typed field (#42279).
if base.Flag.LowerM != 0 { if base.Flag.LowerM != 0 {
base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", call.Left(), typ) base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ)
} }
call.SetOp(ir.OCALLINTER) call.SetOp(ir.OCALLINTER)
call.SetLeft(x) call.SetLeft(x)