sonolus.py 0.1.4__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.
- sonolus/backend/finalize.py +18 -10
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +24 -0
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
- sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
- sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
- sonolus/backend/{flow.py → optimize/flow.py} +6 -5
- sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
- sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/{passes.py → optimize/passes.py} +1 -1
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +10 -0
- sonolus/backend/visitor.py +360 -101
- sonolus/build/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/script/archetype.py +419 -137
- sonolus/script/array.py +25 -8
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +73 -11
- sonolus/script/containers.py +234 -51
- sonolus/script/debug.py +8 -8
- sonolus/script/easing.py +147 -105
- sonolus/script/effect.py +60 -0
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +66 -32
- sonolus/script/instruction.py +79 -25
- sonolus/script/internal/builtin_impls.py +138 -27
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +14 -5
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +5 -2
- sonolus/script/{math.py → internal/math_impls.py} +28 -28
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/interval.py +234 -16
- sonolus/script/iterator.py +120 -167
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +79 -47
- sonolus/script/options.py +78 -12
- sonolus/script/particle.py +37 -4
- sonolus/script/pointer.py +4 -4
- sonolus/script/print.py +22 -1
- sonolus/script/project.py +8 -0
- sonolus/script/{graphics.py → quad.py} +75 -12
- sonolus/script/record.py +44 -13
- sonolus/script/runtime.py +50 -1
- sonolus/script/sprite.py +197 -112
- sonolus/script/text.py +2 -0
- sonolus/script/timing.py +72 -0
- sonolus/script/transform.py +296 -66
- sonolus/script/ui.py +134 -78
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +118 -3
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- sonolus/backend/dead_code.py +0 -80
- sonolus/backend/optimize.py +0 -37
- sonolus/backend/simplify.py +0 -47
- sonolus/script/comptime.py +0 -160
- sonolus/script/random.py +0 -14
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.4.dist-info/RECORD +0 -84
- /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from collections import deque
|
|
2
2
|
|
|
3
|
-
from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
|
|
4
3
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
5
|
-
from sonolus.backend.
|
|
4
|
+
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
5
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
6
6
|
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
7
7
|
|
|
8
8
|
type HasLiveness = SSAPlace | TempBlock
|
|
@@ -15,9 +15,26 @@ class LivenessAnalysis(CompilerPass):
|
|
|
15
15
|
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
16
16
|
self.preprocess(entry)
|
|
17
17
|
self.process(entry)
|
|
18
|
-
self.process_arrays(entry)
|
|
19
18
|
return entry
|
|
20
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
|
+
|
|
21
38
|
def process(self, entry: BasicBlock):
|
|
22
39
|
queue = deque(self.get_exits(entry))
|
|
23
40
|
if not queue:
|
|
@@ -27,101 +44,88 @@ class LivenessAnalysis(CompilerPass):
|
|
|
27
44
|
updated_blocks = self.process_block(block)
|
|
28
45
|
queue.extend(updated_blocks)
|
|
29
46
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
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])
|
|
47
|
+
def preprocess_arrays(self, entry: BasicBlock):
|
|
48
|
+
queue = {entry}
|
|
49
|
+
visited = set()
|
|
36
50
|
while queue:
|
|
37
|
-
block = queue.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
block = queue.pop()
|
|
52
|
+
array_defs = block.array_defs_in.copy()
|
|
53
|
+
is_first_visit = block not in visited
|
|
54
|
+
visited.add(block)
|
|
41
55
|
for statement in block.statements:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if edge.dst.live_arrays_in is None:
|
|
46
|
-
prev_size = -1
|
|
47
|
-
edge.dst.live_arrays_in = set()
|
|
56
|
+
if statement.array_defs - array_defs:
|
|
57
|
+
statement.is_array_init = True
|
|
58
|
+
array_defs.update(statement.array_defs)
|
|
48
59
|
else:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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)
|
|
72
66
|
|
|
73
67
|
def process_block(self, block: BasicBlock) -> list[BasicBlock]:
|
|
74
68
|
if block.live_out is None:
|
|
75
69
|
block.live_out = set()
|
|
76
|
-
live: set[HasLiveness] =
|
|
77
|
-
|
|
78
|
-
|
|
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)
|
|
79
77
|
for statement in reversed(block.statements):
|
|
80
|
-
statement.live
|
|
78
|
+
statement.live.update(live)
|
|
81
79
|
if self.can_skip(statement, live):
|
|
82
80
|
continue
|
|
83
|
-
live.difference_update(
|
|
84
|
-
|
|
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
|
+
}
|
|
85
88
|
live_phi_targets = set()
|
|
86
89
|
for target, args in block.phis.items():
|
|
87
90
|
if target not in live:
|
|
88
91
|
continue
|
|
89
|
-
live.
|
|
90
|
-
|
|
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)
|
|
91
97
|
live_phi_targets.add(target)
|
|
92
|
-
block.live_in = live
|
|
98
|
+
block.live_in = live
|
|
93
99
|
block.live_phi_targets = live_phi_targets
|
|
94
100
|
updated_blocks = []
|
|
95
101
|
for edge in block.incoming:
|
|
96
102
|
if edge.src.live_out is None:
|
|
97
|
-
prev_size = -1
|
|
98
103
|
edge.src.live_out = set()
|
|
99
|
-
else:
|
|
100
|
-
prev_size = len(edge.src.live_out)
|
|
101
104
|
edge.src.live_out.update(live)
|
|
102
|
-
if len(edge.src.live_out) !=
|
|
105
|
+
if len(edge.src.live_out) != prev_sizes_by_block[edge.src]:
|
|
103
106
|
updated_blocks.append(edge.src)
|
|
104
107
|
return updated_blocks
|
|
105
108
|
|
|
106
|
-
def get_uses(
|
|
107
|
-
uses
|
|
109
|
+
def get_uses(
|
|
110
|
+
self, stmt: IRStmt | BlockPlace | SSAPlace | TempBlock | int, uses: set[HasLiveness]
|
|
111
|
+
) -> set[HasLiveness]:
|
|
108
112
|
match stmt:
|
|
109
113
|
case IRPureInstr(op=_, args=args) | IRInstr(op=_, args=args):
|
|
110
114
|
for arg in args:
|
|
111
|
-
|
|
115
|
+
self.get_uses(arg, uses)
|
|
112
116
|
case IRGet(place=place):
|
|
113
|
-
|
|
117
|
+
self.get_uses(place, uses)
|
|
114
118
|
case IRSet(place=place, value=value):
|
|
115
119
|
if isinstance(place, BlockPlace):
|
|
116
120
|
if not isinstance(place.block, TempBlock):
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
self.get_uses(place.block, uses)
|
|
122
|
+
self.get_uses(place.index, uses)
|
|
123
|
+
self.get_uses(value, uses)
|
|
120
124
|
case IRConst() | int():
|
|
121
125
|
pass
|
|
122
126
|
case BlockPlace(block=block, index=index, offset=_):
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
self.get_uses(block, uses)
|
|
128
|
+
self.get_uses(index, uses)
|
|
125
129
|
case SSAPlace() | TempBlock():
|
|
126
130
|
uses.add(stmt)
|
|
127
131
|
case _:
|
|
@@ -151,7 +155,7 @@ class LivenessAnalysis(CompilerPass):
|
|
|
151
155
|
case IRSet(place=_, value=value):
|
|
152
156
|
if isinstance(value, IRInstr) and value.op.side_effects:
|
|
153
157
|
return False
|
|
154
|
-
defs =
|
|
158
|
+
defs = stmt.defs | stmt.array_defs
|
|
155
159
|
return defs and not (defs & live)
|
|
156
160
|
return False
|
|
157
161
|
|
|
@@ -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)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
|
|
2
|
+
from sonolus.backend.ops import Op
|
|
3
|
+
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_preorder
|
|
4
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CoalesceFlow(CompilerPass):
|
|
8
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
9
|
+
queue = [entry]
|
|
10
|
+
processed = set()
|
|
11
|
+
while queue:
|
|
12
|
+
block = queue.pop()
|
|
13
|
+
if block in processed:
|
|
14
|
+
continue
|
|
15
|
+
processed.add(block)
|
|
16
|
+
for edge in block.outgoing:
|
|
17
|
+
while True:
|
|
18
|
+
dst = edge.dst
|
|
19
|
+
if dst.phis or dst.statements or len(dst.outgoing) != 1 or dst is block:
|
|
20
|
+
break
|
|
21
|
+
next_dst = next(iter(dst.outgoing)).dst
|
|
22
|
+
if next_dst.phis:
|
|
23
|
+
break
|
|
24
|
+
dst.incoming.remove(edge)
|
|
25
|
+
if not dst.incoming:
|
|
26
|
+
for dst_edge in dst.outgoing:
|
|
27
|
+
dst_edge.dst.incoming.remove(dst_edge)
|
|
28
|
+
processed.add(dst)
|
|
29
|
+
edge.dst = next_dst
|
|
30
|
+
next_dst.incoming.add(edge)
|
|
31
|
+
if dst is edge.dst:
|
|
32
|
+
break
|
|
33
|
+
default_edge = next((edge for edge in block.outgoing if edge.cond is None), None)
|
|
34
|
+
if default_edge is not None:
|
|
35
|
+
for edge in [*block.outgoing]:
|
|
36
|
+
if edge is default_edge:
|
|
37
|
+
continue
|
|
38
|
+
if edge.dst is default_edge.dst:
|
|
39
|
+
block.outgoing.remove(edge)
|
|
40
|
+
edge.dst.incoming.remove(edge)
|
|
41
|
+
if len(block.outgoing) != 1:
|
|
42
|
+
queue.extend(edge.dst for edge in block.outgoing)
|
|
43
|
+
continue
|
|
44
|
+
next_block = next(iter(block.outgoing)).dst
|
|
45
|
+
if len(next_block.incoming) != 1:
|
|
46
|
+
queue.append(next_block)
|
|
47
|
+
if not block.statements and not block.phis and not next_block.phis:
|
|
48
|
+
for edge in block.incoming:
|
|
49
|
+
edge.dst = next_block
|
|
50
|
+
next_block.incoming.add(edge)
|
|
51
|
+
for edge in block.outgoing: # There should be exactly one
|
|
52
|
+
next_block.incoming.remove(edge)
|
|
53
|
+
if block is entry:
|
|
54
|
+
entry = next_block
|
|
55
|
+
continue
|
|
56
|
+
for p, args in next_block.phis.items():
|
|
57
|
+
if block not in args:
|
|
58
|
+
continue
|
|
59
|
+
block.statements.append(IRSet(p, IRGet(args[block])))
|
|
60
|
+
block.statements.extend(next_block.statements)
|
|
61
|
+
block.test = next_block.test
|
|
62
|
+
block.outgoing = next_block.outgoing
|
|
63
|
+
for edge in block.outgoing:
|
|
64
|
+
edge.src = block
|
|
65
|
+
dst = edge.dst
|
|
66
|
+
for args in dst.phis.values():
|
|
67
|
+
if next_block in args:
|
|
68
|
+
args[block] = args.pop(next_block)
|
|
69
|
+
processed.add(next_block)
|
|
70
|
+
queue.extend(edge.dst for edge in block.outgoing)
|
|
71
|
+
processed.remove(block)
|
|
72
|
+
queue.append(block)
|
|
73
|
+
return entry
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RewriteToSwitch(CompilerPass):
|
|
77
|
+
"""Rewrite if-else chains to switch statements.
|
|
78
|
+
|
|
79
|
+
Note that this needs inlining (and dead code elimination) to be run first to really do anything useful.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
83
|
+
self.ifs_to_switch(entry)
|
|
84
|
+
self.combine_blocks(entry)
|
|
85
|
+
self.remove_unreachable(entry)
|
|
86
|
+
return entry
|
|
87
|
+
|
|
88
|
+
def ifs_to_switch(self, entry: BasicBlock):
|
|
89
|
+
for block in traverse_cfg_preorder(entry):
|
|
90
|
+
if len(block.outgoing) != 2 or {edge.cond for edge in block.outgoing} != {None, 0}:
|
|
91
|
+
continue
|
|
92
|
+
test = block.test
|
|
93
|
+
if not isinstance(test, IRPureInstr) or test.op != Op.Equal:
|
|
94
|
+
continue
|
|
95
|
+
assert len(test.args) == 2
|
|
96
|
+
if isinstance(test.args[0], IRConst):
|
|
97
|
+
const, other = test.args
|
|
98
|
+
elif isinstance(test.args[1], IRConst):
|
|
99
|
+
other, const = test.args
|
|
100
|
+
else:
|
|
101
|
+
continue
|
|
102
|
+
block.test = other
|
|
103
|
+
for edge in block.outgoing:
|
|
104
|
+
if edge.cond is None:
|
|
105
|
+
edge.cond = const.value
|
|
106
|
+
else:
|
|
107
|
+
edge.cond = None
|
|
108
|
+
|
|
109
|
+
def combine_blocks(self, entry: BasicBlock):
|
|
110
|
+
queue = [entry]
|
|
111
|
+
processed = set()
|
|
112
|
+
while queue:
|
|
113
|
+
block = queue.pop()
|
|
114
|
+
if block in processed:
|
|
115
|
+
continue
|
|
116
|
+
processed.add(block)
|
|
117
|
+
queue.extend(edge.dst for edge in block.outgoing)
|
|
118
|
+
|
|
119
|
+
default_edge = next((edge for edge in block.outgoing if edge.cond is None), None)
|
|
120
|
+
if default_edge is None:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
next_block = default_edge.dst
|
|
124
|
+
if (
|
|
125
|
+
len(next_block.incoming) > 1
|
|
126
|
+
or next_block.statements
|
|
127
|
+
or next_block.phis
|
|
128
|
+
or block.test != next_block.test
|
|
129
|
+
or block is next_block
|
|
130
|
+
or next_block is entry
|
|
131
|
+
):
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
outgoing_by_cond = {edge.cond: edge for edge in block.outgoing}
|
|
135
|
+
assert len(outgoing_by_cond) == len(block.outgoing)
|
|
136
|
+
outgoing_by_cond.pop(None)
|
|
137
|
+
for edge in next_block.outgoing:
|
|
138
|
+
if edge.cond in outgoing_by_cond:
|
|
139
|
+
# This edge is unreachable since an equivalent edge would have been taken
|
|
140
|
+
edge.dst.incoming.remove(edge)
|
|
141
|
+
continue
|
|
142
|
+
outgoing_by_cond[edge.cond] = edge
|
|
143
|
+
edge.src = block
|
|
144
|
+
for args in edge.dst.phis.values():
|
|
145
|
+
if next_block in args:
|
|
146
|
+
args[block] = args.pop(next_block)
|
|
147
|
+
block.outgoing = set(outgoing_by_cond.values())
|
|
148
|
+
processed.add(next_block)
|
|
149
|
+
queue.append(block)
|
|
150
|
+
processed.remove(block)
|
|
151
|
+
|
|
152
|
+
def remove_unreachable(self, entry: BasicBlock):
|
|
153
|
+
reachable = {*traverse_cfg_preorder(entry)}
|
|
154
|
+
for block in traverse_cfg_preorder(entry):
|
|
155
|
+
block.incoming = {edge for edge in block.incoming if edge.src in reachable}
|
|
156
|
+
block.outgoing = {edge for edge in block.outgoing if edge.dst in reachable}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class NormalizeSwitch(CompilerPass):
|
|
160
|
+
"""Normalize branches like cond -> case a, case a + b, case a + 2b to ((cond - a) / b) -> case 0, case 1, case 2."""
|
|
161
|
+
|
|
162
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
163
|
+
for block in traverse_cfg_preorder(entry):
|
|
164
|
+
cases = {edge.cond for edge in block.outgoing}
|
|
165
|
+
if len(cases) <= 2:
|
|
166
|
+
continue
|
|
167
|
+
assert None in cases, "Non-terminal blocks should always have a default edge"
|
|
168
|
+
cases.remove(None)
|
|
169
|
+
offset, stride = self.get_offset_stride(cases)
|
|
170
|
+
if offset is None or (offset == 0 and stride == 1):
|
|
171
|
+
continue
|
|
172
|
+
for edge in block.outgoing:
|
|
173
|
+
if edge.cond is None:
|
|
174
|
+
continue
|
|
175
|
+
edge.cond = (edge.cond - offset) // stride
|
|
176
|
+
if offset != 0:
|
|
177
|
+
block.test = IRPureInstr(Op.Subtract, [block.test, IRConst(offset)])
|
|
178
|
+
if stride != 1:
|
|
179
|
+
block.test = IRPureInstr(Op.Divide, [block.test, IRConst(stride)])
|
|
180
|
+
return entry
|
|
181
|
+
|
|
182
|
+
def get_offset_stride(self, cases: set[int]) -> tuple[int | None, int | None]:
|
|
183
|
+
cases = sorted(cases)
|
|
184
|
+
offset = cases[0]
|
|
185
|
+
stride = cases[1] - offset
|
|
186
|
+
if int(offset) != offset or int(stride) != stride:
|
|
187
|
+
return None, None
|
|
188
|
+
for i, case in enumerate(cases[2:], 2):
|
|
189
|
+
if case != offset + i * stride:
|
|
190
|
+
return None, None
|
|
191
|
+
return offset, stride
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from sonolus.backend.dominance import DominanceFrontiers, get_df, get_dom_children
|
|
2
|
-
from sonolus.backend.flow import BasicBlock, FlowEdge, traverse_cfg_preorder
|
|
3
1
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
4
|
-
from sonolus.backend.
|
|
2
|
+
from sonolus.backend.optimize.dominance import DominanceFrontiers, get_df, get_dom_children
|
|
3
|
+
from sonolus.backend.optimize.flow import BasicBlock, FlowEdge, traverse_cfg_preorder
|
|
4
|
+
from sonolus.backend.optimize.passes import CompilerPass
|
|
5
5
|
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
6
6
|
|
|
7
7
|
|
|
@@ -23,13 +23,14 @@ class ToSSA(CompilerPass):
|
|
|
23
23
|
ssa_places: dict[TempBlock, list[SSAPlace]],
|
|
24
24
|
used: dict[str, int],
|
|
25
25
|
):
|
|
26
|
-
|
|
26
|
+
to_pop = []
|
|
27
27
|
for var, args in [*block.phis.items()]:
|
|
28
28
|
if isinstance(var, SSAPlace):
|
|
29
29
|
continue
|
|
30
30
|
ssa_places[var].append(self.get_new_ssa_place(var.name, used))
|
|
31
|
+
to_pop.append(var)
|
|
31
32
|
block.phis[ssa_places[var][-1]] = args
|
|
32
|
-
block.statements = [self.rename_stmt(stmt, ssa_places, used) for stmt in block.statements]
|
|
33
|
+
block.statements = [self.rename_stmt(stmt, ssa_places, used, to_pop) for stmt in block.statements]
|
|
33
34
|
for edge in block.outgoing:
|
|
34
35
|
dst = edge.dst
|
|
35
36
|
for var, args in dst.phis.items():
|
|
@@ -37,35 +38,47 @@ class ToSSA(CompilerPass):
|
|
|
37
38
|
continue
|
|
38
39
|
if ssa_places[var]:
|
|
39
40
|
args[block] = ssa_places[var][-1]
|
|
40
|
-
block.test = self.rename_stmt(block.test, ssa_places, used)
|
|
41
|
+
block.test = self.rename_stmt(block.test, ssa_places, used, to_pop)
|
|
41
42
|
for dom_child in get_dom_children(block):
|
|
42
43
|
self.rename(dom_child, defs, ssa_places, used)
|
|
43
|
-
for var
|
|
44
|
-
ssa_places[var]
|
|
44
|
+
for var in to_pop:
|
|
45
|
+
ssa_places[var].pop()
|
|
45
46
|
|
|
46
47
|
def remove_placeholder_phis(self, entry: BasicBlock):
|
|
47
48
|
for block in traverse_cfg_preorder(entry):
|
|
48
49
|
block.phis = {var: args for var, args in block.phis.items() if isinstance(var, SSAPlace)}
|
|
49
50
|
|
|
50
|
-
def rename_stmt(
|
|
51
|
+
def rename_stmt(
|
|
52
|
+
self, stmt: IRStmt, ssa_places: dict[TempBlock, list[SSAPlace]], used: dict[str, int], to_pop: list[SSAPlace]
|
|
53
|
+
):
|
|
51
54
|
match stmt:
|
|
52
55
|
case IRConst():
|
|
53
56
|
return stmt
|
|
54
57
|
case IRPureInstr(op=op, args=args):
|
|
55
|
-
return IRPureInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used) for arg in args])
|
|
58
|
+
return IRPureInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used, to_pop) for arg in args])
|
|
56
59
|
case IRInstr(op=op, args=args):
|
|
57
|
-
return IRInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used) for arg in args])
|
|
60
|
+
return IRInstr(op=op, args=[self.rename_stmt(arg, ssa_places, used, to_pop) for arg in args])
|
|
58
61
|
case IRGet(place=place):
|
|
59
|
-
return IRGet(place=self.rename_stmt(place, ssa_places, used))
|
|
62
|
+
return IRGet(place=self.rename_stmt(place, ssa_places, used, to_pop))
|
|
60
63
|
case IRSet(place=place, value=value):
|
|
61
|
-
value = self.rename_stmt(value, ssa_places, used)
|
|
64
|
+
value = self.rename_stmt(value, ssa_places, used, to_pop)
|
|
62
65
|
if isinstance(place, BlockPlace) and isinstance(place.block, TempBlock) and place.block.size == 1:
|
|
63
66
|
ssa_places[place.block].append(self.get_new_ssa_place(place.block.name, used))
|
|
64
|
-
|
|
67
|
+
to_pop.append(place.block)
|
|
68
|
+
place = self.rename_stmt(place, ssa_places, used, to_pop)
|
|
65
69
|
return IRSet(place=place, value=value)
|
|
66
70
|
case SSAPlace():
|
|
67
71
|
return stmt
|
|
68
72
|
case TempBlock() if stmt.size == 1:
|
|
73
|
+
if stmt not in ssa_places or not ssa_places[stmt]:
|
|
74
|
+
# This is an access to a definitely undefined variable
|
|
75
|
+
# But it might not be reachable in reality, so we should allow it
|
|
76
|
+
# Maybe there should be an error if this still happens after optimization,
|
|
77
|
+
# but recovering the location of the error in the original code is hard.
|
|
78
|
+
# This can happen in places like matching a VarArray[Num, 1] which was just created.
|
|
79
|
+
# IR generation won't immediately fold a check that size > 0 to false, so here we
|
|
80
|
+
# might see an access to uninitialized memory even though it's not reachable in reality.
|
|
81
|
+
return SSAPlace("err", 0)
|
|
69
82
|
return ssa_places[stmt][-1]
|
|
70
83
|
case TempBlock():
|
|
71
84
|
return stmt
|
|
@@ -73,11 +86,11 @@ class ToSSA(CompilerPass):
|
|
|
73
86
|
return stmt
|
|
74
87
|
case BlockPlace(block=block, index=index, offset=offset):
|
|
75
88
|
if isinstance(block, TempBlock) and block.size == 1:
|
|
76
|
-
return self.rename_stmt(block, ssa_places, used)
|
|
89
|
+
return self.rename_stmt(block, ssa_places, used, to_pop)
|
|
77
90
|
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),
|
|
91
|
+
block=self.rename_stmt(block, ssa_places, used, to_pop),
|
|
92
|
+
index=self.rename_stmt(index, ssa_places, used, to_pop),
|
|
93
|
+
offset=self.rename_stmt(offset, ssa_places, used, to_pop),
|
|
81
94
|
)
|
|
82
95
|
case _:
|
|
83
96
|
raise TypeError(f"Unexpected statement: {stmt}")
|
sonolus/backend/place.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections.abc import Iterator
|
|
2
|
-
from typing import Self
|
|
2
|
+
from typing import NamedTuple, Self
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.blocks import Block
|
|
5
5
|
|
|
@@ -8,13 +8,9 @@ type BlockValue = Block | int | TempBlock | Place
|
|
|
8
8
|
type IndexValue = int | Place
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class TempBlock:
|
|
11
|
+
class TempBlock(NamedTuple):
|
|
12
12
|
name: str
|
|
13
|
-
size: int
|
|
14
|
-
|
|
15
|
-
def __init__(self, name: str, size: int = 1):
|
|
16
|
-
self.name = name
|
|
17
|
-
self.size = size
|
|
13
|
+
size: int = 1
|
|
18
14
|
|
|
19
15
|
def __repr__(self):
|
|
20
16
|
return f"TempBlock(name={self.name!r}, size={self.size!r})"
|
|
@@ -33,19 +29,14 @@ class TempBlock:
|
|
|
33
29
|
return isinstance(other, TempBlock) and self.name == other.name and self.size == other.size
|
|
34
30
|
|
|
35
31
|
def __hash__(self):
|
|
36
|
-
return hash(
|
|
32
|
+
return hash(self.name) # Typically will be unique by name alone
|
|
37
33
|
|
|
38
34
|
|
|
39
|
-
class BlockPlace:
|
|
35
|
+
class BlockPlace(NamedTuple):
|
|
40
36
|
block: BlockValue
|
|
41
|
-
index: IndexValue
|
|
37
|
+
index: IndexValue = 0
|
|
42
38
|
offset: int = 0
|
|
43
39
|
|
|
44
|
-
def __init__(self, block: BlockValue, index: IndexValue = 0, offset: int = 0):
|
|
45
|
-
self.block = block
|
|
46
|
-
self.index = index
|
|
47
|
-
self.offset = offset
|
|
48
|
-
|
|
49
40
|
def __repr__(self):
|
|
50
41
|
return f"BlockPlace(block={self.block!r}, index={self.index!r}, offset={self.offset!r})"
|
|
51
42
|
|
|
@@ -59,24 +50,25 @@ class BlockPlace:
|
|
|
59
50
|
else:
|
|
60
51
|
return f"{self.block}[{self.index} + {self.offset}]"
|
|
61
52
|
|
|
53
|
+
def add_offset(self, offset: int) -> Self:
|
|
54
|
+
return BlockPlace(self.block, self.index, self.offset + offset)
|
|
55
|
+
|
|
62
56
|
def __eq__(self, other):
|
|
63
|
-
return
|
|
57
|
+
return (
|
|
58
|
+
isinstance(other, BlockPlace)
|
|
59
|
+
and self.block == other.block
|
|
60
|
+
and self.index == other.index
|
|
61
|
+
and self.offset == other.offset
|
|
62
|
+
)
|
|
64
63
|
|
|
65
64
|
def __hash__(self):
|
|
66
|
-
return hash((self.block, self.index))
|
|
65
|
+
return hash((self.block, self.index, self.offset))
|
|
67
66
|
|
|
68
|
-
def add_offset(self, offset: int) -> Self:
|
|
69
|
-
return BlockPlace(self.block, self.index, self.offset + offset)
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
class SSAPlace:
|
|
68
|
+
class SSAPlace(NamedTuple):
|
|
73
69
|
name: str
|
|
74
70
|
num: int
|
|
75
71
|
|
|
76
|
-
def __init__(self, name: str, num: int):
|
|
77
|
-
self.name = name
|
|
78
|
-
self.num = num
|
|
79
|
-
|
|
80
72
|
def __repr__(self):
|
|
81
73
|
return f"SSAPlace(name={self.name!r}, num={self.num!r})"
|
|
82
74
|
|
sonolus/backend/utils.py
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import ast
|
|
3
3
|
import inspect
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from functools import cache
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
@cache
|
|
8
10
|
def get_function(fn: Callable) -> tuple[str, ast.FunctionDef]:
|
|
9
11
|
# This preserves both line number and column number in the returned node
|
|
10
12
|
source_file = inspect.getsourcefile(fn)
|
|
@@ -26,6 +28,14 @@ class FindFunction(ast.NodeVisitor):
|
|
|
26
28
|
else:
|
|
27
29
|
self.generic_visit(node)
|
|
28
30
|
|
|
31
|
+
def visit_Lambda(self, node: ast.Lambda):
|
|
32
|
+
if node.lineno == self.line:
|
|
33
|
+
if self.node is not None:
|
|
34
|
+
raise ValueError("Multiple functions defined on the same line are not supported")
|
|
35
|
+
self.node = node
|
|
36
|
+
else:
|
|
37
|
+
self.generic_visit(node)
|
|
38
|
+
|
|
29
39
|
|
|
30
40
|
def find_function(tree: ast.Module, line: int):
|
|
31
41
|
visitor = FindFunction(line)
|