1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45module Language.QBE.Simulator.Default.Funcs (lookupSimFunc) where67import Control.Monad.Error.Class (throwError)8import Control.Monad.IO.Class (MonadIO, liftIO)9import Data.Array (Array, bounds, listArray, (!))10import Data.Bits ((.&.))11import Data.ByteString.Lazy qualified as B12import Data.Word (Word32, Word64)13import Language.QBE.Simulator.ABI.C (charRepr, readArray, writeBuffer)14import Language.QBE.Simulator.ABI.Errno qualified as ERR15import Language.QBE.Simulator.ABI.POSIX qualified as POSIX16import Language.QBE.Simulator.Error (EvalError (FuncArgsMismatch))17import Language.QBE.Simulator.Expression qualified as E18import Language.QBE.Simulator.State (Simulator, toAddress)19import Language.QBE.Types qualified as QBE20import System.Exit (ExitCode (ExitFailure), exitWith)21import System.IO (Handle, stderr, stdin, stdout)2223mkErrno :: (E.ValueRepr v) => ERR.Errno -> v24mkErrno err =25 E.fromLit (QBE.Base QBE.Long) $26 -(fromIntegral $ ERR.errno err)2728fromByteString :: (E.ValueRepr v) => B.ByteString -> [v]29fromByteString = map (E.fromLit QBE.Byte . fromIntegral) . B.unpack3031toByteString :: (E.ValueRepr v) => [v] -> B.ByteString32toByteString = B.pack . map (fromIntegral . E.toWord64)3334readArray' :: (Simulator m v, E.ValueRepr v) => v -> QBE.LoadType -> v -> m [v]35readArray' startPtr elemTy size = do36 startAddr <- toAddress startPtr37 readArray startAddr elemTy $ E.toWord64 size3839writeBuffer' :: (Simulator m v, E.ValueRepr v) => v -> [v] -> m ()40writeBuffer' startPtr elems = do41 startAddr <- toAddress startPtr42 writeBuffer startAddr elems4344------------------------------------------------------------------------4546data FileEntry47 = FileEntry48 { fdFlags :: Word32,49 -- TODO: Use a polymoprh representation for `fdHandle` which is50 -- parameterized over a `ValueRepr` to allow an implementation51 -- of custom file-like objects.52 fdHandle :: Handle53 }5455isWritable :: FileEntry -> Bool56isWritable FileEntry {fdFlags = flags} =57 flags .&. POSIX.oWRONLY /= 05859isReadable :: FileEntry -> Bool60isReadable FileEntry {fdFlags = flags} =61 flags == POSIX.oRDONLY || flags == POSIX.oRDWR6263writeEntry :: (MonadIO m, E.ValueRepr v) => FileEntry -> [v] -> m (Maybe Word64)64writeEntry entry bytes65 | isWritable entry = do66 let bs = toByteString bytes67 liftIO $ B.hPut (fdHandle entry) bs68 pure $ Just (fromIntegral $ B.length bs)69 | otherwise = pure Nothing7071readEntry :: (MonadIO m, E.ValueRepr v) => FileEntry -> Int -> m (Maybe [v])72readEntry entry num73 | isReadable entry = do74 bs <- liftIO $ B.hGet (fdHandle entry) num75 pure $ Just (fromByteString bs)76 | otherwise = pure Nothing7778------------------------------------------------------------------------7980-- TODO: Track open files in the Simulator Env to allow opening files.81openFiles :: Array Int FileEntry82openFiles =83 listArray84 (0, 2)85 [ FileEntry POSIX.oRDONLY stdin,86 FileEntry POSIX.oWRONLY stdout,87 FileEntry POSIX.oWRONLY stderr88 ]8990getFile :: Int -> Maybe FileEntry91getFile fd =92 let (_, fdMax) = bounds openFiles93 in if fd > fdMax94 then Nothing95 else Just $ openFiles ! fd9697------------------------------------------------------------------------9899sysWrite :: (MonadIO m, E.ValueRepr v, Simulator m v) => QBE.GlobalIdent -> [v] -> m (Maybe v)100sysWrite _ [fd, arrayPtr, size] = do101 case getFile (fromIntegral $ E.toWord64 fd) of102 Nothing -> pure $ Just (mkErrno ERR.EBADF)103 Just fe -> do104 bArray <- readArray' arrayPtr charRepr size105 retVal <- writeEntry fe bArray106 pure $ case retVal of107 Nothing -> Just $ mkErrno ERR.EIO108 Just nb -> Just $ E.fromLit (QBE.Base QBE.Word) nb109sysWrite ident _ = throwError $ FuncArgsMismatch ident110111sysRead :: (MonadIO m, E.ValueRepr v, Simulator m v) => QBE.GlobalIdent -> [v] -> m (Maybe v)112sysRead _ [fd, bufPtr, bufSize] = do113 case getFile (fromIntegral $ E.toWord64 fd) of114 Nothing -> pure $ Just (mkErrno ERR.EBADF)115 Just fe -> do116 retVal <- readEntry fe (fromIntegral $ E.toWord64 bufSize)117 case retVal of118 Nothing -> pure $ Just (mkErrno ERR.EIO)119 Just nb -> do120 writeBuffer' bufPtr nb121 let nread = fromIntegral (length nb)122 in pure $ Just (E.fromLit (QBE.Base QBE.Word) nread)123sysRead ident _ = throwError $ FuncArgsMismatch ident124125sysExit :: (MonadIO m, E.ValueRepr v, Simulator m v) => QBE.GlobalIdent -> [v] -> m (Maybe v)126sysExit _ [status] =127 let code = fromIntegral $ E.toWord64 status128 in liftIO $ exitWith (ExitFailure code)129sysExit ident _ = throwError $ FuncArgsMismatch ident130131------------------------------------------------------------------------132133-- TODO: Register functions dynamically.134lookupSimFunc ::135 (MonadIO m, E.ValueRepr v, Simulator m v) =>136 QBE.GlobalIdent ->137 Maybe ([v] -> m (Maybe v))138lookupSimFunc i@(QBE.GlobalIdent "qute_exit") = Just (sysExit i)139lookupSimFunc i@(QBE.GlobalIdent "qute_read") = Just (sysRead i)140lookupSimFunc i@(QBE.GlobalIdent "qute_write") = Just (sysWrite i)141lookupSimFunc _ = Nothing