Skip to content

Commit ea821f7

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Improve disassembly output of compiler --dis
Summary: Currently when you use --dis we just use the built-in disassembler. This pulls our test disassembler out and re-uses it so you can see the extended opcodes in a sensible way: Reviewed By: yoney Differential Revision: D103307684 fbshipit-source-id: abc1b2d3f22150adb0246a3dbee1bc3ae532b49a
1 parent bc9d690 commit ea821f7

3 files changed

Lines changed: 64 additions & 33 deletions

File tree

cinderx/PythonLib/cinderx/compiler/__init__.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,61 @@
1515
"""
1616

1717
import ast
18+
import dis
19+
import sys
20+
from io import StringIO
1821
from types import CodeType
1922
from typing import Any
2023

24+
from .opcodes import STATIC_CONST_OPCODES, STATIC_OPNAMES
2125
from .pycodegen import CinderCodeGenerator, compile, compile_code, compileFile
2226

2327

28+
def make_static_instr(instr: dis.Instruction, co: object) -> dis.Instruction:
29+
if instr.opcode in STATIC_CONST_OPCODES:
30+
# pyre-fixme[21]: Could not find name `_get_code_object` in `dis` (stubbed).
31+
from dis import _get_code_object
32+
33+
return dis.Instruction(
34+
STATIC_OPNAMES[instr.opcode],
35+
instr[1],
36+
instr.arg,
37+
_get_code_object(co).co_consts[instr.arg],
38+
*instr[4:],
39+
)
40+
return dis.Instruction(STATIC_OPNAMES[instr.opcode], *instr[1:])
41+
42+
43+
def get_disassembly_as_string(co: object, recurse: bool = False) -> str:
44+
s = StringIO()
45+
if sys.version_info < (3, 14):
46+
dis.dis(co, file=s)
47+
return s.getvalue()
48+
49+
# pyre-fixme[21]: Could not find name `Formatter` in `dis` (stubbed).
50+
# pyre-fixme[21]: Could not find name `_get_code_object` in `dis` (stubbed).
51+
from dis import _get_code_object, Bytecode, Formatter
52+
53+
formatter = Formatter(file=s, offset_width=3)
54+
bc = Bytecode(co)
55+
extended = False
56+
for instr in bc:
57+
if extended and instr.opname != "EXTENDED_ARG":
58+
extended = False
59+
instr = make_static_instr(instr, co)
60+
elif instr.opname == "EXTENDED_OPCODE":
61+
extended = True
62+
formatter.print_instruction(instr, False)
63+
64+
if recurse:
65+
for const in _get_code_object(co).co_consts:
66+
if isinstance(const, CodeType):
67+
s.write(f"\nDisassembly of {const!r}:\n")
68+
s.write(get_disassembly_as_string(const, recurse=True))
69+
70+
return s.getvalue()
71+
72+
2473
def exec_cinder(
2574
source: str | bytes | ast.Module | ast.Expression | ast.Interactive | CodeType,
2675
locals: dict[str, Any],
@@ -37,4 +86,11 @@ def exec_cinder(
3786
exec(code, locals, globals)
3887

3988

40-
__all__ = ("compile", "compile_code", "compileFile", "exec_cinder")
89+
__all__ = (
90+
"compile",
91+
"compile_code",
92+
"compileFile",
93+
"exec_cinder",
94+
"get_disassembly_as_string",
95+
"make_static_instr",
96+
)

cinderx/PythonLib/cinderx/compiler/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from types import CodeType
1717
from typing import Pattern, TextIO
1818

19+
from . import get_disassembly_as_string
1920
from .pycodegen import CinderCodeGenerator, compile_code, make_header
2021
from .static import FIXED_MODULES, StaticCodeGenerator
2122
from .static.pyrefly_info import Pyrefly
@@ -236,7 +237,7 @@ def main() -> None:
236237
)
237238

238239
if args.dis:
239-
dis(codeobj)
240+
print(get_disassembly_as_string(codeobj, recurse=True), end="")
240241

241242
if args.c:
242243
if args.output:

cinderx/PythonLib/test_cinderx/test_compiler/common.py

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,42 +66,16 @@ class CompilerTest(TestCase):
6666
COMPARE_JUMP_ZERO = "POP_JUMP_IF_ZERO"
6767

6868
def get_disassembly_as_string(self, co: Disassembleable) -> str:
69-
s = StringIO()
70-
if sys.version_info < (3, 14):
71-
dis.dis(co, file=s)
72-
return s.getvalue()
73-
74-
# pyre-fixme[10]: Name `Formatter` is used but not defined.
75-
# pyre-fixme[16]: Module `dis` has no attribute `Formatter`.
76-
formatter = Formatter(file=s, offset_width=3)
77-
# pyre-fixme[10]: Name `Bytecode` is used but not defined.
78-
bc = Bytecode(co)
79-
extended = False
80-
for instr in bc:
81-
if extended and instr.opname != "EXTENDED_ARG":
82-
extended = False
83-
instr = self.make_static_instr(instr, co)
84-
elif instr.opname == "EXTENDED_OPCODE":
85-
extended = True
69+
from cinderx.compiler import get_disassembly_as_string
8670

87-
formatter.print_instruction(instr, False)
88-
89-
return s.getvalue()
71+
return get_disassembly_as_string(co)
9072

9173
def make_static_instr(
9274
self, instr: dis.Instruction, co: Disassembleable
9375
) -> dis.Instruction:
94-
if instr.opcode in STATIC_CONST_OPCODES:
95-
return dis.Instruction(
96-
STATIC_OPNAMES[instr.opcode],
97-
instr[1],
98-
instr.arg,
99-
# pyre-fixme[10]: Name `_get_code_object` is used but not defined.
100-
# pyre-fixme[16]: Module `dis` has no attribute `_get_code_object`.
101-
_get_code_object(co).co_consts[instr.arg],
102-
*instr[4:],
103-
)
104-
return dis.Instruction(STATIC_OPNAMES[instr.opcode], *instr[1:])
76+
from cinderx.compiler import make_static_instr
77+
78+
return make_static_instr(instr, co)
10579

10680
def assertInBytecode(
10781
self,

0 commit comments

Comments
 (0)