Note [Floating primops]
We try to float-in a case expression over an unlifted type. The motivating example was #5658: in particular, this change allows array indexing operations, which have a single DEFAULT alternative without any binders, to be floated inward. In particular, we want to be able to transform case indexIntArray# arr i of vi { __DEFAULT -> case <# j n of _ { __DEFAULT -> False 1# -> case indexIntArray# arr j of vj { __DEFAULT -> ... vi ... vj ... } } } by floating in `indexIntArray# arr i` to produce case <# j n of _ { __DEFAULT -> False 1# -> case indexIntArray# arr i of vi { __DEFAULT -> case indexIntArray# arr j of vj { __DEFAULT -> ... vi ... vj ... } } } ...which skips the `indexIntArray# arr i` call entirely in the out-of-bounds branch. SIMD primops for unpacking SIMD vectors into an unboxed tuple of unboxed scalars also need to be floated inward, but unpacks have a single non-DEFAULT alternative that binds the elements of the tuple. We now therefore also support floating in cases with a single alternative that may bind values. But there are wrinkles * Which unlifted cases do we float? See Note [Transformations affected by primop effects] in GHC.Builtin.PrimOps which explains: - We can float in or discard CanFail primops, but we can't float them out. - We don't want to discard a synchronous exception or side effect so we don't float those at all. Hence exprOkToDiscard. - Throwing precise exceptions is a special case of the previous point: We may /never/ float in a call to (something that ultimately calls) 'raiseIO#'. See Note [Precise exceptions and strictness analysis] in GHC.Types.Demand. * Because we can float can-fail primops (array indexing, division) inwards but not outwards, we must be careful not to transform case a /# b of r -> f (F# r) ===> f (case a /# b of r -> F# r) because that creates a new thunk that wasn't there before. And because it can't be floated out (CanFail), the thunk will stay there. Disaster! (This happened in nofib 'simple' and 'scs'.) Solution: only float cases into the branches of other cases, and not into the arguments of an application, or the RHS of a let. This is somewhat conservative, but it's simple. And it still hits the cases like #5658. This is implemented in sepBindsByJoinPoint; if is_case is False we dump all floating cases right here. * #14511 is another example of why we want to restrict float-in of case-expressions. Consider case indexArray# a n of (# r #) -> writeArray# ma i (f r) Now, floating that indexing operation into the (f r) thunk will not create any new thunks, but it will keep the array 'a' alive for much longer than the programmer expected. So again, not floating a case into a let or argument seems like the Right Thing For @Case@, the possible drop points for the 'to_drop' bindings are: (a) inside the scrutinee (b) inside one of the alternatives/default (default FVs always /first/!).
References 2
- Transformations affected by primop effects GHC.Builtin.PrimOps
- Precise exceptions and strictness analysis GHC.Types.Demand
Referenced by 4
- Transformations affected by primop effects GHC.Builtin.PrimOps ×2
- GHC.Core.Opt.FloatIn call site ×2