You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

973 lines
29 KiB

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