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.

Files changed (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {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
- from sonolus.backend.ir import IRConst, IRExpr, IRStmt
6
-
7
-
8
- class FlowEdge:
9
- src: "BasicBlock"
10
- dst: "BasicBlock"
11
- cond: IRExpr | None
12
-
13
- def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: IRExpr | None = None):
14
- self.src = src
15
- self.dst = dst
16
- self.cond = cond
17
-
18
-
19
- class BasicBlock:
20
- statements: list[IRStmt]
21
- test: IRExpr
22
- incoming: set[FlowEdge]
23
- outgoing: set[FlowEdge]
24
-
25
- def __init__(
26
- self,
27
- statements: list[IRStmt] | None = None,
28
- test: IRExpr | None = None,
29
- incoming: set[FlowEdge] | None = None,
30
- outgoing: set[FlowEdge] | None = None,
31
- ):
32
- self.statements = statements or []
33
- self.test = test or IRConst(1)
34
- self.incoming = incoming or set()
35
- self.outgoing = outgoing or set()
36
-
37
- def connect_to(self, other: "BasicBlock", cond: IRExpr | None = None):
38
- edge = FlowEdge(self, other, cond)
39
- self.outgoing.add(edge)
40
- other.incoming.add(edge)
41
-
42
-
43
- def traverse_cfg_preorder(block: BasicBlock) -> Iterator[BasicBlock]:
44
- visited = set()
45
- queue = deque([block])
46
- while queue:
47
- block = queue.popleft()
48
- if block in visited:
49
- continue
50
- visited.add(block)
51
- yield block
52
- for edge in sorted(block.outgoing, key=lambda e: (e.cond is None, e.cond)):
53
- queue.append(edge.dst)
54
-
55
-
56
- def cfg_to_mermaid(entry: BasicBlock):
57
- def pre(s: str):
58
- return "\"<pre style='text-align: left;'>" + s.replace("\n", "<br/>") + '</pre>"'
59
-
60
- def fmt(nodes):
61
- if nodes:
62
- return "\n".join(str(n) for n in nodes)
63
- else:
64
- return "{}"
65
-
66
- block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
67
-
68
- lines = ["Entry([Entry]) --> 0"]
69
- for block in traverse_cfg_preorder(entry):
70
- index = block_indexes[block]
71
- lines.append(f"{index}[{pre(fmt([f'#{index}', *block.statements]))}]")
72
-
73
- outgoing = {edge.cond: edge.dst for edge in block.outgoing}
74
- match outgoing:
75
- case {**other} if not other:
76
- lines.append(f"{index} --> Exit")
77
- case {None: target, **other} if not other:
78
- lines.append(f"{index} --> {block_indexes[target]}")
79
- case {0: f_branch, None: t_branch, **other} if not other:
80
- lines.append(f"{index}_{{{pre(fmt([block.test]))}}}")
81
- lines.append(f"{index} --> {index}_")
82
- lines.append(f"{index}_ --> |true| {block_indexes[t_branch]}")
83
- lines.append(f"{index}_ --> |false| {block_indexes[f_branch]}")
84
- case dict() as tgt:
85
- lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
86
- lines.append(f"{index} --> {index}_")
87
- for cond, target in tgt.items():
88
- lines.append(f"{index}_ --> |{pre(fmt([cond or "default"]))}| {block_indexes[target]}")
89
- lines.append("Exit([Exit])")
90
-
91
- body = textwrap.indent("\n".join(lines), " ")
92
- return f"graph\n{body}"
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)