[ Update: 1.0.14.36 improved this quite a bit (20-25%) by
eliminating useless work from PROPAGATE-FROM-SETS -- but as alluded
below, maybe we should be smarter about when to decide a derived
type is "good enough". ]

This example could be solved with clever enough constraint
propagation or with SSA, but consider

(let ((x 0))
(loop (incf x 2)))

The careful type of X is {2k} :-(. Is it really important to be
able to work with unions of many intervals?

It seems to me the right place to do that is in DERIVE-NODE-TYPE (and, optionally, in ADD-TEST-CONSTRAINT). The attached patch builds, tests (mostly), and manages to compile all the test cases above in decent time. The main question is how we want to widen/tighten types (see TYPE-ROUND-{UP,DOWN}); a specialised TYPE-APPROX-INTERSECTION2 for D-N-T would be useful, since we probably want to preserve the invariant that node types are always overwritten with subtypes.

Numeric, character set and member types are obvious candidates. Unions containing numeric types and array types are also interesting. What the patch implements is assuredly not on the Pareto front for complexity & effectiveness, but provides an idea of the possibilities.

The cases presented here were fixed in SBCL 1.0.44.28, but I suspect other cases can still be developed that cause the same problem:

The approach taken in .28 was to simplify the types derived for arithmetic operators by allowed non-adjacent numeric ranges to be merged when the unions become overly complex -- preventing the complexity from being introduced in the first place.

However, I specifically suspect that cases still exist where the complexity isn't introduced until constraint propagation -- so something like Paul's generalized type-widening still seems like a good idea.