From d084c318b6c709790710ae587409e1d1211c7128 Mon Sep 17 00:00:00 2001 From: Jan Mercl <0xjnml@gmail.com> Date: Tue, 13 Oct 2026 22:19:11 +0200 Subject: [PATCH] Read the limit of a C loop on every pass when the loop counts down CheckSimpleIncrementLoop rewrites `for (i = 0; i < n; i++)`, with i not used in the body, as a count down from n. That reads n once, before the loop. In C the limit is read on every pass, so a local the body changes, a global, and a global a called function changes all ran the count they had when the loop began (totalspectrum/flexprop#119). For C, the rewrite now requires a limit that cannot change in the loop: a constant, or a local whose address is not taken and which the body does not modify. Spin and BASIC loops are rewritten as before. Test/cexec07.c prints 5 5 5 5 on master and 3 3 3 5 with this change, as gcc does. --- Test/Expect/cexec07.txt | 1 + Test/cexec07.c | 48 +++++++++++++++++++++++++++++++++++++++++ loops.c | 37 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 Test/Expect/cexec07.txt create mode 100644 Test/cexec07.c diff --git a/Test/Expect/cexec07.txt b/Test/Expect/cexec07.txt new file mode 100644 index 000000000..ed2122a5c --- /dev/null +++ b/Test/Expect/cexec07.txt @@ -0,0 +1 @@ +3 3 3 5 diff --git a/Test/cexec07.c b/Test/cexec07.c new file mode 100644 index 000000000..b3d1ca719 --- /dev/null +++ b/Test/cexec07.c @@ -0,0 +1,48 @@ +// +// a counting loop whose limit changes inside it must read the limit on +// every pass (flexprop issue 119) +// +#include +#include + +int gn = 5; +int gm = 5; + +void myexit(int n) +{ + putchar(0xff); + putchar(0x0); + putchar(n); + waitcnt(getcnt() + 40000000); +#ifdef __OUTPUT_BYTECODE__ + _cogstop(_cogid()); +#else + __asm { + cogid n + cogstop n + } +#endif +} + +void lower(void) { gm = 3; } + +void main() { + int c = 0, d = 0, e = 0, f = 0, i, n = 5, k = 5; + for (i = 0; i < n; i++) { // a local limit the body lowers + c++; + if (c == 2) n = 3; + } + for (i = 0; i < gn; i++) { // a global limit the body lowers + d++; + if (d == 2) gn = 3; + } + for (i = 0; i < gm; i++) { // a global limit a called function lowers + e++; + if (e == 2) lower(); + } + for (i = 0; i < k; i++) { // a local limit the body leaves alone + f++; + } + printf("%d %d %d %d\n", c, d, e, f); + myexit(0); +} diff --git a/loops.c b/loops.c index f26c0aba5..446f35e04 100644 --- a/loops.c +++ b/loops.c @@ -956,6 +956,39 @@ doHasBranch(AST *body, bool inCase) static bool HasBranch(AST *body) { return doHasBranch(body, false); } +// +// true if the limit of a loop is the same on every pass: a constant, or a local +// whose address is not taken and which the body does not modify. The loop below +// is rewritten to count down from the limit read once, before the loop; a +// variable in memory may be changed by any function the body calls, or by +// another cog. +// +static bool +IsLoopInvariantLimit(AST *limit, AST *body) +{ + Symbol *sym; + + if (!limit) return false; + if (IsConstExpr(limit)) return true; + if (limit->kind == AST_OPERATOR && (limit->d.ival == '+' || limit->d.ival == '-')) { + return IsLoopInvariantLimit(limit->left, body) && IsLoopInvariantLimit(limit->right, body); + } + if (!IsIdentifier(limit)) return false; + sym = LookupAstSymbol(limit, NULL); + if (!sym) return false; + switch (sym->kind) { + case SYM_PARAMETER: + case SYM_RESULT: + case SYM_LOCALVAR: + case SYM_TEMPVAR: + if (curfunc->local_address_taken) return false; + if (limit->kind == AST_LOCAL_IDENTIFIER) limit = limit->left; + return !AstModifiesIdentifier(body, limit); + default: + return false; + } +} + // // check for "simple" loops using increments (like "repeat i from 0 to 9") these may be further // optimized into "repeat 10" if we discover that the loop index is not @@ -1052,6 +1085,10 @@ CheckSimpleIncrementLoop(AST *stmt) } } + /* in C the limit is read on every pass; the rewritten loop reads it once */ + if (IsCLang(curfunc->language) && !IsLoopInvariantLimit(updateLimit, body)) { + return; + } /* check that the update is i++, and the variable is not used anywhere else */ while (update && update->kind == AST_SEQUENCE) { if (AstUsesName(update->right, updateVar)) {