sonolus.py 0.1.3__py3-none-any.whl → 0.1.4__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 (68) hide show
  1. sonolus/backend/allocate.py +125 -51
  2. sonolus/backend/blocks.py +756 -756
  3. sonolus/backend/coalesce.py +85 -0
  4. sonolus/backend/constant_evaluation.py +374 -0
  5. sonolus/backend/dead_code.py +80 -0
  6. sonolus/backend/dominance.py +111 -0
  7. sonolus/backend/excepthook.py +37 -37
  8. sonolus/backend/finalize.py +69 -69
  9. sonolus/backend/flow.py +121 -92
  10. sonolus/backend/inlining.py +150 -0
  11. sonolus/backend/ir.py +5 -3
  12. sonolus/backend/liveness.py +173 -0
  13. sonolus/backend/mode.py +24 -24
  14. sonolus/backend/node.py +40 -40
  15. sonolus/backend/ops.py +197 -197
  16. sonolus/backend/optimize.py +37 -9
  17. sonolus/backend/passes.py +52 -6
  18. sonolus/backend/simplify.py +47 -30
  19. sonolus/backend/ssa.py +187 -0
  20. sonolus/backend/utils.py +48 -48
  21. sonolus/backend/visitor.py +892 -882
  22. sonolus/build/cli.py +7 -1
  23. sonolus/build/compile.py +88 -90
  24. sonolus/build/level.py +24 -23
  25. sonolus/build/node.py +43 -43
  26. sonolus/script/archetype.py +23 -6
  27. sonolus/script/array.py +2 -2
  28. sonolus/script/bucket.py +191 -191
  29. sonolus/script/callbacks.py +127 -127
  30. sonolus/script/comptime.py +1 -1
  31. sonolus/script/containers.py +23 -0
  32. sonolus/script/debug.py +19 -3
  33. sonolus/script/easing.py +323 -0
  34. sonolus/script/effect.py +131 -131
  35. sonolus/script/globals.py +269 -269
  36. sonolus/script/graphics.py +200 -150
  37. sonolus/script/instruction.py +151 -151
  38. sonolus/script/internal/__init__.py +5 -5
  39. sonolus/script/internal/builtin_impls.py +144 -144
  40. sonolus/script/internal/context.py +12 -4
  41. sonolus/script/internal/descriptor.py +17 -17
  42. sonolus/script/internal/introspection.py +14 -14
  43. sonolus/script/internal/native.py +40 -38
  44. sonolus/script/internal/value.py +3 -3
  45. sonolus/script/interval.py +120 -112
  46. sonolus/script/iterator.py +214 -214
  47. sonolus/script/math.py +30 -1
  48. sonolus/script/num.py +1 -1
  49. sonolus/script/options.py +191 -191
  50. sonolus/script/particle.py +157 -157
  51. sonolus/script/pointer.py +30 -30
  52. sonolus/script/print.py +81 -81
  53. sonolus/script/random.py +14 -0
  54. sonolus/script/range.py +58 -58
  55. sonolus/script/record.py +3 -3
  56. sonolus/script/runtime.py +2 -0
  57. sonolus/script/sprite.py +333 -333
  58. sonolus/script/text.py +407 -407
  59. sonolus/script/timing.py +42 -42
  60. sonolus/script/transform.py +77 -23
  61. sonolus/script/ui.py +160 -160
  62. sonolus/script/vec.py +81 -78
  63. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -1
  64. sonolus_py-0.1.4.dist-info/RECORD +84 -0
  65. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
  66. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
  67. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  68. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,173 @@
1
+ from collections import deque
2
+
3
+ from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
4
+ from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
5
+ from sonolus.backend.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
+ self.process_arrays(entry)
19
+ return entry
20
+
21
+ def process(self, entry: BasicBlock):
22
+ queue = deque(self.get_exits(entry))
23
+ if not queue:
24
+ raise ValueError("Infinite loop detected")
25
+ while queue:
26
+ block = queue.popleft()
27
+ updated_blocks = self.process_block(block)
28
+ queue.extend(updated_blocks)
29
+
30
+ def process_arrays(self, entry: BasicBlock):
31
+ # With arrays, we can't assume that an assignment will render previous assignments dead.
32
+ # Before this function is run, arrays are treated as live at a statement as long as they are read from
33
+ # at some future point.
34
+ # This function will mark arrays as dead if they could not have been assigned to yet.
35
+ queue = deque([entry])
36
+ while queue:
37
+ block = queue.popleft()
38
+ if block.live_arrays_in is None:
39
+ block.live_arrays_in = set()
40
+ live_arrays_in = block.live_arrays_in.copy()
41
+ for statement in block.statements:
42
+ live_arrays_in.update(self.get_array_defs(statement))
43
+ updated_blocks = []
44
+ for edge in block.outgoing:
45
+ if edge.dst.live_arrays_in is None:
46
+ prev_size = -1
47
+ edge.dst.live_arrays_in = set()
48
+ else:
49
+ prev_size = len(edge.dst.live_arrays_in)
50
+ edge.dst.live_arrays_in.update(live_arrays_in)
51
+ if len(edge.dst.live_arrays_in) != prev_size:
52
+ updated_blocks.append(edge.dst)
53
+ queue.extend(updated_blocks)
54
+
55
+ for block in traverse_cfg_preorder(entry):
56
+ live_arrays_in = block.live_arrays_in
57
+ for statement in block.statements:
58
+ if not self.can_skip(statement, statement.live):
59
+ live_arrays_in.update(self.get_array_defs(statement))
60
+ statement.live = {
61
+ place
62
+ for place in statement.live
63
+ if not (isinstance(place, TempBlock) and place.size != 1 and place not in live_arrays_in)
64
+ }
65
+
66
+ def preprocess(self, entry: BasicBlock):
67
+ for block in traverse_cfg_preorder(entry):
68
+ block.live_out = None
69
+ block.live_in = None
70
+ block.live_phi_targets = None
71
+ block.live_arrays_in = None
72
+
73
+ def process_block(self, block: BasicBlock) -> list[BasicBlock]:
74
+ if block.live_out is None:
75
+ block.live_out = set()
76
+ live: set[HasLiveness] = block.live_out.copy()
77
+ block.test.live = live.copy()
78
+ live.update(self.get_uses(block.test))
79
+ for statement in reversed(block.statements):
80
+ statement.live = live.copy()
81
+ if self.can_skip(statement, live):
82
+ continue
83
+ live.difference_update(self.get_defs(statement))
84
+ live.update(self.get_uses(statement))
85
+ live_phi_targets = set()
86
+ for target, args in block.phis.items():
87
+ if target not in live:
88
+ continue
89
+ live.difference_update({target})
90
+ live.update(args.values())
91
+ live_phi_targets.add(target)
92
+ block.live_in = live.copy()
93
+ block.live_phi_targets = live_phi_targets
94
+ updated_blocks = []
95
+ for edge in block.incoming:
96
+ if edge.src.live_out is None:
97
+ prev_size = -1
98
+ edge.src.live_out = set()
99
+ else:
100
+ prev_size = len(edge.src.live_out)
101
+ edge.src.live_out.update(live)
102
+ if len(edge.src.live_out) != prev_size:
103
+ updated_blocks.append(edge.src)
104
+ return updated_blocks
105
+
106
+ def get_uses(self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int) -> set[HasLiveness]:
107
+ uses = set()
108
+ match stmt:
109
+ case IRPureInstr(op=_, args=args) | IRInstr(op=_, args=args):
110
+ for arg in args:
111
+ uses.update(self.get_uses(arg))
112
+ case IRGet(place=place):
113
+ uses.update(self.get_uses(place))
114
+ case IRSet(place=place, value=value):
115
+ if isinstance(place, BlockPlace):
116
+ if not isinstance(place.block, TempBlock):
117
+ uses.update(self.get_uses(place.block))
118
+ uses.update(self.get_uses(place.index))
119
+ uses.update(self.get_uses(value))
120
+ case IRConst() | int():
121
+ pass
122
+ case BlockPlace(block=block, index=index, offset=_):
123
+ uses.update(self.get_uses(block))
124
+ uses.update(self.get_uses(index))
125
+ case SSAPlace() | TempBlock():
126
+ uses.add(stmt)
127
+ case _:
128
+ raise TypeError(f"Unexpected statement type: {type(stmt)}")
129
+ return uses
130
+
131
+ def get_defs(self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int) -> set[HasLiveness]:
132
+ match stmt:
133
+ case IRSet(place=place, value=_):
134
+ match place:
135
+ case SSAPlace():
136
+ return {place}
137
+ case BlockPlace(block=TempBlock() as temp_block, index=_, offset=_) if temp_block.size == 1:
138
+ return {temp_block}
139
+ return set()
140
+
141
+ def get_array_defs(self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int) -> set[HasLiveness]:
142
+ match stmt:
143
+ case IRSet(place=place, value=_):
144
+ match place:
145
+ case BlockPlace(block=TempBlock() as temp_block, index=_, offset=_) if temp_block.size > 1:
146
+ return {temp_block}
147
+ return set()
148
+
149
+ def can_skip(self, stmt: IRStmt, live: set[HasLiveness]) -> bool:
150
+ match stmt:
151
+ case IRSet(place=_, value=value):
152
+ if isinstance(value, IRInstr) and value.op.side_effects:
153
+ return False
154
+ defs = self.get_defs(stmt) | self.get_array_defs(stmt)
155
+ return defs and not (defs & live)
156
+ return False
157
+
158
+ def get_exits(self, entry: BasicBlock) -> list[BasicBlock]:
159
+ return [block for block in traverse_cfg_preorder(entry) if not block.outgoing]
160
+
161
+ def __eq__(self, other):
162
+ return isinstance(other, LivenessAnalysis)
163
+
164
+ def __hash__(self):
165
+ return hash(LivenessAnalysis)
166
+
167
+
168
+ def get_live(stmt: IRStmt) -> set[HasLiveness]:
169
+ return stmt.live
170
+
171
+
172
+ def get_live_phi_targets(block: BasicBlock) -> set[HasLiveness]:
173
+ return block.live_phi_targets
sonolus/backend/mode.py CHANGED
@@ -1,24 +1,24 @@
1
- from enum import Enum
2
- from functools import cached_property
3
-
4
- from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock, WatchBlock
5
-
6
-
7
- class Mode(Enum):
8
- blocks: type[Block]
9
-
10
- PLAY = (PlayBlock,)
11
- WATCH = (WatchBlock,)
12
- PREVIEW = (PreviewBlock,)
13
- TUTORIAL = (TutorialBlock,)
14
-
15
- def __init__(self, blocks: type[Block]):
16
- self.blocks = blocks
17
-
18
- @cached_property
19
- def callbacks(self) -> frozenset[str]:
20
- cbs = set()
21
- for block in self.blocks:
22
- cbs.update(block.readable)
23
- cbs.update(block.writable)
24
- return frozenset(cbs)
1
+ from enum import Enum
2
+ from functools import cached_property
3
+
4
+ from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock, WatchBlock
5
+
6
+
7
+ class Mode(Enum):
8
+ blocks: type[Block]
9
+
10
+ PLAY = (PlayBlock,)
11
+ WATCH = (WatchBlock,)
12
+ PREVIEW = (PreviewBlock,)
13
+ TUTORIAL = (TutorialBlock,)
14
+
15
+ def __init__(self, blocks: type[Block]):
16
+ self.blocks = blocks
17
+
18
+ @cached_property
19
+ def callbacks(self) -> frozenset[str]:
20
+ cbs = set()
21
+ for block in self.blocks:
22
+ cbs.update(block.readable)
23
+ cbs.update(block.writable)
24
+ return frozenset(cbs)
sonolus/backend/node.py CHANGED
@@ -1,40 +1,40 @@
1
- import textwrap
2
- from dataclasses import dataclass
3
-
4
- from sonolus.backend.ops import Op
5
-
6
- type EngineNode = ConstantNode | FunctionNode
7
-
8
-
9
- @dataclass
10
- class ConstantNode:
11
- value: float
12
-
13
- def __hash__(self):
14
- return hash(self.value)
15
-
16
-
17
- @dataclass
18
- class FunctionNode:
19
- func: Op
20
- args: list[EngineNode]
21
-
22
- def __hash__(self):
23
- return hash((self.func, tuple(self.args)))
24
-
25
-
26
- def format_engine_node(node: EngineNode) -> str:
27
- if isinstance(node, ConstantNode):
28
- return str(node.value)
29
- elif isinstance(node, FunctionNode):
30
- match len(node.args):
31
- case 0:
32
- return f"{node.func.name}()"
33
- case 1:
34
- return f"{node.func.name}({format_engine_node(node.args[0])})"
35
- case _:
36
- return f"{node.func.name}(\n{
37
- textwrap.indent("\n".join(format_engine_node(arg) for arg in node.args), " ")
38
- }\n)"
39
- else:
40
- raise ValueError(f"Invalid engine node: {node}")
1
+ import textwrap
2
+ from dataclasses import dataclass
3
+
4
+ from sonolus.backend.ops import Op
5
+
6
+ type EngineNode = ConstantNode | FunctionNode
7
+
8
+
9
+ @dataclass
10
+ class ConstantNode:
11
+ value: float
12
+
13
+ def __hash__(self):
14
+ return hash(self.value)
15
+
16
+
17
+ @dataclass
18
+ class FunctionNode:
19
+ func: Op
20
+ args: list[EngineNode]
21
+
22
+ def __hash__(self):
23
+ return hash((self.func, tuple(self.args)))
24
+
25
+
26
+ def format_engine_node(node: EngineNode) -> str:
27
+ if isinstance(node, ConstantNode):
28
+ return str(node.value)
29
+ elif isinstance(node, FunctionNode):
30
+ match len(node.args):
31
+ case 0:
32
+ return f"{node.func.name}()"
33
+ case 1:
34
+ return f"{node.func.name}({format_engine_node(node.args[0])})"
35
+ case _:
36
+ return f"{node.func.name}(\n{
37
+ textwrap.indent("\n".join(format_engine_node(arg) for arg in node.args), " ")
38
+ }\n)"
39
+ else:
40
+ raise ValueError(f"Invalid engine node: {node}")