qute-0.1.0: src/Language/QBE/Analysis/CFG.hs
-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
-- SPDX-FileCopyrightText: 2026 Reliable System Software, Technische Universität Braunschweig <vss@ibr.cs.tu-bs.de>
--
-- SPDX-License-Identifier: GPL-3.0-only
module Language.QBE.Analysis.CFG
( -- * Control Flow Graph
Label,
CFG (cfgFunction),
build,
identToLabel,
labelToIdent,
labelToBlock,
lookupSuccs,
-- * Graph Representation
asGraph,
nodes,
edges,
bounds,
-- * Dominator Analysis
asDomGraph,
startNode,
)
where
import Data.Graph (Bounds, Graph, buildG)
import Data.IntMap (IntMap)
import Data.IntMap qualified as IntMap
import Data.IntSet qualified as IntSet
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (fromJust)
import Data.Tuple (swap)
import Language.QBE.Analysis.Graph qualified as DG
import Language.QBE.Types qualified as QBE
-- | Representation of a node in the t'CFG'.
type Label = IntMap.Key
-- | A representation of the control-flow within a 'QBE.FuncDef'.
data CFG
= CFG
{ -- | Function for which this CFG was built.
cfgFunction :: QBE.FuncDef,
cfgMaxBound :: Int,
cfgLabelMap :: Map QBE.BlockIdent Label,
cfgBlockMap :: IntMap QBE.BlockIdent,
cfgSuccessors :: IntMap [Label]
}
-- | Returns a list of all graph nodes in an unspecified order.
nodes :: CFG -> [Label]
nodes = IntMap.keys . cfgBlockMap
-- | Returns a list of graph edges in an unspecified order.
edges :: CFG -> [(Label, Label)]
edges cfg = foldl go [] $ IntMap.toList (cfgSuccessors cfg)
where
go acc (p, c) = acc ++ map (p,) c
-- | Returns the bounds of the t'CFG'. This is useful, for example, to
-- build a subgraph using 'Data.Graph.buildG'.
bounds :: CFG -> Bounds
bounds cfg = (0, cfgMaxBound cfg)
-- | Convert a 'QBE.BlockIdent' to a CFG node 'Label'.
--
-- This function is partial, on an invalid 'Label', an error is thrown.
identToLabel :: CFG -> QBE.BlockIdent -> Label
identToLabel CFG {cfgLabelMap = m} blkId =
fromJust $ Map.lookup blkId m
-- | Convert a CFG node 'Label' to a 'QBE.BlockIdent'.
--
-- This function is partial, on an invalid 'Label', an error is thrown.
labelToIdent :: CFG -> Label -> QBE.BlockIdent
labelToIdent CFG {cfgBlockMap = m} label =
fromJust $ IntMap.lookup label m
-- | Utility function to convert a node 'Label' to a 'QBE.Block'.
-- Performs two \(O(\log n)\) lookups internally.
--
-- This function is partial, on an invalid 'Label', an error is thrown.
labelToBlock :: CFG -> Label -> QBE.Block
labelToBlock cfg label =
let blocks = QBE.fBlock $ cfgFunction cfg
in fromJust $ Map.lookup (labelToIdent cfg label) blocks
-- | Mapping of 'Label' to its successors in the CFG, represented as an
-- ordered list of zero, one, or two elements. A list with two elements
-- represents a conditional jump where the left child is the is the true
-- branch and the right child is the false branch. A list wih a single
-- element signifies an unconditional jump. If the given node does not
-- have any successors an empty list is returned.
--
-- This function is partial, on an invalid 'Label', an error is thrown.
lookupSuccs :: CFG -> Label -> [Label]
lookupSuccs CFG {cfgSuccessors = succs} label =
fromJust $ IntMap.lookup label succs
------------------------------------------------------------------------
identStart :: Label
identStart = 0
-- | Construct a t'CFG' for a given function.
build :: QBE.FuncDef -> CFG
build func =
CFG
{ cfgMaxBound = snd $ last blkIdLabels,
cfgFunction = func,
cfgLabelMap = labelMap,
cfgBlockMap = IntMap.fromList $ map swap blkIdLabels,
cfgSuccessors = IntMap.fromList $ build' labelMap blocks
}
where
labelMap :: Map QBE.BlockIdent Label
labelMap = Map.fromList blkIdLabels
blocks :: [QBE.Block]
blocks = Map.elems $ QBE.fBlock func
blkIdLabels :: [(QBE.BlockIdent, Label)]
blkIdLabels = zip (map QBE.label blocks) [identStart ..]
build' :: Map QBE.BlockIdent Label -> [QBE.Block] -> [(IntMap.Key, [Label])]
build' labelMap = foldl go []
where
toLabel :: QBE.BlockIdent -> Label
toLabel ident = fromJust $ Map.lookup ident labelMap
go acc block@(QBE.Block {QBE.label = ident}) =
let succs = case QBE.term block of
QBE.Jump target -> [toLabel target]
QBE.Jnz _ i1 i2 -> [toLabel i1, toLabel i2]
QBE.Return _ -> []
QBE.Halt -> []
in (toLabel ident, succs) : acc
------------------------------------------------------------------------
asGraph :: CFG -> Graph
asGraph cfg = buildG (identStart, cfgMaxBound cfg) $ edges cfg
asDomGraph :: CFG -> DG.Graph
asDomGraph cfg = IntMap.map IntSet.fromList (cfgSuccessors cfg)
-- | Determine the entry node of the t'CFG'. Useful, for example, to
-- generated a 'DG.Rooted' representation for the control-flow graph.
startNode :: CFG -> Label
startNode cfg@(CFG {cfgFunction = func}) =
identToLabel cfg (QBE.fStart func)