github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/go/pointer/hvn.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  // +build go1.5
     6  
     7  package pointer
     8  
     9  // This file implements Hash-Value Numbering (HVN), a pre-solver
    10  // constraint optimization described in Hardekopf & Lin, SAS'07 (see
    11  // doc.go) that analyses the graph topology to determine which sets of
    12  // variables are "pointer equivalent" (PE), i.e. must have identical
    13  // points-to sets in the solution.
    14  //
    15  // A separate ("offline") graph is constructed.  Its nodes are those of
    16  // the main-graph, plus an additional node *X for each pointer node X.
    17  // With this graph we can reason about the unknown points-to set of
    18  // dereferenced pointers.  (We do not generalize this to represent
    19  // unknown fields x->f, perhaps because such fields would be numerous,
    20  // though it might be worth an experiment.)
    21  //
    22  // Nodes whose points-to relations are not entirely captured by the
    23  // graph are marked as "indirect": the *X nodes, the parameters of
    24  // address-taken functions (which includes all functions in method
    25  // sets), or nodes updated by the solver rules for reflection, etc.
    26  //
    27  // All addr (y=&x) nodes are initially assigned a pointer-equivalence
    28  // (PE) label equal to x's nodeid in the main graph.  (These are the
    29  // only PE labels that are less than len(a.nodes).)
    30  //
    31  // All offsetAddr (y=&x.f) constraints are initially assigned a PE
    32  // label; such labels are memoized, keyed by (x, f), so that equivalent
    33  // nodes y as assigned the same label.
    34  //
    35  // Then we process each strongly connected component (SCC) of the graph
    36  // in topological order, assigning it a PE label based on the set P of
    37  // PE labels that flow to it from its immediate dependencies.
    38  //
    39  // If any node in P is "indirect", the entire SCC is assigned a fresh PE
    40  // label.  Otherwise:
    41  //
    42  // |P|=0  if P is empty, all nodes in the SCC are non-pointers (e.g.
    43  //        uninitialized variables, or formal params of dead functions)
    44  //        and the SCC is assigned the PE label of zero.
    45  //
    46  // |P|=1  if P is a singleton, the SCC is assigned the same label as the
    47  //        sole element of P.
    48  //
    49  // |P|>1  if P contains multiple labels, a unique label representing P is
    50  //        invented and recorded in an hash table, so that other
    51  //        equivalent SCCs may also be assigned this label, akin to
    52  //        conventional hash-value numbering in a compiler.
    53  //
    54  // Finally, a renumbering is computed such that each node is replaced by
    55  // the lowest-numbered node with the same PE label.  All constraints are
    56  // renumbered, and any resulting duplicates are eliminated.
    57  //
    58  // The only nodes that are not renumbered are the objects x in addr
    59  // (y=&x) constraints, since the ids of these nodes (and fields derived
    60  // from them via offsetAddr rules) are the elements of all points-to
    61  // sets, so they must remain as they are if we want the same solution.
    62  //
    63  // The solverStates (node.solve) for nodes in the same equivalence class
    64  // are linked together so that all nodes in the class have the same
    65  // solution.  This avoids the need to renumber nodeids buried in
    66  // Queries, cgnodes, etc (like (*analysis).renumber() does) since only
    67  // the solution is needed.
    68  //
    69  // The result of HVN is that the number of distinct nodes and
    70  // constraints is reduced, but the solution is identical (almost---see
    71  // CROSS-CHECK below).  In particular, both linear and cyclic chains of
    72  // copies are each replaced by a single node.
    73  //
    74  // Nodes and constraints created "online" (e.g. while solving reflection
    75  // constraints) are not subject to this optimization.
    76  //
    77  // PERFORMANCE
    78  //
    79  // In two benchmarks (oracle and godoc), HVN eliminates about two thirds
    80  // of nodes, the majority accounted for by non-pointers: nodes of
    81  // non-pointer type, pointers that remain nil, formal parameters of dead
    82  // functions, nodes of untracked types, etc.  It also reduces the number
    83  // of constraints, also by about two thirds, and the solving time by
    84  // 30--42%, although we must pay about 15% for the running time of HVN
    85  // itself.  The benefit is greater for larger applications.
    86  //
    87  // There are many possible optimizations to improve the performance:
    88  // * Use fewer than 1:1 onodes to main graph nodes: many of the onodes
    89  //   we create are not needed.
    90  // * HU (HVN with Union---see paper): coalesce "union" peLabels when
    91  //   their expanded-out sets are equal.
    92  // * HR (HVN with deReference---see paper): this will require that we
    93  //   apply HVN until fixed point, which may need more bookkeeping of the
    94  //   correspondance of main nodes to onodes.
    95  // * Location Equivalence (see paper): have points-to sets contain not
    96  //   locations but location-equivalence class labels, each representing
    97  //   a set of locations.
    98  // * HVN with field-sensitive ref: model each of the fields of a
    99  //   pointer-to-struct.
   100  //
   101  // CROSS-CHECK
   102  //
   103  // To verify the soundness of the optimization, when the
   104  // debugHVNCrossCheck option is enabled, we run the solver twice, once
   105  // before and once after running HVN, dumping the solution to disk, and
   106  // then we compare the results.  If they are not identical, the analysis
   107  // panics.
   108  //
   109  // The solution dumped to disk includes only the N*N submatrix of the
   110  // complete solution where N is the number of nodes after generation.
   111  // In other words, we ignore pointer variables and objects created by
   112  // the solver itself, since their numbering depends on the solver order,
   113  // which is affected by the optimization.  In any case, that's the only
   114  // part the client cares about.
   115  //
   116  // The cross-check is too strict and may fail spuriously.  Although the
   117  // H&L paper describing HVN states that the solutions obtained should be
   118  // identical, this is not the case in practice because HVN can collapse
   119  // cycles involving *p even when pts(p)={}.  Consider this example
   120  // distilled from testdata/hello.go:
   121  //
   122  //	var x T
   123  //	func f(p **T) {
   124  //		t0 = *p
   125  //		...
   126  //		t1 = φ(t0, &x)
   127  //		*p = t1
   128  //	}
   129  //
   130  // If f is dead code, we get:
   131  // 	unoptimized:  pts(p)={} pts(t0)={} pts(t1)={&x}
   132  // 	optimized:    pts(p)={} pts(t0)=pts(t1)=pts(*p)={&x}
   133  //
   134  // It's hard to argue that this is a bug: the result is sound and the
   135  // loss of precision is inconsequential---f is dead code, after all.
   136  // But unfortunately it limits the usefulness of the cross-check since
   137  // failures must be carefully analyzed.  Ben Hardekopf suggests (in
   138  // personal correspondence) some approaches to mitigating it:
   139  //
   140  // 	If there is a node with an HVN points-to set that is a superset
   141  // 	of the NORM points-to set, then either it's a bug or it's a
   142  // 	result of this issue. If it's a result of this issue, then in
   143  // 	the offline constraint graph there should be a REF node inside
   144  // 	some cycle that reaches this node, and in the NORM solution the
   145  // 	pointer being dereferenced by that REF node should be the empty
   146  // 	set. If that isn't true then this is a bug. If it is true, then
   147  // 	you can further check that in the NORM solution the "extra"
   148  // 	points-to info in the HVN solution does in fact come from that
   149  // 	purported cycle (if it doesn't, then this is still a bug). If
   150  // 	you're doing the further check then you'll need to do it for
   151  // 	each "extra" points-to element in the HVN points-to set.
   152  //
   153  // 	There are probably ways to optimize these checks by taking
   154  // 	advantage of graph properties. For example, extraneous points-to
   155  // 	info will flow through the graph and end up in many
   156  // 	nodes. Rather than checking every node with extra info, you
   157  // 	could probably work out the "origin point" of the extra info and
   158  // 	just check there. Note that the check in the first bullet is
   159  // 	looking for soundness bugs, while the check in the second bullet
   160  // 	is looking for precision bugs; depending on your needs, you may
   161  // 	care more about one than the other.
   162  //
   163  // which we should evaluate.  The cross-check is nonetheless invaluable
   164  // for all but one of the programs in the pointer_test suite.
   165  
   166  import (
   167  	"fmt"
   168  	"go/types"
   169  	"io"
   170  	"reflect"
   171  
   172  	"golang.org/x/tools/container/intsets"
   173  )
   174  
   175  // A peLabel is a pointer-equivalence label: two nodes with the same
   176  // peLabel have identical points-to solutions.
   177  //
   178  // The numbers are allocated consecutively like so:
   179  // 	0	not a pointer
   180  //	1..N-1	addrConstraints (equals the constraint's .src field, hence sparse)
   181  //	...	offsetAddr constraints
   182  //	...	SCCs (with indirect nodes or multiple inputs)
   183  //
   184  // Each PE label denotes a set of pointers containing a single addr, a
   185  // single offsetAddr, or some set of other PE labels.
   186  //
   187  type peLabel int
   188  
   189  type hvn struct {
   190  	a        *analysis
   191  	N        int                // len(a.nodes) immediately after constraint generation
   192  	log      io.Writer          // (optional) log of HVN lemmas
   193  	onodes   []*onode           // nodes of the offline graph
   194  	label    peLabel            // the next available PE label
   195  	hvnLabel map[string]peLabel // hash-value numbering (PE label) for each set of onodeids
   196  	stack    []onodeid          // DFS stack
   197  	index    int32              // next onode.index, from Tarjan's SCC algorithm
   198  
   199  	// For each distinct offsetAddrConstraint (src, offset) pair,
   200  	// offsetAddrLabels records a unique PE label >= N.
   201  	offsetAddrLabels map[offsetAddr]peLabel
   202  }
   203  
   204  // The index of an node in the offline graph.
   205  // (Currently the first N align with the main nodes,
   206  // but this may change with HRU.)
   207  type onodeid uint32
   208  
   209  // An onode is a node in the offline constraint graph.
   210  // (Where ambiguous, members of analysis.nodes are referred to as
   211  // "main graph" nodes.)
   212  //
   213  // Edges in the offline constraint graph (edges and implicit) point to
   214  // the source, i.e. against the flow of values: they are dependencies.
   215  // Implicit edges are used for SCC computation, but not for gathering
   216  // incoming labels.
   217  //
   218  type onode struct {
   219  	rep onodeid // index of representative of SCC in offline constraint graph
   220  
   221  	edges    intsets.Sparse // constraint edges X-->Y (this onode is X)
   222  	implicit intsets.Sparse // implicit edges *X-->*Y (this onode is X)
   223  	peLabels intsets.Sparse // set of peLabels are pointer-equivalent to this one
   224  	indirect bool           // node has points-to relations not represented in graph
   225  
   226  	// Tarjan's SCC algorithm
   227  	index, lowlink int32 // Tarjan numbering
   228  	scc            int32 // -ve => on stack; 0 => unvisited; +ve => node is root of a found SCC
   229  }
   230  
   231  type offsetAddr struct {
   232  	ptr    nodeid
   233  	offset uint32
   234  }
   235  
   236  // nextLabel issues the next unused pointer-equivalence label.
   237  func (h *hvn) nextLabel() peLabel {
   238  	h.label++
   239  	return h.label
   240  }
   241  
   242  // ref(X) returns the index of the onode for *X.
   243  func (h *hvn) ref(id onodeid) onodeid {
   244  	return id + onodeid(len(h.a.nodes))
   245  }
   246  
   247  // hvn computes pointer-equivalence labels (peLabels) using the Hash-based
   248  // Value Numbering (HVN) algorithm described in Hardekopf & Lin, SAS'07.
   249  //
   250  func (a *analysis) hvn() {
   251  	start("HVN")
   252  
   253  	if a.log != nil {
   254  		fmt.Fprintf(a.log, "\n\n==== Pointer equivalence optimization\n\n")
   255  	}
   256  
   257  	h := hvn{
   258  		a:                a,
   259  		N:                len(a.nodes),
   260  		log:              a.log,
   261  		hvnLabel:         make(map[string]peLabel),
   262  		offsetAddrLabels: make(map[offsetAddr]peLabel),
   263  	}
   264  
   265  	if h.log != nil {
   266  		fmt.Fprintf(h.log, "\nCreating offline graph nodes...\n")
   267  	}
   268  
   269  	// Create offline nodes.  The first N nodes correspond to main
   270  	// graph nodes; the next N are their corresponding ref() nodes.
   271  	h.onodes = make([]*onode, 2*h.N)
   272  	for id := range a.nodes {
   273  		id := onodeid(id)
   274  		h.onodes[id] = &onode{}
   275  		h.onodes[h.ref(id)] = &onode{indirect: true}
   276  	}
   277  
   278  	// Each node initially represents just itself.
   279  	for id, o := range h.onodes {
   280  		o.rep = onodeid(id)
   281  	}
   282  
   283  	h.markIndirectNodes()
   284  
   285  	// Reserve the first N PE labels for addrConstraints.
   286  	h.label = peLabel(h.N)
   287  
   288  	// Add offline constraint edges.
   289  	if h.log != nil {
   290  		fmt.Fprintf(h.log, "\nAdding offline graph edges...\n")
   291  	}
   292  	for _, c := range a.constraints {
   293  		if debugHVNVerbose && h.log != nil {
   294  			fmt.Fprintf(h.log, "; %s\n", c)
   295  		}
   296  		c.presolve(&h)
   297  	}
   298  
   299  	// Find and collapse SCCs.
   300  	if h.log != nil {
   301  		fmt.Fprintf(h.log, "\nFinding SCCs...\n")
   302  	}
   303  	h.index = 1
   304  	for id, o := range h.onodes {
   305  		if id > 0 && o.index == 0 {
   306  			// Start depth-first search at each unvisited node.
   307  			h.visit(onodeid(id))
   308  		}
   309  	}
   310  
   311  	// Dump the solution
   312  	// (NB: somewhat redundant with logging from simplify().)
   313  	if debugHVNVerbose && h.log != nil {
   314  		fmt.Fprintf(h.log, "\nPointer equivalences:\n")
   315  		for id, o := range h.onodes {
   316  			if id == 0 {
   317  				continue
   318  			}
   319  			if id == int(h.N) {
   320  				fmt.Fprintf(h.log, "---\n")
   321  			}
   322  			fmt.Fprintf(h.log, "o%d\t", id)
   323  			if o.rep != onodeid(id) {
   324  				fmt.Fprintf(h.log, "rep=o%d", o.rep)
   325  			} else {
   326  				fmt.Fprintf(h.log, "p%d", o.peLabels.Min())
   327  				if o.indirect {
   328  					fmt.Fprint(h.log, " indirect")
   329  				}
   330  			}
   331  			fmt.Fprintln(h.log)
   332  		}
   333  	}
   334  
   335  	// Simplify the main constraint graph
   336  	h.simplify()
   337  
   338  	a.showCounts()
   339  
   340  	stop("HVN")
   341  }
   342  
   343  // ---- constraint-specific rules ----
   344  
   345  // dst := &src
   346  func (c *addrConstraint) presolve(h *hvn) {
   347  	// Each object (src) is an initial PE label.
   348  	label := peLabel(c.src) // label < N
   349  	if debugHVNVerbose && h.log != nil {
   350  		// duplicate log messages are possible
   351  		fmt.Fprintf(h.log, "\tcreate p%d: {&n%d}\n", label, c.src)
   352  	}
   353  	odst := onodeid(c.dst)
   354  	osrc := onodeid(c.src)
   355  
   356  	// Assign dst this label.
   357  	h.onodes[odst].peLabels.Insert(int(label))
   358  	if debugHVNVerbose && h.log != nil {
   359  		fmt.Fprintf(h.log, "\to%d has p%d\n", odst, label)
   360  	}
   361  
   362  	h.addImplicitEdge(h.ref(odst), osrc) // *dst ~~> src.
   363  }
   364  
   365  // dst = src
   366  func (c *copyConstraint) presolve(h *hvn) {
   367  	odst := onodeid(c.dst)
   368  	osrc := onodeid(c.src)
   369  	h.addEdge(odst, osrc)                       //  dst -->  src
   370  	h.addImplicitEdge(h.ref(odst), h.ref(osrc)) // *dst ~~> *src
   371  }
   372  
   373  // dst = *src + offset
   374  func (c *loadConstraint) presolve(h *hvn) {
   375  	odst := onodeid(c.dst)
   376  	osrc := onodeid(c.src)
   377  	if c.offset == 0 {
   378  		h.addEdge(odst, h.ref(osrc)) // dst --> *src
   379  	} else {
   380  		// We don't interpret load-with-offset, e.g. results
   381  		// of map value lookup, R-block of dynamic call, slice
   382  		// copy/append, reflection.
   383  		h.markIndirect(odst, "load with offset")
   384  	}
   385  }
   386  
   387  // *dst + offset = src
   388  func (c *storeConstraint) presolve(h *hvn) {
   389  	odst := onodeid(c.dst)
   390  	osrc := onodeid(c.src)
   391  	if c.offset == 0 {
   392  		h.onodes[h.ref(odst)].edges.Insert(int(osrc)) // *dst --> src
   393  		if debugHVNVerbose && h.log != nil {
   394  			fmt.Fprintf(h.log, "\to%d --> o%d\n", h.ref(odst), osrc)
   395  		}
   396  	} else {
   397  		// We don't interpret store-with-offset.
   398  		// See discussion of soundness at markIndirectNodes.
   399  	}
   400  }
   401  
   402  // dst = &src.offset
   403  func (c *offsetAddrConstraint) presolve(h *hvn) {
   404  	// Give each distinct (addr, offset) pair a fresh PE label.
   405  	// The cache performs CSE, effectively.
   406  	key := offsetAddr{c.src, c.offset}
   407  	label, ok := h.offsetAddrLabels[key]
   408  	if !ok {
   409  		label = h.nextLabel()
   410  		h.offsetAddrLabels[key] = label
   411  		if debugHVNVerbose && h.log != nil {
   412  			fmt.Fprintf(h.log, "\tcreate p%d: {&n%d.#%d}\n",
   413  				label, c.src, c.offset)
   414  		}
   415  	}
   416  
   417  	// Assign dst this label.
   418  	h.onodes[c.dst].peLabels.Insert(int(label))
   419  	if debugHVNVerbose && h.log != nil {
   420  		fmt.Fprintf(h.log, "\to%d has p%d\n", c.dst, label)
   421  	}
   422  }
   423  
   424  // dst = src.(typ)  where typ is an interface
   425  func (c *typeFilterConstraint) presolve(h *hvn) {
   426  	h.markIndirect(onodeid(c.dst), "typeFilter result")
   427  }
   428  
   429  // dst = src.(typ)  where typ is concrete
   430  func (c *untagConstraint) presolve(h *hvn) {
   431  	odst := onodeid(c.dst)
   432  	for end := odst + onodeid(h.a.sizeof(c.typ)); odst < end; odst++ {
   433  		h.markIndirect(odst, "untag result")
   434  	}
   435  }
   436  
   437  // dst = src.method(c.params...)
   438  func (c *invokeConstraint) presolve(h *hvn) {
   439  	// All methods are address-taken functions, so
   440  	// their formal P-blocks were already marked indirect.
   441  
   442  	// Mark the caller's targets node as indirect.
   443  	sig := c.method.Type().(*types.Signature)
   444  	id := c.params
   445  	h.markIndirect(onodeid(c.params), "invoke targets node")
   446  	id++
   447  
   448  	id += nodeid(h.a.sizeof(sig.Params()))
   449  
   450  	// Mark the caller's R-block as indirect.
   451  	end := id + nodeid(h.a.sizeof(sig.Results()))
   452  	for id < end {
   453  		h.markIndirect(onodeid(id), "invoke R-block")
   454  		id++
   455  	}
   456  }
   457  
   458  // markIndirectNodes marks as indirect nodes whose points-to relations
   459  // are not entirely captured by the offline graph, including:
   460  //
   461  //    (a) All address-taken nodes (including the following nodes within
   462  //        the same object).  This is described in the paper.
   463  //
   464  // The most subtle cause of indirect nodes is the generation of
   465  // store-with-offset constraints since the offline graph doesn't
   466  // represent them.  A global audit of constraint generation reveals the
   467  // following uses of store-with-offset:
   468  //
   469  //    (b) genDynamicCall, for P-blocks of dynamically called functions,
   470  //        to which dynamic copy edges will be added to them during
   471  //        solving: from storeConstraint for standalone functions,
   472  //        and from invokeConstraint for methods.
   473  //        All such P-blocks must be marked indirect.
   474  //    (c) MakeUpdate, to update the value part of a map object.
   475  //        All MakeMap objects's value parts must be marked indirect.
   476  //    (d) copyElems, to update the destination array.
   477  //        All array elements must be marked indirect.
   478  //
   479  // Not all indirect marking happens here.  ref() nodes are marked
   480  // indirect at construction, and each constraint's presolve() method may
   481  // mark additional nodes.
   482  //
   483  func (h *hvn) markIndirectNodes() {
   484  	// (a) all address-taken nodes, plus all nodes following them
   485  	//     within the same object, since these may be indirectly
   486  	//     stored or address-taken.
   487  	for _, c := range h.a.constraints {
   488  		if c, ok := c.(*addrConstraint); ok {
   489  			start := h.a.enclosingObj(c.src)
   490  			end := start + nodeid(h.a.nodes[start].obj.size)
   491  			for id := c.src; id < end; id++ {
   492  				h.markIndirect(onodeid(id), "A-T object")
   493  			}
   494  		}
   495  	}
   496  
   497  	// (b) P-blocks of all address-taken functions.
   498  	for id := 0; id < h.N; id++ {
   499  		obj := h.a.nodes[id].obj
   500  
   501  		// TODO(adonovan): opt: if obj.cgn.fn is a method and
   502  		// obj.cgn is not its shared contour, this is an
   503  		// "inlined" static method call.  We needn't consider it
   504  		// address-taken since no invokeConstraint will affect it.
   505  
   506  		if obj != nil && obj.flags&otFunction != 0 && h.a.atFuncs[obj.cgn.fn] {
   507  			// address-taken function
   508  			if debugHVNVerbose && h.log != nil {
   509  				fmt.Fprintf(h.log, "n%d is address-taken: %s\n", id, obj.cgn.fn)
   510  			}
   511  			h.markIndirect(onodeid(id), "A-T func identity")
   512  			id++
   513  			sig := obj.cgn.fn.Signature
   514  			psize := h.a.sizeof(sig.Params())
   515  			if sig.Recv() != nil {
   516  				psize += h.a.sizeof(sig.Recv().Type())
   517  			}
   518  			for end := id + int(psize); id < end; id++ {
   519  				h.markIndirect(onodeid(id), "A-T func P-block")
   520  			}
   521  			id--
   522  			continue
   523  		}
   524  	}
   525  
   526  	// (c) all map objects' value fields.
   527  	for _, id := range h.a.mapValues {
   528  		h.markIndirect(onodeid(id), "makemap.value")
   529  	}
   530  
   531  	// (d) all array element objects.
   532  	// TODO(adonovan): opt: can we do better?
   533  	for id := 0; id < h.N; id++ {
   534  		// Identity node for an object of array type?
   535  		if tArray, ok := h.a.nodes[id].typ.(*types.Array); ok {
   536  			// Mark the array element nodes indirect.
   537  			// (Skip past the identity field.)
   538  			for _ = range h.a.flatten(tArray.Elem()) {
   539  				id++
   540  				h.markIndirect(onodeid(id), "array elem")
   541  			}
   542  		}
   543  	}
   544  }
   545  
   546  func (h *hvn) markIndirect(oid onodeid, comment string) {
   547  	h.onodes[oid].indirect = true
   548  	if debugHVNVerbose && h.log != nil {
   549  		fmt.Fprintf(h.log, "\to%d is indirect: %s\n", oid, comment)
   550  	}
   551  }
   552  
   553  // Adds an edge dst-->src.
   554  // Note the unusual convention: edges are dependency (contraflow) edges.
   555  func (h *hvn) addEdge(odst, osrc onodeid) {
   556  	h.onodes[odst].edges.Insert(int(osrc))
   557  	if debugHVNVerbose && h.log != nil {
   558  		fmt.Fprintf(h.log, "\to%d --> o%d\n", odst, osrc)
   559  	}
   560  }
   561  
   562  func (h *hvn) addImplicitEdge(odst, osrc onodeid) {
   563  	h.onodes[odst].implicit.Insert(int(osrc))
   564  	if debugHVNVerbose && h.log != nil {
   565  		fmt.Fprintf(h.log, "\to%d ~~> o%d\n", odst, osrc)
   566  	}
   567  }
   568  
   569  // visit implements the depth-first search of Tarjan's SCC algorithm.
   570  // Precondition: x is canonical.
   571  func (h *hvn) visit(x onodeid) {
   572  	h.checkCanonical(x)
   573  	xo := h.onodes[x]
   574  	xo.index = h.index
   575  	xo.lowlink = h.index
   576  	h.index++
   577  
   578  	h.stack = append(h.stack, x) // push
   579  	assert(xo.scc == 0, "node revisited")
   580  	xo.scc = -1
   581  
   582  	var deps []int
   583  	deps = xo.edges.AppendTo(deps)
   584  	deps = xo.implicit.AppendTo(deps)
   585  
   586  	for _, y := range deps {
   587  		// Loop invariant: x is canonical.
   588  
   589  		y := h.find(onodeid(y))
   590  
   591  		if x == y {
   592  			continue // nodes already coalesced
   593  		}
   594  
   595  		xo := h.onodes[x]
   596  		yo := h.onodes[y]
   597  
   598  		switch {
   599  		case yo.scc > 0:
   600  			// y is already a collapsed SCC
   601  
   602  		case yo.scc < 0:
   603  			// y is on the stack, and thus in the current SCC.
   604  			if yo.index < xo.lowlink {
   605  				xo.lowlink = yo.index
   606  			}
   607  
   608  		default:
   609  			// y is unvisited; visit it now.
   610  			h.visit(y)
   611  			// Note: x and y are now non-canonical.
   612  
   613  			x = h.find(onodeid(x))
   614  
   615  			if yo.lowlink < xo.lowlink {
   616  				xo.lowlink = yo.lowlink
   617  			}
   618  		}
   619  	}
   620  	h.checkCanonical(x)
   621  
   622  	// Is x the root of an SCC?
   623  	if xo.lowlink == xo.index {
   624  		// Coalesce all nodes in the SCC.
   625  		if debugHVNVerbose && h.log != nil {
   626  			fmt.Fprintf(h.log, "scc o%d\n", x)
   627  		}
   628  		for {
   629  			// Pop y from stack.
   630  			i := len(h.stack) - 1
   631  			y := h.stack[i]
   632  			h.stack = h.stack[:i]
   633  
   634  			h.checkCanonical(x)
   635  			xo := h.onodes[x]
   636  			h.checkCanonical(y)
   637  			yo := h.onodes[y]
   638  
   639  			if xo == yo {
   640  				// SCC is complete.
   641  				xo.scc = 1
   642  				h.labelSCC(x)
   643  				break
   644  			}
   645  			h.coalesce(x, y)
   646  		}
   647  	}
   648  }
   649  
   650  // Precondition: x is canonical.
   651  func (h *hvn) labelSCC(x onodeid) {
   652  	h.checkCanonical(x)
   653  	xo := h.onodes[x]
   654  	xpe := &xo.peLabels
   655  
   656  	// All indirect nodes get new labels.
   657  	if xo.indirect {
   658  		label := h.nextLabel()
   659  		if debugHVNVerbose && h.log != nil {
   660  			fmt.Fprintf(h.log, "\tcreate p%d: indirect SCC\n", label)
   661  			fmt.Fprintf(h.log, "\to%d has p%d\n", x, label)
   662  		}
   663  
   664  		// Remove pre-labeling, in case a direct pre-labeled node was
   665  		// merged with an indirect one.
   666  		xpe.Clear()
   667  		xpe.Insert(int(label))
   668  
   669  		return
   670  	}
   671  
   672  	// Invariant: all peLabels sets are non-empty.
   673  	// Those that are logically empty contain zero as their sole element.
   674  	// No other sets contains zero.
   675  
   676  	// Find all labels coming in to the coalesced SCC node.
   677  	for _, y := range xo.edges.AppendTo(nil) {
   678  		y := h.find(onodeid(y))
   679  		if y == x {
   680  			continue // already coalesced
   681  		}
   682  		ype := &h.onodes[y].peLabels
   683  		if debugHVNVerbose && h.log != nil {
   684  			fmt.Fprintf(h.log, "\tedge from o%d = %s\n", y, ype)
   685  		}
   686  
   687  		if ype.IsEmpty() {
   688  			if debugHVNVerbose && h.log != nil {
   689  				fmt.Fprintf(h.log, "\tnode has no PE label\n")
   690  			}
   691  		}
   692  		assert(!ype.IsEmpty(), "incoming node has no PE label")
   693  
   694  		if ype.Has(0) {
   695  			// {0} represents a non-pointer.
   696  			assert(ype.Len() == 1, "PE set contains {0, ...}")
   697  		} else {
   698  			xpe.UnionWith(ype)
   699  		}
   700  	}
   701  
   702  	switch xpe.Len() {
   703  	case 0:
   704  		// SCC has no incoming non-zero PE labels: it is a non-pointer.
   705  		xpe.Insert(0)
   706  
   707  	case 1:
   708  		// already a singleton
   709  
   710  	default:
   711  		// SCC has multiple incoming non-zero PE labels.
   712  		// Find the canonical label representing this set.
   713  		// We use String() as a fingerprint consistent with Equals().
   714  		key := xpe.String()
   715  		label, ok := h.hvnLabel[key]
   716  		if !ok {
   717  			label = h.nextLabel()
   718  			if debugHVNVerbose && h.log != nil {
   719  				fmt.Fprintf(h.log, "\tcreate p%d: union %s\n", label, xpe.String())
   720  			}
   721  			h.hvnLabel[key] = label
   722  		}
   723  		xpe.Clear()
   724  		xpe.Insert(int(label))
   725  	}
   726  
   727  	if debugHVNVerbose && h.log != nil {
   728  		fmt.Fprintf(h.log, "\to%d has p%d\n", x, xpe.Min())
   729  	}
   730  }
   731  
   732  // coalesce combines two nodes in the offline constraint graph.
   733  // Precondition: x and y are canonical.
   734  func (h *hvn) coalesce(x, y onodeid) {
   735  	xo := h.onodes[x]
   736  	yo := h.onodes[y]
   737  
   738  	// x becomes y's canonical representative.
   739  	yo.rep = x
   740  
   741  	if debugHVNVerbose && h.log != nil {
   742  		fmt.Fprintf(h.log, "\tcoalesce o%d into o%d\n", y, x)
   743  	}
   744  
   745  	// x accumulates y's edges.
   746  	xo.edges.UnionWith(&yo.edges)
   747  	yo.edges.Clear()
   748  
   749  	// x accumulates y's implicit edges.
   750  	xo.implicit.UnionWith(&yo.implicit)
   751  	yo.implicit.Clear()
   752  
   753  	// x accumulates y's pointer-equivalence labels.
   754  	xo.peLabels.UnionWith(&yo.peLabels)
   755  	yo.peLabels.Clear()
   756  
   757  	// x accumulates y's indirect flag.
   758  	if yo.indirect {
   759  		xo.indirect = true
   760  	}
   761  }
   762  
   763  // simplify computes a degenerate renumbering of nodeids from the PE
   764  // labels assigned by the hvn, and uses it to simplify the main
   765  // constraint graph, eliminating non-pointer nodes and duplicate
   766  // constraints.
   767  //
   768  func (h *hvn) simplify() {
   769  	// canon maps each peLabel to its canonical main node.
   770  	canon := make([]nodeid, h.label)
   771  	for i := range canon {
   772  		canon[i] = nodeid(h.N) // indicates "unset"
   773  	}
   774  
   775  	// mapping maps each main node index to the index of the canonical node.
   776  	mapping := make([]nodeid, len(h.a.nodes))
   777  
   778  	for id := range h.a.nodes {
   779  		id := nodeid(id)
   780  		if id == 0 {
   781  			canon[0] = 0
   782  			mapping[0] = 0
   783  			continue
   784  		}
   785  		oid := h.find(onodeid(id))
   786  		peLabels := &h.onodes[oid].peLabels
   787  		assert(peLabels.Len() == 1, "PE class is not a singleton")
   788  		label := peLabel(peLabels.Min())
   789  
   790  		canonId := canon[label]
   791  		if canonId == nodeid(h.N) {
   792  			// id becomes the representative of the PE label.
   793  			canonId = id
   794  			canon[label] = canonId
   795  
   796  			if h.a.log != nil {
   797  				fmt.Fprintf(h.a.log, "\tpts(n%d) is canonical : \t(%s)\n",
   798  					id, h.a.nodes[id].typ)
   799  			}
   800  
   801  		} else {
   802  			// Link the solver states for the two nodes.
   803  			assert(h.a.nodes[canonId].solve != nil, "missing solver state")
   804  			h.a.nodes[id].solve = h.a.nodes[canonId].solve
   805  
   806  			if h.a.log != nil {
   807  				// TODO(adonovan): debug: reorganize the log so it prints
   808  				// one line:
   809  				// 	pe y = x1, ..., xn
   810  				// for each canonical y.  Requires allocation.
   811  				fmt.Fprintf(h.a.log, "\tpts(n%d) = pts(n%d) : %s\n",
   812  					id, canonId, h.a.nodes[id].typ)
   813  			}
   814  		}
   815  
   816  		mapping[id] = canonId
   817  	}
   818  
   819  	// Renumber the constraints, eliminate duplicates, and eliminate
   820  	// any containing non-pointers (n0).
   821  	addrs := make(map[addrConstraint]bool)
   822  	copys := make(map[copyConstraint]bool)
   823  	loads := make(map[loadConstraint]bool)
   824  	stores := make(map[storeConstraint]bool)
   825  	offsetAddrs := make(map[offsetAddrConstraint]bool)
   826  	untags := make(map[untagConstraint]bool)
   827  	typeFilters := make(map[typeFilterConstraint]bool)
   828  	invokes := make(map[invokeConstraint]bool)
   829  
   830  	nbefore := len(h.a.constraints)
   831  	cc := h.a.constraints[:0] // in-situ compaction
   832  	for _, c := range h.a.constraints {
   833  		// Renumber.
   834  		switch c := c.(type) {
   835  		case *addrConstraint:
   836  			// Don't renumber c.src since it is the label of
   837  			// an addressable object and will appear in PT sets.
   838  			c.dst = mapping[c.dst]
   839  		default:
   840  			c.renumber(mapping)
   841  		}
   842  
   843  		if c.ptr() == 0 {
   844  			continue // skip: constraint attached to non-pointer
   845  		}
   846  
   847  		var dup bool
   848  		switch c := c.(type) {
   849  		case *addrConstraint:
   850  			_, dup = addrs[*c]
   851  			addrs[*c] = true
   852  
   853  		case *copyConstraint:
   854  			if c.src == c.dst {
   855  				continue // skip degenerate copies
   856  			}
   857  			if c.src == 0 {
   858  				continue // skip copy from non-pointer
   859  			}
   860  			_, dup = copys[*c]
   861  			copys[*c] = true
   862  
   863  		case *loadConstraint:
   864  			if c.src == 0 {
   865  				continue // skip load from non-pointer
   866  			}
   867  			_, dup = loads[*c]
   868  			loads[*c] = true
   869  
   870  		case *storeConstraint:
   871  			if c.src == 0 {
   872  				continue // skip store from non-pointer
   873  			}
   874  			_, dup = stores[*c]
   875  			stores[*c] = true
   876  
   877  		case *offsetAddrConstraint:
   878  			if c.src == 0 {
   879  				continue // skip offset from non-pointer
   880  			}
   881  			_, dup = offsetAddrs[*c]
   882  			offsetAddrs[*c] = true
   883  
   884  		case *untagConstraint:
   885  			if c.src == 0 {
   886  				continue // skip untag of non-pointer
   887  			}
   888  			_, dup = untags[*c]
   889  			untags[*c] = true
   890  
   891  		case *typeFilterConstraint:
   892  			if c.src == 0 {
   893  				continue // skip filter of non-pointer
   894  			}
   895  			_, dup = typeFilters[*c]
   896  			typeFilters[*c] = true
   897  
   898  		case *invokeConstraint:
   899  			if c.params == 0 {
   900  				panic("non-pointer invoke.params")
   901  			}
   902  			if c.iface == 0 {
   903  				continue // skip invoke on non-pointer
   904  			}
   905  			_, dup = invokes[*c]
   906  			invokes[*c] = true
   907  
   908  		default:
   909  			// We don't bother de-duping advanced constraints
   910  			// (e.g. reflection) since they are uncommon.
   911  
   912  			// Eliminate constraints containing non-pointer nodeids.
   913  			//
   914  			// We use reflection to find the fields to avoid
   915  			// adding yet another method to constraint.
   916  			//
   917  			// TODO(adonovan): experiment with a constraint
   918  			// method that returns a slice of pointers to
   919  			// nodeids fields to enable uniform iteration;
   920  			// the renumber() method could be removed and
   921  			// implemented using the new one.
   922  			//
   923  			// TODO(adonovan): opt: this is unsound since
   924  			// some constraints still have an effect if one
   925  			// of the operands is zero: rVCall, rVMapIndex,
   926  			// rvSetMapIndex.  Handle them specially.
   927  			rtNodeid := reflect.TypeOf(nodeid(0))
   928  			x := reflect.ValueOf(c).Elem()
   929  			for i, nf := 0, x.NumField(); i < nf; i++ {
   930  				f := x.Field(i)
   931  				if f.Type() == rtNodeid {
   932  					if f.Uint() == 0 {
   933  						dup = true // skip it
   934  						break
   935  					}
   936  				}
   937  			}
   938  		}
   939  		if dup {
   940  			continue // skip duplicates
   941  		}
   942  
   943  		cc = append(cc, c)
   944  	}
   945  	h.a.constraints = cc
   946  
   947  	if h.log != nil {
   948  		fmt.Fprintf(h.log, "#constraints: was %d, now %d\n", nbefore, len(h.a.constraints))
   949  	}
   950  }
   951  
   952  // find returns the canonical onodeid for x.
   953  // (The onodes form a disjoint set forest.)
   954  func (h *hvn) find(x onodeid) onodeid {
   955  	// TODO(adonovan): opt: this is a CPU hotspot.  Try "union by rank".
   956  	xo := h.onodes[x]
   957  	rep := xo.rep
   958  	if rep != x {
   959  		rep = h.find(rep) // simple path compression
   960  		xo.rep = rep
   961  	}
   962  	return rep
   963  }
   964  
   965  func (h *hvn) checkCanonical(x onodeid) {
   966  	if debugHVN {
   967  		assert(x == h.find(x), "not canonical")
   968  	}
   969  }
   970  
   971  func assert(p bool, msg string) {
   972  	if debugHVN && !p {
   973  		panic("assertion failed: " + msg)
   974  	}
   975  }