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
@@ -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
@@ -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
- from sonolus.backend.ir import IRConst, IRExpr, IRStmt
6
-
7
-
8
- class FlowEdge:
9
- src: "BasicBlock"
10
- dst: "BasicBlock"
11
- cond: IRExpr | None
12
-
13
- def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: IRExpr | None = None):
14
- self.src = src
15
- self.dst = dst
16
- self.cond = cond
17
-
18
-
19
- class BasicBlock:
20
- statements: list[IRStmt]
21
- test: IRExpr
22
- incoming: set[FlowEdge]
23
- outgoing: set[FlowEdge]
24
-
25
- def __init__(
26
- self,
27
- statements: list[IRStmt] | None = None,
28
- test: IRExpr | None = None,
29
- incoming: set[FlowEdge] | None = None,
30
- outgoing: set[FlowEdge] | None = None,
31
- ):
32
- self.statements = statements or []
33
- self.test = test or IRConst(1)
34
- self.incoming = incoming or set()
35
- self.outgoing = outgoing or set()
36
-
37
- def connect_to(self, other: "BasicBlock", cond: IRExpr | None = None):
38
- edge = FlowEdge(self, other, cond)
39
- self.outgoing.add(edge)
40
- other.incoming.add(edge)
41
-
42
-
43
- def traverse_cfg_preorder(block: BasicBlock) -> Iterator[BasicBlock]:
44
- visited = set()
45
- queue = deque([block])
46
- while queue:
47
- block = queue.popleft()
48
- if block in visited:
49
- continue
50
- visited.add(block)
51
- yield block
52
- for edge in sorted(block.outgoing, key=lambda e: (e.cond is None, e.cond)):
53
- queue.append(edge.dst)
54
-
55
-
56
- def cfg_to_mermaid(entry: BasicBlock):
57
- def pre(s: str):
58
- return "\"<pre style='text-align: left;'>" + s.replace("\n", "<br/>") + '</pre>"'
59
-
60
- def fmt(nodes):
61
- if nodes:
62
- return "\n".join(str(n) for n in nodes)
63
- else:
64
- return "{}"
65
-
66
- block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
67
-
68
- lines = ["Entry([Entry]) --> 0"]
69
- for block in traverse_cfg_preorder(entry):
70
- index = block_indexes[block]
71
- lines.append(f"{index}[{pre(fmt([f'#{index}', *block.statements]))}]")
72
-
73
- outgoing = {edge.cond: edge.dst for edge in block.outgoing}
74
- match outgoing:
75
- case {**other} if not other:
76
- lines.append(f"{index} --> Exit")
77
- case {None: target, **other} if not other:
78
- lines.append(f"{index} --> {block_indexes[target]}")
79
- case {0: f_branch, None: t_branch, **other} if not other:
80
- lines.append(f"{index}_{{{pre(fmt([block.test]))}}}")
81
- lines.append(f"{index} --> {index}_")
82
- lines.append(f"{index}_ --> |true| {block_indexes[t_branch]}")
83
- lines.append(f"{index}_ --> |false| {block_indexes[f_branch]}")
84
- case dict() as tgt:
85
- lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
86
- lines.append(f"{index} --> {index}_")
87
- for cond, target in tgt.items():
88
- lines.append(f"{index}_ --> |{pre(fmt([cond or "default"]))}| {block_indexes[target]}")
89
- lines.append("Exit([Exit])")
90
-
91
- body = textwrap.indent("\n".join(lines), " ")
92
- return f"graph\n{body}"
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: IRStmt
75
+ value: IRExpr | IRInstr
74
76
 
75
- def __init__(self, place: Place, value: IRStmt):
77
+ def __init__(self, place: Place, value: IRExpr | IRInstr):
76
78
  self.place = place
77
79
  self.value = value
78
80