For example, try to compile a Rust `match int { 0 => 0, ..., MAX => MAX }` statement with one arm per `int` value: you'll notice that for `u8` this kind of works fast: 1 - 2 seconds on my machine, but that means I'd expect compilation to take ~4-8 minutes for a `u16` with `u16::MAX` match arms, yet it takes > 30 minutes [0].
So the complexity of compiling Rust match statements is not linear in the number of match arms. And this is with a single language feature in isolation.
Now write a 100k LOC function using many different language features, each of which have worst case complexities larger than O(N), with different constant factors.
[0]: it doesn't really matter that much to me why this is the case. E.g. the compiler might be building extra data-structures that fill in the RAM, and then my system starts swapping, and it crawls. Point being, the problem is too large for my system, but one is able to hit this problem on any system with a sufficiently large input.
If, however, the system is just O(n)*k where k is lines of code, there probably isn't much you can do.
For example, you could have a register coloring algorithm that runs in O(n^2) time that has no relation to number of lines of code. It is not unusual to have some optimization pass that has quadratic worst-case behavior but much better average behavior that makes it worth it 99% of the time but 1% of the time goes pathological.
It could be number of registers spilled (graph coloring algorithms are NP-complete but have useful heuristic solutions ... most of the time), number of macros allocated, loop iterations unrolled, etc.
If any of those things has a quadratic (or worse(!)) behavior, they may make compile times blow up if you hit a pathological boundary condition.
And, sometimes I might even want that quadratic behavior optimization. If I'm trying to squeeze every single byte out of a program because I am on a memory constrained system, I'm probably pretty happy to let the compiler churn for a couple of hours to crush my program into memory if it means I don't have to start rewriting code that is nominally correct and risk introducing bugs.
In other words, intraprocedural optimisation has always been more aggressive than interprocedural optimisation. If you try inlining every function ever into one giant function, you'll be forced to scale back optimization levels.
if IR_count > ...
warn "Can't run optimiser ... pass on {func} (too large)"
else
run_pass