Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed test/bytecode_graal310/02_while1else.graalpy310.pyc
Binary file not shown.
91 changes: 91 additions & 0 deletions xdis/codetype/code313rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# (C) Copyright 2025 by Rocky Bernstein
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

from dataclasses import dataclass
from typing import Any, Dict, Tuple, Union

from xdis.codetype.base import CodeBase


@dataclass
class SourceLocation:
# line: 1-based int
line: int
# character_offset: 1-based int (constructed from a zero-indexed stored value)
character_offset: int


class Code313Rust(CodeBase):
"""Class for a RustPython 3.13 code object used when a Python
interpreter is not RustPython 3.13 but working on RustPython 3.13 bytecode. It
also functions as an object that can be used to build or write a
Python3 code object, since we allow mutable structures.

When done mutating, call method to_native().

For convenience in generating code objects, fields like
`co_consts`, co_names which are (immutable) tuples in the end-result can be stored
instead as (mutable) lists. Likewise, the line number table `co_lnotab`
can be stored as a simple list of offset, line_number tuples.

"""
def __init__(
self,
co_argcount: int,
co_posonlyargcount: int,
co_kwonlyargcount: int,
co_nlocals: int,
co_stacksize: int,
co_flags: int,
co_code: bytes,
co_consts: tuple,
co_names: tuple[str],
co_varnames: tuple[str],
co_filename: str,
co_name: str,
co_qualname: str,
co_firstlineno: int,
co_linetable: bytes,
co_freevars: tuple,
co_cellvars: tuple,
co_exceptiontable = tuple(),
collection_order: Dict[Union[set, frozenset, dict], Tuple[Any]] = {},
version_triple: Tuple[int, int, int] = (0, 0, 0),
) -> None:
self.co_argcount = co_argcount
self.co_posonlyargcount = co_posonlyargcount
self.co_kwonlyargcount = co_kwonlyargcount
self.co_nlocals = co_nlocals
self.co_stacksize = co_stacksize
self.co_flags = co_flags
self.co_code = co_code
self.co_consts = co_consts
self.co_names = co_names
self.co_varnames = co_varnames
self.co_filename = co_filename
self.co_name = co_name
self.co_firstlineno = co_firstlineno # None if 0 in serialized form
self.co_linetable = co_linetable
self.co_qualname = co_qualname
self.co_cellvars = co_cellvars
self.co_linetable = co_linetable
self.co_freevars= co_freevars
self.co_exceptiontable = co_exceptiontable
self.co_collection_order = collection_order
version_triple = version_triple

def co_lines(self):
return [(sl.line, sl.character_offset, sl.character_offset) for sl in self.co_linetable]
36 changes: 28 additions & 8 deletions xdis/cross_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ def _try_compile(source: str, name: str) -> CodeType:
return c


def code_info(x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation) -> str:
def code_info(
x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation
) -> str:
"""Formatted details of methods, functions, or code."""
return format_code_info(get_code_object(x), version_tuple, python_implementation=python_implementation)
return format_code_info(
get_code_object(x), version_tuple, python_implementation=python_implementation
)


def get_code_object(x):
Expand Down Expand Up @@ -259,7 +263,12 @@ def op_has_argument(opcode: int, opc) -> bool:
"""
Return True if `opcode` instruction has an operand.
"""
return opcode >= opc.HAVE_ARGUMENT
return (
opcode in opc.hasarg
if hasattr(opc, "hasarg")
and opc.python_implementation is PythonImplementation.RustPython
else opcode >= opc.HAVE_ARGUMENT
)


def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str:
Expand All @@ -269,7 +278,10 @@ def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str:
for i in range(32):
flag = 1 << i
if flags & flag:
if python_implementation == PythonImplementation.PyPy and flag in PYPY_COMPILER_FLAG_NAMES:
if (
python_implementation == PythonImplementation.PyPy
and flag in PYPY_COMPILER_FLAG_NAMES
):
names.append(PYPY_COMPILER_FLAG_NAMES.get(flag, hex(flag)))
else:
names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
Expand Down Expand Up @@ -320,7 +332,9 @@ def format_code_info(
pass

if version_tuple >= (1, 3):
lines.append("# Flags: %s" % pretty_flags(co.co_flags, python_implementation))
lines.append(
"# Flags: %s" % pretty_flags(co.co_flags, python_implementation)
)

if version_tuple >= (1, 5):
lines.append("# First Line: %s" % co.co_firstlineno)
Expand Down Expand Up @@ -373,7 +387,9 @@ def format_exception_table(bytecode, version_tuple) -> str:
for entry in bytecode.exception_entries:
lasti = " lasti" if entry.lasti else ""
end = entry.end - 2
lines.append(f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}")
lines.append(
f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}"
)
return "\n".join(lines)


Expand Down Expand Up @@ -414,7 +430,11 @@ def unpack_opargs_bytecode(code, opc):
offset += 1
if op_has_argument(op, opc):
arg = code2num(code, offset) | extended_arg
extended_arg = extended_arg_val(opc, arg) if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG else 0
extended_arg = (
extended_arg_val(opc, arg)
if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG
else 0
)
offset += 2
else:
arg = None
Expand Down Expand Up @@ -474,7 +494,7 @@ def xstack_effect(opcode, opc, oparg: int = 0, jump=None):
if opname == "BUILD_MAP" and version_tuple >= (3, 5):
return 1 - (2 * oparg)
if opname in ("UNPACK_SEQUENCE",):
return oparg - 1
return oparg - 1
elif opname in ("UNPACK_EX"):
return (oparg & 0xFF) + (oparg >> 8)
elif opname == "BUILD_INTERPOLATION":
Expand Down
2 changes: 2 additions & 0 deletions xdis/disasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def get_opcode(
lookup = "3.12.7Graal"
else:
lookup += "Graal"
elif python_implementation == PythonImplementation.RustPython:
lookup += "Rust"
if lookup in op_imports.keys():
if alternate_opmap is not None:
# TODO: change bytecode version number comment line to indicate altered
Expand Down
9 changes: 7 additions & 2 deletions xdis/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ def load_module_from_file_object(

timestamp = 0
file_offsets = {}
python_implementation = PythonImplementation.CPython
try:
magic = fp.read(4)
magic_int = magic2int(magic)
Expand All @@ -250,7 +249,6 @@ def load_module_from_file_object(
RUSTPYTHON_MAGICS
) + list(JYTHON_MAGICS):
version = magicint2version.get(magic_int, "")
raise ImportError(f"Magic int {magic_int} ({version}) is not supported.")

if magic_int in INTERIM_MAGIC_INTS:
raise ImportError(
Expand Down Expand Up @@ -355,8 +353,15 @@ def load_module_from_file_object(
finally:
fp.close()

python_implementation = PythonImplementation.RustPython if magic_int in RUSTPYTHON_MAGICS else PythonImplementation.CPython
if is_pypy(magic_int, filename):
python_implementation = PythonImplementation.PyPy
elif magic_int in RUSTPYTHON_MAGICS:
python_implementation = PythonImplementation.RustPython
elif magic_int in GRAAL3_MAGICS:
python_implementation = PythonImplementation.Graal
else:
python_implementation = PythonImplementation.CPython

# Below we need to return co.version_triple instead of version_triple,
# because Graal uses the *same* magic number but different bytecode
Expand Down
2 changes: 2 additions & 0 deletions xdis/op_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
opcode_312,
opcode_312graal,
opcode_313,
opcode_313rust,
opcode_314,
)
from xdis.version_info import PythonImplementation, version_tuple_to_str
Expand Down Expand Up @@ -187,6 +188,7 @@
"3.12.0rc2": opcode_312,
"3.12.0": opcode_312,
"3.13.0rc3": opcode_313,
"3.13.0Rust": opcode_313rust,
"3.14b3": opcode_314,
"3.14.0": opcode_314,
"3.14": opcode_314,
Expand Down
18 changes: 18 additions & 0 deletions xdis/opcodes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,15 @@ def call_op(
Put opcode in the class of instructions that perform calls.
"""
loc["callop"].add(opcode)
if "hasarg" in loc:
loc["hasarg"].append(opcode)
nargs_op(loc, name, opcode, pop, push, fallthrough)


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

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

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

Expand All @@ -225,6 +231,8 @@ def def_op(

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


Expand All @@ -242,6 +250,8 @@ def jabs_op(
"""
def_op(loc, name, opcode, pop, push, fallthrough=fallthrough)
loc["hasjabs"].append(opcode)
if "hasarg" in loc:
loc["hasarg"].append(opcode)
if conditional:
loc["hascondition"].append(opcode)

Expand All @@ -252,12 +262,16 @@ def jrel_op(loc, name: str, opcode: int, pop: int=0, push: int=0, conditional=Fa
"""
def_op(loc, name, opcode, pop, push, fallthrough)
loc["hasjrel"].append(opcode)
if "hasarg" in loc:
loc["hasarg"].append(opcode)
if conditional:
loc["hascondition"].append(opcode)


def local_op(loc, name, opcode: int, pop=0, push=1) -> None:
def_op(loc, name, opcode, pop, push)
if "hasarg" in loc:
loc["hasarg"].append(opcode)
loc["haslocal"].append(opcode)
loc["nullaryop"].add(opcode)

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


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


Expand Down
9 changes: 5 additions & 4 deletions xdis/opcodes/opcode_10.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) Copyright 2019-2023 by Rocky Bernstein
# (C) Copyright 2019-2023, 2025 by Rocky Bernstein
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,16 +16,16 @@
"""
CPython 1.0 bytecode opcodes

This is used in bytecode disassembly. This is similar to the
opcodes in Python's dis.py library.
This is like Python 1.0's dis.py with some classification
of stack usage and information for formatting instructions.
"""

import xdis.opcodes.opcode_11 as opcode_11

# This is used from outside this module
from xdis.cross_dis import findlabels # noqa
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
cpython_implementation as python_implementation,
cpython_implementation,
finalize_opcodes,
init_opdata,
name_op,
Expand All @@ -35,6 +35,7 @@
from xdis.opcodes.opcode_11 import opcode_arg_fmt11, opcode_extended_fmt11

version_tuple = (1, 0)
python_implementation = cpython_implementation

loc = locals()
init_opdata(loc, opcode_11, version_tuple)
Expand Down
3 changes: 2 additions & 1 deletion xdis/opcodes/opcode_11.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
# This is used from outside this module
from xdis.cross_dis import findlabels
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
cpython_implementation as python_implementation,
cpython_implementation,
finalize_opcodes,
init_opdata,
update_pj2,
)
from xdis.opcodes.opcode_12 import opcode_arg_fmt12, opcode_extended_fmt12

version_tuple = (1, 1) # 1.2 is the same
python_implementation = cpython_implementation

loc = locals()
init_opdata(loc, opcode_12, version_tuple)
Expand Down
3 changes: 2 additions & 1 deletion xdis/opcodes/opcode_12.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# This is used from outside this module
from xdis.cross_dis import findlabels # noqa
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
cpython_implementation as python_implementation,
cpython_implementation,
finalize_opcodes,
init_opdata,
name_op,
Expand All @@ -36,6 +36,7 @@
from xdis.opcodes.opcode_13 import opcode_arg_fmt13, opcode_extended_fmt13

version_tuple = (1, 2)
python_implementation = cpython_implementation

loc = locals()
init_opdata(loc, opcode_13, version_tuple)
Expand Down
5 changes: 3 additions & 2 deletions xdis/opcodes/opcode_13.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

# This is used from outside this module
from xdis.cross_dis import findlabels # noqa
from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa
cpython_implementation as python_implementation,
from xdis.opcodes.base import (
cpython_implementation,
def_op,
finalize_opcodes,
init_opdata,
Expand All @@ -35,6 +35,7 @@
from xdis.opcodes.opcode_1x import opcode_extended_fmt_base1x, update_arg_fmt_base1x

version_tuple = (1, 3)
python_implementation = cpython_implementation

loc = locals()
init_opdata(loc, opcode_14, version_tuple)
Expand Down
Loading