Skip to content

Commit 42c4b3d

Browse files
committed
Implement live variable analysis as an example of backward analysis
1 parent 6d10ddf commit 42c4b3d

File tree

3 files changed

+151
-1
lines changed

3 files changed

+151
-1
lines changed

tests/data/live.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <stdio.h>
2+
3+
int main()
4+
{
5+
int x;
6+
int y;
7+
8+
y = 10;
9+
10+
x = 10;
11+
printf("%d\\n", x); // used once
12+
printf("==="); // still live since it will be used
13+
a = 20; // even though another variable is used
14+
printf("%d\\n", x); // used again
15+
16+
x = 0; // overwritten - dead
17+
x = 5; // last write - live now
18+
printf("%d\\n", x);
19+
printf("==="); // x is dead now -- no more uses of it
20+
}

tree_climber/dataflow/dataflow_solver.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,37 @@ def solve(self):
2323
}[self.direction]()
2424

2525
def solve_backward(self):
26-
pass
26+
_in = {}
27+
out = {}
28+
for n in self.cfg.nodes():
29+
_in[n] = set() # can optimize by OUT[n] = GEN[n];
30+
if self.verbose >= 1:
31+
print(n, repr(self.cfg.nodes[n]["label"]))
32+
33+
q = list(reversed(list(self.cfg.nodes())))
34+
i = 0
35+
while q:
36+
n = q.pop(0)
37+
38+
out[n] = set()
39+
for succ in self.cfg.successors(n):
40+
out[n] = out[n].union(_in[succ])
41+
42+
new_in_n = self.gen(n).union(out[n].difference(self.kill(n)))
43+
44+
if self.verbose >= 2:
45+
print(f"{i=}, {n=}, {_in=}, {out=}, {new_in_n=}")
46+
47+
if _in[n] != new_in_n:
48+
if self.verbose >= 1:
49+
print(f"{i=}, {n=} changed {_in[n]} -> {new_in_n}")
50+
_in[n] = new_in_n
51+
for pred in self.cfg.predecessors(n):
52+
q.append(pred)
53+
i += 1
54+
55+
return _in, out
56+
2757
def solve_forward(self):
2858
_in = {}
2959
out = {}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from tree_climber.dataflow.dataflow_solver import DataflowSolver
2+
3+
4+
def get_definition(ast_node):
5+
# TODO: Refactor to common library of robust utilities for getting defs/uses
6+
if ast_node.type == "identifier":
7+
return ast_node.text.decode()
8+
elif ast_node.type == "pointer_declarator":
9+
return get_definition(ast_node.children[1])
10+
elif ast_node.type == "init_declarator":
11+
return get_definition(ast_node.children[0])
12+
elif ast_node.type == "declaration":
13+
return get_definition(ast_node.children[1])
14+
elif ast_node.type == "assignment_expression":
15+
return get_definition(ast_node.children[0])
16+
elif ast_node.type == "update_expression":
17+
return get_definition(ast_node.children[0])
18+
elif ast_node.type == "expression_statement":
19+
return get_definition(ast_node.children[0])
20+
return None
21+
22+
def get_uses(cfg, n):
23+
"""return the set of variables used in n"""
24+
# TODO: Exclude functions that are called
25+
used_ids = set()
26+
attr = cfg.nodes[n]
27+
if "n" in attr:
28+
q = [attr["n"]]
29+
while q:
30+
n = q.pop(0)
31+
if n.type == "identifier":
32+
_id = n.text.decode()
33+
used_ids.add(_id)
34+
children = n.children
35+
if n.type == "declaration":
36+
init = n.child_by_field_name("init_declarator")
37+
if init is not None:
38+
children = [init.child_by_field_name("value")]
39+
else:
40+
children = []
41+
# print(init, children)
42+
if n.type == "assignment_expression":
43+
children = [n.child_by_field_name("right")]
44+
q.extend(children)
45+
return used_ids
46+
47+
class LiveVariableSolver(DataflowSolver):
48+
"""
49+
live variables
50+
https://en.wikipedia.org/wiki/Live-variable_analysis
51+
"""
52+
53+
def __init__(self, cfg, verbose=0):
54+
super().__init__(cfg, verbose, "backward")
55+
56+
def gen(self, n) -> set:
57+
uses = get_uses(self.cfg, n)
58+
if self.verbose >= 2: print("Gen", n, uses)
59+
return uses
60+
61+
def kill(self, n) -> set:
62+
cfg_node = self.cfg.nodes[n]
63+
if "n" in cfg_node:
64+
ast_node = cfg_node["n"]
65+
defs = get_definition(ast_node)
66+
if defs is None:
67+
defs = set()
68+
else:
69+
defs = set()
70+
71+
if self.verbose >= 2: print("Kill", n, defs)
72+
return defs
73+
74+
if __name__ == "__main__":
75+
from tree_climber.cfg_parser import CFGParser
76+
data = """int main()
77+
{
78+
int x;
79+
int y;
80+
81+
y = 10;
82+
83+
x = 10;
84+
printf("%d\\n", x); // used once
85+
printf("==="); // still live since it will be used
86+
a = 20; // even though another variable is used
87+
printf("%d\\n", x); // used again
88+
89+
x = 0; // overwritten - dead
90+
x = 5; // last write - live now
91+
printf("%d\\n", x);
92+
printf("==="); // x is dead now -- no more uses of it
93+
}
94+
"""
95+
cfg = CFGParser.parse(data)
96+
solver = LiveVariableSolver(cfg, verbose=True)
97+
solution, sol_out = solver.solve()
98+
print("OUT:")
99+
for k, v in sorted(sol_out.items(), key=lambda p: p[0]):
100+
print(k, v)

0 commit comments

Comments
 (0)