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 Analysis (analTests) where78import Data.Bifunctor (bimap)9import Data.List (sort)10import Language.QBE (parseAndFind)11import Language.QBE.Analysis.CDG qualified as CDG12import Language.QBE.Analysis.CFG qualified as CFG13import Language.QBE.Types qualified as QBE14import System.FilePath ((</>))15import Test.Tasty16import Test.Tasty.HUnit1718getFunction :: QBE.GlobalIdent -> String -> IO QBE.FuncDef19getFunction funcName input = snd <$> parseAndFind funcName input2021getFuncAndProg :: FilePath -> QBE.GlobalIdent -> IO QBE.FuncDef22getFuncAndProg fileName funcName =23 let filePath = "test" </> "testdata" </> fileName24 in readFile filePath >>= getFunction funcName2526toBlkName :: CFG.CFG -> CFG.Label -> String27toBlkName cfg = show . CFG.labelToIdent cfg2829cdgEdges :: CFG.CFG -> CDG.CDG -> [(String, String)]30cdgEdges cfg = map go . CDG.edges31 where32 go (f, t) = (toBlkName cfg f, toBlkName cfg t)3334cfgEdges :: CFG.CFG -> [(String, String)]35cfgEdges cfg =36 let toBlk = toBlkName cfg37 in sort $ map (bimap toBlk toBlk) (CFG.edges cfg)3839------------------------------------------------------------------------4041analTests :: TestTree42analTests =43 testGroup44 "Analysis tests"45 [ testCase "Simple CFG without any loops" $46 do47 func <-48 getFunction49 (QBE.GlobalIdent "foo")50 "function w $foo() {\n\51 \@start\n\52 \%val =w add 0, 1\n\53 \jmp @next\n\54 \@next\n\55 \ret\n\56 \}\n"5758 let cfg = CFG.build func59 let startLabel = CFG.identToLabel cfg $ QBE.BlockIdent "start"60 map (CFG.labelToIdent cfg) (CFG.lookupSuccs cfg startLabel)61 @?= [QBE.BlockIdent "next"]6263 cfgEdges cfg64 @?= [("@start", "@next")]6566 -- “If Y is control dependent on X then X must have two exits.“, in67 -- this CFG there are no nodes with two exits: The CDG must be emtpy.68 let ret = CFG.identToLabel cfg $ QBE.BlockIdent "next"69 cdg = CDG.build cfg ret70 cdgEdges cfg cdg @?= []71 CDG.ctrlDeps cdg ret @?= Nothing,72 testCase "Generate CDG for code with single branch" $73 do74 func <-75 getFunction76 (QBE.GlobalIdent "foo")77 "function w $foo() {\n\78 \@start\n\79 \%val =w add 0, 1\n\80 \jnz %val, @ifT, @ifF\n\81 \@ifT\n\82 \%ret =w copy 1\n\83 \jmp @return\n\84 \@ifF\n\85 \%ret =w copy 0\n\86 \jmp @return\n\87 \@return\n\88 \ret %ret\n\89 \}\n"9091 let cfg = CFG.build func92 ret = CFG.identToLabel cfg (QBE.BlockIdent "return")93 cdg = CDG.build cfg ret9495 cdgEdges cfg cdg96 @?= [ ("@ifF", "@start"),97 ("@ifT", "@start")98 ],99 testCase "Compute CDG for code with loop" $100 do101 func <-102 getFunction103 (QBE.GlobalIdent "main")104 "function w $main() {\n\105 \@start\n\106 \%.1 =w copy 0\n\107 \%.2 =w copy 42\n\108 \%.3 =w copy 0\n\109 \@for_cond\n\110 \%.6 =w csltw %.3, %.2\n\111 \jnz %.6, @for_body, @for_join\n\112 \@for_body\n\113 \%.1 =w add %.1, 1\n\114 \@for_cont\n\115 \%.3 =w add %.3, 1\n\116 \jmp @for_cond\n\117 \@for_join\n\118 \ret %.11\n\119 \}\n"120121 let cfg = CFG.build func122 ret = CFG.identToLabel cfg (QBE.BlockIdent "for_join")123 cdg = CDG.build cfg ret124125 cdgEdges cfg cdg126 @?= [ ("@for_body", "@for_cond"),127 ("@for_cond", "@for_cond"),128 ("@for_cont", "@for_cond")129 ],130 testCase "Compute CDG for code with two paths to node" $131 do132 func <- getFuncAndProg "disjunction.qbe" (QBE.GlobalIdent "main")133134 let cfg = CFG.build func135 ret = CFG.identToLabel cfg (QBE.BlockIdent "return")136 cdg = CDG.build cfg ret137138 cdgEdges cfg cdg139 @?= [ ("@if_false.4", "@body.2"),140 ("@if_false.4", "@if_true.3"),141 ("@if_false.6", "@if_true.3"),142 ("@if_join.7", "@if_true.3"),143 ("@if_true.3", "@body.2"),144 ("@if_true.5", "@if_true.3")145 ],146 testCase "Compute dominators for a simple-cc representation" $147 do148 func <- getFuncAndProg "simple-cc-branches.qbe" (QBE.GlobalIdent "myfunc")149150 let cfg = CFG.build func151 CFG.labelToIdent cfg (CFG.startNode cfg)152 @?= QBE.BlockIdent ".L9"153 ]