go/src/cmd/compile/internal/ssa/prove.go

352 lines
9.2 KiB
Go
Raw Normal View History

// 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
// rangeMask represents the possible relations between a pair of variables.
type rangeMask uint
const (
lt rangeMask = 1 << iota
eq
gt
)
// typeMask represents the universe of a variable pair in which
// a set of relations is known.
// For example, information learned for unsigned pairs cannot
// be transfered to signed pairs because the same bit representation
// can mean something else.
type typeMask uint
const (
signed typeMask = 1 << iota
unsigned
pointer
)
type typeRange struct {
t typeMask
r rangeMask
}
type control struct {
tm typeMask
a0, a1 ID
}
var (
reverseBits = [...]rangeMask{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.
typeRangeTable = map[Op]typeRange{
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},
OpIsSliceInBounds: {unsigned, lt | eq},
}
)
// prove removes redundant BlockIf controls that can be inferred in a straight line.
//
// By far, the most common redundant control 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.
func prove(f *Func) {
idom := dominators(f)
sdom := newSparseTree(f, idom)
// 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
saved []typeRange // save previous map entries modified by node
}
work := make([]bp, 0, 256)
work = append(work, bp{
block: f.Entry,
state: descend,
})
// mask keep tracks of restrictions for each pair of values in
// the dominators for the current node.
// Invariant: a0.ID <= a1.ID
// For example {unsigned, a0, a1} -> eq|gt means that from
// predecessors we know that a0 must be greater or equal to
// a1.
mask := make(map[control]rangeMask)
// DFS on the dominator tree.
for len(work) > 0 {
node := work[len(work)-1]
work = work[:len(work)-1]
switch node.state {
case descend:
parent := idom[node.block.ID]
tr := getRestrict(sdom, parent, node.block)
saved := updateRestrictions(mask, parent, tr)
work = append(work, bp{
block: node.block,
state: simplify,
saved: saved,
})
for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) {
work = append(work, bp{
block: s,
state: descend,
})
}
case simplify:
simplifyBlock(mask, node.block)
restoreRestrictions(mask, idom[node.block.ID], node.saved)
}
}
}
// getRestrict returns the range restrictions added by p
// when reaching b. p is the immediate dominator or b.
func getRestrict(sdom sparseTree, p *Block, b *Block) typeRange {
if p == nil || p.Kind != BlockIf {
return typeRange{}
}
tr, has := typeRangeTable[p.Control.Op]
if !has {
return typeRange{}
}
// 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) && len(p.Succs[0].Preds) == 1 {
return tr
} else if sdom.isAncestorEq(p.Succs[1], b) && len(p.Succs[1].Preds) == 1 {
tr.r = (lt | eq | gt) ^ tr.r
return tr
}
return typeRange{}
}
// updateRestrictions updates restrictions from the previous block (p) based on tr.
// normally tr was calculated with getRestrict.
func updateRestrictions(mask map[control]rangeMask, p *Block, tr typeRange) []typeRange {
if tr.t == 0 {
return nil
}
// p modifies the restrictions for (a0, a1).
// save and return the previous state.
a0 := p.Control.Args[0]
a1 := p.Control.Args[1]
if a0.ID > a1.ID {
tr.r = reverseBits[tr.r]
a0, a1 = a1, a0
}
saved := make([]typeRange, 0, 2)
for t := typeMask(1); t <= tr.t; t <<= 1 {
if t&tr.t == 0 {
continue
}
i := control{t, a0.ID, a1.ID}
oldRange, ok := mask[i]
if !ok {
if a1 != a0 {
oldRange = lt | eq | gt
} else { // sometimes happens after cse
oldRange = eq
}
}
// if i was not already in the map we save the full range
// so that when we restore it we properly keep track of it.
saved = append(saved, typeRange{t, oldRange})
// mask[i] contains the possible relations between a0 and a1.
// When we branched from parent we learned that the possible
// relations cannot be more than tr.r. We compute the new set of
// relations as the intersection betwee the old and the new set.
mask[i] = oldRange & tr.r
}
return saved
}
func restoreRestrictions(mask map[control]rangeMask, p *Block, saved []typeRange) {
if p == nil || p.Kind != BlockIf || len(saved) == 0 {
return
}
a0 := p.Control.Args[0].ID
a1 := p.Control.Args[1].ID
if a0 > a1 {
a0, a1 = a1, a0
}
for _, tr := range saved {
i := control{tr.t, a0, a1}
if tr.r != lt|eq|gt {
mask[i] = tr.r
} else {
delete(mask, i)
}
}
}
// simplifyBlock simplifies block known the restrictions in mask.
func simplifyBlock(mask map[control]rangeMask, b *Block) {
if b.Kind != BlockIf {
return
}
tr, has := typeRangeTable[b.Control.Op]
if !has {
return
}
succ := -1
a0 := b.Control.Args[0].ID
a1 := b.Control.Args[1].ID
if a0 > a1 {
tr.r = reverseBits[tr.r]
a0, a1 = a1, a0
}
for t := typeMask(1); t <= tr.t; t <<= 1 {
if t&tr.t == 0 {
continue
}
// tr.r represents in which case the positive branch is taken.
// m.r represents which cases are possible because of previous relations.
// If the set of possible relations m.r is included in the set of relations
// need to take the positive branch (or negative) then that branch will
// always be taken.
// For shortcut, if m.r == 0 then this block is dead code.
i := control{t, a0, a1}
m := mask[i]
if m != 0 && tr.r&m == m {
if b.Func.pass.debug > 0 {
b.Func.Config.Warnl(int(b.Line), "Proved %s", b.Control.Op)
}
b.Logf("proved positive branch of %s, block %s in %s\n", b.Control, b, b.Func.Name)
succ = 0
break
}
if m != 0 && ((lt|eq|gt)^tr.r)&m == m {
if b.Func.pass.debug > 0 {
b.Func.Config.Warnl(int(b.Line), "Disproved %s", b.Control.Op)
}
b.Logf("proved negative branch of %s, block %s in %s\n", b.Control, b, b.Func.Name)
succ = 1
break
}
}
if succ == -1 {
// HACK: If the first argument of IsInBounds or IsSliceInBounds
// is a constant and we already know that constant is smaller (or equal)
// to the upper bound than this is proven. Most useful in cases such as:
// if len(a) <= 1 { return }
// do something with a[1]
c := b.Control
if (c.Op == OpIsInBounds || c.Op == OpIsSliceInBounds) &&
c.Args[0].Op == OpConst64 && c.Args[0].AuxInt >= 0 {
m := mask[control{signed, a0, a1}]
if m != 0 && tr.r&m == m {
if b.Func.pass.debug > 0 {
b.Func.Config.Warnl(int(b.Line), "Proved constant %s", c.Op)
}
succ = 0
}
}
}
if succ != -1 {
b.Kind = BlockFirst
b.Control = nil
b.Succs[0], b.Succs[1] = b.Succs[succ], b.Succs[1-succ]
}
}