Skip to content

gh-91869: Fix tracing of specialized instructions with extended args (alternate) #91974

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

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
18 changes: 9 additions & 9 deletions Include/internal/pycore_opcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ const uint8_t _PyOpcode_Deopt[256] = {
[DICT_MERGE] = DICT_MERGE,
[DICT_UPDATE] = DICT_UPDATE,
[END_ASYNC_FOR] = END_ASYNC_FOR,
[EXTENDED_ARG] = EXTENDED_ARG,
[EXTENDED_ARG] = EXTENDED_ARG_TRACE,
[EXTENDED_ARG_TRACE] = EXTENDED_ARG_TRACE,
[FORMAT_VALUE] = FORMAT_VALUE,
[FOR_ITER] = FOR_ITER,
[GET_AITER] = GET_AITER,
Expand Down Expand Up @@ -381,44 +382,44 @@ static const char *const _PyOpcode_OpName[256] = {
[JUMP_BACKWARD] = "JUMP_BACKWARD",
[PRECALL_PYFUNC] = "PRECALL_PYFUNC",
[CALL_FUNCTION_EX] = "CALL_FUNCTION_EX",
[RESUME_QUICK] = "RESUME_QUICK",
[EXTENDED_ARG_TRACE] = "EXTENDED_ARG_TRACE",
[EXTENDED_ARG] = "EXTENDED_ARG",
[LIST_APPEND] = "LIST_APPEND",
[SET_ADD] = "SET_ADD",
[MAP_ADD] = "MAP_ADD",
[LOAD_CLASSDEREF] = "LOAD_CLASSDEREF",
[COPY_FREE_VARS] = "COPY_FREE_VARS",
[STORE_ATTR_ADAPTIVE] = "STORE_ATTR_ADAPTIVE",
[RESUME_QUICK] = "RESUME_QUICK",
[RESUME] = "RESUME",
[MATCH_CLASS] = "MATCH_CLASS",
[STORE_ATTR_ADAPTIVE] = "STORE_ATTR_ADAPTIVE",
[STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE",
[STORE_ATTR_SLOT] = "STORE_ATTR_SLOT",
[FORMAT_VALUE] = "FORMAT_VALUE",
[BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP",
[BUILD_STRING] = "BUILD_STRING",
[STORE_ATTR_SLOT] = "STORE_ATTR_SLOT",
[STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT",
[STORE_FAST__LOAD_FAST] = "STORE_FAST__LOAD_FAST",
[LOAD_METHOD] = "LOAD_METHOD",
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
[STORE_FAST__LOAD_FAST] = "STORE_FAST__LOAD_FAST",
[LIST_EXTEND] = "LIST_EXTEND",
[SET_UPDATE] = "SET_UPDATE",
[DICT_MERGE] = "DICT_MERGE",
[DICT_UPDATE] = "DICT_UPDATE",
[PRECALL] = "PRECALL",
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
[STORE_SUBSCR_ADAPTIVE] = "STORE_SUBSCR_ADAPTIVE",
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT",
[STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT",
[UNPACK_SEQUENCE_ADAPTIVE] = "UNPACK_SEQUENCE_ADAPTIVE",
[CALL] = "CALL",
[KW_NAMES] = "KW_NAMES",
[POP_JUMP_BACKWARD_IF_NOT_NONE] = "POP_JUMP_BACKWARD_IF_NOT_NONE",
[POP_JUMP_BACKWARD_IF_NONE] = "POP_JUMP_BACKWARD_IF_NONE",
[POP_JUMP_BACKWARD_IF_FALSE] = "POP_JUMP_BACKWARD_IF_FALSE",
[POP_JUMP_BACKWARD_IF_TRUE] = "POP_JUMP_BACKWARD_IF_TRUE",
[UNPACK_SEQUENCE_ADAPTIVE] = "UNPACK_SEQUENCE_ADAPTIVE",
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
[UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE",
[UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE",
[180] = "<180>",
[181] = "<181>",
[182] = "<182>",
[183] = "<183>",
Expand Down Expand Up @@ -498,7 +499,6 @@ static const char *const _PyOpcode_OpName[256] = {
#endif

#define EXTRA_CASES \
case 180: \
case 181: \
case 182: \
case 183: \
Expand Down
29 changes: 15 additions & 14 deletions Include/opcode.h

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

2 changes: 1 addition & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,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 = (3493).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3494).to_bytes(2, 'little') + b'\r\n'

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

Expand Down
2 changes: 2 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ def jabs_op(name, op, entries=0):

def_op('CALL_FUNCTION_EX', 142) # Flags

def_op('EXTENDED_ARG_TRACE', 143)
def_op('EXTENDED_ARG', 144)
EXTENDED_ARG = 144

def_op('LIST_APPEND', 145)
def_op('SET_ADD', 146)
def_op('MAP_ADD', 147)
Expand Down
12 changes: 10 additions & 2 deletions Lib/test/test__opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ def test_stack_effect(self):
self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
# All defined opcodes
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
opnames = {name: op for name, op in dis.opmap.items()
if name not in dis.deoptmap
and name != 'EXTENDED_ARG_TRACE'}
for name, code in opnames.items():
with self.subTest(opname=name):
if code < dis.HAVE_ARGUMENT:
stack_effect(code)
Expand All @@ -31,6 +34,8 @@ def test_stack_effect(self):
with self.subTest(opcode=code):
self.assertRaises(ValueError, stack_effect, code)
self.assertRaises(ValueError, stack_effect, code, 0)
self.assertRaises(ValueError, stack_effect,
dis.opmap["EXTENDED_ARG_TRACE"])

def test_stack_effect_jump(self):
JUMP_IF_TRUE_OR_POP = dis.opmap['JUMP_IF_TRUE_OR_POP']
Expand All @@ -47,7 +52,10 @@ def test_stack_effect_jump(self):
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
# All defined opcodes
has_jump = dis.hasjabs + dis.hasjrel
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
opnames = {name: op for name, op in dis.opmap.items()
if name not in dis.deoptmap
and name != 'EXTENDED_ARG_TRACE'}
for name, code in opnames.items():
with self.subTest(opname=name):
if code < dis.HAVE_ARGUMENT:
common = stack_effect(code)
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2432,5 +2432,47 @@ def gen():
output.append(5)


class TestExtendedArgs(unittest.TestCase):

def setUp(self):
self.addCleanup(sys.settrace, sys.gettrace())
sys.settrace(None)

def count_traces(self, func):
# warmup
for _ in range(20):
func()

counts = {"call": 0, "line": 0, "return": 0}
def trace(frame, event, arg):
counts[event] += 1
return trace

sys.settrace(trace)
func()
sys.settrace(None)

return counts

def test_trace_unpack_long_sequence(self):
ns = {}
code = "def f():\n (" + "y,\n "*300 + ") = range(300)"
exec(code, ns)
counts = self.count_traces(ns["f"])
self.assertEqual(counts, {'call': 1, 'line': 301, 'return': 1})

def test_trace_lots_of_globals(self):
code = """if 1:
def f():
return (
{}
)
""".format("\n+\n".join(f"var{i}\n" for i in range(1000)))
ns = {f"var{i}": i for i in range(1000)}
exec(code, ns)
counts = self.count_traces(ns["f"])
self.assertEqual(counts, {'call': 1, 'line': 2000, 'return': 1})


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix an issue where specialized opcodes with extended arguments could produce incorrect tracing output or lead to assertion failures.
9 changes: 9 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -5630,6 +5630,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
NOTRACE_DISPATCH_SAME_OPARG();
}

TARGET(EXTENDED_ARG_TRACE) {
assert(oparg);
oparg <<= 8;
oparg |= _Py_OPARG(*next_instr);
opcode = _PyOpcode_Deopt[_Py_OPCODE(*next_instr)];
PRE_DISPATCH_GOTO();
DISPATCH_GOTO();
}

TARGET(CACHE) {
Py_UNREACHABLE();
}
Expand Down
14 changes: 7 additions & 7 deletions Python/opcode_targets.h

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

1 change: 1 addition & 0 deletions Tools/scripts/generate_opcode_h.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
for basic, family in opcode["_specializations"].items():
for specialized in family:
deoptcodes[specialized] = basic
deoptcodes["EXTENDED_ARG"] = "EXTENDED_ARG_TRACE"
iobj.write("\nconst uint8_t _PyOpcode_Deopt[256] = {\n")
for opt, deopt in sorted(deoptcodes.items()):
iobj.write(f" [{opt}] = {deopt},\n")
Expand Down