|
5 | 5 |
|
6 | 6 | # Analysis |
7 | 7 |
|
8 | | -Kirin provides a framework for performing dataflow analysis on the IR. This is done via |
| 8 | +Kirin provides a set of analysis tools for common analysis tasks and for building new analysis tools. |
9 | 9 |
|
10 | | -- IR walking |
11 | | -- Abstract interpretation |
| 10 | +## Forward Dataflow Analysis |
12 | 11 |
|
13 | | -## Lattice |
| 12 | +The forward dataflow analysis is a common analysis technique that computes the dataflow information of a program by propagating the information forward through the control flow graph. The forward dataflow analysis is implemented in the [`kirin.analysis.Forward`][kirin.analysis.Forward] class. |
| 13 | + |
| 14 | +The build a forward dataflow analysis, you need to define a lattice. The lattice represents the set of values that can be computed by the analysis. |
| 15 | + |
| 16 | +### Lattice |
14 | 17 |
|
15 | 18 | A lattice is a set of values that are partially ordered. In Kirin IR, a lattice is a subclass of the [`Lattice`][kirin.lattice.abc.Lattice] ABC class. A lattice can be used to represent the result of a statement that has multiple possible results. |
| 19 | + |
| 20 | +The `kirin.lattice` module provides a set of base and mixin classes that can be used to build some common lattices. |
| 21 | + |
| 22 | +Most of the lattices are bounded lattices, which can be implemented by using the [`BoundedLattice`][kirin.lattice.abc.BoundedLattice] abstract class. |
| 23 | + |
| 24 | +Some lattice elements are singleton, which means the lattice class represents a single instance. A metaclass [`SingletonMeta`][kirin.lattice.abc.SingletonMeta] is provided to create singleton lattice class that guarantees only one instance will be created, e.g the following is a trivial lattice named `EmptyLattice`: |
| 25 | + |
| 26 | +```python |
| 27 | +class EmptyLattice(BoundedLattice["EmptyLattice"], metaclass=SingletonMeta): |
| 28 | + """Empty lattice.""" |
| 29 | + |
| 30 | + def join(self, other: "EmptyLattice") -> "EmptyLattice": |
| 31 | + return self |
| 32 | + |
| 33 | + def meet(self, other: "EmptyLattice") -> "EmptyLattice": |
| 34 | + return self |
| 35 | + |
| 36 | + @classmethod |
| 37 | + def bottom(cls): |
| 38 | + return cls() |
| 39 | + |
| 40 | + @classmethod |
| 41 | + def top(cls): |
| 42 | + return cls() |
| 43 | + |
| 44 | + def __hash__(self) -> int: |
| 45 | + return id(self) |
| 46 | + |
| 47 | + def is_subseteq(self, other: "EmptyLattice") -> bool: |
| 48 | + return True |
| 49 | +``` |
| 50 | + |
| 51 | +where the lattice is a `BoundedLattice` and it is also a singleton class, which means the class will only have one instance. |
| 52 | + |
| 53 | +### Putting things together |
| 54 | + |
| 55 | +To build a forward dataflow analysis, you need to define a lattice and subclass the `Forward` class. The following is the type inference analysis (simplified) that infers the type of each variable in the program: |
| 56 | + |
| 57 | +```python |
| 58 | +class TypeInference(Forward[types.TypeAttribute]): |
| 59 | + keys = ["typeinfer"] |
| 60 | + lattice = types.TypeAttribute |
| 61 | +``` |
| 62 | + |
| 63 | +where the class `TypeInference` is actually the same as the [interpreter](interp.md) we introduced before, but instead of running on concrete values, it runs on the lattice values and walks through all the control flow branches. The field `keys` is a list of keys that tells the interpreter which method registry to use to run the analysis (similar to the key `"main"` for concrete interpretation). |
| 64 | + |
| 65 | +## Control Flow Graph |
| 66 | + |
| 67 | +The control flow graph (CFG) can be constructed by calling the [`CFG`][kirin.analysis.cfg.CFG] class constructor. The CFG is a directed graph that represents the control flow of the program. Each node in the graph represents a basic block, and each edge represents a control flow transfer between two basic blocks. |
| 68 | + |
| 69 | +An example of using the CFG class to construct a CFG is shown below: |
| 70 | + |
| 71 | +```python |
| 72 | +from kirin.analysis import CFG |
| 73 | +from kirin.prelude import basic |
| 74 | + |
| 75 | +@basic |
| 76 | +def main(x): |
| 77 | + if x > 0: |
| 78 | + y = 1 |
| 79 | + else: |
| 80 | + y = 2 |
| 81 | + return y |
| 82 | + |
| 83 | + |
| 84 | +cfg = CFG(main.callable_region) |
| 85 | +cfg.print() |
| 86 | +``` |
| 87 | + |
| 88 | +prints the following directed acyclic graph: |
| 89 | + |
| 90 | +``` |
| 91 | +Successors: |
| 92 | +^0 -> [^1, ^2] |
| 93 | +^2 -> [^3] |
| 94 | +^3 -> [] |
| 95 | +^1 -> [^3] |
| 96 | +
|
| 97 | +Predecessors: |
| 98 | +^1 <- [^0] |
| 99 | +^2 <- [^0] |
| 100 | +^3 <- [^2, ^1] |
| 101 | +``` |
| 102 | + |
| 103 | +## API References |
| 104 | + |
| 105 | +::: kirin.analysis.Forward |
| 106 | +::: kirin.analysis.cfg.CFG |
0 commit comments