-
Notifications
You must be signed in to change notification settings - Fork 52
Description
Currently, we represent exits from optimized code as DEOPT_IF
, rather than EXIT_IF
.
This is because, for tier 1, we de-optimize the tier 1 bytecode if we get sufficient (~53) exits.
However, for tier 2, we don't want to de-optimize for most of these, we just want to exit.
De-optimization is much more expensive in tier 2, and we can support multiple traces for a single tier 1 instruction.
Kinds of exits
There are 172 DEOPT_IF
s in bytecodes.c.
Type checks
E.g. DEOPT_IF(!PyFunction_Check(callable));
These are the most common form of exits.
For these exits, we want to stitch a new trace to the exit, once it has warmed up.
Infrequent event checks
E.g. DEOPT_IF(eval_breaker != version);
These check for the eval breaker, recursion limits, or other such events.
These are infrequent, but will happen. However, we always want to abandon the optimized code, but never de-optimize. We let tier 0 handle the event and then continue as before.
"Never happen" events
E.g. DEOPT_IF(tstate->interp->eval_frame);
These represent a mis-optimization. In these case, we do want to de-optimize, not merely exit to a lower tier.
For most of these events, we also want to keep a global flag to avoid doing the optimization/de-optimization dance in the future.
Value checks
E.g. DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub));
, DEOPT_IF(PyTuple_GET_SIZE(seq) != 2);
We should treat these the same as type checks.
Likelihood of exits
Some type or value checks are much more likely to fail than others.
For those that are sufficiently infrequent, it could be useful (as a memory saving) to convert them into "never happen" checks.
There would no need to allocate memory to hold the exit stub, but it would be considerably more expensive if the exit did happen.
However, it might be very difficult to identify these exits.