Skip to content
Closed
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f7c220a
Implement FOR_END
sweeneyde Feb 20, 2022
4178c30
Use jabs, not jrel
sweeneyde Feb 20, 2022
245b43f
Bump magic
sweeneyde Feb 20, 2022
61a85f0
merge with main
sweeneyde Mar 1, 2022
b0030d5
merge with main
sweeneyde Mar 20, 2022
8f3cd5a
Remove FOR_ITER entirely
sweeneyde Mar 20, 2022
4520a5a
Fix some failng tests
sweeneyde Mar 20, 2022
6284f20
exclude test_frozenmain
sweeneyde Mar 20, 2022
3a0d53e
merge with main
sweeneyde Mar 24, 2022
3b328e7
fix some tracing tests
sweeneyde Mar 24, 2022
bdd7f07
fix swap pop/push in frameobject.c
sweeneyde Mar 24, 2022
b20fed8
merge with main
sweeneyde Apr 9, 2022
77d52e6
Get test.test_trace passing
sweeneyde Apr 10, 2022
53a8734
when marking backwards jumps, set todo=1
sweeneyde Apr 10, 2022
7d32011
remove debugging code
sweeneyde Apr 10, 2022
4a2e781
update test_dis
sweeneyde Apr 10, 2022
0af7e1e
make FOR_END a relative jump
sweeneyde Apr 10, 2022
d3552aa
make test.test_bdb and test.test_pdb pass
sweeneyde Apr 10, 2022
563ca00
refactor maybe_call_line_trace logic
sweeneyde Apr 11, 2022
719e756
add blurb
sweeneyde Apr 11, 2022
8626569
FOR_ITER --> FOR_END in dis.rst
sweeneyde Apr 11, 2022
2884727
FOR_ITER --> FOR_END in specialize.c
sweeneyde Apr 11, 2022
44baacc
Update whatsnew
sweeneyde Apr 11, 2022
0adffbd
Merge branch 'main' into for_end
sweeneyde Apr 11, 2022
7a62601
merge with main
sweeneyde Apr 11, 2022
92e8c6d
Merge branch 'for_end' of https://github.com/sweeneyde/cpython into f…
sweeneyde Apr 11, 2022
e4f240e
fix test_dis
sweeneyde Apr 12, 2022
6c2e53d
Merge branch 'main' into for_end
sweeneyde Apr 12, 2022
32bed37
fix opcode.h
sweeneyde Apr 12, 2022
3f7ec88
merge
sweeneyde Apr 20, 2022
577bd89
merge
sweeneyde Apr 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -981,12 +981,15 @@ iterations of the loop.
.. versionadded:: 3.1


.. opcode:: FOR_ITER (delta)
.. opcode:: FOR_END (delta)

TOS is an :term:`iterator`. Call its :meth:`~iterator.__next__` method. If
this yields a new value, push it on the stack (leaving the iterator below
it). If the iterator indicates it is exhausted, TOS is popped, and the byte
code counter is incremented by *delta*.
it), then decrement the bytecode counter by *delta*. If the iterator
indicates it is exhausted, then pop TOS and do not decrement the bytecode
counter.

.. versionadded:: 3.11


.. opcode:: LOAD_GLOBAL (namei)
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,12 @@ CPython bytecode changes

* Added :opcode:`JUMP_BACKWARD_NO_INTERRUPT`, which is used in certain loops where it is undesirable to handle interrupts.

* Replaced :opcode:`FOR_ITER` (jumps if iterator exhausted)
with :opcode:`FOR_END` (jumps if iterator is not exhausted).
At the end of each ``for``-loop iteration, instead of an unconditional
jump targeted at a conditional jump, there is now
only one (conditional) jump.

Deprecated
==========

Expand Down
80 changes: 41 additions & 39 deletions Include/opcode.h

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

8 changes: 5 additions & 3 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
LOAD_CONST = opmap['LOAD_CONST']
LOAD_GLOBAL = opmap['LOAD_GLOBAL']
BINARY_OP = opmap['BINARY_OP']
JUMP_BACKWARD = opmap['JUMP_BACKWARD']

BACK_JUMPS = {code for name, code in opmap.items()
if 'JUMP_BACKWARD' in name} | {opmap['FOR_END']}

CACHE = opmap["CACHE"]

Expand Down Expand Up @@ -442,7 +444,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
argval = arg*2
argrepr = "to " + repr(argval)
elif op in hasjrel:
signed_arg = -arg if op == JUMP_BACKWARD else arg
signed_arg = -arg if op in BACK_JUMPS else arg
argval = offset + 2 + signed_arg*2
argrepr = "to " + repr(argval)
elif op in haslocal or op in hasfree:
Expand Down Expand Up @@ -568,7 +570,7 @@ def findlabels(code):
for offset, op, arg in _unpack_opargs(code):
if arg is not None:
if op in hasjrel:
if op == JUMP_BACKWARD:
if op in BACK_JUMPS:
arg = -arg
label = offset + 2 + arg*2
elif op in hasjabs:
Expand Down
2 changes: 1 addition & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3491).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3493).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

_PYCACHE = '__pycache__'
Expand Down
5 changes: 4 additions & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ def jabs_op(name, op, entries=0):
name_op('STORE_NAME', 90) # Index in name list
name_op('DELETE_NAME', 91) # ""
def_op('UNPACK_SEQUENCE', 92, 1) # Number of tuple items
jrel_op('FOR_ITER', 93)
def_op('UNPACK_EX', 94)
name_op('STORE_ATTR', 95, 4) # Index in name list
name_op('DELETE_ATTR', 96) # ""
Expand Down Expand Up @@ -197,6 +196,7 @@ def jabs_op(name, op, entries=0):
def_op('KW_NAMES', 172)
hasconst.append(172)

jrel_op('FOR_END', 173)

del def_op, name_op, jrel_op, jabs_op

Expand Down Expand Up @@ -262,6 +262,9 @@ def jabs_op(name, op, entries=0):
"JUMP_BACKWARD": [
"JUMP_BACKWARD_QUICK",
],
"FOR_END": [
"FOR_END_QUICK",
],
"LOAD_ATTR": [
"LOAD_ATTR_ADAPTIVE",
"LOAD_ATTR_INSTANCE_VALUE",
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test__opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ def test_stack_effect_jump(self):
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0), 0)
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=True), 0)
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=False), -1)
FOR_ITER = dis.opmap['FOR_ITER']
self.assertEqual(stack_effect(FOR_ITER, 0), 1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), -1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1)
FOR_END = dis.opmap['FOR_END']
self.assertEqual(stack_effect(FOR_END, 0), 1)
self.assertEqual(stack_effect(FOR_END, 0, jump=True), 1)
self.assertEqual(stack_effect(FOR_END, 0, jump=False), -1)
JUMP_FORWARD = dis.opmap['JUMP_FORWARD']
self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0)
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
Expand Down
25 changes: 13 additions & 12 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,13 @@ def bug708901():
%3d PRECALL 2
CALL 2
GET_ITER
>> FOR_ITER 2 (to 40)
STORE_FAST 0 (res)
JUMP_FORWARD 2 (to 40)
>> STORE_FAST 0 (res)

%3d JUMP_BACKWARD 3 (to 34)
%3d NOP

%3d >> LOAD_CONST 0 (None)
%3d >> FOR_END 3 (to 36)
LOAD_CONST 0 (None)
RETURN_VALUE
""" % (bug708901.__code__.co_firstlineno,
bug708901.__code__.co_firstlineno + 1,
Expand Down Expand Up @@ -569,14 +570,14 @@ def foo(x):
%3d RESUME 0
BUILD_LIST 0
LOAD_FAST 0 (.0)
>> FOR_ITER 7 (to 24)
STORE_FAST 1 (z)
JUMP_FORWARD 6 (to 22)
>> STORE_FAST 1 (z)
LOAD_DEREF 2 (x)
LOAD_FAST 1 (z)
BINARY_OP 0 (+)
LIST_APPEND 2
JUMP_BACKWARD 8 (to 8)
>> RETURN_VALUE
>> FOR_END 7 (to 10)
RETURN_VALUE
""" % (dis_nested_1,
__file__,
_h.__code__.co_firstlineno + 3,
Expand Down Expand Up @@ -1217,8 +1218,8 @@ def _prepare_test_cases():
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=20, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=30, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='FOR_ITER', opcode=93, arg=32, argval=98, argrepr='to 98', offset=32, starts_line=None, is_jump_target=True, positions=None),
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=34, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='JUMP_FORWARD', opcode=110, arg=31, argval=96, argrepr='to 96', offset=32, starts_line=None, is_jump_target=True, positions=None),
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=34, starts_line=None, is_jump_target=True, positions=None),
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=36, starts_line=4, is_jump_target=False, positions=None),
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=48, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=50, starts_line=None, is_jump_target=False, positions=None),
Expand All @@ -1235,8 +1236,8 @@ def _prepare_test_cases():
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=48, argval=96, argrepr='to 96', offset=90, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=92, starts_line=8, is_jump_target=False, positions=None),
Instruction(opname='JUMP_FORWARD', opcode=110, arg=16, argval=128, argrepr='to 128', offset=94, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=33, argval=32, argrepr='to 32', offset=96, starts_line=7, is_jump_target=True, positions=None),
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=98, starts_line=10, is_jump_target=True, positions=None),
Instruction(opname='FOR_END', opcode=173, arg=32, argval=34, argrepr='to 34', offset=96, starts_line=3, is_jump_target=True, positions=None),
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=98, starts_line=10, is_jump_target=False, positions=None),
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=110, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=112, starts_line=None, is_jump_target=False, positions=None),
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=116, starts_line=None, is_jump_target=False, positions=None),
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,16 +509,16 @@ def f(x):
def test_assignment_idiom_in_comprehensions(self):
def listcomp():
return [y for x in a for y in [f(x)]]
self.assertEqual(count_instr_recursively(listcomp, 'FOR_ITER'), 1)
self.assertEqual(count_instr_recursively(listcomp, 'FOR_END'), 1)
def setcomp():
return {y for x in a for y in [f(x)]}
self.assertEqual(count_instr_recursively(setcomp, 'FOR_ITER'), 1)
self.assertEqual(count_instr_recursively(setcomp, 'FOR_END'), 1)
def dictcomp():
return {y: y for x in a for y in [f(x)]}
self.assertEqual(count_instr_recursively(dictcomp, 'FOR_ITER'), 1)
self.assertEqual(count_instr_recursively(dictcomp, 'FOR_END'), 1)
def genexpr():
return (y for x in a for y in [f(x)])
self.assertEqual(count_instr_recursively(genexpr, 'FOR_ITER'), 1)
self.assertEqual(count_instr_recursively(genexpr, 'FOR_END'), 1)

def test_format_combinations(self):
flags = '-+ #0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Replaced the ``FOR_ITER`` opcode that lived at the top of ``for``-loops with a
``FOR_END`` opcode that lives at the bottom. This form of `Loop Inversion
<https://en.wikipedia.org/wiki/Loop_inversion>`_ replaces the (unconditional
jump, conditional jump) pair with just one conditional jump during most
iterations.
13 changes: 8 additions & 5 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,14 @@ mark_stacks(PyCodeObject *code_obj, int len)
next_stack = push_value(pop_value(next_stack), Iterator);
stacks[i+1] = next_stack;
break;
case FOR_ITER:
case FOR_END:
{
int64_t target_stack = pop_value(next_stack);
stacks[i+1] = push_value(next_stack, Object);
j = get_arg(code, i) + i + 1;
int64_t target_stack = push_value(next_stack, Object);
stacks[i+1] = pop_value(next_stack);
j = i + 1 - get_arg(code, i);
if (stacks[j] == UNINITIALIZED && j < i) {
todo = 1;
}
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
stacks[j] = target_stack;
Expand Down Expand Up @@ -570,7 +573,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
}
else if (err < 0) {
if (start_stack == OVERFLOWED) {
msg = "stack to deep to analyze";
msg = "stack too deep to analyze";
}
else if (start_stack == UNINITIALIZED) {
msg = "can't jump from within an exception handler";
Expand Down
Loading