2016-02-19 12:14:42 +01:00
|
|
|
// 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
|
|
|
|
|
|
2016-10-19 11:47:52 -04:00
|
|
|
import (
|
2019-10-01 11:10:22 -04:00
|
|
|
"cmd/internal/src"
|
2016-10-19 11:47:52 -04:00
|
|
|
"fmt"
|
|
|
|
|
"math"
|
|
|
|
|
)
|
2016-03-23 10:20:44 -07:00
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
type branch int
|
2016-02-19 12:14:42 +01:00
|
|
|
|
|
|
|
|
const (
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
unknown branch = iota
|
2016-03-07 18:36:16 +01:00
|
|
|
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
|
2016-02-19 12:14:42 +01:00
|
|
|
eq
|
|
|
|
|
gt
|
|
|
|
|
)
|
|
|
|
|
|
2018-01-09 14:45:30 -05:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
// domain represents the domain of a variable pair in which a set
|
2018-07-24 23:59:48 +00:00
|
|
|
// of relations is known. For example, relations learned for unsigned
|
2016-04-03 12:43:27 +01:00
|
|
|
// pairs cannot be transferred to signed pairs because the same bit
|
2016-03-07 18:36:16 +01:00
|
|
|
// representation can mean something else.
|
|
|
|
|
type domain uint
|
2016-02-19 12:14:42 +01:00
|
|
|
|
|
|
|
|
const (
|
2016-03-07 18:36:16 +01:00
|
|
|
signed domain = 1 << iota
|
2016-02-19 12:14:42 +01:00
|
|
|
unsigned
|
|
|
|
|
pointer
|
2016-03-07 18:36:16 +01:00
|
|
|
boolean
|
2016-02-19 12:14:42 +01:00
|
|
|
)
|
|
|
|
|
|
2018-01-09 14:45:30 -05:00
|
|
|
var domainStrings = [...]string{
|
|
|
|
|
"signed", "unsigned", "pointer", "boolean",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d domain) String() string {
|
|
|
|
|
s := ""
|
|
|
|
|
for i, ds := range domainStrings {
|
|
|
|
|
if d&(1<<uint(i)) != 0 {
|
|
|
|
|
if len(s) != 0 {
|
|
|
|
|
s += "|"
|
|
|
|
|
}
|
|
|
|
|
s += ds
|
|
|
|
|
d &^= 1 << uint(i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if d != 0 {
|
|
|
|
|
if len(s) != 0 {
|
|
|
|
|
s += "|"
|
|
|
|
|
}
|
|
|
|
|
s += fmt.Sprintf("0x%x", uint(d))
|
|
|
|
|
}
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
type pair struct {
|
|
|
|
|
v, w *Value // a pair of values, ordered by ID.
|
|
|
|
|
// v can be nil, to mean the zero value.
|
|
|
|
|
// for booleans the zero value (v == nil) is false.
|
|
|
|
|
d domain
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fact is a pair plus a relation for that pair.
|
|
|
|
|
type fact struct {
|
|
|
|
|
p pair
|
|
|
|
|
r relation
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-23 10:20:44 -07:00
|
|
|
// a limit records known upper and lower bounds for a value.
|
|
|
|
|
type limit struct {
|
|
|
|
|
min, max int64 // min <= value <= max, signed
|
|
|
|
|
umin, umax uint64 // umin <= value <= umax, unsigned
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 11:47:52 -04:00
|
|
|
func (l limit) String() string {
|
|
|
|
|
return fmt.Sprintf("sm,SM,um,UM=%d,%d,%d,%d", l.min, l.max, l.umin, l.umax)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-10 17:39:43 -05:00
|
|
|
func (l limit) intersect(l2 limit) limit {
|
|
|
|
|
if l.min < l2.min {
|
|
|
|
|
l.min = l2.min
|
|
|
|
|
}
|
|
|
|
|
if l.umin < l2.umin {
|
|
|
|
|
l.umin = l2.umin
|
|
|
|
|
}
|
|
|
|
|
if l.max > l2.max {
|
|
|
|
|
l.max = l2.max
|
|
|
|
|
}
|
|
|
|
|
if l.umax > l2.umax {
|
|
|
|
|
l.umax = l2.umax
|
|
|
|
|
}
|
|
|
|
|
return l
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-23 10:20:44 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
// factsTable keeps track of relations between pairs of values.
|
2018-01-09 14:40:54 -05:00
|
|
|
//
|
|
|
|
|
// 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.
|
2016-03-07 18:36:16 +01:00
|
|
|
type factsTable struct {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// 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
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
facts map[pair]relation // current known set of relation
|
|
|
|
|
stack []fact // previous sets of relations
|
2016-03-23 10:20:44 -07:00
|
|
|
|
2018-04-15 23:53:08 +02:00
|
|
|
// order is a couple of partial order sets that record information
|
|
|
|
|
// about relations between SSA values in the signed and unsigned
|
|
|
|
|
// domain.
|
2019-09-20 00:12:15 +02:00
|
|
|
orderS *poset
|
|
|
|
|
orderU *poset
|
2018-04-15 23:53:08 +02:00
|
|
|
|
2016-03-23 10:20:44 -07:00
|
|
|
// known lower and upper bounds on individual values.
|
|
|
|
|
limits map[ID]limit
|
|
|
|
|
limitStack []limitFact // previous entries
|
2016-11-27 11:43:08 -08:00
|
|
|
|
|
|
|
|
// 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
|
2019-02-06 19:49:15 +00:00
|
|
|
|
2019-03-31 22:33:52 +01:00
|
|
|
// zero is a zero-valued constant
|
2019-02-06 19:49:15 +00:00
|
|
|
zero *Value
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
// checkpointFact is an invalid value used for checkpointing
|
|
|
|
|
// and restoring factsTable.
|
|
|
|
|
var checkpointFact = fact{}
|
2016-03-23 10:20:44 -07:00
|
|
|
var checkpointBound = limitFact{}
|
2016-03-07 18:36:16 +01:00
|
|
|
|
2018-05-01 00:57:57 +02:00
|
|
|
func newFactsTable(f *Func) *factsTable {
|
2016-03-07 18:36:16 +01:00
|
|
|
ft := &factsTable{}
|
2019-09-20 00:12:15 +02:00
|
|
|
ft.orderS = f.newPoset()
|
|
|
|
|
ft.orderU = f.newPoset()
|
|
|
|
|
ft.orderS.SetUnsigned(false)
|
|
|
|
|
ft.orderU.SetUnsigned(true)
|
2016-03-07 18:36:16 +01:00
|
|
|
ft.facts = make(map[pair]relation)
|
|
|
|
|
ft.stack = make([]fact, 4)
|
2016-03-23 10:20:44 -07:00
|
|
|
ft.limits = make(map[ID]limit)
|
|
|
|
|
ft.limitStack = make([]limitFact, 4)
|
2019-03-29 19:17:35 +00:00
|
|
|
ft.zero = f.ConstInt64(f.Config.Types.Int64, 0)
|
2016-03-07 18:36:16 +01:00
|
|
|
return ft
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update updates the set of relations between v and w in domain d
|
|
|
|
|
// restricting it to r.
|
2016-10-19 11:47:52 -04:00
|
|
|
func (ft *factsTable) update(parent *Block, v, w *Value, d domain, r relation) {
|
2019-01-02 12:27:55 -05:00
|
|
|
if parent.Func.pass.debug > 2 {
|
|
|
|
|
parent.Func.Warnl(parent.Pos, "parent=%s, update %s %s %s", parent, v, w, r)
|
|
|
|
|
}
|
2018-03-13 00:21:57 +01:00
|
|
|
// No need to do anything else if we already found unsat.
|
|
|
|
|
if ft.unsat {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 00:25:06 +01:00
|
|
|
// Self-fact. It's wasteful to register it into the facts
|
|
|
|
|
// table, so just note whether it's satisfiable
|
|
|
|
|
if v == w {
|
|
|
|
|
if r&eq == 0 {
|
|
|
|
|
ft.unsat = true
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-15 23:53:08 +02:00
|
|
|
if d == signed || d == unsigned {
|
|
|
|
|
var ok bool
|
2019-09-20 00:12:15 +02:00
|
|
|
order := ft.orderS
|
2018-04-15 23:53:08 +02:00
|
|
|
if d == unsigned {
|
2019-09-20 00:12:15 +02:00
|
|
|
order = ft.orderU
|
2018-04-15 23:53:08 +02:00
|
|
|
}
|
|
|
|
|
switch r {
|
|
|
|
|
case lt:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetOrder(v, w)
|
2018-04-15 23:53:08 +02:00
|
|
|
case gt:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetOrder(w, v)
|
2018-04-15 23:53:08 +02:00
|
|
|
case lt | eq:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetOrderOrEqual(v, w)
|
2018-04-15 23:53:08 +02:00
|
|
|
case gt | eq:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetOrderOrEqual(w, v)
|
2018-04-15 23:53:08 +02:00
|
|
|
case eq:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetEqual(v, w)
|
2018-04-15 23:53:08 +02:00
|
|
|
case lt | gt:
|
2019-09-20 00:12:15 +02:00
|
|
|
ok = order.SetNonEqual(v, w)
|
2018-04-15 23:53:08 +02:00
|
|
|
default:
|
|
|
|
|
panic("unknown relation")
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
2019-01-02 12:27:55 -05:00
|
|
|
if parent.Func.pass.debug > 2 {
|
|
|
|
|
parent.Func.Warnl(parent.Pos, "unsat %s %s %s", v, w, r)
|
|
|
|
|
}
|
2018-04-15 23:53:08 +02:00
|
|
|
ft.unsat = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if lessByID(w, v) {
|
|
|
|
|
v, w = w, v
|
|
|
|
|
r = reverseBits[r]
|
|
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
|
2018-04-15 23:53:08 +02:00
|
|
|
p := pair{v, w, d}
|
|
|
|
|
oldR, ok := ft.facts[p]
|
|
|
|
|
if !ok {
|
|
|
|
|
if v == w {
|
|
|
|
|
oldR = eq
|
|
|
|
|
} else {
|
|
|
|
|
oldR = lt | eq | gt
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// No changes compared to information already in facts table.
|
|
|
|
|
if oldR == r {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ft.stack = append(ft.stack, fact{p, oldR})
|
|
|
|
|
ft.facts[p] = oldR & r
|
|
|
|
|
// If this relation is not satisfiable, mark it and exit right away
|
|
|
|
|
if oldR&r == 0 {
|
2019-01-02 12:27:55 -05:00
|
|
|
if parent.Func.pass.debug > 2 {
|
|
|
|
|
parent.Func.Warnl(parent.Pos, "unsat %s %s %s", v, w, r)
|
|
|
|
|
}
|
2018-04-15 23:53:08 +02:00
|
|
|
ft.unsat = true
|
|
|
|
|
return
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
}
|
|
|
|
|
}
|
2016-03-23 10:20:44 -07:00
|
|
|
|
|
|
|
|
// Extract bounds when comparing against constants
|
|
|
|
|
if v.isGenericIntConst() {
|
|
|
|
|
v, w = w, v
|
|
|
|
|
r = reverseBits[r]
|
|
|
|
|
}
|
|
|
|
|
if v != nil && w.isGenericIntConst() {
|
|
|
|
|
// 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
|
2018-03-23 17:21:33 -04:00
|
|
|
if v.isGenericIntConst() {
|
|
|
|
|
switch d {
|
|
|
|
|
case signed:
|
|
|
|
|
old.min, old.max = v.AuxInt, v.AuxInt
|
|
|
|
|
if v.AuxInt >= 0 {
|
|
|
|
|
old.umin, old.umax = uint64(v.AuxInt), uint64(v.AuxInt)
|
|
|
|
|
}
|
|
|
|
|
case unsigned:
|
|
|
|
|
old.umin = v.AuxUnsigned()
|
|
|
|
|
old.umax = old.umin
|
|
|
|
|
if int64(old.umin) >= 0 {
|
|
|
|
|
old.min, old.max = int64(old.umin), int64(old.umin)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-23 10:20:44 -07:00
|
|
|
}
|
2018-01-10 17:39:43 -05:00
|
|
|
lim := noLimit
|
2016-03-23 10:20:44 -07:00
|
|
|
switch d {
|
|
|
|
|
case signed:
|
2018-03-25 12:20:57 -04:00
|
|
|
c := w.AuxInt
|
2016-03-23 10:20:44 -07:00
|
|
|
switch r {
|
|
|
|
|
case lt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.max = c - 1
|
2016-03-23 10:20:44 -07:00
|
|
|
case lt | eq:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.max = c
|
2016-03-23 10:20:44 -07:00
|
|
|
case gt | eq:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.min = c
|
2016-03-23 10:20:44 -07:00
|
|
|
case gt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.min = c + 1
|
2016-03-23 10:20:44 -07:00
|
|
|
case lt | gt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim = old
|
2016-03-23 10:20:44 -07:00
|
|
|
if c == lim.min {
|
|
|
|
|
lim.min++
|
|
|
|
|
}
|
|
|
|
|
if c == lim.max {
|
|
|
|
|
lim.max--
|
|
|
|
|
}
|
|
|
|
|
case eq:
|
|
|
|
|
lim.min = c
|
|
|
|
|
lim.max = c
|
|
|
|
|
}
|
2018-01-11 14:23:01 -05:00
|
|
|
if lim.min >= 0 {
|
|
|
|
|
// int(x) >= 0 && int(x) >= N ⇒ uint(x) >= N
|
|
|
|
|
lim.umin = uint64(lim.min)
|
|
|
|
|
}
|
|
|
|
|
if lim.max != noLimit.max && old.min >= 0 && lim.max >= 0 {
|
|
|
|
|
// 0 <= int(x) <= N ⇒ 0 <= uint(x) <= N
|
|
|
|
|
// This is for a max update, so the lower bound
|
|
|
|
|
// comes from what we already know (old).
|
|
|
|
|
lim.umax = uint64(lim.max)
|
|
|
|
|
}
|
2016-03-23 10:20:44 -07:00
|
|
|
case unsigned:
|
2018-03-25 12:20:57 -04:00
|
|
|
uc := w.AuxUnsigned()
|
2016-03-23 10:20:44 -07:00
|
|
|
switch r {
|
|
|
|
|
case lt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.umax = uc - 1
|
2016-03-23 10:20:44 -07:00
|
|
|
case lt | eq:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.umax = uc
|
2016-03-23 10:20:44 -07:00
|
|
|
case gt | eq:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.umin = uc
|
2016-03-23 10:20:44 -07:00
|
|
|
case gt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim.umin = uc + 1
|
2016-03-23 10:20:44 -07:00
|
|
|
case lt | gt:
|
2018-01-10 17:39:43 -05:00
|
|
|
lim = old
|
2016-03-23 10:20:44 -07:00
|
|
|
if uc == lim.umin {
|
|
|
|
|
lim.umin++
|
|
|
|
|
}
|
|
|
|
|
if uc == lim.umax {
|
|
|
|
|
lim.umax--
|
|
|
|
|
}
|
|
|
|
|
case eq:
|
|
|
|
|
lim.umin = uc
|
|
|
|
|
lim.umax = uc
|
|
|
|
|
}
|
2018-01-11 14:23:01 -05:00
|
|
|
// We could use the contrapositives of the
|
|
|
|
|
// signed implications to derive signed facts,
|
|
|
|
|
// but it turns out not to matter.
|
2016-03-23 10:20:44 -07:00
|
|
|
}
|
|
|
|
|
ft.limitStack = append(ft.limitStack, limitFact{v.ID, old})
|
2018-01-10 17:39:43 -05:00
|
|
|
lim = old.intersect(lim)
|
2016-03-23 10:20:44 -07:00
|
|
|
ft.limits[v.ID] = lim
|
2016-10-19 11:47:52 -04:00
|
|
|
if v.Block.Func.pass.debug > 2 {
|
2019-01-02 12:27:55 -05:00
|
|
|
v.Block.Func.Warnl(parent.Pos, "parent=%s, new limits %s %s %s %s", parent, v, w, r, lim.String())
|
2016-10-19 11:47:52 -04:00
|
|
|
}
|
2018-03-13 00:21:57 +01:00
|
|
|
if lim.min > lim.max || lim.umin > lim.umax {
|
|
|
|
|
ft.unsat = true
|
|
|
|
|
return
|
|
|
|
|
}
|
2016-03-23 10:20:44 -07:00
|
|
|
}
|
2018-01-11 16:32:02 -05:00
|
|
|
|
2018-03-23 17:25:26 -04:00
|
|
|
// Derived facts below here are only about numbers.
|
|
|
|
|
if d != signed && d != unsigned {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Additional facts we know given the relationship between len and cap.
|
|
|
|
|
//
|
|
|
|
|
// TODO: Since prove now derives transitive relations, it
|
|
|
|
|
// should be sufficient to learn that len(w) <= cap(w) at the
|
|
|
|
|
// beginning of prove where we look for all len/cap ops.
|
|
|
|
|
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, d, 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], d, 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, d, 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], d, r|gt)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 16:32:02 -05:00
|
|
|
// Process fence-post implications.
|
|
|
|
|
//
|
|
|
|
|
// First, make the condition > or >=.
|
|
|
|
|
if r == lt || r == lt|eq {
|
|
|
|
|
v, w = w, v
|
|
|
|
|
r = reverseBits[r]
|
|
|
|
|
}
|
|
|
|
|
switch r {
|
|
|
|
|
case gt:
|
|
|
|
|
if x, delta := isConstDelta(v); x != nil && delta == 1 {
|
|
|
|
|
// x+1 > w ⇒ x >= w
|
|
|
|
|
//
|
|
|
|
|
// This is useful for eliminating the
|
|
|
|
|
// growslice branch of append.
|
|
|
|
|
ft.update(parent, x, w, d, gt|eq)
|
|
|
|
|
} else if x, delta := isConstDelta(w); x != nil && delta == -1 {
|
|
|
|
|
// v > x-1 ⇒ v >= x
|
|
|
|
|
ft.update(parent, v, x, d, gt|eq)
|
|
|
|
|
}
|
|
|
|
|
case gt | eq:
|
|
|
|
|
if x, delta := isConstDelta(v); x != nil && delta == -1 {
|
|
|
|
|
// x-1 >= w && x > min ⇒ x > w
|
|
|
|
|
//
|
|
|
|
|
// Useful for i > 0; s[i-1].
|
|
|
|
|
lim, ok := ft.limits[x.ID]
|
2018-08-31 02:15:26 +02:00
|
|
|
if ok && ((d == signed && lim.min > opMin[v.Op]) || (d == unsigned && lim.umin > 0)) {
|
2018-01-11 16:32:02 -05:00
|
|
|
ft.update(parent, x, w, d, gt)
|
|
|
|
|
}
|
|
|
|
|
} else if x, delta := isConstDelta(w); x != nil && delta == 1 {
|
|
|
|
|
// v >= x+1 && x < max ⇒ v > x
|
|
|
|
|
lim, ok := ft.limits[x.ID]
|
2018-08-31 02:15:26 +02:00
|
|
|
if ok && ((d == signed && lim.max < opMax[w.Op]) || (d == unsigned && lim.umax < opUMax[w.Op])) {
|
2018-01-11 16:32:02 -05:00
|
|
|
ft.update(parent, v, x, d, gt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
|
2018-04-15 16:52:49 +02:00
|
|
|
// Process: x+delta > w (with delta constant)
|
|
|
|
|
// Only signed domain for now (useful for accesses to slices in loops).
|
2018-04-02 01:57:49 +02:00
|
|
|
if r == gt || r == gt|eq {
|
2018-04-15 16:52:49 +02:00
|
|
|
if x, delta := isConstDelta(v); x != nil && d == signed {
|
2018-04-02 01:57:49 +02:00
|
|
|
if parent.Func.pass.debug > 1 {
|
2019-01-02 12:27:55 -05:00
|
|
|
parent.Func.Warnl(parent.Pos, "x+d %s w; x:%v %v delta:%v w:%v d:%v", r, x, parent.String(), delta, w.AuxInt, d)
|
2018-04-02 01:57:49 +02:00
|
|
|
}
|
2018-04-15 16:52:49 +02:00
|
|
|
if !w.isGenericIntConst() {
|
|
|
|
|
// If we know that x+delta > w but w is not constant, we can derive:
|
|
|
|
|
// if delta < 0 and x > MinInt - delta, then x > w (because x+delta cannot underflow)
|
|
|
|
|
// This is useful for loops with bounds "len(slice)-K" (delta = -K)
|
|
|
|
|
if l, has := ft.limits[x.ID]; has && delta < 0 {
|
|
|
|
|
if (x.Type.Size() == 8 && l.min >= math.MinInt64-delta) ||
|
|
|
|
|
(x.Type.Size() == 4 && l.min >= math.MinInt32-delta) {
|
|
|
|
|
ft.update(parent, x, w, signed, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
} else {
|
2018-04-15 16:52:49 +02:00
|
|
|
// With w,delta constants, we want to derive: x+delta > w ⇒ x > w-delta
|
|
|
|
|
//
|
|
|
|
|
// We compute (using integers of the correct size):
|
|
|
|
|
// min = w - delta
|
|
|
|
|
// max = MaxInt - delta
|
|
|
|
|
//
|
|
|
|
|
// And we prove that:
|
|
|
|
|
// if min<max: min < x AND x <= max
|
|
|
|
|
// if min>max: min < x OR x <= max
|
|
|
|
|
//
|
|
|
|
|
// This is always correct, even in case of overflow.
|
|
|
|
|
//
|
|
|
|
|
// If the initial fact is x+delta >= w instead, the derived conditions are:
|
|
|
|
|
// if min<max: min <= x AND x <= max
|
|
|
|
|
// if min>max: min <= x OR x <= max
|
|
|
|
|
//
|
|
|
|
|
// Notice the conditions for max are still <=, as they handle overflows.
|
|
|
|
|
var min, max int64
|
|
|
|
|
var vmin, vmax *Value
|
|
|
|
|
switch x.Type.Size() {
|
|
|
|
|
case 8:
|
|
|
|
|
min = w.AuxInt - delta
|
|
|
|
|
max = int64(^uint64(0)>>1) - delta
|
|
|
|
|
|
|
|
|
|
vmin = parent.NewValue0I(parent.Pos, OpConst64, parent.Func.Config.Types.Int64, min)
|
|
|
|
|
vmax = parent.NewValue0I(parent.Pos, OpConst64, parent.Func.Config.Types.Int64, max)
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
min = int64(int32(w.AuxInt) - int32(delta))
|
|
|
|
|
max = int64(int32(^uint32(0)>>1) - int32(delta))
|
|
|
|
|
|
|
|
|
|
vmin = parent.NewValue0I(parent.Pos, OpConst32, parent.Func.Config.Types.Int32, min)
|
|
|
|
|
vmax = parent.NewValue0I(parent.Pos, OpConst32, parent.Func.Config.Types.Int32, max)
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
panic("unimplemented")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if min < max {
|
|
|
|
|
// Record that x > min and max >= x
|
|
|
|
|
ft.update(parent, x, vmin, d, r)
|
|
|
|
|
ft.update(parent, vmax, x, d, r|eq)
|
|
|
|
|
} else {
|
|
|
|
|
// We know that either x>min OR x<=max. factsTable cannot record OR conditions,
|
|
|
|
|
// so let's see if we can already prove that one of them is false, in which case
|
|
|
|
|
// the other must be true
|
|
|
|
|
if l, has := ft.limits[x.ID]; has {
|
|
|
|
|
if l.max <= min {
|
2019-01-02 12:27:55 -05:00
|
|
|
if r&eq == 0 || l.max < min {
|
|
|
|
|
// x>min (x>=min) is impossible, so it must be x<=max
|
|
|
|
|
ft.update(parent, vmax, x, d, r|eq)
|
|
|
|
|
}
|
2018-04-15 16:52:49 +02:00
|
|
|
} else if l.min > max {
|
|
|
|
|
// x<=max is impossible, so it must be x>min
|
|
|
|
|
ft.update(parent, x, vmin, d, r)
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: handle sign/zero extensions in prove, via update method
Array accesses with index types smaller than the machine word size may
involve a sign or zero extension of the index value before bounds
checking. Currently, this defeats prove because the facts about the
original index value don't flow through the sign/zero extension.
This CL fixes this by looking back through value-preserving sign/zero
extensions when adding facts via Update and, where appropriate, applying
the same facts using the pre-extension value. This fix is enhanced by
also looking back through value-preserving extensions within
ft.isNonNegative to infer whether the extended value is known to be
non-negative. Without this additional isNonNegative enhancement, this
logic is rendered significantly less effective by the limitation
discussed in the next paragraph.
In Update, the application of facts to pre-extension values is limited
to cases where the domain of the new fact is consistent with the type of
the pre-extension value. There may be cases where this cross-domain
passing of facts is valid, but distinguishing them from the invalid
cases is difficult for me to reason about and to implement.
Assessing which cases to allow requires details about the context and
inferences behind the fact being applied which are not available
within Update. Additional difficulty arises from the fact that the SSA
does not curently differentiate extensions added by the compiler for
indexing operations, extensions added by the compiler for implicit
conversions, or explicit extensions from the source.
Examples of some cases that would need to be filtered correctly for
cross-domain facts:
(1) A uint8 is zero-extended to int for indexing (a value-preserving
zeroExt). When, if ever, can signed domain facts learned about the int be
applied to the uint8?
(2) An int8 is sign-extended to int16 (value-preserving) for an equality
comparison. Equality comparison facts are currently always learned in both
the signed and unsigned domains. When, if ever, can the unsigned facts
learned about the int16, from the int16 != int16 comparison, be applied
to the original int8?
This is an alternative to CL 122695 and CL 174309. Compared to CL 122695,
this CL differs in that the facts added about the pre-extension value will
pass through the Update method, where additional inferences are processed
(e.g. fence-post implications, see #29964). CL 174309 is limited to bounds
checks, so is narrower in application, and makes the code harder to read.
Fixes #26292.
Fixes #29964.
Fixes #15074
Removes 238 bounds checks from std/cmd.
Change-Id: I1f87c32ee672bfb8be397b27eab7a4c2f304893f
Reviewed-on: https://go-review.googlesource.com/c/go/+/174704
Run-TryBot: Zach Jones <zachj1@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Giovanni Bajo <rasky@develer.com>
2019-04-09 23:19:43 +01:00
|
|
|
// Look through value-preserving extensions.
|
|
|
|
|
// If the domain is appropriate for the pre-extension Type,
|
|
|
|
|
// repeat the update with the pre-extension Value.
|
|
|
|
|
if isCleanExt(v) {
|
|
|
|
|
switch {
|
|
|
|
|
case d == signed && v.Args[0].Type.IsSigned():
|
|
|
|
|
fallthrough
|
|
|
|
|
case d == unsigned && !v.Args[0].Type.IsSigned():
|
|
|
|
|
ft.update(parent, v.Args[0], w, d, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if isCleanExt(w) {
|
|
|
|
|
switch {
|
|
|
|
|
case d == signed && w.Args[0].Type.IsSigned():
|
|
|
|
|
fallthrough
|
|
|
|
|
case d == unsigned && !w.Args[0].Type.IsSigned():
|
|
|
|
|
ft.update(parent, v, w.Args[0], d, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-11 16:32:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var opMin = map[Op]int64{
|
|
|
|
|
OpAdd64: math.MinInt64, OpSub64: math.MinInt64,
|
|
|
|
|
OpAdd32: math.MinInt32, OpSub32: math.MinInt32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var opMax = map[Op]int64{
|
|
|
|
|
OpAdd64: math.MaxInt64, OpSub64: math.MaxInt64,
|
|
|
|
|
OpAdd32: math.MaxInt32, OpSub32: math.MaxInt32,
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
|
|
|
|
|
2018-08-31 02:15:26 +02:00
|
|
|
var opUMax = map[Op]uint64{
|
|
|
|
|
OpAdd64: math.MaxUint64, OpSub64: math.MaxUint64,
|
|
|
|
|
OpAdd32: math.MaxUint32, OpSub32: math.MaxUint32,
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-25 11:52:06 -07:00
|
|
|
// isNonNegative reports whether v is known to be non-negative.
|
2016-04-02 10:29:11 +02:00
|
|
|
func (ft *factsTable) isNonNegative(v *Value) bool {
|
|
|
|
|
if isNonNegative(v) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
|
2019-06-11 19:52:58 -07:00
|
|
|
var max int64
|
|
|
|
|
switch v.Type.Size() {
|
|
|
|
|
case 1:
|
|
|
|
|
max = math.MaxInt8
|
|
|
|
|
case 2:
|
|
|
|
|
max = math.MaxInt16
|
|
|
|
|
case 4:
|
|
|
|
|
max = math.MaxInt32
|
|
|
|
|
case 8:
|
|
|
|
|
max = math.MaxInt64
|
|
|
|
|
default:
|
|
|
|
|
panic("unexpected integer size")
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-02 01:57:49 +02:00
|
|
|
// Check if the recorded limits can prove that the value is positive
|
2019-09-20 00:12:15 +02:00
|
|
|
|
2019-06-11 19:52:58 -07:00
|
|
|
if l, has := ft.limits[v.ID]; has && (l.min >= 0 || l.umax <= uint64(max)) {
|
2018-04-02 01:57:49 +02:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if v = x+delta, and we can use x's limits to prove that it's positive
|
|
|
|
|
if x, delta := isConstDelta(v); x != nil {
|
|
|
|
|
if l, has := ft.limits[x.ID]; has {
|
2019-06-11 19:52:58 -07:00
|
|
|
if delta > 0 && l.min >= -delta && l.max <= max-delta {
|
2018-04-02 01:57:49 +02:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if delta < 0 && l.min >= -delta {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: handle sign/zero extensions in prove, via update method
Array accesses with index types smaller than the machine word size may
involve a sign or zero extension of the index value before bounds
checking. Currently, this defeats prove because the facts about the
original index value don't flow through the sign/zero extension.
This CL fixes this by looking back through value-preserving sign/zero
extensions when adding facts via Update and, where appropriate, applying
the same facts using the pre-extension value. This fix is enhanced by
also looking back through value-preserving extensions within
ft.isNonNegative to infer whether the extended value is known to be
non-negative. Without this additional isNonNegative enhancement, this
logic is rendered significantly less effective by the limitation
discussed in the next paragraph.
In Update, the application of facts to pre-extension values is limited
to cases where the domain of the new fact is consistent with the type of
the pre-extension value. There may be cases where this cross-domain
passing of facts is valid, but distinguishing them from the invalid
cases is difficult for me to reason about and to implement.
Assessing which cases to allow requires details about the context and
inferences behind the fact being applied which are not available
within Update. Additional difficulty arises from the fact that the SSA
does not curently differentiate extensions added by the compiler for
indexing operations, extensions added by the compiler for implicit
conversions, or explicit extensions from the source.
Examples of some cases that would need to be filtered correctly for
cross-domain facts:
(1) A uint8 is zero-extended to int for indexing (a value-preserving
zeroExt). When, if ever, can signed domain facts learned about the int be
applied to the uint8?
(2) An int8 is sign-extended to int16 (value-preserving) for an equality
comparison. Equality comparison facts are currently always learned in both
the signed and unsigned domains. When, if ever, can the unsigned facts
learned about the int16, from the int16 != int16 comparison, be applied
to the original int8?
This is an alternative to CL 122695 and CL 174309. Compared to CL 122695,
this CL differs in that the facts added about the pre-extension value will
pass through the Update method, where additional inferences are processed
(e.g. fence-post implications, see #29964). CL 174309 is limited to bounds
checks, so is narrower in application, and makes the code harder to read.
Fixes #26292.
Fixes #29964.
Fixes #15074
Removes 238 bounds checks from std/cmd.
Change-Id: I1f87c32ee672bfb8be397b27eab7a4c2f304893f
Reviewed-on: https://go-review.googlesource.com/c/go/+/174704
Run-TryBot: Zach Jones <zachj1@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Giovanni Bajo <rasky@develer.com>
2019-04-09 23:19:43 +01:00
|
|
|
// Check if v is a value-preserving extension of a non-negative value.
|
|
|
|
|
if isCleanExt(v) && ft.isNonNegative(v.Args[0]) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 19:49:15 +00:00
|
|
|
// Check if the signed poset can prove that the value is >= 0
|
2019-09-20 00:12:15 +02:00
|
|
|
return ft.orderS.OrderedOrEqual(ft.zero, v)
|
2016-04-02 10:29:11 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
// checkpoint saves the current state of known relations.
|
|
|
|
|
// Called when descending on a branch.
|
|
|
|
|
func (ft *factsTable) checkpoint() {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
if ft.unsat {
|
|
|
|
|
ft.unsatDepth++
|
|
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
ft.stack = append(ft.stack, checkpointFact)
|
2016-03-23 10:20:44 -07:00
|
|
|
ft.limitStack = append(ft.limitStack, checkpointBound)
|
2019-09-20 00:12:15 +02:00
|
|
|
ft.orderS.Checkpoint()
|
|
|
|
|
ft.orderU.Checkpoint()
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// restore restores known relation to the state just
|
|
|
|
|
// before the previous checkpoint.
|
|
|
|
|
// Called when backing up on a branch.
|
|
|
|
|
func (ft *factsTable) restore() {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
if ft.unsatDepth > 0 {
|
|
|
|
|
ft.unsatDepth--
|
|
|
|
|
} else {
|
|
|
|
|
ft.unsat = false
|
|
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-23 10:20:44 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-20 00:12:15 +02:00
|
|
|
ft.orderS.Undo()
|
|
|
|
|
ft.orderU.Undo()
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
2016-03-07 18:36:16 +01:00
|
|
|
reverseBits = [...]relation{0, 4, 2, 6, 1, 5, 3, 7}
|
2016-02-19 12:14:42 +01:00
|
|
|
|
|
|
|
|
// maps what we learn when the positive branch is taken.
|
|
|
|
|
// For example:
|
|
|
|
|
// OpLess8: {signed, lt},
|
|
|
|
|
// v1 = (OpLess8 v2 v3).
|
2018-07-24 23:59:48 +00:00
|
|
|
// If v1 branch is taken then we learn that the rangeMask
|
2016-02-19 12:14:42 +01:00
|
|
|
// can be at most lt.
|
2016-03-07 18:36:16 +01:00
|
|
|
domainRelationTable = map[Op]struct {
|
|
|
|
|
d domain
|
|
|
|
|
r relation
|
|
|
|
|
}{
|
2016-02-19 12:14:42 +01:00
|
|
|
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},
|
|
|
|
|
|
2018-04-03 18:58:01 +02:00
|
|
|
// For these ops, the negative branch is different: we can only
|
|
|
|
|
// prove signed/GE (signed/GT) if we can prove that arg0 is non-negative.
|
|
|
|
|
// See the special case in addBranchRestrictions.
|
|
|
|
|
OpIsInBounds: {signed | unsigned, lt}, // 0 <= arg0 < arg1
|
|
|
|
|
OpIsSliceInBounds: {signed | unsigned, lt | eq}, // 0 <= arg0 <= arg1
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2016-11-27 11:43:08 -08:00
|
|
|
// prove removes redundant BlockIf branches that can be inferred
|
|
|
|
|
// from previous dominating comparisons.
|
2016-02-19 12:14:42 +01:00
|
|
|
//
|
2016-03-07 18:36:16 +01:00
|
|
|
// By far, the most common redundant pair are generated by bounds checking.
|
2016-02-19 12:14:42 +01:00
|
|
|
// 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.
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
//
|
|
|
|
|
// 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.
|
2016-02-19 12:14:42 +01:00
|
|
|
func prove(f *Func) {
|
2018-05-01 00:57:57 +02:00
|
|
|
ft := newFactsTable(f)
|
|
|
|
|
ft.checkpoint()
|
2016-11-27 11:43:08 -08:00
|
|
|
|
2019-09-22 15:39:16 +02:00
|
|
|
var lensVars map[*Block][]*Value
|
|
|
|
|
|
2016-11-27 11:43:08 -08:00
|
|
|
// 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 {
|
2018-04-15 16:52:49 +02:00
|
|
|
case OpStringLen:
|
2019-02-06 19:49:15 +00:00
|
|
|
ft.update(b, v, ft.zero, signed, gt|eq)
|
2016-11-27 11:43:08 -08:00
|
|
|
case OpSliceLen:
|
|
|
|
|
if ft.lens == nil {
|
|
|
|
|
ft.lens = map[ID]*Value{}
|
|
|
|
|
}
|
2020-11-28 02:46:00 +07:00
|
|
|
// Set all len Values for the same slice as equal in the poset.
|
|
|
|
|
// The poset handles transitive relations, so Values related to
|
|
|
|
|
// any OpSliceLen for this slice will be correctly related to others.
|
|
|
|
|
if l, ok := ft.lens[v.Args[0].ID]; ok {
|
|
|
|
|
ft.update(b, v, l, signed, eq)
|
|
|
|
|
} else {
|
|
|
|
|
ft.lens[v.Args[0].ID] = v
|
|
|
|
|
}
|
2019-02-06 19:49:15 +00:00
|
|
|
ft.update(b, v, ft.zero, signed, gt|eq)
|
2019-09-22 15:39:16 +02:00
|
|
|
if v.Args[0].Op == OpSliceMake {
|
|
|
|
|
if lensVars == nil {
|
|
|
|
|
lensVars = make(map[*Block][]*Value)
|
|
|
|
|
}
|
|
|
|
|
lensVars[b] = append(lensVars[b], v)
|
|
|
|
|
}
|
2016-11-27 11:43:08 -08:00
|
|
|
case OpSliceCap:
|
|
|
|
|
if ft.caps == nil {
|
|
|
|
|
ft.caps = map[ID]*Value{}
|
|
|
|
|
}
|
2020-11-28 02:46:00 +07:00
|
|
|
// Same as case OpSliceLen above, but for slice cap.
|
|
|
|
|
if c, ok := ft.caps[v.Args[0].ID]; ok {
|
|
|
|
|
ft.update(b, v, c, signed, eq)
|
|
|
|
|
} else {
|
|
|
|
|
ft.caps[v.Args[0].ID] = v
|
|
|
|
|
}
|
2019-02-06 19:49:15 +00:00
|
|
|
ft.update(b, v, ft.zero, signed, gt|eq)
|
2019-09-22 15:39:16 +02:00
|
|
|
if v.Args[0].Op == OpSliceMake {
|
|
|
|
|
if lensVars == nil {
|
|
|
|
|
lensVars = make(map[*Block][]*Value)
|
|
|
|
|
}
|
|
|
|
|
lensVars[b] = append(lensVars[b], v)
|
|
|
|
|
}
|
2016-11-27 11:43:08 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-02 01:57:49 +02:00
|
|
|
// Find induction variables. Currently, findIndVars
|
|
|
|
|
// is limited to one induction variable per block.
|
|
|
|
|
var indVars map[*Block]indVar
|
|
|
|
|
for _, v := range findIndVar(f) {
|
|
|
|
|
if indVars == nil {
|
|
|
|
|
indVars = make(map[*Block]indVar)
|
|
|
|
|
}
|
|
|
|
|
indVars[v.entry] = v
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-19 12:14:42 +01:00
|
|
|
// current node state
|
|
|
|
|
type walkState int
|
|
|
|
|
const (
|
|
|
|
|
descend walkState = iota
|
|
|
|
|
simplify
|
|
|
|
|
)
|
|
|
|
|
// work maintains the DFS stack.
|
|
|
|
|
type bp struct {
|
2016-03-07 18:36:16 +01:00
|
|
|
block *Block // current handled block
|
|
|
|
|
state walkState // what's to do
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
work := make([]bp, 0, 256)
|
|
|
|
|
work = append(work, bp{
|
|
|
|
|
block: f.Entry,
|
|
|
|
|
state: descend,
|
|
|
|
|
})
|
|
|
|
|
|
2016-10-09 01:09:52 +09:00
|
|
|
idom := f.Idom()
|
2019-11-01 14:04:08 -07:00
|
|
|
sdom := f.Sdom()
|
2016-02-19 12:14:42 +01:00
|
|
|
|
|
|
|
|
// DFS on the dominator tree.
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
//
|
|
|
|
|
// 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.
|
2016-02-19 12:14:42 +01:00
|
|
|
for len(work) > 0 {
|
|
|
|
|
node := work[len(work)-1]
|
|
|
|
|
work = work[:len(work)-1]
|
2016-09-16 13:50:18 -07:00
|
|
|
parent := idom[node.block.ID]
|
|
|
|
|
branch := getBranch(sdom, parent, node.block)
|
2016-02-19 12:14:42 +01:00
|
|
|
|
|
|
|
|
switch node.state {
|
|
|
|
|
case descend:
|
2018-04-02 01:39:03 +02:00
|
|
|
ft.checkpoint()
|
2019-09-22 15:39:16 +02:00
|
|
|
|
|
|
|
|
// Entering the block, add the block-depending facts that we collected
|
|
|
|
|
// at the beginning: induction variables and lens/caps of slices.
|
2018-04-02 01:57:49 +02:00
|
|
|
if iv, ok := indVars[node.block]; ok {
|
|
|
|
|
addIndVarRestrictions(ft, parent, iv)
|
|
|
|
|
}
|
2019-09-22 15:39:16 +02:00
|
|
|
if lens, ok := lensVars[node.block]; ok {
|
|
|
|
|
for _, v := range lens {
|
|
|
|
|
switch v.Op {
|
|
|
|
|
case OpSliceLen:
|
|
|
|
|
ft.update(node.block, v, v.Args[0].Args[1], signed, eq)
|
|
|
|
|
case OpSliceCap:
|
|
|
|
|
ft.update(node.block, v, v.Args[0].Args[2], signed, eq)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
if branch != unknown {
|
2018-04-02 01:39:03 +02:00
|
|
|
addBranchRestrictions(ft, parent, branch)
|
|
|
|
|
if ft.unsat {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// node.block is unreachable.
|
|
|
|
|
// Remove it and don't visit
|
|
|
|
|
// its children.
|
|
|
|
|
removeBranch(parent, branch)
|
2018-04-02 01:39:03 +02:00
|
|
|
ft.restore()
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
break
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// Otherwise, we can now commit to
|
|
|
|
|
// taking this branch. We'll restore
|
|
|
|
|
// ft when we unwind.
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
2016-02-19 12:14:42 +01:00
|
|
|
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
// Add inductive facts for phis in this block.
|
|
|
|
|
addLocalInductiveFacts(ft, node.block)
|
|
|
|
|
|
2016-02-19 12:14:42 +01:00
|
|
|
work = append(work, bp{
|
|
|
|
|
block: node.block,
|
|
|
|
|
state: simplify,
|
|
|
|
|
})
|
2016-09-16 13:50:18 -07:00
|
|
|
for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) {
|
2016-02-19 12:14:42 +01:00
|
|
|
work = append(work, bp{
|
|
|
|
|
block: s,
|
|
|
|
|
state: descend,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case simplify:
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
simplifyBlock(sdom, ft, node.block)
|
2018-04-02 01:39:03 +02:00
|
|
|
ft.restore()
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-01 00:57:57 +02:00
|
|
|
|
|
|
|
|
ft.restore()
|
|
|
|
|
|
|
|
|
|
// Return the posets to the free list
|
2019-09-20 00:12:15 +02:00
|
|
|
for _, po := range []*poset{ft.orderS, ft.orderU} {
|
2018-05-01 00:57:57 +02:00
|
|
|
// Make sure it's empty as it should be. A non-empty poset
|
|
|
|
|
// might cause errors and miscompilations if reused.
|
|
|
|
|
if checkEnabled {
|
|
|
|
|
if err := po.CheckEmpty(); err != nil {
|
|
|
|
|
f.Fatalf("prove poset not empty after function %s: %v", f.Name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
f.retPoset(po)
|
|
|
|
|
}
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
// getBranch returns the range restrictions added by p
|
|
|
|
|
// when reaching b. p is the immediate dominator of b.
|
cmd/compile: use sparse algorithm for phis in large program
This adds a sparse method for locating nearest ancestors
in a dominator tree, and checks blocks with more than one
predecessor for differences and inserts phi functions where
there are.
Uses reversed post order to cut number of passes, running
it from first def to last use ("last use" for paramout and
mem is end-of-program; last use for a phi input from a
backedge is the source of the back edge)
Includes a cutover from old algorithm to new to avoid paying
large constant factor for small programs. This keeps normal
builds running at about the same time, while not running
over-long on large machine-generated inputs.
Add "phase" flags for ssa/build -- ssa/build/stats prints
number of blocks, values (before and after linking references
and inserting phis, so expansion can be measured), and their
product; the product governs the cutover, where a good value
seems to be somewhere between 1 and 5 million.
Among the files compiled by make.bash, this is the shape of
the tail of the distribution for #blocks, #vars, and their
product:
#blocks #vars product
max 6171 28180 173,898,780
99.9% 1641 6548 10,401,878
99% 463 1909 873,721
95% 152 639 95,235
90% 84 359 30,021
The old algorithm is indeed usually fastest, for 99%ile
values of usually.
The fix to LookupVarOutgoing
( https://go-review.googlesource.com/#/c/22790/ )
deals with some of the same problems addressed by this CL,
but on at least one bug ( #15537 ) this change is still
a significant help.
With this CL:
/tmp/gopath$ rm -rf pkg bin
/tmp/gopath$ time go get -v -gcflags -memprofile=y.mprof \
github.com/gogo/protobuf/test/theproto3/combos/...
...
real 4m35.200s
user 13m16.644s
sys 0m36.712s
and pprof reports 3.4GB allocated in one of the larger profiles
With tip:
/tmp/gopath$ rm -rf pkg bin
/tmp/gopath$ time go get -v -gcflags -memprofile=y.mprof \
github.com/gogo/protobuf/test/theproto3/combos/...
...
real 10m36.569s
user 25m52.286s
sys 4m3.696s
and pprof reports 8.3GB allocated in the same larger profile
With this CL, most of the compilation time on the benchmarked
input is spent in register/stack allocation (cumulative 53%)
and in the sparse lookup algorithm itself (cumulative 20%).
Fixes #15537.
Change-Id: Ia0299dda6a291534d8b08e5f9883216ded677a00
Reviewed-on: https://go-review.googlesource.com/22342
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-04-21 13:24:58 -04:00
|
|
|
func getBranch(sdom SparseTree, p *Block, b *Block) branch {
|
2016-02-19 12:14:42 +01:00
|
|
|
if p == nil || p.Kind != BlockIf {
|
2016-03-07 18:36:16 +01:00
|
|
|
return unknown
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
// 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.
|
2019-11-01 14:04:08 -07:00
|
|
|
if sdom.IsAncestorEq(p.Succs[0].b, b) && len(p.Succs[0].b.Preds) == 1 {
|
2016-03-07 18:36:16 +01:00
|
|
|
return positive
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
2019-11-01 14:04:08 -07:00
|
|
|
if sdom.IsAncestorEq(p.Succs[1].b, b) && len(p.Succs[1].b.Preds) == 1 {
|
2016-03-07 18:36:16 +01:00
|
|
|
return negative
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
return unknown
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
2018-04-02 01:57:49 +02:00
|
|
|
// addIndVarRestrictions updates the factsTables ft with the facts
|
|
|
|
|
// learned from the induction variable indVar which drives the loop
|
|
|
|
|
// starting in Block b.
|
|
|
|
|
func addIndVarRestrictions(ft *factsTable, b *Block, iv indVar) {
|
|
|
|
|
d := signed
|
2019-02-06 19:49:15 +00:00
|
|
|
if ft.isNonNegative(iv.min) && ft.isNonNegative(iv.max) {
|
2018-04-02 01:57:49 +02:00
|
|
|
d |= unsigned
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-15 16:03:30 +02:00
|
|
|
if iv.flags&indVarMinExc == 0 {
|
|
|
|
|
addRestrictions(b, ft, d, iv.min, iv.ind, lt|eq)
|
|
|
|
|
} else {
|
|
|
|
|
addRestrictions(b, ft, d, iv.min, iv.ind, lt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if iv.flags&indVarMaxInc == 0 {
|
|
|
|
|
addRestrictions(b, ft, d, iv.ind, iv.max, lt)
|
|
|
|
|
} else {
|
|
|
|
|
addRestrictions(b, ft, d, iv.ind, iv.max, lt|eq)
|
|
|
|
|
}
|
2018-04-02 01:57:49 +02:00
|
|
|
}
|
|
|
|
|
|
2018-04-02 01:39:03 +02:00
|
|
|
// addBranchRestrictions updates the factsTables ft with the facts learned when
|
|
|
|
|
// branching from Block b in direction br.
|
|
|
|
|
func addBranchRestrictions(ft *factsTable, b *Block, br branch) {
|
2019-08-12 20:19:58 +01:00
|
|
|
c := b.Controls[0]
|
2018-04-02 01:45:53 +02:00
|
|
|
switch br {
|
|
|
|
|
case negative:
|
|
|
|
|
addRestrictions(b, ft, boolean, nil, c, eq)
|
|
|
|
|
case positive:
|
|
|
|
|
addRestrictions(b, ft, boolean, nil, c, lt|gt)
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown branch")
|
|
|
|
|
}
|
2019-08-12 20:19:58 +01:00
|
|
|
if tr, has := domainRelationTable[c.Op]; has {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// When we branched from parent we learned a new set of
|
|
|
|
|
// restrictions. Update the factsTable accordingly.
|
2018-04-03 18:58:01 +02:00
|
|
|
d := tr.d
|
2018-04-03 18:59:44 +02:00
|
|
|
if d == signed && ft.isNonNegative(c.Args[0]) && ft.isNonNegative(c.Args[1]) {
|
|
|
|
|
d |= unsigned
|
|
|
|
|
}
|
2019-08-12 20:19:58 +01:00
|
|
|
switch c.Op {
|
2019-03-30 17:28:05 +00:00
|
|
|
case OpIsInBounds, OpIsSliceInBounds:
|
|
|
|
|
// 0 <= a0 < a1 (or 0 <= a0 <= a1)
|
|
|
|
|
//
|
|
|
|
|
// On the positive branch, we learn:
|
|
|
|
|
// signed: 0 <= a0 < a1 (or 0 <= a0 <= a1)
|
|
|
|
|
// unsigned: a0 < a1 (or a0 <= a1)
|
|
|
|
|
//
|
|
|
|
|
// On the negative branch, we learn (0 > a0 ||
|
|
|
|
|
// a0 >= a1). In the unsigned domain, this is
|
|
|
|
|
// simply a0 >= a1 (which is the reverse of the
|
|
|
|
|
// positive branch, so nothing surprising).
|
|
|
|
|
// But in the signed domain, we can't express the ||
|
|
|
|
|
// condition, so check if a0 is non-negative instead,
|
|
|
|
|
// to be able to learn something.
|
|
|
|
|
switch br {
|
|
|
|
|
case negative:
|
2018-04-03 18:58:01 +02:00
|
|
|
d = unsigned
|
|
|
|
|
if ft.isNonNegative(c.Args[0]) {
|
|
|
|
|
d |= signed
|
|
|
|
|
}
|
2019-03-30 17:28:05 +00:00
|
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r^(lt|gt|eq))
|
|
|
|
|
case positive:
|
|
|
|
|
addRestrictions(b, ft, signed, ft.zero, c.Args[0], lt|eq)
|
|
|
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
switch br {
|
|
|
|
|
case negative:
|
|
|
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r^(lt|gt|eq))
|
|
|
|
|
case positive:
|
|
|
|
|
addRestrictions(b, ft, d, c.Args[0], c.Args[1], tr.r)
|
2018-04-03 18:58:01 +02:00
|
|
|
}
|
2018-04-02 01:45:53 +02:00
|
|
|
}
|
2019-03-30 17:28:05 +00:00
|
|
|
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-02 01:39:03 +02:00
|
|
|
// addRestrictions updates restrictions from the immediate
|
2018-04-02 01:45:53 +02:00
|
|
|
// dominating block (p) using r.
|
|
|
|
|
func addRestrictions(parent *Block, ft *factsTable, t domain, v, w *Value, r relation) {
|
|
|
|
|
if t == 0 {
|
|
|
|
|
// Trivial case: nothing to do.
|
2016-03-07 18:36:16 +01:00
|
|
|
// Shoult not happen, but just in case.
|
2016-02-19 12:14:42 +01:00
|
|
|
return
|
|
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
for i := domain(1); i <= t; i <<= 1 {
|
2016-11-27 11:43:08 -08:00
|
|
|
if t&i == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ft.update(parent, v, w, i, r)
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
// addLocalInductiveFacts adds inductive facts when visiting b, where
|
|
|
|
|
// b is a join point in a loop. In contrast with findIndVar, this
|
|
|
|
|
// depends on facts established for b, which is why it happens when
|
|
|
|
|
// visiting b. addLocalInductiveFacts specifically targets the pattern
|
|
|
|
|
// created by OFORUNTIL, which isn't detected by findIndVar.
|
|
|
|
|
//
|
|
|
|
|
// TODO: It would be nice to combine this with findIndVar.
|
|
|
|
|
func addLocalInductiveFacts(ft *factsTable, b *Block) {
|
|
|
|
|
// This looks for a specific pattern of induction:
|
|
|
|
|
//
|
|
|
|
|
// 1. i1 = OpPhi(min, i2) in b
|
|
|
|
|
// 2. i2 = i1 + 1
|
|
|
|
|
// 3. i2 < max at exit from b.Preds[1]
|
|
|
|
|
// 4. min < max
|
|
|
|
|
//
|
|
|
|
|
// If all of these conditions are true, then i1 < max and i1 >= min.
|
|
|
|
|
|
2020-07-24 11:00:36 +08:00
|
|
|
// To ensure this is a loop header node.
|
|
|
|
|
if len(b.Preds) != 2 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
for _, i1 := range b.Values {
|
|
|
|
|
if i1.Op != OpPhi {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for conditions 1 and 2. This is easy to do
|
|
|
|
|
// and will throw out most phis.
|
|
|
|
|
min, i2 := i1.Args[0], i1.Args[1]
|
|
|
|
|
if i1q, delta := isConstDelta(i2); i1q != i1 || delta != 1 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to prove condition 3. We can't just query the
|
|
|
|
|
// fact table for this because we don't know what the
|
|
|
|
|
// facts of b.Preds[1] are (in general, b.Preds[1] is
|
|
|
|
|
// a loop-back edge, so we haven't even been there
|
|
|
|
|
// yet). As a conservative approximation, we look for
|
|
|
|
|
// this condition in the predecessor chain until we
|
|
|
|
|
// hit a join point.
|
|
|
|
|
uniquePred := func(b *Block) *Block {
|
|
|
|
|
if len(b.Preds) == 1 {
|
|
|
|
|
return b.Preds[0].b
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
pred, child := b.Preds[1].b, b
|
2020-07-31 13:57:48 +08:00
|
|
|
for ; pred != nil; pred, child = uniquePred(pred), pred {
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
if pred.Kind != BlockIf {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-08-12 20:19:58 +01:00
|
|
|
control := pred.Controls[0]
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
|
|
|
|
|
br := unknown
|
|
|
|
|
if pred.Succs[0].b == child {
|
|
|
|
|
br = positive
|
|
|
|
|
}
|
|
|
|
|
if pred.Succs[1].b == child {
|
|
|
|
|
if br != unknown {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
br = negative
|
|
|
|
|
}
|
2020-07-24 11:00:36 +08:00
|
|
|
if br == unknown {
|
|
|
|
|
continue
|
|
|
|
|
}
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
|
2019-08-12 20:19:58 +01:00
|
|
|
tr, has := domainRelationTable[control.Op]
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
if !has {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
r := tr.r
|
|
|
|
|
if br == negative {
|
|
|
|
|
// Negative branch taken to reach b.
|
|
|
|
|
// Complement the relations.
|
|
|
|
|
r = (lt | eq | gt) ^ r
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for i2 < max or max > i2.
|
|
|
|
|
var max *Value
|
2019-08-12 20:19:58 +01:00
|
|
|
if r == lt && control.Args[0] == i2 {
|
|
|
|
|
max = control.Args[1]
|
|
|
|
|
} else if r == gt && control.Args[1] == i2 {
|
|
|
|
|
max = control.Args[0]
|
cmd/compile: detect OFORUNTIL inductive facts in prove
Currently, we compile range loops into for loops with the obvious
initialization and update of the index variable. In this form, the
prove pass can see that the body is dominated by an i < len condition,
and findIndVar can detect that i is an induction variable and that
0 <= i < len.
GOEXPERIMENT=preemptibleloops compiles range loops to OFORUNTIL and
we're preparing to unconditionally switch to a variation of this for
#24543. OFORUNTIL moves the increment and condition *after* the body,
which makes the bounds on the index variable much less obvious. With
OFORUNTIL, proving anything about the index variable requires
understanding the phi that joins the index values at the top of the
loop body block.
This interferes with both prove's ability to see that i < len (this is
true on both paths that enter the body, but from two different
conditional checks) and with findIndVar's ability to detect the
induction pattern.
Fix this by teaching prove to detect that the index in the pattern
constructed by OFORUNTIL is an induction variable and add both bounds
to the facts table. Currently this is done separately from findIndVar
because it depends on prove's factsTable, while findIndVar runs before
visiting blocks and building the factsTable.
Without any GOEXPERIMENT, this has no effect on std or cmd. However,
with GOEXPERIMENT=preemptibleloops, this change becomes necessary to
prove 90 conditions in std and cmd.
Change-Id: Ic025d669f81b53426309da5a6e8010e5ccaf4f49
Reviewed-on: https://go-review.googlesource.com/102603
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2018-03-23 17:38:55 -04:00
|
|
|
} else {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check condition 4 now that we have a
|
|
|
|
|
// candidate max. For this we can query the
|
|
|
|
|
// fact table. We "prove" min < max by showing
|
|
|
|
|
// that min >= max is unsat. (This may simply
|
|
|
|
|
// compare two constants; that's fine.)
|
|
|
|
|
ft.checkpoint()
|
|
|
|
|
ft.update(b, min, max, tr.d, gt|eq)
|
|
|
|
|
proved := ft.unsat
|
|
|
|
|
ft.restore()
|
|
|
|
|
|
|
|
|
|
if proved {
|
|
|
|
|
// We know that min <= i1 < max.
|
|
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
printIndVar(b, i1, min, max, 1, 0)
|
|
|
|
|
}
|
|
|
|
|
ft.update(b, min, i1, tr.d, lt|eq)
|
|
|
|
|
ft.update(b, i1, max, tr.d, lt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-25 11:52:06 -07:00
|
|
|
var ctzNonZeroOp = map[Op]Op{OpCtz8: OpCtz8NonZero, OpCtz16: OpCtz16NonZero, OpCtz32: OpCtz32NonZero, OpCtz64: OpCtz64NonZero}
|
2018-08-06 19:50:38 +10:00
|
|
|
var mostNegativeDividend = map[Op]int64{
|
|
|
|
|
OpDiv16: -1 << 15,
|
|
|
|
|
OpMod16: -1 << 15,
|
|
|
|
|
OpDiv32: -1 << 31,
|
|
|
|
|
OpMod32: -1 << 31,
|
|
|
|
|
OpDiv64: -1 << 63,
|
|
|
|
|
OpMod64: -1 << 63}
|
2018-04-25 11:52:06 -07:00
|
|
|
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// 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) {
|
2016-10-25 15:49:52 -07:00
|
|
|
for _, v := range b.Values {
|
2018-04-25 11:52:06 -07:00
|
|
|
switch v.Op {
|
|
|
|
|
case OpSlicemask:
|
|
|
|
|
// Replace OpSlicemask operations in b with constants where possible.
|
|
|
|
|
x, delta := isConstDelta(v.Args[0])
|
|
|
|
|
if x == nil {
|
|
|
|
|
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(-delta) {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
case OpCtz8, OpCtz16, OpCtz32, OpCtz64:
|
|
|
|
|
// On some architectures, notably amd64, we can generate much better
|
|
|
|
|
// code for CtzNN if we know that the argument is non-zero.
|
|
|
|
|
// Capture that information here for use in arch-specific optimizations.
|
|
|
|
|
x := v.Args[0]
|
|
|
|
|
lim, ok := ft.limits[x.ID]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
2016-10-25 15:49:52 -07:00
|
|
|
}
|
2018-04-25 11:52:06 -07:00
|
|
|
if lim.umin > 0 || lim.min > 0 || lim.max < 0 {
|
2018-04-26 15:46:12 -07:00
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
b.Func.Warnl(v.Pos, "Proved %v non-zero", v.Op)
|
|
|
|
|
}
|
2018-04-25 11:52:06 -07:00
|
|
|
v.Op = ctzNonZeroOp[v.Op]
|
2016-10-25 15:49:52 -07:00
|
|
|
}
|
2020-05-07 13:44:51 -07:00
|
|
|
case OpRsh8x8, OpRsh8x16, OpRsh8x32, OpRsh8x64,
|
|
|
|
|
OpRsh16x8, OpRsh16x16, OpRsh16x32, OpRsh16x64,
|
|
|
|
|
OpRsh32x8, OpRsh32x16, OpRsh32x32, OpRsh32x64,
|
|
|
|
|
OpRsh64x8, OpRsh64x16, OpRsh64x32, OpRsh64x64:
|
|
|
|
|
// Check whether, for a >> b, we know that a is non-negative
|
|
|
|
|
// and b is all of a's bits except the MSB. If so, a is shifted to zero.
|
|
|
|
|
bits := 8 * v.Type.Size()
|
|
|
|
|
if v.Args[1].isGenericIntConst() && v.Args[1].AuxInt >= bits-1 && ft.isNonNegative(v.Args[0]) {
|
|
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
b.Func.Warnl(v.Pos, "Proved %v shifts to zero", v.Op)
|
|
|
|
|
}
|
|
|
|
|
switch bits {
|
|
|
|
|
case 64:
|
|
|
|
|
v.reset(OpConst64)
|
|
|
|
|
case 32:
|
|
|
|
|
v.reset(OpConst32)
|
|
|
|
|
case 16:
|
|
|
|
|
v.reset(OpConst16)
|
|
|
|
|
case 8:
|
|
|
|
|
v.reset(OpConst8)
|
|
|
|
|
default:
|
|
|
|
|
panic("unexpected integer size")
|
|
|
|
|
}
|
|
|
|
|
v.AuxInt = 0
|
|
|
|
|
continue // Be sure not to fallthrough - this is no longer OpRsh.
|
|
|
|
|
}
|
|
|
|
|
// If the Rsh hasn't been replaced with 0, still check if it is bounded.
|
|
|
|
|
fallthrough
|
2018-04-26 20:56:03 -07:00
|
|
|
case OpLsh8x8, OpLsh8x16, OpLsh8x32, OpLsh8x64,
|
|
|
|
|
OpLsh16x8, OpLsh16x16, OpLsh16x32, OpLsh16x64,
|
|
|
|
|
OpLsh32x8, OpLsh32x16, OpLsh32x32, OpLsh32x64,
|
|
|
|
|
OpLsh64x8, OpLsh64x16, OpLsh64x32, OpLsh64x64,
|
|
|
|
|
OpRsh8Ux8, OpRsh8Ux16, OpRsh8Ux32, OpRsh8Ux64,
|
|
|
|
|
OpRsh16Ux8, OpRsh16Ux16, OpRsh16Ux32, OpRsh16Ux64,
|
|
|
|
|
OpRsh32Ux8, OpRsh32Ux16, OpRsh32Ux32, OpRsh32Ux64,
|
|
|
|
|
OpRsh64Ux8, OpRsh64Ux16, OpRsh64Ux32, OpRsh64Ux64:
|
|
|
|
|
// Check whether, for a << b, we know that b
|
|
|
|
|
// is strictly less than the number of bits in a.
|
|
|
|
|
by := v.Args[1]
|
|
|
|
|
lim, ok := ft.limits[by.ID]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
bits := 8 * v.Args[0].Type.Size()
|
|
|
|
|
if lim.umax < uint64(bits) || (lim.max < bits && ft.isNonNegative(by)) {
|
2018-04-29 17:40:47 -07:00
|
|
|
v.AuxInt = 1 // see shiftIsBounded
|
2018-04-26 20:56:03 -07:00
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
b.Func.Warnl(v.Pos, "Proved %v bounded", v.Op)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-06 19:50:38 +10:00
|
|
|
case OpDiv16, OpDiv32, OpDiv64, OpMod16, OpMod32, OpMod64:
|
|
|
|
|
// On amd64 and 386 fix-up code can be avoided if we know
|
|
|
|
|
// the divisor is not -1 or the dividend > MinIntNN.
|
2020-02-24 13:53:53 -08:00
|
|
|
// Don't modify AuxInt on other architectures,
|
|
|
|
|
// as that can interfere with CSE.
|
|
|
|
|
// TODO: add other architectures?
|
|
|
|
|
if b.Func.Config.arch != "386" && b.Func.Config.arch != "amd64" {
|
|
|
|
|
break
|
|
|
|
|
}
|
2018-08-06 19:50:38 +10:00
|
|
|
divr := v.Args[1]
|
|
|
|
|
divrLim, divrLimok := ft.limits[divr.ID]
|
|
|
|
|
divd := v.Args[0]
|
|
|
|
|
divdLim, divdLimok := ft.limits[divd.ID]
|
|
|
|
|
if (divrLimok && (divrLim.max < -1 || divrLim.min > -1)) ||
|
|
|
|
|
(divdLimok && divdLim.min > mostNegativeDividend[v.Op]) {
|
2020-01-23 22:18:30 -08:00
|
|
|
// See DivisionNeedsFixUp in rewrite.go.
|
|
|
|
|
// v.AuxInt = 1 means we have proved both that the divisor is not -1
|
|
|
|
|
// and that the dividend is not the most negative integer,
|
|
|
|
|
// so we do not need to add fix-up code.
|
|
|
|
|
v.AuxInt = 1
|
2018-08-06 19:50:38 +10:00
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
b.Func.Warnl(v.Pos, "Proved %v does not need fix-up", v.Op)
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-25 15:49:52 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-19 12:14:42 +01:00
|
|
|
if b.Kind != BlockIf {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
return
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// 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
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// For edges to other blocks, this can trim a branch
|
|
|
|
|
// even if we couldn't get rid of the child itself.
|
2018-04-02 01:39:03 +02:00
|
|
|
ft.checkpoint()
|
|
|
|
|
addBranchRestrictions(ft, parent, branch)
|
|
|
|
|
unsat := ft.unsat
|
|
|
|
|
ft.restore()
|
|
|
|
|
if unsat {
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
// 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
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
}
|
2016-02-19 12:14:42 +01:00
|
|
|
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
func removeBranch(b *Block, branch branch) {
|
2019-10-01 11:10:22 -04:00
|
|
|
c := b.Controls[0]
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
if b.Func.pass.debug > 0 {
|
|
|
|
|
verb := "Proved"
|
|
|
|
|
if branch == positive {
|
|
|
|
|
verb = "Disproved"
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
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)
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-10-01 11:10:22 -04:00
|
|
|
if c != nil && c.Pos.IsStmt() == src.PosIsStmt && c.Pos.SameFileAndLine(b.Pos) {
|
|
|
|
|
// attempt to preserve statement marker.
|
|
|
|
|
b.Pos = b.Pos.WithIsStmt()
|
|
|
|
|
}
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
b.Kind = BlockFirst
|
2019-08-12 20:19:58 +01:00
|
|
|
b.ResetControls()
|
cmd/compile: make prove pass use unsatisfiability
Currently the prove pass uses implication queries. For each block, it
collects the set of branch conditions leading to that block, and
queries this fact table for whether any of these facts imply the
block's own branch condition (or its inverse). This works remarkably
well considering it doesn't do any deduction on these facts, but it
has various downsides:
1. It requires an implementation both of adding facts to the table and
determining implications. These are very nearly duals of each
other, but require separate implementations. Likewise, the process
of asserting facts of dominating branch conditions is very nearly
the dual of the process of querying implied branch conditions.
2. It leads to less effective use of derived facts. For example, the
prove pass currently derives facts about the relations between len
and cap, but can't make use of these unless a branch condition is
in the exact form of a derived fact. If one of these derived facts
contradicts another fact, it won't notice or make use of this.
This CL changes the approach of the prove pass to instead use
*contradiction* instead of implication. Rather than ever querying a
branch condition, it simply adds branch conditions to the fact table.
If this leads to a contradiction (specifically, it makes the fact set
unsatisfiable), that branch is impossible and can be cut. As a result,
1. We can eliminate the code for determining implications
(factsTable.get disappears entirely). Also, there is now a single
implementation of visiting and asserting branch conditions, since
we don't have to flip them around to treat them as facts in one
place and queries in another.
2. Derived facts can be used effectively. It doesn't matter *why* the
fact table is unsatisfiable; a contradiction in any of the facts is
enough.
3. As an added benefit, it's now quite easy to avoid traversing beyond
provably-unreachable blocks. In contrast, the current
implementation always visits all blocks.
The prove pass already has nearly all of the mechanism necessary to
compute unsatisfiability, which means this both simplifies the code
and makes it more powerful.
The only complication is that the current implication procedure has a
hack for dealing with the 0 <= Args[0] condition of OpIsInBounds and
OpIsSliceInBounds. We replace this with asserting the appropriate fact
when we process one of these conditions. This seems much cleaner
anyway, and works because we can now take advantage of derived facts.
This has no measurable effect on compiler performance.
Effectiveness:
There is exactly one condition in all of std and cmd that this fails
to prove that the old implementation could: (int64(^uint(0)>>1) < x)
in encoding/gob. This can never be true because x is an int, and it's
basically coincidence that the old code gets this. (For example, it
fails to prove the similar (x < ^int64(^uint(0)>>1)) condition that
immediately precedes it, and even though the conditions are logically
unrelated, it wouldn't get the second one if it hadn't first processed
the first!)
It does, however, prove a few dozen additional branches. These come
from facts that are added to the fact table about the relations
between len and cap. These were almost never queried directly before,
but could lead to contradictions, which the unsat-based approach is
able to use.
There are exactly two branches in std and cmd that this implementation
proves in the *other* direction. This sounds scary, but is okay
because both occur in already-unreachable blocks, so it doesn't matter
what we chose. Because the fact table logic is sound but incomplete,
it fails to prove that the block isn't reachable, even though it is
able to prove that both outgoing branches are impossible. We could
turn these blocks into BlockExit blocks, but it doesn't seem worth the
trouble of the extra proof effort for something that happens twice in
all of std and cmd.
Tests:
This CL updates test/prove.go to change the expected messages because
it can no longer give a "reason" why it proved or disproved a
condition. It also adds a new test of a branch it couldn't prove
before.
It mostly guts test/sliceopt.go, removing everything related to slice
bounds optimizations and moving a few relevant tests to test/prove.go.
Much of this test is actually unreachable. The new prove pass figures
this out and doesn't try to prove anything about the unreachable
parts. The output on the unreachable parts is already suspect because
anything can be proved at that point, so it's really just a regression
test for an algorithm the compiler no longer uses.
This is a step toward fixing #23354. That issue is quite easy to fix
once we can use derived facts effectively.
Change-Id: Ia48a1b9ee081310579fe474e4a61857424ff8ce8
Reviewed-on: https://go-review.googlesource.com/87478
Reviewed-by: Keith Randall <khr@golang.org>
2018-01-10 16:28:58 -05:00
|
|
|
if branch == positive {
|
|
|
|
|
b.swapSuccessors()
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
}
|
|
|
|
|
|
2018-04-25 11:52:06 -07:00
|
|
|
// isNonNegative reports whether v is known to be greater or equal to zero.
|
2016-03-07 18:36:16 +01:00
|
|
|
func isNonNegative(v *Value) bool {
|
2019-12-20 13:42:39 -08:00
|
|
|
if !v.Type.IsInteger() {
|
2020-08-13 12:39:04 -07:00
|
|
|
v.Fatalf("isNonNegative bad type: %v", v.Type)
|
2019-12-20 13:42:39 -08:00
|
|
|
}
|
2020-03-09 06:37:49 -07:00
|
|
|
// TODO: return true if !v.Type.IsSigned()
|
|
|
|
|
// SSA isn't type-safe enough to do that now (issue 37753).
|
|
|
|
|
// The checks below depend only on the pattern of bits.
|
2019-12-20 13:42:39 -08:00
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
switch v.Op {
|
|
|
|
|
case OpConst64:
|
|
|
|
|
return v.AuxInt >= 0
|
|
|
|
|
|
2016-10-19 11:47:52 -04:00
|
|
|
case OpConst32:
|
|
|
|
|
return int32(v.AuxInt) >= 0
|
|
|
|
|
|
2019-12-20 13:42:39 -08:00
|
|
|
case OpConst16:
|
|
|
|
|
return int16(v.AuxInt) >= 0
|
|
|
|
|
|
|
|
|
|
case OpConst8:
|
|
|
|
|
return int8(v.AuxInt) >= 0
|
|
|
|
|
|
2016-03-07 18:36:16 +01:00
|
|
|
case OpStringLen, OpSliceLen, OpSliceCap,
|
2019-12-20 13:42:39 -08:00
|
|
|
OpZeroExt8to64, OpZeroExt16to64, OpZeroExt32to64,
|
|
|
|
|
OpZeroExt8to32, OpZeroExt16to32, OpZeroExt8to16,
|
|
|
|
|
OpCtz64, OpCtz32, OpCtz16, OpCtz8:
|
2016-03-07 18:36:16 +01:00
|
|
|
return true
|
|
|
|
|
|
2019-12-20 13:42:39 -08:00
|
|
|
case OpRsh64Ux64, OpRsh32Ux64:
|
2018-04-25 13:17:17 -07:00
|
|
|
by := v.Args[1]
|
|
|
|
|
return by.Op == OpConst64 && by.AuxInt > 0
|
|
|
|
|
|
2019-12-20 13:42:39 -08:00
|
|
|
case OpRsh64x64, OpRsh32x64, OpRsh8x64, OpRsh16x64, OpRsh32x32, OpRsh64x32,
|
|
|
|
|
OpSignExt32to64, OpSignExt16to64, OpSignExt8to64, OpSignExt16to32, OpSignExt8to32:
|
2016-03-07 18:36:16 +01:00
|
|
|
return isNonNegative(v.Args[0])
|
2019-12-20 13:42:39 -08:00
|
|
|
|
|
|
|
|
case OpAnd64, OpAnd32, OpAnd16, OpAnd8:
|
|
|
|
|
return isNonNegative(v.Args[0]) || isNonNegative(v.Args[1])
|
|
|
|
|
|
|
|
|
|
case OpMod64, OpMod32, OpMod16, OpMod8,
|
|
|
|
|
OpDiv64, OpDiv32, OpDiv16, OpDiv8,
|
|
|
|
|
OpOr64, OpOr32, OpOr16, OpOr8,
|
|
|
|
|
OpXor64, OpXor32, OpXor16, OpXor8:
|
|
|
|
|
return isNonNegative(v.Args[0]) && isNonNegative(v.Args[1])
|
|
|
|
|
|
|
|
|
|
// We could handle OpPhi here, but the improvements from doing
|
|
|
|
|
// so are very minor, and it is neither simple nor cheap.
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
2016-03-07 18:36:16 +01:00
|
|
|
return false
|
2016-02-19 12:14:42 +01:00
|
|
|
}
|
2018-01-11 16:32:02 -05:00
|
|
|
|
|
|
|
|
// isConstDelta returns non-nil if v is equivalent to w+delta (signed).
|
|
|
|
|
func isConstDelta(v *Value) (w *Value, delta int64) {
|
|
|
|
|
cop := OpConst64
|
|
|
|
|
switch v.Op {
|
|
|
|
|
case OpAdd32, OpSub32:
|
|
|
|
|
cop = OpConst32
|
|
|
|
|
}
|
|
|
|
|
switch v.Op {
|
|
|
|
|
case OpAdd64, OpAdd32:
|
|
|
|
|
if v.Args[0].Op == cop {
|
|
|
|
|
return v.Args[1], v.Args[0].AuxInt
|
|
|
|
|
}
|
|
|
|
|
if v.Args[1].Op == cop {
|
|
|
|
|
return v.Args[0], v.Args[1].AuxInt
|
|
|
|
|
}
|
|
|
|
|
case OpSub64, OpSub32:
|
|
|
|
|
if v.Args[1].Op == cop {
|
|
|
|
|
aux := v.Args[1].AuxInt
|
|
|
|
|
if aux != -aux { // Overflow; too bad
|
|
|
|
|
return v.Args[0], -aux
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil, 0
|
|
|
|
|
}
|
cmd/compile: handle sign/zero extensions in prove, via update method
Array accesses with index types smaller than the machine word size may
involve a sign or zero extension of the index value before bounds
checking. Currently, this defeats prove because the facts about the
original index value don't flow through the sign/zero extension.
This CL fixes this by looking back through value-preserving sign/zero
extensions when adding facts via Update and, where appropriate, applying
the same facts using the pre-extension value. This fix is enhanced by
also looking back through value-preserving extensions within
ft.isNonNegative to infer whether the extended value is known to be
non-negative. Without this additional isNonNegative enhancement, this
logic is rendered significantly less effective by the limitation
discussed in the next paragraph.
In Update, the application of facts to pre-extension values is limited
to cases where the domain of the new fact is consistent with the type of
the pre-extension value. There may be cases where this cross-domain
passing of facts is valid, but distinguishing them from the invalid
cases is difficult for me to reason about and to implement.
Assessing which cases to allow requires details about the context and
inferences behind the fact being applied which are not available
within Update. Additional difficulty arises from the fact that the SSA
does not curently differentiate extensions added by the compiler for
indexing operations, extensions added by the compiler for implicit
conversions, or explicit extensions from the source.
Examples of some cases that would need to be filtered correctly for
cross-domain facts:
(1) A uint8 is zero-extended to int for indexing (a value-preserving
zeroExt). When, if ever, can signed domain facts learned about the int be
applied to the uint8?
(2) An int8 is sign-extended to int16 (value-preserving) for an equality
comparison. Equality comparison facts are currently always learned in both
the signed and unsigned domains. When, if ever, can the unsigned facts
learned about the int16, from the int16 != int16 comparison, be applied
to the original int8?
This is an alternative to CL 122695 and CL 174309. Compared to CL 122695,
this CL differs in that the facts added about the pre-extension value will
pass through the Update method, where additional inferences are processed
(e.g. fence-post implications, see #29964). CL 174309 is limited to bounds
checks, so is narrower in application, and makes the code harder to read.
Fixes #26292.
Fixes #29964.
Fixes #15074
Removes 238 bounds checks from std/cmd.
Change-Id: I1f87c32ee672bfb8be397b27eab7a4c2f304893f
Reviewed-on: https://go-review.googlesource.com/c/go/+/174704
Run-TryBot: Zach Jones <zachj1@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Giovanni Bajo <rasky@develer.com>
2019-04-09 23:19:43 +01:00
|
|
|
|
|
|
|
|
// isCleanExt reports whether v is the result of a value-preserving
|
|
|
|
|
// sign or zero extension
|
|
|
|
|
func isCleanExt(v *Value) bool {
|
|
|
|
|
switch v.Op {
|
|
|
|
|
case OpSignExt8to16, OpSignExt8to32, OpSignExt8to64,
|
|
|
|
|
OpSignExt16to32, OpSignExt16to64, OpSignExt32to64:
|
|
|
|
|
// signed -> signed is the only value-preserving sign extension
|
|
|
|
|
return v.Args[0].Type.IsSigned() && v.Type.IsSigned()
|
|
|
|
|
|
|
|
|
|
case OpZeroExt8to16, OpZeroExt8to32, OpZeroExt8to64,
|
|
|
|
|
OpZeroExt16to32, OpZeroExt16to64, OpZeroExt32to64:
|
|
|
|
|
// unsigned -> signed/unsigned are value-preserving zero extensions
|
|
|
|
|
return !v.Args[0].Type.IsSigned()
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|