Skip to content

Commit e181c0d

Browse files
committed
py: Fix optimised for-loop compiler so it follows proper semantics.
You can now assign to the range end variable and the for-loop still works correctly. This fully addresses issue adafruit#565. Also fixed a bug with the stack not being fully popped when breaking out of an optimised for-loop (and it's actually impossible to write a test for this case!).
1 parent 7764f16 commit e181c0d

2 files changed

Lines changed: 43 additions & 19 deletions

File tree

py/compile.c

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,26 +1776,40 @@ STATIC void compile_while_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) {
17761776
}
17771777

17781778
#if !MICROPY_EMIT_CPYTHON
1779-
// TODO preload end and step onto stack if they are not constants
1780-
// Note that, as per semantics of for .. range, the final failing value should not be stored in the loop variable
1781-
// And, if the loop never runs, the loop variable should never be assigned
1779+
// This function compiles an optimised for-loop of the form:
1780+
// for <var> in range(<start>, <end>, <step>):
1781+
// <body>
1782+
// else:
1783+
// <else>
1784+
// <var> must be an identifier and <step> must be a small-int.
1785+
//
1786+
// Semantics of for-loop require:
1787+
// - final failing value should not be stored in the loop variable
1788+
// - if the loop never runs, the loop variable should never be assigned
1789+
// - assignments to <var>, <end> or <step> in the body do not alter the loop
1790+
// (<step> is a constant for us, so no need to worry about it changing)
1791+
//
1792+
// If <end> is a small-int, then the stack during the for-loop contains just
1793+
// the current value of <var>. Otherwise, the stack contains <end> then the
1794+
// current value of <var>.
17821795
STATIC void compile_for_stmt_optimised_range(compiler_t *comp, mp_parse_node_t pn_var, mp_parse_node_t pn_start, mp_parse_node_t pn_end, mp_parse_node_t pn_step, mp_parse_node_t pn_body, mp_parse_node_t pn_else) {
17831796
START_BREAK_CONTINUE_BLOCK
1784-
// note that we don't need to pop anything when breaking from an optimise for loop
17851797

17861798
uint top_label = comp_next_label(comp);
17871799
uint entry_label = comp_next_label(comp);
17881800

1789-
// compile: start, duplicated on stack
1801+
// put the end value on the stack if it's not a small-int constant
1802+
bool end_on_stack = !MP_PARSE_NODE_IS_SMALL_INT(pn_end);
1803+
if (end_on_stack) {
1804+
compile_node(comp, pn_end);
1805+
}
1806+
1807+
// compile: start
17901808
compile_node(comp, pn_start);
1791-
EMIT(dup_top);
17921809

17931810
EMIT_ARG(jump, entry_label);
17941811
EMIT_ARG(label_assign, top_label);
17951812

1796-
// at this point we actually have 1 less element on the stack
1797-
EMIT_ARG(adjust_stack_size, -1);
1798-
17991813
// duplicate next value and store it to var
18001814
EMIT(dup_top);
18011815
c_assign(comp, pn_var, ASSIGN_STORE);
@@ -1805,15 +1819,20 @@ STATIC void compile_for_stmt_optimised_range(compiler_t *comp, mp_parse_node_t p
18051819

18061820
EMIT_ARG(label_assign, continue_label);
18071821

1808-
// compile: var + step, duplicated on stack
1822+
// compile: var + step
18091823
compile_node(comp, pn_step);
18101824
EMIT_ARG(binary_op, MP_BINARY_OP_INPLACE_ADD);
1811-
EMIT(dup_top);
18121825

18131826
EMIT_ARG(label_assign, entry_label);
18141827

18151828
// compile: if var <cond> end: goto top
1816-
compile_node(comp, pn_end);
1829+
if (end_on_stack) {
1830+
EMIT(dup_top_two);
1831+
EMIT(rot_two);
1832+
} else {
1833+
EMIT(dup_top);
1834+
compile_node(comp, pn_end);
1835+
}
18171836
assert(MP_PARSE_NODE_IS_SMALL_INT(pn_step));
18181837
if (MP_PARSE_NODE_LEAF_SMALL_INT(pn_step) >= 0) {
18191838
EMIT_ARG(binary_op, MP_BINARY_OP_LESS);
@@ -1822,15 +1841,20 @@ STATIC void compile_for_stmt_optimised_range(compiler_t *comp, mp_parse_node_t p
18221841
}
18231842
EMIT_ARG(pop_jump_if_true, top_label);
18241843

1825-
// discard final value of var that failed the loop condition
1826-
EMIT(pop_top);
1827-
18281844
// break/continue apply to outer loop (if any) in the else block
18291845
END_BREAK_CONTINUE_BLOCK
18301846

18311847
compile_node(comp, pn_else);
18321848

18331849
EMIT_ARG(label_assign, break_label);
1850+
1851+
// discard final value of var that failed the loop condition
1852+
EMIT(pop_top);
1853+
1854+
// discard <end> value if it's on the stack
1855+
if (end_on_stack) {
1856+
EMIT(pop_top);
1857+
}
18341858
}
18351859
#endif
18361860

tests/basics/for3.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
# test assigning to range parameter within the loop
77
# (since we optimise for loops, this needs checking, currently it fails)
8-
#n = 2
9-
#for i in range(n):
10-
# print(i)
11-
# n = 0
8+
n = 2
9+
for i in range(n):
10+
print(i)
11+
n = 0

0 commit comments

Comments
 (0)