sonolus.py 0.1.2__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 (72) 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 -880
  22. sonolus/build/cli.py +7 -1
  23. sonolus/build/compile.py +88 -90
  24. sonolus/build/engine.py +55 -5
  25. sonolus/build/level.py +24 -23
  26. sonolus/build/node.py +43 -43
  27. sonolus/script/archetype.py +23 -6
  28. sonolus/script/array.py +2 -2
  29. sonolus/script/bucket.py +191 -191
  30. sonolus/script/callbacks.py +127 -115
  31. sonolus/script/comptime.py +1 -1
  32. sonolus/script/containers.py +23 -0
  33. sonolus/script/debug.py +19 -3
  34. sonolus/script/easing.py +323 -0
  35. sonolus/script/effect.py +131 -131
  36. sonolus/script/engine.py +37 -1
  37. sonolus/script/globals.py +269 -269
  38. sonolus/script/graphics.py +200 -150
  39. sonolus/script/instruction.py +151 -0
  40. sonolus/script/internal/__init__.py +5 -5
  41. sonolus/script/internal/builtin_impls.py +144 -144
  42. sonolus/script/internal/context.py +12 -4
  43. sonolus/script/internal/descriptor.py +17 -17
  44. sonolus/script/internal/introspection.py +14 -14
  45. sonolus/script/internal/native.py +40 -38
  46. sonolus/script/internal/value.py +3 -3
  47. sonolus/script/interval.py +120 -112
  48. sonolus/script/iterator.py +214 -211
  49. sonolus/script/math.py +30 -1
  50. sonolus/script/num.py +1 -1
  51. sonolus/script/options.py +191 -191
  52. sonolus/script/particle.py +157 -157
  53. sonolus/script/pointer.py +30 -30
  54. sonolus/script/{preview.py → print.py} +81 -81
  55. sonolus/script/random.py +14 -0
  56. sonolus/script/range.py +58 -58
  57. sonolus/script/record.py +3 -3
  58. sonolus/script/runtime.py +45 -6
  59. sonolus/script/sprite.py +333 -333
  60. sonolus/script/text.py +407 -407
  61. sonolus/script/timing.py +42 -42
  62. sonolus/script/transform.py +77 -23
  63. sonolus/script/ui.py +160 -160
  64. sonolus/script/vec.py +81 -72
  65. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -2
  66. sonolus_py-0.1.4.dist-info/RECORD +84 -0
  67. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
  68. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
  69. sonolus/build/defaults.py +0 -32
  70. sonolus/script/icon.py +0 -73
  71. sonolus_py-0.1.2.dist-info/RECORD +0 -76
  72. {sonolus_py-0.1.2.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)