[dev.regabi] cmd/compile: use ir.Find for "search" traversals

This CL converts all the generic searching traversal to use ir.Find
instead of relying on direct access to Left, Right, and so on.

Passes buildall w/ toolstash -cmp.

Change-Id: I4d951aef630c00bf333f24be79565cc564694d04
Reviewed-on: https://go-review.googlesource.com/c/go/+/275372
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Russ Cox 2020-12-02 20:18:47 -05:00
parent 0d1b44c645
commit b9df26d7a8
7 changed files with 247 additions and 341 deletions

View File

@ -782,37 +782,14 @@ func geneq(t *types.Type) *obj.LSym {
return closure return closure
} }
func hasCall(n ir.Node) bool { func hasCall(fn *ir.Func) bool {
if n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC { found := ir.Find(fn, func(n ir.Node) interface{} {
return true if op := n.Op(); op == ir.OCALL || op == ir.OCALLFUNC {
} return n
if n.Left() != nil && hasCall(n.Left()) {
return true
}
if n.Right() != nil && hasCall(n.Right()) {
return true
}
for _, x := range n.Init().Slice() {
if hasCall(x) {
return true
} }
} return nil
for _, x := range n.Body().Slice() { })
if hasCall(x) { return found != nil
return true
}
}
for _, x := range n.List().Slice() {
if hasCall(x) {
return true
}
}
for _, x := range n.Rlist().Slice() {
if hasCall(x) {
return true
}
}
return false
} }
// eqfield returns the node // eqfield returns the node

View File

@ -553,7 +553,7 @@ func evalConst(n ir.Node) ir.Node {
return origIntConst(n, int64(len(ir.StringVal(nl)))) return origIntConst(n, int64(len(ir.StringVal(nl))))
} }
case types.TARRAY: case types.TARRAY:
if !hascallchan(nl) { if !hasCallOrChan(nl) {
return origIntConst(n, nl.Type().NumElem()) return origIntConst(n, nl.Type().NumElem())
} }
} }
@ -779,49 +779,35 @@ func isGoConst(n ir.Node) bool {
return n.Op() == ir.OLITERAL return n.Op() == ir.OLITERAL
} }
func hascallchan(n ir.Node) bool { // hasCallOrChan reports whether n contains any calls or channel operations.
if n == nil { func hasCallOrChan(n ir.Node) bool {
return false found := ir.Find(n, func(n ir.Node) interface{} {
} switch n.Op() {
switch n.Op() { case ir.OAPPEND,
case ir.OAPPEND, ir.OCALL,
ir.OCALL, ir.OCALLFUNC,
ir.OCALLFUNC, ir.OCALLINTER,
ir.OCALLINTER, ir.OCALLMETH,
ir.OCALLMETH, ir.OCAP,
ir.OCAP, ir.OCLOSE,
ir.OCLOSE, ir.OCOMPLEX,
ir.OCOMPLEX, ir.OCOPY,
ir.OCOPY, ir.ODELETE,
ir.ODELETE, ir.OIMAG,
ir.OIMAG, ir.OLEN,
ir.OLEN, ir.OMAKE,
ir.OMAKE, ir.ONEW,
ir.ONEW, ir.OPANIC,
ir.OPANIC, ir.OPRINT,
ir.OPRINT, ir.OPRINTN,
ir.OPRINTN, ir.OREAL,
ir.OREAL, ir.ORECOVER,
ir.ORECOVER, ir.ORECV:
ir.ORECV: return n
return true
}
if hascallchan(n.Left()) || hascallchan(n.Right()) {
return true
}
for _, n1 := range n.List().Slice() {
if hascallchan(n1) {
return true
} }
} return nil
for _, n2 := range n.Rlist().Slice() { })
if hascallchan(n2) { return found != nil
return true
}
}
return false
} }
// A constSet represents a set of Go constant expressions. // A constSet represents a set of Go constant expressions.

View File

@ -33,6 +33,7 @@ import (
"cmd/compile/internal/types" "cmd/compile/internal/types"
"cmd/internal/obj" "cmd/internal/obj"
"cmd/internal/src" "cmd/internal/src"
"errors"
"fmt" "fmt"
"go/constant" "go/constant"
"strings" "strings"
@ -206,14 +207,10 @@ func caninl(fn *ir.Func) {
extraCallCost: cc, extraCallCost: cc,
usedLocals: make(map[ir.Node]bool), usedLocals: make(map[ir.Node]bool),
} }
if visitor.visitList(fn.Body()) { if visitor.tooHairy(fn) {
reason = visitor.reason reason = visitor.reason
return return
} }
if visitor.budget < 0 {
reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget)
return
}
n.Func().Inl = &ir.Inline{ n.Func().Inl = &ir.Inline{
Cost: inlineMaxBudget - visitor.budget, Cost: inlineMaxBudget - visitor.budget,
@ -296,21 +293,29 @@ type hairyVisitor struct {
reason string reason string
extraCallCost int32 extraCallCost int32
usedLocals map[ir.Node]bool usedLocals map[ir.Node]bool
do func(ir.Node) error
} }
// Look for anything we want to punt on. var errBudget = errors.New("too expensive")
func (v *hairyVisitor) visitList(ll ir.Nodes) bool {
for _, n := range ll.Slice() { func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
if v.visit(n) { v.do = v.doNode // cache closure
return true
} err := ir.DoChildren(fn, v.do)
if err != nil {
v.reason = err.Error()
return true
}
if v.budget < 0 {
v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget)
return true
} }
return false return false
} }
func (v *hairyVisitor) visit(n ir.Node) bool { func (v *hairyVisitor) doNode(n ir.Node) error {
if n == nil { if n == nil {
return false return nil
} }
switch n.Op() { switch n.Op() {
@ -323,8 +328,7 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
if n.Left().Op() == ir.ONAME && n.Left().Class() == ir.PFUNC && isRuntimePkg(n.Left().Sym().Pkg) { if n.Left().Op() == ir.ONAME && n.Left().Class() == ir.PFUNC && isRuntimePkg(n.Left().Sym().Pkg) {
fn := n.Left().Sym().Name fn := n.Left().Sym().Name
if fn == "getcallerpc" || fn == "getcallersp" { if fn == "getcallerpc" || fn == "getcallersp" {
v.reason = "call to " + fn return errors.New("call to " + fn)
return true
} }
if fn == "throw" { if fn == "throw" {
v.budget -= inlineExtraThrowCost v.budget -= inlineExtraThrowCost
@ -380,8 +384,7 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
case ir.ORECOVER: case ir.ORECOVER:
// recover matches the argument frame pointer to find // recover matches the argument frame pointer to find
// the right panic value, so it needs an argument frame. // the right panic value, so it needs an argument frame.
v.reason = "call to recover" return errors.New("call to recover")
return true
case ir.OCLOSURE, case ir.OCLOSURE,
ir.ORANGE, ir.ORANGE,
@ -390,21 +393,19 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
ir.ODEFER, ir.ODEFER,
ir.ODCLTYPE, // can't print yet ir.ODCLTYPE, // can't print yet
ir.ORETJMP: ir.ORETJMP:
v.reason = "unhandled op " + n.Op().String() return errors.New("unhandled op " + n.Op().String())
return true
case ir.OAPPEND: case ir.OAPPEND:
v.budget -= inlineExtraAppendCost v.budget -= inlineExtraAppendCost
case ir.ODCLCONST, ir.OFALL: case ir.ODCLCONST, ir.OFALL:
// These nodes don't produce code; omit from inlining budget. // These nodes don't produce code; omit from inlining budget.
return false return nil
case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH: case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH:
// ORANGE, OSELECT in "unhandled" above // ORANGE, OSELECT in "unhandled" above
if n.Sym() != nil { if n.Sym() != nil {
v.reason = "labeled control" return errors.New("labeled control")
return true
} }
case ir.OBREAK, ir.OCONTINUE: case ir.OBREAK, ir.OCONTINUE:
@ -416,8 +417,17 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
case ir.OIF: case ir.OIF:
if ir.IsConst(n.Left(), constant.Bool) { if ir.IsConst(n.Left(), constant.Bool) {
// This if and the condition cost nothing. // This if and the condition cost nothing.
return v.visitList(n.Init()) || v.visitList(n.Body()) || // TODO(rsc): It seems strange that we visit the dead branch.
v.visitList(n.Rlist()) if err := ir.DoList(n.Init(), v.do); err != nil {
return err
}
if err := ir.DoList(n.Body(), v.do); err != nil {
return err
}
if err := ir.DoList(n.Rlist(), v.do); err != nil {
return err
}
return nil
} }
case ir.ONAME: case ir.ONAME:
@ -439,34 +449,22 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
// When debugging, don't stop early, to get full cost of inlining this function // When debugging, don't stop early, to get full cost of inlining this function
if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() { if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
return true return errBudget
} }
return v.visit(n.Left()) || v.visit(n.Right()) || return ir.DoChildren(n, v.do)
v.visitList(n.List()) || v.visitList(n.Rlist()) ||
v.visitList(n.Init()) || v.visitList(n.Body())
} }
func countNodes(n ir.Node) int { func isBigFunc(fn *ir.Func) bool {
if n == nil { budget := inlineBigFunctionNodes
return 0 over := ir.Find(fn, func(n ir.Node) interface{} {
} budget--
cnt := 1 if budget <= 0 {
cnt += countNodes(n.Left()) return n
cnt += countNodes(n.Right()) }
for _, n1 := range n.Init().Slice() { return nil
cnt += countNodes(n1) })
} return over != nil
for _, n1 := range n.Body().Slice() {
cnt += countNodes(n1)
}
for _, n1 := range n.List().Slice() {
cnt += countNodes(n1)
}
for _, n1 := range n.Rlist().Slice() {
cnt += countNodes(n1)
}
return cnt
} }
// Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any // Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any
@ -475,7 +473,7 @@ func inlcalls(fn *ir.Func) {
savefn := Curfn savefn := Curfn
Curfn = fn Curfn = fn
maxCost := int32(inlineMaxBudget) maxCost := int32(inlineMaxBudget)
if countNodes(fn) >= inlineBigFunctionNodes { if isBigFunc(fn) {
maxCost = inlineBigFunctionMaxCost maxCost = inlineBigFunctionMaxCost
} }
// Map to keep track of functions that have been inlined at a particular // Map to keep track of functions that have been inlined at a particular
@ -742,82 +740,45 @@ FindRHS:
base.Fatalf("RHS is nil: %v", defn) base.Fatalf("RHS is nil: %v", defn)
} }
unsafe, _ := reassigned(n.(*ir.Name)) if reassigned(n.(*ir.Name)) {
if unsafe {
return nil return nil
} }
return rhs return rhs
} }
var errFound = errors.New("found")
// reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean // reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean
// indicating whether the name has any assignments other than its declaration. // indicating whether the name has any assignments other than its declaration.
// The second return value is the first such assignment encountered in the walk, if any. It is mostly // The second return value is the first such assignment encountered in the walk, if any. It is mostly
// useful for -m output documenting the reason for inhibited optimizations. // useful for -m output documenting the reason for inhibited optimizations.
// NB: global variables are always considered to be re-assigned. // NB: global variables are always considered to be re-assigned.
// TODO: handle initial declaration not including an assignment and followed by a single assignment? // TODO: handle initial declaration not including an assignment and followed by a single assignment?
func reassigned(n *ir.Name) (bool, ir.Node) { func reassigned(name *ir.Name) bool {
if n.Op() != ir.ONAME { if name.Op() != ir.ONAME {
base.Fatalf("reassigned %v", n) base.Fatalf("reassigned %v", name)
} }
// no way to reliably check for no-reassignment of globals, assume it can be // no way to reliably check for no-reassignment of globals, assume it can be
if n.Curfn == nil { if name.Curfn == nil {
return true, nil return true
} }
f := n.Curfn a := ir.Find(name.Curfn, func(n ir.Node) interface{} {
v := reassignVisitor{name: n} switch n.Op() {
a := v.visitList(f.Body()) case ir.OAS:
return a != nil, a if n.Left() == name && n != name.Defn {
}
type reassignVisitor struct {
name ir.Node
}
func (v *reassignVisitor) visit(n ir.Node) ir.Node {
if n == nil {
return nil
}
switch n.Op() {
case ir.OAS:
if n.Left() == v.name && n != v.name.Name().Defn {
return n
}
case ir.OAS2, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2DOTTYPE:
for _, p := range n.List().Slice() {
if p == v.name && n != v.name.Name().Defn {
return n return n
} }
case ir.OAS2, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2DOTTYPE:
for _, p := range n.List().Slice() {
if p == name && n != name.Defn {
return n
}
}
} }
} return nil
if a := v.visit(n.Left()); a != nil { })
return a return a != nil
}
if a := v.visit(n.Right()); a != nil {
return a
}
if a := v.visitList(n.List()); a != nil {
return a
}
if a := v.visitList(n.Rlist()); a != nil {
return a
}
if a := v.visitList(n.Init()); a != nil {
return a
}
if a := v.visitList(n.Body()); a != nil {
return a
}
return nil
}
func (v *reassignVisitor) visitList(l ir.Nodes) ir.Node {
for _, n := range l.Slice() {
if a := v.visit(n); a != nil {
return a
}
}
return nil
} }
func inlParam(t *types.Field, as ir.Node, inlvars map[*ir.Name]ir.Node) ir.Node { func inlParam(t *types.Field, as ir.Node, inlvars map[*ir.Name]ir.Node) ir.Node {
@ -1140,6 +1101,7 @@ func mkinlcall(n ir.Node, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool)
bases: make(map[*src.PosBase]*src.PosBase), bases: make(map[*src.PosBase]*src.PosBase),
newInlIndex: newIndex, newInlIndex: newIndex,
} }
subst.edit = subst.node
body := subst.list(ir.AsNodes(fn.Inl.Body)) body := subst.list(ir.AsNodes(fn.Inl.Body))
@ -1248,6 +1210,8 @@ type inlsubst struct {
// newInlIndex is the index of the inlined call frame to // newInlIndex is the index of the inlined call frame to
// insert for inlined nodes. // insert for inlined nodes.
newInlIndex int newInlIndex int
edit func(ir.Node) ir.Node // cached copy of subst.node method value closure
} }
// list inlines a list of nodes. // list inlines a list of nodes.
@ -1334,21 +1298,13 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
return m return m
} }
m := ir.Copy(n)
m.SetPos(subst.updatedPos(m.Pos()))
m.PtrInit().Set(nil)
if n.Op() == ir.OCLOSURE { if n.Op() == ir.OCLOSURE {
base.Fatalf("cannot inline function containing closure: %+v", n) base.Fatalf("cannot inline function containing closure: %+v", n)
} }
m.SetLeft(subst.node(n.Left())) m := ir.Copy(n)
m.SetRight(subst.node(n.Right())) m.SetPos(subst.updatedPos(m.Pos()))
m.PtrList().Set(subst.list(n.List())) ir.EditChildren(m, subst.edit)
m.PtrRlist().Set(subst.list(n.Rlist()))
m.PtrInit().Set(append(m.Init().Slice(), subst.list(n.Init())...))
m.PtrBody().Set(subst.list(n.Body()))
return m return m
} }

View File

@ -1062,6 +1062,10 @@ func (o *Order) exprListInPlace(l ir.Nodes) {
// prealloc[x] records the allocation to use for x. // prealloc[x] records the allocation to use for x.
var prealloc = map[ir.Node]ir.Node{} var prealloc = map[ir.Node]ir.Node{}
func (o *Order) exprNoLHS(n ir.Node) ir.Node {
return o.expr(n, nil)
}
// expr orders a single expression, appending side // expr orders a single expression, appending side
// effects to o.out as needed. // effects to o.out as needed.
// If this is part of an assignment lhs = *np, lhs is given. // If this is part of an assignment lhs = *np, lhs is given.
@ -1079,10 +1083,7 @@ func (o *Order) expr(n, lhs ir.Node) ir.Node {
switch n.Op() { switch n.Op() {
default: default:
n.SetLeft(o.expr(n.Left(), nil)) ir.EditChildren(n, o.exprNoLHS)
n.SetRight(o.expr(n.Right(), nil))
o.exprList(n.List())
o.exprList(n.Rlist())
// Addition of strings turns into a function call. // Addition of strings turns into a function call.
// Allocate a temporary to hold the strings. // Allocate a temporary to hold the strings.

View File

@ -60,7 +60,8 @@ func (s *InitSchedule) tryStaticInit(n ir.Node) bool {
if n.Op() != ir.OAS { if n.Op() != ir.OAS {
return false return false
} }
if ir.IsBlank(n.Left()) && candiscard(n.Right()) { if ir.IsBlank(n.Left()) && !hasSideEffects(n.Right()) {
// Discard.
return true return true
} }
lno := setlineno(n) lno := setlineno(n)
@ -548,7 +549,8 @@ func fixedlit(ctxt initContext, kind initKind, n ir.Node, var_ ir.Node, init *ir
for _, r := range n.List().Slice() { for _, r := range n.List().Slice() {
a, value := splitnode(r) a, value := splitnode(r)
if a == ir.BlankNode && candiscard(value) { if a == ir.BlankNode && !hasSideEffects(value) {
// Discard.
continue continue
} }

View File

@ -3669,51 +3669,52 @@ func checkmake(t *types.Type, arg string, np *ir.Node) bool {
return true return true
} }
func markbreak(labels *map[*types.Sym]ir.Node, n ir.Node, implicit ir.Node) { // markBreak marks control statements containing break statements with SetHasBreak(true).
if n == nil { func markBreak(fn *ir.Func) {
return var labels map[*types.Sym]ir.Node
var implicit ir.Node
var mark func(ir.Node) error
mark = func(n ir.Node) error {
switch n.Op() {
default:
ir.DoChildren(n, mark)
case ir.OBREAK:
if n.Sym() == nil {
if implicit != nil {
implicit.SetHasBreak(true)
}
} else {
if lab := labels[n.Sym()]; lab != nil {
lab.SetHasBreak(true)
}
}
case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH, ir.OTYPESW, ir.OSELECT, ir.ORANGE:
old := implicit
implicit = n
sym := n.Sym()
if sym != nil {
if labels == nil {
// Map creation delayed until we need it - most functions don't.
labels = make(map[*types.Sym]ir.Node)
}
labels[sym] = n
}
ir.DoChildren(n, mark)
if sym != nil {
delete(labels, sym)
}
implicit = old
}
return nil
} }
switch n.Op() { mark(fn)
case ir.OBREAK:
if n.Sym() == nil {
if implicit != nil {
implicit.SetHasBreak(true)
}
} else {
if lab := (*labels)[n.Sym()]; lab != nil {
lab.SetHasBreak(true)
}
}
case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH, ir.OTYPESW, ir.OSELECT, ir.ORANGE:
implicit = n
if sym := n.Sym(); sym != nil {
if *labels == nil {
// Map creation delayed until we need it - most functions don't.
*labels = make(map[*types.Sym]ir.Node)
}
(*labels)[sym] = n
defer delete(*labels, sym)
}
fallthrough
default:
markbreak(labels, n.Left(), implicit)
markbreak(labels, n.Right(), implicit)
markbreaklist(labels, n.Init(), implicit)
markbreaklist(labels, n.Body(), implicit)
markbreaklist(labels, n.List(), implicit)
markbreaklist(labels, n.Rlist(), implicit)
}
} }
func markbreaklist(labels *map[*types.Sym]ir.Node, l ir.Nodes, implicit ir.Node) { // isTermNodes reports whether the Nodes list ends with a terminating statement.
s := l.Slice()
for i := 0; i < len(s); i++ {
markbreak(labels, s[i], implicit)
}
}
// isterminating reports whether the Nodes list ends with a terminating statement.
func isTermNodes(l ir.Nodes) bool { func isTermNodes(l ir.Nodes) bool {
s := l.Slice() s := l.Slice()
c := len(s) c := len(s)
@ -3723,7 +3724,7 @@ func isTermNodes(l ir.Nodes) bool {
return isTermNode(s[c-1]) return isTermNode(s[c-1])
} }
// Isterminating reports whether the node n, the last one in a // isTermNode reports whether the node n, the last one in a
// statement list, is a terminating statement. // statement list, is a terminating statement.
func isTermNode(n ir.Node) bool { func isTermNode(n ir.Node) bool {
switch n.Op() { switch n.Op() {
@ -3776,8 +3777,7 @@ func isTermNode(n ir.Node) bool {
// checkreturn makes sure that fn terminates appropriately. // checkreturn makes sure that fn terminates appropriately.
func checkreturn(fn *ir.Func) { func checkreturn(fn *ir.Func) {
if fn.Type().NumResults() != 0 && fn.Body().Len() != 0 { if fn.Type().NumResults() != 0 && fn.Body().Len() != 0 {
var labels map[*types.Sym]ir.Node markBreak(fn)
markbreaklist(&labels, fn.Body(), nil)
if !isTermNodes(fn.Body()) { if !isTermNodes(fn.Body()) {
base.ErrorfAt(fn.Endlineno, "missing return at end of function") base.ErrorfAt(fn.Endlineno, "missing return at end of function")
} }

View File

@ -3786,107 +3786,91 @@ func usefield(n ir.Node) {
Curfn.FieldTrack[sym] = struct{}{} Curfn.FieldTrack[sym] = struct{}{}
} }
func candiscardlist(l ir.Nodes) bool { // hasSideEffects reports whether n contains any operations that could have observable side effects.
for _, n := range l.Slice() { func hasSideEffects(n ir.Node) bool {
if !candiscard(n) { found := ir.Find(n, func(n ir.Node) interface{} {
return false switch n.Op() {
// Assume side effects unless we know otherwise.
default:
return n
// No side effects here (arguments are checked separately).
case ir.ONAME,
ir.ONONAME,
ir.OTYPE,
ir.OPACK,
ir.OLITERAL,
ir.ONIL,
ir.OADD,
ir.OSUB,
ir.OOR,
ir.OXOR,
ir.OADDSTR,
ir.OADDR,
ir.OANDAND,
ir.OBYTES2STR,
ir.ORUNES2STR,
ir.OSTR2BYTES,
ir.OSTR2RUNES,
ir.OCAP,
ir.OCOMPLIT,
ir.OMAPLIT,
ir.OSTRUCTLIT,
ir.OARRAYLIT,
ir.OSLICELIT,
ir.OPTRLIT,
ir.OCONV,
ir.OCONVIFACE,
ir.OCONVNOP,
ir.ODOT,
ir.OEQ,
ir.ONE,
ir.OLT,
ir.OLE,
ir.OGT,
ir.OGE,
ir.OKEY,
ir.OSTRUCTKEY,
ir.OLEN,
ir.OMUL,
ir.OLSH,
ir.ORSH,
ir.OAND,
ir.OANDNOT,
ir.ONEW,
ir.ONOT,
ir.OBITNOT,
ir.OPLUS,
ir.ONEG,
ir.OOROR,
ir.OPAREN,
ir.ORUNESTR,
ir.OREAL,
ir.OIMAG,
ir.OCOMPLEX:
return nil
// Only possible side effect is division by zero.
case ir.ODIV, ir.OMOD:
if n.Right().Op() != ir.OLITERAL || constant.Sign(n.Right().Val()) == 0 {
return n
}
// Only possible side effect is panic on invalid size,
// but many makechan and makemap use size zero, which is definitely OK.
case ir.OMAKECHAN, ir.OMAKEMAP:
if !ir.IsConst(n.Left(), constant.Int) || constant.Sign(n.Left().Val()) != 0 {
return n
}
// Only possible side effect is panic on invalid size.
// TODO(rsc): Merge with previous case (probably breaks toolstash -cmp).
case ir.OMAKESLICE, ir.OMAKESLICECOPY:
return n
} }
} return nil
return true })
} return found != nil
func candiscard(n ir.Node) bool {
if n == nil {
return true
}
switch n.Op() {
default:
return false
// Discardable as long as the subpieces are.
case ir.ONAME,
ir.ONONAME,
ir.OTYPE,
ir.OPACK,
ir.OLITERAL,
ir.ONIL,
ir.OADD,
ir.OSUB,
ir.OOR,
ir.OXOR,
ir.OADDSTR,
ir.OADDR,
ir.OANDAND,
ir.OBYTES2STR,
ir.ORUNES2STR,
ir.OSTR2BYTES,
ir.OSTR2RUNES,
ir.OCAP,
ir.OCOMPLIT,
ir.OMAPLIT,
ir.OSTRUCTLIT,
ir.OARRAYLIT,
ir.OSLICELIT,
ir.OPTRLIT,
ir.OCONV,
ir.OCONVIFACE,
ir.OCONVNOP,
ir.ODOT,
ir.OEQ,
ir.ONE,
ir.OLT,
ir.OLE,
ir.OGT,
ir.OGE,
ir.OKEY,
ir.OSTRUCTKEY,
ir.OLEN,
ir.OMUL,
ir.OLSH,
ir.ORSH,
ir.OAND,
ir.OANDNOT,
ir.ONEW,
ir.ONOT,
ir.OBITNOT,
ir.OPLUS,
ir.ONEG,
ir.OOROR,
ir.OPAREN,
ir.ORUNESTR,
ir.OREAL,
ir.OIMAG,
ir.OCOMPLEX:
break
// Discardable as long as we know it's not division by zero.
case ir.ODIV, ir.OMOD:
if n.Right().Op() == ir.OLITERAL && constant.Sign(n.Right().Val()) != 0 {
break
}
return false
// Discardable as long as we know it won't fail because of a bad size.
case ir.OMAKECHAN, ir.OMAKEMAP:
if ir.IsConst(n.Left(), constant.Int) && constant.Sign(n.Left().Val()) == 0 {
break
}
return false
// Difficult to tell what sizes are okay.
case ir.OMAKESLICE:
return false
case ir.OMAKESLICECOPY:
return false
}
if !candiscard(n.Left()) || !candiscard(n.Right()) || !candiscardlist(n.Init()) || !candiscardlist(n.Body()) || !candiscardlist(n.List()) || !candiscardlist(n.Rlist()) {
return false
}
return true
} }
// Rewrite // Rewrite