Back in the 80's it was normal for C compilers to recognize `for` loops and do optimizations on them.
I had attended a summer class at Stanford on Data Flow Analysis, and took advantage. My compiler would break all the C flow control constructs into `if` and `goto` statements. Then, the optimizer would reconstruct the loops using graph algorithms.
Then, no matter what snarl of for, while, break, do-while, continue switch, or goto statements the programmer wrote, the optimizer could find all the loops, identify the loop variables, the loop header, etc. and then optimize it.
While good in general, that does have its downsides. I've had a case of adding an "if (undesired input format) { change input format; goto place_that_decides_method; }" resulting in clang now considering most of a massive function as being a loop body, and thus a bunch of computation from various branches being extracted to be always calculated outside "the loop" (despite me knowing that at no point should any single path (beyond the small dispatching part) be executed more than once).
All optimizations have cases where they are deleterious. The number I used for loop optimizations is assume the loop executes 10 times. It works reliably well.
GCC does the same thing to this day. As a result, the GCC AST hasn't much S in it, except for some OpenMP constructs (which are preserved in the AST). It's not great if you want to write syntax-driven tools.
I had attended a summer class at Stanford on Data Flow Analysis, and took advantage. My compiler would break all the C flow control constructs into `if` and `goto` statements. Then, the optimizer would reconstruct the loops using graph algorithms.
Then, no matter what snarl of for, while, break, do-while, continue switch, or goto statements the programmer wrote, the optimizer could find all the loops, identify the loop variables, the loop header, etc. and then optimize it.