This is one of the legacies modern C++ is still wrestling with today, supposedly solved by Rust, Zig, Val and the likes.
This is one of the legacies modern C++ is still wrestling with today, supposedly solved by Rust, Zig, Val and the likes.
This is only very slightly exaggerated.
In practice, it took years for Rust access these optimizations because the noalias annotations kept hitting codegen bugs in LLVM. It was an area of the compiler that had not seen much active use before Rust, and they were causing a lot of dormant bugs to become issues.
The feedback cycle was also very long because LLVM didn't fix the problems quickly. I didn't pay close attention so I don't know if this is prioritization or because those fixes required major surgery, but Rust sometimes had to wait for major version bumps in LLVM before seeing if "this time was the last time."
How much the optimizer can take advantage of that is another matter, due to what you said. Doing a quick translation to more idiomatic Rust[2], it does hoist the accesses to `matrix` out of the hot loop, and does also seem to be a bit less moving stuff around compared to the C version, which I think is putting the `matrix` values in the right places, which Rust did before the loop.
[1] fn transform(output: &mut [f32], input: &[f32], matrix: &[f32], n: &i32);
Normal C/C++ developers doing normal C/C++ development in GCC probably never saw any of those bugs.
I haven't kept up with the current state of things, but if you can finally liberally use noalias in LLVM without having to fear miscompilations, you probably have solely the increased significance of Rust to thank for that.
The #pragma GCC ivdep annotation also enables this in loops. C23 also has the [[unsequenced]] attribute, which supports pointer arguments for optimizations like these, unlike [[gnu::const]].
c is call by value, you have to at least try to alias
While it may be true that the rules aren't generally checked by compilers, Toolpack did it for pure Fortran77 in the 1980s, though I don't know how comprehensive it actually was.
The technicality is that Fortran is call by descriptor (not by reference, not by value). That's a powerful difference but a sharp two-edged sword.
Sometimes (most of the time, to be fair), the compiler can work out the best solution. Sometimes, the developer has to jump through some hoops to avoid unnecessary copies.