1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00

go/pointer: use sparse bit vectors to represent points-to sets in solver.

This optimization reduces solve time (typically >90% of the
total) by about 78% when analysing real programs.  It also
makes the solver 100% deterministic since all iterations are
ordered.

Also:
- remove unnecessary nodeid parameter to solve() method.
- don't add a fieldInfo for singleton tuples (cosmetic fix).
- inline+simplify "worklist" type.
- replace "constraintset" type by a slice.

LGTM=crawshaw
R=crawshaw
CC=golang-codereviews
https://golang.org/cl/95240043
This commit is contained in:
Alan Donovan 2014-06-11 13:12:15 -04:00
parent fec252214b
commit 74117bcfd8
9 changed files with 219 additions and 246 deletions

View File

@ -4,7 +4,9 @@ Pointer analysis to-do list
===========================
CONSTRAINT GENERATION:
- support reflection
- support reflection:
- a couple of operators are missing
- reflect.Values may contain lvalues (CanAddr)
- implement native intrinsics. These vary by platform.
- unsafe.Pointer conversions. Three options:
1) unsoundly (but type-safely) treat p=unsafe.Pointer(x) conversions as
@ -17,21 +19,8 @@ CONSTRAINT GENERATION:
allocations that identifies the object.
OPTIMISATIONS
- pre-solver: PE and LE via HVN/HRU.
- pre-solver: PE via HVN/HRU and LE.
- solver: HCD, LCD.
- use sparse bitvectors for ptsets
- use sparse bitvectors for graph edges
- experiment with different worklist algorithms:
priority queue (solver visit-time order)
red-black tree (node id order)
double-ended queue (insertion order)
fast doubly-linked list (See Zhanh et al PLDI'13)
(insertion order with fast membership test)
dannyb recommends sparse bitmap.
API:
- Some optimisations (e.g. LE, PE) may change the API.
Think about them sooner rather than later.
MISC:
- Test on all platforms.

View File

@ -99,7 +99,7 @@ type node struct {
// - *typeFilterConstraint y=x.(I)
// - *untagConstraint y=x.(C)
// - *invokeConstraint y=x.f(params...)
complex constraintset
complex []constraint
}
// An analysis instance holds the state of a single pointer analysis problem.
@ -119,9 +119,10 @@ type analysis struct {
globalobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
localval map[ssa.Value]nodeid // node for each local ssa.Value
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
work worklist // solver's worklist
work nodeset // solver's worklist
result *Result // results of the analysis
track track // pointerlike types whose aliasing we track
deltaSpace []int // working space for iterating over PTS deltas
// Reflection & intrinsics:
hasher typeutil.Hasher // cache of type hashes
@ -223,11 +224,11 @@ func Analyze(config *Config) (result *Result, err error) {
trackTypes: make(map[types.Type]bool),
hasher: typeutil.MakeHasher(),
intrinsics: make(map[*ssa.Function]intrinsic),
work: makeMapWorklist(),
result: &Result{
Queries: make(map[ssa.Value]Pointer),
IndirectQueries: make(map[ssa.Value]Pointer),
},
deltaSpace: make([]int, 0, 100),
}
if false {
@ -300,10 +301,11 @@ func Analyze(config *Config) (result *Result, err error) {
}
// Add dynamic edges to call graph.
var space [100]int
for _, caller := range a.cgnodes {
for _, site := range caller.sites {
for callee := range a.nodes[site.targets].pts {
a.callEdge(caller, site, callee)
for _, callee := range a.nodes[site.targets].pts.AppendTo(space[:0]) {
a.callEdge(caller, site, nodeid(callee))
}
}
}

View File

@ -10,6 +10,7 @@ import (
"go/token"
"io"
"code.google.com/p/go.tools/container/intsets"
"code.google.com/p/go.tools/go/callgraph"
"code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/types/typeutil"
@ -134,18 +135,22 @@ type Pointer struct {
// A PointsToSet is a set of labels (locations or allocations).
type PointsToSet struct {
a *analysis // may be nil if pts is nil
pts nodeset
pts *nodeset
}
func (s PointsToSet) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "[")
sep := ""
for l := range s.pts {
fmt.Fprintf(&buf, "%s%s", sep, s.a.labelFor(l))
sep = ", "
buf.WriteByte('[')
if s.pts != nil {
var space [50]int
for i, l := range s.pts.AppendTo(space[:0]) {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(s.a.labelFor(nodeid(l)).String())
}
}
fmt.Fprintf(&buf, "]")
buf.WriteByte(']')
return buf.String()
}
@ -153,8 +158,11 @@ func (s PointsToSet) String() string {
// contains.
func (s PointsToSet) Labels() []*Label {
var labels []*Label
for l := range s.pts {
labels = append(labels, s.a.labelFor(l))
if s.pts != nil {
var space [50]int
for _, l := range s.pts.AppendTo(space[:0]) {
labels = append(labels, s.a.labelFor(nodeid(l)))
}
}
return labels
}
@ -173,20 +181,24 @@ func (s PointsToSet) Labels() []*Label {
func (s PointsToSet) DynamicTypes() *typeutil.Map {
var tmap typeutil.Map
tmap.SetHasher(s.a.hasher)
for ifaceObjId := range s.pts {
if !s.a.isTaggedObject(ifaceObjId) {
continue // !CanHaveDynamicTypes(tDyn)
if s.pts != nil {
var space [50]int
for _, x := range s.pts.AppendTo(space[:0]) {
ifaceObjId := nodeid(x)
if !s.a.isTaggedObject(ifaceObjId) {
continue // !CanHaveDynamicTypes(tDyn)
}
tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
if indirect {
panic("indirect tagged object") // implement later
}
pts, ok := tmap.At(tDyn).(PointsToSet)
if !ok {
pts = PointsToSet{s.a, new(nodeset)}
tmap.Set(tDyn, pts)
}
pts.pts.addAll(&s.a.nodes[v].pts)
}
tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
if indirect {
panic("indirect tagged object") // implement later
}
pts, ok := tmap.At(tDyn).(PointsToSet)
if !ok {
pts = PointsToSet{s.a, make(nodeset)}
tmap.Set(tDyn, pts)
}
pts.pts.addAll(s.a.nodes[v].pts)
}
return &tmap
}
@ -194,12 +206,13 @@ func (s PointsToSet) DynamicTypes() *typeutil.Map {
// Intersects reports whether this points-to set and the
// argument points-to set contain common members.
func (x PointsToSet) Intersects(y PointsToSet) bool {
for l := range x.pts {
if _, ok := y.pts[l]; ok {
return true
}
if x.pts == nil || y.pts == nil {
return false
}
return false
// This takes Θ(|x|+|y|) time.
var z intsets.Sparse
z.Intersection(&x.pts.Sparse, &y.pts.Sparse)
return !z.IsEmpty()
}
func (p Pointer) String() string {
@ -208,7 +221,10 @@ func (p Pointer) String() string {
// PointsTo returns the points-to set of this pointer.
func (p Pointer) PointsTo() PointsToSet {
return PointsToSet{p.a, p.a.nodes[p.n].pts}
if p.n == 0 {
return PointsToSet{}
}
return PointsToSet{p.a, &p.a.nodes[p.n].pts}
}
// MayAlias reports whether the receiver pointer may alias

View File

@ -28,7 +28,7 @@ type constraint interface {
// solve is called for complex constraints when the pts for
// the node to which they are attached has changed.
solve(a *analysis, n *node, delta nodeset)
solve(a *analysis, delta *nodeset)
String() string
}

View File

@ -272,9 +272,9 @@ func (c *runtimeSetFinalizerConstraint) String() string {
return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f)
}
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, _ *node, delta nodeset) {
for fObj := range delta {
tDyn, f, indirect := a.taggedValue(fObj)
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, delta *nodeset) {
for _, fObj := range delta.AppendTo(a.deltaSpace) {
tDyn, f, indirect := a.taggedValue(nodeid(fObj))
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.

View File

@ -13,8 +13,7 @@ import (
func (a *analysis) optimize() {
a.renumber()
// TODO(adonovan): opt:
// PE, LE, HVN, HRU, sparse bitsets, etc.
// TODO(adonovan): opt: PE (HVN, HRU), LE, etc.
}
// renumber permutes a.nodes so that all nodes within an addressable

View File

@ -170,9 +170,10 @@ func (c *rVBytesConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Bytes()", c.result, c.v)
}
func (c *rVBytesConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVBytesConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, slice, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -231,13 +232,14 @@ func (c *rVCallConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Call(n%d)", c.result, c.v, c.arg)
}
func (c *rVCallConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVCallConstraint) solve(a *analysis, delta *nodeset) {
if c.targets == 0 {
panic("no targets")
}
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, fn, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -372,9 +374,10 @@ func (c *rVElemConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Elem()", c.result, c.v)
}
func (c *rVElemConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVElemConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -434,9 +437,10 @@ func (c *rVIndexConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Index()", c.result, c.v)
}
func (c *rVIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVIndexConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -495,9 +499,10 @@ func (c *rVInterfaceConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Interface()", c.result, c.v)
}
func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVInterfaceConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -547,9 +552,10 @@ func (c *rVMapIndexConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapIndex(_)", c.result, c.v)
}
func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVMapIndexConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil {
@ -600,9 +606,10 @@ func (c *rVMapKeysConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapKeys()", c.result, c.v)
}
func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVMapKeysConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil {
@ -663,9 +670,10 @@ func (c *rVRecvConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Recv()", c.result, c.v)
}
func (c *rVRecvConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVRecvConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil {
@ -717,8 +725,9 @@ func (c *rVSendConstraint) String() string {
return fmt.Sprintf("reflect n%d.Send(n%d)", c.v, c.x)
}
func (c *rVSendConstraint) solve(a *analysis, _ *node, delta nodeset) {
for vObj := range delta {
func (c *rVSendConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil {
@ -769,8 +778,9 @@ func (c *rVSetBytesConstraint) String() string {
return fmt.Sprintf("reflect n%d.SetBytes(n%d)", c.v, c.x)
}
func (c *rVSetBytesConstraint) solve(a *analysis, _ *node, delta nodeset) {
for vObj := range delta {
func (c *rVSetBytesConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, slice, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -818,8 +828,9 @@ func (c *rVSetMapIndexConstraint) String() string {
return fmt.Sprintf("reflect n%d.SetMapIndex(n%d, n%d)", c.v, c.key, c.val)
}
func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
for vObj := range delta {
func (c *rVSetMapIndexConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil {
@ -877,9 +888,10 @@ func (c *rVSliceConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Slice(_, _)", c.result, c.v)
}
func (c *rVSliceConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rVSliceConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -955,9 +967,10 @@ func (c *reflectChanOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.ChanOf(n%d)", c.result, c.t)
}
func (c *reflectChanOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectChanOfConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) {
@ -1026,9 +1039,10 @@ func (c *reflectIndirectConstraint) String() string {
return fmt.Sprintf("n%d = reflect.Indirect(n%d)", c.result, c.v)
}
func (c *reflectIndirectConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectIndirectConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for vObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, _, _ := a.taggedValue(vObj)
var res nodeid
if tPtr, ok := tDyn.Underlying().(*types.Pointer); ok {
@ -1077,9 +1091,10 @@ func (c *reflectMakeChanConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeChan(n%d)", c.result, c.typ)
}
func (c *reflectMakeChanConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectMakeChanConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for typObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj)
tChan, ok := T.Underlying().(*types.Chan)
if !ok || tChan.Dir() != types.SendRecv {
@ -1134,9 +1149,10 @@ func (c *reflectMakeMapConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeMap(n%d)", c.result, c.typ)
}
func (c *reflectMakeMapConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectMakeMapConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for typObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj)
tMap, ok := T.Underlying().(*types.Map)
if !ok {
@ -1190,9 +1206,10 @@ func (c *reflectMakeSliceConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeSlice(n%d)", c.result, c.typ)
}
func (c *reflectMakeSliceConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectMakeSliceConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for typObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj)
if _, ok := T.Underlying().(*types.Slice); !ok {
continue // not a slice type
@ -1246,9 +1263,10 @@ func (c *reflectNewConstraint) String() string {
return fmt.Sprintf("n%d = reflect.New(n%d)", c.result, c.typ)
}
func (c *reflectNewConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectNewConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for typObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj)
// allocate new T object
@ -1307,9 +1325,10 @@ func (c *reflectPtrToConstraint) String() string {
return fmt.Sprintf("n%d = reflect.PtrTo(n%d)", c.result, c.t)
}
func (c *reflectPtrToConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectPtrToConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) {
@ -1355,9 +1374,10 @@ func (c *reflectSliceOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.SliceOf(n%d)", c.result, c.t)
}
func (c *reflectSliceOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectSliceOfConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) {
@ -1401,9 +1421,10 @@ func (c *reflectTypeOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.TypeOf(n%d)", c.result, c.i)
}
func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectTypeOfConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for iObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
iObj := nodeid(x)
tDyn, _, _ := a.taggedValue(iObj)
if a.addLabel(c.result, a.makeRtype(tDyn)) {
changed = true
@ -1451,9 +1472,10 @@ func (c *reflectZeroConstraint) String() string {
return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.typ)
}
func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *reflectZeroConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for typObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj)
// TODO(adonovan): if T is an interface type, we need
@ -1510,13 +1532,14 @@ func (c *rtypeElemConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).Elem(n%d)", c.result, c.t)
}
func (c *rtypeElemConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rtypeElemConstraint) solve(a *analysis, delta *nodeset) {
// Implemented by *types.{Map,Chan,Array,Slice,Pointer}.
type hasElem interface {
Elem() types.Type
}
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type)
if tHasElem, ok := T.Underlying().(hasElem); ok {
if a.addLabel(c.result, a.makeRtype(tHasElem.Elem())) {
@ -1560,7 +1583,7 @@ func (c *rtypeFieldByNameConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).FieldByName(n%d, %q)", c.result, c.t, c.name)
}
func (c *rtypeFieldByNameConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rtypeFieldByNameConstraint) solve(a *analysis, delta *nodeset) {
// type StructField struct {
// 0 __identity__
// 1 Name string
@ -1572,7 +1595,8 @@ func (c *rtypeFieldByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
// 7 Anonymous bool
// }
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type)
tStruct, ok := T.Underlying().(*types.Struct)
if !ok {
@ -1648,9 +1672,10 @@ func (c *rtypeInOutConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).InOut(n%d, %d)", c.result, c.t, c.i)
}
func (c *rtypeInOutConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rtypeInOutConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type)
sig, ok := T.Underlying().(*types.Signature)
if !ok {
@ -1722,9 +1747,10 @@ func (c *rtypeKeyConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).Key(n%d)", c.result, c.t)
}
func (c *rtypeKeyConstraint) solve(a *analysis, _ *node, delta nodeset) {
func (c *rtypeKeyConstraint) solve(a *analysis, delta *nodeset) {
changed := false
for tObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type)
if tMap, ok := T.Underlying().(*types.Map); ok {
if a.addLabel(c.result, a.makeRtype(tMap.Key())) {
@ -1782,8 +1808,9 @@ func changeRecv(sig *types.Signature) *types.Signature {
return types.NewSignature(nil, nil, types.NewTuple(p2...), sig.Results(), sig.Variadic())
}
func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset) {
for tObj := range delta {
func (c *rtypeMethodByNameConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type)
isIface := isInterface(T)

View File

@ -14,6 +14,8 @@ import (
)
func (a *analysis) solve() {
var delta nodeset
// Solver main loop.
for round := 1; ; round++ {
if a.log != nil {
@ -25,10 +27,11 @@ func (a *analysis) solve() {
// dynamic constraints from reflection thereafter.
a.processNewConstraints()
id := a.work.take()
if id == empty {
break
var x int
if !a.work.TakeMin(&x) {
break // empty
}
id := nodeid(x)
if a.log != nil {
fmt.Fprintf(a.log, "\tnode n%d\n", id)
}
@ -36,22 +39,22 @@ func (a *analysis) solve() {
n := a.nodes[id]
// Difference propagation.
delta := n.pts.diff(n.prevPts)
if delta == nil {
delta.Difference(&n.pts.Sparse, &n.prevPts.Sparse)
if delta.IsEmpty() {
continue
}
n.prevPts = n.pts.clone()
n.prevPts.Copy(&n.pts.Sparse)
// Apply all resolution rules attached to n.
a.solveConstraints(n, delta)
a.solveConstraints(n, &delta)
if a.log != nil {
fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, n.pts)
fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, &n.pts)
}
}
if len(a.nodes[0].pts) > 0 {
panic(fmt.Sprintf("pts(0) is nonempty: %s", a.nodes[0].pts))
if !a.nodes[0].pts.IsEmpty() {
panic(fmt.Sprintf("pts(0) is nonempty: %s", &a.nodes[0].pts))
}
if a.log != nil {
@ -59,8 +62,8 @@ func (a *analysis) solve() {
// Dump solution.
for i, n := range a.nodes {
if n.pts != nil {
fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, n.pts, n.typ)
if !n.pts.IsEmpty() {
fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, &n.pts, n.typ)
}
}
}
@ -87,7 +90,7 @@ func (a *analysis) processNewConstraints() {
// something initially (due to addrConstraints) and
// have other constraints attached.
// (A no-op in round 1.)
if dst.copyTo != nil || dst.complex != nil {
if !dst.copyTo.IsEmpty() || len(dst.complex) > 0 {
a.addWork(c.dst)
}
}
@ -108,20 +111,22 @@ func (a *analysis) processNewConstraints() {
default:
// complex constraint
id = c.ptr()
a.nodes[id].complex.add(c)
ptr := a.nodes[id]
ptr.complex = append(ptr.complex, c)
}
if n := a.nodes[id]; len(n.pts) > 0 {
if len(n.prevPts) > 0 {
if n := a.nodes[id]; !n.pts.IsEmpty() {
if !n.prevPts.IsEmpty() {
stale.add(id)
}
a.addWork(id)
}
}
// Apply new constraints to pre-existing PTS labels.
for id := range stale {
var space [50]int
for _, id := range stale.AppendTo(space[:0]) {
n := a.nodes[id]
a.solveConstraints(n, n.prevPts)
a.solveConstraints(n, &n.prevPts)
}
}
@ -129,24 +134,23 @@ func (a *analysis) processNewConstraints() {
// the set of labels delta. It may generate new constraints in
// a.constraints.
//
func (a *analysis) solveConstraints(n *node, delta nodeset) {
if delta == nil {
func (a *analysis) solveConstraints(n *node, delta *nodeset) {
if delta.IsEmpty() {
return
}
// Process complex constraints dependent on n.
for c := range n.complex {
for _, c := range n.complex {
if a.log != nil {
fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
}
// TODO(adonovan): parameter n is never needed, since
// it's equal to c.ptr(). Remove.
c.solve(a, n, delta)
c.solve(a, delta)
}
// Process copy constraints.
var copySeen nodeset
for mid := range n.copyTo {
for _, x := range n.copyTo.AppendTo(a.deltaSpace) {
mid := nodeid(x)
if copySeen.add(mid) {
if a.nodes[mid].pts.addAll(delta) {
a.addWork(mid)
@ -161,7 +165,7 @@ func (a *analysis) addLabel(ptr, label nodeid) bool {
}
func (a *analysis) addWork(id nodeid) {
a.work.add(id)
a.work.Insert(int(id))
if a.log != nil {
fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
}
@ -184,7 +188,7 @@ func (a *analysis) onlineCopy(dst, src nodeid) bool {
// are followed by addWork, possibly batched
// via a 'changed' flag; see if there's a
// noticeable penalty to calling addWork here.
return a.nodes[dst].pts.addAll(nsrc.pts)
return a.nodes[dst].pts.addAll(&nsrc.pts)
}
}
return false
@ -207,9 +211,10 @@ func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
return sizeof
}
func (c *loadConstraint) solve(a *analysis, n *node, delta nodeset) {
func (c *loadConstraint) solve(a *analysis, delta *nodeset) {
var changed bool
for k := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
k := nodeid(x)
koff := k + nodeid(c.offset)
if a.onlineCopy(c.dst, koff) {
changed = true
@ -220,8 +225,9 @@ func (c *loadConstraint) solve(a *analysis, n *node, delta nodeset) {
}
}
func (c *storeConstraint) solve(a *analysis, n *node, delta nodeset) {
for k := range delta {
func (c *storeConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
k := nodeid(x)
koff := k + nodeid(c.offset)
if a.onlineCopy(koff, c.src) {
a.addWork(koff)
@ -229,17 +235,19 @@ func (c *storeConstraint) solve(a *analysis, n *node, delta nodeset) {
}
}
func (c *offsetAddrConstraint) solve(a *analysis, n *node, delta nodeset) {
func (c *offsetAddrConstraint) solve(a *analysis, delta *nodeset) {
dst := a.nodes[c.dst]
for k := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
k := nodeid(x)
if dst.pts.add(k + nodeid(c.offset)) {
a.addWork(c.dst)
}
}
}
func (c *typeFilterConstraint) solve(a *analysis, n *node, delta nodeset) {
for ifaceObj := range delta {
func (c *typeFilterConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
ifaceObj := nodeid(x)
tDyn, _, indirect := a.taggedValue(ifaceObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -255,12 +263,13 @@ func (c *typeFilterConstraint) solve(a *analysis, n *node, delta nodeset) {
}
}
func (c *untagConstraint) solve(a *analysis, n *node, delta nodeset) {
func (c *untagConstraint) solve(a *analysis, delta *nodeset) {
predicate := types.AssignableTo
if c.exact {
predicate = types.Identical
}
for ifaceObj := range delta {
for _, x := range delta.AppendTo(a.deltaSpace) {
ifaceObj := nodeid(x)
tDyn, v, indirect := a.taggedValue(ifaceObj)
if indirect {
// TODO(adonovan): we'll need to implement this
@ -271,7 +280,7 @@ func (c *untagConstraint) solve(a *analysis, n *node, delta nodeset) {
if predicate(tDyn, c.typ) {
// Copy payload sans tag to dst.
//
// TODO(adonovan): opt: if tConc is
// TODO(adonovan): opt: if tDyn is
// nonpointerlike we can skip this entire
// constraint, perhaps. We only care about
// pointers among the fields.
@ -280,8 +289,9 @@ func (c *untagConstraint) solve(a *analysis, n *node, delta nodeset) {
}
}
func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
for ifaceObj := range delta {
func (c *invokeConstraint) solve(a *analysis, delta *nodeset) {
for _, x := range delta.AppendTo(a.deltaSpace) {
ifaceObj := nodeid(x)
tDyn, v, indirect := a.taggedValue(ifaceObj)
if indirect {
// TODO(adonovan): we may need to implement this if
@ -329,10 +339,10 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
}
}
func (c *addrConstraint) solve(a *analysis, n *node, delta nodeset) {
func (c *addrConstraint) solve(a *analysis, delta *nodeset) {
panic("addr is not a complex constraint")
}
func (c *copyConstraint) solve(a *analysis, n *node, delta nodeset) {
func (c *copyConstraint) solve(a *analysis, delta *nodeset) {
panic("copy is not a complex constraint")
}

View File

@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"code.google.com/p/go.tools/container/intsets"
"code.google.com/p/go.tools/go/types"
)
@ -154,10 +155,17 @@ func (a *analysis) flatten(t types.Type) []*fieldInfo {
case *types.Tuple:
// No identity node: tuples are never address-taken.
for i, n := 0, t.Len(); i < n; i++ {
f := t.At(i)
for _, fi := range a.flatten(f.Type()) {
fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi})
n := t.Len()
if n == 1 {
// Don't add a fieldInfo link for singletons,
// e.g. in params/results.
fl = append(fl, a.flatten(t.At(0).Type())...)
} else {
for i := 0; i < n; i++ {
f := t.At(i)
for _, fi := range a.flatten(f.Type()) {
fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi})
}
}
}
@ -246,107 +254,29 @@ func sliceToArray(slice types.Type) *types.Array {
// Node set -------------------------------------------------------------------
// NB, mutator methods are attached to *nodeset.
// nodeset may be a reference, but its address matters!
type nodeset map[nodeid]struct{}
type nodeset struct {
intsets.Sparse
}
// ---- Accessors ----
func (ns nodeset) String() string {
func (ns *nodeset) String() string {
var buf bytes.Buffer
buf.WriteRune('{')
var sep string
for n := range ns {
fmt.Fprintf(&buf, "%sn%d", sep, n)
sep = ", "
var space [50]int
for i, n := range ns.AppendTo(space[:0]) {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteRune('n')
fmt.Fprintf(&buf, "%d", n)
}
buf.WriteRune('}')
return buf.String()
}
// diff returns the set-difference x - y. nil => empty.
//
// TODO(adonovan): opt: extremely inefficient. BDDs do this in
// constant time. Sparse bitvectors are linear but very fast.
func (x nodeset) diff(y nodeset) nodeset {
var z nodeset
for k := range x {
if _, ok := y[k]; !ok {
z.add(k)
}
}
return z
}
// clone() returns an unaliased copy of x.
func (x nodeset) clone() nodeset {
return x.diff(nil)
}
// ---- Mutators ----
func (ns *nodeset) add(n nodeid) bool {
sz := len(*ns)
if *ns == nil {
*ns = make(nodeset)
}
(*ns)[n] = struct{}{}
return len(*ns) > sz
return ns.Sparse.Insert(int(n))
}
func (x *nodeset) addAll(y nodeset) bool {
if y == nil {
return false
}
sz := len(*x)
if *x == nil {
*x = make(nodeset)
}
for n := range y {
(*x)[n] = struct{}{}
}
return len(*x) > sz
}
// Constraint set -------------------------------------------------------------
type constraintset map[constraint]struct{}
func (cs *constraintset) add(c constraint) bool {
sz := len(*cs)
if *cs == nil {
*cs = make(constraintset)
}
(*cs)[c] = struct{}{}
return len(*cs) > sz
}
// Worklist -------------------------------------------------------------------
const empty nodeid = 1<<32 - 1
type worklist interface {
add(nodeid) // Adds a node to the set
take() nodeid // Takes a node from the set and returns it, or empty
}
// Simple nondeterministic worklist based on a built-in map.
type mapWorklist struct {
set nodeset
}
func (w *mapWorklist) add(n nodeid) {
w.set[n] = struct{}{}
}
func (w *mapWorklist) take() nodeid {
for k := range w.set {
delete(w.set, k)
return k
}
return empty
}
func makeMapWorklist() worklist {
return &mapWorklist{make(nodeset)}
func (x *nodeset) addAll(y *nodeset) bool {
return x.UnionWith(&y.Sparse)
}