sonolus.py 0.3.4__py3-none-any.whl → 0.4.1__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/excepthook.py +30 -0
- sonolus/backend/finalize.py +15 -1
- sonolus/backend/ops.py +4 -0
- sonolus/backend/optimize/allocate.py +5 -5
- sonolus/backend/optimize/constant_evaluation.py +124 -19
- sonolus/backend/optimize/copy_coalesce.py +15 -12
- sonolus/backend/optimize/dead_code.py +7 -6
- sonolus/backend/optimize/dominance.py +2 -2
- sonolus/backend/optimize/flow.py +54 -8
- sonolus/backend/optimize/inlining.py +137 -30
- sonolus/backend/optimize/liveness.py +2 -2
- sonolus/backend/optimize/optimize.py +15 -1
- sonolus/backend/optimize/passes.py +11 -3
- sonolus/backend/optimize/simplify.py +137 -8
- sonolus/backend/optimize/ssa.py +47 -13
- sonolus/backend/place.py +5 -4
- sonolus/backend/utils.py +44 -16
- sonolus/backend/visitor.py +288 -17
- sonolus/build/cli.py +47 -19
- sonolus/build/compile.py +12 -5
- sonolus/build/engine.py +70 -1
- sonolus/build/level.py +3 -3
- sonolus/build/project.py +2 -2
- sonolus/script/archetype.py +12 -9
- sonolus/script/array.py +23 -18
- sonolus/script/array_like.py +26 -29
- sonolus/script/bucket.py +1 -1
- sonolus/script/containers.py +22 -26
- sonolus/script/debug.py +20 -43
- sonolus/script/effect.py +1 -1
- sonolus/script/globals.py +3 -3
- sonolus/script/instruction.py +2 -2
- sonolus/script/internal/builtin_impls.py +155 -28
- sonolus/script/internal/constant.py +13 -3
- sonolus/script/internal/context.py +46 -15
- sonolus/script/internal/impl.py +9 -3
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/native.py +2 -2
- sonolus/script/internal/range.py +8 -11
- sonolus/script/internal/simulation_context.py +1 -1
- sonolus/script/internal/transient.py +2 -2
- sonolus/script/internal/value.py +41 -3
- sonolus/script/interval.py +13 -13
- sonolus/script/iterator.py +53 -107
- sonolus/script/level.py +2 -2
- sonolus/script/maybe.py +241 -0
- sonolus/script/num.py +29 -14
- sonolus/script/options.py +1 -1
- sonolus/script/particle.py +1 -1
- sonolus/script/project.py +24 -5
- sonolus/script/quad.py +15 -15
- sonolus/script/record.py +48 -44
- sonolus/script/runtime.py +22 -18
- sonolus/script/sprite.py +1 -1
- sonolus/script/stream.py +66 -82
- sonolus/script/transform.py +35 -34
- sonolus/script/values.py +10 -10
- sonolus/script/vec.py +21 -18
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/METADATA +1 -1
- sonolus_py-0.4.1.dist-info/RECORD +93 -0
- sonolus_py-0.3.4.dist-info/RECORD +0 -92
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/excepthook.py
CHANGED
|
@@ -16,6 +16,10 @@ def is_compiler_internal(tb: TracebackType):
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def is_traceback_root(tb: TracebackType | None) -> bool:
|
|
20
|
+
return tb.tb_frame.f_locals.get("_traceback_root_", False) or tb.tb_frame.f_globals.get("_traceback_root_", False)
|
|
21
|
+
|
|
22
|
+
|
|
19
23
|
def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
|
|
20
24
|
if tb is None:
|
|
21
25
|
return None
|
|
@@ -25,6 +29,17 @@ def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
|
|
|
25
29
|
return tb
|
|
26
30
|
|
|
27
31
|
|
|
32
|
+
def truncate_traceback(tb: TracebackType | None) -> TracebackType | None:
|
|
33
|
+
if tb is None:
|
|
34
|
+
return None
|
|
35
|
+
if is_compiler_internal(tb):
|
|
36
|
+
return truncate_traceback(tb.tb_next)
|
|
37
|
+
if is_traceback_root(tb):
|
|
38
|
+
return tb
|
|
39
|
+
else:
|
|
40
|
+
return truncate_traceback(tb.tb_next)
|
|
41
|
+
|
|
42
|
+
|
|
28
43
|
def excepthook(exc, value, tb):
|
|
29
44
|
import traceback
|
|
30
45
|
|
|
@@ -33,5 +48,20 @@ def excepthook(exc, value, tb):
|
|
|
33
48
|
traceback.print_exception(exc, value, tb)
|
|
34
49
|
|
|
35
50
|
|
|
51
|
+
def print_simple_traceback(exc, value, tb):
|
|
52
|
+
import traceback
|
|
53
|
+
|
|
54
|
+
if should_filter_traceback(tb):
|
|
55
|
+
tb = filter_traceback(tb)
|
|
56
|
+
truncated = truncate_traceback(tb)
|
|
57
|
+
tb = truncated if truncated is not None else tb
|
|
58
|
+
cause = value.__cause__
|
|
59
|
+
value.__cause__ = None
|
|
60
|
+
traceback.print_exception(exc, value, tb)
|
|
61
|
+
value.__cause__ = cause
|
|
62
|
+
else:
|
|
63
|
+
traceback.print_exception(exc, value, tb)
|
|
64
|
+
|
|
65
|
+
|
|
36
66
|
def install_excepthook():
|
|
37
67
|
sys.excepthook = excepthook
|
sonolus/backend/finalize.py
CHANGED
|
@@ -13,7 +13,9 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
13
13
|
for block in block_indexes:
|
|
14
14
|
statements = []
|
|
15
15
|
statements.extend(ir_to_engine_node(stmt) for stmt in block.statements)
|
|
16
|
-
outgoing = {
|
|
16
|
+
outgoing = {
|
|
17
|
+
edge.cond: edge.dst for edge in sorted(block.outgoing, key=lambda edge: (edge.cond is None, edge.cond))
|
|
18
|
+
}
|
|
17
19
|
match outgoing:
|
|
18
20
|
case {**other} if not other:
|
|
19
21
|
statements.append(ConstantNode(value=len(block_indexes)))
|
|
@@ -30,6 +32,18 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
30
32
|
],
|
|
31
33
|
)
|
|
32
34
|
)
|
|
35
|
+
case {None: default_branch, **other} if len(other) == 1:
|
|
36
|
+
cond, cond_branch = next(iter(other.items()))
|
|
37
|
+
statements.append(
|
|
38
|
+
FunctionNode(
|
|
39
|
+
func=Op.If,
|
|
40
|
+
args=[
|
|
41
|
+
ir_to_engine_node(IRPureInstr(Op.Equal, args=[block.test, IRConst(cond)])),
|
|
42
|
+
ConstantNode(value=block_indexes[cond_branch]),
|
|
43
|
+
ConstantNode(value=block_indexes[default_branch]),
|
|
44
|
+
],
|
|
45
|
+
)
|
|
46
|
+
)
|
|
33
47
|
case dict() as targets:
|
|
34
48
|
args = [ir_to_engine_node(block.test)]
|
|
35
49
|
default = len(block_indexes)
|
sonolus/backend/ops.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
2
2
|
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
3
3
|
from sonolus.backend.optimize.liveness import LivenessAnalysis, get_live
|
|
4
|
-
from sonolus.backend.optimize.passes import CompilerPass
|
|
4
|
+
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig
|
|
5
5
|
from sonolus.backend.place import BlockPlace, TempBlock
|
|
6
6
|
|
|
7
7
|
TEMP_SIZE = 4096
|
|
@@ -10,7 +10,7 @@ TEMP_SIZE = 4096
|
|
|
10
10
|
class AllocateBasic(CompilerPass):
|
|
11
11
|
"""Allocate temporary memory for temporary variables without considering lifetimes."""
|
|
12
12
|
|
|
13
|
-
def run(self, entry: BasicBlock):
|
|
13
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
14
14
|
offsets = {}
|
|
15
15
|
index = 0
|
|
16
16
|
|
|
@@ -62,7 +62,7 @@ class Allocate(CompilerPass):
|
|
|
62
62
|
def requires(self) -> set[CompilerPass]:
|
|
63
63
|
return {LivenessAnalysis()}
|
|
64
64
|
|
|
65
|
-
def run(self, entry: BasicBlock):
|
|
65
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
66
66
|
mapping = self.get_mapping(entry)
|
|
67
67
|
for block in traverse_cfg_preorder(entry):
|
|
68
68
|
updated_statements = [self.update_stmt(statement, mapping) for statement in block.statements]
|
|
@@ -114,7 +114,7 @@ class Allocate(CompilerPass):
|
|
|
114
114
|
interference = self.get_interference(entry)
|
|
115
115
|
offsets: dict[TempBlock, int] = {}
|
|
116
116
|
|
|
117
|
-
for block, others in sorted(interference.items(), key=lambda x: -x[0].size):
|
|
117
|
+
for block, others in sorted(interference.items(), key=lambda x: (-x[0].size, x[0].name)):
|
|
118
118
|
size = block.size
|
|
119
119
|
offset = 0
|
|
120
120
|
for other in sorted(others, key=lambda x: offsets.get(x, 0) + x.size):
|
|
@@ -152,7 +152,7 @@ class AllocateFast(Allocate):
|
|
|
152
152
|
offsets: dict[TempBlock, int] = dict.fromkeys(interference, 0)
|
|
153
153
|
end_offsets: dict[TempBlock, int] = dict.fromkeys(interference, 0)
|
|
154
154
|
|
|
155
|
-
for block, others in interference.items():
|
|
155
|
+
for block, others in sorted(interference.items(), key=lambda x: (-x[0].size, x[0].name)):
|
|
156
156
|
size = block.size
|
|
157
157
|
offset = max((end_offsets[other] for other in others), default=0)
|
|
158
158
|
if offset + size > TEMP_SIZE:
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import functools
|
|
3
3
|
import math
|
|
4
4
|
import operator
|
|
5
|
+
from collections import defaultdict
|
|
5
6
|
from typing import ClassVar
|
|
6
7
|
|
|
7
8
|
import sonolus.script.internal.math_impls as smath
|
|
8
9
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
9
10
|
from sonolus.backend.ops import Op
|
|
10
11
|
from sonolus.backend.optimize.flow import BasicBlock, FlowEdge, traverse_cfg_preorder
|
|
11
|
-
from sonolus.backend.optimize.passes import CompilerPass
|
|
12
|
+
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig
|
|
12
13
|
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
13
14
|
|
|
14
15
|
|
|
@@ -24,7 +25,11 @@ UNDEF = Undefined()
|
|
|
24
25
|
NAC = NotAConstant()
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
type Value = float | Undefined | NotAConstant
|
|
28
|
+
type Value = float | set[float] | Undefined | NotAConstant
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_constant(value: Value) -> bool:
|
|
32
|
+
return isinstance(value, (int, float))
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
class SparseConditionalConstantPropagation(CompilerPass):
|
|
@@ -61,24 +66,31 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
61
66
|
Op.Arccos,
|
|
62
67
|
Op.Arctan,
|
|
63
68
|
Op.Arctan2,
|
|
69
|
+
Op.Max,
|
|
70
|
+
Op.Min,
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
73
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
67
74
|
ssa_edges: dict[SSAPlace, set[SSAPlace | BasicBlock]] = {}
|
|
68
75
|
executable_edges: set[FlowEdge] = set()
|
|
69
76
|
|
|
70
77
|
# BasicBlock key means the block's test
|
|
71
|
-
values: dict[SSAPlace | BasicBlock, Value] =
|
|
78
|
+
values: dict[SSAPlace | BasicBlock, Value] = defaultdict(lambda: UNDEF)
|
|
72
79
|
defs: dict[SSAPlace | BasicBlock, IRStmt | dict[FlowEdge, SSAPlace]] = {}
|
|
73
80
|
places_to_blocks: dict[SSAPlace, BasicBlock] = {}
|
|
74
81
|
reachable_blocks: set[BasicBlock] = set()
|
|
75
82
|
|
|
76
83
|
for block in traverse_cfg_preorder(entry):
|
|
77
|
-
incoming_by_src = {
|
|
84
|
+
incoming_by_src = {}
|
|
85
|
+
for edge in block.incoming:
|
|
86
|
+
incoming_by_src.setdefault(edge.src, []).append(edge)
|
|
78
87
|
for p, args in block.phis.items():
|
|
79
88
|
if not isinstance(p, SSAPlace):
|
|
80
|
-
|
|
81
|
-
defs[p] = {
|
|
89
|
+
raise TypeError(f"Unexpected phi place: {p}")
|
|
90
|
+
defs[p] = {}
|
|
91
|
+
for b, v in args.items():
|
|
92
|
+
for incoming in incoming_by_src.get(b, []):
|
|
93
|
+
defs[p][incoming] = v
|
|
82
94
|
values[p] = UNDEF
|
|
83
95
|
for arg in args.values():
|
|
84
96
|
ssa_edges.setdefault(arg, set()).add(p)
|
|
@@ -95,13 +107,25 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
95
107
|
ssa_edges.setdefault(dep, set()).add(block)
|
|
96
108
|
|
|
97
109
|
def visit_phi(p):
|
|
98
|
-
arg_values = [values[v] if
|
|
110
|
+
arg_values = [values[v] if e in executable_edges else UNDEF for e, v in defs[p].items()]
|
|
99
111
|
distinct_defined_arg_values = {arg for arg in arg_values if arg is not UNDEF}
|
|
100
112
|
value = values[p]
|
|
101
113
|
if len(distinct_defined_arg_values) == 1:
|
|
102
114
|
new_value = distinct_defined_arg_values.pop()
|
|
103
115
|
elif len(distinct_defined_arg_values) > 1:
|
|
104
|
-
|
|
116
|
+
if any(arg is NAC for arg in distinct_defined_arg_values):
|
|
117
|
+
new_value = NAC
|
|
118
|
+
else:
|
|
119
|
+
new_values = set()
|
|
120
|
+
for arg in distinct_defined_arg_values:
|
|
121
|
+
if isinstance(arg, frozenset):
|
|
122
|
+
new_values.update(arg)
|
|
123
|
+
else:
|
|
124
|
+
new_values.add(arg)
|
|
125
|
+
if len(new_values) == 1:
|
|
126
|
+
new_value = next(iter(new_values))
|
|
127
|
+
else:
|
|
128
|
+
new_value = frozenset(new_values)
|
|
105
129
|
else:
|
|
106
130
|
new_value = UNDEF
|
|
107
131
|
if new_value != value:
|
|
@@ -137,11 +161,22 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
137
161
|
if new_test_value is NAC:
|
|
138
162
|
flow_worklist.update(block.outgoing)
|
|
139
163
|
reachable_blocks.update(e.dst for e in block.outgoing)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
164
|
+
elif block.outgoing:
|
|
165
|
+
outgoing_by_cond = {edge.cond: edge for edge in block.outgoing}
|
|
166
|
+
if is_constant(new_test_value):
|
|
167
|
+
taken_edge = outgoing_by_cond.get(new_test_value, outgoing_by_cond.get(None))
|
|
168
|
+
if taken_edge is None:
|
|
169
|
+
raise ValueError("Unexpected missing edge")
|
|
170
|
+
taken_edges = {taken_edge}
|
|
171
|
+
else:
|
|
172
|
+
taken_edges = set()
|
|
173
|
+
for v in new_test_value:
|
|
174
|
+
taken_edge = outgoing_by_cond.get(v, outgoing_by_cond.get(None))
|
|
175
|
+
if taken_edge:
|
|
176
|
+
taken_edges.add(taken_edge)
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError("Unexpected missing edge")
|
|
179
|
+
for taken_edge in taken_edges:
|
|
145
180
|
flow_worklist.add(taken_edge)
|
|
146
181
|
reachable_blocks.add(taken_edge.dst)
|
|
147
182
|
elif len(block.outgoing) == 1 and next(iter(block.outgoing)).cond is None:
|
|
@@ -164,10 +199,21 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
164
199
|
flow_worklist.update(p.outgoing)
|
|
165
200
|
reachable_blocks.update(e.dst for e in p.outgoing)
|
|
166
201
|
else:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
outgoing_by_cond = {edge.cond: edge for edge in p.outgoing}
|
|
203
|
+
if is_constant(new_test_value):
|
|
204
|
+
taken_edge = outgoing_by_cond.get(new_test_value, outgoing_by_cond.get(None))
|
|
205
|
+
if taken_edge is None:
|
|
206
|
+
raise ValueError("Unexpected missing edge")
|
|
207
|
+
taken_edges = {taken_edge}
|
|
208
|
+
else:
|
|
209
|
+
taken_edges = set()
|
|
210
|
+
for v in new_test_value:
|
|
211
|
+
taken_edge = outgoing_by_cond.get(v, outgoing_by_cond.get(None))
|
|
212
|
+
if taken_edge:
|
|
213
|
+
taken_edges.add(taken_edge)
|
|
214
|
+
else:
|
|
215
|
+
raise ValueError("Unexpected missing edge")
|
|
216
|
+
for taken_edge in taken_edges:
|
|
171
217
|
flow_worklist.add(taken_edge)
|
|
172
218
|
reachable_blocks.add(taken_edge.dst)
|
|
173
219
|
else:
|
|
@@ -183,6 +229,59 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
183
229
|
for block in traverse_cfg_preorder(entry):
|
|
184
230
|
block.statements = [self.substitute_constants(stmt, values) for stmt in block.statements]
|
|
185
231
|
block.test = self.substitute_constants(block.test, values)
|
|
232
|
+
if isinstance(block.test, IRGet) and block.test.place in values:
|
|
233
|
+
test_value = values[block.test.place]
|
|
234
|
+
if isinstance(test_value, frozenset):
|
|
235
|
+
new_outgoing = set()
|
|
236
|
+
outgoing_by_cond = {edge.cond: edge for edge in block.outgoing}
|
|
237
|
+
for v in test_value:
|
|
238
|
+
if v in outgoing_by_cond:
|
|
239
|
+
new_outgoing.add(outgoing_by_cond[v])
|
|
240
|
+
elif None in outgoing_by_cond:
|
|
241
|
+
new_outgoing.add(outgoing_by_cond[None])
|
|
242
|
+
else:
|
|
243
|
+
raise ValueError("Unexpected missing edge")
|
|
244
|
+
removed_edges = set(block.outgoing) - new_outgoing
|
|
245
|
+
for edge in removed_edges:
|
|
246
|
+
edge.dst.incoming.remove(edge)
|
|
247
|
+
if not any(edge.cond is None for edge in new_outgoing):
|
|
248
|
+
default_edge = max(new_outgoing, key=lambda e: e.cond)
|
|
249
|
+
default_edge.cond = None
|
|
250
|
+
block.outgoing = new_outgoing
|
|
251
|
+
|
|
252
|
+
reachable_blocks = set(traverse_cfg_preorder(entry))
|
|
253
|
+
for block in traverse_cfg_preorder(entry):
|
|
254
|
+
block.incoming = {edge for edge in block.incoming if edge.src in reachable_blocks}
|
|
255
|
+
incoming_blocks = {edge.src for edge in block.incoming}
|
|
256
|
+
for v in block.phis:
|
|
257
|
+
block.phis[v] = {k: v for k, v in block.phis[v].items() if k in incoming_blocks}
|
|
258
|
+
|
|
259
|
+
queue = set(traverse_cfg_preorder(entry))
|
|
260
|
+
while queue:
|
|
261
|
+
block = queue.pop()
|
|
262
|
+
dead_phis = {k for k, v in block.phis.items() if not v}
|
|
263
|
+
for edge in block.outgoing:
|
|
264
|
+
for v in edge.dst.phis.values():
|
|
265
|
+
if block in v and v[block] in dead_phis:
|
|
266
|
+
del v[block]
|
|
267
|
+
queue.add(edge.dst)
|
|
268
|
+
block.phis = {k: v for k, v in block.phis.items() if v}
|
|
269
|
+
|
|
270
|
+
for block in traverse_cfg_preorder(entry):
|
|
271
|
+
block.phis = {
|
|
272
|
+
k: {b: arg for b, arg in args.items() if values.get(arg, UNDEF) is not UNDEF}
|
|
273
|
+
for k, args in block.phis.items()
|
|
274
|
+
}
|
|
275
|
+
block.statements = [
|
|
276
|
+
stmt
|
|
277
|
+
for stmt in block.statements
|
|
278
|
+
# Note that if this is a set with a side effect, it will never be undef
|
|
279
|
+
if not (
|
|
280
|
+
isinstance(stmt, IRSet)
|
|
281
|
+
and isinstance(stmt.place, SSAPlace)
|
|
282
|
+
and values.get(stmt.place, UNDEF) is UNDEF
|
|
283
|
+
)
|
|
284
|
+
]
|
|
186
285
|
|
|
187
286
|
return entry
|
|
188
287
|
|
|
@@ -263,7 +362,7 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
263
362
|
case Op.Multiply:
|
|
264
363
|
if any(arg == 0 for arg in args):
|
|
265
364
|
return 0
|
|
266
|
-
if any(arg is NAC for arg in args):
|
|
365
|
+
if any(arg is NAC or isinstance(arg, frozenset) for arg in args):
|
|
267
366
|
return NAC
|
|
268
367
|
if any(arg is UNDEF for arg in args):
|
|
269
368
|
return UNDEF
|
|
@@ -366,6 +465,12 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
366
465
|
case Op.Arctan2:
|
|
367
466
|
assert len(args) == 2
|
|
368
467
|
return math.atan2(args[0], args[1])
|
|
468
|
+
case Op.Max:
|
|
469
|
+
assert len(args) == 2
|
|
470
|
+
return max(args)
|
|
471
|
+
case Op.Min:
|
|
472
|
+
assert len(args) == 2
|
|
473
|
+
return min(args)
|
|
369
474
|
case IRGet(place=SSAPlace() as place):
|
|
370
475
|
return values[place]
|
|
371
476
|
case IRGet():
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
2
2
|
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
3
3
|
from sonolus.backend.optimize.liveness import LivenessAnalysis, get_live
|
|
4
|
-
from sonolus.backend.optimize.passes import CompilerPass
|
|
4
|
+
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig
|
|
5
5
|
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
6
6
|
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ class CopyCoalesce(CompilerPass):
|
|
|
9
9
|
def requires(self) -> set[CompilerPass]:
|
|
10
10
|
return {LivenessAnalysis()}
|
|
11
11
|
|
|
12
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
12
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
13
13
|
mapping = self.get_mapping(entry)
|
|
14
14
|
for block in traverse_cfg_preorder(entry):
|
|
15
15
|
block.statements = [self.apply_to_stmt(stmt, mapping) for stmt in block.statements]
|
|
@@ -41,17 +41,20 @@ class CopyCoalesce(CompilerPass):
|
|
|
41
41
|
|
|
42
42
|
mapping: dict[TempBlock, set[TempBlock]] = {}
|
|
43
43
|
|
|
44
|
+
copy_pairs = set()
|
|
44
45
|
for target, sources in copies.items():
|
|
45
|
-
for source in sources
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
copy_pairs.update((min(target, source), max(target, source)) for source in sources)
|
|
47
|
+
|
|
48
|
+
for target, source in sorted(copy_pairs):
|
|
49
|
+
if source in interference.get(target, set()):
|
|
50
|
+
continue
|
|
51
|
+
combined_mapping = mapping.get(target, {target}) | mapping.get(source, {source})
|
|
52
|
+
combined_interference = interference.get(target, set()) | interference.get(source, set())
|
|
53
|
+
for place in combined_mapping:
|
|
54
|
+
mapping[place] = combined_mapping
|
|
55
|
+
interference[place] = combined_interference
|
|
56
|
+
for place in combined_interference:
|
|
57
|
+
interference[place].update(combined_mapping)
|
|
55
58
|
|
|
56
59
|
canonical_mapping = {}
|
|
57
60
|
for place, group in mapping.items():
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
2
2
|
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
3
3
|
from sonolus.backend.optimize.liveness import HasLiveness, LivenessAnalysis, get_live, get_live_phi_targets
|
|
4
|
-
from sonolus.backend.optimize.passes import CompilerPass
|
|
4
|
+
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig
|
|
5
5
|
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class UnreachableCodeElimination(CompilerPass):
|
|
9
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
9
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
10
10
|
original_blocks = [*traverse_cfg_preorder(entry)]
|
|
11
11
|
worklist = {entry}
|
|
12
12
|
visited = set()
|
|
@@ -29,7 +29,6 @@ class UnreachableCodeElimination(CompilerPass):
|
|
|
29
29
|
block.outgoing.remove(edge)
|
|
30
30
|
if taken_edge:
|
|
31
31
|
taken_edge.cond = None
|
|
32
|
-
block.outgoing.add(taken_edge)
|
|
33
32
|
worklist.add(taken_edge.dst)
|
|
34
33
|
case _:
|
|
35
34
|
worklist.update(edge.dst for edge in block.outgoing)
|
|
@@ -38,15 +37,17 @@ class UnreachableCodeElimination(CompilerPass):
|
|
|
38
37
|
for edge in block.outgoing:
|
|
39
38
|
edge.dst.incoming.remove(edge)
|
|
40
39
|
else:
|
|
40
|
+
incoming_blocks = {edge.src for edge in block.incoming}
|
|
41
41
|
for args in block.phis.values():
|
|
42
42
|
for src_block in [*args]:
|
|
43
|
-
if src_block not in visited:
|
|
43
|
+
if src_block not in visited or src_block not in incoming_blocks:
|
|
44
44
|
args.pop(src_block)
|
|
45
|
+
block.phis = {tgt: args for tgt, args in block.phis.items() if args}
|
|
45
46
|
return entry
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class DeadCodeElimination(CompilerPass):
|
|
49
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
50
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
50
51
|
uses = set()
|
|
51
52
|
defs = {}
|
|
52
53
|
for block in traverse_cfg_preorder(entry):
|
|
@@ -157,7 +158,7 @@ class AdvancedDeadCodeElimination(CompilerPass):
|
|
|
157
158
|
def requires(self) -> set[CompilerPass]:
|
|
158
159
|
return {LivenessAnalysis()}
|
|
159
160
|
|
|
160
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
161
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
161
162
|
for block in traverse_cfg_preorder(entry):
|
|
162
163
|
live_stmts = []
|
|
163
164
|
for statement in block.statements:
|
|
@@ -2,14 +2,14 @@ from sonolus.backend.optimize.flow import (
|
|
|
2
2
|
BasicBlock,
|
|
3
3
|
traverse_cfg_reverse_postorder,
|
|
4
4
|
)
|
|
5
|
-
from sonolus.backend.optimize.passes import CompilerPass
|
|
5
|
+
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class DominanceFrontiers(CompilerPass):
|
|
9
9
|
def destroys(self) -> set[CompilerPass] | None:
|
|
10
10
|
return set()
|
|
11
11
|
|
|
12
|
-
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
12
|
+
def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
|
|
13
13
|
blocks = list(traverse_cfg_reverse_postorder(entry))
|
|
14
14
|
|
|
15
15
|
self.number_blocks(blocks)
|
sonolus/backend/optimize/flow.py
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import textwrap
|
|
2
4
|
from collections import deque
|
|
3
5
|
from collections.abc import Iterator
|
|
4
|
-
from typing import Self
|
|
5
6
|
|
|
6
7
|
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
7
8
|
from sonolus.backend.place import SSAPlace, TempBlock
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class FlowEdge:
|
|
11
|
-
src:
|
|
12
|
-
dst:
|
|
12
|
+
src: BasicBlock
|
|
13
|
+
dst: BasicBlock
|
|
13
14
|
cond: float | int | None
|
|
14
15
|
|
|
15
|
-
def __init__(self, src:
|
|
16
|
+
def __init__(self, src: BasicBlock, dst: BasicBlock, cond: float | None = None):
|
|
16
17
|
self.src = src
|
|
17
18
|
self.dst = dst
|
|
18
19
|
self.cond = cond
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class BasicBlock:
|
|
22
|
-
phis: dict[SSAPlace | TempBlock, dict[
|
|
23
|
+
phis: dict[SSAPlace | TempBlock, dict[BasicBlock, SSAPlace]]
|
|
23
24
|
statements: list[IRStmt]
|
|
24
25
|
test: IRExpr
|
|
25
26
|
incoming: set[FlowEdge]
|
|
@@ -28,7 +29,7 @@ class BasicBlock:
|
|
|
28
29
|
def __init__(
|
|
29
30
|
self,
|
|
30
31
|
*,
|
|
31
|
-
phi: dict[SSAPlace, dict[
|
|
32
|
+
phi: dict[SSAPlace, dict[BasicBlock, SSAPlace]] | None = None,
|
|
32
33
|
statements: list[IRStmt] | None = None,
|
|
33
34
|
test: IRExpr | None = None,
|
|
34
35
|
incoming: set[FlowEdge] | None = None,
|
|
@@ -40,7 +41,7 @@ class BasicBlock:
|
|
|
40
41
|
self.incoming = incoming or set()
|
|
41
42
|
self.outgoing = outgoing or set()
|
|
42
43
|
|
|
43
|
-
def connect_to(self, other:
|
|
44
|
+
def connect_to(self, other: BasicBlock, cond: int | float | None = None):
|
|
44
45
|
edge = FlowEdge(self, other, cond)
|
|
45
46
|
self.outgoing.add(edge)
|
|
46
47
|
other.incoming.add(edge)
|
|
@@ -129,7 +130,7 @@ def cfg_to_mermaid(entry: BasicBlock):
|
|
|
129
130
|
case dict() as tgt:
|
|
130
131
|
lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
|
|
131
132
|
lines.append(f"{index} --> {index}_")
|
|
132
|
-
for cond, target in tgt.items():
|
|
133
|
+
for cond, target in sorted(tgt.items(), key=lambda x: (x[0] is None, x[0])):
|
|
133
134
|
lines.append(
|
|
134
135
|
f"{index}_ --> |{pre(fmt([cond if cond is not None else 'default']))}| {block_indexes[target]}"
|
|
135
136
|
)
|
|
@@ -137,3 +138,48 @@ def cfg_to_mermaid(entry: BasicBlock):
|
|
|
137
138
|
|
|
138
139
|
body = textwrap.indent("\n".join(lines), " ")
|
|
139
140
|
return f"graph\n{body}"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cfg_to_text(entry: BasicBlock) -> str:
|
|
144
|
+
def indent(iterable, prefix=" "):
|
|
145
|
+
for line in iterable:
|
|
146
|
+
yield f"{prefix}{line}"
|
|
147
|
+
|
|
148
|
+
block_indexes = {block: i for i, block in enumerate(traverse_cfg_reverse_postorder(entry))}
|
|
149
|
+
|
|
150
|
+
def format_phis(phis):
|
|
151
|
+
for dst, phi_srcs in phis.items():
|
|
152
|
+
srcs = ", ".join(
|
|
153
|
+
f"{block_indexes.get(src_block, '<dead>')}: {src_place}"
|
|
154
|
+
for src_block, src_place in sorted(phi_srcs.items(), key=lambda x: block_indexes.get(x[0]))
|
|
155
|
+
)
|
|
156
|
+
yield f"{dst} := phi({srcs})\n"
|
|
157
|
+
|
|
158
|
+
def format_statements(statements):
|
|
159
|
+
for stmt in statements:
|
|
160
|
+
yield f"{stmt}\n"
|
|
161
|
+
|
|
162
|
+
def format_outgoing(outgoing_edges, test, indexes):
|
|
163
|
+
outgoing = {edge.cond: edge.dst for edge in outgoing_edges}
|
|
164
|
+
match outgoing:
|
|
165
|
+
case {**other} if not other:
|
|
166
|
+
yield "goto exit\n"
|
|
167
|
+
case {None: target, **other} if not other:
|
|
168
|
+
yield f"goto {indexes[target]}\n"
|
|
169
|
+
case {0: f_branch, None: t_branch, **other} if not other:
|
|
170
|
+
yield f"goto {indexes[t_branch]} if {test} else {indexes[f_branch]}\n"
|
|
171
|
+
case dict() as tgt:
|
|
172
|
+
yield f"goto when {test}\n"
|
|
173
|
+
yield from indent(
|
|
174
|
+
f"{('default' if cond is None else str(cond))} -> {indexes[target]}\n"
|
|
175
|
+
for cond, target in sorted(tgt.items(), key=lambda x: (x[0] is None, x[0]))
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def format_blocks():
|
|
179
|
+
for block, index in block_indexes.items():
|
|
180
|
+
yield f"{index}:\n"
|
|
181
|
+
yield from indent(format_phis(block.phis))
|
|
182
|
+
yield from indent(format_statements(block.statements))
|
|
183
|
+
yield from indent(format_outgoing(block.outgoing, block.test, block_indexes))
|
|
184
|
+
|
|
185
|
+
return "".join(format_blocks())
|