qute

A software analysis framework built around the QBE intermediate language

git clone https://git.8pit.net/qute.git

  1-- SPDX-FileCopyrightText: 2023-2024 University of Bremen
  2-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
  3--
  4-- SPDX-License-Identifier: MIT AND GPL-3.0-only
  5
  6-- | This module provides an implementation of a simple byte-addressable memory
  7-- based on "Data.Array".
  8module Language.QBE.Simulator.Memory
  9  ( -- * Type Aliases
 10    Address,
 11    Size,
 12    showAddr,
 13
 14    -- * Value Representation
 15    Storable (toBytes, fromBytes),
 16
 17    -- * Memory Representation
 18    Memory,
 19    mkMemory,
 20    memSize,
 21    loadBytes,
 22    storeBytes,
 23
 24    -- * Memory Address
 25    toMemAddr,
 26    addrOverlap,
 27    alignAddr,
 28  )
 29where
 30
 31import Data.Array.IO
 32  ( MArray,
 33    getBounds,
 34    newArray,
 35    newArray_,
 36    readArray,
 37    writeArray,
 38  )
 39import Data.Bits (complement, (.&.))
 40import Data.Word (Word64)
 41import Language.QBE.Types qualified as QBE
 42import Numeric (showHex)
 43
 44-- | Type used to represent an address in memory.
 45type Address = Word64
 46
 47-- | Type used to represent the memory's size.
 48type Size = Word64
 49
 50-- | Represent an address as a hexadecimal string.
 51showAddr :: Address -> String
 52showAddr addr = "0x" ++ showHex addr ""
 53
 54------------------------------------------------------------------------
 55
 56-- | Type class for types that can be stored in memory. That is, types whose
 57-- values can be converted to the given byte representation and vice versa.
 58class Storable valTy byteTy where
 59  -- | Convert a value type to a list of byte types.
 60  toBytes :: valTy -> [byteTy]
 61
 62  -- | Convert a list of bytes to a value type of 'QBE.LoadType'. Returns
 63  -- 'Nothing' if the length of the list is incompatible with the given
 64  -- 'QBE.LoadType'.
 65  fromBytes :: QBE.LoadType -> [byteTy] -> Maybe valTy
 66
 67-- | Memory parameterized over the Array type (e.g. 'Data.Array.IO.IOUArray')
 68-- and a byte polymorphic representation (e.g. 'Data.Word.Word8').
 69data Memory a v = Memory
 70  { memStart :: Address,
 71    memBytes :: a Address v
 72  }
 73
 74-- | Create a new t'Memory' which starts at the given base address and
 75-- has a maximum capacity (i.e., can store up to the given amount of bytes).
 76-- The memory is not initialized, reading an uninitialized values results
 77-- in an error.
 78mkMemory :: (MArray t a IO) => Address -> Maybe a -> Size -> IO (Memory t a)
 79mkMemory startAddr zeroValueMay size = do
 80  let bounds = (0, size - 1)
 81  ary <-
 82    case zeroValueMay of
 83      Just zeroValue -> newArray bounds zeroValue
 84      _ -> newArray_ bounds
 85
 86  return $ Memory startAddr ary
 87
 88-- | Translate global address to a memory-local address. That is, performs
 89-- address translation relative to the base address of the t'Memory'.
 90toMemAddr :: Memory t a -> Address -> Address
 91toMemAddr mem addr = addr - memStart mem
 92
 93-- | Returns true if the given addresses, passed in the first and second
 94-- argument, overlap in the given range (i.e., the given amount of bytes).
 95addrOverlap :: Address -> Address -> Size -> Bool
 96addrOverlap addr1 addr2 range =
 97  addr1 < endAddr addr2 && endAddr addr1 > addr2
 98  where
 99    endAddr :: Address -> Address
100    endAddr a = a + range
101
102-- | Align an address upwards for the given alignment.
103alignAddr :: Address -> Size -> Address
104alignAddr addr align = (addr + (align - 1)) .&. complement (align - 1)
105
106-- | Returns the size of the memory in bytes.
107memSize :: (MArray t a IO) => Memory t a -> IO Size
108memSize = fmap ((+ 1) . snd) . getBounds . memBytes
109
110-- | Write the list of bytes to memory at the given address.
111storeBytes :: (MArray t a IO) => Memory t a -> Address -> [a] -> IO ()
112storeBytes mem addr bytes =
113  mapM_ (\(off, val) -> storeByte mem (addr + off) val) $
114    zip [0 ..] bytes
115  where
116    storeByte :: (MArray t a IO) => Memory t a -> Address -> a -> IO ()
117    storeByte m a = writeArray (memBytes m) $ toMemAddr mem a
118{-# INLINEABLE storeBytes #-}
119
120-- | Load the given amount of bytes at the given address.
121loadBytes :: (MArray t a IO) => Memory t a -> Address -> Size -> IO [a]
122loadBytes mem addr byteSize =
123  mapM (\off -> loadByte mem (addr + off)) [0 .. byteSize - 1]
124  where
125    loadByte :: (MArray t a IO) => Memory t a -> Address -> IO a
126    loadByte m = readArray (memBytes m) . toMemAddr m
127{-# INLINEABLE loadBytes #-}