summaryrefslogtreecommitdiff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/GHC/Core/Opt/ConstantFold.hs289
1 files changed, 230 insertions, 59 deletions
diff --git a/compiler/GHC/Core/Opt/ConstantFold.hs b/compiler/GHC/Core/Opt/ConstantFold.hs
index f2c9e95253..2da7348d66 100644
--- a/compiler/GHC/Core/Opt/ConstantFold.hs
+++ b/compiler/GHC/Core/Opt/ConstantFold.hs
@@ -1431,10 +1431,13 @@ instance Alternative RuleM where
instance MonadPlus RuleM
getPlatform :: RuleM Platform
-getPlatform = roPlatform <$> getEnv
+getPlatform = roPlatform <$> getRuleOpts
-getEnv :: RuleM RuleOpts
-getEnv = RuleM $ \env _ _ _ -> Just env
+getRuleOpts :: RuleM RuleOpts
+getRuleOpts = RuleM $ \rule_opts _ _ _ -> Just rule_opts
+
+getEnv :: RuleM InScopeEnv
+getEnv = RuleM $ \_ env _ _ -> Just env
liftMaybe :: Maybe a -> RuleM a
liftMaybe Nothing = mzero
@@ -1468,6 +1471,20 @@ getInScopeEnv = RuleM $ \_ iu _ _ -> Just iu
getFunction :: RuleM Id
getFunction = RuleM $ \_ _ fn _ -> Just fn
+exprIsVarApp_maybe :: InScopeEnv -> CoreExpr -> Maybe (Id,CoreArg)
+exprIsVarApp_maybe env@(_, id_unf) e = case e of
+ App (Var f) a -> Just (f, a)
+ Var v
+ | Just rhs <- expandUnfolding_maybe (id_unf v)
+ -> exprIsVarApp_maybe env rhs
+ _ -> Nothing
+
+-- | Looks into the expression or its unfolding to find "App (Var f) x"
+isVarApp :: InScopeEnv -> CoreExpr -> RuleM (Id,CoreArg)
+isVarApp env e = case exprIsVarApp_maybe env e of
+ Nothing -> mzero
+ Just r -> pure r
+
isLiteral :: CoreExpr -> RuleM Literal
isLiteral e = do
env <- getInScopeEnv
@@ -1509,13 +1526,13 @@ getLiteral n = RuleM $ \_ _ _ exprs -> case drop n exprs of
unaryLit :: (RuleOpts -> Literal -> Maybe CoreExpr) -> RuleM CoreExpr
unaryLit op = do
- env <- getEnv
+ env <- getRuleOpts
[Lit l] <- getArgs
liftMaybe $ op env (convFloating env l)
binaryLit :: (RuleOpts -> Literal -> Literal -> Maybe CoreExpr) -> RuleM CoreExpr
binaryLit op = do
- env <- getEnv
+ env <- getRuleOpts
[Lit l1, Lit l2] <- getArgs
liftMaybe $ op env (convFloating env l1) (convFloating env l2)
@@ -2048,64 +2065,91 @@ builtinBignumRules =
, bignum_popcount "integerPopCount" integerPopCountName mkLitIntWrap
, bignum_popcount "naturalPopCount" naturalPopCountName mkLitWordWrap
- -- identity passthrough
- , id_passthrough "Int# -> Integer -> Int#"
- integerToIntName integerISName
- , id_passthrough "Word# -> Integer -> Word#"
- integerToWordName integerFromWordName
- , id_passthrough "Int64# -> Integer -> Int64#"
- integerToInt64Name integerFromInt64Name
- , id_passthrough "Word64# -> Integer -> Word64#"
- integerToWord64Name integerFromWord64Name
- , id_passthrough "Natural -> Integer -> Natural (wrap)"
- integerToNaturalName integerFromNaturalName
- , id_passthrough "Natural -> Integer -> Natural (throw)"
- integerToNaturalThrowName integerFromNaturalName
- , id_passthrough "Natural -> Integer -> Natural (clamp)"
- integerToNaturalClampName integerFromNaturalName
- , id_passthrough "Word# -> Natural -> Word#"
- naturalToWordName naturalNSName
- , id_passthrough "Word# -> Natural -> Word# (clamp)"
- naturalToWordClampName naturalNSName
-
- -- passthrough bignum small constructors with a conversion that can be done
- -- directly instead
-
- , small_passthrough "Int# -> Integer -> Word#"
+ ------------------------------------------------------------
+ -- The following `small_passthough_*` rules are used to optimise conversions
+ -- between numeric types by avoiding passing through "small" constructors of
+ -- Integer and Natural.
+ --
+ -- See Note [Optimising conversions between numeric types]
+ --
+
+ , small_passthrough_id "Word# -> Natural -> Word#"
+ naturalNSName naturalToWordName
+ , small_passthrough_id "Word# -> Natural -> Word# (clamp)"
+ naturalNSName naturalToWordClampName
+
+ , small_passthrough_id "Int# -> Integer -> Int#"
+ integerISName integerToIntName
+ , small_passthrough_id "Word# -> Integer -> Word#"
+ integerFromWordName integerToWordName
+ , small_passthrough_id "Int64# -> Integer -> Int64#"
+ integerFromInt64Name integerToInt64Name
+ , small_passthrough_id "Word64# -> Integer -> Word64#"
+ integerFromWord64Name integerToWord64Name
+ , small_passthrough_id "Natural -> Integer -> Natural (wrap)"
+ integerFromNaturalName integerToNaturalName
+ , small_passthrough_id "Natural -> Integer -> Natural (throw)"
+ integerFromNaturalName integerToNaturalThrowName
+ , small_passthrough_id "Natural -> Integer -> Natural (clamp)"
+ integerFromNaturalName integerToNaturalClampName
+
+ , small_passthrough_app "Int# -> Integer -> Word#"
integerISName integerToWordName (mkPrimOpId IntToWordOp)
- , small_passthrough "Int# -> Integer -> Float#"
+ , small_passthrough_app "Int# -> Integer -> Float#"
integerISName integerToFloatName (mkPrimOpId IntToFloatOp)
- , small_passthrough "Int# -> Integer -> Double#"
+ , small_passthrough_app "Int# -> Integer -> Double#"
integerISName integerToDoubleName (mkPrimOpId IntToDoubleOp)
- , small_passthrough "Word# -> Integer -> Float#"
+
+ , small_passthrough_app "Word# -> Integer -> Int#"
+ integerFromWordName integerToIntName (mkPrimOpId WordToIntOp)
+ , small_passthrough_app "Word# -> Integer -> Float#"
integerFromWordName integerToFloatName (mkPrimOpId WordToFloatOp)
- , small_passthrough "Word# -> Integer -> Double#"
+ , small_passthrough_app "Word# -> Integer -> Double#"
integerFromWordName integerToDoubleName (mkPrimOpId WordToDoubleOp)
-
- , small_passthrough "Word# -> Natural -> Float#"
+ , small_passthrough_app "Word# -> Integer -> Natural (wrap)"
+ integerFromWordName integerToNaturalName naturalNSId
+ , small_passthrough_app "Word# -> Integer -> Natural (throw)"
+ integerFromWordName integerToNaturalThrowName naturalNSId
+ , small_passthrough_app "Word# -> Integer -> Natural (clamp)"
+ integerFromWordName integerToNaturalClampName naturalNSId
+
+ , small_passthrough_app "Word# -> Natural -> Float#"
naturalNSName naturalToFloatName (mkPrimOpId WordToFloatOp)
- , small_passthrough "Word# -> Natural -> Double#"
+ , small_passthrough_app "Word# -> Natural -> Double#"
naturalNSName naturalToDoubleName (mkPrimOpId WordToDoubleOp)
#if WORD_SIZE_IN_BITS < 64
- , id_passthrough "Int64# -> Integer -> Int64#"
- integerToInt64Name integerFromInt64Name
- , id_passthrough "Word64# -> Integer -> Word64#"
- integerToWord64Name integerFromWord64Name
+ , small_passthrough_id "Int64# -> Integer -> Int64#"
+ integerFromInt64Name integerToInt64Name
+ , small_passthrough_id "Word64# -> Integer -> Word64#"
+ integerFromWord64Name integerToWord64Name
- , small_passthrough "Int64# -> Integer -> Word64#"
+ , small_passthrough_app "Int64# -> Integer -> Word64#"
integerFromInt64Name integerToWord64Name (mkPrimOpId Int64ToWord64Op)
- , small_passthrough "Word64# -> Integer -> Int64#"
+ , small_passthrough_app "Word64# -> Integer -> Int64#"
integerFromWord64Name integerToInt64Name (mkPrimOpId Word64ToInt64Op)
- , small_passthrough "Word# -> Integer -> Word64#"
+ , small_passthrough_app "Word# -> Integer -> Word64#"
integerFromWordName integerToWord64Name (mkPrimOpId WordToWord64Op)
- , small_passthrough "Word64# -> Integer -> Word#"
+ , small_passthrough_app "Word64# -> Integer -> Word#"
integerFromWord64Name integerToWordName (mkPrimOpId Word64ToWordOp)
- , small_passthrough "Int# -> Integer -> Int64#"
+ , small_passthrough_app "Int# -> Integer -> Int64#"
integerISName integerToInt64Name (mkPrimOpId IntToInt64Op)
- , small_passthrough "Int64# -> Integer -> Int#"
+ , small_passthrough_app "Int64# -> Integer -> Int#"
integerFromInt64Name integerToIntName (mkPrimOpId Int64ToIntOp)
+
+ , small_passthrough_custom "Int# -> Integer -> Word64#"
+ integerISName integerToWord64Name
+ (\x -> Var (mkPrimOpId Int64ToWord64Op) `App` (Var (mkPrimOpId IntToInt64Op) `App` x))
+ , small_passthrough_custom "Word64# -> Integer -> Int#"
+ integerFromWord64Name integerToIntName
+ (\x -> Var (mkPrimOpId WordToIntOp) `App` (Var (mkPrimOpId Word64ToWordOp) `App` x))
+ , small_passthrough_custom "Word# -> Integer -> Int64#"
+ integerFromWordName integerToInt64Name
+ (\x -> Var (mkPrimOpId Word64ToInt64Op) `App` (Var (mkPrimOpId WordToWord64Op) `App` x))
+ , small_passthrough_custom "Int64# -> Integer -> Word#"
+ integerFromInt64Name integerToWordName
+ (\x -> Var (mkPrimOpId IntToWordOp) `App` (Var (mkPrimOpId Int64ToIntOp) `App` x))
#endif
-- Bits.bit
@@ -2159,15 +2203,17 @@ builtinBignumRules =
-- The data constructor may or may not have a wrapper, but if not
-- dataConWrapId will return the worker
--
- integerISName = idName (dataConWrapId integerISDataCon)
- naturalNSName = idName (dataConWrapId naturalNSDataCon)
+ integerISId = dataConWrapId integerISDataCon
+ naturalNSId = dataConWrapId naturalNSDataCon
+ integerISName = idName integerISId
+ naturalNSName = idName naturalNSId
mkRule str name nargs f = BuiltinRule
{ ru_name = fsLit str
, ru_fn = name
, ru_nargs = nargs
, ru_try = runRuleM $ do
- env <- getEnv
+ env <- getRuleOpts
guard (roBignumRules env)
f
}
@@ -2269,15 +2315,18 @@ builtinBignumRules =
x <- isNumberLiteral a0
pure $ Lit (mk_lit platform (fromIntegral (popCount x)))
- id_passthrough str to_x from_x = mkRule str to_x 1 $ do
- [App (Var f) x] <- getArgs
- guard (idName f == from_x)
- pure x
+ small_passthrough_id str from_x to_x =
+ small_passthrough_custom str from_x to_x id
+
+ small_passthrough_app str from_x to_y x_to_y =
+ small_passthrough_custom str from_x to_y (App (Var x_to_y))
- small_passthrough str from_x to_y x_to_y = mkRule str to_y 1 $ do
- [App (Var f) x] <- getArgs
+ small_passthrough_custom str from_x to_y x_to_y = mkRule str to_y 1 $ do
+ [a0] <- getArgs
+ env <- getEnv
+ (f,x) <- isVarApp env a0
guard (idName f == from_x)
- pure $ App (Var x_to_y) x
+ pure $ x_to_y x
bignum_bit str name mk_lit = mkRule str name 1 $ do
[a0] <- getArgs
@@ -2640,7 +2689,7 @@ match_inline _ = Nothing
addFoldingRules :: PrimOp -> NumOps -> RuleM CoreExpr
addFoldingRules op num_ops = do
massert (op == numAdd num_ops)
- env <- getEnv
+ env <- getRuleOpts
guard (roNumConstantFolding env)
[arg1,arg2] <- getArgs
platform <- getPlatform
@@ -2652,7 +2701,7 @@ addFoldingRules op num_ops = do
subFoldingRules :: PrimOp -> NumOps -> RuleM CoreExpr
subFoldingRules op num_ops = do
massert (op == numSub num_ops)
- env <- getEnv
+ env <- getRuleOpts
guard (roNumConstantFolding env)
[arg1,arg2] <- getArgs
platform <- getPlatform
@@ -2661,7 +2710,7 @@ subFoldingRules op num_ops = do
mulFoldingRules :: PrimOp -> NumOps -> RuleM CoreExpr
mulFoldingRules op num_ops = do
massert (op == numMul num_ops)
- env <- getEnv
+ env <- getRuleOpts
guard (roNumConstantFolding env)
[arg1,arg2] <- getArgs
platform <- getPlatform
@@ -3245,4 +3294,126 @@ Hence caseRules returns (AltCon -> Maybe AltCon), with Nothing indicating
an alternative that is unreachable.
You may wonder how this can happen: check out #15436.
+
+
+Note [Optimising conversions between numeric types]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Converting between numeric types is very common in Haskell codes. Suppose that
+we have N inter-convertible numeric types (Word, Word8, Int, Integer, etc.).
+
+- We don't want to have to use one conversion function per pair of types as that
+would require N^2 functions: wordToWord8, wordToInt, wordToInteger...
+
+- The following kind of class would allow us to have a single conversion
+function at the price of N^2 instances and of the use of MultiParamTypeClasses
+extension.
+
+ class Convert a b where
+ convert :: a -> b
+
+What we do instead is that we use the Integer type (signed, unbounded) as a
+passthrough type to perform every conversion. Hence we only need to define two
+functions per numeric type:
+
+ class Integral a where
+ toInteger :: a -> Integer
+
+ class Num a where
+ fromInteger :: Integer -> a
+
+These classes have a single parameter and can be derived automatically (e.g. for
+newtypes). So we don't even have to define 2*N instances.
+
+fromIntegral
+------------
+
+We can now define a generic conversion function:
+
+ -- in the Prelude
+ fromIntegral :: (Integral a, Num b) => a -> b
+ fromIntegral = fromInteger . toInteger
+
+The trouble with this approach is that performance might be terrible. E.g.
+converting an Int into a Word, which is a no-op at the machine level, becomes
+costly when performed via `fromIntegral` because an Integer has to be allocated.
+
+To alleviate this:
+
+- first `fromIntegral` was specialized (SPECIALIZE pragma). However it would
+need N^2 pragmas to cover every case and it wouldn't cover user defined numeric
+types which don't belong to base.
+
+- while writing this note I discovered that we have a `-fwarn-identities` warning
+to detect useless conversions (since 0656c72a8f):
+
+ > fromIntegral (1 :: Int) :: Int
+
+ <interactive>:3:21: warning: [-Widentities]
+ Call of fromIntegral :: Int -> Int
+ can probably be omitted
+
+- but more importantly, many rules were added (e.g. in e0c787c10f):
+
+ "fromIntegral/Int8->Int8" fromIntegral = id :: Int8 -> Int8
+ "fromIntegral/a->Int8" fromIntegral = \x -> case fromIntegral x of I# x# -> I8# (intToInt8# x#)
+ "fromIntegral/Int8->a" fromIntegral = \(I8# x#) -> fromIntegral (I# x#)
+
+ The idea was to ensure that only cheap conversions ended up being used. E.g.:
+
+ foo :: Int8 --> {- Integer -> -} -> Word8
+ foo = fromIntegral
+
+ ====> {Some fromIntegral rule for Int8}
+
+ foo :: Int8 -> {- Int -> Integer -} -> Word8
+ foo = fromIntegral . int8ToInt
+
+ ====> {Some fromIntegral rule for Word8}
+
+ foo :: Int8 -> {- Int -> Integer -> Word -} -> Word8
+ foo = wordToWord8 . fromIntegral . int8ToInt
+
+ ====> {Some fromIntegral rule for Int/Word}
+
+ foo :: Int8 -> {- Int -> Word -} -> Word8
+ foo = wordToWord8 . intToWord . int8ToInt
+ -- not passing through Integer anymore!
+
+
+It worked but there were still some issues with this approach:
+
+1. These rules only work for `fromIntegral`. If we wanted to define our own
+ similar function (e.g. using other type-classes), we would also have to redefine
+ all the rules to get similar performance.
+
+2. `fromIntegral` had to be marked `NOINLINE [1]`:
+ - NOINLINE to allow rules to match
+ - [1] to allow inlining in later phases to avoid incurring a function call
+ overhead for such a trivial operation
+
+ Users of the function had to be careful because a simple helper without an
+ INLINE pragma like:
+
+ toInt :: Integral a => a -> Int
+ toInt = fromIntegral
+
+ has the following unfolding:
+
+ toInt = integerToInt . toInteger
+
+ which doesn't mention `fromIntegral` anymore. Hence `fromIntegral` rules
+ wouldn't be triggered for any user of `toInt`. For this reason, we also have
+ a bunch of rules for bignum primitives such as `integerToInt`.
+
+3. These rewrite rules are tedious to write and error-prone (cf #19345).
+
+
+For these reasons, it is simpler to only rely on built-in rewrite rules for
+bignum primitives. There aren't so many conversion primitives:
+ - Natural <-> Word
+ - Integer <-> Int/Word/Natural (+ Int64/Word64 on 32-bit arch)
+
+All the built-in "small_passthrough_*" rules are used to avoid passing through
+Integer/Natural. We now always inline `fromIntegral`.
+
-}