opast 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.
- opast/__init__.py +60 -0
- opast/__main__.py +136 -0
- opast/analysis.py +608 -0
- opast/bench/__init__.py +12 -0
- opast/bench/__main__.py +162 -0
- opast/bench/workloads/__init__.py +1 -0
- opast/bench/workloads/algebra.py +16 -0
- opast/bench/workloads/comptomap.py +14 -0
- opast/bench/workloads/control.py +14 -0
- opast/bench/workloads/daily.py +78 -0
- opast/bench/workloads/dedynamize.py +12 -0
- opast/bench/workloads/inline.py +23 -0
- opast/bench/workloads/inlinestmt.py +22 -0
- opast/bench/workloads/jitlazy.py +20 -0
- opast/bench/workloads/lencache.py +19 -0
- opast/bench/workloads/licm.py +22 -0
- opast/bench/workloads/localize.py +28 -0
- opast/bench/workloads/loopfold.py +25 -0
- opast/bench/workloads/looptocomp.py +17 -0
- opast/bench/workloads/mixed.py +25 -0
- opast/bench/workloads/rangeiter.py +17 -0
- opast/bench/workloads/strength.py +15 -0
- opast/importhook.py +198 -0
- opast/jitsupport.py +372 -0
- opast/magic.py +157 -0
- opast/passes/__init__.py +35 -0
- opast/passes/algebra.py +222 -0
- opast/passes/base.py +40 -0
- opast/passes/comprehension.py +281 -0
- opast/passes/cse.py +281 -0
- opast/passes/deadcode.py +167 -0
- opast/passes/dedynamize.py +266 -0
- opast/passes/folding.py +227 -0
- opast/passes/inline.py +454 -0
- opast/passes/jit.py +929 -0
- opast/passes/licm.py +253 -0
- opast/passes/localize.py +263 -0
- opast/passes/loopfold.py +358 -0
- opast/passes/looptocomp.py +274 -0
- opast/passes/propagate.py +491 -0
- opast/passes/rangeiter.py +269 -0
- opast/passes/unused.py +221 -0
- opast/pipeline.py +198 -0
- opast/runner.py +125 -0
- opast/safety.py +140 -0
- opast-0.1.0.dist-info/METADATA +167 -0
- opast-0.1.0.dist-info/RECORD +51 -0
- opast-0.1.0.dist-info/WHEEL +5 -0
- opast-0.1.0.dist-info/entry_points.txt +2 -0
- opast-0.1.0.dist-info/licenses/LICENSE +21 -0
- opast-0.1.0.dist-info/top_level.txt +1 -0
opast/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""opast -- AST-based Python source optimizer.
|
|
2
|
+
|
|
3
|
+
Optimizations: constant folding, algebraic simplification, dead code
|
|
4
|
+
elimination and simple function inlining. Scopes containing dynamic
|
|
5
|
+
constructs (eval/exec/globals/locals/vars/compile/__import__/``import *`` ...)
|
|
6
|
+
are conservatively skipped so optimization never changes their behaviour.
|
|
7
|
+
|
|
8
|
+
Typical use::
|
|
9
|
+
|
|
10
|
+
from opast import optimize_file, run_path
|
|
11
|
+
|
|
12
|
+
result = optimize_file("script.py")
|
|
13
|
+
print(result.source) # optimized source
|
|
14
|
+
print(result.report.summary()) # what happened
|
|
15
|
+
|
|
16
|
+
run_path("script.py", argv=("--flag",)) # optimize then execute
|
|
17
|
+
|
|
18
|
+
Every entry point accepts ``disable`` -- an iterable or comma-separated
|
|
19
|
+
string of pass names (see :data:`PASS_NAMES`, plus ``"jit"``) to skip::
|
|
20
|
+
|
|
21
|
+
optimize_file("script.py", disable="inline,licm")
|
|
22
|
+
run_source("print(1+1)", disable=("constant-folding",))
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .pipeline import (
|
|
26
|
+
DEFAULT_MAX_ITERATIONS,
|
|
27
|
+
PASS_NAMES,
|
|
28
|
+
OptimizationReport,
|
|
29
|
+
OptimizationResult,
|
|
30
|
+
PassStats,
|
|
31
|
+
optimize_ast,
|
|
32
|
+
optimize_file,
|
|
33
|
+
optimize_source,
|
|
34
|
+
)
|
|
35
|
+
from .runner import execute, run_path, run_source
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_ipython_extension(ipython) -> None:
|
|
39
|
+
"""Enable ``%%opast`` in IPython/Jupyter via ``%load_ext opast``."""
|
|
40
|
+
from .magic import load_ipython_extension as _load
|
|
41
|
+
|
|
42
|
+
_load(ipython)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__version__ = "0.1.0"
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"DEFAULT_MAX_ITERATIONS",
|
|
49
|
+
"PASS_NAMES",
|
|
50
|
+
"OptimizationReport",
|
|
51
|
+
"OptimizationResult",
|
|
52
|
+
"PassStats",
|
|
53
|
+
"optimize_ast",
|
|
54
|
+
"optimize_file",
|
|
55
|
+
"optimize_source",
|
|
56
|
+
"execute",
|
|
57
|
+
"run_path",
|
|
58
|
+
"run_source",
|
|
59
|
+
"__version__",
|
|
60
|
+
]
|
opast/__main__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Command line interface: ``python -m opast [options] script.py [args...]``
|
|
2
|
+
or ``python -m opast [options] -c "code" [args...]``."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .pipeline import (
|
|
12
|
+
DEFAULT_MAX_ITERATIONS,
|
|
13
|
+
PASS_NAMES,
|
|
14
|
+
optimize_file,
|
|
15
|
+
optimize_source,
|
|
16
|
+
)
|
|
17
|
+
from .runner import execute
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main(argv: list[str] | None = None) -> int:
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="opast",
|
|
23
|
+
description=(
|
|
24
|
+
"Optimize a Python script with AST passes (constant folding, "
|
|
25
|
+
"algebraic simplification, dead code elimination, simple function "
|
|
26
|
+
"inlining) and run it with the current interpreter. Scopes that "
|
|
27
|
+
"use dynamic constructs (eval/exec/globals/locals/vars/compile/"
|
|
28
|
+
"__import__/import *) are left untouched."
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument("--show", action="store_true",
|
|
32
|
+
help="print the optimized source to stdout")
|
|
33
|
+
parser.add_argument("-o", "--output", metavar="FILE",
|
|
34
|
+
help="write the optimized source to FILE")
|
|
35
|
+
parser.add_argument("--no-run", action="store_true",
|
|
36
|
+
help="only optimize, do not execute the script")
|
|
37
|
+
parser.add_argument("--report", action="store_true",
|
|
38
|
+
help="print per-pass statistics to stderr")
|
|
39
|
+
parser.add_argument("--max-iterations", type=int,
|
|
40
|
+
default=DEFAULT_MAX_ITERATIONS, metavar="N",
|
|
41
|
+
help="max pipeline iterations (default: %(default)s)")
|
|
42
|
+
parser.add_argument("--jit", action="store_true",
|
|
43
|
+
help="decorate hot numeric functions with numba.njit "
|
|
44
|
+
"(optional dependency 'opast[jit]'; any numba "
|
|
45
|
+
"failure falls back to plain Python at runtime; "
|
|
46
|
+
"see README for the int64 caveat)")
|
|
47
|
+
parser.add_argument("--disable", metavar="PASSES", default="",
|
|
48
|
+
help="comma-separated pass names to skip: "
|
|
49
|
+
+ ", ".join((*PASS_NAMES, "jit")))
|
|
50
|
+
parser.add_argument("--opt-imports", action="store_true",
|
|
51
|
+
help="also optimize imported pure-Python modules "
|
|
52
|
+
"under the script's directory (opt-in: modules "
|
|
53
|
+
"whose globals are monkeypatched from outside "
|
|
54
|
+
"must not be optimized; see README)")
|
|
55
|
+
parser.add_argument("--opt-imports-under", metavar="DIR", action="append",
|
|
56
|
+
default=[],
|
|
57
|
+
help="optimize imports under DIR (repeatable; "
|
|
58
|
+
"implies the hook without adding the script's "
|
|
59
|
+
"directory unless --opt-imports is also given)")
|
|
60
|
+
parser.add_argument("-c", "--code", metavar="CODE",
|
|
61
|
+
help="optimize and run CODE directly (like python -c; "
|
|
62
|
+
"remaining arguments go to sys.argv)")
|
|
63
|
+
parser.add_argument("script", nargs="?",
|
|
64
|
+
help="path to the Python script (or, with -c, the "
|
|
65
|
+
"first script argument)")
|
|
66
|
+
parser.add_argument("args", nargs=argparse.REMAINDER,
|
|
67
|
+
help="arguments passed to the script")
|
|
68
|
+
ns = parser.parse_args(argv)
|
|
69
|
+
|
|
70
|
+
if ns.code is None and ns.script is None:
|
|
71
|
+
parser.error("a script path is required (or use -c CODE)")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
if ns.code is not None:
|
|
75
|
+
result = optimize_source(
|
|
76
|
+
ns.code, filename="<string>",
|
|
77
|
+
max_iterations=ns.max_iterations, jit=ns.jit,
|
|
78
|
+
disable=ns.disable,
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
result = optimize_file(
|
|
82
|
+
ns.script, max_iterations=ns.max_iterations, jit=ns.jit,
|
|
83
|
+
disable=ns.disable,
|
|
84
|
+
)
|
|
85
|
+
except ValueError as exc: # unknown --disable pass name
|
|
86
|
+
parser.error(str(exc))
|
|
87
|
+
|
|
88
|
+
if ns.jit:
|
|
89
|
+
from . import jitsupport
|
|
90
|
+
|
|
91
|
+
if not jitsupport.numba_available():
|
|
92
|
+
print(
|
|
93
|
+
"opast: numba is not available on this interpreter; "
|
|
94
|
+
"--jit decorations will run as plain Python.",
|
|
95
|
+
file=sys.stderr,
|
|
96
|
+
)
|
|
97
|
+
if ns.show:
|
|
98
|
+
print(result.source)
|
|
99
|
+
if ns.output:
|
|
100
|
+
Path(ns.output).write_text(result.source + "\n", encoding="utf-8")
|
|
101
|
+
if ns.report:
|
|
102
|
+
print(result.report.summary(), file=sys.stderr)
|
|
103
|
+
if not ns.no_run:
|
|
104
|
+
finder = None
|
|
105
|
+
if ns.opt_imports or ns.opt_imports_under:
|
|
106
|
+
from . import importhook
|
|
107
|
+
|
|
108
|
+
roots = list(ns.opt_imports_under)
|
|
109
|
+
if ns.opt_imports:
|
|
110
|
+
if ns.code is not None:
|
|
111
|
+
roots.append(os.getcwd())
|
|
112
|
+
else:
|
|
113
|
+
roots.append(str(Path(ns.script).resolve().parent))
|
|
114
|
+
finder = importhook.install(
|
|
115
|
+
roots,
|
|
116
|
+
max_iterations=ns.max_iterations,
|
|
117
|
+
jit=ns.jit,
|
|
118
|
+
disable=ns.disable,
|
|
119
|
+
report=ns.report,
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
if ns.code is not None:
|
|
123
|
+
# With -c the positional slot, if used, is really the first
|
|
124
|
+
# script argument -- mimic ``python -c`` (sys.argv[0] == "-c").
|
|
125
|
+
args = ([ns.script] if ns.script is not None else []) + list(ns.args)
|
|
126
|
+
execute(result, tuple(args), write_source=ns.jit, argv0="-c")
|
|
127
|
+
else:
|
|
128
|
+
execute(result, tuple(ns.args), write_source=ns.jit)
|
|
129
|
+
finally:
|
|
130
|
+
if finder is not None:
|
|
131
|
+
importhook.uninstall(finder)
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
sys.exit(main())
|