970 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			970 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
| package pointer
 | |
| 
 | |
| // This file implements Hash-Value Numbering (HVN), a pre-solver
 | |
| // constraint optimization described in Hardekopf & Lin, SAS'07 (see
 | |
| // doc.go) that analyses the graph topology to determine which sets of
 | |
| // variables are "pointer equivalent" (PE), i.e. must have identical
 | |
| // points-to sets in the solution.
 | |
| //
 | |
| // A separate ("offline") graph is constructed.  Its nodes are those of
 | |
| // the main-graph, plus an additional node *X for each pointer node X.
 | |
| // With this graph we can reason about the unknown points-to set of
 | |
| // dereferenced pointers.  (We do not generalize this to represent
 | |
| // unknown fields x->f, perhaps because such fields would be numerous,
 | |
| // though it might be worth an experiment.)
 | |
| //
 | |
| // Nodes whose points-to relations are not entirely captured by the
 | |
| // graph are marked as "indirect": the *X nodes, the parameters of
 | |
| // address-taken functions (which includes all functions in method
 | |
| // sets), or nodes updated by the solver rules for reflection, etc.
 | |
| //
 | |
| // All addr (y=&x) nodes are initially assigned a pointer-equivalence
 | |
| // (PE) label equal to x's nodeid in the main graph.  (These are the
 | |
| // only PE labels that are less than len(a.nodes).)
 | |
| //
 | |
| // All offsetAddr (y=&x.f) constraints are initially assigned a PE
 | |
| // label; such labels are memoized, keyed by (x, f), so that equivalent
 | |
| // nodes y as assigned the same label.
 | |
| //
 | |
| // Then we process each strongly connected component (SCC) of the graph
 | |
| // in topological order, assigning it a PE label based on the set P of
 | |
| // PE labels that flow to it from its immediate dependencies.
 | |
| //
 | |
| // If any node in P is "indirect", the entire SCC is assigned a fresh PE
 | |
| // label.  Otherwise:
 | |
| //
 | |
| // |P|=0  if P is empty, all nodes in the SCC are non-pointers (e.g.
 | |
| //        uninitialized variables, or formal params of dead functions)
 | |
| //        and the SCC is assigned the PE label of zero.
 | |
| //
 | |
| // |P|=1  if P is a singleton, the SCC is assigned the same label as the
 | |
| //        sole element of P.
 | |
| //
 | |
| // |P|>1  if P contains multiple labels, a unique label representing P is
 | |
| //        invented and recorded in an hash table, so that other
 | |
| //        equivalent SCCs may also be assigned this label, akin to
 | |
| //        conventional hash-value numbering in a compiler.
 | |
| //
 | |
| // Finally, a renumbering is computed such that each node is replaced by
 | |
| // the lowest-numbered node with the same PE label.  All constraints are
 | |
| // renumbered, and any resulting duplicates are eliminated.
 | |
| //
 | |
| // The only nodes that are not renumbered are the objects x in addr
 | |
| // (y=&x) constraints, since the ids of these nodes (and fields derived
 | |
| // from them via offsetAddr rules) are the elements of all points-to
 | |
| // sets, so they must remain as they are if we want the same solution.
 | |
| //
 | |
| // The solverStates (node.solve) for nodes in the same equivalence class
 | |
| // are linked together so that all nodes in the class have the same
 | |
| // solution.  This avoids the need to renumber nodeids buried in
 | |
| // Queries, cgnodes, etc (like (*analysis).renumber() does) since only
 | |
| // the solution is needed.
 | |
| //
 | |
| // The result of HVN is that the number of distinct nodes and
 | |
| // constraints is reduced, but the solution is identical (almost---see
 | |
| // CROSS-CHECK below).  In particular, both linear and cyclic chains of
 | |
| // copies are each replaced by a single node.
 | |
| //
 | |
| // Nodes and constraints created "online" (e.g. while solving reflection
 | |
| // constraints) are not subject to this optimization.
 | |
| //
 | |
| // PERFORMANCE
 | |
| //
 | |
| // In two benchmarks (oracle and godoc), HVN eliminates about two thirds
 | |
| // of nodes, the majority accounted for by non-pointers: nodes of
 | |
| // non-pointer type, pointers that remain nil, formal parameters of dead
 | |
| // functions, nodes of untracked types, etc.  It also reduces the number
 | |
| // of constraints, also by about two thirds, and the solving time by
 | |
| // 30--42%, although we must pay about 15% for the running time of HVN
 | |
| // itself.  The benefit is greater for larger applications.
 | |
| //
 | |
| // There are many possible optimizations to improve the performance:
 | |
| // * Use fewer than 1:1 onodes to main graph nodes: many of the onodes
 | |
| //   we create are not needed.
 | |
| // * HU (HVN with Union---see paper): coalesce "union" peLabels when
 | |
| //   their expanded-out sets are equal.
 | |
| // * HR (HVN with deReference---see paper): this will require that we
 | |
| //   apply HVN until fixed point, which may need more bookkeeping of the
 | |
| //   correspondance of main nodes to onodes.
 | |
| // * Location Equivalence (see paper): have points-to sets contain not
 | |
| //   locations but location-equivalence class labels, each representing
 | |
| //   a set of locations.
 | |
| // * HVN with field-sensitive ref: model each of the fields of a
 | |
| //   pointer-to-struct.
 | |
| //
 | |
| // CROSS-CHECK
 | |
| //
 | |
| // To verify the soundness of the optimization, when the
 | |
| // debugHVNCrossCheck option is enabled, we run the solver twice, once
 | |
| // before and once after running HVN, dumping the solution to disk, and
 | |
| // then we compare the results.  If they are not identical, the analysis
 | |
| // panics.
 | |
| //
 | |
| // The solution dumped to disk includes only the N*N submatrix of the
 | |
| // complete solution where N is the number of nodes after generation.
 | |
| // In other words, we ignore pointer variables and objects created by
 | |
| // the solver itself, since their numbering depends on the solver order,
 | |
| // which is affected by the optimization.  In any case, that's the only
 | |
| // part the client cares about.
 | |
| //
 | |
| // The cross-check is too strict and may fail spuriously.  Although the
 | |
| // H&L paper describing HVN states that the solutions obtained should be
 | |
| // identical, this is not the case in practice because HVN can collapse
 | |
| // cycles involving *p even when pts(p)={}.  Consider this example
 | |
| // distilled from testdata/hello.go:
 | |
| //
 | |
| //	var x T
 | |
| //	func f(p **T) {
 | |
| //		t0 = *p
 | |
| //		...
 | |
| //		t1 = φ(t0, &x)
 | |
| //		*p = t1
 | |
| //	}
 | |
| //
 | |
| // If f is dead code, we get:
 | |
| // 	unoptimized:  pts(p)={} pts(t0)={} pts(t1)={&x}
 | |
| // 	optimized:    pts(p)={} pts(t0)=pts(t1)=pts(*p)={&x}
 | |
| //
 | |
| // It's hard to argue that this is a bug: the result is sound and the
 | |
| // loss of precision is inconsequential---f is dead code, after all.
 | |
| // But unfortunately it limits the usefulness of the cross-check since
 | |
| // failures must be carefully analyzed.  Ben Hardekopf suggests (in
 | |
| // personal correspondence) some approaches to mitigating it:
 | |
| //
 | |
| // 	If there is a node with an HVN points-to set that is a superset
 | |
| // 	of the NORM points-to set, then either it's a bug or it's a
 | |
| // 	result of this issue. If it's a result of this issue, then in
 | |
| // 	the offline constraint graph there should be a REF node inside
 | |
| // 	some cycle that reaches this node, and in the NORM solution the
 | |
| // 	pointer being dereferenced by that REF node should be the empty
 | |
| // 	set. If that isn't true then this is a bug. If it is true, then
 | |
| // 	you can further check that in the NORM solution the "extra"
 | |
| // 	points-to info in the HVN solution does in fact come from that
 | |
| // 	purported cycle (if it doesn't, then this is still a bug). If
 | |
| // 	you're doing the further check then you'll need to do it for
 | |
| // 	each "extra" points-to element in the HVN points-to set.
 | |
| //
 | |
| // 	There are probably ways to optimize these checks by taking
 | |
| // 	advantage of graph properties. For example, extraneous points-to
 | |
| // 	info will flow through the graph and end up in many
 | |
| // 	nodes. Rather than checking every node with extra info, you
 | |
| // 	could probably work out the "origin point" of the extra info and
 | |
| // 	just check there. Note that the check in the first bullet is
 | |
| // 	looking for soundness bugs, while the check in the second bullet
 | |
| // 	is looking for precision bugs; depending on your needs, you may
 | |
| // 	care more about one than the other.
 | |
| //
 | |
| // which we should evaluate.  The cross-check is nonetheless invaluable
 | |
| // for all but one of the programs in the pointer_test suite.
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"reflect"
 | |
| 
 | |
| 	"golang.org/x/tools/container/intsets"
 | |
| 	"golang.org/x/tools/go/types"
 | |
| )
 | |
| 
 | |
| // A peLabel is a pointer-equivalence label: two nodes with the same
 | |
| // peLabel have identical points-to solutions.
 | |
| //
 | |
| // The numbers are allocated consecutively like so:
 | |
| // 	0	not a pointer
 | |
| //	1..N-1	addrConstraints (equals the constraint's .src field, hence sparse)
 | |
| //	...	offsetAddr constraints
 | |
| //	...	SCCs (with indirect nodes or multiple inputs)
 | |
| //
 | |
| // Each PE label denotes a set of pointers containing a single addr, a
 | |
| // single offsetAddr, or some set of other PE labels.
 | |
| //
 | |
| type peLabel int
 | |
| 
 | |
| type hvn struct {
 | |
| 	a        *analysis
 | |
| 	N        int                // len(a.nodes) immediately after constraint generation
 | |
| 	log      io.Writer          // (optional) log of HVN lemmas
 | |
| 	onodes   []*onode           // nodes of the offline graph
 | |
| 	label    peLabel            // the next available PE label
 | |
| 	hvnLabel map[string]peLabel // hash-value numbering (PE label) for each set of onodeids
 | |
| 	stack    []onodeid          // DFS stack
 | |
| 	index    int32              // next onode.index, from Tarjan's SCC algorithm
 | |
| 
 | |
| 	// For each distinct offsetAddrConstraint (src, offset) pair,
 | |
| 	// offsetAddrLabels records a unique PE label >= N.
 | |
| 	offsetAddrLabels map[offsetAddr]peLabel
 | |
| }
 | |
| 
 | |
| // The index of an node in the offline graph.
 | |
| // (Currently the first N align with the main nodes,
 | |
| // but this may change with HRU.)
 | |
| type onodeid uint32
 | |
| 
 | |
| // An onode is a node in the offline constraint graph.
 | |
| // (Where ambiguous, members of analysis.nodes are referred to as
 | |
| // "main graph" nodes.)
 | |
| //
 | |
| // Edges in the offline constraint graph (edges and implicit) point to
 | |
| // the source, i.e. against the flow of values: they are dependencies.
 | |
| // Implicit edges are used for SCC computation, but not for gathering
 | |
| // incoming labels.
 | |
| //
 | |
| type onode struct {
 | |
| 	rep onodeid // index of representative of SCC in offline constraint graph
 | |
| 
 | |
| 	edges    intsets.Sparse // constraint edges X-->Y (this onode is X)
 | |
| 	implicit intsets.Sparse // implicit edges *X-->*Y (this onode is X)
 | |
| 	peLabels intsets.Sparse // set of peLabels are pointer-equivalent to this one
 | |
| 	indirect bool           // node has points-to relations not represented in graph
 | |
| 
 | |
| 	// Tarjan's SCC algorithm
 | |
| 	index, lowlink int32 // Tarjan numbering
 | |
| 	scc            int32 // -ve => on stack; 0 => unvisited; +ve => node is root of a found SCC
 | |
| }
 | |
| 
 | |
| type offsetAddr struct {
 | |
| 	ptr    nodeid
 | |
| 	offset uint32
 | |
| }
 | |
| 
 | |
| // nextLabel issues the next unused pointer-equivalence label.
 | |
| func (h *hvn) nextLabel() peLabel {
 | |
| 	h.label++
 | |
| 	return h.label
 | |
| }
 | |
| 
 | |
| // ref(X) returns the index of the onode for *X.
 | |
| func (h *hvn) ref(id onodeid) onodeid {
 | |
| 	return id + onodeid(len(h.a.nodes))
 | |
| }
 | |
| 
 | |
| // hvn computes pointer-equivalence labels (peLabels) using the Hash-based
 | |
| // Value Numbering (HVN) algorithm described in Hardekopf & Lin, SAS'07.
 | |
| //
 | |
| func (a *analysis) hvn() {
 | |
| 	start("HVN")
 | |
| 
 | |
| 	if a.log != nil {
 | |
| 		fmt.Fprintf(a.log, "\n\n==== Pointer equivalence optimization\n\n")
 | |
| 	}
 | |
| 
 | |
| 	h := hvn{
 | |
| 		a:                a,
 | |
| 		N:                len(a.nodes),
 | |
| 		log:              a.log,
 | |
| 		hvnLabel:         make(map[string]peLabel),
 | |
| 		offsetAddrLabels: make(map[offsetAddr]peLabel),
 | |
| 	}
 | |
| 
 | |
| 	if h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\nCreating offline graph nodes...\n")
 | |
| 	}
 | |
| 
 | |
| 	// Create offline nodes.  The first N nodes correspond to main
 | |
| 	// graph nodes; the next N are their corresponding ref() nodes.
 | |
| 	h.onodes = make([]*onode, 2*h.N)
 | |
| 	for id := range a.nodes {
 | |
| 		id := onodeid(id)
 | |
| 		h.onodes[id] = &onode{}
 | |
| 		h.onodes[h.ref(id)] = &onode{indirect: true}
 | |
| 	}
 | |
| 
 | |
| 	// Each node initially represents just itself.
 | |
| 	for id, o := range h.onodes {
 | |
| 		o.rep = onodeid(id)
 | |
| 	}
 | |
| 
 | |
| 	h.markIndirectNodes()
 | |
| 
 | |
| 	// Reserve the first N PE labels for addrConstraints.
 | |
| 	h.label = peLabel(h.N)
 | |
| 
 | |
| 	// Add offline constraint edges.
 | |
| 	if h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\nAdding offline graph edges...\n")
 | |
| 	}
 | |
| 	for _, c := range a.constraints {
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "; %s\n", c)
 | |
| 		}
 | |
| 		c.presolve(&h)
 | |
| 	}
 | |
| 
 | |
| 	// Find and collapse SCCs.
 | |
| 	if h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\nFinding SCCs...\n")
 | |
| 	}
 | |
| 	h.index = 1
 | |
| 	for id, o := range h.onodes {
 | |
| 		if id > 0 && o.index == 0 {
 | |
| 			// Start depth-first search at each unvisited node.
 | |
| 			h.visit(onodeid(id))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Dump the solution
 | |
| 	// (NB: somewhat redundant with logging from simplify().)
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\nPointer equivalences:\n")
 | |
| 		for id, o := range h.onodes {
 | |
| 			if id == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			if id == int(h.N) {
 | |
| 				fmt.Fprintf(h.log, "---\n")
 | |
| 			}
 | |
| 			fmt.Fprintf(h.log, "o%d\t", id)
 | |
| 			if o.rep != onodeid(id) {
 | |
| 				fmt.Fprintf(h.log, "rep=o%d", o.rep)
 | |
| 			} else {
 | |
| 				fmt.Fprintf(h.log, "p%d", o.peLabels.Min())
 | |
| 				if o.indirect {
 | |
| 					fmt.Fprint(h.log, " indirect")
 | |
| 				}
 | |
| 			}
 | |
| 			fmt.Fprintln(h.log)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Simplify the main constraint graph
 | |
| 	h.simplify()
 | |
| 
 | |
| 	a.showCounts()
 | |
| 
 | |
| 	stop("HVN")
 | |
| }
 | |
| 
 | |
| // ---- constraint-specific rules ----
 | |
| 
 | |
| // dst := &src
 | |
| func (c *addrConstraint) presolve(h *hvn) {
 | |
| 	// Each object (src) is an initial PE label.
 | |
| 	label := peLabel(c.src) // label < N
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		// duplicate log messages are possible
 | |
| 		fmt.Fprintf(h.log, "\tcreate p%d: {&n%d}\n", label, c.src)
 | |
| 	}
 | |
| 	odst := onodeid(c.dst)
 | |
| 	osrc := onodeid(c.src)
 | |
| 
 | |
| 	// Assign dst this label.
 | |
| 	h.onodes[odst].peLabels.Insert(int(label))
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d has p%d\n", odst, label)
 | |
| 	}
 | |
| 
 | |
| 	h.addImplicitEdge(h.ref(odst), osrc) // *dst ~~> src.
 | |
| }
 | |
| 
 | |
| // dst = src
 | |
| func (c *copyConstraint) presolve(h *hvn) {
 | |
| 	odst := onodeid(c.dst)
 | |
| 	osrc := onodeid(c.src)
 | |
| 	h.addEdge(odst, osrc)                       //  dst -->  src
 | |
| 	h.addImplicitEdge(h.ref(odst), h.ref(osrc)) // *dst ~~> *src
 | |
| }
 | |
| 
 | |
| // dst = *src + offset
 | |
| func (c *loadConstraint) presolve(h *hvn) {
 | |
| 	odst := onodeid(c.dst)
 | |
| 	osrc := onodeid(c.src)
 | |
| 	if c.offset == 0 {
 | |
| 		h.addEdge(odst, h.ref(osrc)) // dst --> *src
 | |
| 	} else {
 | |
| 		// We don't interpret load-with-offset, e.g. results
 | |
| 		// of map value lookup, R-block of dynamic call, slice
 | |
| 		// copy/append, reflection.
 | |
| 		h.markIndirect(odst, "load with offset")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // *dst + offset = src
 | |
| func (c *storeConstraint) presolve(h *hvn) {
 | |
| 	odst := onodeid(c.dst)
 | |
| 	osrc := onodeid(c.src)
 | |
| 	if c.offset == 0 {
 | |
| 		h.onodes[h.ref(odst)].edges.Insert(int(osrc)) // *dst --> src
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "\to%d --> o%d\n", h.ref(odst), osrc)
 | |
| 		}
 | |
| 	} else {
 | |
| 		// We don't interpret store-with-offset.
 | |
| 		// See discussion of soundness at markIndirectNodes.
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dst = &src.offset
 | |
| func (c *offsetAddrConstraint) presolve(h *hvn) {
 | |
| 	// Give each distinct (addr, offset) pair a fresh PE label.
 | |
| 	// The cache performs CSE, effectively.
 | |
| 	key := offsetAddr{c.src, c.offset}
 | |
| 	label, ok := h.offsetAddrLabels[key]
 | |
| 	if !ok {
 | |
| 		label = h.nextLabel()
 | |
| 		h.offsetAddrLabels[key] = label
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "\tcreate p%d: {&n%d.#%d}\n",
 | |
| 				label, c.src, c.offset)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Assign dst this label.
 | |
| 	h.onodes[c.dst].peLabels.Insert(int(label))
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d has p%d\n", c.dst, label)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dst = src.(typ)  where typ is an interface
 | |
| func (c *typeFilterConstraint) presolve(h *hvn) {
 | |
| 	h.markIndirect(onodeid(c.dst), "typeFilter result")
 | |
| }
 | |
| 
 | |
| // dst = src.(typ)  where typ is concrete
 | |
| func (c *untagConstraint) presolve(h *hvn) {
 | |
| 	odst := onodeid(c.dst)
 | |
| 	for end := odst + onodeid(h.a.sizeof(c.typ)); odst < end; odst++ {
 | |
| 		h.markIndirect(odst, "untag result")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dst = src.method(c.params...)
 | |
| func (c *invokeConstraint) presolve(h *hvn) {
 | |
| 	// All methods are address-taken functions, so
 | |
| 	// their formal P-blocks were already marked indirect.
 | |
| 
 | |
| 	// Mark the caller's targets node as indirect.
 | |
| 	sig := c.method.Type().(*types.Signature)
 | |
| 	id := c.params
 | |
| 	h.markIndirect(onodeid(c.params), "invoke targets node")
 | |
| 	id++
 | |
| 
 | |
| 	id += nodeid(h.a.sizeof(sig.Params()))
 | |
| 
 | |
| 	// Mark the caller's R-block as indirect.
 | |
| 	end := id + nodeid(h.a.sizeof(sig.Results()))
 | |
| 	for id < end {
 | |
| 		h.markIndirect(onodeid(id), "invoke R-block")
 | |
| 		id++
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // markIndirectNodes marks as indirect nodes whose points-to relations
 | |
| // are not entirely captured by the offline graph, including:
 | |
| //
 | |
| //    (a) All address-taken nodes (including the following nodes within
 | |
| //        the same object).  This is described in the paper.
 | |
| //
 | |
| // The most subtle cause of indirect nodes is the generation of
 | |
| // store-with-offset constraints since the offline graph doesn't
 | |
| // represent them.  A global audit of constraint generation reveals the
 | |
| // following uses of store-with-offset:
 | |
| //
 | |
| //    (b) genDynamicCall, for P-blocks of dynamically called functions,
 | |
| //        to which dynamic copy edges will be added to them during
 | |
| //        solving: from storeConstraint for standalone functions,
 | |
| //        and from invokeConstraint for methods.
 | |
| //        All such P-blocks must be marked indirect.
 | |
| //    (c) MakeUpdate, to update the value part of a map object.
 | |
| //        All MakeMap objects's value parts must be marked indirect.
 | |
| //    (d) copyElems, to update the destination array.
 | |
| //        All array elements must be marked indirect.
 | |
| //
 | |
| // Not all indirect marking happens here.  ref() nodes are marked
 | |
| // indirect at construction, and each constraint's presolve() method may
 | |
| // mark additional nodes.
 | |
| //
 | |
| func (h *hvn) markIndirectNodes() {
 | |
| 	// (a) all address-taken nodes, plus all nodes following them
 | |
| 	//     within the same object, since these may be indirectly
 | |
| 	//     stored or address-taken.
 | |
| 	for _, c := range h.a.constraints {
 | |
| 		if c, ok := c.(*addrConstraint); ok {
 | |
| 			start := h.a.enclosingObj(c.src)
 | |
| 			end := start + nodeid(h.a.nodes[start].obj.size)
 | |
| 			for id := c.src; id < end; id++ {
 | |
| 				h.markIndirect(onodeid(id), "A-T object")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// (b) P-blocks of all address-taken functions.
 | |
| 	for id := 0; id < h.N; id++ {
 | |
| 		obj := h.a.nodes[id].obj
 | |
| 
 | |
| 		// TODO(adonovan): opt: if obj.cgn.fn is a method and
 | |
| 		// obj.cgn is not its shared contour, this is an
 | |
| 		// "inlined" static method call.  We needn't consider it
 | |
| 		// address-taken since no invokeConstraint will affect it.
 | |
| 
 | |
| 		if obj != nil && obj.flags&otFunction != 0 && h.a.atFuncs[obj.cgn.fn] {
 | |
| 			// address-taken function
 | |
| 			if debugHVNVerbose && h.log != nil {
 | |
| 				fmt.Fprintf(h.log, "n%d is address-taken: %s\n", id, obj.cgn.fn)
 | |
| 			}
 | |
| 			h.markIndirect(onodeid(id), "A-T func identity")
 | |
| 			id++
 | |
| 			sig := obj.cgn.fn.Signature
 | |
| 			psize := h.a.sizeof(sig.Params())
 | |
| 			if sig.Recv() != nil {
 | |
| 				psize += h.a.sizeof(sig.Recv().Type())
 | |
| 			}
 | |
| 			for end := id + int(psize); id < end; id++ {
 | |
| 				h.markIndirect(onodeid(id), "A-T func P-block")
 | |
| 			}
 | |
| 			id--
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// (c) all map objects' value fields.
 | |
| 	for _, id := range h.a.mapValues {
 | |
| 		h.markIndirect(onodeid(id), "makemap.value")
 | |
| 	}
 | |
| 
 | |
| 	// (d) all array element objects.
 | |
| 	// TODO(adonovan): opt: can we do better?
 | |
| 	for id := 0; id < h.N; id++ {
 | |
| 		// Identity node for an object of array type?
 | |
| 		if tArray, ok := h.a.nodes[id].typ.(*types.Array); ok {
 | |
| 			// Mark the array element nodes indirect.
 | |
| 			// (Skip past the identity field.)
 | |
| 			for _ = range h.a.flatten(tArray.Elem()) {
 | |
| 				id++
 | |
| 				h.markIndirect(onodeid(id), "array elem")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (h *hvn) markIndirect(oid onodeid, comment string) {
 | |
| 	h.onodes[oid].indirect = true
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d is indirect: %s\n", oid, comment)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Adds an edge dst-->src.
 | |
| // Note the unusual convention: edges are dependency (contraflow) edges.
 | |
| func (h *hvn) addEdge(odst, osrc onodeid) {
 | |
| 	h.onodes[odst].edges.Insert(int(osrc))
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d --> o%d\n", odst, osrc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (h *hvn) addImplicitEdge(odst, osrc onodeid) {
 | |
| 	h.onodes[odst].implicit.Insert(int(osrc))
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d ~~> o%d\n", odst, osrc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // visit implements the depth-first search of Tarjan's SCC algorithm.
 | |
| // Precondition: x is canonical.
 | |
| func (h *hvn) visit(x onodeid) {
 | |
| 	h.checkCanonical(x)
 | |
| 	xo := h.onodes[x]
 | |
| 	xo.index = h.index
 | |
| 	xo.lowlink = h.index
 | |
| 	h.index++
 | |
| 
 | |
| 	h.stack = append(h.stack, x) // push
 | |
| 	assert(xo.scc == 0, "node revisited")
 | |
| 	xo.scc = -1
 | |
| 
 | |
| 	var deps []int
 | |
| 	deps = xo.edges.AppendTo(deps)
 | |
| 	deps = xo.implicit.AppendTo(deps)
 | |
| 
 | |
| 	for _, y := range deps {
 | |
| 		// Loop invariant: x is canonical.
 | |
| 
 | |
| 		y := h.find(onodeid(y))
 | |
| 
 | |
| 		if x == y {
 | |
| 			continue // nodes already coalesced
 | |
| 		}
 | |
| 
 | |
| 		xo := h.onodes[x]
 | |
| 		yo := h.onodes[y]
 | |
| 
 | |
| 		switch {
 | |
| 		case yo.scc > 0:
 | |
| 			// y is already a collapsed SCC
 | |
| 
 | |
| 		case yo.scc < 0:
 | |
| 			// y is on the stack, and thus in the current SCC.
 | |
| 			if yo.index < xo.lowlink {
 | |
| 				xo.lowlink = yo.index
 | |
| 			}
 | |
| 
 | |
| 		default:
 | |
| 			// y is unvisited; visit it now.
 | |
| 			h.visit(y)
 | |
| 			// Note: x and y are now non-canonical.
 | |
| 
 | |
| 			x = h.find(onodeid(x))
 | |
| 
 | |
| 			if yo.lowlink < xo.lowlink {
 | |
| 				xo.lowlink = yo.lowlink
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	h.checkCanonical(x)
 | |
| 
 | |
| 	// Is x the root of an SCC?
 | |
| 	if xo.lowlink == xo.index {
 | |
| 		// Coalesce all nodes in the SCC.
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "scc o%d\n", x)
 | |
| 		}
 | |
| 		for {
 | |
| 			// Pop y from stack.
 | |
| 			i := len(h.stack) - 1
 | |
| 			y := h.stack[i]
 | |
| 			h.stack = h.stack[:i]
 | |
| 
 | |
| 			h.checkCanonical(x)
 | |
| 			xo := h.onodes[x]
 | |
| 			h.checkCanonical(y)
 | |
| 			yo := h.onodes[y]
 | |
| 
 | |
| 			if xo == yo {
 | |
| 				// SCC is complete.
 | |
| 				xo.scc = 1
 | |
| 				h.labelSCC(x)
 | |
| 				break
 | |
| 			}
 | |
| 			h.coalesce(x, y)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Precondition: x is canonical.
 | |
| func (h *hvn) labelSCC(x onodeid) {
 | |
| 	h.checkCanonical(x)
 | |
| 	xo := h.onodes[x]
 | |
| 	xpe := &xo.peLabels
 | |
| 
 | |
| 	// All indirect nodes get new labels.
 | |
| 	if xo.indirect {
 | |
| 		label := h.nextLabel()
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "\tcreate p%d: indirect SCC\n", label)
 | |
| 			fmt.Fprintf(h.log, "\to%d has p%d\n", x, label)
 | |
| 		}
 | |
| 
 | |
| 		// Remove pre-labeling, in case a direct pre-labeled node was
 | |
| 		// merged with an indirect one.
 | |
| 		xpe.Clear()
 | |
| 		xpe.Insert(int(label))
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Invariant: all peLabels sets are non-empty.
 | |
| 	// Those that are logically empty contain zero as their sole element.
 | |
| 	// No other sets contains zero.
 | |
| 
 | |
| 	// Find all labels coming in to the coalesced SCC node.
 | |
| 	for _, y := range xo.edges.AppendTo(nil) {
 | |
| 		y := h.find(onodeid(y))
 | |
| 		if y == x {
 | |
| 			continue // already coalesced
 | |
| 		}
 | |
| 		ype := &h.onodes[y].peLabels
 | |
| 		if debugHVNVerbose && h.log != nil {
 | |
| 			fmt.Fprintf(h.log, "\tedge from o%d = %s\n", y, ype)
 | |
| 		}
 | |
| 
 | |
| 		if ype.IsEmpty() {
 | |
| 			if debugHVNVerbose && h.log != nil {
 | |
| 				fmt.Fprintf(h.log, "\tnode has no PE label\n")
 | |
| 			}
 | |
| 		}
 | |
| 		assert(!ype.IsEmpty(), "incoming node has no PE label")
 | |
| 
 | |
| 		if ype.Has(0) {
 | |
| 			// {0} represents a non-pointer.
 | |
| 			assert(ype.Len() == 1, "PE set contains {0, ...}")
 | |
| 		} else {
 | |
| 			xpe.UnionWith(ype)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch xpe.Len() {
 | |
| 	case 0:
 | |
| 		// SCC has no incoming non-zero PE labels: it is a non-pointer.
 | |
| 		xpe.Insert(0)
 | |
| 
 | |
| 	case 1:
 | |
| 		// already a singleton
 | |
| 
 | |
| 	default:
 | |
| 		// SCC has multiple incoming non-zero PE labels.
 | |
| 		// Find the canonical label representing this set.
 | |
| 		// We use String() as a fingerprint consistent with Equals().
 | |
| 		key := xpe.String()
 | |
| 		label, ok := h.hvnLabel[key]
 | |
| 		if !ok {
 | |
| 			label = h.nextLabel()
 | |
| 			if debugHVNVerbose && h.log != nil {
 | |
| 				fmt.Fprintf(h.log, "\tcreate p%d: union %s\n", label, xpe.String())
 | |
| 			}
 | |
| 			h.hvnLabel[key] = label
 | |
| 		}
 | |
| 		xpe.Clear()
 | |
| 		xpe.Insert(int(label))
 | |
| 	}
 | |
| 
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\to%d has p%d\n", x, xpe.Min())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // coalesce combines two nodes in the offline constraint graph.
 | |
| // Precondition: x and y are canonical.
 | |
| func (h *hvn) coalesce(x, y onodeid) {
 | |
| 	xo := h.onodes[x]
 | |
| 	yo := h.onodes[y]
 | |
| 
 | |
| 	// x becomes y's canonical representative.
 | |
| 	yo.rep = x
 | |
| 
 | |
| 	if debugHVNVerbose && h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "\tcoalesce o%d into o%d\n", y, x)
 | |
| 	}
 | |
| 
 | |
| 	// x accumulates y's edges.
 | |
| 	xo.edges.UnionWith(&yo.edges)
 | |
| 	yo.edges.Clear()
 | |
| 
 | |
| 	// x accumulates y's implicit edges.
 | |
| 	xo.implicit.UnionWith(&yo.implicit)
 | |
| 	yo.implicit.Clear()
 | |
| 
 | |
| 	// x accumulates y's pointer-equivalence labels.
 | |
| 	xo.peLabels.UnionWith(&yo.peLabels)
 | |
| 	yo.peLabels.Clear()
 | |
| 
 | |
| 	// x accumulates y's indirect flag.
 | |
| 	if yo.indirect {
 | |
| 		xo.indirect = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // simplify computes a degenerate renumbering of nodeids from the PE
 | |
| // labels assigned by the hvn, and uses it to simplify the main
 | |
| // constraint graph, eliminating non-pointer nodes and duplicate
 | |
| // constraints.
 | |
| //
 | |
| func (h *hvn) simplify() {
 | |
| 	// canon maps each peLabel to its canonical main node.
 | |
| 	canon := make([]nodeid, h.label)
 | |
| 	for i := range canon {
 | |
| 		canon[i] = nodeid(h.N) // indicates "unset"
 | |
| 	}
 | |
| 
 | |
| 	// mapping maps each main node index to the index of the canonical node.
 | |
| 	mapping := make([]nodeid, len(h.a.nodes))
 | |
| 
 | |
| 	for id := range h.a.nodes {
 | |
| 		id := nodeid(id)
 | |
| 		if id == 0 {
 | |
| 			canon[0] = 0
 | |
| 			mapping[0] = 0
 | |
| 			continue
 | |
| 		}
 | |
| 		oid := h.find(onodeid(id))
 | |
| 		peLabels := &h.onodes[oid].peLabels
 | |
| 		assert(peLabels.Len() == 1, "PE class is not a singleton")
 | |
| 		label := peLabel(peLabels.Min())
 | |
| 
 | |
| 		canonId := canon[label]
 | |
| 		if canonId == nodeid(h.N) {
 | |
| 			// id becomes the representative of the PE label.
 | |
| 			canonId = id
 | |
| 			canon[label] = canonId
 | |
| 
 | |
| 			if h.a.log != nil {
 | |
| 				fmt.Fprintf(h.a.log, "\tpts(n%d) is canonical : \t(%s)\n",
 | |
| 					id, h.a.nodes[id].typ)
 | |
| 			}
 | |
| 
 | |
| 		} else {
 | |
| 			// Link the solver states for the two nodes.
 | |
| 			assert(h.a.nodes[canonId].solve != nil, "missing solver state")
 | |
| 			h.a.nodes[id].solve = h.a.nodes[canonId].solve
 | |
| 
 | |
| 			if h.a.log != nil {
 | |
| 				// TODO(adonovan): debug: reorganize the log so it prints
 | |
| 				// one line:
 | |
| 				// 	pe y = x1, ..., xn
 | |
| 				// for each canonical y.  Requires allocation.
 | |
| 				fmt.Fprintf(h.a.log, "\tpts(n%d) = pts(n%d) : %s\n",
 | |
| 					id, canonId, h.a.nodes[id].typ)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		mapping[id] = canonId
 | |
| 	}
 | |
| 
 | |
| 	// Renumber the constraints, eliminate duplicates, and eliminate
 | |
| 	// any containing non-pointers (n0).
 | |
| 	addrs := make(map[addrConstraint]bool)
 | |
| 	copys := make(map[copyConstraint]bool)
 | |
| 	loads := make(map[loadConstraint]bool)
 | |
| 	stores := make(map[storeConstraint]bool)
 | |
| 	offsetAddrs := make(map[offsetAddrConstraint]bool)
 | |
| 	untags := make(map[untagConstraint]bool)
 | |
| 	typeFilters := make(map[typeFilterConstraint]bool)
 | |
| 	invokes := make(map[invokeConstraint]bool)
 | |
| 
 | |
| 	nbefore := len(h.a.constraints)
 | |
| 	cc := h.a.constraints[:0] // in-situ compaction
 | |
| 	for _, c := range h.a.constraints {
 | |
| 		// Renumber.
 | |
| 		switch c := c.(type) {
 | |
| 		case *addrConstraint:
 | |
| 			// Don't renumber c.src since it is the label of
 | |
| 			// an addressable object and will appear in PT sets.
 | |
| 			c.dst = mapping[c.dst]
 | |
| 		default:
 | |
| 			c.renumber(mapping)
 | |
| 		}
 | |
| 
 | |
| 		if c.ptr() == 0 {
 | |
| 			continue // skip: constraint attached to non-pointer
 | |
| 		}
 | |
| 
 | |
| 		var dup bool
 | |
| 		switch c := c.(type) {
 | |
| 		case *addrConstraint:
 | |
| 			_, dup = addrs[*c]
 | |
| 			addrs[*c] = true
 | |
| 
 | |
| 		case *copyConstraint:
 | |
| 			if c.src == c.dst {
 | |
| 				continue // skip degenerate copies
 | |
| 			}
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip copy from non-pointer
 | |
| 			}
 | |
| 			_, dup = copys[*c]
 | |
| 			copys[*c] = true
 | |
| 
 | |
| 		case *loadConstraint:
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip load from non-pointer
 | |
| 			}
 | |
| 			_, dup = loads[*c]
 | |
| 			loads[*c] = true
 | |
| 
 | |
| 		case *storeConstraint:
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip store from non-pointer
 | |
| 			}
 | |
| 			_, dup = stores[*c]
 | |
| 			stores[*c] = true
 | |
| 
 | |
| 		case *offsetAddrConstraint:
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip offset from non-pointer
 | |
| 			}
 | |
| 			_, dup = offsetAddrs[*c]
 | |
| 			offsetAddrs[*c] = true
 | |
| 
 | |
| 		case *untagConstraint:
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip untag of non-pointer
 | |
| 			}
 | |
| 			_, dup = untags[*c]
 | |
| 			untags[*c] = true
 | |
| 
 | |
| 		case *typeFilterConstraint:
 | |
| 			if c.src == 0 {
 | |
| 				continue // skip filter of non-pointer
 | |
| 			}
 | |
| 			_, dup = typeFilters[*c]
 | |
| 			typeFilters[*c] = true
 | |
| 
 | |
| 		case *invokeConstraint:
 | |
| 			if c.params == 0 {
 | |
| 				panic("non-pointer invoke.params")
 | |
| 			}
 | |
| 			if c.iface == 0 {
 | |
| 				continue // skip invoke on non-pointer
 | |
| 			}
 | |
| 			_, dup = invokes[*c]
 | |
| 			invokes[*c] = true
 | |
| 
 | |
| 		default:
 | |
| 			// We don't bother de-duping advanced constraints
 | |
| 			// (e.g. reflection) since they are uncommon.
 | |
| 
 | |
| 			// Eliminate constraints containing non-pointer nodeids.
 | |
| 			//
 | |
| 			// We use reflection to find the fields to avoid
 | |
| 			// adding yet another method to constraint.
 | |
| 			//
 | |
| 			// TODO(adonovan): experiment with a constraint
 | |
| 			// method that returns a slice of pointers to
 | |
| 			// nodeids fields to enable uniform iteration;
 | |
| 			// the renumber() method could be removed and
 | |
| 			// implemented using the new one.
 | |
| 			//
 | |
| 			// TODO(adonovan): opt: this is unsound since
 | |
| 			// some constraints still have an effect if one
 | |
| 			// of the operands is zero: rVCall, rVMapIndex,
 | |
| 			// rvSetMapIndex.  Handle them specially.
 | |
| 			rtNodeid := reflect.TypeOf(nodeid(0))
 | |
| 			x := reflect.ValueOf(c).Elem()
 | |
| 			for i, nf := 0, x.NumField(); i < nf; i++ {
 | |
| 				f := x.Field(i)
 | |
| 				if f.Type() == rtNodeid {
 | |
| 					if f.Uint() == 0 {
 | |
| 						dup = true // skip it
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if dup {
 | |
| 			continue // skip duplicates
 | |
| 		}
 | |
| 
 | |
| 		cc = append(cc, c)
 | |
| 	}
 | |
| 	h.a.constraints = cc
 | |
| 
 | |
| 	if h.log != nil {
 | |
| 		fmt.Fprintf(h.log, "#constraints: was %d, now %d\n", nbefore, len(h.a.constraints))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // find returns the canonical onodeid for x.
 | |
| // (The onodes form a disjoint set forest.)
 | |
| func (h *hvn) find(x onodeid) onodeid {
 | |
| 	// TODO(adonovan): opt: this is a CPU hotspot.  Try "union by rank".
 | |
| 	xo := h.onodes[x]
 | |
| 	rep := xo.rep
 | |
| 	if rep != x {
 | |
| 		rep = h.find(rep) // simple path compression
 | |
| 		xo.rep = rep
 | |
| 	}
 | |
| 	return rep
 | |
| }
 | |
| 
 | |
| func (h *hvn) checkCanonical(x onodeid) {
 | |
| 	if debugHVN {
 | |
| 		assert(x == h.find(x), "not canonical")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func assert(p bool, msg string) {
 | |
| 	if debugHVN && !p {
 | |
| 		panic("assertion failed: " + msg)
 | |
| 	}
 | |
| }
 |