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
sonolus/backend/ssa.py ADDED
@@ -0,0 +1,187 @@
1
+ from sonolus.backend.dominance import DominanceFrontiers, get_df, get_dom_children
2
+ from sonolus.backend.flow import BasicBlock, FlowEdge, traverse_cfg_preorder
3
+ from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
4
+ from sonolus.backend.passes import CompilerPass
5
+ from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
6
+
7
+
8
+ class ToSSA(CompilerPass):
9
+ def requires(self) -> set[CompilerPass]:
10
+ return {DominanceFrontiers()}
11
+
12
+ def run(self, entry: BasicBlock) -> BasicBlock:
13
+ defs = self.defs_to_blocks(entry)
14
+ self.insert_phis(defs)
15
+ self.rename(entry, defs, {var: [] for var in defs}, {})
16
+ self.remove_placeholder_phis(entry)
17
+ return entry
18
+
19
+ def rename(
20
+ self,
21
+ block: BasicBlock,
22
+ defs: dict[TempBlock, set[BasicBlock]],
23
+ ssa_places: dict[TempBlock, list[SSAPlace]],
24
+ used: dict[str, int],
25
+ ):
26
+ original_ssa_place_lens = {var: len(ssa_places[var]) for var in defs}
27
+ for var, args in [*block.phis.items()]:
28
+ if isinstance(var, SSAPlace):
29
+ continue
30
+ ssa_places[var].append(self.get_new_ssa_place(var.name, used))
31
+ block.phis[ssa_places[var][-1]] = args
32
+ block.statements = [self.rename_stmt(stmt, ssa_places, used) for stmt in block.statements]
33
+ for edge in block.outgoing:
34
+ dst = edge.dst
35
+ for var, args in dst.phis.items():
36
+ if isinstance(var, SSAPlace):
37
+ continue
38
+ if ssa_places[var]:
39
+ args[block] = ssa_places[var][-1]
40
+ block.test = self.rename_stmt(block.test, ssa_places, used)
41
+ for dom_child in get_dom_children(block):
42
+ self.rename(dom_child, defs, ssa_places, used)
43
+ for var, length in original_ssa_place_lens.items():
44
+ ssa_places[var] = ssa_places[var][:length]
45
+
46
+ def remove_placeholder_phis(self, entry: BasicBlock):
47
+ for block in traverse_cfg_preorder(entry):
48
+ block.phis = {var: args for var, args in block.phis.items() if isinstance(var, SSAPlace)}
49
+
50
+ def rename_stmt(self, stmt: IRStmt, ssa_places: dict[TempBlock, list[SSAPlace]], used: dict[str, int]):
51
+ match stmt:
52
+ case IRConst():
53
+ return stmt
54
+ case IRPureInstr(op=op, args=args):
55
+ return IRPureInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used) for arg in args])
56
+ case IRInstr(op=op, args=args):
57
+ return IRInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used) for arg in args])
58
+ case IRGet(place=place):
59
+ return IRGet(place=self.rename_stmt(place, ssa_places, used))
60
+ case IRSet(place=place, value=value):
61
+ value = self.rename_stmt(value, ssa_places, used)
62
+ if isinstance(place, BlockPlace) and isinstance(place.block, TempBlock) and place.block.size == 1:
63
+ ssa_places[place.block].append(self.get_new_ssa_place(place.block.name, used))
64
+ place = self.rename_stmt(place, ssa_places, used)
65
+ return IRSet(place=place, value=value)
66
+ case SSAPlace():
67
+ return stmt
68
+ case TempBlock() if stmt.size == 1:
69
+ return ssa_places[stmt][-1]
70
+ case TempBlock():
71
+ return stmt
72
+ case int():
73
+ return stmt
74
+ case BlockPlace(block=block, index=index, offset=offset):
75
+ if isinstance(block, TempBlock) and block.size == 1:
76
+ return self.rename_stmt(block, ssa_places, used)
77
+ return BlockPlace(
78
+ block=self.rename_stmt(block, ssa_places, used),
79
+ index=self.rename_stmt(index, ssa_places, used),
80
+ offset=self.rename_stmt(offset, ssa_places, used),
81
+ )
82
+ case _:
83
+ raise TypeError(f"Unexpected statement: {stmt}")
84
+
85
+ def insert_phis(self, defs: dict[TempBlock, set[BasicBlock]]):
86
+ for var, blocks in defs.items():
87
+ df = self.get_iterated_df(blocks)
88
+ for block in df:
89
+ block.phis[var] = {}
90
+
91
+ def defs_to_blocks(self, entry: BasicBlock) -> dict[TempBlock, set[BasicBlock]]:
92
+ result = {}
93
+ for block in traverse_cfg_preorder(entry):
94
+ for stmt in block.statements:
95
+ def_block = self.get_stmt_def(stmt)
96
+ if def_block is not None:
97
+ result.setdefault(def_block, set()).add(block)
98
+ return result
99
+
100
+ def get_stmt_def(self, stmt: IRStmt) -> TempBlock:
101
+ if (
102
+ isinstance(stmt, IRSet)
103
+ and isinstance(stmt.place, BlockPlace)
104
+ and isinstance(stmt.place.block, TempBlock)
105
+ and stmt.place.block.size == 1
106
+ ):
107
+ return stmt.place.block
108
+ return None
109
+
110
+ def get_iterated_df(self, blocks: set[BasicBlock]) -> set[BasicBlock]:
111
+ df = set()
112
+ worklist = set(blocks)
113
+ while worklist:
114
+ block = worklist.pop()
115
+ new_df = get_df(block) - df
116
+ if new_df:
117
+ df.update(new_df)
118
+ worklist.update(new_df)
119
+ return df
120
+
121
+ def get_new_ssa_place(self, name: str, used: dict[str, int]) -> SSAPlace:
122
+ if name not in used:
123
+ used[name] = 0
124
+ used[name] += 1
125
+ return SSAPlace(name, used[name])
126
+
127
+
128
+ class FromSSA(CompilerPass):
129
+ def run(self, entry: BasicBlock) -> BasicBlock:
130
+ for block in [*traverse_cfg_preorder(entry)]:
131
+ self.process_block(block)
132
+ return entry
133
+
134
+ def process_block(self, block: BasicBlock):
135
+ incoming = [*block.incoming]
136
+ block.incoming.clear()
137
+ for edge in incoming:
138
+ between_block = BasicBlock()
139
+ edge.dst = between_block
140
+ between_block.incoming.add(edge)
141
+ next_edge = FlowEdge(between_block, block, None)
142
+ block.incoming.add(next_edge)
143
+ between_block.outgoing.add(next_edge)
144
+ for args in block.phis.values():
145
+ if edge.src in args:
146
+ args[between_block] = args.pop(edge.src)
147
+ for var, args in block.phis.items():
148
+ for src, arg in args.items():
149
+ src.statements.append(
150
+ IRSet(place=self.place_from_ssa_place(var), value=IRGet(place=self.place_from_ssa_place(arg)))
151
+ )
152
+ block.phis = {}
153
+ block.statements = [self.process_stmt(stmt) for stmt in block.statements]
154
+ block.test = self.process_stmt(block.test)
155
+
156
+ def process_stmt(self, stmt: IRStmt):
157
+ match stmt:
158
+ case IRConst():
159
+ return stmt
160
+ case IRPureInstr(op=op, args=args):
161
+ return IRPureInstr(op=op, args=[self.process_stmt(arg) for arg in args])
162
+ case IRInstr(op=op, args=args):
163
+ return IRInstr(op=op, args=[self.process_stmt(arg) for arg in args])
164
+ case IRGet(place=place):
165
+ return IRGet(place=self.process_stmt(place))
166
+ case IRSet(place=place, value=value):
167
+ return IRSet(place=self.process_stmt(place), value=self.process_stmt(value))
168
+ case SSAPlace():
169
+ return self.place_from_ssa_place(stmt)
170
+ case TempBlock():
171
+ return stmt
172
+ case int():
173
+ return stmt
174
+ case BlockPlace(block=block, index=index, offset=offset):
175
+ return BlockPlace(
176
+ block=self.process_stmt(block),
177
+ index=self.process_stmt(index),
178
+ offset=self.process_stmt(offset),
179
+ )
180
+ case _:
181
+ raise TypeError(f"Unexpected statement: {stmt}")
182
+
183
+ def temp_block_from_ssa_place(self, ssa_place: SSAPlace) -> TempBlock:
184
+ return TempBlock(f"{ssa_place.name}.{ssa_place.num}")
185
+
186
+ def place_from_ssa_place(self, ssa_place: SSAPlace) -> BlockPlace:
187
+ return BlockPlace(block=self.temp_block_from_ssa_place(ssa_place), index=0, offset=0)
sonolus/backend/utils.py CHANGED
@@ -1,48 +1,48 @@
1
- # ruff: noqa: N802
2
- import ast
3
- import inspect
4
- from collections.abc import Callable
5
- from pathlib import Path
6
-
7
-
8
- def get_function(fn: Callable) -> tuple[str, ast.FunctionDef]:
9
- # This preserves both line number and column number in the returned node
10
- source_file = inspect.getsourcefile(fn)
11
- _, start_line = inspect.getsourcelines(fn)
12
- base_tree = ast.parse(Path(source_file).read_text(encoding="utf-8"))
13
- return source_file, find_function(base_tree, start_line)
14
-
15
-
16
- class FindFunction(ast.NodeVisitor):
17
- def __init__(self, line):
18
- self.line = line
19
- self.node: ast.FunctionDef | None = None
20
-
21
- def visit_FunctionDef(self, node: ast.FunctionDef):
22
- if node.lineno == self.line or (
23
- node.decorator_list and (node.decorator_list[-1].end_lineno <= self.line <= node.lineno)
24
- ):
25
- self.node = node
26
- else:
27
- self.generic_visit(node)
28
-
29
-
30
- def find_function(tree: ast.Module, line: int):
31
- visitor = FindFunction(line)
32
- visitor.visit(tree)
33
- return visitor.node
34
-
35
-
36
- class ScanWrites(ast.NodeVisitor):
37
- def __init__(self):
38
- self.writes = []
39
-
40
- def visit_Name(self, node):
41
- if isinstance(node.ctx, ast.Store | ast.Delete):
42
- self.writes.append(node.id)
43
-
44
-
45
- def scan_writes(node: ast.AST) -> set[str]:
46
- visitor = ScanWrites()
47
- visitor.visit(node)
48
- return set(visitor.writes)
1
+ # ruff: noqa: N802
2
+ import ast
3
+ import inspect
4
+ from collections.abc import Callable
5
+ from pathlib import Path
6
+
7
+
8
+ def get_function(fn: Callable) -> tuple[str, ast.FunctionDef]:
9
+ # This preserves both line number and column number in the returned node
10
+ source_file = inspect.getsourcefile(fn)
11
+ _, start_line = inspect.getsourcelines(fn)
12
+ base_tree = ast.parse(Path(source_file).read_text(encoding="utf-8"))
13
+ return source_file, find_function(base_tree, start_line)
14
+
15
+
16
+ class FindFunction(ast.NodeVisitor):
17
+ def __init__(self, line):
18
+ self.line = line
19
+ self.node: ast.FunctionDef | None = None
20
+
21
+ def visit_FunctionDef(self, node: ast.FunctionDef):
22
+ if node.lineno == self.line or (
23
+ node.decorator_list and (node.decorator_list[-1].end_lineno <= self.line <= node.lineno)
24
+ ):
25
+ self.node = node
26
+ else:
27
+ self.generic_visit(node)
28
+
29
+
30
+ def find_function(tree: ast.Module, line: int):
31
+ visitor = FindFunction(line)
32
+ visitor.visit(tree)
33
+ return visitor.node
34
+
35
+
36
+ class ScanWrites(ast.NodeVisitor):
37
+ def __init__(self):
38
+ self.writes = []
39
+
40
+ def visit_Name(self, node):
41
+ if isinstance(node.ctx, ast.Store | ast.Delete):
42
+ self.writes.append(node.id)
43
+
44
+
45
+ def scan_writes(node: ast.AST) -> set[str]:
46
+ visitor = ScanWrites()
47
+ visitor.visit(node)
48
+ return set(visitor.writes)