qute

A software analysis framework built around the QBE intermediate language

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

  1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
  2--
  3-- SPDX-License-Identifier: GPL-3.0-only
  4
  5-- | This module provides top-level definitions for representing programs
  6-- written in the [QBE](https://c9x.me/compile/) intermediate representation.
  7module Language.QBE
  8  ( Program,
  9    Definition (..),
 10    localDefs,
 11    typeDefs,
 12    globalFuncs,
 13    Language.QBE.parse,
 14    ExecError (..),
 15    parseAndFind,
 16  )
 17where
 18
 19import Control.Monad.Catch (Exception, MonadThrow, throwM)
 20import Data.List (find)
 21import Data.Maybe (mapMaybe)
 22import Language.QBE.Parser (dataDef, fileDef, funcDef, skipInitComments, typeDef)
 23import Language.QBE.Types qualified as QBE
 24import Text.ParserCombinators.Parsec
 25  ( ParseError,
 26    Parser,
 27    SourceName,
 28    choice,
 29    eof,
 30    many,
 31    parse,
 32    try,
 33  )
 34
 35-- | A QBE program consists of a sequence of definitions. Four types of objects
 36-- can be defined: aggregate types, data, functions, and debugging information.
 37--
 38-- See also: The corresponding section of the [QBE specification](https://c9x.me/compile/doc/il-v1.2.html#Definitions).
 39data Definition
 40  = -- | Definition of data (e.g. a string).
 41    DefData QBE.DataDef
 42  | -- | Definition of an aggregate data type.
 43    DefType QBE.TypeDef
 44  | -- | Definition of a function.
 45    DefFunc QBE.FuncDef
 46  | -- | Definition of a debug file.
 47    DefFile String
 48  deriving (Eq, Show)
 49
 50parseDef :: Parser Definition
 51parseDef =
 52  choice
 53    [ DefType <$> typeDef,
 54      -- Need to try funcDef as both funcDef and
 55      -- dataDef start with a linkage definition.
 56      --
 57      -- TODO: Try parsing linkage then funcDef <|> dataDef.
 58      DefData <$> try dataDef,
 59      DefFunc <$> funcDef,
 60      DefFile <$> fileDef
 61    ]
 62
 63-- | A parsed QBE program, represented as a list of 'Definition' values.
 64type Program = [Definition]
 65
 66-- | Wrapper to parse a QBE program using 'Text.ParserCombinators.Parsec.parse'.
 67parse :: SourceName -> String -> Either ParseError Program
 68parse =
 69  Text.ParserCombinators.Parsec.parse
 70    (skipInitComments *> many parseDef <* eof)
 71
 72-- | Utility function to obtain all functions defined in a QBE 'Program'.
 73globalFuncs :: Program -> [QBE.FuncDef]
 74globalFuncs = mapMaybe globalFuncs'
 75  where
 76    globalFuncs' :: Definition -> Maybe QBE.FuncDef
 77    globalFuncs' (DefFunc f) = Just f
 78    globalFuncs' _ = Nothing
 79
 80-- | Returns all non-exported data and function definitions of a 'Program'.
 81localDefs :: Program -> [QBE.GlobalIdent]
 82localDefs = mapMaybe go
 83  where
 84    isGlobal :: [QBE.Linkage] -> Bool
 85    isGlobal = elem QBE.LExport
 86
 87    go :: Definition -> Maybe QBE.GlobalIdent
 88    go (DefData dd)
 89      | isGlobal (QBE.linkage dd) = Nothing
 90      | otherwise = Just (QBE.name dd)
 91    go (DefFunc fd)
 92      | isGlobal (QBE.fLinkage fd) = Nothing
 93      | otherwise = Just (QBE.fName fd)
 94    go _ = Nothing
 95
 96-- | Returns all defined types of a 'Program'.
 97typeDefs :: Program -> [QBE.UserIdent]
 98typeDefs = mapMaybe go
 99  where
100    go :: Definition -> Maybe QBE.UserIdent
101    go (DefType ty) = Just $ QBE.aggName ty
102    go _ = Nothing
103
104------------------------------------------------------------------------
105
106-- | Custom 'Exception' used for error handling in 'parseAndFind'.
107data ExecError
108  = -- | The input is not a valid QBE program.
109    ESyntaxError ParseError
110  | -- | The given entry function is not defined in the QBE program.
111    EUnknownEntry QBE.GlobalIdent
112  deriving (Show)
113
114instance Exception ExecError
115
116-- | Utility function for the common task of parsing an input as a QBE
117-- 'Program' and, within that program, finding the entry function. If the
118-- function doesn't exist or a the input is invalid a 'ExecError' exception
119-- is thrown.
120parseAndFind ::
121  (MonadThrow m) =>
122  QBE.GlobalIdent ->
123  String ->
124  m (Program, QBE.FuncDef)
125parseAndFind entryIdent input = do
126  prog <- case Language.QBE.parse "" input of -- TODO: file name
127    Right rt -> pure rt
128    Left err -> throwM $ ESyntaxError err
129
130  let funcs = globalFuncs prog
131  func <- case find (\f -> QBE.fName f == entryIdent) funcs of
132    Just x -> pure x
133    Nothing -> throwM $ EUnknownEntry entryIdent
134
135  pure (prog, func)