cinderx 2026.1.16.2__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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.
- __static__/__init__.py +641 -0
- __static__/compiler_flags.py +8 -0
- __static__/enum.py +160 -0
- __static__/native_utils.py +77 -0
- __static__/type_code.py +48 -0
- __strict__/__init__.py +39 -0
- _cinderx.so +0 -0
- cinderx/__init__.py +577 -0
- cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
- cinderx/_asyncio.py +156 -0
- cinderx/compileall.py +710 -0
- cinderx/compiler/__init__.py +40 -0
- cinderx/compiler/__main__.py +137 -0
- cinderx/compiler/config.py +7 -0
- cinderx/compiler/consts.py +72 -0
- cinderx/compiler/debug.py +70 -0
- cinderx/compiler/dis_stable.py +283 -0
- cinderx/compiler/errors.py +151 -0
- cinderx/compiler/flow_graph_optimizer.py +1287 -0
- cinderx/compiler/future.py +91 -0
- cinderx/compiler/misc.py +32 -0
- cinderx/compiler/opcode_cinder.py +18 -0
- cinderx/compiler/opcode_static.py +100 -0
- cinderx/compiler/opcodebase.py +158 -0
- cinderx/compiler/opcodes.py +991 -0
- cinderx/compiler/optimizer.py +547 -0
- cinderx/compiler/pyassem.py +3711 -0
- cinderx/compiler/pycodegen.py +7660 -0
- cinderx/compiler/pysourceloader.py +62 -0
- cinderx/compiler/static/__init__.py +1404 -0
- cinderx/compiler/static/compiler.py +629 -0
- cinderx/compiler/static/declaration_visitor.py +335 -0
- cinderx/compiler/static/definite_assignment_checker.py +280 -0
- cinderx/compiler/static/effects.py +160 -0
- cinderx/compiler/static/module_table.py +666 -0
- cinderx/compiler/static/type_binder.py +2176 -0
- cinderx/compiler/static/types.py +10580 -0
- cinderx/compiler/static/util.py +81 -0
- cinderx/compiler/static/visitor.py +91 -0
- cinderx/compiler/strict/__init__.py +69 -0
- cinderx/compiler/strict/class_conflict_checker.py +249 -0
- cinderx/compiler/strict/code_gen_base.py +409 -0
- cinderx/compiler/strict/common.py +507 -0
- cinderx/compiler/strict/compiler.py +352 -0
- cinderx/compiler/strict/feature_extractor.py +130 -0
- cinderx/compiler/strict/flag_extractor.py +97 -0
- cinderx/compiler/strict/loader.py +827 -0
- cinderx/compiler/strict/preprocessor.py +11 -0
- cinderx/compiler/strict/rewriter/__init__.py +5 -0
- cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
- cinderx/compiler/strict/rewriter/rewriter.py +975 -0
- cinderx/compiler/strict/runtime.py +77 -0
- cinderx/compiler/symbols.py +1754 -0
- cinderx/compiler/unparse.py +414 -0
- cinderx/compiler/visitor.py +194 -0
- cinderx/jit.py +230 -0
- cinderx/opcode.py +202 -0
- cinderx/static.py +113 -0
- cinderx/strictmodule.py +6 -0
- cinderx/test_support.py +341 -0
- cinderx-2026.1.16.2.dist-info/METADATA +15 -0
- cinderx-2026.1.16.2.dist-info/RECORD +68 -0
- cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
- cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
- cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
- opcodes/__init__.py +0 -0
- opcodes/assign_opcode_numbers.py +272 -0
- opcodes/cinderx_opcodes.py +121 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
"""Package for compiling Python source code
|
|
6
|
+
|
|
7
|
+
There are several functions defined at the top level that are imported
|
|
8
|
+
from modules contained in the package.
|
|
9
|
+
|
|
10
|
+
compile(source, filename, mode, flags=None, dont_inherit=None)
|
|
11
|
+
Returns a code object. A replacement for the builtin compile() function.
|
|
12
|
+
|
|
13
|
+
compileFile(filename)
|
|
14
|
+
Generates a .pyc file by compiling filename.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
from types import CodeType
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from .pycodegen import CinderCodeGenerator, compile, compile_code, compileFile
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def exec_cinder(
|
|
25
|
+
source: str | bytes | ast.Module | ast.Expression | ast.Interactive | CodeType,
|
|
26
|
+
locals: dict[str, Any],
|
|
27
|
+
globals: dict[str, Any],
|
|
28
|
+
modname: str = "<module>",
|
|
29
|
+
) -> None:
|
|
30
|
+
if isinstance(source, CodeType):
|
|
31
|
+
code = source
|
|
32
|
+
else:
|
|
33
|
+
code = compile_code(
|
|
34
|
+
source, "<module>", "exec", compiler=CinderCodeGenerator, modname=modname
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
exec(code, locals, globals)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = ("compile", "compile_code", "compileFile", "exec_cinder")
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# pyre-strict
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import builtins
|
|
6
|
+
import importlib.util
|
|
7
|
+
import marshal
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from dis import dis
|
|
12
|
+
from types import CodeType
|
|
13
|
+
from typing import Pattern, TextIO
|
|
14
|
+
|
|
15
|
+
from .pycodegen import CinderCodeGenerator, CodeGenerator312, compile_code, make_header
|
|
16
|
+
from .static import FIXED_MODULES, StaticCodeGenerator
|
|
17
|
+
from .strict import StrictCodeGenerator
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from cinder import StrictModule
|
|
21
|
+
except ImportError:
|
|
22
|
+
StrictModule = None
|
|
23
|
+
|
|
24
|
+
# https://www.python.org/dev/peps/pep-0263/
|
|
25
|
+
coding_re: Pattern = re.compile(rb"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def open_with_coding(fname: str) -> TextIO:
|
|
29
|
+
with open(fname, "rb") as f:
|
|
30
|
+
line = f.readline()
|
|
31
|
+
m = coding_re.match(line)
|
|
32
|
+
if not m:
|
|
33
|
+
line = f.readline()
|
|
34
|
+
m = coding_re.match(line)
|
|
35
|
+
encoding = "utf-8"
|
|
36
|
+
if m:
|
|
37
|
+
encoding = m.group(1).decode()
|
|
38
|
+
return open(fname, encoding=encoding)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
argparser = argparse.ArgumentParser(
|
|
43
|
+
prog="compiler",
|
|
44
|
+
description="Compile/execute a Python3 source file",
|
|
45
|
+
epilog="""\
|
|
46
|
+
By default, compile source code into in-memory code object and execute it.
|
|
47
|
+
If -c is specified, instead of executing write .pyc file.
|
|
48
|
+
""",
|
|
49
|
+
)
|
|
50
|
+
argparser.add_argument(
|
|
51
|
+
"-c", action="store_true", help="compile into .pyc file instead of executing"
|
|
52
|
+
)
|
|
53
|
+
argparser.add_argument(
|
|
54
|
+
"--dis", action="store_true", help="disassemble compiled code"
|
|
55
|
+
)
|
|
56
|
+
argparser.add_argument("--output", help="path to the output .pyc file")
|
|
57
|
+
argparser.add_argument(
|
|
58
|
+
"--modname",
|
|
59
|
+
help="module name to compile as (default __main__)",
|
|
60
|
+
default="__main__",
|
|
61
|
+
)
|
|
62
|
+
argparser.add_argument("input", help="source .py file")
|
|
63
|
+
group = argparser.add_mutually_exclusive_group()
|
|
64
|
+
group.add_argument(
|
|
65
|
+
"--static", action="store_true", help="compile using static compiler"
|
|
66
|
+
)
|
|
67
|
+
group.add_argument(
|
|
68
|
+
"--builtin", action="store_true", help="compile using built-in C compiler"
|
|
69
|
+
)
|
|
70
|
+
argparser.add_argument(
|
|
71
|
+
"--opt",
|
|
72
|
+
action="store",
|
|
73
|
+
type=int,
|
|
74
|
+
default=-1,
|
|
75
|
+
help="set optimization level to compile with",
|
|
76
|
+
)
|
|
77
|
+
argparser.add_argument("--strict", action="store_true", help="run in strict module")
|
|
78
|
+
args = argparser.parse_args()
|
|
79
|
+
|
|
80
|
+
with open_with_coding(args.input) as f:
|
|
81
|
+
fileinfo = os.stat(args.input)
|
|
82
|
+
source = f.read()
|
|
83
|
+
|
|
84
|
+
if args.builtin:
|
|
85
|
+
codeobj = compile(source, args.input, "exec")
|
|
86
|
+
assert isinstance(codeobj, CodeType)
|
|
87
|
+
else:
|
|
88
|
+
if args.static and args.strict:
|
|
89
|
+
raise ValueError("Cannot specify both --static and --strict options.")
|
|
90
|
+
|
|
91
|
+
compiler = (
|
|
92
|
+
StaticCodeGenerator
|
|
93
|
+
if args.static
|
|
94
|
+
else StrictCodeGenerator
|
|
95
|
+
if args.strict
|
|
96
|
+
else CinderCodeGenerator
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
codeobj = compile_code(
|
|
100
|
+
source,
|
|
101
|
+
args.input,
|
|
102
|
+
"exec",
|
|
103
|
+
optimize=args.opt,
|
|
104
|
+
compiler=compiler,
|
|
105
|
+
modname=args.modname,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if args.dis:
|
|
109
|
+
dis(codeobj)
|
|
110
|
+
|
|
111
|
+
if args.c:
|
|
112
|
+
if args.output:
|
|
113
|
+
name = args.output
|
|
114
|
+
else:
|
|
115
|
+
name = args.input.rsplit(".", 1)[0] + ".pyc"
|
|
116
|
+
with open(name, "wb") as f:
|
|
117
|
+
hdr = make_header(int(fileinfo.st_mtime), fileinfo.st_size)
|
|
118
|
+
f.write(importlib.util.MAGIC_NUMBER)
|
|
119
|
+
f.write(hdr)
|
|
120
|
+
marshal.dump(codeobj, f)
|
|
121
|
+
else:
|
|
122
|
+
if args.strict and StrictModule is not None:
|
|
123
|
+
d: dict[str, object] = {"__name__": "__main__"}
|
|
124
|
+
mod = StrictModule(d, False)
|
|
125
|
+
else:
|
|
126
|
+
mod = type(sys)("__main__")
|
|
127
|
+
d = mod.__dict__
|
|
128
|
+
if args.static or args.strict:
|
|
129
|
+
d["<fixed-modules>"] = FIXED_MODULES
|
|
130
|
+
d["<builtins>"] = builtins.__dict__
|
|
131
|
+
sys.modules["__main__"] = mod
|
|
132
|
+
# don't confuse the script with args meant for us
|
|
133
|
+
sys.argv = sys.argv[:1]
|
|
134
|
+
exec(codeobj, d, d)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
main()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# operation flags
|
|
8
|
+
OP_ASSIGN = "OP_ASSIGN"
|
|
9
|
+
OP_DELETE = "OP_DELETE"
|
|
10
|
+
OP_APPLY = "OP_APPLY"
|
|
11
|
+
|
|
12
|
+
SC_LOCAL = 1
|
|
13
|
+
SC_GLOBAL_IMPLICIT = 2
|
|
14
|
+
SC_GLOBAL_EXPLICIT = 3
|
|
15
|
+
SC_FREE = 4
|
|
16
|
+
SC_CELL = 5
|
|
17
|
+
SC_TYPE_PARAM = 9
|
|
18
|
+
SC_UNKNOWN = 6
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
CO_OPTIMIZED = 0x0001
|
|
22
|
+
CO_NEWLOCALS = 0x0002
|
|
23
|
+
CO_VARARGS = 0x0004
|
|
24
|
+
CO_VARKEYWORDS = 0x0008
|
|
25
|
+
CO_NESTED = 0x0010
|
|
26
|
+
CO_GENERATOR = 0x0020
|
|
27
|
+
CO_NOFREE = 0x0040
|
|
28
|
+
CO_COROUTINE = 0x0080
|
|
29
|
+
CO_GENERATOR_ALLOWED = 0
|
|
30
|
+
CO_ITERABLE_COROUTINE = 0x0100
|
|
31
|
+
CO_ASYNC_GENERATOR = 0x0200
|
|
32
|
+
CO_FUTURE_DIVISION = 0x20000
|
|
33
|
+
CO_FUTURE_ABSOLUTE_IMPORT = 0x40000
|
|
34
|
+
CO_FUTURE_WITH_STATEMENT = 0x80000
|
|
35
|
+
CO_FUTURE_PRINT_FUNCTION = 0x100000
|
|
36
|
+
CO_FUTURE_UNICODE_LITERALS = 0x200000
|
|
37
|
+
CO_FUTURE_BARRY_AS_BDFL = 0x400000
|
|
38
|
+
CO_FUTURE_GENERATOR_STOP = 0x800000
|
|
39
|
+
CO_FUTURE_ANNOTATIONS = 0x1000000
|
|
40
|
+
CO_HAS_DOCSTRING = 0x4000000
|
|
41
|
+
CO_METHOD = 0x8000000
|
|
42
|
+
if sys.version_info >= (3, 14):
|
|
43
|
+
CI_CO_STATICALLY_COMPILED = 0x4000
|
|
44
|
+
else:
|
|
45
|
+
CI_CO_STATICALLY_COMPILED = 0x4000000
|
|
46
|
+
CO_SUPPRESS_JIT = 0x40000000
|
|
47
|
+
|
|
48
|
+
PyCF_MASK_OBSOLETE: int = CO_NESTED
|
|
49
|
+
PyCF_SOURCE_IS_UTF8 = 0x0100
|
|
50
|
+
PyCF_DONT_IMPLY_DEDENT = 0x0200
|
|
51
|
+
PyCF_ONLY_AST = 0x0400
|
|
52
|
+
PyCF_IGNORE_COOKIE = 0x0800
|
|
53
|
+
PyCF_TYPE_COMMENTS = 0x1000
|
|
54
|
+
PyCF_ALLOW_TOP_LEVEL_AWAIT = 0x2000
|
|
55
|
+
PyCF_COMPILE_MASK: int = (
|
|
56
|
+
PyCF_ONLY_AST
|
|
57
|
+
| PyCF_ALLOW_TOP_LEVEL_AWAIT
|
|
58
|
+
| PyCF_TYPE_COMMENTS
|
|
59
|
+
| PyCF_DONT_IMPLY_DEDENT
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
PyCF_MASK: int = (
|
|
63
|
+
CO_FUTURE_DIVISION
|
|
64
|
+
| CO_FUTURE_ABSOLUTE_IMPORT
|
|
65
|
+
| CO_FUTURE_WITH_STATEMENT
|
|
66
|
+
| CO_FUTURE_PRINT_FUNCTION
|
|
67
|
+
| CO_FUTURE_UNICODE_LITERALS
|
|
68
|
+
| CO_FUTURE_BARRY_AS_BDFL
|
|
69
|
+
| CO_FUTURE_GENERATOR_STOP
|
|
70
|
+
| CO_FUTURE_ANNOTATIONS
|
|
71
|
+
| CI_CO_STATICALLY_COMPILED
|
|
72
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
# pyre-strict
|
|
4
|
+
"""Debugging output for various internal datatypes."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from .opcodes import opcode
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .pyassem import Block, Instruction, PyFlowGraph
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def str_of_oparg(instr: Instruction) -> str:
|
|
17
|
+
if instr.target is None:
|
|
18
|
+
# This is not a block
|
|
19
|
+
return str(instr.oparg)
|
|
20
|
+
elif instr.target.label:
|
|
21
|
+
# This is a labelled block
|
|
22
|
+
return f"{instr.target.bid} ({instr.target.label})"
|
|
23
|
+
else:
|
|
24
|
+
# This is an unlabelled block
|
|
25
|
+
return str(instr.target.bid)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def str_of_instr(instr: Instruction) -> str:
|
|
29
|
+
oparg = str_of_oparg(instr)
|
|
30
|
+
opname = instr.opname
|
|
31
|
+
return f"{instr.lineno} {opname} {oparg}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def str_of_block_header(block: Block) -> str:
|
|
35
|
+
return repr(block)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def str_of_stack_effect(instr: Instruction) -> str:
|
|
39
|
+
d1 = opcode.stack_effect_raw(instr.opname, instr.oparg, False)
|
|
40
|
+
d2 = opcode.stack_effect_raw(instr.opname, instr.oparg, True)
|
|
41
|
+
if d1 != d2:
|
|
42
|
+
delta = f"{d1}/{d2}"
|
|
43
|
+
else:
|
|
44
|
+
delta = str(d1)
|
|
45
|
+
return delta
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def str_of_block_instr(
|
|
49
|
+
instr: Instruction, pc: int = 0, stack_effect: bool = False
|
|
50
|
+
) -> str:
|
|
51
|
+
delta = str_of_stack_effect(instr) if stack_effect else ""
|
|
52
|
+
eh = f" EH: {instr.exc_handler.bid}" if instr.exc_handler is not None else ""
|
|
53
|
+
|
|
54
|
+
return f"{delta:>6} | {pc:3} {str_of_instr(instr)}{eh}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def dump_block(
|
|
58
|
+
graph: PyFlowGraph, block: Block, pc: int = 0, stack_effect: bool = False
|
|
59
|
+
) -> int:
|
|
60
|
+
print(str_of_block_header(block))
|
|
61
|
+
for instr in block.getInstructions():
|
|
62
|
+
print(" ", str_of_block_instr(instr, pc, stack_effect))
|
|
63
|
+
pc += graph.instrsize(instr, instr.ioparg) * opcode.CODEUNIT_SIZE
|
|
64
|
+
return pc
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dump_graph(graph: PyFlowGraph, stack_effect: bool = False) -> None:
|
|
68
|
+
pc = 0
|
|
69
|
+
for block in graph.getBlocks():
|
|
70
|
+
pc = dump_block(graph, block, pc, stack_effect)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Portions copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
#
|
|
4
|
+
# Dissassemble code objects:
|
|
5
|
+
# a) recursively (like dis.dis() in CPython behaves);
|
|
6
|
+
# b) providing stable references to internal code objects (by replacing
|
|
7
|
+
# memory address with incrementing number);
|
|
8
|
+
# c) besides disassembly, also dump other fields of code objects.
|
|
9
|
+
# Useful for comparing disassembly outputs from different runs.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
# pyre-strict
|
|
13
|
+
|
|
14
|
+
import dis as _dis
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Generator, Iterable
|
|
18
|
+
from pprint import pformat
|
|
19
|
+
from re import Pattern
|
|
20
|
+
from types import CodeType
|
|
21
|
+
from typing import Optional, TextIO
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _make_stable(
|
|
25
|
+
gen: Iterable[_dis.Instruction],
|
|
26
|
+
) -> Generator[_dis.Instruction, None, None]:
|
|
27
|
+
for instr in gen:
|
|
28
|
+
if sys.version_info >= (3, 14):
|
|
29
|
+
yield _dis.Instruction.make(
|
|
30
|
+
instr.opname,
|
|
31
|
+
instr.arg,
|
|
32
|
+
instr.argval,
|
|
33
|
+
_stable_repr(instr.argval),
|
|
34
|
+
instr.offset,
|
|
35
|
+
instr.start_offset,
|
|
36
|
+
instr.starts_line,
|
|
37
|
+
instr.line_number,
|
|
38
|
+
instr.label,
|
|
39
|
+
instr.positions,
|
|
40
|
+
instr.cache_info,
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
yield _dis.Instruction(
|
|
44
|
+
instr.opname,
|
|
45
|
+
instr.opcode,
|
|
46
|
+
instr.arg,
|
|
47
|
+
instr.argval,
|
|
48
|
+
_stable_repr(instr.argval),
|
|
49
|
+
instr.offset,
|
|
50
|
+
instr.starts_line,
|
|
51
|
+
instr.is_jump_target,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _stable_repr(obj: object) -> str:
|
|
56
|
+
if isinstance(obj, frozenset):
|
|
57
|
+
obj = frozenset(sorted(obj, key=repr))
|
|
58
|
+
return repr(obj)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _disassemble_bytes(
|
|
62
|
+
co: CodeType,
|
|
63
|
+
code: bytes,
|
|
64
|
+
lasti: int = -1,
|
|
65
|
+
varnames: Optional[tuple[str]] = None,
|
|
66
|
+
names: Optional[tuple[str]] = None,
|
|
67
|
+
constants: Optional[tuple[object]] = None,
|
|
68
|
+
cells: Optional[tuple[object]] = None,
|
|
69
|
+
linestarts: Optional[dict[int, int]] = None,
|
|
70
|
+
*,
|
|
71
|
+
file: Optional[TextIO] = None,
|
|
72
|
+
line_offset: int = 0,
|
|
73
|
+
localsplusnames: Optional[tuple[str]] = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
# Omit the line number column entirely if we have no line number info
|
|
76
|
+
show_lineno = linestarts is not None
|
|
77
|
+
if show_lineno:
|
|
78
|
+
# pyre-fixme [16]: `Optional` has no attribute `values`.
|
|
79
|
+
maxlineno = max(linestarts.values()) + line_offset
|
|
80
|
+
if maxlineno >= 1000:
|
|
81
|
+
lineno_width = len(str(maxlineno))
|
|
82
|
+
else:
|
|
83
|
+
lineno_width = 3
|
|
84
|
+
else:
|
|
85
|
+
lineno_width = 0
|
|
86
|
+
maxoffset = len(code) - 2
|
|
87
|
+
if maxoffset >= 10000:
|
|
88
|
+
offset_width = len(str(maxoffset))
|
|
89
|
+
else:
|
|
90
|
+
offset_width = 4
|
|
91
|
+
if sys.version_info >= (3, 14):
|
|
92
|
+
if co is not None:
|
|
93
|
+
exception_entries = _dis._parse_exception_table(co)
|
|
94
|
+
labels_map = _dis._make_labels_map(
|
|
95
|
+
code, exception_entries=exception_entries
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
labels_map = None
|
|
99
|
+
instr_bytes = _dis._get_instructions_bytes(
|
|
100
|
+
code,
|
|
101
|
+
arg_resolver=_dis.ArgResolver(
|
|
102
|
+
constants, names, lambda oparg: localsplusnames[oparg], labels_map
|
|
103
|
+
),
|
|
104
|
+
linestarts=linestarts,
|
|
105
|
+
line_offset=line_offset,
|
|
106
|
+
)
|
|
107
|
+
elif sys.version_info >= (3, 12):
|
|
108
|
+
# pyre-fixme[16]: Module `dis` has no attribute `_get_instructions_bytes`.
|
|
109
|
+
instr_bytes = _dis._get_instructions_bytes(
|
|
110
|
+
code,
|
|
111
|
+
# pyre-fixme[16]: `Optional` has no attribute `__getitem__`.
|
|
112
|
+
lambda oparg: localsplusnames[oparg],
|
|
113
|
+
names,
|
|
114
|
+
constants,
|
|
115
|
+
linestarts,
|
|
116
|
+
line_offset=line_offset,
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
# pyre-fixme [16]: Module `dis` has no attribute `_get_instructions_bytes`
|
|
120
|
+
instr_bytes = _dis._get_instructions_bytes(
|
|
121
|
+
code, varnames, names, constants, cells, linestarts, line_offset=line_offset
|
|
122
|
+
)
|
|
123
|
+
for instr in _make_stable(instr_bytes):
|
|
124
|
+
new_source_line = (
|
|
125
|
+
show_lineno and instr.starts_line is not None and instr.offset > 0
|
|
126
|
+
)
|
|
127
|
+
if new_source_line:
|
|
128
|
+
print(file=file)
|
|
129
|
+
is_current_instr = instr.offset == lasti
|
|
130
|
+
|
|
131
|
+
if sys.version_info >= (3, 14):
|
|
132
|
+
_dis.Formatter(
|
|
133
|
+
file=file, lineno_width=lineno_width, offset_width=offset_width
|
|
134
|
+
).print_instruction(instr, is_current_instr)
|
|
135
|
+
else:
|
|
136
|
+
print(
|
|
137
|
+
instr._disassemble(lineno_width, is_current_instr, offset_width),
|
|
138
|
+
file=file,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def disassemble(
|
|
143
|
+
co: CodeType,
|
|
144
|
+
lasti: int = -1,
|
|
145
|
+
*,
|
|
146
|
+
file: Optional[TextIO] = None,
|
|
147
|
+
skip_line_nos: bool = False,
|
|
148
|
+
) -> None:
|
|
149
|
+
cell_names = co.co_cellvars + co.co_freevars
|
|
150
|
+
if skip_line_nos:
|
|
151
|
+
linestarts = None
|
|
152
|
+
else:
|
|
153
|
+
linestarts = dict(_dis.findlinestarts(co))
|
|
154
|
+
localsplusnames = (
|
|
155
|
+
co.co_varnames
|
|
156
|
+
if sys.version_info < (3, 12)
|
|
157
|
+
else (co.co_varnames + co.co_cellvars + co.co_freevars)
|
|
158
|
+
)
|
|
159
|
+
_disassemble_bytes(
|
|
160
|
+
co,
|
|
161
|
+
co.co_code,
|
|
162
|
+
lasti,
|
|
163
|
+
co.co_varnames,
|
|
164
|
+
co.co_names,
|
|
165
|
+
co.co_consts,
|
|
166
|
+
cell_names,
|
|
167
|
+
linestarts,
|
|
168
|
+
file=file,
|
|
169
|
+
localsplusnames=localsplusnames,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Disassembler:
|
|
174
|
+
def __init__(self) -> None:
|
|
175
|
+
self.id_map: dict[int, int] = {}
|
|
176
|
+
self.id_cnt: int = 0
|
|
177
|
+
|
|
178
|
+
def get_co_id(self, co: CodeType) -> int:
|
|
179
|
+
addr = id(co)
|
|
180
|
+
if addr in self.id_map:
|
|
181
|
+
return self.id_map[addr]
|
|
182
|
+
self.id_map[addr] = self.id_cnt
|
|
183
|
+
self.id_cnt += 1
|
|
184
|
+
return self.id_cnt - 1
|
|
185
|
+
|
|
186
|
+
def co_repr(self, co: CodeType) -> str:
|
|
187
|
+
return '<code object %s at #%d, file "%s", line %d>' % (
|
|
188
|
+
co.co_name,
|
|
189
|
+
self.get_co_id(co),
|
|
190
|
+
co.co_filename,
|
|
191
|
+
co.co_firstlineno,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def disassemble(
|
|
195
|
+
self,
|
|
196
|
+
co: CodeType,
|
|
197
|
+
lasti: int = -1,
|
|
198
|
+
file: Optional[TextIO] = None,
|
|
199
|
+
skip_line_nos: bool = False,
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Disassemble a code object."""
|
|
202
|
+
consts = tuple(
|
|
203
|
+
[self.co_repr(x) if hasattr(x, "co_code") else x for x in co.co_consts]
|
|
204
|
+
)
|
|
205
|
+
codeobj = co.replace(co_consts=consts)
|
|
206
|
+
disassemble(codeobj, file=file, skip_line_nos=skip_line_nos)
|
|
207
|
+
|
|
208
|
+
def dump_code(self, co: CodeType, file: Optional[TextIO] = None) -> None:
|
|
209
|
+
if not file:
|
|
210
|
+
file = sys.stdout
|
|
211
|
+
print(self.co_repr(co), file=file)
|
|
212
|
+
self.disassemble(co, file=file, skip_line_nos=True)
|
|
213
|
+
print("co_argcount:", co.co_argcount, file=file)
|
|
214
|
+
print("co_kwonlyargcount:", co.co_kwonlyargcount, file=file)
|
|
215
|
+
print("co_stacksize:", co.co_stacksize, file=file)
|
|
216
|
+
flags = []
|
|
217
|
+
co_flags = co.co_flags
|
|
218
|
+
for val, name in _dis.COMPILER_FLAG_NAMES.items():
|
|
219
|
+
if co_flags & val:
|
|
220
|
+
flags.append(name)
|
|
221
|
+
co_flags &= ~val
|
|
222
|
+
if co_flags:
|
|
223
|
+
flags.append(hex(co_flags))
|
|
224
|
+
print("co_flags:", hex(co.co_flags), "(" + " | ".join(flags) + ")", file=file)
|
|
225
|
+
print(
|
|
226
|
+
"co_consts:",
|
|
227
|
+
pformat(
|
|
228
|
+
tuple(
|
|
229
|
+
[
|
|
230
|
+
self.co_repr(x) if hasattr(x, "co_code") else _stable_repr(x)
|
|
231
|
+
for x in co.co_consts
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
),
|
|
235
|
+
file=file,
|
|
236
|
+
)
|
|
237
|
+
print("co_firstlineno:", co.co_firstlineno, file=file)
|
|
238
|
+
print("co_names:", co.co_names, file=file)
|
|
239
|
+
print("co_varnames:", co.co_varnames, file=file)
|
|
240
|
+
print("co_cellvars:", co.co_cellvars, file=file)
|
|
241
|
+
print("co_freevars:", co.co_freevars, file=file)
|
|
242
|
+
print("co_lines:", pformat(list(co.co_lines())), file=file)
|
|
243
|
+
if sys.version_info >= (3, 12):
|
|
244
|
+
print("co_positions:", file=file)
|
|
245
|
+
for i, position in enumerate(co.co_positions()):
|
|
246
|
+
print(f"Offset {i * 2}: {position}", file=file)
|
|
247
|
+
print("co_exceptiontable:", co.co_exceptiontable, file=file)
|
|
248
|
+
print("exception table: ", file=file)
|
|
249
|
+
print(
|
|
250
|
+
# pyre-fixme[16]: Module `dis` has no attribute
|
|
251
|
+
# `_parse_exception_table`.
|
|
252
|
+
"\n".join(" " + str(x) for x in _dis._parse_exception_table(co)),
|
|
253
|
+
file=file,
|
|
254
|
+
)
|
|
255
|
+
print(file=file)
|
|
256
|
+
for c in co.co_consts:
|
|
257
|
+
if hasattr(c, "co_code"):
|
|
258
|
+
self.dump_code(c, file)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# https://www.python.org/dev/peps/pep-0263/
|
|
262
|
+
coding_re: Pattern[bytes] = re.compile(
|
|
263
|
+
rb"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def open_with_coding(fname: str) -> TextIO:
|
|
268
|
+
with open(fname, "rb") as f:
|
|
269
|
+
line = f.readline()
|
|
270
|
+
m = coding_re.match(line)
|
|
271
|
+
if not m:
|
|
272
|
+
line = f.readline()
|
|
273
|
+
m = coding_re.match(line)
|
|
274
|
+
encoding = "utf-8"
|
|
275
|
+
if m:
|
|
276
|
+
encoding = m.group(1).decode()
|
|
277
|
+
return open(fname, encoding=encoding)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == "__main__":
|
|
281
|
+
with open_with_coding(sys.argv[1]) as f:
|
|
282
|
+
co: CodeType = compile(f.read(), sys.argv[1], "exec")
|
|
283
|
+
Disassembler().dump_code(co, file=sys.stdout)
|