1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>2-- SPDX-FileCopyrightText: 2026 Reliable System Software, Technische Universität Braunschweig <vss@ibr.cs.tu-bs.de>3--4-- SPDX-License-Identifier: GPL-3.0-only56module Language.QBE.Analysis.CFG7 ( -- * Control Flow Graph8 Label,9 CFG (cfgFunction),10 build,11 identToLabel,12 labelToIdent,13 labelToBlock,14 lookupSuccs,1516 -- * Graph Representation17 asGraph,18 nodes,19 edges,20 bounds,2122 -- * Dominator Analysis23 asDomGraph,24 startNode,25 )26where2728import Data.Graph (Bounds, Graph, buildG)29import Data.IntMap (IntMap)30import Data.IntMap qualified as IntMap31import Data.IntSet qualified as IntSet32import Data.Map (Map)33import Data.Map qualified as Map34import Data.Maybe (fromJust)35import Data.Tuple (swap)36import Language.QBE.Analysis.Graph qualified as DG37import Language.QBE.Types qualified as QBE3839-- | Representation of a node in the t'CFG'.40type Label = IntMap.Key4142-- | A representation of the control-flow within a 'QBE.FuncDef'.43data CFG44 = CFG45 { -- | Function for which this CFG was built.46 cfgFunction :: QBE.FuncDef,47 cfgMaxBound :: Int,48 cfgLabelMap :: Map QBE.BlockIdent Label,49 cfgBlockMap :: IntMap QBE.BlockIdent,50 cfgSuccessors :: IntMap [Label]51 }5253-- | Returns a list of all graph nodes in an unspecified order.54nodes :: CFG -> [Label]55nodes = IntMap.keys . cfgBlockMap5657-- | Returns a list of graph edges in an unspecified order.58edges :: CFG -> [(Label, Label)]59edges cfg = foldl go [] $ IntMap.toList (cfgSuccessors cfg)60 where61 go acc (p, c) = acc ++ map (p,) c6263-- | Returns the bounds of the t'CFG'. This is useful, for example, to64-- build a subgraph using 'Data.Graph.buildG'.65bounds :: CFG -> Bounds66bounds cfg = (0, cfgMaxBound cfg)6768-- | Convert a 'QBE.BlockIdent' to a CFG node 'Label'.69--70-- This function is partial, on an invalid 'Label', an error is thrown.71identToLabel :: CFG -> QBE.BlockIdent -> Label72identToLabel CFG {cfgLabelMap = m} blkId =73 fromJust $ Map.lookup blkId m7475-- | Convert a CFG node 'Label' to a 'QBE.BlockIdent'.76--77-- This function is partial, on an invalid 'Label', an error is thrown.78labelToIdent :: CFG -> Label -> QBE.BlockIdent79labelToIdent CFG {cfgBlockMap = m} label =80 fromJust $ IntMap.lookup label m8182-- | Utility function to convert a node 'Label' to a 'QBE.Block'.83-- Performs two \(O(\log n)\) lookups internally.84--85-- This function is partial, on an invalid 'Label', an error is thrown.86labelToBlock :: CFG -> Label -> QBE.Block87labelToBlock cfg label =88 let blocks = QBE.fBlock $ cfgFunction cfg89 in fromJust $ Map.lookup (labelToIdent cfg label) blocks9091-- | Mapping of 'Label' to its successors in the CFG, represented as an92-- ordered list of zero, one, or two elements. A list with two elements93-- represents a conditional jump where the left child is the is the true94-- branch and the right child is the false branch. A list wih a single95-- element signifies an unconditional jump. If the given node does not96-- have any successors an empty list is returned.97--98-- This function is partial, on an invalid 'Label', an error is thrown.99lookupSuccs :: CFG -> Label -> [Label]100lookupSuccs CFG {cfgSuccessors = succs} label =101 fromJust $ IntMap.lookup label succs102103------------------------------------------------------------------------104105identStart :: Label106identStart = 0107108-- | Construct a t'CFG' for a given function.109build :: QBE.FuncDef -> CFG110build func =111 CFG112 { cfgMaxBound = snd $ last blkIdLabels,113 cfgFunction = func,114 cfgLabelMap = labelMap,115 cfgBlockMap = IntMap.fromList $ map swap blkIdLabels,116 cfgSuccessors = IntMap.fromList $ build' labelMap blocks117 }118 where119 labelMap :: Map QBE.BlockIdent Label120 labelMap = Map.fromList blkIdLabels121122 blocks :: [QBE.Block]123 blocks = Map.elems $ QBE.fBlock func124125 blkIdLabels :: [(QBE.BlockIdent, Label)]126 blkIdLabels = zip (map QBE.label blocks) [identStart ..]127128build' :: Map QBE.BlockIdent Label -> [QBE.Block] -> [(IntMap.Key, [Label])]129build' labelMap = foldl go []130 where131 toLabel :: QBE.BlockIdent -> Label132 toLabel ident = fromJust $ Map.lookup ident labelMap133134 go acc block@(QBE.Block {QBE.label = ident}) =135 let succs = case QBE.term block of136 QBE.Jump target -> [toLabel target]137 QBE.Jnz _ i1 i2 -> [toLabel i1, toLabel i2]138 QBE.Return _ -> []139 QBE.Halt -> []140 in (toLabel ident, succs) : acc141142------------------------------------------------------------------------143144asGraph :: CFG -> Graph145asGraph cfg = buildG (identStart, cfgMaxBound cfg) $ edges cfg146147asDomGraph :: CFG -> DG.Graph148asDomGraph cfg = IntMap.map IntSet.fromList (cfgSuccessors cfg)149150-- | Determine the entry node of the t'CFG'. Useful, for example, to151-- generated a 'DG.Rooted' representation for the control-flow graph.152startNode :: CFG -> Label153startNode cfg@(CFG {cfgFunction = func}) =154 identToLabel cfg (QBE.fStart func)