Skip to content

Commit 7b14c2e

Browse files
authored
GH-100982: Add COMPARE_AND_BRANCH instruction (GH-100983)
1 parent b1a74a1 commit 7b14c2e

17 files changed

+267
-239
lines changed

Doc/library/dis.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,15 @@ iterations of the loop.
10431043
``cmp_op[opname]``.
10441044

10451045

1046+
.. opcode:: COMPARE_AND_BRANCH (opname)
1047+
1048+
Compares the top two values on the stack, popping them, then branches.
1049+
The direction and offset of the jump is embedded as a ``POP_JUMP_IF_TRUE``
1050+
or ``POP_JUMP_IF_FALSE`` instruction immediately following the cache.
1051+
1052+
.. versionadded:: 3.12
1053+
1054+
10461055
.. opcode:: IS_OP (invert)
10471056

10481057
Performs ``is`` comparison, or ``is not`` if ``invert`` is 1.

Include/internal/pycore_code.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ extern void _Py_Specialize_Call(PyObject *callable, _Py_CODEUNIT *instr,
228228
int nargs, PyObject *kwnames);
229229
extern void _Py_Specialize_BinaryOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr,
230230
int oparg, PyObject **locals);
231-
extern void _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs,
231+
extern void _Py_Specialize_CompareAndBranch(PyObject *lhs, PyObject *rhs,
232232
_Py_CODEUNIT *instr, int oparg);
233233
extern void _Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr,
234234
int oparg);

Include/internal/pycore_opcode.h

+12-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode.h

+10-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def _write_atomic(path, data, mode=0o666):
429429
# Python 3.12a1 3513 (Add CALL_INTRINSIC_1 instruction, removed STOPITERATION_ERROR, PRINT_EXPR, IMPORT_STAR)
430430
# Python 3.12a1 3514 (Remove ASYNC_GEN_WRAP, LIST_TO_TUPLE, and UNARY_POSITIVE)
431431
# Python 3.12a1 3515 (Embed jump mask in COMPARE_OP oparg)
432+
# Python 3.12a1 3516 (Add COMAPRE_AND_BRANCH instruction)
432433

433434
# Python 3.13 will start with 3550
434435

@@ -441,7 +442,7 @@ def _write_atomic(path, data, mode=0o666):
441442
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
442443
# in PC/launcher.c must also be updated.
443444

444-
MAGIC_NUMBER = (3515).to_bytes(2, 'little') + b'\r\n'
445+
MAGIC_NUMBER = (3516).to_bytes(2, 'little') + b'\r\n'
445446

446447
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
447448

Lib/opcode.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ def pseudo_op(name, op, real_ops):
189189
def_op('DELETE_DEREF', 139)
190190
hasfree.append(139)
191191
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
192+
def_op('COMPARE_AND_BRANCH', 141) # Comparison and jump
193+
hascompare.append(141)
192194

193195
def_op('CALL_FUNCTION_EX', 142) # Flags
194196

@@ -309,10 +311,10 @@ def pseudo_op(name, op, real_ops):
309311
"CALL_NO_KW_TUPLE_1",
310312
"CALL_NO_KW_TYPE_1",
311313
],
312-
"COMPARE_OP": [
313-
"COMPARE_OP_FLOAT_JUMP",
314-
"COMPARE_OP_INT_JUMP",
315-
"COMPARE_OP_STR_JUMP",
314+
"COMPARE_AND_BRANCH": [
315+
"COMPARE_AND_BRANCH_FLOAT",
316+
"COMPARE_AND_BRANCH_INT",
317+
"COMPARE_AND_BRANCH_STR",
316318
],
317319
"FOR_ITER": [
318320
"FOR_ITER_LIST",
@@ -392,6 +394,9 @@ def pseudo_op(name, op, real_ops):
392394
"COMPARE_OP": {
393395
"counter": 1,
394396
},
397+
"COMPARE_AND_BRANCH": {
398+
"counter": 1,
399+
},
395400
"BINARY_SUBSCR": {
396401
"counter": 1,
397402
"type_version": 2,

Lib/test/test_compile.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1121,11 +1121,11 @@ def aug():
11211121
check_op_count(aug, "BUILD_SLICE", 0)
11221122

11231123
def test_compare_positions(self):
1124-
for opname, op in [
1125-
("COMPARE_OP", "<"),
1126-
("COMPARE_OP", "<="),
1127-
("COMPARE_OP", ">"),
1128-
("COMPARE_OP", ">="),
1124+
for opname_prefix, op in [
1125+
("COMPARE_", "<"),
1126+
("COMPARE_", "<="),
1127+
("COMPARE_", ">"),
1128+
("COMPARE_", ">="),
11291129
("CONTAINS_OP", "in"),
11301130
("CONTAINS_OP", "not in"),
11311131
("IS_OP", "is"),
@@ -1140,7 +1140,7 @@ def test_compare_positions(self):
11401140
actual_positions = [
11411141
instruction.positions
11421142
for instruction in dis.get_instructions(code)
1143-
if instruction.opname == opname
1143+
if instruction.opname.startswith(opname_prefix)
11441144
]
11451145
with self.subTest(source):
11461146
self.assertEqual(actual_positions, expected_positions)
@@ -1270,7 +1270,7 @@ def test_multiline_boolean_expression(self):
12701270
self.assertOpcodeSourcePositionIs(compiled_code, 'POP_JUMP_IF_FALSE',
12711271
line=2, end_line=2, column=15, end_column=16, occurrence=2)
12721272
# compare d and 0
1273-
self.assertOpcodeSourcePositionIs(compiled_code, 'COMPARE_OP',
1273+
self.assertOpcodeSourcePositionIs(compiled_code, 'COMPARE_AND_BRANCH',
12741274
line=4, end_line=4, column=8, end_column=13, occurrence=1)
12751275
# jump if comparison it True
12761276
self.assertOpcodeSourcePositionIs(compiled_code, 'POP_JUMP_IF_TRUE',

Lib/test/test_dis.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1561,12 +1561,12 @@ def _prepare_test_cases():
15611561
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=58, starts_line=None, is_jump_target=False, positions=None),
15621562
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=60, starts_line=5, is_jump_target=False, positions=None),
15631563
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=62, starts_line=None, is_jump_target=False, positions=None),
1564-
Instruction(opname='COMPARE_OP', opcode=107, arg=13, argval='<', argrepr='<', offset=64, starts_line=None, is_jump_target=False, positions=None),
1564+
Instruction(opname='COMPARE_AND_BRANCH', opcode=141, arg=13, argval='<', argrepr='<', offset=64, starts_line=None, is_jump_target=False, positions=None),
15651565
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=1, argval=72, argrepr='to 72', offset=68, starts_line=None, is_jump_target=False, positions=None),
15661566
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=22, argval=28, argrepr='to 28', offset=70, starts_line=6, is_jump_target=False, positions=None),
15671567
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=72, starts_line=7, is_jump_target=True, positions=None),
15681568
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=74, starts_line=None, is_jump_target=False, positions=None),
1569-
Instruction(opname='COMPARE_OP', opcode=107, arg=68, argval='>', argrepr='>', offset=76, starts_line=None, is_jump_target=False, positions=None),
1569+
Instruction(opname='COMPARE_AND_BRANCH', opcode=141, arg=68, argval='>', argrepr='>', offset=76, starts_line=None, is_jump_target=False, positions=None),
15701570
Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=1, argval=84, argrepr='to 84', offset=80, starts_line=None, is_jump_target=False, positions=None),
15711571
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=28, argval=28, argrepr='to 28', offset=82, starts_line=None, is_jump_target=False, positions=None),
15721572
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=84, starts_line=8, is_jump_target=True, positions=None),
@@ -1588,12 +1588,12 @@ def _prepare_test_cases():
15881588
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=154, starts_line=None, is_jump_target=False, positions=None),
15891589
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=156, starts_line=14, is_jump_target=False, positions=None),
15901590
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=158, starts_line=None, is_jump_target=False, positions=None),
1591-
Instruction(opname='COMPARE_OP', opcode=107, arg=75, argval='>', argrepr='>', offset=160, starts_line=None, is_jump_target=False, positions=None),
1591+
Instruction(opname='COMPARE_AND_BRANCH', opcode=141, arg=75, argval='>', argrepr='>', offset=160, starts_line=None, is_jump_target=False, positions=None),
15921592
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=1, argval=168, argrepr='to 168', offset=164, starts_line=None, is_jump_target=False, positions=None),
15931593
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=26, argval=116, argrepr='to 116', offset=166, starts_line=15, is_jump_target=False, positions=None),
15941594
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=168, starts_line=16, is_jump_target=True, positions=None),
15951595
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=170, starts_line=None, is_jump_target=False, positions=None),
1596-
Instruction(opname='COMPARE_OP', opcode=107, arg=13, argval='<', argrepr='<', offset=172, starts_line=None, is_jump_target=False, positions=None),
1596+
Instruction(opname='COMPARE_AND_BRANCH', opcode=141, arg=13, argval='<', argrepr='<', offset=172, starts_line=None, is_jump_target=False, positions=None),
15971597
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=1, argval=180, argrepr='to 180', offset=176, starts_line=None, is_jump_target=False, positions=None),
15981598
Instruction(opname='JUMP_FORWARD', opcode=110, arg=16, argval=212, argrepr='to 212', offset=178, starts_line=17, is_jump_target=False, positions=None),
15991599
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=180, starts_line=11, is_jump_target=True, positions=None),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Adds a new :opcode:`COMPARE_AND_BRANCH` instruction. This is a bit more
2+
efficient when performing a comparison immediately followed by a branch, and
3+
restores the design intent of PEP 659 that specializations are local to a
4+
single instruction.

Objects/frameobject.c

+8
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ mark_stacks(PyCodeObject *code_obj, int len)
357357
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
358358
stacks[j] = next_stack;
359359
break;
360+
case COMPARE_AND_BRANCH:
361+
next_stack = pop_value(pop_value(next_stack));
362+
i++;
363+
j = get_arg(code, i) + i + 1;
364+
assert(j < len);
365+
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
366+
stacks[j] = next_stack;
367+
break;
360368
case GET_ITER:
361369
case GET_AITER:
362370
next_stack = push_value(pop_value(next_stack), Iterator);

0 commit comments

Comments
 (0)