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-109369: Exit tier 2 if executor is invalid #111657

Merged
merged 11 commits into from
Nov 9, 2023
457 changes: 233 additions & 224 deletions Include/internal/pycore_opcode_metadata.h

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1790,3 +1790,28 @@ def test_func(x):
test_func(1000)
sys.monitoring.set_local_events(TEST_TOOL, code, 0)
self.assertEqual(sys.monitoring.get_local_events(TEST_TOOL, code), 0)

class TestTier2Optimizer(CheckEvents):

def test_monitoring_already_opimized_loop(self):
def test_func(recorder):
set_events = sys.monitoring.set_events
line = E.LINE
i = 0
for i in range(551):
# Turn on events without branching once i reaches 500.
set_events(TEST_TOOL, line * int(i >= 500))
pass
pass
pass

self.assertEqual(sys.monitoring._all_events(), {})
events = []
recorder = LineRecorder(events)
sys.monitoring.register_callback(TEST_TOOL, E.LINE, recorder)
try:
test_func(recorder)
finally:
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
sys.monitoring.set_events(TEST_TOOL, 0)
self.assertGreater(len(events), 250)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make sure that tier 2 traces are de-optimized if the code is instrumented
4 changes: 4 additions & 0 deletions Python/abstract_interp_cases.c.h

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

5 changes: 5 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4039,6 +4039,11 @@ dummy_func(
memmove(&stack_pointer[-1 - oparg], &stack_pointer[-oparg], oparg * sizeof(stack_pointer[0]));
}

op(_CHECK_VALIDITY, (--)) {
TIER_TWO_ONLY
DEOPT_IF(!current_executor->base.vm_data.valid);
}


// END BYTECODES //

Expand Down
2 changes: 1 addition & 1 deletion Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
deoptimize:
// On DEOPT_IF we just repeat the last instruction.
// This presumes nothing was popped from the stack (nor pushed).
DPRINTF(2, "DEOPT: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand);
DPRINTF(2, "DEOPT: [Opcode %d, operand %" PRIu64 " @ %d]\n", opcode, operand, (int)(next_uop-current_executor->trace-1));
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
UOP_STAT_INC(opcode, miss);
frame->return_offset = 0; // Dispatch to frame->instr_ptr
Expand Down
6 changes: 6 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.

7 changes: 4 additions & 3 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ translate_bytecode_to_trace(
} \
reserved = (n); // Keep ADD_TO_TRACE / ADD_TO_STUB honest

// Reserve space for main+stub uops, plus 2 for _SET_IP and _EXIT_TRACE
#define RESERVE(main, stub) RESERVE_RAW((main) + (stub) + 2, uop_name(opcode))
// Reserve space for main+stub uops, plus 2 for _SET_IP, _CHECK_VALIDITY and _EXIT_TRACE
markshannon marked this conversation as resolved.
Show resolved Hide resolved
#define RESERVE(main, stub) RESERVE_RAW((main) + (stub) + 3, uop_name(opcode))

// Trace stack operations (used by _PUSH_FRAME, _POP_FRAME)
#define TRACE_STACK_PUSH() \
Expand Down Expand Up @@ -495,8 +495,9 @@ translate_bytecode_to_trace(

top: // Jump here after _PUSH_FRAME or likely branches
for (;;) {
RESERVE_RAW(2, "epilogue"); // Always need space for _SET_IP and _EXIT_TRACE
RESERVE_RAW(3, "epilogue"); // Always need space for _SET_IP, _CHECK_VALIDITY and _EXIT_TRACE
ADD_TO_TRACE(_SET_IP, INSTR_IP(instr, code), 0);
ADD_TO_TRACE(_CHECK_VALIDITY, 0, 0);

uint32_t opcode = instr->op.code;
uint32_t oparg = instr->op.arg;
Expand Down
15 changes: 14 additions & 1 deletion Python/optimizer_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
#include <stddef.h>
#include "pycore_optimizer.h"


static void
remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size)
{
// Note that we don't enter stubs, those SET_IPs are needed.
int last_set_ip = -1;
bool need_ip = true;
bool maybe_invalid = false;
for (int pc = 0; pc < buffer_size; pc++) {
int opcode = buffer[pc].opcode;
if (opcode == _SET_IP) {
Expand All @@ -28,6 +28,16 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size)
need_ip = false;
last_set_ip = pc;
}
else if (opcode == _CHECK_VALIDITY) {
if (maybe_invalid) {
/* Exiting the trace requires that IP is correct */
need_ip = true;
maybe_invalid = false;
}
else {
buffer[pc].opcode = NOP;
}
}
else if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE) {
break;
}
Expand All @@ -36,6 +46,9 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size)
if (_PyOpcode_opcode_metadata[opcode].flags & (HAS_ERROR_FLAG | HAS_DEOPT_FLAG) || opcode == _PUSH_FRAME) {
need_ip = true;
}
if (_PyOpcode_opcode_metadata[opcode].flags & HAS_ESCAPES_FLAG) {
markshannon marked this conversation as resolved.
Show resolved Hide resolved
maybe_invalid = true;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tools/cases_generator/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def analyze_pseudo(self, pseudo: parsing.Pseudo) -> PseudoInstruction:
else:
targets.append(self.macro_instrs[target_name])
assert targets
ignored_flags = {"HAS_EVAL_BREAK_FLAG", "HAS_DEOPT_FLAG", "HAS_ERROR_FLAG"}
ignored_flags = {"HAS_EVAL_BREAK_FLAG", "HAS_DEOPT_FLAG", "HAS_ERROR_FLAG", "HAS_ESCAPES_FLAG"}
assert len({t.instr_flags.bitmap(ignore=ignored_flags) for t in targets}) == 1
return PseudoInstruction(pseudo.name, targets, targets[0].instr_flags)

Expand Down
54 changes: 54 additions & 0 deletions Tools/cases_generator/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@
import parsing
from typing import AbstractSet

WHITELIST = (
"Py_INCREF",
"_PyDictOrValues_IsValues",
"_PyObject_DictOrValuesPointer",
"_PyDictOrValues_GetValues",
"_PyObject_MakeInstanceAttributesFromDict",
"Py_DECREF",
"_Py_DECREF_SPECIALIZED",
"DECREF_INPUTS_AND_REUSE_FLOAT",
"PyUnicode_Append",
"_PyLong_IsZero",
"Py_SIZE",
"Py_TYPE",
"PyList_GET_ITEM",
"PyTuple_GET_ITEM",
"PyList_GET_SIZE",
"PyTuple_GET_SIZE",
"Py_ARRAY_LENGTH",
"Py_Unicode_GET_LENGTH",
"PyUnicode_READ_CHAR",
"_Py_SINGLETON",
"PyUnicode_GET_LENGTH",
"_PyLong_IsCompact",
"_PyLong_IsNonNegativeCompact",
"_PyLong_CompactValue",
"_Py_NewRef",
)

def makes_escaping_api_call(instr: parsing.Node) -> bool:
tkns = iter(instr.tokens)
for tkn in tkns:
if tkn.kind != lx.IDENTIFIER:
continue
try:
next_tkn = next(tkns)
except StopIteration:
return False
if next_tkn.kind != lx.LPAREN:
continue
if not tkn.text.startswith("Py") and not tkn.text.startswith("_Py"):
continue
if tkn.text.endswith("Check"):
continue
if tkn.text.endswith("CheckExact"):
continue
if tkn.text in WHITELIST:
continue
return True
return False

@dataclasses.dataclass
class InstructionFlags:
Expand All @@ -19,6 +68,7 @@ class InstructionFlags:
HAS_EVAL_BREAK_FLAG: bool = False
HAS_DEOPT_FLAG: bool = False
HAS_ERROR_FLAG: bool = False
HAS_ESCAPES_FLAG: bool = False

def __post_init__(self) -> None:
self.bitmask = {name: (1 << i) for i, name in enumerate(self.names())}
Expand Down Expand Up @@ -50,6 +100,10 @@ def fromInstruction(instr: parsing.Node) -> "InstructionFlags":
or variable_used(instr, "exception_unwind")
or variable_used(instr, "resume_with_error")
),
HAS_ESCAPES_FLAG=(
variable_used(instr, "tstate")
or makes_escaping_api_call(instr)
),
markshannon marked this conversation as resolved.
Show resolved Hide resolved
)

@staticmethod
Expand Down
Loading