language-bluespec-0.1: src/Language/Bluespec/Log2.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
-- This corresponds to src/comp/Log2.hs in bsc.
module Language.Bluespec.Log2
( log2
) where
import Data.Bits
import GHC.Exts
-- GHC 9.0 has an entirely new ghc-bignum package.
-- See https://iohk.io/en/blog/posts/2020/07/28/improving-haskells-big-numbers-support/
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 900)
import GHC.Num.Integer (Integer(IS, IP, IN))
import GHC.Num.BigNat
#else
import GHC.Integer.GMP.Internals
#endif
import Language.Bluespec.Prelude
-- In GHC 7.8, internal primops return Int# instead of Bool
eqInt, neqInt :: Int# -> Int# -> Bool
{-# INLINE eqInt #-}
{-# INLINE neqInt #-}
eqInt a b = isTrue# (a ==# b)
neqInt a b = isTrue# (a /=# b)
-- |Number of bits in an Int (or Int#) on this machine
wordSize :: Int
wordSize = finiteBitSize (0 :: Int)
-- |Compute the logarithm base 2, rounded up, for Integral types.
-- One interpretation of this log2 function is that it tells you
-- the minimum number of bits required to represent a given number
-- of distinct values.
log2 :: (Integral a, Integral b) => a -> b
log2 0 = 0
log2 x = case (toInteger x) of
-- for values which fit in a single word, we
-- find the index of the most significant non-zero
-- bit. if all other bits are zero, then this is
-- the value of the base 2 log, otherwise we must
-- add one to the value to get the base 2 log.
-- 'small' integer that fits in a single word
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 900)
IS n -> let !(top_one,any_other_ones) = analyze (I# n)
in toEnum (top_one + (if any_other_ones then 1 else 0))
#else
S# n -> let !(top_one,any_other_ones) = analyze (I# n)
in toEnum (top_one + (if any_other_ones then 1 else 0))
#endif
-- for values which exceed the word size, we use the
-- log2large function to examine the values in the array.
-- positive and negative multiword integers respectively
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 900)
IP bn -> let sz = bigNatSize# bn
in log2large (sz -# 1#) bn
IN bn -> let sz = bigNatSize# bn
in log2large (sz -# 1#) bn
#else
Jp# bn@(BN# arr) -> let sz = sizeofBigNat# bn
in log2large (sz -# 1#) arr
Jn# bn@(BN# arr) -> let sz = sizeofBigNat# bn
in log2large (sz -# 1#) arr
#endif
-- |Utility function to find the index of the most significant
-- non-zero bit in a single-word Int and also report if any
-- other bits are non-zero. Note: this assumes n /= 0.
analyze :: Int -> (Int,Bool)
analyze n = helper n (wordSize - 1)
where helper v idx = if (testBit v idx)
then (idx, (clearBit v idx) /= 0)
else helper n (idx - 1)
-- |Utility function to determine if any words in a ByteArray#
-- up to a given index are non-zero.
anyNonZero :: ByteArray# -> Int# -> Bool
anyNonZero arr idx = if (indexIntArray# arr idx) `neqInt` 0#
then True
else (idx `neqInt` 0#) && (anyNonZero arr (idx -# 1#))
-- |Compute the log2 for Integers which span multiple words. This
-- first scans the memory for the most significant word that is not 0.
-- Then it analyzes that word to determine, along with the word's
-- index, the provisional logarithm value, If that word had any
-- additional non-zero bits, or if any other words in the ByteArray#
-- are non-zero, then the actual logarithm will be one more than the
-- provisional value.
log2large :: (Integral b) => Int# -> ByteArray# -> b
log2large i arr =
let !w = indexIntArray# arr i
in if w `eqInt` 0#
then if i `eqInt` 0# then 0 else log2large (i -# 1#) arr
else let !(top_one,any_other_ones) = analyze (I# w)
base = (wordSize * (I# i) + top_one)
in if any_other_ones || ((i `neqInt` 0#) && (anyNonZero arr (i -# 1#)))
then toEnum (base + 1)
else toEnum base