qute-0.1.0: src/Language/QBE/Simulator/Default/Expression.hs
-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
--
-- SPDX-License-Identifier: GPL-3.0-only
{-# LANGUAGE TemplateHaskell #-}
-- The code generated by template-haskell does not have type signatures.
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
-- | This module provides an implementation of the expression abstract from
-- 'Language.QBE.Simulator.Expression' which uses concrete fixed-width integer
-- values from "Data.Word" internally.
module Language.QBE.Simulator.Default.Expression
( RegVal (..),
bitSize,
fromBits,
)
where
import Control.Exception (assert)
import Data.Bits
( FiniteBits,
finiteBitSize,
shift,
shiftR,
unsafeShiftL,
unsafeShiftR,
xor,
(.&.),
(.|.),
)
import Data.Int (Int16, Int32, Int64, Int8)
import Data.Word (Word16, Word32, Word64, Word8)
import GHC.Float
( castDoubleToWord64,
castFloatToWord32,
castWord32ToFloat,
castWord64ToDouble,
double2Float,
float2Double,
)
import Language.QBE.Simulator.Default.Generator (generateOperators)
import Language.QBE.Simulator.Expression qualified as E
import Language.QBE.Simulator.Memory qualified as MEM
import Language.QBE.Types qualified as QBE
-- TODO: Can we just wrap base type here?
-- TODO: Do not export the constructors
data RegVal
= VByte Word8
| VHalf Word16
| VWord Word32
| VLong Word64
| VSingle Float
| VDouble Double
deriving (Show, Eq)
-- | Size of the value in bits.
bitSize :: RegVal -> Int
bitSize (VByte _) = 8
bitSize (VHalf _) = 16
bitSize (VWord _) = 32
bitSize (VLong _) = 64
bitSize (VSingle _) = 32
bitSize (VDouble _) = 64
-- | Create a a 'RegVal' from an 'Integer' inferring the type from a given
-- amount of bits instead of requiring the user to provide a 'QBE.ExtType',
-- as required by 'Language.QBE.Simulator.Expression.fromLit'.
fromBits :: Int -> Integer -> Maybe RegVal
fromBits 08 = Just . VHalf . fromIntegral
fromBits 16 = Just . VHalf . fromIntegral
fromBits 32 = Just . VWord . fromIntegral
fromBits 64 = Just . VLong . fromIntegral
fromBits _ = const Nothing
fromBool :: Bool -> RegVal
fromBool True = VLong 1
fromBool False = VLong 0
------------------------------------------------------------------------
shiftInstr ::
(RegVal -> Word32 -> Maybe RegVal) ->
RegVal ->
RegVal ->
Maybe RegVal
shiftInstr shiftOp val (VWord amount) = val `shiftOp` amount
shiftInstr _ _ _ = Nothing
toShiftAmount :: Word32 -> Word32 -> Int
toShiftAmount valBitSize amount =
-- From the QBE specification: "The shifting amount
-- is taken modulo the size of the result type."
let s = fromIntegral $ amount `mod` valBitSize
in assert (s > 0) s
shiftSar :: RegVal -> Word32 -> Maybe RegVal
shiftSar (VWord val) amount =
(Just . VWord . fromIntegral) $
(fromIntegral val :: Int32) `unsafeShiftR` toShiftAmount 32 amount
shiftSar (VLong val) amount =
(Just . VLong . fromIntegral) $
(fromIntegral val :: Int64) `unsafeShiftR` toShiftAmount 64 amount
shiftSar _ _ = Nothing
shiftShr :: RegVal -> Word32 -> Maybe RegVal
shiftShr (VWord val) amount =
(Just . VWord) $ val `unsafeShiftR` toShiftAmount 32 amount
shiftShr (VLong val) amount =
(Just . VLong) $ val `unsafeShiftR` toShiftAmount 64 amount
shiftShr _ _ = Nothing
shiftShl :: RegVal -> Word32 -> Maybe RegVal
shiftShl (VWord val) amount =
(Just . VWord) $ val `unsafeShiftL` toShiftAmount 32 amount
shiftShl (VLong val) amount =
(Just . VLong) $ val `unsafeShiftL` toShiftAmount 64 amount
shiftShl _ _ = Nothing
------------------------------------------------------------------------
regToBytes :: RegVal -> [Word8]
regToBytes val =
let f w =
map
(\off -> fromIntegral $ shiftR w off .&. 0xff)
(take (bytesize w) $ iterate (+ 8) 0)
in case val of
(VByte v) -> [v]
(VWord v) -> f v
(VHalf v) -> f v
(VLong v) -> f v
(VSingle v) -> MEM.toBytes (VWord $ castFloatToWord32 v)
(VDouble v) -> MEM.toBytes (VLong $ castDoubleToWord64 v)
where
bytesize :: (FiniteBits a) => a -> Int
bytesize v = finiteBitSize v `div` 8
regFromBytes :: QBE.LoadType -> [Word8] -> Maybe RegVal
regFromBytes ty lst =
let f a =
foldl
(\acc (byte, idx) -> (fromIntegral byte `shift` (idx * 8)) .|. acc)
0
$ zip a [0 ..]
in case (ty, lst) of
(QBE.LSubWord QBE.UnsignedByte, [byte]) -> Just (VWord (fromIntegral byte))
(QBE.LSubWord QBE.SignedByte, [byte]) -> Just (VWord $ fromIntegral (fromIntegral byte :: Int8))
(QBE.LSubWord QBE.SignedHalf, bytes@[_, _]) -> Just (VWord $ fromIntegral (f bytes :: Int16))
(QBE.LSubWord QBE.UnsignedHalf, bytes@[_, _]) -> Just (VWord $ fromIntegral (f bytes :: Word16))
(QBE.LBase QBE.Word, bytes@[_, _, _, _]) -> Just (VWord $ f bytes)
(QBE.LBase QBE.Long, bytes@[_, _, _, _, _, _, _, _]) -> Just (VLong $ f bytes)
(QBE.LBase QBE.Single, bytes@[_, _, _, _]) ->
Just (VSingle $ castWord32ToFloat (f bytes))
(QBE.LBase QBE.Double, bytes@[_, _, _, _, _, _, _, _]) ->
Just (VDouble $ castWord64ToDouble (f bytes))
_ -> Nothing
instance MEM.Storable RegVal Word8 where
toBytes = regToBytes
fromBytes = regFromBytes
------------------------------------------------------------------------
-- TODO: Insert the generated code directly into the instance declaration.
generateOperators
maxValue :: RegVal -> Maybe RegVal
maxValue val =
let bitSiz = bitSize val
maxVal = (2 ^ bitSiz) - 1
in fromBits bitSiz maxVal
withZeroDiv ::
Maybe RegVal ->
(RegVal -> RegVal -> Maybe RegVal) ->
RegVal ->
RegVal ->
Maybe RegVal
withZeroDiv defVal op lhs rhs
| E.toWord64 rhs == 0 = defVal
| otherwise = op lhs rhs
-- Signed division overflow occurs when the most-negative integer is divided by -1.
withSDivOverflow ::
Maybe RegVal ->
(RegVal -> RegVal -> Maybe RegVal) ->
RegVal ->
RegVal ->
Maybe RegVal
withSDivOverflow defVal op lhs rhs
| E.toWord64 lhs == mostNeg && E.toWord64 rhs == minusOne = defVal
| otherwise = op lhs rhs
where
numBits :: Int
numBits =
assert (bitSize lhs == bitSize rhs) $
bitSize lhs
minusOne :: Word64
minusOne = (2 ^ numBits) - 1
mostNeg :: Word64
mostNeg = 2 ^ (numBits - 1)
-- We could also add support for unary operators to the generator. However,
-- presently there is only one unary operator so it isn't worth it.
neg' :: RegVal -> Maybe RegVal
neg' (VWord v) = Just . VWord $ negate v
neg' (VLong v) = Just . VLong $ negate v
neg' (VSingle v) = Just . VSingle $ negate v
neg' (VDouble v) = Just . VDouble $ negate v
neg' _ = Nothing
-- This can't be easily auto generated because the operation differs
-- based on the type.
div' :: RegVal -> RegVal -> Maybe RegVal
div' (VWord lhs) (VWord rhs) =
(Just . VWord . fromIntegral) $
(fromIntegral lhs :: Int32) `quot` (fromIntegral rhs :: Int32)
div' (VLong lhs) (VLong rhs) =
(Just . VLong . fromIntegral) $
(fromIntegral lhs :: Int64) `quot` (fromIntegral rhs :: Int64)
div' (VSingle lhs) (VSingle rhs) = (Just . VSingle) $ lhs / rhs
div' (VDouble lhs) (VDouble rhs) = (Just . VDouble) $ lhs / rhs
div' _ _ = Nothing
instance E.ValueRepr RegVal where
fromLit QBE.Byte n = VByte $ fromIntegral n
fromLit QBE.HalfWord n = VHalf $ fromIntegral n
fromLit (QBE.Base QBE.Long) n = VLong n
fromLit (QBE.Base QBE.Word) n = VWord $ fromIntegral n
fromLit (QBE.Base QBE.Single) n = VSingle $ castWord32ToFloat (fromIntegral n)
fromLit (QBE.Base QBE.Double) n = VDouble $ castWord64ToDouble n
toWord64 (VByte v) = fromIntegral v
toWord64 (VHalf v) = fromIntegral v
toWord64 (VWord v) = fromIntegral v
toWord64 (VLong v) = v
toWord64 (VSingle v) = fromIntegral $ castFloatToWord32 v
toWord64 (VDouble v) = castDoubleToWord64 v
fromFloat = VSingle
fromDouble = VDouble
-- stosi
floatToInt ty@(QBE.Base QBE.Word) True (VSingle v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Int32))
floatToInt ty@(QBE.Base QBE.Long) True (VSingle v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Int64))
-- stoui
floatToInt ty@(QBE.Base QBE.Word) False (VSingle v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Word32))
floatToInt ty@(QBE.Base QBE.Long) False (VSingle v) =
Just $ E.fromLit ty (truncate v :: Word64)
-- dtosi
floatToInt ty@(QBE.Base QBE.Word) True (VDouble v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Int32))
floatToInt ty@(QBE.Base QBE.Long) True (VDouble v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Int64))
-- dtoui
floatToInt ty@(QBE.Base QBE.Word) False (VDouble v) =
Just $ E.fromLit ty (fromIntegral (truncate v :: Word32))
floatToInt ty@(QBE.Base QBE.Long) False (VDouble v) =
Just $ E.fromLit ty (truncate v :: Word64)
-- rest
floatToInt _ _ _ = Nothing
-- swtof
intToFloat ty@(QBE.Base QBE.Single) True (VWord v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Int32))
intToFloat ty@(QBE.Base QBE.Double) True (VWord v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Int32))
-- uwtof
intToFloat ty@(QBE.Base QBE.Single) False (VWord v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Word32))
intToFloat ty@(QBE.Base QBE.Double) False (VWord v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Word32))
-- sltof
intToFloat ty@(QBE.Base QBE.Single) True (VLong v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Int64))
intToFloat ty@(QBE.Base QBE.Double) True (VLong v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Int64))
-- ultof
intToFloat ty@(QBE.Base QBE.Single) False (VLong v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Word64))
intToFloat ty@(QBE.Base QBE.Double) False (VLong v) =
Just $ E.fromLit ty (fromIntegral (fromIntegral v :: Word64))
-- rest
intToFloat _ _ _ = Nothing
extendFloat (VSingle v) = Just $ VDouble (float2Double v)
extendFloat _ = Nothing
truncFloat (VDouble v) = Just $ VSingle (double2Float v)
truncFloat _ = Nothing
getType (VByte _) = QBE.Byte
getType (VHalf _) = QBE.HalfWord
getType (VWord _) = QBE.Base QBE.Word
getType (VLong _) = QBE.Base QBE.Long
getType (VSingle _) = QBE.Base QBE.Single
getType (VDouble _) = QBE.Base QBE.Double
-- TODO: Consider replacing Nothing cases with assert as this on the hot path.
extend extTy isSigned val
| QBE.extTypeBitSize extTy <= bitSize val = Nothing
| otherwise =
E.fromLit extTy
<$> case (isSigned, val) of
(True, VByte v) -> Just $ fromIntegral (fromIntegral v :: Int8)
(True, VHalf v) -> Just $ fromIntegral (fromIntegral v :: Int16)
(True, VWord v) -> Just $ fromIntegral (fromIntegral v :: Int32)
(True, VLong v) -> Just $ fromIntegral (fromIntegral v :: Int64)
(False, VByte v) -> Just $ fromIntegral (fromIntegral v :: Word8)
(False, VHalf v) -> Just $ fromIntegral (fromIntegral v :: Word16)
(False, VWord v) -> Just $ fromIntegral (fromIntegral v :: Word32)
(False, VLong v) -> Just $ fromIntegral (fromIntegral v :: Word64)
_ -> Nothing
-- TODO: Consider replacing Nothing cases with assert as this on the hot path.
extract (QBE.Base QBE.Single) _ = Nothing
extract (QBE.Base QBE.Double) _ = Nothing
extract _ (VSingle _) = Nothing
extract _ (VDouble _) = Nothing
extract extTy v
| QBE.extTypeBitSize extTy > bitSize v = Nothing
| otherwise =
let word = E.toWord64 v
mask = (2 `unsafeShiftL` (QBE.extTypeBitSize extTy - 1)) - 1
in Just $ E.fromLit extTy (word .&. mask)
-- This is needed to align the behavior of qute/ and qute-symex/ on
-- division-by-zero. QBE does not explicitly mandate a specific behavior
-- for this edge case. Therefore, in order to avoid extra branches in the
-- symbolic executor, we use the behavior mandated by SMT-LIB here.
--
-- TODO: Move this into the Expression abstraction (just like overshift handling).
div lhs = withZeroDiv (maxValue lhs) (withSDivOverflow (Just lhs) div') lhs
udiv lhs = withZeroDiv (maxValue lhs) udiv' lhs
urem lhs = withZeroDiv (Just lhs) urem' lhs
srem lhs = withZeroDiv (Just lhs) (withSDivOverflow (fromBits (bitSize lhs) 0) srem') lhs
add = add'
sub = sub'
mul = mul'
or = or'
xor = xor'
and = and'
neg = neg'
sar = shiftInstr shiftSar
shr = shiftInstr shiftShr
shl = shiftInstr shiftShl
-- TODO: Provide default implementations
eq = eq'
ne = ne'
sle = sle'
slt = slt'
sge = sge'
sgt = sgt'
ule = ule'
ult = ult'
uge = uge'
ugt = ugt'
ord (VSingle lhs) (VSingle rhs) =
Just . fromBool $ not (isNaN lhs || isNaN rhs)
ord (VDouble lhs) (VDouble rhs) =
Just . fromBool $ not (isNaN lhs || isNaN rhs)
ord _ _ = Nothing