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

GH-104584: Miscellaneous fixes for -Xuops #106908

Merged
merged 6 commits into from
Jul 20, 2023
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
29 changes: 19 additions & 10 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
co.co_names, co.co_consts,
linestarts, line_offset,
co_positions=co.co_positions(),
show_caches=show_caches)
show_caches=show_caches,
original_code=co.co_code)

def _get_const_value(op, arg, co_consts):
"""Helper to get the value of the const in a hasconst op.
Expand Down Expand Up @@ -504,7 +505,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
names=None, co_consts=None,
linestarts=None, line_offset=0,
exception_entries=(), co_positions=None,
show_caches=False):
show_caches=False, original_code=None):
"""Iterate over the instructions in a bytecode string.

Generates a sequence of Instruction namedtuples giving the details of each
Expand All @@ -513,14 +514,18 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
arguments.

"""
# Use the basic, unadaptive code for finding labels and actually walking the
# bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can
# mess that logic up pretty badly:
original_code = original_code or code
co_positions = co_positions or iter(())
get_name = None if names is None else names.__getitem__
labels = set(findlabels(code))
labels = set(findlabels(original_code))
for start, end, target, _, _ in exception_entries:
for i in range(start, end):
labels.add(target)
starts_line = None
for offset, start_offset, op, arg in _unpack_opargs(code):
for offset, start_offset, op, arg in _unpack_opargs(original_code):
if linestarts is not None:
starts_line = linestarts.get(offset, None)
if starts_line is not None:
Expand All @@ -531,6 +536,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
positions = Positions(*next(co_positions, ()))
deop = _deoptop(op)
caches = _inline_cache_entries[deop]
op = code[offset]
if arg is not None:
# Set argval to the dereferenced value of the argument when
# available, and argrepr to the string representation of argval.
Expand Down Expand Up @@ -591,7 +597,6 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
yield Instruction(_all_opname[op], op,
arg, argval, argrepr,
offset, start_offset, starts_line, is_jump_target, positions)
caches = _inline_cache_entries[deop]
if not caches:
continue
if not show_caches:
Expand Down Expand Up @@ -622,7 +627,8 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
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.co_positions(), show_caches=show_caches,
original_code=co.co_code)

def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False):
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive)
Expand All @@ -640,7 +646,7 @@ def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adap
def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
names=None, co_consts=None, linestarts=None,
*, file=None, line_offset=0, exception_entries=(),
co_positions=None, show_caches=False):
co_positions=None, show_caches=False, original_code=None):
# Omit the line number column entirely if we have no line number info
show_lineno = bool(linestarts)
if show_lineno:
Expand All @@ -661,7 +667,8 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
line_offset=line_offset,
exception_entries=exception_entries,
co_positions=co_positions,
show_caches=show_caches):
show_caches=show_caches,
original_code=original_code):
new_source_line = (show_lineno and
instr.starts_line is not None and
instr.offset > 0)
Expand Down Expand Up @@ -823,7 +830,8 @@ def __iter__(self):
line_offset=self._line_offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
show_caches=self.show_caches)
show_caches=self.show_caches,
original_code=co.co_code)

def __repr__(self):
return "{}({!r})".format(self.__class__.__name__,
Expand Down Expand Up @@ -859,7 +867,8 @@ def dis(self):
lasti=offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
show_caches=self.show_caches)
show_caches=self.show_caches,
original_code=co.co_code)
return output.getvalue()


Expand Down
14 changes: 9 additions & 5 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2368,12 +2368,16 @@ def clear_executors(func):
class TestOptimizerAPI(unittest.TestCase):

def test_get_set_optimizer(self):
self.assertEqual(_testinternalcapi.get_optimizer(), None)
old = _testinternalcapi.get_optimizer()
opt = _testinternalcapi.get_counter_optimizer()
_testinternalcapi.set_optimizer(opt)
self.assertEqual(_testinternalcapi.get_optimizer(), opt)
_testinternalcapi.set_optimizer(None)
self.assertEqual(_testinternalcapi.get_optimizer(), None)
try:
_testinternalcapi.set_optimizer(opt)
self.assertEqual(_testinternalcapi.get_optimizer(), opt)
_testinternalcapi.set_optimizer(None)
self.assertEqual(_testinternalcapi.get_optimizer(), None)
finally:
_testinternalcapi.set_optimizer(old)


def test_counter_optimizer(self):
# Generate a new function at each call
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1244,10 +1244,14 @@ def test_call_specialize(self):
@cpython_only
@requires_specialization
def test_loop_quicken(self):
import _testinternalcapi
# Loop can trigger a quicken where the loop is located
self.code_quicken(loop_test, 1)
got = self.get_disassembly(loop_test, adaptive=True)
self.do_disassembly_compare(got, dis_loop_test_quickened_code)
expected = dis_loop_test_quickened_code
if _testinternalcapi.get_optimizer():
expected = expected.replace("JUMP_BACKWARD ", "ENTER_EXECUTOR")
self.do_disassembly_compare(got, expected)

@cpython_only
def test_extended_arg_quick(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix various hangs, reference leaks, test failures, and tracing/introspection
bugs when running with :envvar:`PYTHONUOPS` or :option:`-X uops <-X>`
enabled.
9 changes: 8 additions & 1 deletion Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2182,7 +2182,14 @@ dummy_func(
JUMPBY(1-oparg);
#if ENABLE_SPECIALIZATION
here[1].cache += (1 << OPTIMIZER_BITS_IN_COUNTER);
if (here[1].cache > tstate->interp->optimizer_backedge_threshold) {
if (here[1].cache > tstate->interp->optimizer_backedge_threshold &&
// Double-check that the opcode isn't instrumented or something:
here->op.code == JUMP_BACKWARD &&
// _PyOptimizer_BackEdge is going to change frame->prev_instr,
// which breaks line event calculations:
next_instr->op.code != INSTRUMENTED_LINE
gvanrossum marked this conversation as resolved.
Show resolved Hide resolved
)
{
OBJECT_STAT_INC(optimization_attempts);
frame = _PyOptimizer_BackEdge(frame, here, next_instr, stack_pointer);
if (frame == NULL) {
Expand Down
8 changes: 8 additions & 0 deletions Python/executor_cases.c.h

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

9 changes: 8 additions & 1 deletion Python/generated_cases.c.h

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

1 change: 1 addition & 0 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ PyUnstable_SetOptimizer(_PyOptimizerObject *optimizer)
_PyInterpreterFrame *
_PyOptimizer_BackEdge(_PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNIT *dest, PyObject **stack_pointer)
{
assert(src->op.code == JUMP_BACKWARD);
PyCodeObject *code = (PyCodeObject *)frame->f_executable;
assert(PyCode_Check(code));
PyInterpreterState *interp = _PyInterpreterState_GET();
Expand Down
4 changes: 4 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,11 @@ init_interp_main(PyThreadState *tstate)
}
if (enabled) {
PyObject *opt = PyUnstable_Optimizer_NewUOpOptimizer();
if (opt == NULL) {
return _PyStatus_ERR("can't initialize optimizer");
}
PyUnstable_SetOptimizer((_PyOptimizerObject *)opt);
Py_DECREF(opt);
}
}

Expand Down
2 changes: 2 additions & 0 deletions Tools/cases_generator/generate_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,8 @@ def write_executor_instructions(self) -> None:
self.out.emit("")
with self.out.block(f"case {thing.name}:"):
instr.write(self.out, tier=TIER_TWO)
if instr.check_eval_breaker:
self.out.emit("CHECK_EVAL_BREAKER();")
self.out.emit("break;")
# elif instr.kind != "op":
# print(f"NOTE: {thing.name} is not a viable uop")
Expand Down
Loading