flummi 0.1.0__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.
- flummi/IR/AST.py +111 -0
- flummi/IR/CFP.py +94 -0
- flummi/IR/__init__.py +0 -0
- flummi/IR/common.py +43 -0
- flummi/IR/pretty/__init__.py +0 -0
- flummi/IR/pretty/pretty.py +28 -0
- flummi/IR/pretty/render.py +68 -0
- flummi/__init__.py +3 -0
- flummi/cli.py +91 -0
- flummi/compiler/__init__.py +26 -0
- flummi/compiler/allocation.py +155 -0
- flummi/compiler/analysis.py +274 -0
- flummi/compiler/generation.py +573 -0
- flummi/compiler/lowering.py +334 -0
- flummi/compiler/names.py +17 -0
- flummi/compiler/parsing.py +275 -0
- flummi/compiler/solving.py +168 -0
- flummi/library/__init__.py +1 -0
- flummi/library/errors.py +58 -0
- flummi/library/graph.py +101 -0
- flummi/library/parser.py +275 -0
- flummi/library/sql.py +191 -0
- flummi/library/utils.py +77 -0
- flummi-0.1.0.dist-info/METADATA +15 -0
- flummi-0.1.0.dist-info/RECORD +28 -0
- flummi-0.1.0.dist-info/WHEEL +4 -0
- flummi-0.1.0.dist-info/entry_points.txt +3 -0
- flummi-0.1.0.dist-info/licenses/LICENSE +674 -0
flummi/IR/AST.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from .common import (
|
|
5
|
+
Expression,
|
|
6
|
+
Label,
|
|
7
|
+
Located,
|
|
8
|
+
Type,
|
|
9
|
+
Variable,
|
|
10
|
+
)
|
|
11
|
+
from .common import (
|
|
12
|
+
Program as BaseProgram,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"Assignment",
|
|
17
|
+
"Block",
|
|
18
|
+
"Break",
|
|
19
|
+
"Conditional",
|
|
20
|
+
"Continue",
|
|
21
|
+
"Declaration",
|
|
22
|
+
"Emit",
|
|
23
|
+
"Expression",
|
|
24
|
+
"Fork",
|
|
25
|
+
"Gather",
|
|
26
|
+
"Label",
|
|
27
|
+
"Loop",
|
|
28
|
+
"NoOp",
|
|
29
|
+
"Program",
|
|
30
|
+
"Statement",
|
|
31
|
+
"Stop",
|
|
32
|
+
"Sync",
|
|
33
|
+
"Type",
|
|
34
|
+
"Variable",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Statement(Located, ABC): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Declaration(Statement):
|
|
43
|
+
variable: Variable
|
|
44
|
+
type: Type
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Block(Statement):
|
|
49
|
+
statements: list[Statement]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Assignment(Statement):
|
|
54
|
+
variable: Variable
|
|
55
|
+
expression: Expression
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Emit(Statement):
|
|
60
|
+
variable: Variable
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Stop(Statement): ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class NoOp(Statement): ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Conditional(Statement):
|
|
73
|
+
condition: Variable
|
|
74
|
+
true_branch: Statement
|
|
75
|
+
false_branch: Statement
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class Loop(Statement):
|
|
80
|
+
label: Label
|
|
81
|
+
body: Statement
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Continue(Statement):
|
|
86
|
+
label: Label
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class Break(Statement):
|
|
91
|
+
label: Label
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class Fork(Statement):
|
|
96
|
+
variables: list[Variable]
|
|
97
|
+
expression: Expression
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class Gather(Statement):
|
|
102
|
+
aggregates: dict[Variable, Expression]
|
|
103
|
+
keys: list[Variable]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class Sync(Statement):
|
|
108
|
+
keys: list[Variable]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Program = BaseProgram[Statement]
|
flummi/IR/CFP.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from ..library import graph
|
|
5
|
+
from .common import Expression, Label, Located, Variable
|
|
6
|
+
from .common import Program as BaseProgram
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"Assignment",
|
|
10
|
+
"Emit",
|
|
11
|
+
"Expression",
|
|
12
|
+
"Fork",
|
|
13
|
+
"Gather",
|
|
14
|
+
"Jump",
|
|
15
|
+
"IsSynced",
|
|
16
|
+
"Label",
|
|
17
|
+
"Label",
|
|
18
|
+
"Merge",
|
|
19
|
+
"Plan",
|
|
20
|
+
"Primitive",
|
|
21
|
+
"Program",
|
|
22
|
+
"Start",
|
|
23
|
+
"Stop",
|
|
24
|
+
"Variable",
|
|
25
|
+
"Where",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Plan(Located):
|
|
31
|
+
entry_label: Label
|
|
32
|
+
primitives: dict[Label, Primitive]
|
|
33
|
+
successors_of: graph.Graph[Label]
|
|
34
|
+
virtual_successors_of: graph.Graph[Label]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Primitive(Located, ABC): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Start(Primitive): ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class Stop(Primitive): ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Assignment(Primitive):
|
|
51
|
+
variable: Variable
|
|
52
|
+
expression: Expression
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Emit(Primitive):
|
|
57
|
+
variable: Variable
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Where(Primitive):
|
|
62
|
+
condition: Variable
|
|
63
|
+
inverted: bool
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Merge(Primitive): ...
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class Jump(Primitive):
|
|
72
|
+
label: Label
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Fork(Primitive):
|
|
77
|
+
variables: list[Variable]
|
|
78
|
+
expression: Expression
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class Gather(Primitive):
|
|
83
|
+
aggregates: dict[Variable, Expression]
|
|
84
|
+
keys: list[Variable]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class IsSynced(Primitive):
|
|
89
|
+
variable: Variable
|
|
90
|
+
label: Label
|
|
91
|
+
keys: list[Variable]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Program = BaseProgram[Plan]
|
flummi/IR/__init__.py
ADDED
|
File without changes
|
flummi/IR/common.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from ..library.errors import Location
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
"Expression",
|
|
7
|
+
"Identifier",
|
|
8
|
+
"Label",
|
|
9
|
+
"Located",
|
|
10
|
+
"Program",
|
|
11
|
+
"Type",
|
|
12
|
+
"Variable",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(kw_only=True, match_args=False, slots=True)
|
|
17
|
+
class Located:
|
|
18
|
+
location: Location = field(hash=False, compare=False, repr=False)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(unsafe_hash=True, order=True, slots=True)
|
|
22
|
+
class Identifier(Located):
|
|
23
|
+
identifier: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Variable = Identifier
|
|
27
|
+
Label = Identifier
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class Expression(Located):
|
|
32
|
+
source: str
|
|
33
|
+
arguments: list[Variable]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(unsafe_hash=True, order=True, slots=True)
|
|
37
|
+
class Type(Located):
|
|
38
|
+
source: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class Program[B](Located):
|
|
43
|
+
body: B
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
2
|
+
|
|
3
|
+
from .. import CFP
|
|
4
|
+
from ...library import utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def pretty(primitive: CFP.Primitive) -> str:
|
|
8
|
+
match primitive:
|
|
9
|
+
case CFP.Emit(variable):
|
|
10
|
+
return f"EMIT {variable.identifier}"
|
|
11
|
+
|
|
12
|
+
case CFP.Assignment(variable, expression):
|
|
13
|
+
this = f"LET {variable.identifier} = "
|
|
14
|
+
this += utils.indent1(
|
|
15
|
+
dedent(
|
|
16
|
+
expression.source.format(
|
|
17
|
+
*(
|
|
18
|
+
f"{{{argument.identifier}}}"
|
|
19
|
+
for argument in expression.arguments
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
),
|
|
23
|
+
" " * len(this),
|
|
24
|
+
)
|
|
25
|
+
return this
|
|
26
|
+
|
|
27
|
+
case _:
|
|
28
|
+
return type(primitive).__name__.upper()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from .. import CFP
|
|
2
|
+
from .pretty import pretty
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ("render",)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PRORGAM_TEMPLATE = """\
|
|
9
|
+
digraph "program" {{
|
|
10
|
+
node [
|
|
11
|
+
shape = Mrecord,
|
|
12
|
+
nojustify = true,
|
|
13
|
+
fontname = "{font}",
|
|
14
|
+
];
|
|
15
|
+
graph [
|
|
16
|
+
fontname = "{font}",
|
|
17
|
+
];
|
|
18
|
+
edge [
|
|
19
|
+
fontname = "{font}",
|
|
20
|
+
];
|
|
21
|
+
{nodes}
|
|
22
|
+
{edges}
|
|
23
|
+
}}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
NODE_TEMPLATE = '"{label}" [label="{body}",{style}];'
|
|
27
|
+
|
|
28
|
+
NODE_STYLES: dict[type[CFP.Primitive], str] = {
|
|
29
|
+
CFP.Assignment: "style = filled, fillcolor = royalblue, color = white, fontcolor = white",
|
|
30
|
+
CFP.Emit: "style = filled, fillcolor = navy, color = white, fontcolor = white",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def render[A](graph: CFP.Plan, *, font: str = "monospace") -> str:
|
|
35
|
+
nodes = (
|
|
36
|
+
NODE_TEMPLATE.format(
|
|
37
|
+
label=label.identifier,
|
|
38
|
+
body=(
|
|
39
|
+
f"{{{
|
|
40
|
+
pretty(node)
|
|
41
|
+
.replace('\n', '\\l')
|
|
42
|
+
.replace('|', '\\|')
|
|
43
|
+
.replace(' ', '\\ ')
|
|
44
|
+
.replace('{', '\\{')
|
|
45
|
+
.replace('}', '\\}')
|
|
46
|
+
.replace('<', '\\<')
|
|
47
|
+
.replace('>', '\\>')
|
|
48
|
+
.replace('"', '\\"')
|
|
49
|
+
}\\l|{{{label.identifier}\\l|@{label.location.column}:{
|
|
50
|
+
label.location.line
|
|
51
|
+
}}}}}"
|
|
52
|
+
),
|
|
53
|
+
style=NODE_STYLES.get(type(node), ""),
|
|
54
|
+
)
|
|
55
|
+
for label, node in graph.primitives.items()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
edges: list[str] = []
|
|
59
|
+
|
|
60
|
+
edges.extend(
|
|
61
|
+
f'"{source.identifier}":s -> "{target.identifier}":n;'
|
|
62
|
+
for source, targets in graph.successors_of.items()
|
|
63
|
+
for target in targets
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return PRORGAM_TEMPLATE.format(
|
|
67
|
+
nodes="\n".join(nodes), edges="\n".join(edges), font=font
|
|
68
|
+
)
|
flummi/__init__.py
ADDED
flummi/cli.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from .compiler.allocation import allocate
|
|
9
|
+
from .compiler.analysis import analyze
|
|
10
|
+
from .compiler.generation import generate
|
|
11
|
+
from .compiler.lowering import lower
|
|
12
|
+
from .compiler.parsing import parse
|
|
13
|
+
from .compiler.solving import solve
|
|
14
|
+
from .IR.CFP import Plan
|
|
15
|
+
from .IR.pretty.render import render
|
|
16
|
+
from .library.errors import PrettyError
|
|
17
|
+
|
|
18
|
+
__all__ = ("cli",)
|
|
19
|
+
|
|
20
|
+
cli = typer.Typer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@cli.command("compile")
|
|
24
|
+
def compile(
|
|
25
|
+
input: Annotated[
|
|
26
|
+
Path,
|
|
27
|
+
typer.Argument(
|
|
28
|
+
dir_okay=False,
|
|
29
|
+
file_okay=True,
|
|
30
|
+
readable=True,
|
|
31
|
+
exists=True,
|
|
32
|
+
help="The file to compile.",
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
output: Annotated[
|
|
36
|
+
Path | None,
|
|
37
|
+
typer.Argument(
|
|
38
|
+
dir_okay=False,
|
|
39
|
+
file_okay=True,
|
|
40
|
+
writable=True,
|
|
41
|
+
help="The path to write the compiled query to.",
|
|
42
|
+
),
|
|
43
|
+
] = None,
|
|
44
|
+
graph: Annotated[
|
|
45
|
+
Path | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
dir_okay=False,
|
|
48
|
+
file_okay=True,
|
|
49
|
+
writable=True,
|
|
50
|
+
help="File to render CFP to.",
|
|
51
|
+
),
|
|
52
|
+
] = None,
|
|
53
|
+
dot: Annotated[
|
|
54
|
+
str, typer.Option(help="GraphVis dot-command to use for rendering.")
|
|
55
|
+
] = "dot",
|
|
56
|
+
):
|
|
57
|
+
with open(input, "r") as f:
|
|
58
|
+
source = f.read()
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
program = parse(source)
|
|
62
|
+
|
|
63
|
+
analysis = analyze(program)
|
|
64
|
+
|
|
65
|
+
lowered_program = lower(program)
|
|
66
|
+
|
|
67
|
+
if graph is not None:
|
|
68
|
+
render_to_file(lowered_program.body, graph, dot)
|
|
69
|
+
|
|
70
|
+
dataflow = solve(lowered_program, analysis)
|
|
71
|
+
|
|
72
|
+
allocation = allocate(lowered_program, analysis, dataflow)
|
|
73
|
+
|
|
74
|
+
sql = generate(lowered_program, analysis, dataflow, allocation)
|
|
75
|
+
|
|
76
|
+
except PrettyError as e:
|
|
77
|
+
print(e.format(source), file=sys.stderr)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
if output:
|
|
81
|
+
with open(output, "w+") as f:
|
|
82
|
+
_ = f.write(sql)
|
|
83
|
+
else:
|
|
84
|
+
print(sql)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def render_to_file(graph: Plan, path: Path, command: str = "dot"):
|
|
88
|
+
_ = subprocess.run(
|
|
89
|
+
args=[command, "-T", path.suffix[1:] or "png", "-o", path.absolute()],
|
|
90
|
+
input=render(graph).encode(),
|
|
91
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from ..library.sql import SQL
|
|
2
|
+
|
|
3
|
+
from .parsing import parse
|
|
4
|
+
from .analysis import analyze
|
|
5
|
+
from .lowering import lower
|
|
6
|
+
from .solving import solve
|
|
7
|
+
from .allocation import allocate
|
|
8
|
+
from .generation import generate
|
|
9
|
+
|
|
10
|
+
__all__ = ("compile",)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def compile(source: str) -> SQL:
|
|
14
|
+
program = parse(source)
|
|
15
|
+
|
|
16
|
+
analysis = analyze(program)
|
|
17
|
+
|
|
18
|
+
lowered_program = lower(program)
|
|
19
|
+
|
|
20
|
+
dataflow = solve(lowered_program, analysis)
|
|
21
|
+
|
|
22
|
+
allocation = allocate(lowered_program, analysis, dataflow)
|
|
23
|
+
|
|
24
|
+
sql = generate(lowered_program, analysis, dataflow, allocation)
|
|
25
|
+
|
|
26
|
+
return sql
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from ..IR.CFP import (
|
|
5
|
+
Label,
|
|
6
|
+
Program,
|
|
7
|
+
Start,
|
|
8
|
+
Variable,
|
|
9
|
+
)
|
|
10
|
+
from ..IR.common import Type
|
|
11
|
+
from ..library import utils
|
|
12
|
+
from .analysis import AnalysisResult
|
|
13
|
+
from .names import PROGRAM_VARIABLE, SystemVariable
|
|
14
|
+
from .solving import DataflowResult
|
|
15
|
+
|
|
16
|
+
__all__ = ("allocate",)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
type Column = str
|
|
20
|
+
type Schema = dict[Column, Type]
|
|
21
|
+
|
|
22
|
+
type PerLabel[T] = dict[Label, T]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class Allocation:
|
|
27
|
+
_variable_allocation: dict[Variable, Column]
|
|
28
|
+
_column_allocation: dict[Column, Variable] = field(init=False)
|
|
29
|
+
|
|
30
|
+
def __post_init__(self):
|
|
31
|
+
self._column_allocation = {
|
|
32
|
+
column: variable
|
|
33
|
+
for variable, column in self._variable_allocation.items()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def column_for(self, variable: Variable) -> Column | None:
|
|
37
|
+
return self._variable_allocation.get(variable)
|
|
38
|
+
|
|
39
|
+
def variable_at(self, column: Column) -> Variable | None:
|
|
40
|
+
return self._column_allocation.get(column)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class AllocationResult:
|
|
45
|
+
schema: Schema
|
|
46
|
+
at: PerLabel[Allocation]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def allocate(
|
|
50
|
+
program: Program,
|
|
51
|
+
analysis: AnalysisResult,
|
|
52
|
+
data_flow: DataflowResult,
|
|
53
|
+
naive: bool = True,
|
|
54
|
+
) -> AllocationResult:
|
|
55
|
+
labels_to_allocate: list[Label] = [
|
|
56
|
+
label
|
|
57
|
+
for label, primitive in program.body.primitives.items()
|
|
58
|
+
if isinstance(primitive, Start)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
SYSTEM_SCHEMA = {
|
|
62
|
+
variable.identifier: analysis.symbol_table[variable]
|
|
63
|
+
for variable in analysis.system_variables.values()
|
|
64
|
+
if variable.identifier != SystemVariable.CONTROL
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if naive:
|
|
68
|
+
schema = SYSTEM_SCHEMA
|
|
69
|
+
allocations = {}
|
|
70
|
+
for label in labels_to_allocate:
|
|
71
|
+
schema |= {
|
|
72
|
+
input.identifier: analysis.symbol_table[input]
|
|
73
|
+
for input in data_flow.inputs_of[label]
|
|
74
|
+
}
|
|
75
|
+
allocations[label] = Allocation(
|
|
76
|
+
{
|
|
77
|
+
analysis.system_variables[
|
|
78
|
+
SystemVariable.LABEL
|
|
79
|
+
]: SystemVariable.LABEL,
|
|
80
|
+
}
|
|
81
|
+
| {
|
|
82
|
+
input: input.identifier
|
|
83
|
+
for input in data_flow.inputs_of[label]
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return AllocationResult(schema, allocations)
|
|
88
|
+
|
|
89
|
+
# First, collect all "type siblings", i.e., groupings of like typed
|
|
90
|
+
# variables, at each label we wish to perform column allocation for.
|
|
91
|
+
# Since we are only looking at nodes containing start primitives, we
|
|
92
|
+
# compute these type siblings over the inputs of the nodes. In addition
|
|
93
|
+
# to the sets of type siblings themselves, we also collect the maximum
|
|
94
|
+
# cardinality of them by type.
|
|
95
|
+
|
|
96
|
+
type_siblings_at: PerLabel[dict[Type, list[Variable]]] = defaultdict(
|
|
97
|
+
lambda: defaultdict(list)
|
|
98
|
+
)
|
|
99
|
+
max_type_counts: dict[Type, int] = defaultdict(int)
|
|
100
|
+
|
|
101
|
+
for label in labels_to_allocate:
|
|
102
|
+
for type, variables in utils.groupby(
|
|
103
|
+
iter(
|
|
104
|
+
data_flow.inputs_of[label]
|
|
105
|
+
- set(analysis.system_variables.values())
|
|
106
|
+
),
|
|
107
|
+
lambda variable: analysis.symbol_table[variable],
|
|
108
|
+
):
|
|
109
|
+
variables = list(variables)
|
|
110
|
+
type_siblings_at[label][type] = variables
|
|
111
|
+
count = len(variables)
|
|
112
|
+
if count > max_type_counts[type]:
|
|
113
|
+
max_type_counts[type] = count
|
|
114
|
+
|
|
115
|
+
# Second, we compute the schema of the working table in two parts. The
|
|
116
|
+
# first part simply contains the system columns, e.g. the column for
|
|
117
|
+
# goto targets, the one for program results etc. The second part,
|
|
118
|
+
# contains the program columns, i.e., this set contains just enough
|
|
119
|
+
# columns of each type, such that all sets of type siblings computed
|
|
120
|
+
# above are covered.
|
|
121
|
+
|
|
122
|
+
schema: Schema = dict(SYSTEM_SCHEMA)
|
|
123
|
+
offset: int = 0
|
|
124
|
+
offset_of: dict[Type, int] = {}
|
|
125
|
+
for type, count in max_type_counts.items():
|
|
126
|
+
offset_of[type] = offset
|
|
127
|
+
schema.update(
|
|
128
|
+
{
|
|
129
|
+
PROGRAM_VARIABLE.format(idx=offset + i): type
|
|
130
|
+
for i in range(count)
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
offset += count
|
|
134
|
+
|
|
135
|
+
# Lastly, we compute the allocations for each label based on the schema
|
|
136
|
+
# we determined above. (The reverse mappings are generated automatically
|
|
137
|
+
# elsewhere, thus we can focus on simply computing on direction here.)
|
|
138
|
+
|
|
139
|
+
allocations: dict[Label, Allocation] = {
|
|
140
|
+
label: Allocation(
|
|
141
|
+
{
|
|
142
|
+
analysis.system_variables[
|
|
143
|
+
SystemVariable.LABEL
|
|
144
|
+
]: SystemVariable.LABEL,
|
|
145
|
+
}
|
|
146
|
+
| {
|
|
147
|
+
variable: PROGRAM_VARIABLE.format(idx=offset_of[type] + i)
|
|
148
|
+
for type, variables in type_siblings_at.get(label, {}).items()
|
|
149
|
+
for i, variable in enumerate(variables)
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
for label in labels_to_allocate
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return AllocationResult(schema, allocations)
|