[Git][ghc/ghc][master] Improve the situation with the stimes cycle

Marge Bot (@marge-bot) gitlab at gitlab.haskell.org
Wed Jul 5 15:43:05 UTC 2023



Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC


Commits:
9ce44336 by meooow25 at 2023-07-05T11:42:37-04:00
Improve the situation with the stimes cycle

Currently the Semigroup stimes cycle is resolved in GHC.Base by
importing stimes implementations from a hs-boot file. Resolve the cycle
using hs-boot files for required classes (Num, Integral) instead. Now
stimes can be defined directly in GHC.Base, making inlining and
specialization possible.

This leads to some new boot files for `GHC.Num` and `GHC.Real`, the
methods for those are only used to implement `stimes` so it doesn't
appear that these boot files will introduce any new performance traps.

Metric Decrease:
    T13386
    T8095
Metric Increase:
    T13253
    T13386
    T18698a
    T18698b
    T19695
    T8095

- - - - -


10 changed files:

- libraries/base/Data/Semigroup/Internal.hs
- − libraries/base/Data/Semigroup/Internal.hs-boot
- libraries/base/GHC/Base.hs
- libraries/base/GHC/Base.hs-boot → libraries/base/GHC/Enum.hs-boot
- + libraries/base/GHC/Num.hs-boot
- libraries/base/GHC/Real.hs-boot
- libraries/base/changelog.md
- + testsuite/tests/simplCore/should_compile/T23074.hs
- + testsuite/tests/simplCore/should_compile/T23074.stderr
- testsuite/tests/simplCore/should_compile/all.T


Changes:

=====================================
libraries/base/Data/Semigroup/Internal.hs
=====================================
@@ -14,8 +14,7 @@
 -- 'Semigroup' class some.
 --
 -- This module exists mostly to simplify or workaround import-graph
--- issues; there is also a .hs-boot file to allow "GHC.Base" and other
--- modules to import method default implementations for 'stimes'
+-- issues.
 --
 -- @since 4.11.0.0
 module Data.Semigroup.Internal where
@@ -67,43 +66,12 @@ stimesMonoid n x0 = case compare n 0 of
         | y == 1 = x `mappend` z
         | otherwise = g (x `mappend` x) (y `quot` 2) (x `mappend` z) -- See Note [Half of y - 1]
 
--- this is used by the class definition in GHC.Base;
--- it lives here to avoid cycles
-stimesDefault :: (Integral b, Semigroup a) => b -> a -> a
-stimesDefault y0 x0
-  | y0 <= 0   = errorWithoutStackTrace "stimes: positive multiplier expected"
-  | otherwise = f x0 y0
-  where
-    f x y
-      | even y = f (x <> x) (y `quot` 2)
-      | y == 1 = x
-      | otherwise = g (x <> x) (y `quot` 2) x        -- See Note [Half of y - 1]
-    g x y z
-      | even y = g (x <> x) (y `quot` 2) z
-      | y == 1 = x <> z
-      | otherwise = g (x <> x) (y `quot` 2) (x <> z) -- See Note [Half of y - 1]
-
 {- Note [Half of y - 1]
    ~~~~~~~~~~~~~~~~~~~~~
    Since y is guaranteed to be odd and positive here,
    half of y - 1 can be computed as y `quot` 2, optimising subtraction away.
 -}
 
-stimesMaybe :: (Integral b, Semigroup a) => b -> Maybe a -> Maybe a
-stimesMaybe _ Nothing = Nothing
-stimesMaybe n (Just a) = case compare n 0 of
-    LT -> errorWithoutStackTrace "stimes: Maybe, negative multiplier"
-    EQ -> Nothing
-    GT -> Just (stimes n a)
-
-stimesList  :: Integral b => b -> [a] -> [a]
-stimesList n x
-  | n < 0 = errorWithoutStackTrace "stimes: [], negative multiplier"
-  | otherwise = rep n
-  where
-    rep 0 = []
-    rep i = x ++ rep (i - 1)
-
 -- | The dual of a 'Monoid', obtained by swapping the arguments of 'mappend'.
 --
 -- >>> getDual (mappend (Dual "Hello") (Dual "World"))


=====================================
libraries/base/Data/Semigroup/Internal.hs-boot deleted
=====================================
@@ -1,13 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-
-module Data.Semigroup.Internal where
-
-import {-# SOURCE #-} GHC.Real (Integral)
-import {-# SOURCE #-} GHC.Base (Semigroup,Monoid,Maybe)
-import GHC.Num.Integer () -- See Note [Depend on GHC.Num.Integer] in GHC.Base
-
-stimesIdempotentMonoid :: (Integral b, Monoid a) => b -> a -> a
-
-stimesDefault :: (Integral b, Semigroup a) => b -> a -> a
-stimesMaybe :: (Integral b, Semigroup a) => b -> Maybe a -> Maybe a
-stimesList :: Integral b => b -> [a] -> [a]


=====================================
libraries/base/GHC/Base.hs
=====================================
@@ -127,13 +127,9 @@ import {-# SOURCE #-} GHC.IO (mkUserError, mplusIO)
 import GHC.Tuple (Solo (MkSolo)) -- Note [Depend on GHC.Tuple]
 import GHC.Num.Integer ()        -- Note [Depend on GHC.Num.Integer]
 
--- for 'class Semigroup'
-import {-# SOURCE #-} GHC.Real (Integral)
-import {-# SOURCE #-} Data.Semigroup.Internal ( stimesDefault
-                                              , stimesMaybe
-                                              , stimesList
-                                              , stimesIdempotentMonoid
-                                              )
+-- See Note [Semigroup stimes cycle]
+import {-# SOURCE #-} GHC.Num (Num (..))
+import {-# SOURCE #-} GHC.Real (Integral (..))
 
 -- $setup
 -- >>> import GHC.Num
@@ -181,6 +177,38 @@ GHC.Tuple, so we use the same rule as for Integer --- see Note [Depend on
 GHC.Num.Integer] --- to explain this to the build system.  We make GHC.Base
 depend on GHC.Tuple, and everything else depends on GHC.Base or Prelude.
 
+
+Note [Semigroup stimes cycle]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Semigroup is defined in this module, GHC.Base, with the method
+stimes :: (Semigroup a, Integral b) => b -> a -> a
+
+This presents a problem.
+* We use Integral methods (quot, rem) and Num methods (-) in stimes definitions
+  in this module. Num is a superclass of Integral.
+* Num is defined in GHC.Num, which imports GHC.Base.
+* Enum is defined in GHC.Enum, which imports GHC.Base and GHC.Num. Enum is a
+  superclass of Integral. We don't use any Enum methods here, but it is relevant
+  (read on).
+* Integral is defined in GHC.Real, which imports GHC.Base, GHC.Num, and
+  GHC.Enum.
+
+We resolve this web of dependencies with hs-boot files. The rules
+https://ghc.gitlab.haskell.org/ghc/doc/users_guide/separate_compilation.html#how-to-compile-mutually-recursive-modules
+require us to put either the full declarations or only the instance head for
+classes in a hs-boot file.
+So we put the full class decls for Num and Integral in Num.hs-boot and
+Real.hs-boot respectively. This also forces us to have an Enum.hs-boot.
+
+An obvious alternative is to move the class decls for Num, Enum, Real, and
+Integral here. We don't do that because we would then need to move all the
+instances (for Int, Word, Integer, etc.) here as well, or leave those instances
+as orphans, which is generally bad.
+
+We previously resolved this problem in a different way, with an hs-boot for
+Semigroup.Internal that provided stimes implementations. This made them
+impossible to inline or specialize when used in this module. We no longer have
+that problem because we only import classes and not implementations.
 -}
 
 #if 0
@@ -282,10 +310,26 @@ class Semigroup a where
         -- >>> stimes 4 [1]
         -- [1,1,1,1]
         stimes :: Integral b => b -> a -> a
-        stimes = stimesDefault
+        stimes y0 x0
+          | y0 <= 0   = errorWithoutStackTrace "stimes: positive multiplier expected"
+          | otherwise = f x0 y0
+          where
+            f x y
+              | y `rem` 2 == 0 = f (x <> x) (y `quot` 2)
+              | y == 1 = x
+              | otherwise = g (x <> x) (y `quot` 2) x        -- See Note [Half of y - 1]
+            g x y z
+              | y `rem` 2 == 0 = g (x <> x) (y `quot` 2) z
+              | y == 1 = x <> z
+              | otherwise = g (x <> x) (y `quot` 2) (x <> z) -- See Note [Half of y - 1]
 
         {-# MINIMAL (<>) | sconcat #-}
 
+{- Note [Half of y - 1]
+   ~~~~~~~~~~~~~~~~~~~~~
+   Since y is guaranteed to be odd and positive here,
+   half of y - 1 can be computed as y `quot` 2, optimising subtraction away.
+-}
 
 -- | The class of monoids (types with an associative binary operation that
 -- has an identity).  Instances should satisfy the following:
@@ -351,7 +395,12 @@ instance Semigroup [a] where
         (<>) = (++)
         {-# INLINE (<>) #-}
 
-        stimes = stimesList
+        stimes n x
+          | n < 0 = errorWithoutStackTrace "stimes: [], negative multiplier"
+          | otherwise = rep n
+          where
+            rep 0 = []
+            rep i = x ++ rep (i - 1)
 
 -- | @since 2.01
 instance Monoid [a] where
@@ -471,7 +520,10 @@ instance Semigroup Ordering where
     EQ <> y = y
     GT <> _ = GT
 
-    stimes = stimesIdempotentMonoid
+    stimes n x = case compare n 0 of
+      LT -> errorWithoutStackTrace "stimes: Ordering, negative multiplier"
+      EQ -> EQ
+      GT -> x
 
 -- lexicographical ordering
 -- | @since 2.01
@@ -484,7 +536,11 @@ instance Semigroup a => Semigroup (Maybe a) where
     a       <> Nothing = a
     Just a  <> Just b  = Just (a <> b)
 
-    stimes = stimesMaybe
+    stimes _ Nothing = Nothing
+    stimes n (Just a) = case compare n 0 of
+      LT -> errorWithoutStackTrace "stimes: Maybe, negative multiplier"
+      EQ -> Nothing
+      GT -> Just (stimes n a)
 
 -- | Lift a semigroup into 'Maybe' forming a 'Monoid' according to
 -- <http://en.wikipedia.org/wiki/Monoid>: \"Any semigroup @S@ may be


=====================================
libraries/base/GHC/Base.hs-boot → libraries/base/GHC/Enum.hs-boot
=====================================
@@ -1,9 +1,10 @@
 {-# LANGUAGE NoImplicitPrelude #-}
 
-module GHC.Base (Maybe, Semigroup, Monoid) where
+module GHC.Enum (Enum) where
+
+-- For why this file exists
+-- See Note [Semigroup stimes cycle] in GHC.Base
 
-import GHC.Maybe (Maybe)
 import GHC.Types ()
 
-class Semigroup a
-class Monoid a
+class Enum a


=====================================
libraries/base/GHC/Num.hs-boot
=====================================
@@ -0,0 +1,24 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module GHC.Num (Num (..)) where
+
+-- For why this file exists
+-- See Note [Semigroup stimes cycle] in GHC.Base
+
+import GHC.Num.Integer (Integer)
+import GHC.Types ()
+
+infixl 7  *
+infixl 6  +, -
+
+class Num a where
+    {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
+
+    (+), (-), (*)       :: a -> a -> a
+    negate              :: a -> a
+    abs                 :: a -> a
+    signum              :: a -> a
+    fromInteger         :: Integer -> a
+
+    x - y               = x + negate y
+    negate x            = 0 - x


=====================================
libraries/base/GHC/Real.hs-boot
=====================================
@@ -1,7 +1,36 @@
 {-# LANGUAGE NoImplicitPrelude #-}
 
-module GHC.Real where
+module GHC.Real (Integral (..)) where
 
+-- For why this file exists
+-- See Note [Semigroup stimes cycle] in GHC.Base
+
+import GHC.Classes (Ord)
+import GHC.Num.Integer (Integer)
 import GHC.Types ()
 
-class Integral a
+import {-# SOURCE #-} GHC.Num (Num)
+import {-# SOURCE #-} GHC.Enum (Enum)
+
+data Ratio a
+type Rational = Ratio Integer
+
+class (Num a, Ord a) => Real a where
+    toRational          :: a -> Rational
+
+class (Real a, Enum a) => Integral a where
+    quot                :: a -> a -> a
+    rem                 :: a -> a -> a
+    div                 :: a -> a -> a
+    mod                 :: a -> a -> a
+    quotRem             :: a -> a -> (a,a)
+    divMod              :: a -> a -> (a,a)
+    toInteger           :: a -> Integer
+
+    n `quot` d          =  q  where (q,_) = quotRem n d
+    n `rem` d           =  r  where (_,r) = quotRem n d
+    n `div` d           =  q  where (q,_) = divMod n d
+    n `mod` d           =  r  where (_,r) = divMod n d
+
+    divMod n d          =  if signum r == negate (signum d) then (q-1, r+d) else qr
+                           where qr@(q,r) = quotRem n d


=====================================
libraries/base/changelog.md
=====================================
@@ -33,6 +33,7 @@
   * Implement `GHC.IORef.atomicSwapIORef` via a new dedicated primop `atomicSwapMutVar#` ([CLC proposal #139](https://github.com/haskell/core-libraries-committee/issues/139))
   * Change codebuffers to use an unboxed implementation, while providing a compatibility layer using pattern synonyms. ([CLC proposal #134](https://github.com/haskell/core-libraries-committee/issues/134))
   * Add nominal role annotations to SNat/SSymbol/SChar ([CLC proposal #170](https://github.com/haskell/core-libraries-committee/issues/170))
+  * Make `Semigroup`'s `stimes` specializable. ([CLC proposal #8](https://github.com/haskell/core-libraries-committee/issues/8))
 
 ## 4.18.0.0 *March 2023*
   * Shipped with GHC 9.6.1


=====================================
testsuite/tests/simplCore/should_compile/T23074.hs
=====================================
@@ -0,0 +1,14 @@
+module T23074 where
+
+import Data.Semigroup
+
+-- Test that stimes for SumInt is specialized
+
+newtype SumInt = SumInt Int
+
+instance Semigroup SumInt where
+  SumInt x <> SumInt y = SumInt (x + y)
+
+
+foo :: Int -> SumInt -> SumInt
+foo = stimes


=====================================
testsuite/tests/simplCore/should_compile/T23074.stderr
=====================================
@@ -0,0 +1,8 @@
+
+==================== Tidy Core rules ====================
+"SPEC $cstimes @Int"
+    forall ($dIntegral :: Integral Int).
+      $fSemigroupSumInt_$cstimes @Int $dIntegral
+      = foo
+
+


=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -484,8 +484,8 @@ test('T23307a', normal, compile, ['-O -ddump-simpl -dno-typeable-binds -dsuppres
 test('T23307b', normal, compile, ['-O'])
 test('T23307c', normal, compile, ['-O'])
 test('T23426', normal, compile, ['-O'])
-
 test('T23491a', [extra_files(['T23491.hs']), grep_errmsg(r'Float out')], multimod_compile, ['T23491', '-ffull-laziness -ddump-full-laziness'])
 test('T23491b', [extra_files(['T23491.hs']), grep_errmsg(r'Float inwards')], multimod_compile, ['T23491', '-ffloat-in -ddump-float-in'])
 test('T23491c', [extra_files(['T23491.hs']), grep_errmsg(r'Liberate case')], multimod_compile, ['T23491', '-fliberate-case -ddump-liberate-case'])
 test('T23491d', [extra_files(['T23491.hs']), grep_errmsg(r'Static argument')], multimod_compile, ['T23491', '-fstatic-argument-transformation -ddump-static-argument-transformation'])
+test('T23074', normal, compile, ['-O -ddump-rules'])



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/9ce44336ce8344ea640fdb88e47b13fd4a249ddd

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/9ce44336ce8344ea640fdb88e47b13fd4a249ddd
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20230705/70548022/attachment-0001.html>


More information about the ghc-commits mailing list