github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gotools/go/pointer/hvn.go (about) 1 package 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 160 import ( 161 "fmt" 162 "io" 163 "reflect" 164 165 "llvm.org/llgo/third_party/gotools/container/intsets" 166 "llvm.org/llgo/third_party/gotools/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 // 181 type peLabel int 182 183 type 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.) 201 type 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 // 212 type 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 225 type offsetAddr struct { 226 ptr nodeid 227 offset uint32 228 } 229 230 // nextLabel issues the next unused pointer-equivalence label. 231 func (h *hvn) nextLabel() peLabel { 232 h.label++ 233 return h.label 234 } 235 236 // ref(X) returns the index of the onode for *X. 237 func (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 // 244 func (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 340 func (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 360 func (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 368 func (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 382 func (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 397 func (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 419 func (c *typeFilterConstraint) presolve(h *hvn) { 420 h.markIndirect(onodeid(c.dst), "typeFilter result") 421 } 422 423 // dst = src.(typ) where typ is concrete 424 func (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...) 432 func (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 // 477 func (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 540 func (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. 549 func (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 556 func (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. 565 func (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. 645 func (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. 728 func (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 // 762 func (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 941 if h.log != nil { 942 fmt.Fprintf(h.log, "#constraints: was %d, now %d\n", nbefore, len(h.a.constraints)) 943 } 944 } 945 946 // find returns the canonical onodeid for x. 947 // (The onodes form a disjoint set forest.) 948 func (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 959 func (h *hvn) checkCanonical(x onodeid) { 960 if debugHVN { 961 assert(x == h.find(x), "not canonical") 962 } 963 } 964 965 func assert(p bool, msg string) { 966 if debugHVN && !p { 967 panic("assertion failed: " + msg) 968 } 969 }