monogate-forge 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.
- lang/loader/__init__.py +26 -0
- lang/loader/resolver.py +288 -0
- lang/optimizer/__init__.py +111 -0
- lang/optimizer/constant_folding.py +301 -0
- lang/optimizer/cse.py +249 -0
- lang/optimizer/fusion.py +18 -0
- lang/optimizer/inliner.py +176 -0
- lang/optimizer/ml_routing.py +182 -0
- lang/optimizer/superbest.py +198 -0
- lang/optimizer/tree_shaker.py +88 -0
- lang/parser/__init__.py +53 -0
- lang/parser/ast_nodes.py +232 -0
- lang/parser/errors.py +37 -0
- lang/parser/lexer.py +195 -0
- lang/parser/parser.py +706 -0
- lang/parser/type_checker.py +71 -0
- lang/profiler/__init__.py +14 -0
- lang/profiler/ast_to_sympy.py +254 -0
- lang/profiler/dynamics.py +36 -0
- lang/profiler/eml_interpreter.py +254 -0
- lang/profiler/profiler.py +218 -0
- lang/profiler/sympy_to_ast.py +152 -0
- lang/spec/EML_LANG_DESIGN.md +698 -0
- lang/spec/SPEC.md +213 -0
- lang/spec/__init__.py +0 -0
- lang/spec/grammar/__init__.py +0 -0
- lang/spec/grammar/eml_lang.g4 +123 -0
- lang/spec/grammar/examples/arrhenius.eml +12 -0
- lang/spec/grammar/examples/bessel_fm.eml +18 -0
- lang/spec/grammar/examples/hello.eml +9 -0
- lang/spec/grammar/examples/kalman.eml +19 -0
- lang/spec/grammar/examples/motor_control.eml +84 -0
- lang/spec/grammar/examples/motor_foc.eml +16 -0
- lang/spec/grammar/examples/orbit.eml +23 -0
- lang/spec/grammar/examples/pid_basic.eml +13 -0
- lang/spec/grammar/examples/pid_nonlinear.eml +22 -0
- lang/spec/grammar/examples/sigmoid.eml +18 -0
- lang/spec/grammar/examples/trajectory.eml +16 -0
- lang/spec/grammar/lexer_rules.g4 +58 -0
- lang/spec/stdlib/STDLIB.md +127 -0
- lang/spec/stdlib/__init__.py +0 -0
- lang/spec/stdlib/constants.eml +23 -0
- lang/spec/stdlib/control.eml +134 -0
- lang/spec/stdlib/linalg.eml +135 -0
- lang/spec/stdlib/math.eml +149 -0
- lang/spec/stdlib/ml.eml +143 -0
- lang/spec/stdlib/signal.eml +118 -0
- lang/spec/types/TYPES.md +58 -0
- lang/spec/types/__init__.py +0 -0
- lang/spec/types/chain_order_types.md +91 -0
- lang/spec/types/domain_types.md +33 -0
- lang/spec/types/precision_types.md +48 -0
- monogate_forge-0.1.0.dist-info/METADATA +224 -0
- monogate_forge-0.1.0.dist-info/RECORD +95 -0
- monogate_forge-0.1.0.dist-info/WHEEL +5 -0
- monogate_forge-0.1.0.dist-info/entry_points.txt +3 -0
- monogate_forge-0.1.0.dist-info/licenses/LICENSE +32 -0
- monogate_forge-0.1.0.dist-info/top_level.txt +3 -0
- software/backends/__init__.py +14 -0
- software/backends/c_backend.py +422 -0
- software/backends/cpp_backend.py +405 -0
- software/backends/go_backend.py +370 -0
- software/backends/java_backend.py +399 -0
- software/backends/kotlin_backend.py +441 -0
- software/backends/matlab_backend.py +386 -0
- software/backends/python_backend.py +307 -0
- software/backends/rust_backend.py +379 -0
- software/verification/__init__.py +0 -0
- software/verification/lean/LeanBackend.py +548 -0
- tools/benchmarks/__init__.py +29 -0
- tools/benchmarks/dashboard.py +148 -0
- tools/benchmarks/regen_baselines.py +100 -0
- tools/benchmarks/snapshot.py +137 -0
- tools/cli/audit.py +449 -0
- tools/cli/explain.py +510 -0
- tools/cli/init_cmd.py +162 -0
- tools/cli/live_counts.py +389 -0
- tools/cli/main.py +1190 -0
- tools/cli/manpage.py +189 -0
- tools/equivalence/__init__.py +23 -0
- tools/equivalence/c_runner.py +217 -0
- tools/equivalence/harness.py +288 -0
- tools/equivalence/lean_runner.py +246 -0
- tools/equivalence/python_runner.py +177 -0
- tools/equivalence/rust_runner.py +314 -0
- tools/fmt/__init__.py +28 -0
- tools/fmt/formatter.py +364 -0
- tools/forge_graph.py +464 -0
- tools/ide/lsp/__init__.py +7 -0
- tools/ide/lsp/server.py +845 -0
- tools/ide/lsp/workspace.py +251 -0
- tools/license/__init__.py +26 -0
- tools/license/issuer.py +87 -0
- tools/license/verifier.py +187 -0
- tools/scripts/auto_prove.py +568 -0
lang/loader/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Module loader for `use` declarations.
|
|
2
|
+
|
|
3
|
+
Resolves `use <root>::<name>(...);` paths to `.eml` source files
|
|
4
|
+
on disk, parses them once, and caches the results so a function
|
|
5
|
+
imported by ten different modules is parsed only once per
|
|
6
|
+
process.
|
|
7
|
+
|
|
8
|
+
Search-path table:
|
|
9
|
+
|
|
10
|
+
stdlib -> lang/spec/stdlib/<name>.eml
|
|
11
|
+
|
|
12
|
+
(More roots will land as the import system grows -- e.g.
|
|
13
|
+
`local::X` for sibling files relative to the importing file.)
|
|
14
|
+
|
|
15
|
+
Cycles are detected by tracking the in-progress load set; when a
|
|
16
|
+
module already on that set is requested again, ImportError is
|
|
17
|
+
raised with the cycle path.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from lang.loader.resolver import (
|
|
21
|
+
LoaderError,
|
|
22
|
+
ModuleLoader,
|
|
23
|
+
resolve_imports,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = ["LoaderError", "ModuleLoader", "resolve_imports"]
|
lang/loader/resolver.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Import resolver -- finds, parses, caches, and merges modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from lang.parser.ast_nodes import (
|
|
10
|
+
EMLConstant,
|
|
11
|
+
EMLFunction,
|
|
12
|
+
EMLImport,
|
|
13
|
+
EMLModule,
|
|
14
|
+
EMLTypeAlias,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Repo root -- two levels up from this file (lang/loader/resolver.py).
|
|
19
|
+
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Search-path table mapping the FIRST path segment to a directory.
|
|
23
|
+
# When `use stdlib::math;` is parsed, the loader looks for
|
|
24
|
+
# `<DEFAULT_SEARCH_PATHS["stdlib"]>/math.eml`.
|
|
25
|
+
#
|
|
26
|
+
# The reserved root `"local"` does NOT appear here -- it resolves
|
|
27
|
+
# relative to the importing file's directory at load time.
|
|
28
|
+
DEFAULT_SEARCH_PATHS: dict[str, Path] = {
|
|
29
|
+
"stdlib": _REPO_ROOT / "lang" / "spec" / "stdlib",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
LOCAL_ROOT = "local"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LoaderError(Exception):
|
|
36
|
+
"""Loader-level error: not-found, cycle, or symbol clash."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ModuleLoader:
|
|
41
|
+
"""Resolves + parses + caches imports.
|
|
42
|
+
|
|
43
|
+
Construct one per parse session if you want test isolation;
|
|
44
|
+
construct one and reuse it across many parse calls if you
|
|
45
|
+
want maximum cache hit rate.
|
|
46
|
+
|
|
47
|
+
Cache keys are RESOLVED file-paths (strings), not joined
|
|
48
|
+
import paths -- so `local::helpers` from `/a/x.eml` and
|
|
49
|
+
`/b/x.eml` correctly cache as two different modules even
|
|
50
|
+
though they share a joined-path spelling."""
|
|
51
|
+
search_paths: dict[str, Path] = field(
|
|
52
|
+
default_factory=lambda: dict(DEFAULT_SEARCH_PATHS),
|
|
53
|
+
)
|
|
54
|
+
cache: dict[str, EMLModule] = field(default_factory=dict)
|
|
55
|
+
_in_progress: set[str] = field(default_factory=set)
|
|
56
|
+
|
|
57
|
+
def load(
|
|
58
|
+
self,
|
|
59
|
+
joined_path: str,
|
|
60
|
+
*,
|
|
61
|
+
source_dir: Path | None = None,
|
|
62
|
+
) -> EMLModule:
|
|
63
|
+
"""Load the module identified by `joined_path` (e.g.
|
|
64
|
+
'stdlib::math' or 'local::helpers').
|
|
65
|
+
|
|
66
|
+
`source_dir` is the directory of the file doing the import.
|
|
67
|
+
Required when `joined_path` starts with `local::`; ignored
|
|
68
|
+
otherwise.
|
|
69
|
+
|
|
70
|
+
Raises LoaderError on:
|
|
71
|
+
- unknown root segment (not in search_paths and not 'local')
|
|
72
|
+
- missing `local::` source_dir
|
|
73
|
+
- missing .eml file
|
|
74
|
+
- import cycle (resolved file path already in progress)
|
|
75
|
+
"""
|
|
76
|
+
file_path = self.resolve(joined_path, source_dir=source_dir)
|
|
77
|
+
cache_key = str(file_path.resolve())
|
|
78
|
+
|
|
79
|
+
if cache_key in self.cache:
|
|
80
|
+
return self.cache[cache_key]
|
|
81
|
+
|
|
82
|
+
if cache_key in self._in_progress:
|
|
83
|
+
raise LoaderError(
|
|
84
|
+
f"import cycle while loading {joined_path!r} "
|
|
85
|
+
f"({file_path}) -- in progress: "
|
|
86
|
+
f"{sorted(self._in_progress)}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not file_path.is_file():
|
|
90
|
+
raise LoaderError(
|
|
91
|
+
f"module {joined_path!r} not found "
|
|
92
|
+
f"(looked for {file_path})"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Local import to avoid a parser <-> loader cycle.
|
|
96
|
+
from lang.parser.parser import parse_source
|
|
97
|
+
|
|
98
|
+
self._in_progress.add(cache_key)
|
|
99
|
+
try:
|
|
100
|
+
text = file_path.read_text(encoding="utf-8")
|
|
101
|
+
mod = parse_source(text, source_file=str(file_path))
|
|
102
|
+
# Recursively resolve transitive imports BEFORE caching.
|
|
103
|
+
mod = resolve_imports(mod, loader=self)
|
|
104
|
+
self.cache[cache_key] = mod
|
|
105
|
+
return mod
|
|
106
|
+
finally:
|
|
107
|
+
self._in_progress.discard(cache_key)
|
|
108
|
+
|
|
109
|
+
def resolve(
|
|
110
|
+
self,
|
|
111
|
+
joined_path: str,
|
|
112
|
+
*,
|
|
113
|
+
source_dir: Path | None = None,
|
|
114
|
+
) -> Path:
|
|
115
|
+
"""Translate `<root>::<name>(::<sub>...)` into a file path
|
|
116
|
+
without loading.
|
|
117
|
+
|
|
118
|
+
`local::name` resolves against `source_dir`; every other
|
|
119
|
+
root resolves against `self.search_paths`."""
|
|
120
|
+
parts = joined_path.split("::")
|
|
121
|
+
if len(parts) < 2:
|
|
122
|
+
raise LoaderError(
|
|
123
|
+
f"path {joined_path!r} must have at least 2 segments"
|
|
124
|
+
)
|
|
125
|
+
root, *rest = parts
|
|
126
|
+
|
|
127
|
+
if root == LOCAL_ROOT:
|
|
128
|
+
if source_dir is None:
|
|
129
|
+
raise LoaderError(
|
|
130
|
+
f"`use local::{ '::'.join(rest) };` requires a "
|
|
131
|
+
f"source-file directory but none was supplied "
|
|
132
|
+
f"(use parse_file or pass source_dir=)"
|
|
133
|
+
)
|
|
134
|
+
base = source_dir
|
|
135
|
+
else:
|
|
136
|
+
if root not in self.search_paths:
|
|
137
|
+
raise LoaderError(
|
|
138
|
+
f"unknown import root {root!r} "
|
|
139
|
+
f"(known: {sorted(self.search_paths) + [LOCAL_ROOT]})"
|
|
140
|
+
)
|
|
141
|
+
base = self.search_paths[root]
|
|
142
|
+
|
|
143
|
+
# rest = ["math"] -> math.eml
|
|
144
|
+
# rest = ["foo", "bar"] -> foo/bar.eml
|
|
145
|
+
rel = Path(*rest[:-1]) / f"{rest[-1]}.eml"
|
|
146
|
+
return base / rel
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def resolve_imports(
|
|
150
|
+
mod: EMLModule,
|
|
151
|
+
*,
|
|
152
|
+
loader: ModuleLoader | None = None,
|
|
153
|
+
) -> EMLModule:
|
|
154
|
+
"""Return a new EMLModule with every `use ...;` import resolved
|
|
155
|
+
and its constants/types/functions merged into `mod`'s namespace.
|
|
156
|
+
|
|
157
|
+
`local::` imports are resolved against the directory of
|
|
158
|
+
`mod.source_file`; if that is "<unknown>" or "<string>"
|
|
159
|
+
(parsed from a string, not a file), local imports raise
|
|
160
|
+
LoaderError -- callers wanting local imports must set the
|
|
161
|
+
EMLModule.source_file before calling.
|
|
162
|
+
|
|
163
|
+
Conflicts (an imported name collides with a local one, or two
|
|
164
|
+
imports both define the same name) raise LoaderError.
|
|
165
|
+
"""
|
|
166
|
+
if not mod.imports:
|
|
167
|
+
return mod
|
|
168
|
+
|
|
169
|
+
if loader is None:
|
|
170
|
+
loader = ModuleLoader()
|
|
171
|
+
|
|
172
|
+
# Compute the importing file's directory once so every
|
|
173
|
+
# local::... lookup uses the same base.
|
|
174
|
+
source_dir: Path | None = None
|
|
175
|
+
src = mod.source_file
|
|
176
|
+
if src and not src.startswith("<"):
|
|
177
|
+
source_dir = Path(src).resolve().parent
|
|
178
|
+
|
|
179
|
+
out = deepcopy(mod)
|
|
180
|
+
|
|
181
|
+
# Build name tables seeded with the local module's own decls.
|
|
182
|
+
# Imports CANNOT shadow a locally-defined name; the local
|
|
183
|
+
# definition wins and the import is rejected as a clash so the
|
|
184
|
+
# author sees the conflict explicitly.
|
|
185
|
+
const_names = {c.name for c in out.constants}
|
|
186
|
+
type_names = {t.name for t in out.types}
|
|
187
|
+
func_names = {f.name for f in out.functions}
|
|
188
|
+
|
|
189
|
+
# Track which import provided each name so error messages can
|
|
190
|
+
# point at the offending source.
|
|
191
|
+
provided_by: dict[str, str] = {}
|
|
192
|
+
|
|
193
|
+
for imp in mod.imports:
|
|
194
|
+
sub = loader.load(imp.joined, source_dir=source_dir)
|
|
195
|
+
|
|
196
|
+
# Selective-import filter. When `imp.only` is set, only
|
|
197
|
+
# names in that allowlist are merged. Names outside it are
|
|
198
|
+
# NOT brought into the importing module's namespace; they
|
|
199
|
+
# remain in the imported sub-module and the tree-shaker
|
|
200
|
+
# later sees them as unreached.
|
|
201
|
+
wanted = set(imp.only) if imp.only is not None else None
|
|
202
|
+
|
|
203
|
+
# Alias map (None -> empty dict). Key is the original
|
|
204
|
+
# name in the imported module; value is what it should be
|
|
205
|
+
# called in the importing module's namespace.
|
|
206
|
+
alias_map: dict[str, str] = imp.aliases or {}
|
|
207
|
+
|
|
208
|
+
# Validate the selective-import allowlist against what the
|
|
209
|
+
# imported module actually exports -- a typo in the user's
|
|
210
|
+
# `use ::{misspelled}` should surface as a clear error
|
|
211
|
+
# rather than silently importing nothing.
|
|
212
|
+
if wanted is not None:
|
|
213
|
+
exported_names = (
|
|
214
|
+
{c.name for c in sub.constants}
|
|
215
|
+
| {t.name for t in sub.types}
|
|
216
|
+
| {f.name for f in sub.functions}
|
|
217
|
+
)
|
|
218
|
+
unknown = sorted(wanted - exported_names)
|
|
219
|
+
if unknown:
|
|
220
|
+
raise LoaderError(
|
|
221
|
+
f"`use {imp.joined}::{{...}};` requested name(s) "
|
|
222
|
+
f"{unknown} not exported by {imp.joined!r} "
|
|
223
|
+
f"(available: {sorted(exported_names)[:20]})"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
for c in sub.constants:
|
|
227
|
+
if wanted is not None and c.name not in wanted:
|
|
228
|
+
continue
|
|
229
|
+
local_name = alias_map.get(c.name, c.name)
|
|
230
|
+
_check_clash(local_name, "constant", imp, provided_by,
|
|
231
|
+
local_names=const_names)
|
|
232
|
+
new_c = deepcopy(c)
|
|
233
|
+
new_c.name = local_name
|
|
234
|
+
out.constants.append(new_c)
|
|
235
|
+
const_names.add(local_name)
|
|
236
|
+
provided_by[local_name] = imp.joined
|
|
237
|
+
|
|
238
|
+
for t in sub.types:
|
|
239
|
+
if wanted is not None and t.name not in wanted:
|
|
240
|
+
continue
|
|
241
|
+
local_name = alias_map.get(t.name, t.name)
|
|
242
|
+
_check_clash(local_name, "type alias", imp, provided_by,
|
|
243
|
+
local_names=type_names)
|
|
244
|
+
new_t = deepcopy(t)
|
|
245
|
+
new_t.name = local_name
|
|
246
|
+
out.types.append(new_t)
|
|
247
|
+
type_names.add(local_name)
|
|
248
|
+
provided_by[local_name] = imp.joined
|
|
249
|
+
|
|
250
|
+
for f in sub.functions:
|
|
251
|
+
if wanted is not None and f.name not in wanted:
|
|
252
|
+
continue
|
|
253
|
+
local_name = alias_map.get(f.name, f.name)
|
|
254
|
+
_check_clash(local_name, "function", imp, provided_by,
|
|
255
|
+
local_names=func_names)
|
|
256
|
+
new_fn = deepcopy(f)
|
|
257
|
+
new_fn.name = local_name
|
|
258
|
+
# Tag for the tree-shaker: this function arrived via
|
|
259
|
+
# `use ...;` and is droppable if no local function
|
|
260
|
+
# ends up calling it.
|
|
261
|
+
new_fn.imported_from = imp.joined
|
|
262
|
+
out.functions.append(new_fn)
|
|
263
|
+
func_names.add(local_name)
|
|
264
|
+
provided_by[local_name] = imp.joined
|
|
265
|
+
|
|
266
|
+
return out
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _check_clash(
|
|
270
|
+
name: str, kind: str, imp: EMLImport,
|
|
271
|
+
provided_by: dict[str, str],
|
|
272
|
+
*,
|
|
273
|
+
local_names: set[str],
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Raise if `name` is already in scope, distinguishing local
|
|
276
|
+
redefinitions from import conflicts in the message."""
|
|
277
|
+
if name in local_names:
|
|
278
|
+
# Was it locally-defined or imported earlier?
|
|
279
|
+
if name in provided_by:
|
|
280
|
+
raise LoaderError(
|
|
281
|
+
f"`use {imp.joined};` brings in {kind} {name!r}, "
|
|
282
|
+
f"but it was already imported via "
|
|
283
|
+
f"`{provided_by[name]}`",
|
|
284
|
+
)
|
|
285
|
+
raise LoaderError(
|
|
286
|
+
f"`use {imp.joined};` brings in {kind} {name!r}, "
|
|
287
|
+
f"but the importing module already defines that name",
|
|
288
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""SuperBEST routing + fusion + CSE + constant folding.
|
|
2
|
+
|
|
3
|
+
The optimizer is the DEFAULT (not opt-in). Every function passes
|
|
4
|
+
through it before any backend emits code, so all backends share
|
|
5
|
+
the same optimal node count + the same per-node operator choice.
|
|
6
|
+
|
|
7
|
+
Public entries:
|
|
8
|
+
|
|
9
|
+
optimize_module(mod) Run the full pass sequence over every
|
|
10
|
+
function in `mod`. Mutates the module
|
|
11
|
+
in place and returns it for chaining.
|
|
12
|
+
optimize_function(fn) Same, single function.
|
|
13
|
+
fold_constants(node) Just the constant-folding pass.
|
|
14
|
+
apply_cse(fn) Just the CSE pass.
|
|
15
|
+
route_superbest(node) Just SuperBEST routing (today: identity).
|
|
16
|
+
|
|
17
|
+
Default pass sequence (in order):
|
|
18
|
+
|
|
19
|
+
1. constant_folding -- fold pure-literal sub-trees first so
|
|
20
|
+
downstream passes see a smaller tree.
|
|
21
|
+
2. cse -- hoist remaining duplicates into lets.
|
|
22
|
+
3. superbest -- per-node operator-family selection
|
|
23
|
+
(placeholder; wired in for Phase 2.1).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from copy import deepcopy
|
|
27
|
+
|
|
28
|
+
from lang.optimizer.constant_folding import fold_constants, fold_in_place
|
|
29
|
+
from lang.optimizer.cse import apply_cse, apply_cse_module
|
|
30
|
+
from lang.optimizer.inliner import inline_calls
|
|
31
|
+
from lang.optimizer.ml_routing import route_ml_activations_module
|
|
32
|
+
from lang.optimizer.superbest import (
|
|
33
|
+
route_superbest,
|
|
34
|
+
superbest_function,
|
|
35
|
+
superbest_module,
|
|
36
|
+
)
|
|
37
|
+
from lang.optimizer.tree_shaker import shake_imports
|
|
38
|
+
from lang.parser.ast_nodes import EMLFunction, EMLModule, NodeKind
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def optimize_function(fn: EMLFunction) -> EMLFunction:
|
|
42
|
+
"""Run the per-function pass sequence (constant_folding + CSE +
|
|
43
|
+
superbest). Returns a new function -- input is not mutated.
|
|
44
|
+
|
|
45
|
+
NOTE: the call-inliner is a MODULE-level pass (it needs to
|
|
46
|
+
look up callees in the module's function table) so it lives
|
|
47
|
+
in `optimize_module`, not here. Calling `optimize_function`
|
|
48
|
+
directly skips inlining."""
|
|
49
|
+
if fn.body is None:
|
|
50
|
+
return fn
|
|
51
|
+
|
|
52
|
+
out = deepcopy(fn)
|
|
53
|
+
|
|
54
|
+
# Pass 1: constant folding (in-place on the deep copy).
|
|
55
|
+
if out.body is not None:
|
|
56
|
+
out.body = fold_in_place(out.body)
|
|
57
|
+
# Pass 2: CSE -- operates on the whole function.
|
|
58
|
+
out = apply_cse(out)
|
|
59
|
+
# Pass 3: superbest (no-op today).
|
|
60
|
+
if out.body is not None:
|
|
61
|
+
out.body = route_superbest(out.body)
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def optimize_module(mod: EMLModule, *,
|
|
66
|
+
ml_routing: bool = False) -> EMLModule:
|
|
67
|
+
"""Run the default pass sequence on every function in `mod`.
|
|
68
|
+
Returns a new module; the input is not mutated.
|
|
69
|
+
|
|
70
|
+
Pass order:
|
|
71
|
+
0. inline_calls -- substitute eligible same-module
|
|
72
|
+
CALLs with the callee's body
|
|
73
|
+
1. constant_folding -- fold pure-literal sub-trees
|
|
74
|
+
(incl. ones exposed by inlining)
|
|
75
|
+
2. cse -- hoist remaining duplicates
|
|
76
|
+
3. superbest -- per-node operator-family selection
|
|
77
|
+
3.5 ml_routing -- (opt-in) pattern-rewrite sigmoid /
|
|
78
|
+
softplus to libmonogate runtime calls
|
|
79
|
+
for HIGH-drift functions
|
|
80
|
+
4. shake_imports -- drop unused imports
|
|
81
|
+
|
|
82
|
+
`ml_routing` defaults to False because the pass emits CALL
|
|
83
|
+
nodes targeting libmonogate runtime symbols, which only the
|
|
84
|
+
C and Rust backends know how to resolve. Enable when
|
|
85
|
+
targeting C / Rust on a known-drifty workload.
|
|
86
|
+
"""
|
|
87
|
+
# Pass 0: module-level inliner.
|
|
88
|
+
out = inline_calls(mod)
|
|
89
|
+
# Passes 1 -> 2: per-function (constant_folding + CSE).
|
|
90
|
+
out.functions = [optimize_function(fn) for fn in out.functions]
|
|
91
|
+
# Pass 3: module-level SuperBEST routing -- needs SymPy
|
|
92
|
+
# bridge access so it lives outside optimize_function.
|
|
93
|
+
out = superbest_module(out)
|
|
94
|
+
# Pass 3.5: opt-in ML pattern rewriter (libmonogate runtime).
|
|
95
|
+
if ml_routing:
|
|
96
|
+
out = route_ml_activations_module(out)
|
|
97
|
+
# Pass 4: drop unused imports (after inlining so reachable set
|
|
98
|
+
# reflects the post-inline call graph).
|
|
99
|
+
out = shake_imports(out)
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = [
|
|
104
|
+
"optimize_function",
|
|
105
|
+
"optimize_module",
|
|
106
|
+
"fold_constants",
|
|
107
|
+
"apply_cse",
|
|
108
|
+
"apply_cse_module",
|
|
109
|
+
"route_superbest",
|
|
110
|
+
"route_ml_activations_module",
|
|
111
|
+
]
|