// Copyright 2016 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 ssa import ( "fmt" "math" ) type branch int const ( unknown branch = iota positive negative ) // relation represents the set of possible relations between // pairs of variables (v, w). Without a priori knowledge the // mask is lt | eq | gt meaning v can be less than, equal to or // greater than w. When the execution path branches on the condition // `v op w` the set of relations is updated to exclude any // relation not possible due to `v op w` being true (or false). // // E.g. // // r := relation(...) // // if v < w { // newR := r & lt // } // if v >= w { // newR := r & (eq|gt) // } // if v != w { // newR := r & (lt|gt) // } type relation uint const ( lt relation = 1 << iota eq gt ) var relationStrings = [...]string{ 0: "none", lt: "<", eq: "==", lt | eq: "<=", gt: ">", gt | lt: "!=", gt | eq: ">=", gt | eq | lt: "any", } func (r relation) String() string { if r < relation(len(relationStrings)) { return relationStrings[r] } return fmt.Sprintf("relation(%d)", uint(r)) } // domain represents the domain of a variable pair in which a set // of relations is known. For example, relations learned for unsigned // pairs cannot be transferred to signed pairs because the same bit // representation can mean something else. type domain uint const ( signed domain = 1 << iota unsigned pointer boolean ) var domainStrings = [...]string{ "signed", "unsigned", "pointer", "boolean", } func (d domain) String() string { s := "" for i, ds := range domainStrings { if d&(1< l2.max { l.max = l2.max } if l.umax > l2.umax { l.umax = l2.umax } return l } var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64} // a limitFact is a limit known for a particular value. type limitFact struct { vid ID limit limit } // factsTable keeps track of relations between pairs of values. // // The fact table logic is sound, but incomplete. Outside of a few // special cases, it performs no deduction or arithmetic. While there // are known decision procedures for this, the ad hoc approach taken // by the facts table is effective for real code while remaining very // efficient. type factsTable struct { // unsat is true if facts contains a contradiction. // // Note that the factsTable logic is incomplete, so if unsat // is false, the assertions in factsTable could be satisfiable // *or* unsatisfiable. unsat bool // true if facts contains a contradiction unsatDepth int // number of unsat checkpoints facts map[pair]relation // current known set of relation stack []fact // previous sets of relations // known lower and upper bounds on individual values. limits map[ID]limit limitStack []limitFact // previous entries // For each slice s, a map from s to a len(s)/cap(s) value (if any) // TODO: check if there are cases that matter where we have // more than one len(s) for a slice. We could keep a list if necessary. lens map[ID]*Value caps map[ID]*Value } // checkpointFact is an invalid value used for checkpointing // and restoring factsTable. var checkpointFact = fact{} var checkpointBound = limitFact{} func newFactsTable() *factsTable { ft := &factsTable{} ft.facts = make(map[pair]relation) ft.stack = make([]fact, 4) ft.limits = make(map[ID]limit) ft.limitStack = make([]limitFact, 4) return ft } // update updates the set of relations between v and w in domain d // restricting it to r. func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) { if lessByID(w, v) { v, w = w, v r = reverseBits[r] } p := pair{v, w, d} oldR, ok := ft.facts[p] if !ok { if v == w { oldR = eq } else { oldR = lt | eq | gt } } ft.stack = append(ft.stack, fact{p, oldR}) ft.facts[p] = oldR & r if oldR&r == 0 { ft.unsat = true } // Extract bounds when comparing against constants if v.isGenericIntConst() { v, w = w, v r = reverseBits[r] } if v != nil && w.isGenericIntConst() { c := w.AuxInt // Note: all the +1/-1 below could overflow/underflow. Either will // still generate correct results, it will just lead to imprecision. // In fact if there is overflow/underflow, the corresponding // code is unreachable because the known range is outside the range // of the value's type. old, ok := ft.limits[v.ID] if !ok { old = noLimit } lim := noLimit switch d { case signed: switch r { case lt: lim.max = c - 1 case lt | eq: lim.max = c case gt | eq: lim.min = c case gt: lim.min = c + 1 case lt | gt: lim = old if c == lim.min { lim.min++ } if c == lim.max { lim.max-- } case eq: lim.min = c lim.max = c } case unsigned: var uc uint64 switch w.Op { case OpConst64: uc = uint64(c) case OpConst32: uc = uint64(uint32(c)) case OpConst16: uc = uint64(uint16(c)) case OpConst8: uc = uint64(uint8(c)) } switch r { case lt: lim.umax = uc - 1 case lt | eq: lim.umax = uc case gt | eq: lim.umin = uc case gt: lim.umin = uc + 1 case lt | gt: lim = old if uc == lim.umin { lim.umin++ } if uc == lim.umax { lim.umax-- } case eq: lim.umin = uc lim.umax = uc } } ft.limitStack = append(ft.limitStack, limitFact{v.ID, old}) lim = old.intersect(lim) ft.limits[v.ID] = lim if lim.min > lim.max || lim.umin > lim.umax { ft.unsat = true } if v.Block.Func.pass.debug > 2 { v.Block.Func.Warnl(parent.Pos, "parent=%s, new limits %s %s %s", parent, v, w, lim.String()) } } } // isNonNegative returns true if v is known to be non-negative. func (ft *factsTable) isNonNegative(v *Value) bool { if isNonNegative(v) { return true } l, has := ft.limits[v.ID] return has && (l.min >= 0 || l.umax <= math.MaxInt64) } // checkpoint saves the current state of known relations. // Called when descending on a branch. func (ft *factsTable) checkpoint() { if ft.unsat { ft.unsatDepth++ } ft.stack = append(ft.stack, checkpointFact) ft.limitStack = append(ft.limitStack, checkpointBound) } // restore restores known relation to the state just // before the previous checkpoint. // Called when backing up on a branch. func (ft *factsTable) restore() { if ft.unsatDepth > 0 { ft.unsatDepth-- } else { ft.unsat = false } for { old := ft.stack[len(ft.stack)-1] ft.stack = ft.stack[:len(ft.stack)-1] if old == checkpointFact { break } if old.r == lt|eq|gt { delete(ft.facts, old.p) } else { ft.facts[old.p] = old.r } } for { old := ft.limitStack[len(ft.limitStack)-1] ft.limitStack = ft.limitStack[:len(ft.limitStack)-1] if old.vid == 0 { // checkpointBound break } if old.limit == noLimit { delete(ft.limits, old.vid) } else { ft.limits[old.vid] = old.limit } } } func lessByID(v, w *Value) bool { if v == nil && w == nil { // Should not happen, but just in case. return false } if v == nil { return true } return w != nil && v.ID < w.ID } var ( reverseBits = [...]relation{0, 4, 2, 6, 1, 5, 3, 7} // maps what we learn when the positive branch is taken. // For example: // OpLess8: {signed, lt}, // v1 = (OpLess8 v2 v3). // If v1 branch is taken than we learn that the rangeMaks // can be at most lt. domainRelationTable = map[Op]struct { d domain r relation }{ OpEq8: {signed | unsigned, eq}, OpEq16: {signed | unsigned, eq}, OpEq32: {signed | unsigned, eq}, OpEq64: {signed | unsigned, eq}, OpEqPtr: {pointer, eq}, OpNeq8: {signed | unsigned, lt | gt}, OpNeq16: {signed | unsigned, lt | gt}, OpNeq32: {signed | unsigned, lt | gt}, OpNeq64: {signed | unsigned, lt | gt}, OpNeqPtr: {pointer, lt | gt}, OpLess8: {signed, lt}, OpLess8U: {unsigned, lt}, OpLess16: {signed, lt}, OpLess16U: {unsigned, lt}, OpLess32: {signed, lt}, OpLess32U: {unsigned, lt}, OpLess64: {signed, lt}, OpLess64U: {unsigned, lt}, OpLeq8: {signed, lt | eq}, OpLeq8U: {unsigned, lt | eq}, OpLeq16: {signed, lt | eq}, OpLeq16U: {unsigned, lt | eq}, OpLeq32: {signed, lt | eq}, OpLeq32U: {unsigned, lt | eq}, OpLeq64: {signed, lt | eq}, OpLeq64U: {unsigned, lt | eq}, OpGeq8: {signed, eq | gt}, OpGeq8U: {unsigned, eq | gt}, OpGeq16: {signed, eq | gt}, OpGeq16U: {unsigned, eq | gt}, OpGeq32: {signed, eq | gt}, OpGeq32U: {unsigned, eq | gt}, OpGeq64: {signed, eq | gt}, OpGeq64U: {unsigned, eq | gt}, OpGreater8: {signed, gt}, OpGreater8U: {unsigned, gt}, OpGreater16: {signed, gt}, OpGreater16U: {unsigned, gt}, OpGreater32: {signed, gt}, OpGreater32U: {unsigned, gt}, OpGreater64: {signed, gt}, OpGreater64U: {unsigned, gt}, // TODO: OpIsInBounds actually test 0 <= a < b. This means // that the positive branch learns signed/LT and unsigned/LT // but the negative branch only learns unsigned/GE. OpIsInBounds: {unsigned, lt}, // 0 <= arg0 < arg1 OpIsSliceInBounds: {unsigned, lt | eq}, // 0 <= arg0 <= arg1 } ) // prove removes redundant BlockIf branches that can be inferred // from previous dominating comparisons. // // By far, the most common redundant pair are generated by bounds checking. // For example for the code: // // a[i] = 4 // foo(a[i]) // // The compiler will generate the following code: // // if i >= len(a) { // panic("not in bounds") // } // a[i] = 4 // if i >= len(a) { // panic("not in bounds") // } // foo(a[i]) // // The second comparison i >= len(a) is clearly redundant because if the // else branch of the first comparison is executed, we already know that i < len(a). // The code for the second panic can be removed. // // prove works by finding contradictions and trimming branches whose // conditions are unsatisfiable given the branches leading up to them. // It tracks a "fact table" of branch conditions. For each branching // block, it asserts the branch conditions that uniquely dominate that // block, and then separately asserts the block's branch condition and // its negation. If either leads to a contradiction, it can trim that // successor. func prove(f *Func) { ft := newFactsTable() // Find length and capacity ops. for _, b := range f.Blocks { for _, v := range b.Values { if v.Uses == 0 { // We don't care about dead values. // (There can be some that are CSEd but not removed yet.) continue } switch v.Op { case OpSliceLen: if ft.lens == nil { ft.lens = map[ID]*Value{} } ft.lens[v.Args[0].ID] = v case OpSliceCap: if ft.caps == nil { ft.caps = map[ID]*Value{} } ft.caps[v.Args[0].ID] = v } } } // current node state type walkState int const ( descend walkState = iota simplify ) // work maintains the DFS stack. type bp struct { block *Block // current handled block state walkState // what's to do } work := make([]bp, 0, 256) work = append(work, bp{ block: f.Entry, state: descend, }) idom := f.Idom() sdom := f.sdom() // DFS on the dominator tree. // // For efficiency, we consider only the dominator tree rather // than the entire flow graph. On the way down, we consider // incoming branches and accumulate conditions that uniquely // dominate the current block. If we discover a contradiction, // we can eliminate the entire block and all of its children. // On the way back up, we consider outgoing branches that // haven't already been considered. This way we consider each // branch condition only once. for len(work) > 0 { node := work[len(work)-1] work = work[:len(work)-1] parent := idom[node.block.ID] branch := getBranch(sdom, parent, node.block) switch node.state { case descend: if branch != unknown { if !tryPushBranch(ft, parent, branch) { // node.block is unreachable. // Remove it and don't visit // its children. removeBranch(parent, branch) break } // Otherwise, we can now commit to // taking this branch. We'll restore // ft when we unwind. } work = append(work, bp{ block: node.block, state: simplify, }) for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) { work = append(work, bp{ block: s, state: descend, }) } case simplify: simplifyBlock(sdom, ft, node.block) if branch != unknown { popBranch(ft) } } } } // getBranch returns the range restrictions added by p // when reaching b. p is the immediate dominator of b. func getBranch(sdom SparseTree, p *Block, b *Block) branch { if p == nil || p.Kind != BlockIf { return unknown } // If p and p.Succs[0] are dominators it means that every path // from entry to b passes through p and p.Succs[0]. We care that // no path from entry to b passes through p.Succs[1]. If p.Succs[0] // has one predecessor then (apart from the degenerate case), // there is no path from entry that can reach b through p.Succs[1]. // TODO: how about p->yes->b->yes, i.e. a loop in yes. if sdom.isAncestorEq(p.Succs[0].b, b) && len(p.Succs[0].b.Preds) == 1 { return positive } if sdom.isAncestorEq(p.Succs[1].b, b) && len(p.Succs[1].b.Preds) == 1 { return negative } return unknown } // tryPushBranch tests whether it is possible to branch from Block b // in direction br and, if so, pushes the branch conditions in the // factsTable and returns true. A successful tryPushBranch must be // paired with a popBranch. func tryPushBranch(ft *factsTable, b *Block, br branch) bool { ft.checkpoint() c := b.Control updateRestrictions(b, ft, boolean, nil, c, lt|gt, br) if tr, has := domainRelationTable[b.Control.Op]; has { // When we branched from parent we learned a new set of // restrictions. Update the factsTable accordingly. updateRestrictions(b, ft, tr.d, c.Args[0], c.Args[1], tr.r, br) } if ft.unsat { // This branch's conditions contradict some known // fact, so it cannot be taken. Unwind the facts. // // (Since we never checkpoint an unsat factsTable, we // don't really need factsTable.unsatDepth, but // there's no cost to keeping checkpoint/restore more // general.) ft.restore() return false } return true } // popBranch undoes the effects of a successful tryPushBranch. func popBranch(ft *factsTable) { ft.restore() } // updateRestrictions updates restrictions from the immediate // dominating block (p) using r. r is adjusted according to the branch taken. func updateRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r relation, branch branch) { if t == 0 || branch == unknown { // Trivial case: nothing to do, or branch unknown. // Shoult not happen, but just in case. return } if branch == negative { // Negative branch taken, complement the relations. r = (lt | eq | gt) ^ r } for i := domain(1); i <= t; i <<= 1 { if t&i == 0 { continue } ft.update(parent, v, w, i, r) if i == boolean && v == nil && w != nil && (w.Op == OpIsInBounds || w.Op == OpIsSliceInBounds) { // 0 <= a0 < a1 (or 0 <= a0 <= a1) // // domainRelationTable handles the a0 / a1 // relation, but not the 0 / a0 relation. // // On the positive branch we learn 0 <= a0, // but this turns out never to be useful. // // On the negative branch we learn (0 > a0 || // a0 >= a1) (or (0 > a0 || a0 > a1)). We // can't express an || condition, but we learn // something if we can disprove the LHS. if r == eq && ft.isNonNegative(w.Args[0]) { // false == w, so we're on the // negative branch. a0 >= 0, so the // LHS is false. Thus, the RHS holds. opr := eq | gt if w.Op == OpIsSliceInBounds { opr = gt } ft.update(parent, w.Args[0], w.Args[1], signed, opr) } } // Additional facts we know given the relationship between len and cap. if i != signed && i != unsigned { continue } if v.Op == OpSliceLen && r< == 0 && ft.caps[v.Args[0].ID] != nil { // len(s) > w implies cap(s) > w // len(s) >= w implies cap(s) >= w // len(s) == w implies cap(s) >= w ft.update(parent, ft.caps[v.Args[0].ID], w, i, r|gt) } if w.Op == OpSliceLen && r> == 0 && ft.caps[w.Args[0].ID] != nil { // same, length on the RHS. ft.update(parent, v, ft.caps[w.Args[0].ID], i, r|lt) } if v.Op == OpSliceCap && r> == 0 && ft.lens[v.Args[0].ID] != nil { // cap(s) < w implies len(s) < w // cap(s) <= w implies len(s) <= w // cap(s) == w implies len(s) <= w ft.update(parent, ft.lens[v.Args[0].ID], w, i, r|lt) } if w.Op == OpSliceCap && r< == 0 && ft.lens[w.Args[0].ID] != nil { // same, capacity on the RHS. ft.update(parent, v, ft.lens[w.Args[0].ID], i, r|gt) } } } // simplifyBlock simplifies some constant values in b and evaluates // branches to non-uniquely dominated successors of b. func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) { // Replace OpSlicemask operations in b with constants where possible. for _, v := range b.Values { if v.Op != OpSlicemask { continue } add := v.Args[0] if add.Op != OpAdd64 && add.Op != OpAdd32 { continue } // Note that the arg of slicemask was originally a sub, but // was rewritten to an add by generic.rules (if the thing // being subtracted was a constant). x := add.Args[0] y := add.Args[1] if x.Op == OpConst64 || x.Op == OpConst32 { x, y = y, x } if y.Op != OpConst64 && y.Op != OpConst32 { continue } // slicemask(x + y) // if x is larger than -y (y is negative), then slicemask is -1. lim, ok := ft.limits[x.ID] if !ok { continue } if lim.umin > uint64(-y.AuxInt) { if v.Args[0].Op == OpAdd64 { v.reset(OpConst64) } else { v.reset(OpConst32) } if b.Func.pass.debug > 0 { b.Func.Warnl(v.Pos, "Proved slicemask not needed") } v.AuxInt = -1 } } if b.Kind != BlockIf { return } // Consider outgoing edges from this block. parent := b for i, branch := range [...]branch{positive, negative} { child := parent.Succs[i].b if getBranch(sdom, parent, child) != unknown { // For edges to uniquely dominated blocks, we // already did this when we visited the child. continue } // For edges to other blocks, this can trim a branch // even if we couldn't get rid of the child itself. if !tryPushBranch(ft, parent, branch) { // This branch is impossible, so remove it // from the block. removeBranch(parent, branch) // No point in considering the other branch. // (It *is* possible for both to be // unsatisfiable since the fact table is // incomplete. We could turn this into a // BlockExit, but it doesn't seem worth it.) break } popBranch(ft) } } func removeBranch(b *Block, branch branch) { if b.Func.pass.debug > 0 { verb := "Proved" if branch == positive { verb = "Disproved" } c := b.Control if b.Func.pass.debug > 1 { b.Func.Warnl(b.Pos, "%s %s (%s)", verb, c.Op, c) } else { b.Func.Warnl(b.Pos, "%s %s", verb, c.Op) } } b.Kind = BlockFirst b.SetControl(nil) if branch == positive { b.swapSuccessors() } } // isNonNegative returns true is v is known to be greater or equal to zero. func isNonNegative(v *Value) bool { switch v.Op { case OpConst64: return v.AuxInt >= 0 case OpConst32: return int32(v.AuxInt) >= 0 case OpStringLen, OpSliceLen, OpSliceCap, OpZeroExt8to64, OpZeroExt16to64, OpZeroExt32to64: return true case OpRsh64x64: return isNonNegative(v.Args[0]) } return false }