Skip to content

Commit cf6758f

Browse files
gh-143092: Make CALL_LIST_APPEND and BINARY_OP_INPLACE_ADD_UNICODE normal instructions (GH-143124)
These super instructions need many special cases in the interpreter, specializer, and JIT. It's best we convert them to normal instructions.
1 parent 594a463 commit cf6758f

File tree

14 files changed

+167
-205
lines changed

14 files changed

+167
-205
lines changed

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_tstate.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ typedef struct _PyJitTracerInitialState {
3737

3838
typedef struct _PyJitTracerPreviousState {
3939
bool dependencies_still_valid;
40-
bool instr_is_super;
4140
int code_max_size;
4241
int code_curr_size;
4342
int instr_oparg;

Include/internal/pycore_uop_ids.h

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_opcode_metadata.py

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_opt.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3152,6 +3152,47 @@ def f1():
31523152
"""), PYTHON_JIT="1")
31533153
self.assertEqual(result[0].rc, 0, result)
31543154

3155+
def test_143092(self):
3156+
def f1():
3157+
a = "a"
3158+
for i in range(50):
3159+
x = a[i % len(a)]
3160+
3161+
s = ""
3162+
for _ in range(10):
3163+
s += ""
3164+
3165+
class A: ...
3166+
class B: ...
3167+
3168+
match s:
3169+
case int(): ...
3170+
case str(): ...
3171+
case dict(): ...
3172+
3173+
(
3174+
u0,
3175+
*u1,
3176+
u2,
3177+
u4,
3178+
u5,
3179+
u6,
3180+
u7,
3181+
u8,
3182+
u9, u10, u11,
3183+
u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25, u26, u27, u28, u29,
3184+
) = [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
3185+
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
3186+
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
3187+
None, None, None, None, None, None, None, None,]
3188+
3189+
s = ""
3190+
for _ in range(10):
3191+
s += ""
3192+
s += ""
3193+
3194+
for i in range(TIER2_THRESHOLD * 10):
3195+
f1()
31553196

31563197
def global_identity(x):
31573198
return x
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash in the JIT when dealing with ``list.append(x)`` style code.

Python/bytecodes.c

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ dummy_func(
588588
BINARY_OP_SUBSCR_STR_INT,
589589
BINARY_OP_SUBSCR_DICT,
590590
BINARY_OP_SUBSCR_GETITEM,
591-
// BINARY_OP_INPLACE_ADD_UNICODE, // See comments at that opcode.
591+
BINARY_OP_INPLACE_ADD_UNICODE,
592592
BINARY_OP_EXTEND,
593593
};
594594

@@ -762,13 +762,10 @@ dummy_func(
762762
macro(BINARY_OP_ADD_UNICODE) =
763763
_GUARD_TOS_UNICODE + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_ADD_UNICODE + _POP_TOP_UNICODE + _POP_TOP_UNICODE;
764764

765-
// This is a subtle one. It's a super-instruction for
766-
// BINARY_OP_ADD_UNICODE followed by STORE_FAST
767-
// where the store goes into the left argument.
768-
// So the inputs are the same as for all BINARY_OP
769-
// specializations, but there is no output.
770-
// At the end we just skip over the STORE_FAST.
771-
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) {
765+
// This is a subtle one. We write NULL to the local
766+
// of the following STORE_FAST and leave the result for STORE_FAST
767+
// later to store.
768+
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- res)) {
772769
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
773770
assert(PyUnicode_CheckExact(left_o));
774771
assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(right)));
@@ -796,20 +793,16 @@ dummy_func(
796793
* that the string is safe to mutate.
797794
*/
798795
assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left));
799-
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
800-
DEAD(left);
801796
PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local);
802-
PyObject *right_o = PyStackRef_AsPyObjectSteal(right);
797+
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
803798
PyUnicode_Append(&temp, right_o);
804-
*target_local = PyStackRef_FromPyObjectSteal(temp);
805-
Py_DECREF(right_o);
806-
ERROR_IF(PyStackRef_IsNull(*target_local));
807-
#if TIER_ONE
808-
// The STORE_FAST is already done. This is done here in tier one,
809-
// and during trace projection in tier two:
810-
assert(next_instr->op.code == STORE_FAST);
811-
SKIP_OVER(1);
812-
#endif
799+
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
800+
DEAD(right);
801+
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
802+
DEAD(left);
803+
ERROR_IF(temp == NULL);
804+
res = PyStackRef_FromPyObjectSteal(temp);
805+
*target_local = PyStackRef_NULL;
813806
}
814807

815808
op(_GUARD_BINARY_OP_EXTEND, (descr/4, left, right -- left, right)) {
@@ -4330,8 +4323,7 @@ dummy_func(
43304323
DEOPT_IF(callable_o != interp->callable_cache.list_append);
43314324
}
43324325

4333-
// This is secretly a super-instruction
4334-
op(_CALL_LIST_APPEND, (callable, self, arg -- c, s)) {
4326+
op(_CALL_LIST_APPEND, (callable, self, arg -- none, c, s)) {
43354327
assert(oparg == 1);
43364328
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
43374329

@@ -4344,13 +4336,9 @@ dummy_func(
43444336
}
43454337
c = callable;
43464338
s = self;
4347-
INPUTS_DEAD();
4348-
#if TIER_ONE
4349-
// Skip the following POP_TOP. This is done here in tier one, and
4350-
// during trace projection in tier two:
4351-
assert(next_instr->op.code == POP_TOP);
4352-
SKIP_OVER(1);
4353-
#endif
4339+
DEAD(callable);
4340+
DEAD(self);
4341+
none = PyStackRef_None;
43544342
}
43554343

43564344
op(_CALL_METHOD_DESCRIPTOR_O, (callable, self_or_null, args[oparg] -- res)) {
@@ -5598,15 +5586,9 @@ dummy_func(
55985586
// Super instructions. Instruction deopted. There's a mismatch in what the stack expects
55995587
// in the optimizer. So we have to reflect in the trace correctly.
56005588
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
5601-
if ((_tstate->jit_tracer_state.prev_state.instr->op.code == CALL_LIST_APPEND &&
5602-
opcode == POP_TOP) ||
5603-
(_tstate->jit_tracer_state.prev_state.instr->op.code == BINARY_OP_INPLACE_ADD_UNICODE &&
5604-
opcode == STORE_FAST)) {
5605-
_tstate->jit_tracer_state.prev_state.instr_is_super = true;
5606-
}
5607-
else {
5608-
_tstate->jit_tracer_state.prev_state.instr = next_instr;
5609-
}
5589+
// JIT should have disabled super instructions, as we can
5590+
// do these optimizations ourselves in the JIT.
5591+
_tstate->jit_tracer_state.prev_state.instr = next_instr;
56105592
PyObject *prev_code = PyStackRef_AsPyObjectBorrow(frame->f_executable);
56115593
if (_tstate->jit_tracer_state.prev_state.instr_code != (PyCodeObject *)prev_code) {
56125594
Py_SETREF(_tstate->jit_tracer_state.prev_state.instr_code, (PyCodeObject*)Py_NewRef((prev_code)));

0 commit comments

Comments
 (0)