|
|
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pointer
// This file defines a naive Andersen-style solver for the inclusion
// constraint system.
import ( "fmt" "go/types" )
type solverState struct { complex []constraint // complex constraints attached to this node
copyTo nodeset // simple copy constraint edges
pts nodeset // points-to set of this node
prevPTS nodeset // pts(n) in previous iteration (for difference propagation)
}
func (a *analysis) solve() { start("Solving") if a.log != nil { fmt.Fprintf(a.log, "\n\n==== Solving constraints\n\n") }
// Solver main loop.
var delta nodeset for { // Add new constraints to the graph:
// static constraints from SSA on round 1,
// dynamic constraints from reflection thereafter.
a.processNewConstraints()
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) }
n := a.nodes[id]
// Difference propagation.
delta.Difference(&n.solve.pts.Sparse, &n.solve.prevPTS.Sparse) if delta.IsEmpty() { continue } if a.log != nil { fmt.Fprintf(a.log, "\t\tpts(n%d : %s) = %s + %s\n", id, n.typ, &delta, &n.solve.prevPTS) } n.solve.prevPTS.Copy(&n.solve.pts.Sparse)
// Apply all resolution rules attached to n.
a.solveConstraints(n, &delta)
if a.log != nil { fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, &n.solve.pts) } }
if !a.nodes[0].solve.pts.IsEmpty() { panic(fmt.Sprintf("pts(0) is nonempty: %s", &a.nodes[0].solve.pts)) }
// Release working state (but keep final PTS).
for _, n := range a.nodes { n.solve.complex = nil n.solve.copyTo.Clear() n.solve.prevPTS.Clear() }
if a.log != nil { fmt.Fprintf(a.log, "Solver done\n")
// Dump solution.
for i, n := range a.nodes { if !n.solve.pts.IsEmpty() { fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, &n.solve.pts, n.typ) } } } stop("Solving") }
// processNewConstraints takes the new constraints from a.constraints
// and adds them to the graph, ensuring
// that new constraints are applied to pre-existing labels and
// that pre-existing constraints are applied to new labels.
//
func (a *analysis) processNewConstraints() { // Take the slice of new constraints.
// (May grow during call to solveConstraints.)
constraints := a.constraints a.constraints = nil
// Initialize points-to sets from addr-of (base) constraints.
for _, c := range constraints { if c, ok := c.(*addrConstraint); ok { dst := a.nodes[c.dst] dst.solve.pts.add(c.src)
// Populate the worklist with nodes that point to
// something initially (due to addrConstraints) and
// have other constraints attached.
// (A no-op in round 1.)
if !dst.solve.copyTo.IsEmpty() || len(dst.solve.complex) > 0 { a.addWork(c.dst) } } }
// Attach simple (copy) and complex constraints to nodes.
var stale nodeset for _, c := range constraints { var id nodeid switch c := c.(type) { case *addrConstraint: // base constraints handled in previous loop
continue case *copyConstraint: // simple (copy) constraint
id = c.src a.nodes[id].solve.copyTo.add(c.dst) default: // complex constraint
id = c.ptr() solve := a.nodes[id].solve solve.complex = append(solve.complex, c) }
if n := a.nodes[id]; !n.solve.pts.IsEmpty() { if !n.solve.prevPTS.IsEmpty() { stale.add(id) } a.addWork(id) } } // Apply new constraints to pre-existing PTS labels.
var space [50]int for _, id := range stale.AppendTo(space[:0]) { n := a.nodes[nodeid(id)] a.solveConstraints(n, &n.solve.prevPTS) } }
// solveConstraints applies each resolution rule attached to node n to
// the set of labels delta. It may generate new constraints in
// a.constraints.
//
func (a *analysis) solveConstraints(n *node, delta *nodeset) { if delta.IsEmpty() { return }
// Process complex constraints dependent on n.
for _, c := range n.solve.complex { if a.log != nil { fmt.Fprintf(a.log, "\t\tconstraint %s\n", c) } c.solve(a, delta) }
// Process copy constraints.
var copySeen nodeset for _, x := range n.solve.copyTo.AppendTo(a.deltaSpace) { mid := nodeid(x) if copySeen.add(mid) { if a.nodes[mid].solve.pts.addAll(delta) { a.addWork(mid) } } } }
// addLabel adds label to the points-to set of ptr and reports whether the set grew.
func (a *analysis) addLabel(ptr, label nodeid) bool { b := a.nodes[ptr].solve.pts.add(label) if b && a.log != nil { fmt.Fprintf(a.log, "\t\tpts(n%d) += n%d\n", ptr, label) } return b }
func (a *analysis) addWork(id nodeid) { a.work.Insert(int(id)) if a.log != nil { fmt.Fprintf(a.log, "\t\twork: n%d\n", id) } }
// onlineCopy adds a copy edge. It is called online, i.e. during
// solving, so it adds edges and pts members directly rather than by
// instantiating a 'constraint'.
//
// The size of the copy is implicitly 1.
// It returns true if pts(dst) changed.
//
func (a *analysis) onlineCopy(dst, src nodeid) bool { if dst != src { if nsrc := a.nodes[src]; nsrc.solve.copyTo.add(dst) { if a.log != nil { fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src) } // TODO(adonovan): most calls to onlineCopy
// 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].solve.pts.addAll(&nsrc.solve.pts) } } return false }
// Returns sizeof.
// Implicitly adds nodes to worklist.
//
// TODO(adonovan): now that we support a.copy() during solving, we
// could eliminate onlineCopyN, but it's much slower. Investigate.
//
func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 { for i := uint32(0); i < sizeof; i++ { if a.onlineCopy(dst, src) { a.addWork(dst) } src++ dst++ } return sizeof }
func (c *loadConstraint) solve(a *analysis, delta *nodeset) { var changed bool for _, x := range delta.AppendTo(a.deltaSpace) { k := nodeid(x) koff := k + nodeid(c.offset) if a.onlineCopy(c.dst, koff) { changed = true } } if changed { a.addWork(c.dst) } }
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) } } }
func (c *offsetAddrConstraint) solve(a *analysis, delta *nodeset) { dst := a.nodes[c.dst] for _, x := range delta.AppendTo(a.deltaSpace) { k := nodeid(x) if dst.solve.pts.add(k + nodeid(c.offset)) { a.addWork(c.dst) } } }
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
// when we start creating indirect tagged objects.
panic("indirect tagged object") }
if types.AssignableTo(tDyn, c.typ) { if a.addLabel(c.dst, ifaceObj) { a.addWork(c.dst) } } } }
func (c *untagConstraint) solve(a *analysis, delta *nodeset) { predicate := types.AssignableTo if c.exact { predicate = types.Identical } 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
// when we start creating indirect tagged objects.
panic("indirect tagged object") }
if predicate(tDyn, c.typ) { // Copy payload sans tag to dst.
//
// TODO(adonovan): opt: if tDyn is
// nonpointerlike we can skip this entire
// constraint, perhaps. We only care about
// pointers among the fields.
a.onlineCopyN(c.dst, v, a.sizeof(tDyn)) } } }
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
// we ever apply invokeConstraints to reflect.Value PTSs,
// e.g. for (reflect.Value).Call.
panic("indirect tagged object") }
// Look up the concrete method.
fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name()) if fn == nil { panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, c.method)) } sig := fn.Signature
fnObj := a.globalobj[fn] // dynamic calls use shared contour
if fnObj == 0 { // a.objectNode(fn) was not called during gen phase.
panic(fmt.Sprintf("a.globalobj[%s]==nil", fn)) }
// Make callsite's fn variable point to identity of
// concrete method. (There's no need to add it to
// worklist since it never has attached constraints.)
a.addLabel(c.params, fnObj)
// Extract value and connect to method's receiver.
// Copy payload to method's receiver param (arg0).
arg0 := a.funcParams(fnObj) recvSize := a.sizeof(sig.Recv().Type()) a.onlineCopyN(arg0, v, recvSize)
src := c.params + 1 // skip past identity
dst := arg0 + nodeid(recvSize)
// Copy caller's argument block to method formal parameters.
paramsSize := a.sizeof(sig.Params()) a.onlineCopyN(dst, src, paramsSize) src += nodeid(paramsSize) dst += nodeid(paramsSize)
// Copy method results to caller's result block.
resultsSize := a.sizeof(sig.Results()) a.onlineCopyN(src, dst, resultsSize) } }
func (c *addrConstraint) solve(a *analysis, delta *nodeset) { panic("addr is not a complex constraint") }
func (c *copyConstraint) solve(a *analysis, delta *nodeset) { panic("copy is not a complex constraint") }
|