1-- SPDX-FileCopyrightText: 2025-2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only4{-# LANGUAGE FunctionalDependencies #-}56-- | This module defines the abstract 'Simulator' monad and thus provides the primitives7-- used by "Language.QBE.Simulator" to describe the semantics of the QBE intermediate8-- representation.9module Language.QBE.Simulator.State10 ( -- * Abstract Monad11 Simulator (..),1213 -- * Name Resolution14 SomeFunc (..),15 lookupFunc,16 lookupArgs,17 lookupGlobal,18 lookupLocal,19 lookupValue,2021 -- * Helper22 liftMaybe,23 subType,24 runBinary,25 returnFromFunc,2627 -- * Stack28 StackFrame (..),29 newStackFrame,30 storeLocal,31 modifyFrame,32 stackAlign,33 stackAlloc,34 stackSpill,35 )36where3738import Control.Monad.Error.Class (MonadError, throwError)39import Data.Functor ((<&>))40import Data.Map qualified as Map41import Data.Maybe (catMaybes)42import Data.Word (Word64)43import Language.QBE.Simulator.Error44import Language.QBE.Simulator.Expression qualified as E45import Language.QBE.Simulator.Memory qualified as MEM46import Language.QBE.Types qualified as QBE4748-- | Representation of a stack frame on the function call stack.49data StackFrame v50 = StackFrame51 { stkFunc :: QBE.FuncDef,52 stkVars :: Map.Map QBE.LocalIdent v,53 stkVarArgs :: [v],54 stkFp :: v55 }5657-- | Create a new t'StackFrame' and push it onto the call stack.58newStackFrame ::59 (Simulator m v) =>60 -- | Definition of the functions to which this frame belongs.61 QBE.FuncDef ->62 -- | Named arguments passed to this function.63 Map.Map QBE.LocalIdent v ->64 -- | Optional, unnamed variadic arguments.65 [v] ->66 m (StackFrame v)67newStackFrame f args variadicArgs = do68 frame <- getSP <&> StackFrame f args variadicArgs69 pushStackFrame frame >> pure frame70{-# INLINEABLE newStackFrame #-}7172-- | Store a local variable with a given name and value in the given t'StackFrame'.73storeLocal :: QBE.LocalIdent -> v -> StackFrame v -> StackFrame v74storeLocal ident value frame@(StackFrame {stkVars = v}) =75 frame {stkVars = Map.insert ident value v}7677-- | Lookup a local variable in the current t'StackFrame'.78lookupLocal :: StackFrame v -> QBE.LocalIdent -> Maybe v79lookupLocal (StackFrame {stkVars = v}) = flip Map.lookup v80{-# INLINEABLE lookupLocal #-}8182------------------------------------------------------------------------8384-- | Representation of a function.85data SomeFunc m v86 = -- | A simulated function whose execution is intercepted by the Simulator.87 SSimFunc ([v] -> m (Maybe v))88 | -- | A QBE function defined in the input program.89 SFuncDef QBE.FuncDef9091-- | This is an “abstract monad” representing the Simulator and allowing92-- interaction with an encapsulated Simulator state @m@. Conceptually,93-- this monads describes the primitives based on which the semantics of94-- the QBE intermediate representation are abstractly described in95-- 'Language.QBE.Simulator'.96--97-- An instance of this monad then provides concrete semantics for these98-- primitives. For example, the module "Language.QBE.Simulator.Default.State"99-- provides an implementation of a polymorphic Simulator state implement over a100-- "Control.Monad.State" monad.101--102-- The idea is inspired by Bourgeat et al. <https://doi.org/10.1145/3607833>.103class (E.ValueRepr v, MonadError EvalError m) => Simulator m v | m -> v where104 -- | Check if a value of type 'E.ValueRepr' evaluates to true. This is used105 -- within "Language.QBE.Simulator" to implement conditional jumps.106 isTrue :: v -> m Bool107108 -- | Convert a value of type 'E.ValueRepr' to a 'MEM.Address' that can be109 -- used to index a "Language.QBE.Simulator.Memory".110 toAddress :: v -> m MEM.Address111112 -- | Lookup the address of a data symbol.113 lookupSymbol :: QBE.GlobalIdent -> m (Maybe MEM.Address)114115 -- | Find a function by name, required to implement [call instructions](https://c9x.me/compile/doc/il-v1.2.html#Call).116 findFunc :: QBE.GlobalIdent -> m (Maybe (SomeFunc m v))117118 -- | Find a function by "text segment" address, used for the implementation of function pointers.119 findFuncByAddr :: MEM.Address -> m (Maybe (SomeFunc m v))120121 -- | Return the t'StackFrame' of the currently executed function.122 activeFrame :: m (StackFrame v)123124 -- | Push a new t'StackFrame' onto the function call stack.125 pushStackFrame :: StackFrame v -> m ()126127 -- | Pop the current stack frame from the function call stack.128 -- Should throw 'EmptyStack' when invoked on an empty function call stack.129 popStackFrame :: m (StackFrame v)130131 -- | Get the current value of the stack pointer.132 getSP :: m v133134 -- | Set the value of the stack pointer.135 setSP :: v -> m ()136137 -- | Write a value to memory.138 writeMemory :: MEM.Address -> QBE.ExtType -> v -> m () -- TODO: LoadType?139140 -- | Read a value from memory.141 readMemory :: QBE.LoadType -> MEM.Address -> m v142143-- | Extracts the element out of a 'Just' or throw the given 'EvalError' if144-- if its argument is 'Nothing'.145liftMaybe :: (MonadError EvalError m) => EvalError -> Maybe a -> m a146liftMaybe e Nothing = throwError e147liftMaybe _ (Just r) = pure r148{-# INLINE liftMaybe #-}149150-- | Implements the subtyping rules of the QBE intermediate representation.151--152-- See <https://c9x.me/compile/doc/il-v1.2.html#Subtyping>.153subType :: (Simulator m v) => QBE.BaseType -> v -> m v154subType baseTy v = liftMaybe TypingError $ subType' baseTy (E.getType v)155 where156 subType' QBE.Word (QBE.Base QBE.Word) = Just v157 subType' QBE.Word (QBE.Base QBE.Long) =158 E.extract (QBE.Base QBE.Word) v159 subType' QBE.Long (QBE.Base QBE.Long) = Just v160 subType' QBE.Single (QBE.Base QBE.Single) = Just v161 subType' QBE.Double (QBE.Base QBE.Double) = Just v162 subType' _ _ = Nothing163{-# INLINEABLE subType #-}164165-- | Invoke a binary operation and perform subtyping (see 'subType') on its166-- results for the provided 'QBE.BaseType'. If the operation returns a 'Nothing'167-- value a 'TypingError' is raised.168runBinary ::169 (Simulator m v) =>170 QBE.BaseType ->171 (v -> v -> Maybe v) ->172 v ->173 v ->174 m v175runBinary ty op lhs rhs =176 liftMaybe TypingError (op lhs rhs) >>= subType ty177{-# INLINEABLE runBinary #-}178179-- | Modify the current t'StackFrame', e.g. to add a new local variable to it.180-- If the function call stack is currently empty an 'EmptyStack' error is thrown.181modifyFrame :: (Simulator m v) => (StackFrame v -> StackFrame v) -> m ()182modifyFrame func = do183 frame <- popStackFrame184 pushStackFrame (func frame)185{-# INLINEABLE modifyFrame #-}186187-- | Align a stack address. Contrary to 'MEM.alignAddr', this rounds down to188-- the nearest aligned addressed (not up) as the stack grows downward. Further,189-- since the SP representation is presently not fixed, it operates on 'E.ValueRepr'.190stackAlign :: (E.ValueRepr v) => v -> v -> Maybe v191stackAlign addr alignment =192 addr `E.urem` alignment >>= (addr `E.sub`)193{-# INLINEABLE stackAlign #-}194195-- | Allocate a given amount of bytes on the stack with the given alignment.196-- Advances the stack pointer accordingly.197stackAlloc :: (Simulator m v) => v -> Word64 -> m v198stackAlloc size align = do199 stkPtr <- getSP200 let newStkPtr = stkPtr `E.sub` size >>= (`stackAlign` E.fromLit (QBE.Base QBE.Long) align)201 case newStkPtr of202 Just ptr -> setSP ptr >> pure ptr203 Nothing -> throwError InvalidAddressType204{-# INLINEABLE stackAlloc #-}205206-- | Allocate space for the given value on the stack and store it there.207-- Returns a reference (i.e., a memory address) fore the allocated memory.208stackSpill :: (Simulator m v) => v -> m MEM.Address209stackSpill val = do210 let ty = E.getType val211 size = fromIntegral $ QBE.extTypeByteSize ty212 sizeVal = E.fromLit (QBE.Base QBE.Long) size213 ptr <- stackAlloc sizeVal size >>= toAddress214 writeMemory ptr ty val215 pure ptr216{-# INLINEABLE stackSpill #-}217218-- | Trigger a function return, popping its t'StackFrame' from the call stack219-- and updating both the stack and frame pointer.220returnFromFunc :: (Simulator m v) => m ()221returnFromFunc = popStackFrame >>= setSP . stkFp222{-# INLINE returnFromFunc #-}223224maybeLookup :: (Simulator m v) => String -> Maybe a -> m a225maybeLookup name = liftMaybe (UnknownVariable name)226{-# INLINE maybeLookup #-}227228-- | Lookup a global variable, might throw an 'UnknownVariable' error.229lookupGlobal :: (Simulator m v) => QBE.BaseType -> QBE.GlobalIdent -> m v230lookupGlobal ty name = do231 v <- lookupSymbol name >>= maybeLookup (show name)232 subType ty (E.fromLit (QBE.Base QBE.Long) v)233{-# INLINEABLE lookupGlobal #-}234235-- | Lookup a 'QBE.Value', invoking the correct lookup function. For example,236-- 'lookupGlobal' for globals or 'lookupLocal' for local variables.237lookupValue :: (Simulator m v) => QBE.BaseType -> QBE.Value -> m v238lookupValue ty (QBE.VConst (QBE.Const (QBE.Number v))) =239 pure $ E.fromLit (QBE.Base ty) v240lookupValue ty (QBE.VConst (QBE.Const (QBE.SFP v))) =241 subType ty (E.fromFloat v)242lookupValue ty (QBE.VConst (QBE.Const (QBE.DFP v))) =243 subType ty (E.fromDouble v)244lookupValue ty (QBE.VConst (QBE.Const (QBE.Global k))) = lookupGlobal ty k245lookupValue ty (QBE.VConst (QBE.Thread k)) = lookupGlobal ty k246lookupValue ty (QBE.VConst (QBE.Common k)) = lookupGlobal ty k247lookupValue ty (QBE.VConst (QBE.Extern k)) = lookupGlobal ty k248lookupValue ty (QBE.VConst (QBE.ExternThread k)) = lookupGlobal ty k249lookupValue ty (QBE.VLocal k) = do250 v <- activeFrame >>= maybeLookup (show k) . flip lookupLocal k251 subType ty v252{-# INLINEABLE lookupValue #-}253254lookupFuncName :: (Simulator m v) => QBE.GlobalIdent -> m (SomeFunc m v)255lookupFuncName name = do256 maybeFunc <- findFunc name257 case maybeFunc of258 Just def -> pure def259 Nothing -> throwError (UnknownFunction name)260{-# INLINEABLE lookupFuncName #-}261262-- | Interpret the given 'QBE.Value' as a function reference, either263-- looking it up by name or by address. If the function could not be264-- found by address an 'UnknownFunctionAddr' is thrown, otherwise an265-- 'UnknownFunction' error is thrown.266lookupFunc :: (Simulator m v) => QBE.Value -> m (SomeFunc m v)267lookupFunc (QBE.VConst (QBE.Extern n)) = lookupFuncName n268lookupFunc (QBE.VConst (QBE.Const (QBE.Global n))) = lookupFuncName n269lookupFunc value = do270 addr <- lookupValue QBE.Long value >>= toAddress271 maybeFunc <- findFuncByAddr addr272 case maybeFunc of273 Just def -> pure def274 Nothing -> throwError (UnknownFunctionAddr addr)275{-# INLINEABLE lookupFunc #-}276277lookupArg :: (Simulator m v) => QBE.FuncArg -> m (Maybe v)278lookupArg (QBE.ArgReg abity value) =279 Just <$> lookupValue (QBE.abityToBase abity) value280lookupArg (QBE.ArgEnv _) = error "env function parameters not supported"281lookupArg QBE.ArgVar = pure Nothing282{-# INLINEABLE lookupArg #-}283284-- | Lookup the arguments to a function.285lookupArgs :: (Simulator m v) => [QBE.FuncArg] -> m [v]286lookupArgs args = catMaybes <$> mapM lookupArg args287{-# INLINE lookupArgs #-}