Compare commits

..

2 Commits

Author SHA1 Message Date
Enrico Lumetti 690985cb87 Add CFG construction 2024-10-13 20:22:53 +02:00
Enrico Lumetti 27f24389ae Fix label and Op show 2024-10-13 20:22:21 +02:00
4 changed files with 121 additions and 22 deletions

View File

@ -17,7 +17,7 @@ build-type: Simple
extra-source-files: README.md extra-source-files: README.md
library eoc-lib library eoc-lib
exposed-modules: Eoc, Bril exposed-modules: Eoc, Bril, Bril.CFG
hs-source-dirs: lib hs-source-dirs: lib
build-depends: build-depends:
base >=4.13 && <4.18, base >=4.13 && <4.18,
@ -25,7 +25,8 @@ library eoc-lib
aeson >= 2.2, aeson >= 2.2,
sorted-list ^>= 0.2, sorted-list ^>= 0.2,
bytestring ^>= 0.11, bytestring ^>= 0.11,
scientific ^>= 0.3 scientific ^>= 0.3,
containers ^>= 0.6.7
default-language: Haskell2010 default-language: Haskell2010
default-extensions: DeriveGeneric, OverloadedStrings default-extensions: DeriveGeneric, OverloadedStrings
@ -46,6 +47,7 @@ test-suite tests
build-depends: build-depends:
base >=4.13 && <4.18, base >=4.13 && <4.18,
HUnit >=1.6, HUnit >=1.6,
QuickCheck ^>=2.15,
eoc-lib, eoc-lib,
text >= 2.0, text >= 2.0,
aeson >= 2.2 aeson >= 2.2

View File

@ -120,24 +120,24 @@ instrFromString s
instance Show InstrOperation where instance Show InstrOperation where
show op = case op of show op = case op of
Nop -> "add" Add -> "add"
Add -> "sub" Sub -> "sub"
Sub -> "mul" Mul -> "mul"
Mul -> "eq" Eq -> "eq"
Eq -> "lt" Lt -> "lt"
Lt -> "gt" Gt -> "gt"
Gt -> "le" Le -> "le"
Le -> "ge" Ge -> "ge"
Ge -> "not" Not -> "not"
Not -> "and" And -> "and"
And -> "or" Or -> "or"
Or -> "jmp" Jmp -> "jmp"
Jmp -> "br" Br -> "br"
Br -> "ret" Ret -> "ret"
Ret -> "call" Call -> "call"
Call -> "print" Print -> "print"
Print -> "id" Id -> "id"
Id -> "nop" Nop -> "nop"
optionalArrayField :: FromJSON a => Data.Aeson.Object -> Data.Aeson.Key -> Parser [a] optionalArrayField :: FromJSON a => Data.Aeson.Object -> Data.Aeson.Key -> Parser [a]

85
lib/Bril/CFG.hs Normal file
View File

@ -0,0 +1,85 @@
module Bril.CFG (BasicBlock, CFG, createCFG) where
import Bril
import qualified Data.Map.Strict as Map
data BasicBlock = BasicBlock {
instrs :: [Instruction],
incoming :: [String]
}
deriving (Show)
data CFG = CFG {
cfgBlocks :: Map.Map String BasicBlock,
cfgStart :: String
}
deriving (Show)
createCFG :: [Instruction] -> CFG
createCFG instrs =
let
(initCFG, firstLabel) = initializeCFG $ addFallthroughJmp $ splitBBs $ addInitialLabel instrs
cfgBlocks' = foldr processBB (cfgBlocks initCFG) (Map.keys (cfgBlocks initCFG))
in
CFG cfgBlocks' firstLabel
where
processBB :: String -> Map.Map String BasicBlock -> Map.Map String BasicBlock
processBB label m = addBBJumps label (outgoingCFGLabels m label) m
addBBJumps :: String -> [String] -> Map.Map String BasicBlock -> Map.Map String BasicBlock
addBBJumps bblockLabel incomingLabels m = Map.adjust (addLabels incomingLabels) bblockLabel m
addLabels :: [String] -> BasicBlock -> BasicBlock
addLabels labels (BasicBlock instrs incoming) = (BasicBlock instrs (incoming ++ labels))
initializeCFG :: [([Instruction], String)] -> (CFG, String)
initializeCFG instrs =
let mappedBlocks = foldr mapBlock Map.empty instrs -- Map.fromList $ map swap instrs
firstLabel = snd . head $ instrs
in (CFG mappedBlocks firstLabel, firstLabel)
where
mapBlock (instrs, label) m = Map.insert label (BasicBlock instrs []) m
addFallthroughJmp :: [([Instruction], String)] -> [([Instruction], String)]
addFallthroughJmp blocks =
let blocks' = map addJumpToNextBB $ zip blocks $ tail $ map snd blocks
in blocks' ++ [last blocks]
where
addJumpToNextBB (block@(instrs, label), nextLabel)
-- TODO: handle empty instrs
| isJump (last instrs) = block
| otherwise = (instrs ++ [EffectInstr Jmp [] [] [nextLabel]], label)
splitBBs :: [Instruction] -> [([Instruction], String)]
splitBBs instrs = zip (tail basicBlocks) labels
where
(basicBlocks, labels) = foldr splitBBs' ([[]], []) instrs
splitBBs' instr (bblocks, labels)
| isLabel instr = ([] : bblocks, (labelName instr) : labels)
| isJump instr = ([instr] : (tail bblocks) , labels)
| otherwise = ((instr : (head bblocks)) : (tail bblocks), labels)
addInitialLabel :: [Instruction] -> [Instruction]
addInitialLabel instrs@(firstInstr : instrs')
| isLabel firstInstr = instrs
| otherwise = (Label "@start") : instrs
addInitialLabel [] = []
labelName (Label name) = name
isLabel instr = case instr of
(Label _) -> True
_ -> False
isJump instr = case instr of
(EffectInstr op _ _ _) -> op == Jmp || op == Br
_ -> False
outgoingCFGLabels :: Map.Map String BasicBlock -> String -> [String]
outgoingCFGLabels m label =
case Map.lookup label m of
Just bb -> outgoingLabels bb
_ -> []
outgoingLabels :: BasicBlock -> [String]
outgoingLabels (BasicBlock instrs _) =
let (EffectInstr lastOp _ _ labels) = last instrs
in labels

View File

@ -1,7 +1,8 @@
module Main where module Main where
import qualified Bril as B import qualified Bril as B
import Data.Aeson (eitherDecode, decode, toJSON, parseJSON, Result(..)) import qualified Bril.CFG as BCFG
import Data.Aeson (eitherDecode, decode, encode, toJSON, parseJSON, Result(..))
import Data.Aeson.Types (parse) import Data.Aeson.Types (parse)
import Data.Text import Data.Text
import Data.Maybe import Data.Maybe
@ -10,6 +11,7 @@ import Data.Function ((&))
import Control.Exception (try) import Control.Exception (try)
import Test.HUnit import Test.HUnit
import qualified Test.QuickCheck as QC
import qualified System.Exit as Exit import qualified System.Exit as Exit
testTypes :: Test testTypes :: Test
@ -81,6 +83,10 @@ testInstrs = TestCase $ do
(eitherDecode "{\"op\": \"id\", \"dest\": \"a\", \"type\": \"bool\", \"args\": [\"b\"]}") (eitherDecode "{\"op\": \"id\", \"dest\": \"a\", \"type\": \"bool\", \"args\": [\"b\"]}")
(Right $ B.ValueInstr B.Id B.Bool "a" ["b"] [] []) (Right $ B.ValueInstr B.Id B.Bool "a" ["b"] [] [])
assertEqual "parse label"
(eitherDecode "{\"label\": \"test\"}")
(Right $ B.Label "test")
testProgramFromFiles :: Test testProgramFromFiles :: Test
testProgramFromFiles = TestCase $ do testProgramFromFiles = TestCase $ do
res <- try $ (B.parseBrilFromPath "bril-sources/add.json") :: IO (Either IOError (Either String B.Program)) res <- try $ (B.parseBrilFromPath "bril-sources/add.json") :: IO (Either IOError (Either String B.Program))
@ -88,6 +94,10 @@ testProgramFromFiles = TestCase $ do
(Right parseResult) -> assertBool "parse add.json" $ isRight parseResult (Right parseResult) -> assertBool "parse add.json" $ isRight parseResult
_ -> assertFailure "cannot open file" _ -> assertFailure "cannot open file"
prop_InvariantBrilSerialization :: B.Program -> Bool
prop_InvariantBrilSerialization program =
(decode (encode program)) == (Just program)
tests :: Test tests :: Test
tests = TestList [ tests = TestList [
@ -100,4 +110,6 @@ tests = TestList [
main :: IO () main :: IO ()
main = do main = do
result <- runTestTT tests result <- runTestTT tests
-- QC.quickCheck prop_InvariantBrilSerialization
if failures result > 0 then Exit.exitFailure else Exit.exitSuccess if failures result > 0 then Exit.exitFailure else Exit.exitSuccess