Bounds inference overhaul

Changed the way splitting clamps so that storage bounds can be computed
from first principles, instead of recursively sniffing the IR. Now it clamps
to be within the bounds realized (the bounds at the store_at level), whereas
previously it clamped to be within the bounds produced (the bounds at the
compute_at level). There's only a difference when you store_at outside the
compute_at, and the compute_at region required is smaller than the minimum
region that can be computed due to splitting/vectorization. It's a corner case,
to be sure, but the autotuner was having a field day there. This fixes
the slowness of the last commit in this series. Interpolate with HL_TARGET=ptx
went from 2 minutes to 6 seconds, most of which is actually running and
benchmarking.

Now at every loop level we track bounds required and bounds produced for each
function. The bounds required come from sniffing the lowered IR pre-bounds
inference. These will be expressions in the bounds produced of later pipeline
stages. Bounds produced are the bounds required rounded up slightly.

They get rounded up to (A) at least the size of the outermost split in the
produce step, and then get rounded up to (B) a multiple of the outermost
split used in any reduction stage.

(A) is necessary because when we split, the inner dimension must be the
size promised (in case we're vectorizing). We can make multiple instances
of the inner loop overlap though, (e.g. compute elements [0 1 2 3], then
[2, 3, 4, 5]). (B) is necessary because we're not allowed to do the
overlapping trick with reductions. Updating at a pure var multiple times
without reinitializing that site will produce the wrong result.

If we're inside the store_at level, the min produced then gets pushed
downwards so that it does not escape the realization. Outside of the
store_at level, we make sure we don't escape the bounds produced one
loop level up. I.e. we expect that the bounds produced at each loop level
are strictly contained within the bounds produced at all outer loop levels.

The whole point of this commit was to infer the bounds allocated more easily.
The bounds allocated are now just the bounds produced at the store_at level,
expanded to cover the memory locations hit by any update step (in case we're
scattering stuff out of bounds).

Now that we distinguish between bounds required and bounds produced all
through compilation, bounds inference is on a much better footing for
future changes.