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