INLINABLE exports bottom crud
INLINABLE
bindings have "unoptimized" unfoldings, the better to optimize in context and with RULES
. One aspect of this is a bit annoying: error calls are included in the unfolding. Example:
module Gunk where
import GHC.Err
{-# INLINABLE potato #-}
potato :: (Num a, Eq a) => a -> a
potato 3 = errorWithoutStackTrace "3 is a fake number"
potato n = n - 17
Compiling with -O2 -ddump-simpl
gives the following:
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
lvl_r1pI :: Integer
[GblId, Unf=OtherCon []]
lvl_r1pI = 17
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
lvl1_r1qY :: GHC.Prim.Addr#
[GblId, Unf=OtherCon []]
lvl1_r1qY = "3 is a fake number"#
-- RHS size: {terms: 4, types: 4, coercions: 0, joins: 0/0}
lvl2_r1qZ :: forall {a}. a
[GblId, Str=b, Cpr=b]
lvl2_r1qZ
= \ (@a_a1oE) ->
errorWithoutStackTrace
@'GHC.Types.LiftedRep
@a_a1oE
(GHC.CString.unpackCString# lvl1_r1qY)
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
lvl3_r1r0 :: Integer
[GblId, Unf=OtherCon []]
lvl3_r1r0 = 3
-- RHS size: {terms: 20, types: 13, coercions: 0, joins: 0/0}
potato [InlPrag=INLINABLE] :: forall a. (Num a, Eq a) => a -> a
[GblId,
Arity=3,
Str=<S(LC(C(S))LLLLL),U(A,1*C1(C1(U)),A,A,A,A,C(U))><S(C(C(S))L),1*U(1*C1(C1(U)),A)><L,U>,
Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=IF_ARGS [90 30 0] 460 0
Tmpl= \ (@a_a1oE)
($dNum_a1oF :: Num a_a1oE)
($dEq_a1oG [Occ=Once1] :: Eq a_a1oE)
(ds_d1p7 :: a_a1oE) ->
case ==
@a_a1oE $dEq_a1oG ds_d1p7 (fromInteger @a_a1oE $dNum_a1oF 3)
of {
False ->
- @a_a1oE $dNum_a1oF ds_d1p7 (fromInteger @a_a1oE $dNum_a1oF 17);
True ->
errorWithoutStackTrace
@'GHC.Types.LiftedRep
@a_a1oE
(GHC.Base.build
@Char
(\ (@b_a1pT) ->
GHC.CString.unpackFoldrCString# @b_a1pT "3 is a fake number"#))
}}]
potato
= \ (@a_a1oE)
($dNum_a1oF :: Num a_a1oE)
($dEq_a1oG :: Eq a_a1oE)
(ds_d1p7 :: a_a1oE) ->
case ==
@a_a1oE
$dEq_a1oG
ds_d1p7
(fromInteger @a_a1oE $dNum_a1oF lvl3_r1r0)
of {
False ->
- @a_a1oE
$dNum_a1oF
ds_d1p7
(fromInteger @a_a1oE $dNum_a1oF lvl_r1pI);
True -> lvl2_r1qZ @a_a1oE
}
This unfolding means that each specialization of potato
will have its very own
errorWithoutStackTrace
@'GHC.Types.LiftedRep
@a_a1oE
(GHC.Base.build
@Char
(\ (@b_a1pT) ->
GHC.CString.unpackFoldrCString# @b_a1pT "3 is a fake number"#)
To the best of my knowledge, there is no possible way this can reveal optimization opportunities. It's also rather difficult to imagine someone writing RULES
that try to match on a particular error call. Thus it seems to me that terms known to be bottom should be allowed to float to the top level from even INLINABLE
bindings, if they can do so. A great many cases (if not most) are of the trivial sort exhibited here, where a term can be seen to be bottom because it's a fully saturated call to a top-level function known to throw an exception or diverge.
The current workaround is for user's to manually pull error calls out of functions marked INLINABLE
and class method definitions. It's tolerable, but it's awfully annoying!