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.

Files changed (64) hide show
  1. sonolus/backend/excepthook.py +30 -0
  2. sonolus/backend/finalize.py +15 -1
  3. sonolus/backend/ops.py +4 -0
  4. sonolus/backend/optimize/allocate.py +5 -5
  5. sonolus/backend/optimize/constant_evaluation.py +124 -19
  6. sonolus/backend/optimize/copy_coalesce.py +15 -12
  7. sonolus/backend/optimize/dead_code.py +7 -6
  8. sonolus/backend/optimize/dominance.py +2 -2
  9. sonolus/backend/optimize/flow.py +54 -8
  10. sonolus/backend/optimize/inlining.py +137 -30
  11. sonolus/backend/optimize/liveness.py +2 -2
  12. sonolus/backend/optimize/optimize.py +15 -1
  13. sonolus/backend/optimize/passes.py +11 -3
  14. sonolus/backend/optimize/simplify.py +137 -8
  15. sonolus/backend/optimize/ssa.py +47 -13
  16. sonolus/backend/place.py +5 -4
  17. sonolus/backend/utils.py +44 -16
  18. sonolus/backend/visitor.py +288 -17
  19. sonolus/build/cli.py +47 -19
  20. sonolus/build/compile.py +12 -5
  21. sonolus/build/engine.py +70 -1
  22. sonolus/build/level.py +3 -3
  23. sonolus/build/project.py +2 -2
  24. sonolus/script/archetype.py +12 -9
  25. sonolus/script/array.py +23 -18
  26. sonolus/script/array_like.py +26 -29
  27. sonolus/script/bucket.py +1 -1
  28. sonolus/script/containers.py +22 -26
  29. sonolus/script/debug.py +20 -43
  30. sonolus/script/effect.py +1 -1
  31. sonolus/script/globals.py +3 -3
  32. sonolus/script/instruction.py +2 -2
  33. sonolus/script/internal/builtin_impls.py +155 -28
  34. sonolus/script/internal/constant.py +13 -3
  35. sonolus/script/internal/context.py +46 -15
  36. sonolus/script/internal/impl.py +9 -3
  37. sonolus/script/internal/introspection.py +8 -1
  38. sonolus/script/internal/native.py +2 -2
  39. sonolus/script/internal/range.py +8 -11
  40. sonolus/script/internal/simulation_context.py +1 -1
  41. sonolus/script/internal/transient.py +2 -2
  42. sonolus/script/internal/value.py +41 -3
  43. sonolus/script/interval.py +13 -13
  44. sonolus/script/iterator.py +53 -107
  45. sonolus/script/level.py +2 -2
  46. sonolus/script/maybe.py +241 -0
  47. sonolus/script/num.py +29 -14
  48. sonolus/script/options.py +1 -1
  49. sonolus/script/particle.py +1 -1
  50. sonolus/script/project.py +24 -5
  51. sonolus/script/quad.py +15 -15
  52. sonolus/script/record.py +48 -44
  53. sonolus/script/runtime.py +22 -18
  54. sonolus/script/sprite.py +1 -1
  55. sonolus/script/stream.py +66 -82
  56. sonolus/script/transform.py +35 -34
  57. sonolus/script/values.py +10 -10
  58. sonolus/script/vec.py +21 -18
  59. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/METADATA +1 -1
  60. sonolus_py-0.4.1.dist-info/RECORD +93 -0
  61. sonolus_py-0.3.4.dist-info/RECORD +0 -92
  62. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/WHEEL +0 -0
  63. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/entry_points.txt +0 -0
  64. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -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 = {edge.cond: edge.dst for edge in block.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
@@ -2,6 +2,10 @@ from enum import StrEnum
2
2
 
3
3
 
4
4
  class Op(StrEnum):
5
+ side_effects: bool
6
+ pure: bool
7
+ control_flow: bool
8
+
5
9
  def __new__(cls, name: str, side_effects: bool, pure: bool, control_flow: bool):
6
10
  obj = str.__new__(cls, name)
7
11
  obj._value_ = name
@@ -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] = {SSAPlace("err", 0): UNDEF}
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 = {edge.src: edge for edge in block.incoming}
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
- continue
81
- defs[p] = {incoming_by_src[b]: v for b, v in args.items()}
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 b in executable_edges else UNDEF for b, v in defs[p].items()]
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
- new_value = NAC
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
- else:
141
- taken_edge = next(
142
- (edge for edge in block.outgoing if edge.cond == new_test_value), None
143
- ) or next((edge for edge in block.outgoing if edge.cond is None), None)
144
- if taken_edge:
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
- taken_edge = next(
168
- (edge for edge in p.outgoing if edge.cond == new_test_value), None
169
- ) or next((edge for edge in p.outgoing if edge.cond is None), None)
170
- if taken_edge:
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
- if source in interference.get(target, set()):
47
- continue
48
- combined_mapping = mapping.get(target, {target}) | mapping.get(source, {source})
49
- combined_interference = interference.get(target, set()) | interference.get(source, set())
50
- for place in combined_mapping:
51
- mapping[place] = combined_mapping
52
- interference[place] = combined_interference
53
- for place in combined_interference:
54
- interference[place].update(combined_mapping)
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)
@@ -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: "BasicBlock"
12
- dst: "BasicBlock"
12
+ src: BasicBlock
13
+ dst: BasicBlock
13
14
  cond: float | int | None
14
15
 
15
- def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: float | None = None):
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[Self, SSAPlace]]
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[Self, SSAPlace]] | None = None,
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: "BasicBlock", cond: int | float | None = None):
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())