Replying specifically to Sanjoy's questions inline. For the record, I've talked with Max about his proposal in depth offline and generally support the direction. As Sanjoy pointed out, there are some details to be worked out, but Max has gotten far enough with his prototyping at this point that I'm personally convinced they're all handleable w/effort.
If we only need the guard representation only for widening, why not
first run some cleanup passes, widen the guards and then lower the
guards and then run the rest of the pipeline which will see the
explicit branching?
Key thing is that we want to be able to widen conditions across function boundaries. Here's a trivial example:
int load(int a, int offset) { return a[offset]; }
int b(int a) { load(a, 0) + load(a,1); }
This example should require (at most) one runtime range check.
As such, we need the widenable representation through inlining which is a very sizable chunk of the pipeline.
Also, in the past we had talked about piggybacking on
AssumptionCacheTracker to solve some of the problems (with the
implicit nature of guards) you highlight. Can that be made to work?
The conclusion of the "let's just use invokes" thread I started a while ago was essentially the opposite. That is, we should be using invokes for assumes as well specifically because it eliminates the need for anything like an assumption cache.
Or to say it differently, yes, we can improve (a bunch) the current implementation by commoning some code between guards and assumes, and using explicit vs implicit control flow (i.e. terminators), but we still have the general problem of needing to update every single pass. You could argue that if we did a better job of sharing/structuring the code, this would at least mean we picked up support for assumes pretty much everywhere too, but I'm personally not convinced that's a large enough value to justify the investment.
It's also a bit odd to have an invoke without a meaningful unwind target which is what we end up with for both assumes and invokes. You essentially end up having to special case unreachable to leave the invoke in place. Doable, but a bit odd.
I was originally of the opinion that improving/commoning/merging the current representation was the easiest path forward, but Max's prototyping work has impressed me with how few changes he's needed to make to get full coverage of the proposed representation throughout the optimizer. I'm (weakly) convinced his proposal is the right path forward, but I think either approach could be made to work without an unsurmountable amount of effort.