]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.regabi] cmd/compile: use ir.Find for "search" traversals
authorRuss Cox <rsc@golang.org>
Thu, 3 Dec 2020 01:18:47 +0000 (20:18 -0500)
committerRuss Cox <rsc@golang.org>
Fri, 4 Dec 2020 16:52:50 +0000 (16:52 +0000)
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>
src/cmd/compile/internal/gc/alg.go
src/cmd/compile/internal/gc/const.go
src/cmd/compile/internal/gc/inl.go
src/cmd/compile/internal/gc/order.go
src/cmd/compile/internal/gc/sinit.go
src/cmd/compile/internal/gc/typecheck.go
src/cmd/compile/internal/gc/walk.go

index b2716399a5535b706eff9a00f210c449a6c415a2..c786a27415c3876e43af0f1ee2f2d210d96d97df 100644 (file)
@@ -782,37 +782,14 @@ func geneq(t *types.Type) *obj.LSym {
        return closure
 }
 
-func hasCall(n ir.Node) bool {
-       if n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC {
-               return true
-       }
-       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
-               }
-       }
-       for _, x := range n.Body().Slice() {
-               if hasCall(x) {
-                       return true
-               }
-       }
-       for _, x := range n.List().Slice() {
-               if hasCall(x) {
-                       return true
+func hasCall(fn *ir.Func) bool {
+       found := ir.Find(fn, func(n ir.Node) interface{} {
+               if op := n.Op(); op == ir.OCALL || op == ir.OCALLFUNC {
+                       return n
                }
-       }
-       for _, x := range n.Rlist().Slice() {
-               if hasCall(x) {
-                       return true
-               }
-       }
-       return false
+               return nil
+       })
+       return found != nil
 }
 
 // eqfield returns the node
index 9aa65f97b6fe78cf98b522205cddbfe8b5ff0ddc..6cd414a41931a0527f5a399d1f0f404fa74c8910 100644 (file)
@@ -553,7 +553,7 @@ func evalConst(n ir.Node) ir.Node {
                                return origIntConst(n, int64(len(ir.StringVal(nl))))
                        }
                case types.TARRAY:
-                       if !hascallchan(nl) {
+                       if !hasCallOrChan(nl) {
                                return origIntConst(n, nl.Type().NumElem())
                        }
                }
@@ -779,49 +779,35 @@ func isGoConst(n ir.Node) bool {
        return n.Op() == ir.OLITERAL
 }
 
-func hascallchan(n ir.Node) bool {
-       if n == nil {
-               return false
-       }
-       switch n.Op() {
-       case ir.OAPPEND,
-               ir.OCALL,
-               ir.OCALLFUNC,
-               ir.OCALLINTER,
-               ir.OCALLMETH,
-               ir.OCAP,
-               ir.OCLOSE,
-               ir.OCOMPLEX,
-               ir.OCOPY,
-               ir.ODELETE,
-               ir.OIMAG,
-               ir.OLEN,
-               ir.OMAKE,
-               ir.ONEW,
-               ir.OPANIC,
-               ir.OPRINT,
-               ir.OPRINTN,
-               ir.OREAL,
-               ir.ORECOVER,
-               ir.ORECV:
-               return true
-       }
-
-       if hascallchan(n.Left()) || hascallchan(n.Right()) {
-               return true
-       }
-       for _, n1 := range n.List().Slice() {
-               if hascallchan(n1) {
-                       return true
-               }
-       }
-       for _, n2 := range n.Rlist().Slice() {
-               if hascallchan(n2) {
-                       return true
+// hasCallOrChan reports whether n contains any calls or channel operations.
+func hasCallOrChan(n ir.Node) bool {
+       found := ir.Find(n, func(n ir.Node) interface{} {
+               switch n.Op() {
+               case ir.OAPPEND,
+                       ir.OCALL,
+                       ir.OCALLFUNC,
+                       ir.OCALLINTER,
+                       ir.OCALLMETH,
+                       ir.OCAP,
+                       ir.OCLOSE,
+                       ir.OCOMPLEX,
+                       ir.OCOPY,
+                       ir.ODELETE,
+                       ir.OIMAG,
+                       ir.OLEN,
+                       ir.OMAKE,
+                       ir.ONEW,
+                       ir.OPANIC,
+                       ir.OPRINT,
+                       ir.OPRINTN,
+                       ir.OREAL,
+                       ir.ORECOVER,
+                       ir.ORECV:
+                       return n
                }
-       }
-
-       return false
+               return nil
+       })
+       return found != nil
 }
 
 // A constSet represents a set of Go constant expressions.
index efd6fea84453800a5d0983c3c9d5a5aca9322a97..09ec0b6f99a1182b6042b305f5cbced9554fc7ba 100644 (file)
@@ -33,6 +33,7 @@ import (
        "cmd/compile/internal/types"
        "cmd/internal/obj"
        "cmd/internal/src"
+       "errors"
        "fmt"
        "go/constant"
        "strings"
@@ -206,14 +207,10 @@ func caninl(fn *ir.Func) {
                extraCallCost: cc,
                usedLocals:    make(map[ir.Node]bool),
        }
-       if visitor.visitList(fn.Body()) {
+       if visitor.tooHairy(fn) {
                reason = visitor.reason
                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{
                Cost: inlineMaxBudget - visitor.budget,
@@ -296,21 +293,29 @@ type hairyVisitor struct {
        reason        string
        extraCallCost int32
        usedLocals    map[ir.Node]bool
+       do            func(ir.Node) error
 }
 
-// Look for anything we want to punt on.
-func (v *hairyVisitor) visitList(ll ir.Nodes) bool {
-       for _, n := range ll.Slice() {
-               if v.visit(n) {
-                       return true
-               }
+var errBudget = errors.New("too expensive")
+
+func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
+       v.do = v.doNode // cache closure
+
+       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
 }
 
-func (v *hairyVisitor) visit(n ir.Node) bool {
+func (v *hairyVisitor) doNode(n ir.Node) error {
        if n == nil {
-               return false
+               return nil
        }
 
        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) {
                        fn := n.Left().Sym().Name
                        if fn == "getcallerpc" || fn == "getcallersp" {
-                               v.reason = "call to " + fn
-                               return true
+                               return errors.New("call to " + fn)
                        }
                        if fn == "throw" {
                                v.budget -= inlineExtraThrowCost
@@ -380,8 +384,7 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
        case ir.ORECOVER:
                // recover matches the argument frame pointer to find
                // the right panic value, so it needs an argument frame.
-               v.reason = "call to recover"
-               return true
+               return errors.New("call to recover")
 
        case ir.OCLOSURE,
                ir.ORANGE,
@@ -390,21 +393,19 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
                ir.ODEFER,
                ir.ODCLTYPE, // can't print yet
                ir.ORETJMP:
-               v.reason = "unhandled op " + n.Op().String()
-               return true
+               return errors.New("unhandled op " + n.Op().String())
 
        case ir.OAPPEND:
                v.budget -= inlineExtraAppendCost
 
        case ir.ODCLCONST, ir.OFALL:
                // These nodes don't produce code; omit from inlining budget.
-               return false
+               return nil
 
        case ir.OFOR, ir.OFORUNTIL, ir.OSWITCH:
                // ORANGE, OSELECT in "unhandled" above
                if n.Sym() != nil {
-                       v.reason = "labeled control"
-                       return true
+                       return errors.New("labeled control")
                }
 
        case ir.OBREAK, ir.OCONTINUE:
@@ -416,8 +417,17 @@ func (v *hairyVisitor) visit(n ir.Node) bool {
        case ir.OIF:
                if ir.IsConst(n.Left(), constant.Bool) {
                        // This if and the condition cost nothing.
-                       return v.visitList(n.Init()) || v.visitList(n.Body()) ||
-                               v.visitList(n.Rlist())
+                       // TODO(rsc): It seems strange that we visit the dead branch.
+                       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:
@@ -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
        if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
-               return true
+               return errBudget
        }
 
-       return v.visit(n.Left()) || v.visit(n.Right()) ||
-               v.visitList(n.List()) || v.visitList(n.Rlist()) ||
-               v.visitList(n.Init()) || v.visitList(n.Body())
+       return ir.DoChildren(n, v.do)
 }
 
-func countNodes(n ir.Node) int {
-       if n == nil {
-               return 0
-       }
-       cnt := 1
-       cnt += countNodes(n.Left())
-       cnt += countNodes(n.Right())
-       for _, n1 := range n.Init().Slice() {
-               cnt += countNodes(n1)
-       }
-       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
+func isBigFunc(fn *ir.Func) bool {
+       budget := inlineBigFunctionNodes
+       over := ir.Find(fn, func(n ir.Node) interface{} {
+               budget--
+               if budget <= 0 {
+                       return n
+               }
+               return nil
+       })
+       return over != nil
 }
 
 // Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any
@@ -475,7 +473,7 @@ func inlcalls(fn *ir.Func) {
        savefn := Curfn
        Curfn = fn
        maxCost := int32(inlineMaxBudget)
-       if countNodes(fn) >= inlineBigFunctionNodes {
+       if isBigFunc(fn) {
                maxCost = inlineBigFunctionMaxCost
        }
        // 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)
        }
 
-       unsafe, _ := reassigned(n.(*ir.Name))
-       if unsafe {
+       if reassigned(n.(*ir.Name)) {
                return nil
        }
 
        return rhs
 }
 
+var errFound = errors.New("found")
+
 // 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.
 // 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.
 // NB: global variables are always considered to be re-assigned.
 // TODO: handle initial declaration not including an assignment and followed by a single assignment?
-func reassigned(n *ir.Name) (bool, ir.Node) {
-       if n.Op() != ir.ONAME {
-               base.Fatalf("reassigned %v", n)
+func reassigned(name *ir.Name) bool {
+       if name.Op() != ir.ONAME {
+               base.Fatalf("reassigned %v", name)
        }
        // no way to reliably check for no-reassignment of globals, assume it can be
-       if n.Curfn == nil {
-               return true, nil
-       }
-       f := n.Curfn
-       v := reassignVisitor{name: n}
-       a := v.visitList(f.Body())
-       return a != nil, a
-}
-
-type reassignVisitor struct {
-       name ir.Node
-}
-
-func (v *reassignVisitor) visit(n ir.Node) ir.Node {
-       if n == nil {
-               return nil
+       if name.Curfn == nil {
+               return true
        }
-       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 {
+       a := ir.Find(name.Curfn, func(n ir.Node) interface{} {
+               switch n.Op() {
+               case ir.OAS:
+                       if n.Left() == name && n != name.Defn {
                                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
+                               }
+                       }
                }
-       }
-       if a := v.visit(n.Left()); a != nil {
-               return a
-       }
-       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
+               return nil
+       })
+       return a != nil
 }
 
 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),
                newInlIndex:  newIndex,
        }
+       subst.edit = subst.node
 
        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
        // insert for inlined nodes.
        newInlIndex int
+
+       edit func(ir.Node) ir.Node // cached copy of subst.node method value closure
 }
 
 // list inlines a list of nodes.
@@ -1334,21 +1298,13 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
                return m
        }
 
-       m := ir.Copy(n)
-       m.SetPos(subst.updatedPos(m.Pos()))
-       m.PtrInit().Set(nil)
-
        if n.Op() == ir.OCLOSURE {
                base.Fatalf("cannot inline function containing closure: %+v", n)
        }
 
-       m.SetLeft(subst.node(n.Left()))
-       m.SetRight(subst.node(n.Right()))
-       m.PtrList().Set(subst.list(n.List()))
-       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()))
-
+       m := ir.Copy(n)
+       m.SetPos(subst.updatedPos(m.Pos()))
+       ir.EditChildren(m, subst.edit)
        return m
 }
 
index 5440806e8ecb738e33c64e813d0701b1bcea4c09..1680d9d920781cf1595ad80bc7d0c002f6e997f4 100644 (file)
@@ -1062,6 +1062,10 @@ func (o *Order) exprListInPlace(l ir.Nodes) {
 // prealloc[x] records the allocation to use for x.
 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
 // effects to o.out as needed.
 // 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() {
        default:
-               n.SetLeft(o.expr(n.Left(), nil))
-               n.SetRight(o.expr(n.Right(), nil))
-               o.exprList(n.List())
-               o.exprList(n.Rlist())
+               ir.EditChildren(n, o.exprNoLHS)
 
        // Addition of strings turns into a function call.
        // Allocate a temporary to hold the strings.
index 3ef976d8aa7d3a79db6b8e119feb0f2e415b7358..20abbfef8cf9c37b8f4fe975a486b64421483cfe 100644 (file)
@@ -60,7 +60,8 @@ func (s *InitSchedule) tryStaticInit(n ir.Node) bool {
        if n.Op() != ir.OAS {
                return false
        }
-       if ir.IsBlank(n.Left()) && candiscard(n.Right()) {
+       if ir.IsBlank(n.Left()) && !hasSideEffects(n.Right()) {
+               // Discard.
                return true
        }
        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() {
                a, value := splitnode(r)
-               if a == ir.BlankNode && candiscard(value) {
+               if a == ir.BlankNode && !hasSideEffects(value) {
+                       // Discard.
                        continue
                }
 
index e2100481aa6fa1b02d71405c47a82d6db8c1fb38..a8acd468c9f6becb7900a39951cd4b5509e35ced 100644 (file)
@@ -3669,51 +3669,52 @@ func checkmake(t *types.Type, arg string, np *ir.Node) bool {
        return true
 }
 
-func markbreak(labels *map[*types.Sym]ir.Node, n ir.Node, implicit ir.Node) {
-       if n == nil {
-               return
-       }
+// markBreak marks control statements containing break statements with SetHasBreak(true).
+func markBreak(fn *ir.Func) {
+       var labels map[*types.Sym]ir.Node
+       var implicit ir.Node
 
-       switch n.Op() {
-       case ir.OBREAK:
-               if n.Sym() == nil {
-                       if implicit != nil {
-                               implicit.SetHasBreak(true)
+       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)
+                               }
                        }
-               } 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
                        }
-               }
-       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)
+                       ir.DoChildren(n, mark)
+                       if sym != nil {
+                               delete(labels, sym)
                        }
-                       (*labels)[sym] = n
-                       defer delete(*labels, sym)
+                       implicit = old
                }
-               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)
+               return nil
        }
-}
 
-func markbreaklist(labels *map[*types.Sym]ir.Node, l ir.Nodes, implicit ir.Node) {
-       s := l.Slice()
-       for i := 0; i < len(s); i++ {
-               markbreak(labels, s[i], implicit)
-       }
+       mark(fn)
 }
 
-// isterminating reports whether the Nodes list ends with a terminating statement.
+// isTermNodes reports whether the Nodes list ends with a terminating statement.
 func isTermNodes(l ir.Nodes) bool {
        s := l.Slice()
        c := len(s)
@@ -3723,7 +3724,7 @@ func isTermNodes(l ir.Nodes) bool {
        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.
 func isTermNode(n ir.Node) bool {
        switch n.Op() {
@@ -3776,8 +3777,7 @@ func isTermNode(n ir.Node) bool {
 // checkreturn makes sure that fn terminates appropriately.
 func checkreturn(fn *ir.Func) {
        if fn.Type().NumResults() != 0 && fn.Body().Len() != 0 {
-               var labels map[*types.Sym]ir.Node
-               markbreaklist(&labels, fn.Body(), nil)
+               markBreak(fn)
                if !isTermNodes(fn.Body()) {
                        base.ErrorfAt(fn.Endlineno, "missing return at end of function")
                }
index 3d22c66d901c5c6d50a65b6ef079306c57970d84..bbc08ab9532492c9167c09d140bacaa2272c068d 100644 (file)
@@ -3786,107 +3786,91 @@ func usefield(n ir.Node) {
        Curfn.FieldTrack[sym] = struct{}{}
 }
 
-func candiscardlist(l ir.Nodes) bool {
-       for _, n := range l.Slice() {
-               if !candiscard(n) {
-                       return false
-               }
-       }
-       return true
-}
-
-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
+// hasSideEffects reports whether n contains any operations that could have observable side effects.
+func hasSideEffects(n ir.Node) bool {
+       found := ir.Find(n, func(n ir.Node) interface{} {
+               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
+                       }
 
-               // 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
+               // 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
+                       }
 
-               // 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
+               // 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 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
+               return nil
+       })
+       return found != nil
 }
 
 // Rewrite