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.
- sonolus/backend/allocate.py +125 -51
- sonolus/backend/blocks.py +756 -756
- sonolus/backend/coalesce.py +85 -0
- sonolus/backend/constant_evaluation.py +374 -0
- sonolus/backend/dead_code.py +80 -0
- sonolus/backend/dominance.py +111 -0
- sonolus/backend/excepthook.py +37 -37
- sonolus/backend/finalize.py +69 -69
- sonolus/backend/flow.py +121 -92
- sonolus/backend/inlining.py +150 -0
- sonolus/backend/ir.py +5 -3
- sonolus/backend/liveness.py +173 -0
- sonolus/backend/mode.py +24 -24
- sonolus/backend/node.py +40 -40
- sonolus/backend/ops.py +197 -197
- sonolus/backend/optimize.py +37 -9
- sonolus/backend/passes.py +52 -6
- sonolus/backend/simplify.py +47 -30
- sonolus/backend/ssa.py +187 -0
- sonolus/backend/utils.py +48 -48
- sonolus/backend/visitor.py +892 -882
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/level.py +24 -23
- sonolus/build/node.py +43 -43
- sonolus/script/archetype.py +23 -6
- sonolus/script/array.py +2 -2
- sonolus/script/bucket.py +191 -191
- sonolus/script/callbacks.py +127 -127
- sonolus/script/comptime.py +1 -1
- sonolus/script/containers.py +23 -0
- sonolus/script/debug.py +19 -3
- sonolus/script/easing.py +323 -0
- sonolus/script/effect.py +131 -131
- sonolus/script/globals.py +269 -269
- sonolus/script/graphics.py +200 -150
- sonolus/script/instruction.py +151 -151
- sonolus/script/internal/__init__.py +5 -5
- sonolus/script/internal/builtin_impls.py +144 -144
- sonolus/script/internal/context.py +12 -4
- sonolus/script/internal/descriptor.py +17 -17
- sonolus/script/internal/introspection.py +14 -14
- sonolus/script/internal/native.py +40 -38
- sonolus/script/internal/value.py +3 -3
- sonolus/script/interval.py +120 -112
- sonolus/script/iterator.py +214 -214
- sonolus/script/math.py +30 -1
- sonolus/script/num.py +1 -1
- sonolus/script/options.py +191 -191
- sonolus/script/particle.py +157 -157
- sonolus/script/pointer.py +30 -30
- sonolus/script/print.py +81 -81
- sonolus/script/random.py +14 -0
- sonolus/script/range.py +58 -58
- sonolus/script/record.py +3 -3
- sonolus/script/runtime.py +2 -0
- sonolus/script/sprite.py +333 -333
- sonolus/script/text.py +407 -407
- sonolus/script/timing.py +42 -42
- sonolus/script/transform.py +77 -23
- sonolus/script/ui.py +160 -160
- sonolus/script/vec.py +81 -78
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -1
- sonolus_py-0.1.4.dist-info/RECORD +84 -0
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
- sonolus_py-0.1.3.dist-info/RECORD +0 -75
- {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}")
|