gh-124285: Fix bug where bool() is called multiple times for the same part of a boolean expression (#124394)

This commit is contained in:
Irit Katriel 2024-09-25 15:51:25 +01:00 committed by GitHub
parent c58c572a65
commit 78aeb38f7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 234 additions and 47 deletions

View File

@ -1872,6 +1872,12 @@ but are replaced by real opcodes or removed before bytecode is generated.
Undirected relative jump instructions which are replaced by their Undirected relative jump instructions which are replaced by their
directed (forward/backward) counterparts by the assembler. directed (forward/backward) counterparts by the assembler.
.. opcode:: JUMP_IF_TRUE
.. opcode:: JUMP_IF_FALSE
Conditional jumps which do not impact the stack. Replaced by the sequence
``COPY 1``, ``TO_BOOL``, ``POP_JUMP_IF_TRUE/FALSE``.
.. opcode:: LOAD_CLOSURE (i) .. opcode:: LOAD_CLOSURE (i)
Pushes a reference to the cell contained in slot ``i`` of the "fast locals" Pushes a reference to the cell contained in slot ``i`` of the "fast locals"

View File

@ -258,6 +258,7 @@ Known values:
Python 3.14a1 3604 (Do not duplicate test at end of while statements) Python 3.14a1 3604 (Do not duplicate test at end of while statements)
Python 3.14a1 3605 (Move ENTER_EXECUTOR to opcode 255) Python 3.14a1 3605 (Move ENTER_EXECUTOR to opcode 255)
Python 3.14a1 3606 (Specialize CALL_KW) Python 3.14a1 3606 (Specialize CALL_KW)
Python 3.14a1 3607 (Add pseudo instructions JUMP_IF_TRUE/FALSE)
Python 3.15 will start with 3650 Python 3.15 will start with 3650
@ -270,7 +271,7 @@ PC/launcher.c must also be updated.
*/ */
#define PYC_MAGIC_NUMBER 3606 #define PYC_MAGIC_NUMBER 3607
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */ (little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \ #define PYC_MAGIC_NUMBER_TOKEN \

View File

@ -22,6 +22,8 @@ extern "C" {
((OP) == STORE_FAST_MAYBE_NULL) || \ ((OP) == STORE_FAST_MAYBE_NULL) || \
((OP) == JUMP) || \ ((OP) == JUMP) || \
((OP) == JUMP_NO_INTERRUPT) || \ ((OP) == JUMP_NO_INTERRUPT) || \
((OP) == JUMP_IF_FALSE) || \
((OP) == JUMP_IF_TRUE) || \
((OP) == SETUP_FINALLY) || \ ((OP) == SETUP_FINALLY) || \
((OP) == SETUP_CLEANUP) || \ ((OP) == SETUP_CLEANUP) || \
((OP) == SETUP_WITH) || \ ((OP) == SETUP_WITH) || \
@ -269,6 +271,10 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 0; return 0;
case JUMP_FORWARD: case JUMP_FORWARD:
return 0; return 0;
case JUMP_IF_FALSE:
return 1;
case JUMP_IF_TRUE:
return 1;
case JUMP_NO_INTERRUPT: case JUMP_NO_INTERRUPT:
return 0; return 0;
case LIST_APPEND: case LIST_APPEND:
@ -726,6 +732,10 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 0; return 0;
case JUMP_FORWARD: case JUMP_FORWARD:
return 0; return 0;
case JUMP_IF_FALSE:
return 1;
case JUMP_IF_TRUE:
return 1;
case JUMP_NO_INTERRUPT: case JUMP_NO_INTERRUPT:
return 0; return 0;
case LIST_APPEND: case LIST_APPEND:
@ -956,7 +966,7 @@ enum InstructionFormat {
}; };
#define IS_VALID_OPCODE(OP) \ #define IS_VALID_OPCODE(OP) \
(((OP) >= 0) && ((OP) < 264) && \ (((OP) >= 0) && ((OP) < 266) && \
(_PyOpcode_opcode_metadata[(OP)].valid_entry)) (_PyOpcode_opcode_metadata[(OP)].valid_entry))
#define HAS_ARG_FLAG (1) #define HAS_ARG_FLAG (1)
@ -1005,9 +1015,9 @@ struct opcode_metadata {
int16_t flags; int16_t flags;
}; };
extern const struct opcode_metadata _PyOpcode_opcode_metadata[264]; extern const struct opcode_metadata _PyOpcode_opcode_metadata[266];
#ifdef NEED_OPCODE_METADATA #ifdef NEED_OPCODE_METADATA
const struct opcode_metadata _PyOpcode_opcode_metadata[264] = { const struct opcode_metadata _PyOpcode_opcode_metadata[266] = {
[BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG },
[BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
@ -1224,6 +1234,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[264] = {
[YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG },
[_DO_CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [_DO_CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG },
[LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG },
[POP_BLOCK] = { true, -1, HAS_PURE_FLAG }, [POP_BLOCK] = { true, -1, HAS_PURE_FLAG },
@ -1422,9 +1434,9 @@ _PyOpcode_macro_expansion[256] = {
}; };
#endif // NEED_OPCODE_METADATA #endif // NEED_OPCODE_METADATA
extern const char *_PyOpcode_OpName[264]; extern const char *_PyOpcode_OpName[266];
#ifdef NEED_OPCODE_METADATA #ifdef NEED_OPCODE_METADATA
const char *_PyOpcode_OpName[264] = { const char *_PyOpcode_OpName[266] = {
[BINARY_OP] = "BINARY_OP", [BINARY_OP] = "BINARY_OP",
[BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT", [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT",
[BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT", [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT",
@ -1543,6 +1555,8 @@ const char *_PyOpcode_OpName[264] = {
[JUMP_BACKWARD] = "JUMP_BACKWARD", [JUMP_BACKWARD] = "JUMP_BACKWARD",
[JUMP_BACKWARD_NO_INTERRUPT] = "JUMP_BACKWARD_NO_INTERRUPT", [JUMP_BACKWARD_NO_INTERRUPT] = "JUMP_BACKWARD_NO_INTERRUPT",
[JUMP_FORWARD] = "JUMP_FORWARD", [JUMP_FORWARD] = "JUMP_FORWARD",
[JUMP_IF_FALSE] = "JUMP_IF_FALSE",
[JUMP_IF_TRUE] = "JUMP_IF_TRUE",
[JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT",
[LIST_APPEND] = "LIST_APPEND", [LIST_APPEND] = "LIST_APPEND",
[LIST_EXTEND] = "LIST_EXTEND", [LIST_EXTEND] = "LIST_EXTEND",
@ -1943,25 +1957,28 @@ const uint8_t _PyOpcode_Deopt[256] = {
case 235: \ case 235: \
; ;
struct pseudo_targets { struct pseudo_targets {
uint8_t targets[3]; uint8_t as_sequence;
uint8_t targets[4];
}; };
extern const struct pseudo_targets _PyOpcode_PseudoTargets[8]; extern const struct pseudo_targets _PyOpcode_PseudoTargets[10];
#ifdef NEED_OPCODE_METADATA #ifdef NEED_OPCODE_METADATA
const struct pseudo_targets _PyOpcode_PseudoTargets[8] = { const struct pseudo_targets _PyOpcode_PseudoTargets[10] = {
[LOAD_CLOSURE-256] = { { LOAD_FAST, 0, 0 } }, [LOAD_CLOSURE-256] = { 0, { LOAD_FAST, 0, 0, 0 } },
[STORE_FAST_MAYBE_NULL-256] = { { STORE_FAST, 0, 0 } }, [STORE_FAST_MAYBE_NULL-256] = { 0, { STORE_FAST, 0, 0, 0 } },
[JUMP-256] = { { JUMP_FORWARD, JUMP_BACKWARD, 0 } }, [JUMP-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD, 0, 0 } },
[JUMP_NO_INTERRUPT-256] = { { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0 } }, [JUMP_NO_INTERRUPT-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0, 0 } },
[SETUP_FINALLY-256] = { { NOP, 0, 0 } }, [JUMP_IF_FALSE-256] = { 1, { COPY, TO_BOOL, POP_JUMP_IF_FALSE, 0 } },
[SETUP_CLEANUP-256] = { { NOP, 0, 0 } }, [JUMP_IF_TRUE-256] = { 1, { COPY, TO_BOOL, POP_JUMP_IF_TRUE, 0 } },
[SETUP_WITH-256] = { { NOP, 0, 0 } }, [SETUP_FINALLY-256] = { 0, { NOP, 0, 0, 0 } },
[POP_BLOCK-256] = { { NOP, 0, 0 } }, [SETUP_CLEANUP-256] = { 0, { NOP, 0, 0, 0 } },
[SETUP_WITH-256] = { 0, { NOP, 0, 0, 0 } },
[POP_BLOCK-256] = { 0, { NOP, 0, 0, 0 } },
}; };
#endif // NEED_OPCODE_METADATA #endif // NEED_OPCODE_METADATA
static inline bool static inline bool
is_pseudo_target(int pseudo, int target) { is_pseudo_target(int pseudo, int target) {
if (pseudo < 256 || pseudo >= 264) { if (pseudo < 256 || pseudo >= 266) {
return false; return false;
} }
for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) { for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) {

16
Include/opcode_ids.h generated
View File

@ -226,13 +226,15 @@ extern "C" {
#define INSTRUMENTED_LINE 254 #define INSTRUMENTED_LINE 254
#define ENTER_EXECUTOR 255 #define ENTER_EXECUTOR 255
#define JUMP 256 #define JUMP 256
#define JUMP_NO_INTERRUPT 257 #define JUMP_IF_FALSE 257
#define LOAD_CLOSURE 258 #define JUMP_IF_TRUE 258
#define POP_BLOCK 259 #define JUMP_NO_INTERRUPT 259
#define SETUP_CLEANUP 260 #define LOAD_CLOSURE 260
#define SETUP_FINALLY 261 #define POP_BLOCK 261
#define SETUP_WITH 262 #define SETUP_CLEANUP 262
#define STORE_FAST_MAYBE_NULL 263 #define SETUP_FINALLY 263
#define SETUP_WITH 264
#define STORE_FAST_MAYBE_NULL 265
#define HAVE_ARGUMENT 41 #define HAVE_ARGUMENT 41
#define MIN_SPECIALIZED_OPCODE 150 #define MIN_SPECIALIZED_OPCODE 150

View File

@ -335,13 +335,15 @@ opmap = {
'INSTRUMENTED_CALL': 252, 'INSTRUMENTED_CALL': 252,
'INSTRUMENTED_JUMP_BACKWARD': 253, 'INSTRUMENTED_JUMP_BACKWARD': 253,
'JUMP': 256, 'JUMP': 256,
'JUMP_NO_INTERRUPT': 257, 'JUMP_IF_FALSE': 257,
'LOAD_CLOSURE': 258, 'JUMP_IF_TRUE': 258,
'POP_BLOCK': 259, 'JUMP_NO_INTERRUPT': 259,
'SETUP_CLEANUP': 260, 'LOAD_CLOSURE': 260,
'SETUP_FINALLY': 261, 'POP_BLOCK': 261,
'SETUP_WITH': 262, 'SETUP_CLEANUP': 262,
'STORE_FAST_MAYBE_NULL': 263, 'SETUP_FINALLY': 263,
'SETUP_WITH': 264,
'STORE_FAST_MAYBE_NULL': 265,
} }
HAVE_ARGUMENT = 41 HAVE_ARGUMENT = 41

View File

@ -1527,6 +1527,45 @@ class TestSpecifics(unittest.TestCase):
pass pass
[[]] [[]]
class TestBooleanExpression(unittest.TestCase):
class Value:
def __init__(self):
self.called = 0
def __bool__(self):
self.called += 1
return self.value
class Yes(Value):
value = True
class No(Value):
value = False
def test_short_circuit_and(self):
v = [self.Yes(), self.No(), self.Yes()]
res = v[0] and v[1] and v[0]
self.assertIs(res, v[1])
self.assertEqual([e.called for e in v], [1, 1, 0])
def test_short_circuit_or(self):
v = [self.No(), self.Yes(), self.No()]
res = v[0] or v[1] or v[0]
self.assertIs(res, v[1])
self.assertEqual([e.called for e in v], [1, 1, 0])
def test_compound(self):
# See gh-124285
v = [self.No(), self.Yes(), self.Yes(), self.Yes()]
res = v[0] and v[1] or v[2] or v[3]
self.assertIs(res, v[2])
self.assertEqual([e.called for e in v], [1, 0, 1, 0])
v = [self.No(), self.No(), self.Yes(), self.Yes(), self.No()]
res = v[0] or v[1] and v[2] or v[3] or v[4]
self.assertIs(res, v[3])
self.assertEqual([e.called for e in v], [1, 1, 0, 1, 0])
@requires_debug_ranges() @requires_debug_ranges()
class TestSourcePositions(unittest.TestCase): class TestSourcePositions(unittest.TestCase):
# Ensure that compiled code snippets have correct line and column numbers # Ensure that compiled code snippets have correct line and column numbers

View File

@ -523,6 +523,36 @@ class TestGeneratedCases(unittest.TestCase):
""" """
self.run_cases_test(input, output) self.run_cases_test(input, output)
def test_pseudo_instruction_as_sequence(self):
input = """
pseudo(OP, (in -- out1, out2)) = [
OP1, OP2
];
inst(OP1, (--)) {
}
inst(OP2, (--)) {
}
"""
output = """
TARGET(OP1) {
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP1);
DISPATCH();
}
TARGET(OP2) {
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(OP2);
DISPATCH();
}
"""
self.run_cases_test(input, output)
def test_array_input(self): def test_array_input(self):
input = """ input = """
inst(OP, (below, values[oparg*2], above --)) { inst(OP, (below, values[oparg*2], above --)) {

View File

@ -0,0 +1,2 @@
Fix bug where ``bool(a)`` can be invoked more than once during the
evaluation of a compound boolean expression.

View File

@ -2570,6 +2570,14 @@ dummy_func(
JUMP_BACKWARD_NO_INTERRUPT, JUMP_BACKWARD_NO_INTERRUPT,
}; };
pseudo(JUMP_IF_FALSE, (cond -- cond)) = [
COPY, TO_BOOL, POP_JUMP_IF_FALSE,
];
pseudo(JUMP_IF_TRUE, (cond -- cond)) = [
COPY, TO_BOOL, POP_JUMP_IF_TRUE,
];
tier1 inst(ENTER_EXECUTOR, (--)) { tier1 inst(ENTER_EXECUTOR, (--)) {
#ifdef _Py_TIER2 #ifdef _Py_TIER2
PyCodeObject *code = _PyFrame_GetCode(frame); PyCodeObject *code = _PyFrame_GetCode(frame);

View File

@ -3140,17 +3140,15 @@ codegen_boolop(compiler *c, expr_ty e)
location loc = LOC(e); location loc = LOC(e);
assert(e->kind == BoolOp_kind); assert(e->kind == BoolOp_kind);
if (e->v.BoolOp.op == And) if (e->v.BoolOp.op == And)
jumpi = POP_JUMP_IF_FALSE; jumpi = JUMP_IF_FALSE;
else else
jumpi = POP_JUMP_IF_TRUE; jumpi = JUMP_IF_TRUE;
NEW_JUMP_TARGET_LABEL(c, end); NEW_JUMP_TARGET_LABEL(c, end);
s = e->v.BoolOp.values; s = e->v.BoolOp.values;
n = asdl_seq_LEN(s) - 1; n = asdl_seq_LEN(s) - 1;
assert(n >= 0); assert(n >= 0);
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
VISIT(c, expr, (expr_ty)asdl_seq_GET(s, i)); VISIT(c, expr, (expr_ty)asdl_seq_GET(s, i));
ADDOP_I(c, loc, COPY, 1);
ADDOP(c, loc, TO_BOOL);
ADDOP_JUMP(c, loc, jumpi, end); ADDOP_JUMP(c, loc, jumpi, end);
ADDOP(c, loc, POP_TOP); ADDOP(c, loc, POP_TOP);
} }

View File

@ -1589,6 +1589,8 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *
switch(nextop) { switch(nextop) {
case POP_JUMP_IF_FALSE: case POP_JUMP_IF_FALSE:
case POP_JUMP_IF_TRUE: case POP_JUMP_IF_TRUE:
case JUMP_IF_FALSE:
case JUMP_IF_TRUE:
{ {
/* Remove LOAD_CONST const; conditional jump */ /* Remove LOAD_CONST const; conditional jump */
PyObject* cnt = get_const_value(opcode, oparg, consts); PyObject* cnt = get_const_value(opcode, oparg, consts);
@ -1600,8 +1602,11 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *
if (is_true == -1) { if (is_true == -1) {
return ERROR; return ERROR;
} }
INSTR_SET_OP0(inst, NOP); if (PyCompile_OpcodeStackEffect(nextop, 0) == -1) {
int jump_if_true = nextop == POP_JUMP_IF_TRUE; /* POP_JUMP_IF_FALSE or POP_JUMP_IF_TRUE */
INSTR_SET_OP0(inst, NOP);
}
int jump_if_true = (nextop == POP_JUMP_IF_TRUE || nextop == JUMP_IF_TRUE);
if (is_true == jump_if_true) { if (is_true == jump_if_true) {
bb->b_instr[i+1].i_opcode = JUMP; bb->b_instr[i+1].i_opcode = JUMP;
} }
@ -1761,6 +1766,36 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
i -= jump_thread(bb, inst, target, POP_JUMP_IF_TRUE); i -= jump_thread(bb, inst, target, POP_JUMP_IF_TRUE);
} }
break; break;
case JUMP_IF_FALSE:
switch (target->i_opcode) {
case JUMP:
case JUMP_IF_FALSE:
i -= jump_thread(bb, inst, target, JUMP_IF_FALSE);
continue;
case JUMP_IF_TRUE:
// No need to check for loops here, a block's b_next
// cannot point to itself.
assert(inst->i_target != inst->i_target->b_next);
inst->i_target = inst->i_target->b_next;
i--;
continue;
}
break;
case JUMP_IF_TRUE:
switch (target->i_opcode) {
case JUMP:
case JUMP_IF_TRUE:
i -= jump_thread(bb, inst, target, JUMP_IF_TRUE);
continue;
case JUMP_IF_FALSE:
// No need to check for loops here, a block's b_next
// cannot point to itself.
assert(inst->i_target != inst->i_target->b_next);
inst->i_target = inst->i_target->b_next;
i--;
continue;
}
break;
case JUMP: case JUMP:
case JUMP_NO_INTERRUPT: case JUMP_NO_INTERRUPT:
switch (target->i_opcode) { switch (target->i_opcode) {
@ -2367,6 +2402,38 @@ push_cold_blocks_to_end(cfg_builder *g) {
return SUCCESS; return SUCCESS;
} }
static int
convert_pseudo_conditional_jumps(cfg_builder *g)
{
basicblock *entryblock = g->g_entryblock;
for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
for (int i = 0; i < b->b_iused; i++) {
cfg_instr *instr = &b->b_instr[i];
if (instr->i_opcode == JUMP_IF_FALSE || instr->i_opcode == JUMP_IF_TRUE) {
assert(i == b->b_iused - 1);
instr->i_opcode = instr->i_opcode == JUMP_IF_FALSE ?
POP_JUMP_IF_FALSE : POP_JUMP_IF_TRUE;
location loc = instr->i_loc;
cfg_instr copy = {
.i_opcode = COPY,
.i_oparg = 1,
.i_loc = loc,
.i_target = NULL,
};
RETURN_IF_ERROR(basicblock_insert_instruction(b, i++, &copy));
cfg_instr to_bool = {
.i_opcode = TO_BOOL,
.i_oparg = 0,
.i_loc = loc,
.i_target = NULL,
};
RETURN_IF_ERROR(basicblock_insert_instruction(b, i++, &to_bool));
}
}
}
return SUCCESS;
}
static int static int
convert_pseudo_ops(cfg_builder *g) convert_pseudo_ops(cfg_builder *g)
{ {
@ -2826,6 +2893,8 @@ _PyCfg_OptimizedCfgToInstructionSequence(cfg_builder *g,
int *stackdepth, int *nlocalsplus, int *stackdepth, int *nlocalsplus,
_PyInstructionSequence *seq) _PyInstructionSequence *seq)
{ {
RETURN_IF_ERROR(convert_pseudo_conditional_jumps(g));
*stackdepth = calculate_stackdepth(g); *stackdepth = calculate_stackdepth(g);
if (*stackdepth < 0) { if (*stackdepth < 0) {
return ERROR; return ERROR;

View File

@ -248,6 +248,7 @@ class PseudoInstruction:
name: str name: str
stack: StackEffect stack: StackEffect
targets: list[Instruction] targets: list[Instruction]
as_sequence: bool
flags: list[str] flags: list[str]
opcode: int = -1 opcode: int = -1
@ -852,6 +853,7 @@ def add_pseudo(
pseudo.name, pseudo.name,
analyze_stack(pseudo), analyze_stack(pseudo),
[instructions[target] for target in pseudo.targets], [instructions[target] for target in pseudo.targets],
pseudo.as_sequence,
pseudo.flags, pseudo.flags,
) )

View File

@ -305,6 +305,7 @@ def generate_pseudo_targets(analysis: Analysis, out: CWriter) -> None:
table_size = len(analysis.pseudos) table_size = len(analysis.pseudos)
max_targets = max(len(pseudo.targets) for pseudo in analysis.pseudos.values()) max_targets = max(len(pseudo.targets) for pseudo in analysis.pseudos.values())
out.emit("struct pseudo_targets {\n") out.emit("struct pseudo_targets {\n")
out.emit(f"uint8_t as_sequence;\n")
out.emit(f"uint8_t targets[{max_targets + 1}];\n") out.emit(f"uint8_t targets[{max_targets + 1}];\n")
out.emit("};\n") out.emit("};\n")
out.emit( out.emit(
@ -315,10 +316,11 @@ def generate_pseudo_targets(analysis: Analysis, out: CWriter) -> None:
f"const struct pseudo_targets _PyOpcode_PseudoTargets[{table_size}] = {{\n" f"const struct pseudo_targets _PyOpcode_PseudoTargets[{table_size}] = {{\n"
) )
for pseudo in analysis.pseudos.values(): for pseudo in analysis.pseudos.values():
as_sequence = "1" if pseudo.as_sequence else "0"
targets = ["0"] * (max_targets + 1) targets = ["0"] * (max_targets + 1)
for i, target in enumerate(pseudo.targets): for i, target in enumerate(pseudo.targets):
targets[i] = target.name targets[i] = target.name
out.emit(f"[{pseudo.name}-256] = {{ {{ {', '.join(targets)} }} }},\n") out.emit(f"[{pseudo.name}-256] = {{ {as_sequence}, {{ {', '.join(targets)} }} }},\n")
out.emit("};\n\n") out.emit("};\n\n")
out.emit("#endif // NEED_OPCODE_METADATA\n") out.emit("#endif // NEED_OPCODE_METADATA\n")
out.emit("static inline bool\n") out.emit("static inline bool\n")

View File

@ -148,6 +148,7 @@ class Pseudo(Node):
outputs: list[OutputEffect] outputs: list[OutputEffect]
flags: list[str] # instr flags to set on the pseudo instruction flags: list[str] # instr flags to set on the pseudo instruction
targets: list[str] # opcodes this can be replaced by targets: list[str] # opcodes this can be replaced by
as_sequence: bool
AstNode = InstDef | Macro | Pseudo | Family AstNode = InstDef | Macro | Pseudo | Family
@ -423,16 +424,22 @@ class Parser(PLexer):
flags = [] flags = []
if self.expect(lx.RPAREN): if self.expect(lx.RPAREN):
if self.expect(lx.EQUALS): if self.expect(lx.EQUALS):
if not self.expect(lx.LBRACE): if self.expect(lx.LBRACE):
raise self.make_syntax_error("Expected {") as_sequence = False
if members := self.members(): closing = lx.RBRACE
if self.expect(lx.RBRACE) and self.expect(lx.SEMI): elif self.expect(lx.LBRACKET):
as_sequence = True
closing = lx.RBRACKET
else:
raise self.make_syntax_error("Expected { or [")
if members := self.members(allow_sequence=True):
if self.expect(closing) and self.expect(lx.SEMI):
return Pseudo( return Pseudo(
tkn.text, inp, outp, flags, members tkn.text, inp, outp, flags, members, as_sequence
) )
return None return None
def members(self) -> list[str] | None: def members(self, allow_sequence : bool=False) -> list[str] | None:
here = self.getpos() here = self.getpos()
if tkn := self.expect(lx.IDENTIFIER): if tkn := self.expect(lx.IDENTIFIER):
members = [tkn.text] members = [tkn.text]
@ -442,8 +449,10 @@ class Parser(PLexer):
else: else:
break break
peek = self.peek() peek = self.peek()
if not peek or peek.kind != lx.RBRACE: kinds = [lx.RBRACE, lx.RBRACKET] if allow_sequence else [lx.RBRACE]
raise self.make_syntax_error("Expected comma or right paren") if not peek or peek.kind not in kinds:
raise self.make_syntax_error(
f"Expected comma or right paren{'/bracket' if allow_sequence else ''}")
return members return members
self.setpos(here) self.setpos(here)
return None return None