Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Each instruction is two codewords, and consists of "opcode, oparg, 0, 0" #100106

Closed
wants to merge 8 commits into from
Closed
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
3 changes: 3 additions & 0 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 32 additions & 9 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
_cache_format,
_inline_cache_entries,
_nb_ops,
_opsize,
_specializations,
_specialized_instructions,
)
Expand Down Expand Up @@ -346,11 +347,12 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
line_offset = first_line - co.co_firstlineno
else:
line_offset = 0
co_positions = _get_co_positions(co, show_caches=show_caches)
return _get_instructions_bytes(_get_code_array(co, adaptive),
co._varname_from_oparg,
co.co_names, co.co_consts,
linestarts, line_offset,
co_positions=co.co_positions(),
co_positions=co_positions,
show_caches=show_caches)

def _get_const_value(op, arg, co_consts):
Expand Down Expand Up @@ -422,6 +424,25 @@ def _parse_exception_table(code):
def _is_backward_jump(op):
return 'JUMP_BACKWARD' in opname[op]

def _get_co_positions(code, show_caches=False):
# generate all co_positions, with or without caches,
# skipping the oparg2, oparg3 codewords.

if code is None:
return iter(())

ops = code.co_code[::2]
prev_op = 0
for op, positions in zip(ops, code.co_positions()):
assert _opsize in (1, 2)
if _opsize == 2 and prev_op != 0:
# skip oparg2, oparg3
prev_op = op
continue
if show_caches or op != CACHE:
yield positions
prev_op = op

def _get_instructions_bytes(code, varname_from_oparg=None,
names=None, co_consts=None,
linestarts=None, line_offset=0,
Expand Down Expand Up @@ -476,7 +497,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
argrepr = "to " + repr(argval)
elif deop in hasjrel:
signed_arg = -arg if _is_backward_jump(deop) else arg
argval = offset + 2 + signed_arg*2
argval = offset + (signed_arg + _opsize) * 2
if deop == FOR_ITER:
argval += 2
argrepr = "to " + repr(argval)
Expand Down Expand Up @@ -504,9 +525,6 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
if not caches:
continue
if not show_caches:
# We still need to advance the co_positions iterator:
for _ in range(caches):
next(co_positions, ())
continue
for name, size in _cache_format[opname[deop]].items():
for i in range(size):
Expand All @@ -527,11 +545,13 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
"""Disassemble a code object."""
linestarts = dict(findlinestarts(co))
exception_entries = _parse_exception_table(co)
co_positions = _get_co_positions(co, show_caches=show_caches)
_disassemble_bytes(_get_code_array(co, adaptive),
lasti, co._varname_from_oparg,
co.co_names, co.co_consts, linestarts, file=file,
exception_entries=exception_entries,
co_positions=co.co_positions(), show_caches=show_caches)
co_positions=co_positions,
show_caches=show_caches)

def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False):
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive)
Expand Down Expand Up @@ -609,6 +629,7 @@ def _unpack_opargs(code):
op = code[i]
deop = _deoptop(op)
caches = _inline_cache_entries[deop]
caches += _opsize - 1 # also skip over oparg2, oparg3
if deop in hasarg:
arg = code[i+1] | extended_arg
extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0
Expand All @@ -635,7 +656,7 @@ def findlabels(code):
if deop in hasjrel:
if _is_backward_jump(deop):
arg = -arg
label = offset + 2 + arg*2
label = offset + (arg + _opsize) * 2
if deop == FOR_ITER:
label += 2
elif deop in hasjabs:
Expand Down Expand Up @@ -722,13 +743,14 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False

def __iter__(self):
co = self.codeobj
co_positions = _get_co_positions(co, show_caches=self.show_caches)
return _get_instructions_bytes(_get_code_array(co, self.adaptive),
co._varname_from_oparg,
co.co_names, co.co_consts,
self._linestarts,
line_offset=self._line_offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
co_positions=co_positions,
show_caches=self.show_caches)

def __repr__(self):
Expand Down Expand Up @@ -756,6 +778,7 @@ def dis(self):
else:
offset = -1
with io.StringIO() as output:
co_positions=_get_co_positions(co, show_caches=self.show_caches)
_disassemble_bytes(_get_code_array(co, self.adaptive),
varname_from_oparg=co._varname_from_oparg,
names=co.co_names, co_consts=co.co_consts,
Expand All @@ -764,7 +787,7 @@ def dis(self):
file=output,
lasti=offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
co_positions=co_positions,
show_caches=self.show_caches)
return output.getvalue()

Expand Down
2 changes: 1 addition & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3512).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3518).to_bytes(2, 'little') + b'\r\n'

_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

Expand Down
3 changes: 3 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,9 @@ def pseudo_op(name, op, real_ops):
"deopt",
]

# number of codewords for opcode+oparg(s)
_opsize = 2

_cache_format = {
"LOAD_GLOBAL": {
"counter": 1,
Expand Down
30 changes: 17 additions & 13 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
gc_collect)
from test.support.script_helper import assert_python_ok
from test.support import threading_helper
from opcode import opmap
from opcode import opmap, opname
COPY_FREE_VARS = opmap['COPY_FREE_VARS']


Expand Down Expand Up @@ -192,7 +192,7 @@ def create_closure(__class__):

def new_code(c):
'''A new code object with a __class__ cell added to freevars'''
return c.replace(co_freevars=c.co_freevars + ('__class__',), co_code=bytes([COPY_FREE_VARS, 1])+c.co_code)
return c.replace(co_freevars=c.co_freevars + ('__class__',), co_code=bytes([COPY_FREE_VARS, 1, 0, 0])+c.co_code)

def add_foreign_method(cls, name, f):
code = new_code(f.__code__)
Expand Down Expand Up @@ -339,15 +339,19 @@ def func():
self.assertEqual(list(new_code.co_lines()), [])

def test_invalid_bytecode(self):
def foo(): pass
foo.__code__ = co = foo.__code__.replace(co_code=b'\xee\x00d\x00S\x00')
def foo():
pass

with self.assertRaises(SystemError) as se:
foo()
self.assertEqual(
f"{co.co_filename}:{co.co_firstlineno}: unknown opcode 238",
str(se.exception))
# assert that opcode 238 is invalid
self.assertEqual(opname[238], '<238>')

# change first opcode to 0xee (=238)
foo.__code__ = foo.__code__.replace(
co_code=b'\xee' + foo.__code__.co_code[1:])

msg = f"unknown opcode 238"
with self.assertRaisesRegex(SystemError, msg):
foo()

@requires_debug_ranges()
def test_co_positions_artificial_instructions(self):
Expand All @@ -368,7 +372,7 @@ def test_co_positions_artificial_instructions(self):
artificial_instructions = []
for instr, positions in zip(
dis.get_instructions(code, show_caches=True),
code.co_positions(),
dis._get_co_positions(code, show_caches=True),
strict=True
):
# If any of the positions is None, then all have to
Expand Down Expand Up @@ -695,9 +699,9 @@ def f():
co_firstlineno=42,
co_code=bytes(
[
dis.opmap["RESUME"], 0,
dis.opmap["LOAD_ASSERTION_ERROR"], 0,
dis.opmap["RAISE_VARARGS"], 1,
dis.opmap["RESUME"], 0, 0, 0,
dis.opmap["LOAD_ASSERTION_ERROR"], 0, 0, 0,
dis.opmap["RAISE_VARARGS"], 1, 0, 0,
]
),
co_linetable=bytes(
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,9 @@ def assertOpcodeSourcePositionIs(self, code, opcode,
line, end_line, column, end_column, occurrence=1):

for instr, position in zip(
dis.Bytecode(code, show_caches=True), code.co_positions(), strict=True
dis.Bytecode(code, show_caches=True),
dis._get_co_positions(code, show_caches=True),
strict=True
):
if instr.opname == opcode:
occurrence -= 1
Expand Down
Loading