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: 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. - implement native intrinsics. These vary by platform.
- unsafe.Pointer conversions. Three options: - unsafe.Pointer conversions. Three options:
1) unsoundly (but type-safely) treat p=unsafe.Pointer(x) conversions as 1) unsoundly (but type-safely) treat p=unsafe.Pointer(x) conversions as
@ -17,21 +19,8 @@ CONSTRAINT GENERATION:
allocations that identifies the object. allocations that identifies the object.
OPTIMISATIONS OPTIMISATIONS
- pre-solver: PE and LE via HVN/HRU. - pre-solver: PE via HVN/HRU and LE.
- solver: HCD, LCD. - 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: MISC:
- Test on all platforms. - Test on all platforms.

View File

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

View File

@ -10,6 +10,7 @@ import (
"go/token" "go/token"
"io" "io"
"code.google.com/p/go.tools/container/intsets"
"code.google.com/p/go.tools/go/callgraph" "code.google.com/p/go.tools/go/callgraph"
"code.google.com/p/go.tools/go/ssa" "code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/types/typeutil" "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). // A PointsToSet is a set of labels (locations or allocations).
type PointsToSet struct { type PointsToSet struct {
a *analysis // may be nil if pts is nil a *analysis // may be nil if pts is nil
pts nodeset pts *nodeset
} }
func (s PointsToSet) String() string { func (s PointsToSet) String() string {
var buf bytes.Buffer var buf bytes.Buffer
fmt.Fprintf(&buf, "[") buf.WriteByte('[')
sep := "" if s.pts != nil {
for l := range s.pts { var space [50]int
fmt.Fprintf(&buf, "%s%s", sep, s.a.labelFor(l)) for i, l := range s.pts.AppendTo(space[:0]) {
sep = ", " if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(s.a.labelFor(nodeid(l)).String())
}
} }
fmt.Fprintf(&buf, "]") buf.WriteByte(']')
return buf.String() return buf.String()
} }
@ -153,8 +158,11 @@ func (s PointsToSet) String() string {
// contains. // contains.
func (s PointsToSet) Labels() []*Label { func (s PointsToSet) Labels() []*Label {
var labels []*Label var labels []*Label
for l := range s.pts { if s.pts != nil {
labels = append(labels, s.a.labelFor(l)) var space [50]int
for _, l := range s.pts.AppendTo(space[:0]) {
labels = append(labels, s.a.labelFor(nodeid(l)))
}
} }
return labels return labels
} }
@ -173,20 +181,24 @@ func (s PointsToSet) Labels() []*Label {
func (s PointsToSet) DynamicTypes() *typeutil.Map { func (s PointsToSet) DynamicTypes() *typeutil.Map {
var tmap typeutil.Map var tmap typeutil.Map
tmap.SetHasher(s.a.hasher) tmap.SetHasher(s.a.hasher)
for ifaceObjId := range s.pts { if s.pts != nil {
if !s.a.isTaggedObject(ifaceObjId) { var space [50]int
continue // !CanHaveDynamicTypes(tDyn) 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 return &tmap
} }
@ -194,12 +206,13 @@ func (s PointsToSet) DynamicTypes() *typeutil.Map {
// Intersects reports whether this points-to set and the // Intersects reports whether this points-to set and the
// argument points-to set contain common members. // argument points-to set contain common members.
func (x PointsToSet) Intersects(y PointsToSet) bool { func (x PointsToSet) Intersects(y PointsToSet) bool {
for l := range x.pts { if x.pts == nil || y.pts == nil {
if _, ok := y.pts[l]; ok { return false
return true
}
} }
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 { func (p Pointer) String() string {
@ -208,7 +221,10 @@ func (p Pointer) String() string {
// PointsTo returns the points-to set of this pointer. // PointsTo returns the points-to set of this pointer.
func (p Pointer) PointsTo() PointsToSet { 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 // 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 // solve is called for complex constraints when the pts for
// the node to which they are attached has changed. // the node to which they are attached has changed.
solve(a *analysis, n *node, delta nodeset) solve(a *analysis, delta *nodeset)
String() string 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) return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f)
} }
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *runtimeSetFinalizerConstraint) solve(a *analysis, delta *nodeset) {
for fObj := range delta { for _, fObj := range delta.AppendTo(a.deltaSpace) {
tDyn, f, indirect := a.taggedValue(fObj) tDyn, f, indirect := a.taggedValue(nodeid(fObj))
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects. // when we start creating indirect tagged objects.

View File

@ -13,8 +13,7 @@ import (
func (a *analysis) optimize() { func (a *analysis) optimize() {
a.renumber() a.renumber()
// TODO(adonovan): opt: // TODO(adonovan): opt: PE (HVN, HRU), LE, etc.
// PE, LE, HVN, HRU, sparse bitsets, etc.
} }
// renumber permutes a.nodes so that all nodes within an addressable // 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, slice, indirect := a.taggedValue(vObj) tDyn, slice, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 { if c.targets == 0 {
panic("no targets") panic("no targets")
} }
changed := false changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, fn, indirect := a.taggedValue(vObj) tDyn, fn, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj) tDyn, payload, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj) tDyn, payload, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj) tDyn, payload, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, ch, indirect := a.taggedValue(vObj) tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan) tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil { 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) return fmt.Sprintf("reflect n%d.Send(n%d)", c.v, c.x)
} }
func (c *rVSendConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVSendConstraint) solve(a *analysis, delta *nodeset) {
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, ch, indirect := a.taggedValue(vObj) tDyn, ch, indirect := a.taggedValue(vObj)
tChan, _ := tDyn.Underlying().(*types.Chan) tChan, _ := tDyn.Underlying().(*types.Chan)
if tChan == nil { 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) return fmt.Sprintf("reflect n%d.SetBytes(n%d)", c.v, c.x)
} }
func (c *rVSetBytesConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rVSetBytesConstraint) solve(a *analysis, delta *nodeset) {
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, slice, indirect := a.taggedValue(vObj) tDyn, slice, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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) { func (c *rVSetMapIndexConstraint) solve(a *analysis, delta *nodeset) {
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, m, indirect := a.taggedValue(vObj) tDyn, m, indirect := a.taggedValue(vObj)
tMap, _ := tDyn.Underlying().(*types.Map) tMap, _ := tDyn.Underlying().(*types.Map)
if tMap == nil { 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, payload, indirect := a.taggedValue(vObj) tDyn, payload, indirect := a.taggedValue(vObj)
if indirect { if indirect {
// TODO(adonovan): we'll need to implement this // 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) 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 changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj) T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) { 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) 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 changed := false
for vObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
vObj := nodeid(x)
tDyn, _, _ := a.taggedValue(vObj) tDyn, _, _ := a.taggedValue(vObj)
var res nodeid var res nodeid
if tPtr, ok := tDyn.Underlying().(*types.Pointer); ok { 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) 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 changed := false
for typObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj) T := a.rtypeTaggedValue(typObj)
tChan, ok := T.Underlying().(*types.Chan) tChan, ok := T.Underlying().(*types.Chan)
if !ok || tChan.Dir() != types.SendRecv { 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) 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 changed := false
for typObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj) T := a.rtypeTaggedValue(typObj)
tMap, ok := T.Underlying().(*types.Map) tMap, ok := T.Underlying().(*types.Map)
if !ok { if !ok {
@ -1190,9 +1206,10 @@ func (c *reflectMakeSliceConstraint) String() string {
return fmt.Sprintf("n%d = reflect.MakeSlice(n%d)", c.result, c.typ) 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 changed := false
for typObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj) T := a.rtypeTaggedValue(typObj)
if _, ok := T.Underlying().(*types.Slice); !ok { if _, ok := T.Underlying().(*types.Slice); !ok {
continue // not a slice type 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) 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 changed := false
for typObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj) T := a.rtypeTaggedValue(typObj)
// allocate new T object // 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) 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 changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj) T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) { 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) 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 changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.rtypeTaggedValue(tObj) T := a.rtypeTaggedValue(tObj)
if typeTooHigh(T) { 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) 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 changed := false
for iObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
iObj := nodeid(x)
tDyn, _, _ := a.taggedValue(iObj) tDyn, _, _ := a.taggedValue(iObj)
if a.addLabel(c.result, a.makeRtype(tDyn)) { if a.addLabel(c.result, a.makeRtype(tDyn)) {
changed = true changed = true
@ -1451,9 +1472,10 @@ func (c *reflectZeroConstraint) String() string {
return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.typ) 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 changed := false
for typObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
typObj := nodeid(x)
T := a.rtypeTaggedValue(typObj) T := a.rtypeTaggedValue(typObj)
// TODO(adonovan): if T is an interface type, we need // 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) 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}. // Implemented by *types.{Map,Chan,Array,Slice,Pointer}.
type hasElem interface { type hasElem interface {
Elem() types.Type Elem() types.Type
} }
changed := false changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type) T := a.nodes[tObj].obj.data.(types.Type)
if tHasElem, ok := T.Underlying().(hasElem); ok { if tHasElem, ok := T.Underlying().(hasElem); ok {
if a.addLabel(c.result, a.makeRtype(tHasElem.Elem())) { 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) 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 { // type StructField struct {
// 0 __identity__ // 0 __identity__
// 1 Name string // 1 Name string
@ -1572,7 +1595,8 @@ func (c *rtypeFieldByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
// 7 Anonymous bool // 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) T := a.nodes[tObj].obj.data.(types.Type)
tStruct, ok := T.Underlying().(*types.Struct) tStruct, ok := T.Underlying().(*types.Struct)
if !ok { 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) 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 changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type) T := a.nodes[tObj].obj.data.(types.Type)
sig, ok := T.Underlying().(*types.Signature) sig, ok := T.Underlying().(*types.Signature)
if !ok { 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) 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 changed := false
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type) T := a.nodes[tObj].obj.data.(types.Type)
if tMap, ok := T.Underlying().(*types.Map); ok { if tMap, ok := T.Underlying().(*types.Map); ok {
if a.addLabel(c.result, a.makeRtype(tMap.Key())) { 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()) return types.NewSignature(nil, nil, types.NewTuple(p2...), sig.Results(), sig.Variadic())
} }
func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset) { func (c *rtypeMethodByNameConstraint) solve(a *analysis, delta *nodeset) {
for tObj := range delta { for _, x := range delta.AppendTo(a.deltaSpace) {
tObj := nodeid(x)
T := a.nodes[tObj].obj.data.(types.Type) T := a.nodes[tObj].obj.data.(types.Type)
isIface := isInterface(T) isIface := isInterface(T)

View File

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

View File

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"code.google.com/p/go.tools/container/intsets"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
@ -154,10 +155,17 @@ func (a *analysis) flatten(t types.Type) []*fieldInfo {
case *types.Tuple: case *types.Tuple:
// No identity node: tuples are never address-taken. // No identity node: tuples are never address-taken.
for i, n := 0, t.Len(); i < n; i++ { n := t.Len()
f := t.At(i) if n == 1 {
for _, fi := range a.flatten(f.Type()) { // Don't add a fieldInfo link for singletons,
fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi}) // 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 ------------------------------------------------------------------- // Node set -------------------------------------------------------------------
// NB, mutator methods are attached to *nodeset. type nodeset struct {
// nodeset may be a reference, but its address matters! intsets.Sparse
type nodeset map[nodeid]struct{} }
// ---- Accessors ---- func (ns *nodeset) String() string {
func (ns nodeset) String() string {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune('{') buf.WriteRune('{')
var sep string var space [50]int
for n := range ns { for i, n := range ns.AppendTo(space[:0]) {
fmt.Fprintf(&buf, "%sn%d", sep, n) if i > 0 {
sep = ", " buf.WriteString(", ")
}
buf.WriteRune('n')
fmt.Fprintf(&buf, "%d", n)
} }
buf.WriteRune('}') buf.WriteRune('}')
return buf.String() 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 { func (ns *nodeset) add(n nodeid) bool {
sz := len(*ns) return ns.Sparse.Insert(int(n))
if *ns == nil {
*ns = make(nodeset)
}
(*ns)[n] = struct{}{}
return len(*ns) > sz
} }
func (x *nodeset) addAll(y nodeset) bool { func (x *nodeset) addAll(y *nodeset) bool {
if y == nil { return x.UnionWith(&y.Sparse)
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)}
} }