From 74117bcfd8ba581052280e6718c3b70e6b9d1067 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 11 Jun 2014 13:12:15 -0400 Subject: [PATCH] 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 --- go/pointer/TODO | 19 ++---- go/pointer/analysis.go | 12 ++-- go/pointer/api.go | 72 +++++++++++++-------- go/pointer/constraint.go | 2 +- go/pointer/intrinsics.go | 6 +- go/pointer/opt.go | 3 +- go/pointer/reflect.go | 135 +++++++++++++++++++++++---------------- go/pointer/solve.go | 94 +++++++++++++++------------ go/pointer/util.go | 122 ++++++++--------------------------- 9 files changed, 219 insertions(+), 246 deletions(-) diff --git a/go/pointer/TODO b/go/pointer/TODO index eb0ddd73..5b7b1684 100644 --- a/go/pointer/TODO +++ b/go/pointer/TODO @@ -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. diff --git a/go/pointer/analysis.go b/go/pointer/analysis.go index 699462ba..884962b5 100644 --- a/go/pointer/analysis.go +++ b/go/pointer/analysis.go @@ -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)) } } } diff --git a/go/pointer/api.go b/go/pointer/api.go index 6da20146..190e459d 100644 --- a/go/pointer/api.go +++ b/go/pointer/api.go @@ -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 diff --git a/go/pointer/constraint.go b/go/pointer/constraint.go index f7603cbc..caf235dd 100644 --- a/go/pointer/constraint.go +++ b/go/pointer/constraint.go @@ -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 } diff --git a/go/pointer/intrinsics.go b/go/pointer/intrinsics.go index a2257fec..b6e0b053 100644 --- a/go/pointer/intrinsics.go +++ b/go/pointer/intrinsics.go @@ -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. diff --git a/go/pointer/opt.go b/go/pointer/opt.go index e27508af..e8995342 100644 --- a/go/pointer/opt.go +++ b/go/pointer/opt.go @@ -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 diff --git a/go/pointer/reflect.go b/go/pointer/reflect.go index 56364c42..fe51718f 100644 --- a/go/pointer/reflect.go +++ b/go/pointer/reflect.go @@ -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) diff --git a/go/pointer/solve.go b/go/pointer/solve.go index 6a6a5ee9..e087e051 100644 --- a/go/pointer/solve.go +++ b/go/pointer/solve.go @@ -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") } diff --git a/go/pointer/util.go b/go/pointer/util.go index 06d271d7..b4d80646 100644 --- a/go/pointer/util.go +++ b/go/pointer/util.go @@ -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) }