what4-1.1: src/What4/InterpretedFloatingPoint.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module What4.InterpretedFloatingPoint
( -- * FloatInfo data kind
type FloatInfo
-- ** Constructors for kind FloatInfo
, HalfFloat
, SingleFloat
, DoubleFloat
, QuadFloat
, X86_80Float
, DoubleDoubleFloat
-- ** Representations of FloatInfo types
, FloatInfoRepr(..)
-- ** extended 80 bit float values ("long double")
, X86_80Val(..)
, fp80ToBits
, fp80ToRational
-- ** FloatInfo to/from FloatPrecision
, FloatInfoToPrecision
, FloatPrecisionToInfo
, floatInfoToPrecisionRepr
, floatPrecisionToInfoRepr
-- ** Bit-width type family
, FloatInfoToBitWidth
, floatInfoToBVTypeRepr
-- * Interface classes
-- ** Interpretation type family
, SymInterpretedFloatType
-- ** Type alias
, SymInterpretedFloat
-- ** IsInterpretedFloatExprBuilder
, IsInterpretedFloatExprBuilder(..)
, IsInterpretedFloatSymExprBuilder(..)
) where
import Data.Bits
import Data.Hashable
import Data.Kind
import Data.Parameterized.Classes
import Data.Parameterized.TH.GADT
import Data.Ratio
import Data.Word ( Word16, Word64 )
import GHC.TypeNats
import Prettyprinter
import What4.BaseTypes
import What4.Interface
-- | This data kind describes the types of floating-point formats.
-- This consist of the standard IEEE 754-2008 binary floating point formats,
-- as well as the X86 extended 80-bit format and the double-double format.
data FloatInfo where
HalfFloat :: FloatInfo -- 16 bit binary IEEE754
SingleFloat :: FloatInfo -- 32 bit binary IEEE754
DoubleFloat :: FloatInfo -- 64 bit binary IEEE754
QuadFloat :: FloatInfo -- 128 bit binary IEEE754
X86_80Float :: FloatInfo -- X86 80-bit extended floats
DoubleDoubleFloat :: FloatInfo -- two 64-bit floats fused in the "double-double" style
type HalfFloat = 'HalfFloat -- ^ 16 bit binary IEEE754.
type SingleFloat = 'SingleFloat -- ^ 32 bit binary IEEE754.
type DoubleFloat = 'DoubleFloat -- ^ 64 bit binary IEEE754.
type QuadFloat = 'QuadFloat -- ^ 128 bit binary IEEE754.
type X86_80Float = 'X86_80Float -- ^ X86 80-bit extended floats.
type DoubleDoubleFloat = 'DoubleDoubleFloat -- ^ Two 64-bit floats fused in the "double-double" style.
-- | A family of value-level representatives for floating-point types.
data FloatInfoRepr (fi :: FloatInfo) where
HalfFloatRepr :: FloatInfoRepr HalfFloat
SingleFloatRepr :: FloatInfoRepr SingleFloat
DoubleFloatRepr :: FloatInfoRepr DoubleFloat
QuadFloatRepr :: FloatInfoRepr QuadFloat
X86_80FloatRepr :: FloatInfoRepr X86_80Float
DoubleDoubleFloatRepr :: FloatInfoRepr DoubleDoubleFloat
instance KnownRepr FloatInfoRepr HalfFloat where knownRepr = HalfFloatRepr
instance KnownRepr FloatInfoRepr SingleFloat where knownRepr = SingleFloatRepr
instance KnownRepr FloatInfoRepr DoubleFloat where knownRepr = DoubleFloatRepr
instance KnownRepr FloatInfoRepr QuadFloat where knownRepr = QuadFloatRepr
instance KnownRepr FloatInfoRepr X86_80Float where knownRepr = X86_80FloatRepr
instance KnownRepr FloatInfoRepr DoubleDoubleFloat where knownRepr = DoubleDoubleFloatRepr
$(return [])
instance HashableF FloatInfoRepr where
hashWithSaltF = hashWithSalt
instance Hashable (FloatInfoRepr fi) where
hashWithSalt = $(structuralHashWithSalt [t|FloatInfoRepr|] [])
instance Pretty (FloatInfoRepr fi) where
pretty = viaShow
instance Show (FloatInfoRepr fi) where
showsPrec = $(structuralShowsPrec [t|FloatInfoRepr|])
instance ShowF FloatInfoRepr
instance TestEquality FloatInfoRepr where
testEquality = $(structuralTypeEquality [t|FloatInfoRepr|] [])
instance OrdF FloatInfoRepr where
compareF = $(structuralTypeOrd [t|FloatInfoRepr|] [])
type family FloatInfoToPrecision (fi :: FloatInfo) :: FloatPrecision where
FloatInfoToPrecision HalfFloat = Prec16
FloatInfoToPrecision SingleFloat = Prec32
FloatInfoToPrecision DoubleFloat = Prec64
FloatInfoToPrecision X86_80Float = Prec80
FloatInfoToPrecision QuadFloat = Prec128
type family FloatPrecisionToInfo (fpp :: FloatPrecision) :: FloatInfo where
FloatPrecisionToInfo Prec16 = HalfFloat
FloatPrecisionToInfo Prec32 = SingleFloat
FloatPrecisionToInfo Prec64 = DoubleFloat
FloatPrecisionToInfo Prec80 = X86_80Float
FloatPrecisionToInfo Prec128 = QuadFloat
type family FloatInfoToBitWidth (fi :: FloatInfo) :: GHC.TypeNats.Nat where
FloatInfoToBitWidth HalfFloat = 16
FloatInfoToBitWidth SingleFloat = 32
FloatInfoToBitWidth DoubleFloat = 64
FloatInfoToBitWidth X86_80Float = 80
FloatInfoToBitWidth QuadFloat = 128
FloatInfoToBitWidth DoubleDoubleFloat = 128
floatInfoToPrecisionRepr
:: FloatInfoRepr fi -> FloatPrecisionRepr (FloatInfoToPrecision fi)
floatInfoToPrecisionRepr = \case
HalfFloatRepr -> knownRepr
SingleFloatRepr -> knownRepr
DoubleFloatRepr -> knownRepr
QuadFloatRepr -> knownRepr
X86_80FloatRepr -> knownRepr -- n.b. semantics TBD, not technically an IEEE-754 format.
DoubleDoubleFloatRepr -> error "double-double is not an IEEE-754 format."
floatPrecisionToInfoRepr
:: FloatPrecisionRepr fpp -> FloatInfoRepr (FloatPrecisionToInfo fpp)
floatPrecisionToInfoRepr fpp
| Just Refl <- testEquality fpp (knownRepr :: FloatPrecisionRepr Prec16)
= knownRepr
| Just Refl <- testEquality fpp (knownRepr :: FloatPrecisionRepr Prec32)
= knownRepr
| Just Refl <- testEquality fpp (knownRepr :: FloatPrecisionRepr Prec64)
= knownRepr
| Just Refl <- testEquality fpp (knownRepr :: FloatPrecisionRepr Prec80)
= knownRepr
| Just Refl <- testEquality fpp (knownRepr :: FloatPrecisionRepr Prec128)
= knownRepr
| otherwise
= error $ "unexpected IEEE-754 precision: " ++ show fpp
floatInfoToBVTypeRepr
:: FloatInfoRepr fi -> BaseTypeRepr (BaseBVType (FloatInfoToBitWidth fi))
floatInfoToBVTypeRepr = \case
HalfFloatRepr -> knownRepr
SingleFloatRepr -> knownRepr
DoubleFloatRepr -> knownRepr
QuadFloatRepr -> knownRepr
X86_80FloatRepr -> knownRepr
DoubleDoubleFloatRepr -> knownRepr
-- | Representation of 80-bit floating values, since there's no native
-- Haskell type for these.
data X86_80Val = X86_80Val
Word16 -- exponent
Word64 -- significand
deriving (Show, Eq, Ord)
fp80ToBits :: X86_80Val -> Integer
fp80ToBits (X86_80Val ex mantissa) =
shiftL (toInteger ex) 64 .|. toInteger mantissa
fp80ToRational :: X86_80Val -> Maybe Rational
fp80ToRational (X86_80Val ex mantissa)
-- infinities/NaN/etc
| ex' == 0x7FFF = Nothing
-- denormal/pseudo-denormal/normal/unnormal numbers
| otherwise = Just $! (if s then negate else id) (m * (1 % 2^e))
where
s = testBit ex 15
ex' = ex .&. 0x7FFF
m = (toInteger mantissa) % ((2::Integer)^(63::Integer))
e = 16382 - toInteger ex'
-- Note that the long-double package also provides a representation
-- for 80-bit floating point values but that package includes
-- significant FFI compatibility elements which may not be necessary
-- here; in the future that could be used by defining 'type X86_80Val
-- = LongDouble'.
-- | Interpretation of the floating point type.
type family SymInterpretedFloatType (sym :: Type) (fi :: FloatInfo) :: BaseType
-- | Symbolic floating point numbers.
type SymInterpretedFloat sym fi = SymExpr sym (SymInterpretedFloatType sym fi)
-- | Abstact floating point operations.
class IsExprBuilder sym => IsInterpretedFloatExprBuilder sym where
-- | Return floating point number @+0@.
iFloatPZero :: sym -> FloatInfoRepr fi -> IO (SymInterpretedFloat sym fi)
-- | Return floating point number @-0@.
iFloatNZero :: sym -> FloatInfoRepr fi -> IO (SymInterpretedFloat sym fi)
-- | Return floating point NaN.
iFloatNaN :: sym -> FloatInfoRepr fi -> IO (SymInterpretedFloat sym fi)
-- | Return floating point @+infinity@.
iFloatPInf :: sym -> FloatInfoRepr fi -> IO (SymInterpretedFloat sym fi)
-- | Return floating point @-infinity@.
iFloatNInf :: sym -> FloatInfoRepr fi -> IO (SymInterpretedFloat sym fi)
-- | Create a floating point literal from a rational literal.
iFloatLitRational
:: sym -> FloatInfoRepr fi -> Rational -> IO (SymInterpretedFloat sym fi)
-- | Create a (single precision) floating point literal.
iFloatLitSingle :: sym -> Float -> IO (SymInterpretedFloat sym SingleFloat)
-- | Create a (double precision) floating point literal.
iFloatLitDouble :: sym -> Double -> IO (SymInterpretedFloat sym DoubleFloat)
-- | Create an (extended double precision) floating point literal.
iFloatLitLongDouble :: sym -> X86_80Val -> IO (SymInterpretedFloat sym X86_80Float)
-- | Negate a floating point number.
iFloatNeg
:: sym
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Return the absolute value of a floating point number.
iFloatAbs
:: sym
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Compute the square root of a floating point number.
iFloatSqrt
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Add two floating point numbers.
iFloatAdd
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Subtract two floating point numbers.
iFloatSub
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Multiply two floating point numbers.
iFloatMul
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Divide two floating point numbers.
iFloatDiv
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Compute the reminder: @x - y * n@, where @n@ in Z is nearest to @x / y@.
iFloatRem
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Return the min of two floating point numbers.
iFloatMin
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Return the max of two floating point numbers.
iFloatMax
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Compute the fused multiplication and addition: @(x * y) + z@.
iFloatFMA
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Check logical equality of two floating point numbers.
iFloatEq
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check logical non-equality of two floating point numbers.
iFloatNe
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check IEEE equality of two floating point numbers.
iFloatFpEq
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check IEEE apartness of two floating point numbers.
iFloatFpApart
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check @<=@ on two floating point numbers.
iFloatLe
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check @<@ on two floating point numbers.
iFloatLt
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check @>=@ on two floating point numbers.
iFloatGe
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
-- | Check @>@ on two floating point numbers.
iFloatGt
:: sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (Pred sym)
iFloatIsNaN :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsInf :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsZero :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsPos :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsNeg :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsSubnorm :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
iFloatIsNorm :: sym -> SymInterpretedFloat sym fi -> IO (Pred sym)
-- | If-then-else on floating point numbers.
iFloatIte
:: sym
-> Pred sym
-> SymInterpretedFloat sym fi
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Change the precision of a floating point number.
iFloatCast
:: sym
-> FloatInfoRepr fi
-> RoundingMode
-> SymInterpretedFloat sym fi'
-> IO (SymInterpretedFloat sym fi)
-- | Round a floating point number to an integral value.
iFloatRound
:: sym
-> RoundingMode
-> SymInterpretedFloat sym fi
-> IO (SymInterpretedFloat sym fi)
-- | Convert from binary representation in IEEE 754-2008 format to
-- floating point.
iFloatFromBinary
:: sym
-> FloatInfoRepr fi
-> SymBV sym (FloatInfoToBitWidth fi)
-> IO (SymInterpretedFloat sym fi)
-- | Convert from floating point from to the binary representation in
-- IEEE 754-2008 format.
iFloatToBinary
:: sym
-> FloatInfoRepr fi
-> SymInterpretedFloat sym fi
-> IO (SymBV sym (FloatInfoToBitWidth fi))
-- | Convert a unsigned bitvector to a floating point number.
iBVToFloat
:: (1 <= w)
=> sym
-> FloatInfoRepr fi
-> RoundingMode
-> SymBV sym w
-> IO (SymInterpretedFloat sym fi)
-- | Convert a signed bitvector to a floating point number.
iSBVToFloat
:: (1 <= w) => sym
-> FloatInfoRepr fi
-> RoundingMode
-> SymBV sym w
-> IO (SymInterpretedFloat sym fi)
-- | Convert a real number to a floating point number.
iRealToFloat
:: sym
-> FloatInfoRepr fi
-> RoundingMode
-> SymReal sym
-> IO (SymInterpretedFloat sym fi)
-- | Convert a floating point number to a unsigned bitvector.
iFloatToBV
:: (1 <= w)
=> sym
-> NatRepr w
-> RoundingMode
-> SymInterpretedFloat sym fi
-> IO (SymBV sym w)
-- | Convert a floating point number to a signed bitvector.
iFloatToSBV
:: (1 <= w)
=> sym
-> NatRepr w
-> RoundingMode
-> SymInterpretedFloat sym fi
-> IO (SymBV sym w)
-- | Convert a floating point number to a real number.
iFloatToReal :: sym -> SymInterpretedFloat sym fi -> IO (SymReal sym)
-- | The associated BaseType representative of the floating point
-- interpretation for each format.
iFloatBaseTypeRepr
:: sym
-> FloatInfoRepr fi
-> BaseTypeRepr (SymInterpretedFloatType sym fi)
-- | Helper interface for creating new symbolic floating-point constants and
-- variables.
class (IsSymExprBuilder sym, IsInterpretedFloatExprBuilder sym) => IsInterpretedFloatSymExprBuilder sym where
-- | Create a fresh top-level floating-point uninterpreted constant.
freshFloatConstant
:: sym
-> SolverSymbol
-> FloatInfoRepr fi
-> IO (SymExpr sym (SymInterpretedFloatType sym fi))
freshFloatConstant sym nm fi = freshConstant sym nm $ iFloatBaseTypeRepr sym fi
-- | Create a fresh floating-point latch variable.
freshFloatLatch
:: sym
-> SolverSymbol
-> FloatInfoRepr fi
-> IO (SymExpr sym (SymInterpretedFloatType sym fi))
freshFloatLatch sym nm fi = freshLatch sym nm $ iFloatBaseTypeRepr sym fi
-- | Creates a floating-point bound variable.
freshFloatBoundVar
:: sym
-> SolverSymbol
-> FloatInfoRepr fi
-> IO (BoundVar sym (SymInterpretedFloatType sym fi))
freshFloatBoundVar sym nm fi = freshBoundVar sym nm $ iFloatBaseTypeRepr sym fi