packages feed

atrophy-0.2.0.0: src/Atrophy/Known.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Division where one side is known at compile time, supplied at the type
-- level so that it is guaranteed to be a constant.
--
-- GHC's native code generator does /not/ strength-reduce division by
-- constants: @x \`quot\` 7@ compiles to a @div@ instruction. 'divK' computes
-- the magic numbers during type checking instead:
--
-- >>> divK @7 (100 :: Word64)
-- 14
--
-- 'divRemN' goes the other way: the divisor is a runtime 'StrengthReduced'
-- value, but the numerator is fixed, which lets e.g. @divN \@(2 ^ 63)@ skip
-- the multiplication entirely.
module Atrophy.Known
  ( -- * Divisors known at compile time
    KnownDivisor (..)
  , divK
  , remK
    -- * Numerators known at compile time
  , KnownNumerator (..)
  , divRemN
  , divN
  , remN
  , divRemNonZeroN
  , divNonZeroN
  , remNonZeroN
  ) where

import Atrophy.Internal
import Atrophy.Internal.Prim
import Data.Bits
import Data.Kind (Constraint, Type)
import Data.Type.Bool (If)
import Data.Type.Equality (type (==))
import Data.WideWord.Word128 (Word128 (..))
import Data.Word
import GHC.TypeLits (ErrorMessage (..), TypeError)
import GHC.TypeNats

type Assert :: Bool -> Constraint -> Constraint
type family Assert b err where
  Assert 'True _ = ()
  Assert _ err = err

type CheckDivisor :: Nat -> Nat -> Constraint
type CheckDivisor bits d =
  ( Assert (1 <=? d) (TypeError ('Text "atrophy: division by zero"))
  , Assert (d <=? 2 ^ bits - 1)
      (TypeError ('Text "atrophy: the divisor " ':<>: 'ShowType d ':<>: 'Text " does not fit in " ':<>: 'ShowType bits ':<>: 'Text " bits"))
  )

type CheckNumerator :: Nat -> Nat -> Constraint
type CheckNumerator bits n =
  Assert (n <=? 2 ^ bits - 1)
    (TypeError ('Text "atrophy: the numerator " ':<>: 'ShowType n ':<>: 'Text " does not fit in " ':<>: 'ShowType bits ':<>: 'Text " bits"))

--------------------------------------------------------------------------------
-- Magic numbers, following libdivide's unsigned branching algorithm. Since the
-- choice of algorithm is made at compile time there is no branch at runtime.
--
-- 0: power of two, shift only
-- 1: q = mulhi m n >> s
-- 2: q = (((n - t) >> 1) + t) >> s  where t = mulhi m n
--------------------------------------------------------------------------------

type IsPow2 d = 2 ^ Log2 d == d
type ProposedM bits d = Div (2 ^ (bits + Log2 d)) d
type ProposedR bits d = Mod (2 ^ (bits + Log2 d)) d
type NoAdd bits d = CmpNat (d - ProposedR bits d) (2 ^ Log2 d) == 'LT

type Algo bits d = If (IsPow2 d) 0 (If (NoAdd bits d) 1 2)

type Magic bits d =
  If (IsPow2 d) 0
    (If (NoAdd bits d)
      (ProposedM bits d + 1)
      (Mod (2 * ProposedM bits d + If (d <=? 2 * ProposedR bits d) 1 0 + 1) (2 ^ bits)))

-- | For 32 bits and below: q = mulhi c n, with c = ceil (2^64 / d).
type Lemire d = Div (2 ^ 64) d + 1

-- | Types that can be divided by the type-level constant @d@.
--
-- @divRemK \@d n == n \`quotRem\` d@, but the division is replaced by at most
-- one multiplication and some shifts, with no branches.
type KnownDivisor :: Nat -> Type -> Constraint
class KnownDivisor d a where
  divRemK :: a -> (a, a)

-- | Quotient by the type-level divisor @d@.
{-# INLINE divK #-}
divK :: forall d a. KnownDivisor d a => a -> a
divK n = case divRemK @d n of (q, _) -> q

-- | Remainder by the type-level divisor @d@.
{-# INLINE remK #-}
remK :: forall d a. KnownDivisor d a => a -> a
remK n = case divRemK @d n of (_, r) -> r

instance
  ( CheckDivisor 64 d
  , KnownNat d
  , KnownNat (Log2 d)
  , KnownNat (Algo 64 d)
  , KnownNat (Magic 64 d)
  ) => KnownDivisor d Word64 where
  {-# INLINE divRemK #-}
  divRemK n = case natWord64 @(Algo 64 d) of
    0 -> (unsafeShiftR n sh, n .&. (dv - 1))
    1 -> let !q = unsafeShiftR (mulHi64 mg n) sh in (q, n - q * dv)
    _ -> let !t = mulHi64 mg n
             !q = unsafeShiftR (unsafeShiftR (n - t) 1 + t) sh
         in (q, n - q * dv)
    where
    sh = natInt @(Log2 d)
    mg = natWord64 @(Magic 64 d)
    dv = natWord64 @d

{-# INLINE smallK #-}
smallK :: Word64 -> Int -> Word64 -> Word64 -> Word64 -> (Word64, Word64)
smallK algo sh c dv n = case algo of
  0 -> (unsafeShiftR n sh, n .&. (dv - 1))
  _ -> let !q = mulHi64 c n in (q, n - q * dv)

instance
  ( CheckDivisor 32 d
  , KnownNat d
  , KnownNat (Log2 d)
  , KnownNat (If (IsPow2 d) 0 1)
  , KnownNat (Lemire d)
  ) => KnownDivisor d Word32 where
  {-# INLINE divRemK #-}
  divRemK n =
    case smallK (natWord64 @(If (IsPow2 d) 0 1)) (natInt @(Log2 d)) (natWord64 @(Lemire d)) (natWord64 @d) (fromIntegral n) of
      (q, r) -> (fromIntegral q, fromIntegral r)

instance
  ( CheckDivisor 16 d
  , KnownNat d
  , KnownNat (Log2 d)
  , KnownNat (If (IsPow2 d) 0 1)
  , KnownNat (Lemire d)
  ) => KnownDivisor d Word16 where
  {-# INLINE divRemK #-}
  divRemK n =
    case smallK (natWord64 @(If (IsPow2 d) 0 1)) (natInt @(Log2 d)) (natWord64 @(Lemire d)) (natWord64 @d) (fromIntegral n) of
      (q, r) -> (fromIntegral q, fromIntegral r)

instance
  ( CheckDivisor 8 d
  , KnownNat d
  , KnownNat (Log2 d)
  , KnownNat (If (IsPow2 d) 0 1)
  , KnownNat (Lemire d)
  ) => KnownDivisor d Word8 where
  {-# INLINE divRemK #-}
  divRemK n =
    case smallK (natWord64 @(If (IsPow2 d) 0 1)) (natInt @(Log2 d)) (natWord64 @(Lemire d)) (natWord64 @d) (fromIntegral n) of
      (q, r) -> (fromIntegral q, fromIntegral r)

instance
  ( CheckDivisor 128 d
  , KnownNat (Log2 d)
  , KnownNat (Algo 128 d)
  , KnownNat (Div (Magic 128 d) (2 ^ 64))
  , KnownNat (Mod (Magic 128 d) (2 ^ 64))
  , KnownNat (Div d (2 ^ 64))
  , KnownNat (Mod d (2 ^ 64))
  ) => KnownDivisor d Word128 where
  {-# INLINE divRemK #-}
  divRemK (Word128 n1 n0) = case natWord64 @(Algo 128 d) of
    0 ->
      case shr128 n1 n0 sh of { (# q1, q0 #) ->
      case sub128 d1 d0 0 1 of { (# e1, e0 #) ->
      (Word128 q1 q0, Word128 (n1 .&. e1) (n0 .&. e0)) }}
    1 ->
      case mulHi128 m1 m0 n1 n0 of { (# t1, t0 #) ->
      case shr128 t1 t0 sh of { (# q1, q0 #) ->
      finish q1 q0 }}
    _ ->
      case mulHi128 m1 m0 n1 n0 of { (# t1, t0 #) ->
      case sub128 n1 n0 t1 t0 of { (# x1, x0 #) ->
      case shr128Small x1 x0 1 of { (# y1, y0 #) ->
      case add128 y1 y0 t1 t0 of { (# z1, z0 #) ->
      case shr128 z1 z0 sh of { (# q1, q0 #) ->
      finish q1 q0 }}}}}
    where
    sh = natInt @(Log2 d)
    m1 = natWord64 @(Div (Magic 128 d) (2 ^ 64))
    m0 = natWord64 @(Mod (Magic 128 d) (2 ^ 64))
    d1 = natWord64 @(Div d (2 ^ 64))
    d0 = natWord64 @(Mod d (2 ^ 64))
    finish q1 q0 =
      case mulLo128 q1 q0 d1 d0 of { (# p1, p0 #) ->
      case sub128 n1 n0 p1 p0 of { (# r1, r0 #) ->
      (Word128 q1 q0, Word128 r1 r0) }}

-- | Type-level numerators.
type KnownNumerator :: Nat -> Type -> Constraint
class KnownNumerator n a where
  -- | The numerator as a value.
  numeratorVal :: a

instance (CheckNumerator 128 n, KnownNat (Div n (2 ^ 64)), KnownNat (Mod n (2 ^ 64))) => KnownNumerator n Word128 where
  {-# INLINE numeratorVal #-}
  numeratorVal = Word128 (natWord64 @(Div n (2 ^ 64))) (natWord64 @(Mod n (2 ^ 64)))

instance (CheckNumerator 64 n, KnownNat n) => KnownNumerator n Word64 where
  {-# INLINE numeratorVal #-}
  numeratorVal = natWord64 @n

instance (CheckNumerator 32 n, KnownNat n) => KnownNumerator n Word32 where
  {-# INLINE numeratorVal #-}
  numeratorVal = fromIntegral (natWord64 @n)

instance (CheckNumerator 16 n, KnownNat n) => KnownNumerator n Word16 where
  {-# INLINE numeratorVal #-}
  numeratorVal = fromIntegral (natWord64 @n)

instance (CheckNumerator 8 n, KnownNat n) => KnownNumerator n Word8 where
  {-# INLINE numeratorVal #-}
  numeratorVal = fromIntegral (natWord64 @n)

-- | @divRemN \@n d == divRem n d@ for a type-level numerator. See 'divRemConst'.
{-# INLINE divRemN #-}
divRemN :: forall n a. (KnownNumerator n a, StrengthReduce a) => StrengthReduced a -> (a, a)
divRemN = divRemConst (numeratorVal @n)

-- | Quotient of the type-level numerator @n@.
{-# INLINE divN #-}
divN :: forall n a. (KnownNumerator n a, StrengthReduce a) => StrengthReduced a -> a
divN d = case divRemN @n d of (q, _) -> q

-- | Remainder of the type-level numerator @n@.
{-# INLINE remN #-}
remN :: forall n a. (KnownNumerator n a, StrengthReduce a) => StrengthReduced a -> a
remN d = case divRemN @n d of (_, r) -> r

-- | @divRemNonZeroN \@n d == divRemNonZero n d@ for a type-level numerator.
-- See 'divRemNonZeroConst'.
{-# INLINE divRemNonZeroN #-}
divRemNonZeroN :: forall n a. (KnownNumerator n a, StrengthReduce a) => NonZero a -> (a, a)
divRemNonZeroN = divRemNonZeroConst (numeratorVal @n)

-- | Unchecked hardware quotient of the type-level numerator @n@.
{-# INLINE divNonZeroN #-}
divNonZeroN :: forall n a. (KnownNumerator n a, StrengthReduce a) => NonZero a -> a
divNonZeroN d = case divRemNonZeroN @n d of (q, _) -> q

-- | Unchecked hardware remainder of the type-level numerator @n@.
{-# INLINE remNonZeroN #-}
remNonZeroN :: forall n a. (KnownNumerator n a, StrengthReduce a) => NonZero a -> a
remNonZeroN d = case divRemNonZeroN @n d of (_, r) -> r