sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/blocks.py +756 -756
- sonolus/backend/excepthook.py +37 -37
- sonolus/backend/finalize.py +77 -69
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +29 -3
- sonolus/backend/mode.py +24 -24
- sonolus/backend/node.py +40 -40
- sonolus/backend/ops.py +197 -197
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/optimize/allocate.py +126 -0
- sonolus/backend/optimize/constant_evaluation.py +374 -0
- sonolus/backend/optimize/copy_coalesce.py +85 -0
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/optimize/dominance.py +96 -0
- sonolus/backend/{flow.py → optimize/flow.py} +122 -92
- sonolus/backend/optimize/inlining.py +137 -0
- sonolus/backend/optimize/liveness.py +177 -0
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/optimize/passes.py +52 -0
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/optimize/ssa.py +200 -0
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +58 -48
- sonolus/backend/visitor.py +1151 -882
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/engine.py +10 -5
- sonolus/build/level.py +24 -23
- sonolus/build/node.py +43 -43
- sonolus/script/archetype.py +438 -139
- sonolus/script/array.py +27 -10
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +253 -191
- sonolus/script/containers.py +257 -51
- sonolus/script/debug.py +26 -10
- sonolus/script/easing.py +365 -0
- sonolus/script/effect.py +191 -131
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +303 -269
- sonolus/script/instruction.py +205 -151
- sonolus/script/internal/__init__.py +5 -5
- sonolus/script/internal/builtin_impls.py +255 -144
- sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +26 -9
- sonolus/script/internal/descriptor.py +17 -17
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +17 -14
- sonolus/script/internal/math_impls.py +121 -0
- sonolus/script/internal/native.py +40 -38
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/internal/value.py +3 -3
- sonolus/script/interval.py +338 -112
- sonolus/script/iterator.py +167 -214
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +80 -48
- sonolus/script/options.py +257 -191
- sonolus/script/particle.py +190 -157
- sonolus/script/pointer.py +30 -30
- sonolus/script/print.py +102 -81
- sonolus/script/project.py +8 -0
- sonolus/script/quad.py +263 -0
- sonolus/script/record.py +47 -16
- sonolus/script/runtime.py +52 -1
- sonolus/script/sprite.py +418 -333
- sonolus/script/text.py +409 -407
- sonolus/script/timing.py +114 -42
- sonolus/script/transform.py +332 -48
- sonolus/script/ui.py +216 -160
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +196 -78
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
- sonolus/backend/allocate.py +0 -51
- sonolus/backend/optimize.py +0 -9
- sonolus/backend/passes.py +0 -6
- sonolus/backend/simplify.py +0 -30
- sonolus/script/comptime.py +0 -160
- sonolus/script/graphics.py +0 -150
- sonolus/script/math.py +0 -92
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.3.dist-info/RECORD +0 -75
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from sonolus.backend.optimize.flow import (
|
|
2
|
+
BasicBlock,
|
|
3
|
+
traverse_cfg_reverse_postorder,
|
|
4
|
+
)
|
|
5
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DominanceFrontiers(CompilerPass):
|
|
9
|
+
def destroys(self) -> set[CompilerPass] | None:
|
|
10
|
+
return set()
|
|
11
|
+
|
|
12
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
13
|
+
blocks = list(traverse_cfg_reverse_postorder(entry))
|
|
14
|
+
|
|
15
|
+
self.number_blocks(blocks)
|
|
16
|
+
self.initialize_idoms(blocks, entry)
|
|
17
|
+
self.compute_idoms(blocks)
|
|
18
|
+
self.build_dominator_tree(blocks)
|
|
19
|
+
self.compute_dominance_frontiers(blocks)
|
|
20
|
+
|
|
21
|
+
return entry
|
|
22
|
+
|
|
23
|
+
def number_blocks(self, blocks: list[BasicBlock]):
|
|
24
|
+
"""Assign a unique number to each block based on reverse post-order."""
|
|
25
|
+
for idx, block in enumerate(blocks):
|
|
26
|
+
block.num = idx
|
|
27
|
+
|
|
28
|
+
def initialize_idoms(self, blocks: list[BasicBlock], entry_block: BasicBlock):
|
|
29
|
+
"""Initialize immediate dominators for each block."""
|
|
30
|
+
for block in blocks:
|
|
31
|
+
block.idom = None
|
|
32
|
+
entry_block.idom = entry_block
|
|
33
|
+
|
|
34
|
+
def compute_idoms(self, blocks: list[BasicBlock]):
|
|
35
|
+
"""Iteratively compute the immediate dominators of each block."""
|
|
36
|
+
changed = True
|
|
37
|
+
while changed:
|
|
38
|
+
changed = False
|
|
39
|
+
for b in blocks[1:]: # Skip the entry block
|
|
40
|
+
new_idom = None
|
|
41
|
+
for edge in b.incoming:
|
|
42
|
+
p = edge.src
|
|
43
|
+
if p.idom is not None:
|
|
44
|
+
if new_idom is None:
|
|
45
|
+
new_idom = p
|
|
46
|
+
else:
|
|
47
|
+
new_idom = self.intersect(p, new_idom)
|
|
48
|
+
if b.idom != new_idom:
|
|
49
|
+
b.idom = new_idom
|
|
50
|
+
changed = True
|
|
51
|
+
|
|
52
|
+
def build_dominator_tree(self, blocks: list[BasicBlock]):
|
|
53
|
+
"""Construct the dominator tree using the immediate dominators."""
|
|
54
|
+
for block in blocks:
|
|
55
|
+
block.dom_children = []
|
|
56
|
+
|
|
57
|
+
for block in blocks:
|
|
58
|
+
if block.idom != block:
|
|
59
|
+
block.idom.dom_children.append(block)
|
|
60
|
+
|
|
61
|
+
def compute_dominance_frontiers(self, blocks: list[BasicBlock]):
|
|
62
|
+
"""Compute the dominance frontiers for all blocks."""
|
|
63
|
+
for block in blocks:
|
|
64
|
+
block.df = set()
|
|
65
|
+
|
|
66
|
+
for b in blocks:
|
|
67
|
+
if len(b.incoming) >= 2:
|
|
68
|
+
for edge in b.incoming:
|
|
69
|
+
p = edge.src
|
|
70
|
+
runner = p
|
|
71
|
+
while runner != b.idom:
|
|
72
|
+
runner.df.add(b)
|
|
73
|
+
runner = runner.idom
|
|
74
|
+
|
|
75
|
+
def intersect(self, b1: BasicBlock, b2: BasicBlock) -> BasicBlock:
|
|
76
|
+
"""Helper function to find the closest common dominator of two blocks."""
|
|
77
|
+
while b1 != b2:
|
|
78
|
+
while b1.num > b2.num:
|
|
79
|
+
b1 = b1.idom
|
|
80
|
+
while b2.num > b1.num:
|
|
81
|
+
b2 = b2.idom
|
|
82
|
+
return b1
|
|
83
|
+
|
|
84
|
+
def __eq__(self, other):
|
|
85
|
+
return isinstance(other, DominanceFrontiers)
|
|
86
|
+
|
|
87
|
+
def __hash__(self):
|
|
88
|
+
return hash(DominanceFrontiers)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_df(block: BasicBlock) -> set[BasicBlock]:
|
|
92
|
+
return block.df
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_dom_children(block: BasicBlock) -> list[BasicBlock]:
|
|
96
|
+
return block.dom_children
|
|
@@ -1,92 +1,122 @@
|
|
|
1
|
-
import textwrap
|
|
2
|
-
from collections import deque
|
|
3
|
-
from collections.abc import Iterator
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
self.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
import textwrap
|
|
2
|
+
from collections import deque
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
7
|
+
from sonolus.backend.place import SSAPlace, TempBlock
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FlowEdge:
|
|
11
|
+
src: "BasicBlock"
|
|
12
|
+
dst: "BasicBlock"
|
|
13
|
+
cond: float | int | None
|
|
14
|
+
|
|
15
|
+
def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: float | None = None):
|
|
16
|
+
self.src = src
|
|
17
|
+
self.dst = dst
|
|
18
|
+
self.cond = cond
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BasicBlock:
|
|
22
|
+
phis: dict[SSAPlace | TempBlock, dict[Self, SSAPlace]]
|
|
23
|
+
statements: list[IRStmt]
|
|
24
|
+
test: IRExpr
|
|
25
|
+
incoming: set[FlowEdge]
|
|
26
|
+
outgoing: set[FlowEdge]
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
phi: dict[SSAPlace, dict[Self, SSAPlace]] | None = None,
|
|
32
|
+
statements: list[IRStmt] | None = None,
|
|
33
|
+
test: IRExpr | None = None,
|
|
34
|
+
incoming: set[FlowEdge] | None = None,
|
|
35
|
+
outgoing: set[FlowEdge] | None = None,
|
|
36
|
+
):
|
|
37
|
+
self.phis = phi or {}
|
|
38
|
+
self.statements = statements or []
|
|
39
|
+
self.test = test or IRConst(0)
|
|
40
|
+
self.incoming = incoming or set()
|
|
41
|
+
self.outgoing = outgoing or set()
|
|
42
|
+
|
|
43
|
+
def connect_to(self, other: "BasicBlock", cond: int | float | None = None):
|
|
44
|
+
edge = FlowEdge(self, other, cond)
|
|
45
|
+
self.outgoing.add(edge)
|
|
46
|
+
other.incoming.add(edge)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def traverse_cfg_preorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
50
|
+
visited = set()
|
|
51
|
+
queue = deque([block])
|
|
52
|
+
while queue:
|
|
53
|
+
block = queue.popleft()
|
|
54
|
+
if block in visited:
|
|
55
|
+
continue
|
|
56
|
+
visited.add(block)
|
|
57
|
+
yield block
|
|
58
|
+
for edge in sorted(block.outgoing, key=lambda e: (e.cond is None, e.cond)):
|
|
59
|
+
queue.append(edge.dst)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def traverse_cfg_postorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
63
|
+
visited = set()
|
|
64
|
+
|
|
65
|
+
def dfs(current: BasicBlock):
|
|
66
|
+
if current in visited:
|
|
67
|
+
return
|
|
68
|
+
visited.add(current)
|
|
69
|
+
for edge in sorted(current.outgoing, key=lambda e: (e.cond is None, e.cond)):
|
|
70
|
+
yield from dfs(edge.dst)
|
|
71
|
+
yield current
|
|
72
|
+
|
|
73
|
+
yield from dfs(block)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def traverse_cfg_reverse_postorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
77
|
+
yield from reversed(list(traverse_cfg_postorder(block)))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cfg_to_mermaid(entry: BasicBlock):
|
|
81
|
+
def pre(s: str):
|
|
82
|
+
return "\"<pre style='text-align: left;'>" + s.replace("\n", "<br/>") + '</pre>"'
|
|
83
|
+
|
|
84
|
+
def fmt(nodes):
|
|
85
|
+
if nodes:
|
|
86
|
+
return "\n".join(str(n) for n in nodes)
|
|
87
|
+
else:
|
|
88
|
+
return "{}"
|
|
89
|
+
|
|
90
|
+
block_indexes = {block: i for i, block in enumerate(traverse_cfg_reverse_postorder(entry))}
|
|
91
|
+
|
|
92
|
+
lines = ["Entry([Entry]) --> 0"]
|
|
93
|
+
for block, index in block_indexes.items():
|
|
94
|
+
lines.append(f"{index}[{pre(fmt([f'#{index}', *(
|
|
95
|
+
f"{dst} := phi({", ".join(f"{block_indexes.get(src_block, "<dead>")}: {src_place}"
|
|
96
|
+
for src_block, src_place
|
|
97
|
+
in sorted(phis.items(), key=lambda x: block_indexes.get(x[0])))})"
|
|
98
|
+
for dst, phis in block.phis.items()
|
|
99
|
+
), *block.statements]))}]")
|
|
100
|
+
|
|
101
|
+
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
102
|
+
match outgoing:
|
|
103
|
+
case {**other} if not other:
|
|
104
|
+
lines.append(f"{index} --> Exit")
|
|
105
|
+
case {None: target, **other} if not other:
|
|
106
|
+
lines.append(f"{index} --> {block_indexes[target]}")
|
|
107
|
+
case {0: f_branch, None: t_branch, **other} if not other:
|
|
108
|
+
lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
|
|
109
|
+
lines.append(f"{index} --> {index}_")
|
|
110
|
+
lines.append(f"{index}_ --> |true| {block_indexes[t_branch]}")
|
|
111
|
+
lines.append(f"{index}_ --> |false| {block_indexes[f_branch]}")
|
|
112
|
+
case dict() as tgt:
|
|
113
|
+
lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
|
|
114
|
+
lines.append(f"{index} --> {index}_")
|
|
115
|
+
for cond, target in tgt.items():
|
|
116
|
+
lines.append(
|
|
117
|
+
f"{index}_ --> |{pre(fmt([cond if cond is not None else "default"]))}| {block_indexes[target]}"
|
|
118
|
+
)
|
|
119
|
+
lines.append("Exit([Exit])")
|
|
120
|
+
|
|
121
|
+
body = textwrap.indent("\n".join(lines), " ")
|
|
122
|
+
return f"graph\n{body}"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
2
|
+
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
3
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
4
|
+
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InlineVars(CompilerPass):
|
|
8
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
9
|
+
use_counts: dict[SSAPlace, int] = {}
|
|
10
|
+
definitions: dict[SSAPlace, IRStmt] = {}
|
|
11
|
+
|
|
12
|
+
for block in traverse_cfg_preorder(entry):
|
|
13
|
+
for stmt in block.statements:
|
|
14
|
+
self.count_uses(stmt, use_counts)
|
|
15
|
+
if isinstance(stmt, IRSet) and isinstance(stmt.place, SSAPlace):
|
|
16
|
+
definitions[stmt.place] = stmt.value
|
|
17
|
+
self.count_uses(block.test, use_counts)
|
|
18
|
+
|
|
19
|
+
for p, defn in definitions.items():
|
|
20
|
+
while True:
|
|
21
|
+
if isinstance(defn, IRGet) and isinstance(defn.place, SSAPlace) and defn.place in definitions:
|
|
22
|
+
inside_defn = definitions[defn.place]
|
|
23
|
+
if not self.is_inlinable(inside_defn):
|
|
24
|
+
break
|
|
25
|
+
defn = inside_defn
|
|
26
|
+
continue
|
|
27
|
+
inlinable_uses = self.get_inlinable_uses(defn, set())
|
|
28
|
+
subs = {}
|
|
29
|
+
for inside_p in inlinable_uses:
|
|
30
|
+
if inside_p not in definitions:
|
|
31
|
+
continue
|
|
32
|
+
inside_defn = definitions[inside_p]
|
|
33
|
+
if not self.is_inlinable(inside_defn):
|
|
34
|
+
continue
|
|
35
|
+
if (isinstance(inside_defn, IRGet) and isinstance(inside_defn.place, SSAPlace)) or use_counts[
|
|
36
|
+
inside_p
|
|
37
|
+
] == 1:
|
|
38
|
+
subs[inside_p] = inside_defn
|
|
39
|
+
if not subs:
|
|
40
|
+
break
|
|
41
|
+
defn = self.substitute(defn, subs)
|
|
42
|
+
definitions[p] = defn
|
|
43
|
+
|
|
44
|
+
valid = {p for p in definitions if self.is_inlinable(definitions[p]) and use_counts.get(p, 0) <= 1}
|
|
45
|
+
|
|
46
|
+
for block in traverse_cfg_preorder(entry):
|
|
47
|
+
new_statements = []
|
|
48
|
+
for stmt in [*block.statements, block.test]:
|
|
49
|
+
inlinable_uses = self.get_inlinable_uses(stmt, set())
|
|
50
|
+
subs = {}
|
|
51
|
+
for p in inlinable_uses:
|
|
52
|
+
if p not in valid:
|
|
53
|
+
continue
|
|
54
|
+
definition = definitions[p]
|
|
55
|
+
subs[p] = definition
|
|
56
|
+
|
|
57
|
+
if subs:
|
|
58
|
+
new_statements.append(self.substitute(stmt, subs))
|
|
59
|
+
else:
|
|
60
|
+
new_statements.append(stmt)
|
|
61
|
+
|
|
62
|
+
block.statements = new_statements[:-1]
|
|
63
|
+
block.test = new_statements[-1]
|
|
64
|
+
|
|
65
|
+
return entry
|
|
66
|
+
|
|
67
|
+
def substitute(self, stmt, subs):
|
|
68
|
+
match stmt:
|
|
69
|
+
case IRConst():
|
|
70
|
+
return stmt
|
|
71
|
+
case IRInstr(op=op, args=args):
|
|
72
|
+
return IRInstr(op=op, args=[self.substitute(arg, subs) for arg in args])
|
|
73
|
+
case IRPureInstr(op=op, args=args):
|
|
74
|
+
return IRPureInstr(op=op, args=[self.substitute(arg, subs) for arg in args])
|
|
75
|
+
case IRGet(place=place):
|
|
76
|
+
if place in subs:
|
|
77
|
+
return subs[place]
|
|
78
|
+
return stmt
|
|
79
|
+
case IRSet(place=place, value=value):
|
|
80
|
+
return IRSet(place=place, value=self.substitute(value, subs))
|
|
81
|
+
case _:
|
|
82
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
83
|
+
|
|
84
|
+
def count_uses(self, stmt, use_counts):
|
|
85
|
+
match stmt:
|
|
86
|
+
case IRConst():
|
|
87
|
+
pass
|
|
88
|
+
case IRInstr(op=_, args=args) | IRPureInstr(op=_, args=args):
|
|
89
|
+
for arg in args:
|
|
90
|
+
self.count_uses(arg, use_counts)
|
|
91
|
+
case IRGet(place=place):
|
|
92
|
+
self.count_uses(place, use_counts)
|
|
93
|
+
case IRSet(place=place, value=value):
|
|
94
|
+
if not isinstance(place, SSAPlace): # We don't want to count the definition itself
|
|
95
|
+
self.count_uses(place, use_counts)
|
|
96
|
+
self.count_uses(value, use_counts)
|
|
97
|
+
case SSAPlace():
|
|
98
|
+
use_counts[stmt] = use_counts.get(stmt, 0) + 1
|
|
99
|
+
case BlockPlace(block=block, index=index, offset=_):
|
|
100
|
+
self.count_uses(block, use_counts)
|
|
101
|
+
self.count_uses(index, use_counts)
|
|
102
|
+
case int() | float():
|
|
103
|
+
pass
|
|
104
|
+
case TempBlock():
|
|
105
|
+
pass
|
|
106
|
+
case _:
|
|
107
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
108
|
+
return use_counts
|
|
109
|
+
|
|
110
|
+
def get_inlinable_uses(self, stmt, uses):
|
|
111
|
+
match stmt:
|
|
112
|
+
case IRConst():
|
|
113
|
+
pass
|
|
114
|
+
case IRInstr(op=_, args=args) | IRPureInstr(op=_, args=args):
|
|
115
|
+
for arg in args:
|
|
116
|
+
self.get_inlinable_uses(arg, uses)
|
|
117
|
+
case IRGet(place=place):
|
|
118
|
+
if isinstance(place, SSAPlace):
|
|
119
|
+
uses.add(place)
|
|
120
|
+
case IRSet(place=_, value=value):
|
|
121
|
+
self.get_inlinable_uses(value, uses)
|
|
122
|
+
case _:
|
|
123
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
124
|
+
return uses
|
|
125
|
+
|
|
126
|
+
def is_inlinable(self, stmt):
|
|
127
|
+
match stmt:
|
|
128
|
+
case IRConst():
|
|
129
|
+
return True
|
|
130
|
+
case IRInstr(op=op, args=args) | IRPureInstr(op=op, args=args):
|
|
131
|
+
return not op.side_effects and op.pure and all(self.is_inlinable(arg) for arg in args)
|
|
132
|
+
case IRGet():
|
|
133
|
+
return isinstance(stmt.place, SSAPlace)
|
|
134
|
+
case IRSet():
|
|
135
|
+
return False
|
|
136
|
+
case _:
|
|
137
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
|
|
3
|
+
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
4
|
+
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
5
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
6
|
+
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
7
|
+
|
|
8
|
+
type HasLiveness = SSAPlace | TempBlock
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LivenessAnalysis(CompilerPass):
|
|
12
|
+
def destroys(self) -> set[CompilerPass]:
|
|
13
|
+
return set()
|
|
14
|
+
|
|
15
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
16
|
+
self.preprocess(entry)
|
|
17
|
+
self.process(entry)
|
|
18
|
+
return entry
|
|
19
|
+
|
|
20
|
+
def preprocess(self, entry: BasicBlock):
|
|
21
|
+
for block in traverse_cfg_preorder(entry):
|
|
22
|
+
block.live_out = None
|
|
23
|
+
block.live_in = set()
|
|
24
|
+
block.live_phi_targets = set()
|
|
25
|
+
block.array_defs_in = set()
|
|
26
|
+
block.array_defs_out = None
|
|
27
|
+
for statement in block.statements:
|
|
28
|
+
statement.live = set()
|
|
29
|
+
statement.visited = False
|
|
30
|
+
statement.uses = self.get_uses(statement, set())
|
|
31
|
+
statement.defs = self.get_defs(statement)
|
|
32
|
+
statement.is_array_init = False # True if this may be the first assignment to an array
|
|
33
|
+
statement.array_defs = self.get_array_defs(statement)
|
|
34
|
+
block.test.live = set()
|
|
35
|
+
block.test.uses = self.get_uses(block.test, set())
|
|
36
|
+
self.preprocess_arrays(entry)
|
|
37
|
+
|
|
38
|
+
def process(self, entry: BasicBlock):
|
|
39
|
+
queue = deque(self.get_exits(entry))
|
|
40
|
+
if not queue:
|
|
41
|
+
raise ValueError("Infinite loop detected")
|
|
42
|
+
while queue:
|
|
43
|
+
block = queue.popleft()
|
|
44
|
+
updated_blocks = self.process_block(block)
|
|
45
|
+
queue.extend(updated_blocks)
|
|
46
|
+
|
|
47
|
+
def preprocess_arrays(self, entry: BasicBlock):
|
|
48
|
+
queue = {entry}
|
|
49
|
+
visited = set()
|
|
50
|
+
while queue:
|
|
51
|
+
block = queue.pop()
|
|
52
|
+
array_defs = block.array_defs_in.copy()
|
|
53
|
+
is_first_visit = block not in visited
|
|
54
|
+
visited.add(block)
|
|
55
|
+
for statement in block.statements:
|
|
56
|
+
if statement.array_defs - array_defs:
|
|
57
|
+
statement.is_array_init = True
|
|
58
|
+
array_defs.update(statement.array_defs)
|
|
59
|
+
else:
|
|
60
|
+
statement.is_array_init = False
|
|
61
|
+
if is_first_visit or array_defs != block.array_defs_out:
|
|
62
|
+
block.array_defs_out = array_defs
|
|
63
|
+
for edge in block.outgoing:
|
|
64
|
+
queue.add(edge.dst)
|
|
65
|
+
edge.dst.array_defs_in.update(array_defs)
|
|
66
|
+
|
|
67
|
+
def process_block(self, block: BasicBlock) -> list[BasicBlock]:
|
|
68
|
+
if block.live_out is None:
|
|
69
|
+
block.live_out = set()
|
|
70
|
+
live: set[HasLiveness] = {
|
|
71
|
+
place
|
|
72
|
+
for place in block.live_out
|
|
73
|
+
if not (isinstance(place, TempBlock) and place.size > 1 and place not in block.array_defs_out)
|
|
74
|
+
}
|
|
75
|
+
block.test.live.update(live)
|
|
76
|
+
live.update(block.test.uses)
|
|
77
|
+
for statement in reversed(block.statements):
|
|
78
|
+
statement.live.update(live)
|
|
79
|
+
if self.can_skip(statement, live):
|
|
80
|
+
continue
|
|
81
|
+
live.difference_update(statement.defs)
|
|
82
|
+
if statement.is_array_init:
|
|
83
|
+
live.difference_update(statement.array_defs)
|
|
84
|
+
live.update(statement.uses)
|
|
85
|
+
prev_sizes_by_block = {
|
|
86
|
+
edge.src: len(edge.src.live_out) if edge.src.live_out is not None else -1 for edge in block.incoming
|
|
87
|
+
}
|
|
88
|
+
live_phi_targets = set()
|
|
89
|
+
for target, args in block.phis.items():
|
|
90
|
+
if target not in live:
|
|
91
|
+
continue
|
|
92
|
+
live.remove(target)
|
|
93
|
+
for src_block, arg in args.items():
|
|
94
|
+
if src_block.live_out is None:
|
|
95
|
+
src_block.live_out = set()
|
|
96
|
+
src_block.live_out.add(arg)
|
|
97
|
+
live_phi_targets.add(target)
|
|
98
|
+
block.live_in = live
|
|
99
|
+
block.live_phi_targets = live_phi_targets
|
|
100
|
+
updated_blocks = []
|
|
101
|
+
for edge in block.incoming:
|
|
102
|
+
if edge.src.live_out is None:
|
|
103
|
+
edge.src.live_out = set()
|
|
104
|
+
edge.src.live_out.update(live)
|
|
105
|
+
if len(edge.src.live_out) != prev_sizes_by_block[edge.src]:
|
|
106
|
+
updated_blocks.append(edge.src)
|
|
107
|
+
return updated_blocks
|
|
108
|
+
|
|
109
|
+
def get_uses(
|
|
110
|
+
self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int, uses: set[HasLiveness]
|
|
111
|
+
) -> set[HasLiveness]:
|
|
112
|
+
match stmt:
|
|
113
|
+
case IRPureInstr(op=_, args=args) | IRInstr(op=_, args=args):
|
|
114
|
+
for arg in args:
|
|
115
|
+
self.get_uses(arg, uses)
|
|
116
|
+
case IRGet(place=place):
|
|
117
|
+
self.get_uses(place, uses)
|
|
118
|
+
case IRSet(place=place, value=value):
|
|
119
|
+
if isinstance(place, BlockPlace):
|
|
120
|
+
if not isinstance(place.block, TempBlock):
|
|
121
|
+
self.get_uses(place.block, uses)
|
|
122
|
+
self.get_uses(place.index, uses)
|
|
123
|
+
self.get_uses(value, uses)
|
|
124
|
+
case IRConst() | int():
|
|
125
|
+
pass
|
|
126
|
+
case BlockPlace(block=block, index=index, offset=_):
|
|
127
|
+
self.get_uses(block, uses)
|
|
128
|
+
self.get_uses(index, uses)
|
|
129
|
+
case SSAPlace() | TempBlock():
|
|
130
|
+
uses.add(stmt)
|
|
131
|
+
case _:
|
|
132
|
+
raise TypeError(f"Unexpected statement type: {type(stmt)}")
|
|
133
|
+
return uses
|
|
134
|
+
|
|
135
|
+
def get_defs(self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int) -> set[HasLiveness]:
|
|
136
|
+
match stmt:
|
|
137
|
+
case IRSet(place=place, value=_):
|
|
138
|
+
match place:
|
|
139
|
+
case SSAPlace():
|
|
140
|
+
return {place}
|
|
141
|
+
case BlockPlace(block=TempBlock() as temp_block, index=_, offset=_) if temp_block.size == 1:
|
|
142
|
+
return {temp_block}
|
|
143
|
+
return set()
|
|
144
|
+
|
|
145
|
+
def get_array_defs(self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int) -> set[HasLiveness]:
|
|
146
|
+
match stmt:
|
|
147
|
+
case IRSet(place=place, value=_):
|
|
148
|
+
match place:
|
|
149
|
+
case BlockPlace(block=TempBlock() as temp_block, index=_, offset=_) if temp_block.size > 1:
|
|
150
|
+
return {temp_block}
|
|
151
|
+
return set()
|
|
152
|
+
|
|
153
|
+
def can_skip(self, stmt: IRStmt, live: set[HasLiveness]) -> bool:
|
|
154
|
+
match stmt:
|
|
155
|
+
case IRSet(place=_, value=value):
|
|
156
|
+
if isinstance(value, IRInstr) and value.op.side_effects:
|
|
157
|
+
return False
|
|
158
|
+
defs = stmt.defs | stmt.array_defs
|
|
159
|
+
return defs and not (defs & live)
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def get_exits(self, entry: BasicBlock) -> list[BasicBlock]:
|
|
163
|
+
return [block for block in traverse_cfg_preorder(entry) if not block.outgoing]
|
|
164
|
+
|
|
165
|
+
def __eq__(self, other):
|
|
166
|
+
return isinstance(other, LivenessAnalysis)
|
|
167
|
+
|
|
168
|
+
def __hash__(self):
|
|
169
|
+
return hash(LivenessAnalysis)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_live(stmt: IRStmt) -> set[HasLiveness]:
|
|
173
|
+
return stmt.live
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_live_phi_targets(block: BasicBlock) -> set[HasLiveness]:
|
|
177
|
+
return block.live_phi_targets
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from sonolus.backend.optimize.allocate import Allocate, AllocateBasic
|
|
2
|
+
from sonolus.backend.optimize.constant_evaluation import SparseConditionalConstantPropagation
|
|
3
|
+
from sonolus.backend.optimize.copy_coalesce import CopyCoalesce
|
|
4
|
+
from sonolus.backend.optimize.dead_code import (
|
|
5
|
+
AdvancedDeadCodeElimination,
|
|
6
|
+
DeadCodeElimination,
|
|
7
|
+
UnreachableCodeElimination,
|
|
8
|
+
)
|
|
9
|
+
from sonolus.backend.optimize.flow import BasicBlock
|
|
10
|
+
from sonolus.backend.optimize.inlining import InlineVars
|
|
11
|
+
from sonolus.backend.optimize.passes import run_passes
|
|
12
|
+
from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
|
|
13
|
+
from sonolus.backend.optimize.ssa import FromSSA, ToSSA
|
|
14
|
+
|
|
15
|
+
MINIMAL_PASSES = [
|
|
16
|
+
CoalesceFlow(),
|
|
17
|
+
UnreachableCodeElimination(),
|
|
18
|
+
AllocateBasic(),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
STANDARD_PASSES = [
|
|
22
|
+
CoalesceFlow(),
|
|
23
|
+
UnreachableCodeElimination(),
|
|
24
|
+
DeadCodeElimination(),
|
|
25
|
+
ToSSA(),
|
|
26
|
+
SparseConditionalConstantPropagation(),
|
|
27
|
+
UnreachableCodeElimination(),
|
|
28
|
+
DeadCodeElimination(),
|
|
29
|
+
CoalesceFlow(),
|
|
30
|
+
InlineVars(),
|
|
31
|
+
DeadCodeElimination(),
|
|
32
|
+
RewriteToSwitch(),
|
|
33
|
+
FromSSA(),
|
|
34
|
+
CoalesceFlow(),
|
|
35
|
+
CopyCoalesce(),
|
|
36
|
+
AdvancedDeadCodeElimination(),
|
|
37
|
+
CoalesceFlow(),
|
|
38
|
+
NormalizeSwitch(),
|
|
39
|
+
Allocate(),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def optimize_and_allocate(cfg: BasicBlock):
|
|
44
|
+
return run_passes(cfg, STANDARD_PASSES)
|