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.
- 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 -880
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/engine.py +55 -5
- 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 -115
- 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/engine.py +37 -1
- sonolus/script/globals.py +269 -269
- sonolus/script/graphics.py +200 -150
- sonolus/script/instruction.py +151 -0
- 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 -211
- 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/{preview.py → 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 +45 -6
- 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 -72
- {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -2
- sonolus_py-0.1.4.dist-info/RECORD +84 -0
- {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
- sonolus/build/defaults.py +0 -32
- sonolus/script/icon.py +0 -73
- sonolus_py-0.1.2.dist-info/RECORD +0 -76
- {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/entry_points.txt +0 -0
sonolus/backend/excepthook.py
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
"""Exception hook to filter out compiler internal frames from tracebacks."""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from types import TracebackType
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def should_filter_traceback(tb: TracebackType | None) -> bool:
|
|
8
|
-
return tb is not None and (
|
|
9
|
-
tb.tb_frame.f_globals.get("_filter_traceback_", False) or should_filter_traceback(tb.tb_next)
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def is_compiler_internal(tb: TracebackType):
|
|
14
|
-
return tb.tb_frame.f_locals.get("_compiler_internal_", False) or tb.tb_frame.f_globals.get(
|
|
15
|
-
"_compiler_internal_", False
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
|
|
20
|
-
if tb is None:
|
|
21
|
-
return None
|
|
22
|
-
if is_compiler_internal(tb):
|
|
23
|
-
return filter_traceback(tb.tb_next)
|
|
24
|
-
tb.tb_next = filter_traceback(tb.tb_next)
|
|
25
|
-
return tb
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def excepthook(exc, value, tb):
|
|
29
|
-
import traceback
|
|
30
|
-
|
|
31
|
-
if should_filter_traceback(tb):
|
|
32
|
-
tb = filter_traceback(tb)
|
|
33
|
-
traceback.print_exception(exc, value, tb)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def install_excepthook():
|
|
37
|
-
sys.excepthook = excepthook
|
|
1
|
+
"""Exception hook to filter out compiler internal frames from tracebacks."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from types import TracebackType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def should_filter_traceback(tb: TracebackType | None) -> bool:
|
|
8
|
+
return tb is not None and (
|
|
9
|
+
tb.tb_frame.f_globals.get("_filter_traceback_", False) or should_filter_traceback(tb.tb_next)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_compiler_internal(tb: TracebackType):
|
|
14
|
+
return tb.tb_frame.f_locals.get("_compiler_internal_", False) or tb.tb_frame.f_globals.get(
|
|
15
|
+
"_compiler_internal_", False
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
|
|
20
|
+
if tb is None:
|
|
21
|
+
return None
|
|
22
|
+
if is_compiler_internal(tb):
|
|
23
|
+
return filter_traceback(tb.tb_next)
|
|
24
|
+
tb.tb_next = filter_traceback(tb.tb_next)
|
|
25
|
+
return tb
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def excepthook(exc, value, tb):
|
|
29
|
+
import traceback
|
|
30
|
+
|
|
31
|
+
if should_filter_traceback(tb):
|
|
32
|
+
tb = filter_traceback(tb)
|
|
33
|
+
traceback.print_exception(exc, value, tb)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def install_excepthook():
|
|
37
|
+
sys.excepthook = excepthook
|
sonolus/backend/finalize.py
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
|
|
2
|
-
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
3
|
-
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
4
|
-
from sonolus.backend.ops import Op
|
|
5
|
-
from sonolus.backend.place import BlockPlace
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def cfg_to_engine_node(entry: BasicBlock):
|
|
9
|
-
block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
|
|
10
|
-
block_statements = []
|
|
11
|
-
for block in block_indexes:
|
|
12
|
-
statements = []
|
|
13
|
-
statements.extend(ir_to_engine_node(stmt) for stmt in block.statements)
|
|
14
|
-
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
15
|
-
match outgoing:
|
|
16
|
-
case {**other} if not other:
|
|
17
|
-
statements.append(ConstantNode(value=len(block_indexes)))
|
|
18
|
-
case {None: target, **other} if not other:
|
|
19
|
-
statements.append(ConstantNode(value=block_indexes[target]))
|
|
20
|
-
case {0: f_branch, None: t_branch, **other} if not other:
|
|
21
|
-
statements.append(
|
|
22
|
-
FunctionNode(
|
|
23
|
-
func=Op.If,
|
|
24
|
-
args=[
|
|
25
|
-
ir_to_engine_node(block.test),
|
|
26
|
-
ConstantNode(value=block_indexes[t_branch]),
|
|
27
|
-
ConstantNode(value=block_indexes[f_branch]),
|
|
28
|
-
],
|
|
29
|
-
)
|
|
30
|
-
)
|
|
31
|
-
case dict() as tgt:
|
|
32
|
-
args = [ir_to_engine_node(block.test)]
|
|
33
|
-
default = len(block_indexes)
|
|
34
|
-
for cond, target in tgt.items():
|
|
35
|
-
if cond is None:
|
|
36
|
-
default = block_indexes[target]
|
|
37
|
-
args.append(ConstantNode(value=cond))
|
|
38
|
-
args.append(ConstantNode(value=block_indexes[target]))
|
|
39
|
-
args.append(ConstantNode(value=default))
|
|
40
|
-
statements.append(FunctionNode(Op.SwitchWithDefault, args))
|
|
41
|
-
block_statements.append(FunctionNode(Op.Execute, statements))
|
|
42
|
-
block_statements.append(ConstantNode(value=0))
|
|
43
|
-
return FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def ir_to_engine_node(stmt) -> EngineNode:
|
|
47
|
-
match stmt:
|
|
48
|
-
case int() | float():
|
|
49
|
-
return ConstantNode(value=float(stmt))
|
|
50
|
-
case IRConst(value=value):
|
|
51
|
-
return ConstantNode(value=value)
|
|
52
|
-
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
53
|
-
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
54
|
-
case IRGet(place=place):
|
|
55
|
-
return ir_to_engine_node(place)
|
|
56
|
-
case BlockPlace() as place:
|
|
57
|
-
if place.offset == 0:
|
|
58
|
-
index = ir_to_engine_node(place.index)
|
|
59
|
-
elif place.index == 0:
|
|
60
|
-
index = ConstantNode(value=place.offset)
|
|
61
|
-
else:
|
|
62
|
-
index = FunctionNode(
|
|
63
|
-
func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
|
|
64
|
-
)
|
|
65
|
-
return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
|
|
66
|
-
case IRSet(place=place, value=value):
|
|
67
|
-
return FunctionNode(func=Op.Set, args=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
|
|
68
|
-
case _:
|
|
69
|
-
raise TypeError(f"Unsupported IR statement: {stmt}")
|
|
1
|
+
from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
|
|
2
|
+
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
3
|
+
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
4
|
+
from sonolus.backend.ops import Op
|
|
5
|
+
from sonolus.backend.place import BlockPlace
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def cfg_to_engine_node(entry: BasicBlock):
|
|
9
|
+
block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
|
|
10
|
+
block_statements = []
|
|
11
|
+
for block in block_indexes:
|
|
12
|
+
statements = []
|
|
13
|
+
statements.extend(ir_to_engine_node(stmt) for stmt in block.statements)
|
|
14
|
+
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
15
|
+
match outgoing:
|
|
16
|
+
case {**other} if not other:
|
|
17
|
+
statements.append(ConstantNode(value=len(block_indexes)))
|
|
18
|
+
case {None: target, **other} if not other:
|
|
19
|
+
statements.append(ConstantNode(value=block_indexes[target]))
|
|
20
|
+
case {0: f_branch, None: t_branch, **other} if not other:
|
|
21
|
+
statements.append(
|
|
22
|
+
FunctionNode(
|
|
23
|
+
func=Op.If,
|
|
24
|
+
args=[
|
|
25
|
+
ir_to_engine_node(block.test),
|
|
26
|
+
ConstantNode(value=block_indexes[t_branch]),
|
|
27
|
+
ConstantNode(value=block_indexes[f_branch]),
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
case dict() as tgt:
|
|
32
|
+
args = [ir_to_engine_node(block.test)]
|
|
33
|
+
default = len(block_indexes)
|
|
34
|
+
for cond, target in tgt.items():
|
|
35
|
+
if cond is None:
|
|
36
|
+
default = block_indexes[target]
|
|
37
|
+
args.append(ConstantNode(value=cond))
|
|
38
|
+
args.append(ConstantNode(value=block_indexes[target]))
|
|
39
|
+
args.append(ConstantNode(value=default))
|
|
40
|
+
statements.append(FunctionNode(Op.SwitchWithDefault, args))
|
|
41
|
+
block_statements.append(FunctionNode(Op.Execute, statements))
|
|
42
|
+
block_statements.append(ConstantNode(value=0))
|
|
43
|
+
return FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ir_to_engine_node(stmt) -> EngineNode:
|
|
47
|
+
match stmt:
|
|
48
|
+
case int() | float():
|
|
49
|
+
return ConstantNode(value=float(stmt))
|
|
50
|
+
case IRConst(value=value):
|
|
51
|
+
return ConstantNode(value=value)
|
|
52
|
+
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
53
|
+
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
54
|
+
case IRGet(place=place):
|
|
55
|
+
return ir_to_engine_node(place)
|
|
56
|
+
case BlockPlace() as place:
|
|
57
|
+
if place.offset == 0:
|
|
58
|
+
index = ir_to_engine_node(place.index)
|
|
59
|
+
elif place.index == 0:
|
|
60
|
+
index = ConstantNode(value=place.offset)
|
|
61
|
+
else:
|
|
62
|
+
index = FunctionNode(
|
|
63
|
+
func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
|
|
64
|
+
)
|
|
65
|
+
return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
|
|
66
|
+
case IRSet(place=place, value=value):
|
|
67
|
+
return FunctionNode(func=Op.Set, args=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
|
|
68
|
+
case _:
|
|
69
|
+
raise TypeError(f"Unsupported IR statement: {stmt}")
|
sonolus/backend/flow.py
CHANGED
|
@@ -1,92 +1,121 @@
|
|
|
1
|
-
import textwrap
|
|
2
|
-
from collections import deque
|
|
3
|
-
from collections.abc import Iterator
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
self.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
import textwrap
|
|
2
|
+
from collections import deque
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
7
|
+
from sonolus.backend.place import SSAPlace, TempBlock
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FlowEdge:
|
|
11
|
+
src: "BasicBlock"
|
|
12
|
+
dst: "BasicBlock"
|
|
13
|
+
cond: float | int | None
|
|
14
|
+
|
|
15
|
+
def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: float | None = None):
|
|
16
|
+
self.src = src
|
|
17
|
+
self.dst = dst
|
|
18
|
+
self.cond = cond
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BasicBlock:
|
|
22
|
+
phis: dict[SSAPlace | TempBlock, dict[Self, SSAPlace]]
|
|
23
|
+
statements: list[IRStmt]
|
|
24
|
+
test: IRExpr
|
|
25
|
+
incoming: set[FlowEdge]
|
|
26
|
+
outgoing: set[FlowEdge]
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
phi: dict[SSAPlace, dict[Self, SSAPlace]] | None = None,
|
|
32
|
+
statements: list[IRStmt] | None = None,
|
|
33
|
+
test: IRExpr | None = None,
|
|
34
|
+
incoming: set[FlowEdge] | None = None,
|
|
35
|
+
outgoing: set[FlowEdge] | None = None,
|
|
36
|
+
):
|
|
37
|
+
self.phis = phi or {}
|
|
38
|
+
self.statements = statements or []
|
|
39
|
+
self.test = test or IRConst(0)
|
|
40
|
+
self.incoming = incoming or set()
|
|
41
|
+
self.outgoing = outgoing or set()
|
|
42
|
+
|
|
43
|
+
def connect_to(self, other: "BasicBlock", cond: int | float | None = None):
|
|
44
|
+
edge = FlowEdge(self, other, cond)
|
|
45
|
+
self.outgoing.add(edge)
|
|
46
|
+
other.incoming.add(edge)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def traverse_cfg_preorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
50
|
+
visited = set()
|
|
51
|
+
queue = deque([block])
|
|
52
|
+
while queue:
|
|
53
|
+
block = queue.popleft()
|
|
54
|
+
if block in visited:
|
|
55
|
+
continue
|
|
56
|
+
visited.add(block)
|
|
57
|
+
yield block
|
|
58
|
+
for edge in sorted(block.outgoing, key=lambda e: (e.cond is None, e.cond)):
|
|
59
|
+
queue.append(edge.dst)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def traverse_cfg_postorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
63
|
+
visited = set()
|
|
64
|
+
|
|
65
|
+
def dfs(current: BasicBlock):
|
|
66
|
+
if current in visited:
|
|
67
|
+
return
|
|
68
|
+
visited.add(current)
|
|
69
|
+
for edge in sorted(current.outgoing, key=lambda e: (e.cond is None, e.cond)):
|
|
70
|
+
yield from dfs(edge.dst)
|
|
71
|
+
yield current
|
|
72
|
+
|
|
73
|
+
yield from dfs(block)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def traverse_cfg_reverse_postorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
77
|
+
yield from reversed(list(traverse_cfg_postorder(block)))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cfg_to_mermaid(entry: BasicBlock):
|
|
81
|
+
def pre(s: str):
|
|
82
|
+
return "\"<pre style='text-align: left;'>" + s.replace("\n", "<br/>") + '</pre>"'
|
|
83
|
+
|
|
84
|
+
def fmt(nodes):
|
|
85
|
+
if nodes:
|
|
86
|
+
return "\n".join(str(n) for n in nodes)
|
|
87
|
+
else:
|
|
88
|
+
return "{}"
|
|
89
|
+
|
|
90
|
+
block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
|
|
91
|
+
|
|
92
|
+
lines = ["Entry([Entry]) --> 0"]
|
|
93
|
+
for block in traverse_cfg_preorder(entry):
|
|
94
|
+
index = block_indexes[block]
|
|
95
|
+
lines.append(f"{index}[{pre(fmt([f'#{index}', *(
|
|
96
|
+
f"{dst} := phi({", ".join(f"{block_indexes.get(src_block, "<dead>")}: {src_place}"
|
|
97
|
+
for src_block, src_place
|
|
98
|
+
in sorted(phis.items(), key=lambda x: block_indexes.get(x[0])))})"
|
|
99
|
+
for dst, phis in block.phis.items()
|
|
100
|
+
), *block.statements]))}]")
|
|
101
|
+
|
|
102
|
+
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
103
|
+
match outgoing:
|
|
104
|
+
case {**other} if not other:
|
|
105
|
+
lines.append(f"{index} --> Exit")
|
|
106
|
+
case {None: target, **other} if not other:
|
|
107
|
+
lines.append(f"{index} --> {block_indexes[target]}")
|
|
108
|
+
case {0: f_branch, None: t_branch, **other} if not other:
|
|
109
|
+
lines.append(f"{index}_{{{pre(fmt([block.test]))}}}")
|
|
110
|
+
lines.append(f"{index} --> {index}_")
|
|
111
|
+
lines.append(f"{index}_ --> |true| {block_indexes[t_branch]}")
|
|
112
|
+
lines.append(f"{index}_ --> |false| {block_indexes[f_branch]}")
|
|
113
|
+
case dict() as tgt:
|
|
114
|
+
lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
|
|
115
|
+
lines.append(f"{index} --> {index}_")
|
|
116
|
+
for cond, target in tgt.items():
|
|
117
|
+
lines.append(f"{index}_ --> |{pre(fmt([cond or "default"]))}| {block_indexes[target]}")
|
|
118
|
+
lines.append("Exit([Exit])")
|
|
119
|
+
|
|
120
|
+
body = textwrap.indent("\n".join(lines), " ")
|
|
121
|
+
return f"graph\n{body}"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
|
|
2
|
+
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet, IRStmt
|
|
3
|
+
from sonolus.backend.passes import CompilerPass
|
|
4
|
+
from sonolus.backend.place import BlockPlace, SSAPlace, TempBlock
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InlineVars(CompilerPass):
|
|
8
|
+
def run(self, entry: BasicBlock) -> BasicBlock:
|
|
9
|
+
use_counts: dict[SSAPlace, int] = {}
|
|
10
|
+
definitions: dict[SSAPlace, IRStmt] = {}
|
|
11
|
+
|
|
12
|
+
for block in traverse_cfg_preorder(entry):
|
|
13
|
+
for stmt in block.statements:
|
|
14
|
+
if (
|
|
15
|
+
isinstance(stmt, IRSet)
|
|
16
|
+
and isinstance(stmt.place, SSAPlace)
|
|
17
|
+
and isinstance(stmt.value, IRGet)
|
|
18
|
+
and isinstance(stmt.value.place, SSAPlace)
|
|
19
|
+
):
|
|
20
|
+
# This is effectively an alias
|
|
21
|
+
pass
|
|
22
|
+
else:
|
|
23
|
+
self.count_uses(stmt, use_counts)
|
|
24
|
+
if isinstance(stmt, IRSet) and isinstance(stmt.place, SSAPlace):
|
|
25
|
+
definitions[stmt.place] = stmt.value
|
|
26
|
+
self.count_uses(block.test, use_counts)
|
|
27
|
+
|
|
28
|
+
for p, defn in definitions.items():
|
|
29
|
+
while True:
|
|
30
|
+
if isinstance(defn, IRGet) and isinstance(defn.place, SSAPlace) and defn.place in definitions:
|
|
31
|
+
inside_defn = definitions[defn.place]
|
|
32
|
+
if not self.is_inlinable(inside_defn):
|
|
33
|
+
break
|
|
34
|
+
defn = inside_defn
|
|
35
|
+
continue
|
|
36
|
+
inlinable_uses = self.get_inlinable_uses(defn, set())
|
|
37
|
+
subs = {}
|
|
38
|
+
for inside_p in inlinable_uses:
|
|
39
|
+
if inside_p not in definitions:
|
|
40
|
+
continue
|
|
41
|
+
inside_defn = definitions[inside_p]
|
|
42
|
+
if not self.is_inlinable(inside_defn):
|
|
43
|
+
continue
|
|
44
|
+
if (isinstance(inside_defn, IRGet) and isinstance(inside_defn.place, SSAPlace)) or use_counts[
|
|
45
|
+
inside_p
|
|
46
|
+
] == 1:
|
|
47
|
+
subs[inside_p] = inside_defn
|
|
48
|
+
if not subs:
|
|
49
|
+
break
|
|
50
|
+
defn = self.substitute(defn, subs)
|
|
51
|
+
definitions[p] = defn
|
|
52
|
+
|
|
53
|
+
valid = {
|
|
54
|
+
p
|
|
55
|
+
for p, count in use_counts.items()
|
|
56
|
+
if (p in definitions and count == 1 and self.is_inlinable(definitions[p]))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for block in traverse_cfg_preorder(entry):
|
|
60
|
+
new_statements = []
|
|
61
|
+
for stmt in [*block.statements, block.test]:
|
|
62
|
+
inlinable_uses = self.get_inlinable_uses(stmt, set())
|
|
63
|
+
subs = {}
|
|
64
|
+
for p in inlinable_uses:
|
|
65
|
+
if p not in valid:
|
|
66
|
+
continue
|
|
67
|
+
definition = definitions[p]
|
|
68
|
+
subs[p] = definition
|
|
69
|
+
|
|
70
|
+
if subs:
|
|
71
|
+
new_statements.append(self.substitute(stmt, subs))
|
|
72
|
+
else:
|
|
73
|
+
new_statements.append(stmt)
|
|
74
|
+
|
|
75
|
+
block.statements = new_statements[:-1]
|
|
76
|
+
block.test = new_statements[-1]
|
|
77
|
+
|
|
78
|
+
return entry
|
|
79
|
+
|
|
80
|
+
def substitute(self, stmt, subs):
|
|
81
|
+
match stmt:
|
|
82
|
+
case IRConst():
|
|
83
|
+
return stmt
|
|
84
|
+
case IRInstr(op=op, args=args):
|
|
85
|
+
return IRInstr(op=op, args=[self.substitute(arg, subs) for arg in args])
|
|
86
|
+
case IRPureInstr(op=op, args=args):
|
|
87
|
+
return IRPureInstr(op=op, args=[self.substitute(arg, subs) for arg in args])
|
|
88
|
+
case IRGet(place=place):
|
|
89
|
+
if place in subs:
|
|
90
|
+
return subs[place]
|
|
91
|
+
return stmt
|
|
92
|
+
case IRSet(place=place, value=value):
|
|
93
|
+
return IRSet(place=place, value=self.substitute(value, subs))
|
|
94
|
+
case _:
|
|
95
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
96
|
+
|
|
97
|
+
def count_uses(self, stmt, use_counts):
|
|
98
|
+
match stmt:
|
|
99
|
+
case IRConst():
|
|
100
|
+
pass
|
|
101
|
+
case IRInstr(op=_, args=args) | IRPureInstr(op=_, args=args):
|
|
102
|
+
for arg in args:
|
|
103
|
+
self.count_uses(arg, use_counts)
|
|
104
|
+
case IRGet(place=place):
|
|
105
|
+
self.count_uses(place, use_counts)
|
|
106
|
+
case IRSet(place=place, value=value):
|
|
107
|
+
if not isinstance(place, SSAPlace): # We don't want to count the definition itself
|
|
108
|
+
self.count_uses(place, use_counts)
|
|
109
|
+
self.count_uses(value, use_counts)
|
|
110
|
+
case SSAPlace():
|
|
111
|
+
use_counts[stmt] = use_counts.get(stmt, 0) + 1
|
|
112
|
+
case BlockPlace(block=block, index=index, offset=_):
|
|
113
|
+
self.count_uses(block, use_counts)
|
|
114
|
+
self.count_uses(index, use_counts)
|
|
115
|
+
case int() | float():
|
|
116
|
+
pass
|
|
117
|
+
case TempBlock():
|
|
118
|
+
pass
|
|
119
|
+
case _:
|
|
120
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
121
|
+
return use_counts
|
|
122
|
+
|
|
123
|
+
def get_inlinable_uses(self, stmt, uses):
|
|
124
|
+
match stmt:
|
|
125
|
+
case IRConst():
|
|
126
|
+
pass
|
|
127
|
+
case IRInstr(op=_, args=args) | IRPureInstr(op=_, args=args):
|
|
128
|
+
for arg in args:
|
|
129
|
+
self.get_inlinable_uses(arg, uses)
|
|
130
|
+
case IRGet(place=place):
|
|
131
|
+
if isinstance(place, SSAPlace):
|
|
132
|
+
uses.add(place)
|
|
133
|
+
case IRSet(place=_, value=value):
|
|
134
|
+
self.get_inlinable_uses(value, uses)
|
|
135
|
+
case _:
|
|
136
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
|
137
|
+
return uses
|
|
138
|
+
|
|
139
|
+
def is_inlinable(self, stmt):
|
|
140
|
+
match stmt:
|
|
141
|
+
case IRConst():
|
|
142
|
+
return True
|
|
143
|
+
case IRInstr(op=op, args=args) | IRPureInstr(op=op, args=args):
|
|
144
|
+
return not op.side_effects and op.pure and all(self.is_inlinable(arg) for arg in args)
|
|
145
|
+
case IRGet():
|
|
146
|
+
return isinstance(stmt.place, SSAPlace)
|
|
147
|
+
case IRSet():
|
|
148
|
+
return False
|
|
149
|
+
case _:
|
|
150
|
+
raise TypeError(f"Unexpected statement: {stmt}")
|
sonolus/backend/ir.py
CHANGED
|
@@ -6,9 +6,11 @@ type IRStmt = IRExpr | IRInstr | IRSet
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class IRConst:
|
|
9
|
-
value: float
|
|
9
|
+
value: float | int
|
|
10
10
|
|
|
11
11
|
def __init__(self, value: float):
|
|
12
|
+
if isinstance(value, bool):
|
|
13
|
+
value = int(value)
|
|
12
14
|
self.value = value
|
|
13
15
|
|
|
14
16
|
def __repr__(self):
|
|
@@ -70,9 +72,9 @@ class IRGet:
|
|
|
70
72
|
|
|
71
73
|
class IRSet:
|
|
72
74
|
place: Place
|
|
73
|
-
value:
|
|
75
|
+
value: IRExpr | IRInstr
|
|
74
76
|
|
|
75
|
-
def __init__(self, place: Place, value:
|
|
77
|
+
def __init__(self, place: Place, value: IRExpr | IRInstr):
|
|
76
78
|
self.place = place
|
|
77
79
|
self.value = value
|
|
78
80
|
|