Note [Guarding against silly shifts]
Consider this code:
import Data.Bits( (.|.), shiftL )
chunkToBitmap :: [Bool] -> Word32
chunkToBitmap chunk = foldr (.|.) 0 [ 1 `shiftL` n | (True,n) <- zip chunk [0..] ]
This optimises to:
Shift.$wgo = \ (w_sCS :: GHC.Prim.Int#) (w1_sCT :: [GHC.Types.Bool]) ->
case w1_sCT of _ {
[] -> 0##;
: x_aAW xs_aAX ->
case x_aAW of _ {
GHC.Types.False ->
case w_sCS of wild2_Xh {
__DEFAULT -> Shift.$wgo (GHC.Prim.+# wild2_Xh 1) xs_aAX;
9223372036854775807 -> 0## };
GHC.Types.True ->
case GHC.Prim.>=# w_sCS 64 of _ {
GHC.Types.False ->
case w_sCS of wild3_Xh {
__DEFAULT ->
case Shift.$wgo (GHC.Prim.+# wild3_Xh 1) xs_aAX of ww_sCW { __DEFAULT ->
GHC.Prim.or# (GHC.Prim.narrow32Word#
(GHC.Prim.uncheckedShiftL# 1## wild3_Xh))
ww_sCW
};
9223372036854775807 ->
GHC.Prim.narrow32Word#
!!!!--> (GHC.Prim.uncheckedShiftL# 1## 9223372036854775807)
};
GHC.Types.True ->
case w_sCS of wild3_Xh {
__DEFAULT -> Shift.$wgo (GHC.Prim.+# wild3_Xh 1) xs_aAX;
9223372036854775807 -> 0##
} } } }
Note the massive shift on line "!!!!". It can't happen, because we've checked
that w < 64, but the optimiser didn't spot that. We DO NOT want to constant-fold this!
Moreover, if the programmer writes (n `uncheckedShiftL` 9223372036854775807), we
can't constant fold it, but if it gets to the assembler we get
Error: operand type mismatch for `shl'
So the best thing to do is to rewrite the shift with a call to error,
when the second arg is large. However, in general we cannot do this; consider
this case
let x = I# (uncheckedIShiftL# n 80)
in ...
Here x contains an invalid shift and consequently we would like to rewrite it
as follows:
let x = I# (error "invalid shift")
in ...
This was originally done in the fix to #16449 but this breaks the
let-can-float invariant (see Note [Core let-can-float invariant] in
GHC.Core) as noted in #16742. For the reasons discussed under
"NoEffect" in Note [Classifying primop effects] (in GHC.Builtin.PrimOps)
there is no safe way to rewrite the argument of I# such that it bottoms.
Consequently we instead take advantage of the fact that the result of a
large shift is unspecified (see associated documentation in primops.txt.pp)
and transform the invalid shift into an "obviously incorrect" value.
There are two cases:
- Shifting fixed-width things: the primops IntSll, Sll, etc
These are handled by shiftRule.
We are happy to shift by any amount up to wordSize but no more.
- Shifting Bignums (Integer, Natural): these are handled by bignum_shift.
Here we could in principle shift by any amount, but we arbitrary
limit the shift to 4 bits; in particular we do not want shift by a
huge amount, which can happen in code like that above.
The two cases are more different in their code paths that is comfortable,
but that is only a historical accident.
************************************************************************
* *
\subsection{Vaguely generic functions}
* *
************************************************************************ References 2
- Classifying primop effects GHC.Builtin.PrimOps
- Core let-can-float invariant GHC.Core
Referenced by 3
- GHC.Core.Opt.ConstantFold call site ×2
- Classifying primop effects GHC.Builtin.PrimOps