Skip to content

Commit efdfd05

Browse files
authored
Merge pull request #179 from rocky/add-rustpython-disassembly
Add rustpython disassembly
2 parents 56bbe10 + 5fc58a1 commit efdfd05

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1197
-106
lines changed
-997 Bytes
Binary file not shown.

xdis/codetype/code313rust.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# (C) Copyright 2025 by Rocky Bernstein
2+
#
3+
# This program is free software; you can redistribute it and/or
4+
# modify it under the terms of the GNU General Public License
5+
# as published by the Free Software Foundation; either version 2
6+
# of the License, or (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program; if not, write to the Free Software
15+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
17+
from dataclasses import dataclass
18+
from typing import Any, Dict, Tuple, Union
19+
20+
from xdis.codetype.base import CodeBase
21+
22+
23+
@dataclass
24+
class SourceLocation:
25+
# line: 1-based int
26+
line: int
27+
# character_offset: 1-based int (constructed from a zero-indexed stored value)
28+
character_offset: int
29+
30+
31+
class Code313Rust(CodeBase):
32+
"""Class for a RustPython 3.13 code object used when a Python
33+
interpreter is not RustPython 3.13 but working on RustPython 3.13 bytecode. It
34+
also functions as an object that can be used to build or write a
35+
Python3 code object, since we allow mutable structures.
36+
37+
When done mutating, call method to_native().
38+
39+
For convenience in generating code objects, fields like
40+
`co_consts`, co_names which are (immutable) tuples in the end-result can be stored
41+
instead as (mutable) lists. Likewise, the line number table `co_lnotab`
42+
can be stored as a simple list of offset, line_number tuples.
43+
44+
"""
45+
def __init__(
46+
self,
47+
co_argcount: int,
48+
co_posonlyargcount: int,
49+
co_kwonlyargcount: int,
50+
co_nlocals: int,
51+
co_stacksize: int,
52+
co_flags: int,
53+
co_code: bytes,
54+
co_consts: tuple,
55+
co_names: tuple[str],
56+
co_varnames: tuple[str],
57+
co_filename: str,
58+
co_name: str,
59+
co_qualname: str,
60+
co_firstlineno: int,
61+
co_linetable: bytes,
62+
co_freevars: tuple,
63+
co_cellvars: tuple,
64+
co_exceptiontable = tuple(),
65+
collection_order: Dict[Union[set, frozenset, dict], Tuple[Any]] = {},
66+
version_triple: Tuple[int, int, int] = (0, 0, 0),
67+
) -> None:
68+
self.co_argcount = co_argcount
69+
self.co_posonlyargcount = co_posonlyargcount
70+
self.co_kwonlyargcount = co_kwonlyargcount
71+
self.co_nlocals = co_nlocals
72+
self.co_stacksize = co_stacksize
73+
self.co_flags = co_flags
74+
self.co_code = co_code
75+
self.co_consts = co_consts
76+
self.co_names = co_names
77+
self.co_varnames = co_varnames
78+
self.co_filename = co_filename
79+
self.co_name = co_name
80+
self.co_firstlineno = co_firstlineno # None if 0 in serialized form
81+
self.co_linetable = co_linetable
82+
self.co_qualname = co_qualname
83+
self.co_cellvars = co_cellvars
84+
self.co_linetable = co_linetable
85+
self.co_freevars= co_freevars
86+
self.co_exceptiontable = co_exceptiontable
87+
self.co_collection_order = collection_order
88+
version_triple = version_triple
89+
90+
def co_lines(self):
91+
return [(sl.line, sl.character_offset, sl.character_offset) for sl in self.co_linetable]

xdis/cross_dis.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ def _try_compile(source: str, name: str) -> CodeType:
4444
return c
4545

4646

47-
def code_info(x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation) -> str:
47+
def code_info(
48+
x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation
49+
) -> str:
4850
"""Formatted details of methods, functions, or code."""
49-
return format_code_info(get_code_object(x), version_tuple, python_implementation=python_implementation)
51+
return format_code_info(
52+
get_code_object(x), version_tuple, python_implementation=python_implementation
53+
)
5054

5155

5256
def get_code_object(x):
@@ -259,7 +263,12 @@ def op_has_argument(opcode: int, opc) -> bool:
259263
"""
260264
Return True if `opcode` instruction has an operand.
261265
"""
262-
return opcode >= opc.HAVE_ARGUMENT
266+
return (
267+
opcode in opc.hasarg
268+
if hasattr(opc, "hasarg")
269+
and opc.python_implementation is PythonImplementation.RustPython
270+
else opcode >= opc.HAVE_ARGUMENT
271+
)
263272

264273

265274
def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str:
@@ -269,7 +278,10 @@ def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str:
269278
for i in range(32):
270279
flag = 1 << i
271280
if flags & flag:
272-
if python_implementation == PythonImplementation.PyPy and flag in PYPY_COMPILER_FLAG_NAMES:
281+
if (
282+
python_implementation == PythonImplementation.PyPy
283+
and flag in PYPY_COMPILER_FLAG_NAMES
284+
):
273285
names.append(PYPY_COMPILER_FLAG_NAMES.get(flag, hex(flag)))
274286
else:
275287
names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
@@ -320,7 +332,9 @@ def format_code_info(
320332
pass
321333

322334
if version_tuple >= (1, 3):
323-
lines.append("# Flags: %s" % pretty_flags(co.co_flags, python_implementation))
335+
lines.append(
336+
"# Flags: %s" % pretty_flags(co.co_flags, python_implementation)
337+
)
324338

325339
if version_tuple >= (1, 5):
326340
lines.append("# First Line: %s" % co.co_firstlineno)
@@ -373,7 +387,9 @@ def format_exception_table(bytecode, version_tuple) -> str:
373387
for entry in bytecode.exception_entries:
374388
lasti = " lasti" if entry.lasti else ""
375389
end = entry.end - 2
376-
lines.append(f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}")
390+
lines.append(
391+
f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}"
392+
)
377393
return "\n".join(lines)
378394

379395

@@ -414,7 +430,11 @@ def unpack_opargs_bytecode(code, opc):
414430
offset += 1
415431
if op_has_argument(op, opc):
416432
arg = code2num(code, offset) | extended_arg
417-
extended_arg = extended_arg_val(opc, arg) if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG else 0
433+
extended_arg = (
434+
extended_arg_val(opc, arg)
435+
if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG
436+
else 0
437+
)
418438
offset += 2
419439
else:
420440
arg = None
@@ -474,7 +494,7 @@ def xstack_effect(opcode, opc, oparg: int = 0, jump=None):
474494
if opname == "BUILD_MAP" and version_tuple >= (3, 5):
475495
return 1 - (2 * oparg)
476496
if opname in ("UNPACK_SEQUENCE",):
477-
return oparg - 1
497+
return oparg - 1
478498
elif opname in ("UNPACK_EX"):
479499
return (oparg & 0xFF) + (oparg >> 8)
480500
elif opname == "BUILD_INTERPOLATION":

xdis/disasm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def get_opcode(
6868
lookup = "3.12.7Graal"
6969
else:
7070
lookup += "Graal"
71+
elif python_implementation == PythonImplementation.RustPython:
72+
lookup += "Rust"
7173
if lookup in op_imports.keys():
7274
if alternate_opmap is not None:
7375
# TODO: change bytecode version number comment line to indicate altered

xdis/load.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ def load_module_from_file_object(
224224

225225
timestamp = 0
226226
file_offsets = {}
227-
python_implementation = PythonImplementation.CPython
228227
try:
229228
magic = fp.read(4)
230229
magic_int = magic2int(magic)
@@ -250,7 +249,6 @@ def load_module_from_file_object(
250249
RUSTPYTHON_MAGICS
251250
) + list(JYTHON_MAGICS):
252251
version = magicint2version.get(magic_int, "")
253-
raise ImportError(f"Magic int {magic_int} ({version}) is not supported.")
254252

255253
if magic_int in INTERIM_MAGIC_INTS:
256254
raise ImportError(
@@ -355,8 +353,15 @@ def load_module_from_file_object(
355353
finally:
356354
fp.close()
357355

356+
python_implementation = PythonImplementation.RustPython if magic_int in RUSTPYTHON_MAGICS else PythonImplementation.CPython
358357
if is_pypy(magic_int, filename):
359358
python_implementation = PythonImplementation.PyPy
359+
elif magic_int in RUSTPYTHON_MAGICS:
360+
python_implementation = PythonImplementation.RustPython
361+
elif magic_int in GRAAL3_MAGICS:
362+
python_implementation = PythonImplementation.Graal
363+
else:
364+
python_implementation = PythonImplementation.CPython
360365

361366
# Below we need to return co.version_triple instead of version_triple,
362367
# because Graal uses the *same* magic number but different bytecode

xdis/op_imports.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
opcode_312,
6565
opcode_312graal,
6666
opcode_313,
67+
opcode_313rust,
6768
opcode_314,
6869
)
6970
from xdis.version_info import PythonImplementation, version_tuple_to_str
@@ -187,6 +188,7 @@
187188
"3.12.0rc2": opcode_312,
188189
"3.12.0": opcode_312,
189190
"3.13.0rc3": opcode_313,
191+
"3.13.0Rust": opcode_313rust,
190192
"3.14b3": opcode_314,
191193
"3.14.0": opcode_314,
192194
"3.14": opcode_314,

xdis/opcodes/base.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,15 @@ def call_op(
188188
Put opcode in the class of instructions that perform calls.
189189
"""
190190
loc["callop"].add(opcode)
191+
if "hasarg" in loc:
192+
loc["hasarg"].append(opcode)
191193
nargs_op(loc, name, opcode, pop, push, fallthrough)
192194

193195

194196
def compare_op(loc: dict, name: str, opcode: int, pop: int = 2, push: int = 1) -> None:
195197
def_op(loc, name, opcode, pop, push)
198+
if "hasarg" in loc:
199+
loc["hasarg"].append(opcode)
196200
loc["hascompare"].append(opcode)
197201
loc["binaryop"].add(opcode)
198202

@@ -203,6 +207,8 @@ def conditional_op(loc: dict, name: str, opcode: int) -> None:
203207

204208
def const_op(loc: dict, name: str, opcode: int, pop: int = 0, push: int = 1) -> None:
205209
def_op(loc, name, opcode, pop, push)
210+
if "hasarg" in loc:
211+
loc["hasarg"].append(opcode)
206212
loc["hasconst"].append(opcode)
207213
loc["nullaryop"].add(opcode)
208214

@@ -225,6 +231,8 @@ def def_op(
225231

226232
def free_op(loc: dict, name: str, opcode: int, pop: int = 0, push: int = 1) -> None:
227233
def_op(loc, name, opcode, pop, push)
234+
if "hasarg" in loc:
235+
loc["hasarg"].append(opcode)
228236
loc["hasfree"].append(opcode)
229237

230238

@@ -242,6 +250,8 @@ def jabs_op(
242250
"""
243251
def_op(loc, name, opcode, pop, push, fallthrough=fallthrough)
244252
loc["hasjabs"].append(opcode)
253+
if "hasarg" in loc:
254+
loc["hasarg"].append(opcode)
245255
if conditional:
246256
loc["hascondition"].append(opcode)
247257

@@ -252,12 +262,16 @@ def jrel_op(loc, name: str, opcode: int, pop: int=0, push: int=0, conditional=Fa
252262
"""
253263
def_op(loc, name, opcode, pop, push, fallthrough)
254264
loc["hasjrel"].append(opcode)
265+
if "hasarg" in loc:
266+
loc["hasarg"].append(opcode)
255267
if conditional:
256268
loc["hascondition"].append(opcode)
257269

258270

259271
def local_op(loc, name, opcode: int, pop=0, push=1) -> None:
260272
def_op(loc, name, opcode, pop, push)
273+
if "hasarg" in loc:
274+
loc["hasarg"].append(opcode)
261275
loc["haslocal"].append(opcode)
262276
loc["nullaryop"].add(opcode)
263277

@@ -268,6 +282,8 @@ def name_op(loc: dict, op_name, opcode: int, pop=-2, push=-2) -> None:
268282
"""
269283
def_op(loc, op_name, opcode, pop, push)
270284
loc["hasname"].append(opcode)
285+
if "hasarg" in loc:
286+
loc["hasarg"].append(opcode)
271287
loc["nullaryop"].add(opcode)
272288

273289

@@ -278,6 +294,8 @@ def nargs_op(
278294
Put opcode in the class of instructions that have a variable number of (or *n*) arguments
279295
"""
280296
def_op(loc, name, opcode, pop, push, fallthrough=fallthrough)
297+
if "hasarg" in loc:
298+
loc["hasarg"].append(opcode)
281299
loc["hasnargs"].append(opcode)
282300

283301

xdis/opcodes/opcode_10.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) Copyright 2019-2023 by Rocky Bernstein
1+
# (C) Copyright 2019-2023, 2025 by Rocky Bernstein
22
#
33
# This program is free software; you can redistribute it and/or
44
# modify it under the terms of the GNU General Public License
@@ -16,16 +16,16 @@
1616
"""
1717
CPython 1.0 bytecode opcodes
1818
19-
This is used in bytecode disassembly. This is similar to the
20-
opcodes in Python's dis.py library.
19+
This is like Python 1.0's dis.py with some classification
20+
of stack usage and information for formatting instructions.
2121
"""
2222

2323
import xdis.opcodes.opcode_11 as opcode_11
2424

2525
# This is used from outside this module
2626
from xdis.cross_dis import findlabels # noqa
2727
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
28-
cpython_implementation as python_implementation,
28+
cpython_implementation,
2929
finalize_opcodes,
3030
init_opdata,
3131
name_op,
@@ -35,6 +35,7 @@
3535
from xdis.opcodes.opcode_11 import opcode_arg_fmt11, opcode_extended_fmt11
3636

3737
version_tuple = (1, 0)
38+
python_implementation = cpython_implementation
3839

3940
loc = locals()
4041
init_opdata(loc, opcode_11, version_tuple)

xdis/opcodes/opcode_11.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
# This is used from outside this module
2626
from xdis.cross_dis import findlabels
2727
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
28-
cpython_implementation as python_implementation,
28+
cpython_implementation,
2929
finalize_opcodes,
3030
init_opdata,
3131
update_pj2,
3232
)
3333
from xdis.opcodes.opcode_12 import opcode_arg_fmt12, opcode_extended_fmt12
3434

3535
version_tuple = (1, 1) # 1.2 is the same
36+
python_implementation = cpython_implementation
3637

3738
loc = locals()
3839
init_opdata(loc, opcode_12, version_tuple)

xdis/opcodes/opcode_12.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# This is used from outside this module
2626
from xdis.cross_dis import findlabels # noqa
2727
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
28-
cpython_implementation as python_implementation,
28+
cpython_implementation,
2929
finalize_opcodes,
3030
init_opdata,
3131
name_op,
@@ -36,6 +36,7 @@
3636
from xdis.opcodes.opcode_13 import opcode_arg_fmt13, opcode_extended_fmt13
3737

3838
version_tuple = (1, 2)
39+
python_implementation = cpython_implementation
3940

4041
loc = locals()
4142
init_opdata(loc, opcode_13, version_tuple)

0 commit comments

Comments
 (0)