Skip to content

Commit edceef1

Browse files
authored
add more docstrings for analysis (#168)
1 parent ef05a98 commit edceef1

File tree

12 files changed

+185
-3
lines changed

12 files changed

+185
-3
lines changed

mkdocs.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ nav:
4141
- Python Lowering: reference/kirin/lowering/
4242
- Analysis:
4343
- reference/kirin/analysis/index.md
44-
- Dataflow: reference/kirin/analysis/dataflow/
4544
- CFG: reference/kirin/analysis/cfg.md
45+
- Call Graph: reference/kirin/analysis/callgraph.md
46+
- Forward Dataflow: reference/kirin/analysis/forward.md
47+
- Type Inference: reference/kirin/analysis/typeinfer/
48+
- Constant Propagation: reference/kirin/analysis/const/
4649
- Rewrite:
4750
- Generic: reference/kirin/rewrite/
4851
- Rules: reference/kirin/rules/

src/kirin/analysis/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""Analysis module for kirin.
2+
3+
This module contains the analysis framework for kirin. The analysis framework is
4+
built on top of the interpreter framework. This module provides a set of base classes
5+
and frameworks for implementing compiler analysis passes on the IR.
6+
7+
The analysis framework contains the following modules:
8+
9+
- [`cfg`][kirin.analysis.cfg]: Control flow graph for a given IR.
10+
- [`forward`][kirin.analysis.forward]: Forward dataflow analysis.
11+
- [`callgraph`][kirin.analysis.callgraph]: Call graph for a given IR.
12+
- [`typeinfer`][kirin.analysis.typeinfer]: Type inference analysis.
13+
- [`const`][kirin.analysis.const]: Constants used in the analysis framework.
14+
"""
15+
116
from kirin.analysis import const as const
217
from kirin.analysis.cfg import CFG as CFG
318
from kirin.analysis.forward import Forward as Forward, ForwardExtra as ForwardExtra

src/kirin/analysis/callgraph.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,19 @@
99

1010
@dataclass
1111
class CallGraph(Printable):
12+
"""Call graph for a given [`ir.Method`][kirin.ir.Method].
13+
14+
This class implements the [`kirin.graph.Graph`][kirin.graph.Graph] protocol.
15+
16+
!!! note "Pretty Printing"
17+
This object is pretty printable via
18+
[`.print()`][kirin.print.printable.Printable.print] method.
19+
"""
20+
1221
defs: dict[str, ir.Method] = field(default_factory=dict)
22+
"""Mapping from symbol names to methods."""
1323
backedges: dict[str, set[str]] = field(default_factory=dict)
24+
"""Mapping from symbol names to backedges."""
1425

1526
def __init__(self, mt: ir.Method):
1627
self.defs = {}
@@ -26,14 +37,17 @@ def __build(self, mt: ir.Method):
2637
self.__build(stmt.callee)
2738

2839
def get_neighbors(self, node: str) -> Iterable[str]:
40+
"""Get the neighbors of a node in the call graph."""
2941
return self.backedges.get(node, ())
3042

3143
def get_edges(self) -> Iterable[tuple[str, str]]:
44+
"""Get the edges of the call graph."""
3245
for node, neighbors in self.backedges.items():
3346
for neighbor in neighbors:
3447
yield node, neighbor
3548

3649
def get_nodes(self) -> Iterable[str]:
50+
"""Get the nodes of the call graph."""
3751
return self.defs.keys()
3852

3953
def print_impl(self, printer: Printer) -> None:

src/kirin/analysis/cfg.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99

1010
@dataclass
1111
class CFG(Printable):
12-
"""Control Flow Graph of a given IR statement."""
12+
"""Control Flow Graph of a given IR statement.
13+
14+
This class implements the [`kirin.graph.Graph`][kirin.graph.Graph] protocol.
15+
16+
!!! note "Pretty Printing"
17+
This object is pretty printable via
18+
[`.print()`][kirin.print.printable.Printable.print] method.
19+
"""
1320

1421
parent: ir.Region
1522
"""Parent IR statement.

src/kirin/analysis/const/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
"""Const analysis module.
2+
3+
This module contains the constant analysis framework for kirin. The constant
4+
analysis framework is built on top of the interpreter framework.
5+
6+
This module provides a lattice for constant propagation analysis and a
7+
propagation algorithm for computing the constant values for each SSA value in
8+
the IR.
9+
"""
10+
111
from .prop import Propagate as Propagate
212
from .lattice import (
313
Pure as Pure,

src/kirin/analysis/const/lattice.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
"""Lattice for constant analysis.
2+
"""
3+
14
from typing import Any, final
25
from dataclasses import field, dataclass
36

@@ -22,6 +25,7 @@ class Result(
2225
BoundedLattice["Result"],
2326
_ElemVisitor,
2427
):
28+
"""Base class for constant analysis results."""
2529

2630
@classmethod
2731
def top(cls) -> "Result":
@@ -35,6 +39,7 @@ def bottom(cls) -> "Result":
3539
@final
3640
@dataclass
3741
class Unknown(Result, metaclass=SingletonMeta):
42+
"""Unknown constant value. This is the top element of the lattice."""
3843

3944
def is_subseteq(self, other: Result) -> bool:
4045
return isinstance(other, Unknown)
@@ -43,6 +48,7 @@ def is_subseteq(self, other: Result) -> bool:
4348
@final
4449
@dataclass
4550
class Bottom(Result, metaclass=SingletonMeta):
51+
"""Bottom element of the lattice."""
4652

4753
def is_subseteq(self, other: Result) -> bool:
4854
return True
@@ -51,6 +57,8 @@ def is_subseteq(self, other: Result) -> bool:
5157
@final
5258
@dataclass
5359
class Value(Result):
60+
"""Constant value. Wraps any Python value."""
61+
5462
data: Any
5563

5664
def is_subseteq_Value(self, other: "Value") -> bool:
@@ -64,11 +72,19 @@ def is_equal(self, other: Result) -> bool:
6472

6573
@dataclass
6674
class PartialConst(Result):
75+
"""Base class for partial constant values."""
76+
6777
pass
6878

6979

7080
@final
7181
class PartialTupleMeta(LatticeMeta):
82+
"""Metaclass for PartialTuple.
83+
84+
This metaclass canonicalizes PartialTuple instances with all Value elements
85+
into a single Value instance.
86+
"""
87+
7288
def __call__(cls, data: tuple[Result, ...]):
7389
if all(isinstance(x, Value) for x in data):
7490
return Value(tuple(x.data for x in data)) # type: ignore
@@ -78,6 +94,8 @@ def __call__(cls, data: tuple[Result, ...]):
7894
@final
7995
@dataclass
8096
class PartialTuple(PartialConst, metaclass=PartialTupleMeta):
97+
"""Partial tuple constant value."""
98+
8199
data: tuple[Result, ...]
82100

83101
def join(self, other: Result) -> Result:
@@ -125,6 +143,11 @@ def is_subseteq_Value(self, other: Value) -> bool:
125143
@final
126144
@dataclass
127145
class PartialLambda(PartialConst):
146+
"""Partial lambda constant value.
147+
148+
This represents a closure with captured variables.
149+
"""
150+
128151
argnames: list[str]
129152
code: ir.Statement
130153
captured: tuple[Result, ...]
@@ -177,6 +200,7 @@ def meet(self, other: Result) -> Result:
177200
class Purity(
178201
SimpleJoinMixin["Purity"], SimpleMeetMixin["Purity"], BoundedLattice["Purity"]
179202
):
203+
"""Base class for purity lattice."""
180204

181205
@classmethod
182206
def bottom(cls) -> "Purity":
@@ -189,29 +213,43 @@ def top(cls) -> "Purity":
189213

190214
@dataclass(frozen=True)
191215
class Pure(Purity, metaclass=SingletonMeta):
216+
"""The result is from a pure function."""
192217

193218
def is_subseteq(self, other: Purity) -> bool:
194219
return isinstance(other, (NotPure, Pure))
195220

196221

197222
@dataclass(frozen=True)
198223
class NotPure(Purity, metaclass=SingletonMeta):
224+
"""The result is from an impure function."""
199225

200226
def is_subseteq(self, other: Purity) -> bool:
201227
return isinstance(other, NotPure)
202228

203229

204230
@dataclass(frozen=True)
205231
class PurityBottom(Purity, metaclass=SingletonMeta):
232+
"""The bottom element of the purity lattice."""
206233

207234
def is_subseteq(self, other: Purity) -> bool:
208235
return True
209236

210237

211238
@dataclass
212239
class JointResult(BoundedLattice["JointResult"]):
240+
"""Joint result of constant value and purity.
241+
242+
This lattice is used to join the constant value and purity of a function
243+
during constant propagation analysis. This allows the analysis to track
244+
both the constant value and the purity of the function, so that the analysis
245+
can propagate constant values through function calls even if the function
246+
is only partially pure.
247+
"""
248+
213249
const: Result
250+
"""The constant value of the result."""
214251
purity: Purity = field(default_factory=Purity.top)
252+
"""The purity of statement that produces the result."""
215253

216254
@classmethod
217255
def from_const(cls, value: Any) -> "JointResult":

src/kirin/analysis/const/prop.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ class ExtraFrameInfo:
1313

1414
@dataclass
1515
class Propagate(ForwardExtra[JointResult, ExtraFrameInfo]):
16+
"""Forward dataflow analysis for constant propagation.
17+
18+
This analysis is a forward dataflow analysis that propagates constant values
19+
through the program. It uses the `JointResult` lattice to track the constant
20+
values and purity of the values.
21+
22+
The analysis is implemented as a forward dataflow analysis, where the
23+
`eval_stmt` method is overridden to handle the different types of statements
24+
in the IR. The analysis uses the `interp.Interpreter` to evaluate the
25+
statements and propagate the constant values.
26+
27+
When a statement is registered under the "constprop" key in the method table,
28+
the analysis will call the method to evaluate the statement instead of using
29+
the interpreter. This allows for custom handling of statements.
30+
"""
31+
1632
keys = ["constprop"]
1733
lattice = JointResult
1834

src/kirin/analysis/forward.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ def set_values(
6666
ssa: Iterable[ir.SSAValue],
6767
results: Iterable[LatticeElemType],
6868
):
69+
"""Set the abstract values for the given SSA values in the frame.
70+
71+
This method is used to customize how the abstract values are set in
72+
the frame. By default, the abstract values are set directly in the
73+
frame. This method is overridden to join the results if the SSA value
74+
already exists in the frame.
75+
"""
6976
for ssa_value, result in zip(ssa, results):
7077
if ssa_value in frame.entries:
7178
frame.entries[ssa_value] = frame.entries[ssa_value].join(result)
@@ -88,4 +95,11 @@ def new_frame(self, code: ir.Statement) -> ForwardFrame[LatticeElemType, ExtraTy
8895

8996

9097
class Forward(ForwardExtra[LatticeElemType, None], ABC):
98+
"""Forward dataflow analysis.
99+
100+
This is the base class for forward dataflow analysis. If your analysis
101+
requires extra information per frame, you should subclass
102+
[`ForwardExtra`][kirin.analysis.forward.ForwardExtra] instead.
103+
"""
104+
91105
pass
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
"""Type inference analysis for kirin.
2+
"""
3+
14
from .solve import TypeResolution as TypeResolution
25
from .analysis import TypeInference as TypeInference

src/kirin/analysis/typeinfer/analysis.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010

1111
@final
1212
class TypeInference(Forward[types.TypeAttribute]):
13+
"""Type inference analysis for kirin.
14+
15+
This analysis uses the forward dataflow analysis framework to infer the types of
16+
the IR. The analysis uses the type information within the IR to determine the
17+
method dispatch.
18+
19+
The analysis will fallback to a type resolution algorithm if the type information
20+
is not available in the IR but the type information is available in the abstract
21+
values.
22+
"""
23+
1324
keys = ["typeinfer"]
1425
lattice = types.TypeAttribute
1526

0 commit comments

Comments
 (0)