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 }