|
1 |
| -/* |
| 1 | +#[doc = " |
2 | 2 |
|
3 | 3 | A classic liveness analysis based on dataflow over the AST. Computes,
|
4 | 4 | for each local variable in a function, whether that variable is live
|
@@ -42,7 +42,65 @@ Each field is assigned an index just as with local variables. A use of
|
42 | 42 | `self` is considered a use of all fields. A use of `self.f` is just a use
|
43 | 43 | of `f`.
|
44 | 44 |
|
45 |
| -*/ |
| 45 | +# Implementation details |
| 46 | +
|
| 47 | +The actual implementation contains two (nested) walks over the AST. |
| 48 | +The outer walk has the job of building up the ir_maps instance for the |
| 49 | +enclosing function. On the way down the tree, it identifies those AST |
| 50 | +nodes and variable IDs that will be needed for the liveness analysis |
| 51 | +and assigns them contiguous IDs. The liveness id for an AST node is |
| 52 | +called a `live_node` (it's a newtype'd uint) and the id for a variable |
| 53 | +is called a `variable` (another newtype'd uint). |
| 54 | +
|
| 55 | +On the way back up the tree, as we are about to exit from a function |
| 56 | +declaration we allocate a `liveness` instance. Now that we know |
| 57 | +precisely how many nodes and variables we need, we can allocate all |
| 58 | +the various arrays that we will need to precisely the right size. We then |
| 59 | +perform the actual propagation on the `liveness` instance. |
| 60 | +
|
| 61 | +This propagation is encoded in the various `propagate_through_*()` |
| 62 | +methods. It effectively does a reverse walk of the AST; whenever we |
| 63 | +reach a loop node, we iterate until a fixed point is reached. |
| 64 | +
|
| 65 | +## The `users` struct |
| 66 | +
|
| 67 | +At each live node `N`, we track three pieces of information for each |
| 68 | +variable `V` (these are encapsulated in the `users` struct): |
| 69 | +
|
| 70 | +- `reader`: the `live_node` ID of some node which will read the value |
| 71 | + that `V` holds on entry to `N`. Formally: a node `M` such |
| 72 | + that there exists a path `P` from `N` to `M` where `P` does not |
| 73 | + write `V`. If the `reader` is `invalid_node()`, then the current |
| 74 | + value will never be read (the variable is dead, essentially). |
| 75 | +
|
| 76 | +- `writer`: the `live_node` ID of some node which will write the |
| 77 | + variable `V` and which is reachable from `N`. Formally: a node `M` |
| 78 | + such that there exists a path `P` from `N` to `M` and `M` writes |
| 79 | + `V`. If the `writer` is `invalid_node()`, then there is no writer |
| 80 | + of `V` that follows `N`. |
| 81 | +
|
| 82 | +- `used`: a boolean value indicating whether `V` is *used*. We |
| 83 | + distinguish a *read* from a *use* in that a *use* is some read that |
| 84 | + is not just used to generate a new value. For example, `x += 1` is |
| 85 | + a read but not a use. This is used to generate better warnings. |
| 86 | +
|
| 87 | +## Special Variables |
| 88 | +
|
| 89 | +We generate various special variables for various, well, special purposes. |
| 90 | +These are described in the `specials` struct: |
| 91 | +
|
| 92 | +- `exit_ln`: a live node that is generated to represent every 'exit' from the |
| 93 | + function, whether it be by explicit return, fail, or other means. |
| 94 | +
|
| 95 | +- `fallthrough_ln`: a live node that represents a fallthrough |
| 96 | +
|
| 97 | +- `no_ret_var`: a synthetic variable that is only 'read' from, the |
| 98 | + fallthrough node. This allows us to detect functions where we fail |
| 99 | + to return explicitly. |
| 100 | +
|
| 101 | +- `self_var`: a variable representing 'self' |
| 102 | +
|
| 103 | +"]; |
46 | 104 |
|
47 | 105 | import dvec::{dvec, extensions};
|
48 | 106 | import std::map::{hashmap, int_hash, str_hash, box_str_hash};
|
|
0 commit comments