github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/pointer/solve.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package pointer
     6  
     7  // This file defines a naive Andersen-style solver for the inclusion
     8  // constraint system.
     9  
    10  import (
    11  	"fmt"
    12  	"go/types"
    13  )
    14  
    15  type solverState struct {
    16  	complex []constraint // complex constraints attached to this node
    17  	copyTo  nodeset      // simple copy constraint edges
    18  	pts     nodeset      // points-to set of this node
    19  	prevPTS nodeset      // pts(n) in previous iteration (for difference propagation)
    20  }
    21  
    22  func (a *analysis) solve() {
    23  	start("Solving")
    24  	if a.log != nil {
    25  		fmt.Fprintf(a.log, "\n\n==== Solving constraints\n\n")
    26  	}
    27  
    28  	// Solver main loop.
    29  	var delta nodeset
    30  	for {
    31  		// Add new constraints to the graph:
    32  		// static constraints from SSA on round 1,
    33  		// dynamic constraints from reflection thereafter.
    34  		a.processNewConstraints()
    35  
    36  		var x int
    37  		if !a.work.TakeMin(&x) {
    38  			break // empty
    39  		}
    40  		id := nodeid(x)
    41  		if a.log != nil {
    42  			fmt.Fprintf(a.log, "\tnode n%d\n", id)
    43  		}
    44  
    45  		n := a.nodes[id]
    46  
    47  		// Difference propagation.
    48  		delta.Difference(&n.solve.pts.Sparse, &n.solve.prevPTS.Sparse)
    49  		if delta.IsEmpty() {
    50  			continue
    51  		}
    52  		if a.log != nil {
    53  			fmt.Fprintf(a.log, "\t\tpts(n%d : %s) = %s + %s\n",
    54  				id, n.typ, &delta, &n.solve.prevPTS)
    55  		}
    56  		n.solve.prevPTS.Copy(&n.solve.pts.Sparse)
    57  
    58  		// Apply all resolution rules attached to n.
    59  		a.solveConstraints(n, &delta)
    60  
    61  		if a.log != nil {
    62  			fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, &n.solve.pts)
    63  		}
    64  	}
    65  
    66  	if !a.nodes[0].solve.pts.IsEmpty() {
    67  		panic(fmt.Sprintf("pts(0) is nonempty: %s", &a.nodes[0].solve.pts))
    68  	}
    69  
    70  	// Release working state (but keep final PTS).
    71  	for _, n := range a.nodes {
    72  		n.solve.complex = nil
    73  		n.solve.copyTo.Clear()
    74  		n.solve.prevPTS.Clear()
    75  	}
    76  
    77  	if a.log != nil {
    78  		fmt.Fprintf(a.log, "Solver done\n")
    79  
    80  		// Dump solution.
    81  		for i, n := range a.nodes {
    82  			if !n.solve.pts.IsEmpty() {
    83  				fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, &n.solve.pts, n.typ)
    84  			}
    85  		}
    86  	}
    87  	stop("Solving")
    88  }
    89  
    90  // processNewConstraints takes the new constraints from a.constraints
    91  // and adds them to the graph, ensuring
    92  // that new constraints are applied to pre-existing labels and
    93  // that pre-existing constraints are applied to new labels.
    94  //
    95  func (a *analysis) processNewConstraints() {
    96  	// Take the slice of new constraints.
    97  	// (May grow during call to solveConstraints.)
    98  	constraints := a.constraints
    99  	a.constraints = nil
   100  
   101  	// Initialize points-to sets from addr-of (base) constraints.
   102  	for _, c := range constraints {
   103  		if c, ok := c.(*addrConstraint); ok {
   104  			dst := a.nodes[c.dst]
   105  			dst.solve.pts.add(c.src)
   106  
   107  			// Populate the worklist with nodes that point to
   108  			// something initially (due to addrConstraints) and
   109  			// have other constraints attached.
   110  			// (A no-op in round 1.)
   111  			if !dst.solve.copyTo.IsEmpty() || len(dst.solve.complex) > 0 {
   112  				a.addWork(c.dst)
   113  			}
   114  		}
   115  	}
   116  
   117  	// Attach simple (copy) and complex constraints to nodes.
   118  	var stale nodeset
   119  	for _, c := range constraints {
   120  		var id nodeid
   121  		switch c := c.(type) {
   122  		case *addrConstraint:
   123  			// base constraints handled in previous loop
   124  			continue
   125  		case *copyConstraint:
   126  			// simple (copy) constraint
   127  			id = c.src
   128  			a.nodes[id].solve.copyTo.add(c.dst)
   129  		default:
   130  			// complex constraint
   131  			id = c.ptr()
   132  			solve := a.nodes[id].solve
   133  			solve.complex = append(solve.complex, c)
   134  		}
   135  
   136  		if n := a.nodes[id]; !n.solve.pts.IsEmpty() {
   137  			if !n.solve.prevPTS.IsEmpty() {
   138  				stale.add(id)
   139  			}
   140  			a.addWork(id)
   141  		}
   142  	}
   143  	// Apply new constraints to pre-existing PTS labels.
   144  	var space [50]int
   145  	for _, id := range stale.AppendTo(space[:0]) {
   146  		n := a.nodes[nodeid(id)]
   147  		a.solveConstraints(n, &n.solve.prevPTS)
   148  	}
   149  }
   150  
   151  // solveConstraints applies each resolution rule attached to node n to
   152  // the set of labels delta.  It may generate new constraints in
   153  // a.constraints.
   154  //
   155  func (a *analysis) solveConstraints(n *node, delta *nodeset) {
   156  	if delta.IsEmpty() {
   157  		return
   158  	}
   159  
   160  	// Process complex constraints dependent on n.
   161  	for _, c := range n.solve.complex {
   162  		if a.log != nil {
   163  			fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
   164  		}
   165  		c.solve(a, delta)
   166  	}
   167  
   168  	// Process copy constraints.
   169  	var copySeen nodeset
   170  	for _, x := range n.solve.copyTo.AppendTo(a.deltaSpace) {
   171  		mid := nodeid(x)
   172  		if copySeen.add(mid) {
   173  			if a.nodes[mid].solve.pts.addAll(delta) {
   174  				a.addWork(mid)
   175  			}
   176  		}
   177  	}
   178  }
   179  
   180  // addLabel adds label to the points-to set of ptr and reports whether the set grew.
   181  func (a *analysis) addLabel(ptr, label nodeid) bool {
   182  	b := a.nodes[ptr].solve.pts.add(label)
   183  	if b && a.log != nil {
   184  		fmt.Fprintf(a.log, "\t\tpts(n%d) += n%d\n", ptr, label)
   185  	}
   186  	return b
   187  }
   188  
   189  func (a *analysis) addWork(id nodeid) {
   190  	a.work.Insert(int(id))
   191  	if a.log != nil {
   192  		fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
   193  	}
   194  }
   195  
   196  // onlineCopy adds a copy edge.  It is called online, i.e. during
   197  // solving, so it adds edges and pts members directly rather than by
   198  // instantiating a 'constraint'.
   199  //
   200  // The size of the copy is implicitly 1.
   201  // It returns true if pts(dst) changed.
   202  //
   203  func (a *analysis) onlineCopy(dst, src nodeid) bool {
   204  	if dst != src {
   205  		if nsrc := a.nodes[src]; nsrc.solve.copyTo.add(dst) {
   206  			if a.log != nil {
   207  				fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src)
   208  			}
   209  			// TODO(adonovan): most calls to onlineCopy
   210  			// are followed by addWork, possibly batched
   211  			// via a 'changed' flag; see if there's a
   212  			// noticeable penalty to calling addWork here.
   213  			return a.nodes[dst].solve.pts.addAll(&nsrc.solve.pts)
   214  		}
   215  	}
   216  	return false
   217  }
   218  
   219  // Returns sizeof.
   220  // Implicitly adds nodes to worklist.
   221  //
   222  // TODO(adonovan): now that we support a.copy() during solving, we
   223  // could eliminate onlineCopyN, but it's much slower.  Investigate.
   224  //
   225  func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
   226  	for i := uint32(0); i < sizeof; i++ {
   227  		if a.onlineCopy(dst, src) {
   228  			a.addWork(dst)
   229  		}
   230  		src++
   231  		dst++
   232  	}
   233  	return sizeof
   234  }
   235  
   236  func (c *loadConstraint) solve(a *analysis, delta *nodeset) {
   237  	var changed bool
   238  	for _, x := range delta.AppendTo(a.deltaSpace) {
   239  		k := nodeid(x)
   240  		koff := k + nodeid(c.offset)
   241  		if a.onlineCopy(c.dst, koff) {
   242  			changed = true
   243  		}
   244  	}
   245  	if changed {
   246  		a.addWork(c.dst)
   247  	}
   248  }
   249  
   250  func (c *storeConstraint) solve(a *analysis, delta *nodeset) {
   251  	for _, x := range delta.AppendTo(a.deltaSpace) {
   252  		k := nodeid(x)
   253  		koff := k + nodeid(c.offset)
   254  		if a.onlineCopy(koff, c.src) {
   255  			a.addWork(koff)
   256  		}
   257  	}
   258  }
   259  
   260  func (c *offsetAddrConstraint) solve(a *analysis, delta *nodeset) {
   261  	dst := a.nodes[c.dst]
   262  	for _, x := range delta.AppendTo(a.deltaSpace) {
   263  		k := nodeid(x)
   264  		if dst.solve.pts.add(k + nodeid(c.offset)) {
   265  			a.addWork(c.dst)
   266  		}
   267  	}
   268  }
   269  
   270  func (c *typeFilterConstraint) solve(a *analysis, delta *nodeset) {
   271  	for _, x := range delta.AppendTo(a.deltaSpace) {
   272  		ifaceObj := nodeid(x)
   273  		tDyn, _, indirect := a.taggedValue(ifaceObj)
   274  		if indirect {
   275  			// TODO(adonovan): we'll need to implement this
   276  			// when we start creating indirect tagged objects.
   277  			panic("indirect tagged object")
   278  		}
   279  
   280  		if types.AssignableTo(tDyn, c.typ) {
   281  			if a.addLabel(c.dst, ifaceObj) {
   282  				a.addWork(c.dst)
   283  			}
   284  		}
   285  	}
   286  }
   287  
   288  func (c *untagConstraint) solve(a *analysis, delta *nodeset) {
   289  	predicate := types.AssignableTo
   290  	if c.exact {
   291  		predicate = types.Identical
   292  	}
   293  	for _, x := range delta.AppendTo(a.deltaSpace) {
   294  		ifaceObj := nodeid(x)
   295  		tDyn, v, indirect := a.taggedValue(ifaceObj)
   296  		if indirect {
   297  			// TODO(adonovan): we'll need to implement this
   298  			// when we start creating indirect tagged objects.
   299  			panic("indirect tagged object")
   300  		}
   301  
   302  		if predicate(tDyn, c.typ) {
   303  			// Copy payload sans tag to dst.
   304  			//
   305  			// TODO(adonovan): opt: if tDyn is
   306  			// nonpointerlike we can skip this entire
   307  			// constraint, perhaps.  We only care about
   308  			// pointers among the fields.
   309  			a.onlineCopyN(c.dst, v, a.sizeof(tDyn))
   310  		}
   311  	}
   312  }
   313  
   314  func (c *invokeConstraint) solve(a *analysis, delta *nodeset) {
   315  	for _, x := range delta.AppendTo(a.deltaSpace) {
   316  		ifaceObj := nodeid(x)
   317  		tDyn, v, indirect := a.taggedValue(ifaceObj)
   318  		if indirect {
   319  			// TODO(adonovan): we may need to implement this if
   320  			// we ever apply invokeConstraints to reflect.Value PTSs,
   321  			// e.g. for (reflect.Value).Call.
   322  			panic("indirect tagged object")
   323  		}
   324  
   325  		// Look up the concrete method.
   326  		fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name())
   327  		if fn == nil {
   328  			panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, c.method))
   329  		}
   330  		sig := fn.Signature
   331  
   332  		fnObj := a.globalobj[fn] // dynamic calls use shared contour
   333  		if fnObj == 0 {
   334  			// a.objectNode(fn) was not called during gen phase.
   335  			panic(fmt.Sprintf("a.globalobj[%s]==nil", fn))
   336  		}
   337  
   338  		// Make callsite's fn variable point to identity of
   339  		// concrete method.  (There's no need to add it to
   340  		// worklist since it never has attached constraints.)
   341  		a.addLabel(c.params, fnObj)
   342  
   343  		// Extract value and connect to method's receiver.
   344  		// Copy payload to method's receiver param (arg0).
   345  		arg0 := a.funcParams(fnObj)
   346  		recvSize := a.sizeof(sig.Recv().Type())
   347  		a.onlineCopyN(arg0, v, recvSize)
   348  
   349  		src := c.params + 1 // skip past identity
   350  		dst := arg0 + nodeid(recvSize)
   351  
   352  		// Copy caller's argument block to method formal parameters.
   353  		paramsSize := a.sizeof(sig.Params())
   354  		a.onlineCopyN(dst, src, paramsSize)
   355  		src += nodeid(paramsSize)
   356  		dst += nodeid(paramsSize)
   357  
   358  		// Copy method results to caller's result block.
   359  		resultsSize := a.sizeof(sig.Results())
   360  		a.onlineCopyN(src, dst, resultsSize)
   361  	}
   362  }
   363  
   364  func (c *addrConstraint) solve(a *analysis, delta *nodeset) {
   365  	panic("addr is not a complex constraint")
   366  }
   367  
   368  func (c *copyConstraint) solve(a *analysis, delta *nodeset) {
   369  	panic("copy is not a complex constraint")
   370  }