blob: 95c338e68f400c969420bcc3a1db3272bd224fd2 [file] [log] [blame] [edit]
--
-- Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
--
-- SPDX-License-Identifier: BSD-2-Clause
--
module CapDL.PrintIsabelle where
import CapDL.Model
import CapDL.State (koType, lookupSizeMap, objSizeBits)
import CapDL.PrintUtils (printAsid)
import Prelude ()
import Prelude.Compat
import Text.PrettyPrint
import Numeric (showHex)
import Data.List.Compat
import Data.Ord (comparing)
import Control.Monad (unless)
import Control.Monad.State.Strict (StateT (..))
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Maybe
import Data.Bits
import System.FilePath.Posix
import Text.Regex (mkRegex, matchRegex)
-- This lifts `mapAccumL` over a monad
mapAccumM :: (Monad m, Traversable t) =>
(a -> b -> m (a, c)) -> a -> t b -> m (a, t c)
mapAccumM f z xs = swap <$> runStateT (traverse (StateT . f') xs) z
where swap (a, b) = (b, a)
f' x z = swap <$> f z x
lookupElem :: (Eq a) => a -> Map.Map k a -> Maybe k
lookupElem x m =
case find ((==) x . snd) (Map.toList m) of
Just (k, _) -> Just k
Nothing -> Nothing
mapElem :: (Eq a) => a -> Map.Map k a -> Bool
mapElem x m = case lookupElem x m of
Just _ -> True
Nothing -> False
indent = 2
num n = int (fromIntegral n)
hex :: Word -> String
hex x = "0x" ++ showHex x ""
equiv = text "\\<equiv>"
lambda = text "\\<lambda>"
isaComment x = "\\<comment> \\<open>" ++ x ++ "\\<close>"
{- This mangles hierarchical names into valid Isabelle identifiers:
- we replace '.' by '\'' and check that the result would be valid. -}
mangle :: String -> String
mangle = fixDots . checkValid
where -- Technically, we could support more names, but we don't
-- expect the camkes toolchain to generate them.
nameRE = mkRegex "^[a-zA-Z]([a-zA-Z0-9_.]*[a-zA-Z0-9])?$"
checkValid s = case matchRegex nameRE s of
Just _ -> s
_ -> error $ "can't mangle object name to Isabelle: " ++ show s
fixDots = map (\c -> if c == '.' then '\'' else c)
showID :: ObjID -> String
showID (name, Nothing) = mangle name
showID (name, Just num) = mangle $ name ++ "_" ++ show num
capsName id = showID id ++ "_caps"
constdefs :: String -> String -> Doc
constdefs name typ =
text ("definition " ++ name ++ " :: ") <> doubleQuotes (text typ) <+>
text "where"
{- Prove a set of lemmas. The given proof script is assumed to
- discharge all the proof goals. -}
lemmas :: String -> [String] -> [String] -> Doc
lemmas name statements proof =
text ("lemma " ++ name ++ ": ") $+$
nest 2 (vcat (map (doubleQuotes . text) statements)) $+$
nest 2 (vcat (map text proof))
-- Prove a single lemma.
lemma :: String -> String -> [String] -> Doc
lemma name statement proof =
lemmas name [statement] proof
record :: Doc -> Doc
record p = text "\\<lparr>" <+> p <+> text "\\<rparr>"
printSet :: [Doc] -> Doc
printSet ls = braces $ hsep $ punctuate comma ls
filterEmpty :: [Doc] -> [Doc]
filterEmpty = filter (not . isEmpty)
-- Untypeds are not kernel objects in the Isabelle models.
notUntyped :: KernelObject a -> Bool
notUntyped Untyped{} = False
notUntyped _ = True
{- HACK: fake location for IRQ nodes.
- IRQ CNodes are model-level objects, so we need to invent a physical
- presence for them even if the capDL input doesn't mention them.
- Suitably aligned for 1024 irqs. DSpec probably doesn't care, though. -}
irq_node_paddr__hack :: Word
irq_node_paddr__hack = 0x10000
{-
- Helper that recovers memory layout from a single untypedCover.
-}
-- Check that we allocate objects consistently with their paddrs.
checkEqPaddr :: KernelObject a -> Word -> Bool
checkEqPaddr obj p = maybe True (p ==) (objPaddr obj)
untypedCoverToPaddrs ::
ObjectSizeMap -> ObjMap Word -> ObjID -> KernelObject Word -> [ObjID]
-> Either (String, [ObjID]) (Map.Map ObjID Word)
untypedCoverToPaddrs sizeMap objs
utID (Untyped { maybePaddr = Just utPaddr, maybeSizeBits = Just utSizeBits })
cover =
do
let getObject id =
case Map.lookup id objs of
Just o -> return o
_ -> Left ("Object in untyped but not in objects list", [id, utID])
utSize = 2^utSizeBits
utEnd = utPaddr + utSize
alloc1 addr objID =
do obj <- getObject objID
let objSize = 2^objSizeBits sizeMap obj
unless (checkEqPaddr obj addr) $
Left ("Object addr doesn't match untyped's current addr (" ++
hex addr ++ ")", [utID, objID])
let addr' = addr + objSize
unless (addr `mod` objSize == 0) $
Left ("Can't place obj at unaligned addr (addr=" ++
hex addr ++ ", obj size=" ++
hex objSize ++ ")", [utID, objID])
unless (addr' <= utEnd) $
Left ("Can't place obj at addr outside untyped (addr=" ++
hex addr ++ ", ut end=" ++ hex utEnd ++ ", obj size=" ++
hex objSize ++ ")", [utID, objID])
return (addr', (objID, addr))
Map.fromList . snd <$> mapAccumM alloc1 utPaddr cover
-- unused, or child untyped; no-op
untypedCoverToPaddrs _ _ _ Untyped{} [] = return Map.empty
untypedCoverToPaddrs _ _ utID Untyped{} (:){} =
Left ("Untyped has children but is missing paddr or size", [utID])
untypedCoverToPaddrs _ _ koID _ _ =
error $ "internal error in untypedCoverToPaddrs: not an untyped: " ++ show koID
{-
- Determine the memory address (i.e. cdl_object_id) for each object.
- This only works for capDL specs that have been pre-allocated, i.e.,
- every object belongs to an untypedCover that has a physical address.
-}
getPhysAddrs :: ObjectSizeMap -> ObjMap Word -> CoverMap
-> Either (String, [ObjID]) (Map.Map ObjID Word)
getPhysAddrs objSizeMap objs untypedCovers =
Map.unions <$>
sequence [
Map.insert utID utPaddr <$> -- add untyped's own addr
untypedCoverToPaddrs objSizeMap objs utID ut cover
| (utID, cover) <- Map.toList untypedCovers,
let ut@Untyped{ maybePaddr = Just utPaddr } = objs Map.! utID ]
{-
- Look up object locations that have been computed by getPhysAddrs.
- Also invent locations for IRQ nodes.
-}
getAddr :: ObjectSizeMap -> Map.Map ObjID Word -> IRQMap -> ObjID -> Word
getAddr objSizeMap objAddrs irqNode id
| Just irq <- lookupElem id irqNode = irq_node_paddr__hack + irq * slotSize
| Just addr <- Map.lookup id objAddrs = addr
| otherwise = error $ "getID: no address for object: " ++ showID id
where slotSize = 2^lookupSizeMap CNode_T objSizeMap
getID :: ObjectSizeMap -> Map.Map ObjID Word -> IRQMap -> ObjID -> Doc
getID objSizeMap objAddrs irqNode id = text $ hex $ getAddr objSizeMap objAddrs irqNode id
printID :: ObjID -> Doc
printID id = text (showID id ++ "_id")
printRight :: Rights -> Doc
printRight = text . show
printRightsList :: [Rights] -> [Doc]
printRightsList [] = [empty]
printRightsList xs = map printRight xs
printRights :: CapRights -> Doc
printRights r =
printSet $ printRightsList $ Set.toList r
printFrameSize :: ObjID -> ObjMap Word -> Doc
printFrameSize id ms =
let Just (Frame sz _ _) = Map.lookup id ms
in num sz
printCoverSet :: ObjMap Word -> Maybe [ObjID] -> Doc
printCoverSet _ Nothing = printSet []
printCoverSet ms (Just objs) = printSet $ map printID objs'
where objs' = filter (\id -> isJust $ Map.lookup id ms) objs
printMaybeAsid :: Maybe Asid -> Doc
printMaybeAsid Nothing = text "None"
printMaybeAsid (Just asid) = parens $ text "Some" <+> printAsid asid
printReal :: Bool -> Doc
printReal True = text "Real"
printReal False = text "Fake"
printCNodeSize :: ObjMap Word -> ObjID -> Doc
printCNodeSize ms id =
let (CNode _ sz _) = fromJust $ Map.lookup id ms
in num sz
-- FIXME: missing capDL attribute
untypedIsDev :: KernelObject Word -> Bool
untypedIsDev Untyped{} = False -- lies
untypedIsDev obj = error $ "untypedIsDev: got " ++ show (koType obj)
-- The bool represents whether the cap is a real cap;
-- i.e. if it is not in a PT or PD
printCap :: ObjMap Word -> IRQMap -> CoverMap -> Bool -> Cap -> Doc
printCap _ _ _ _ NullCap = text "NullCap"
printCap ms _ _ _ (UntypedCap id) =
text "UntypedCap" <+> text (show $ untypedIsDev (ms Map.! id)) <+>
parens (text "ptr_range" <+> printID id <+> num utSize) <+>
printSet [] -- TODO: check implied free range (or assert ut is empty)
where Just Untyped{ maybeSizeBits = Just utSize } =
Map.lookup id ms
printCap _ _ _ _ (EndpointCap id badge rights) = text "EndpointCap" <+>
printID id <+> num badge <+> printRights rights
printCap _ _ _ _ (NotificationCap id badge rights) = text "NotificationCap" <+>
printID id <+> num badge <+> printRights rights
printCap _ _ _ _ (ReplyCap id) = text "ReplyCap" <+> printID id
printCap _ _ _ _ (MasterReplyCap id) =
text "MasterReplyCap" <+> printID id
printCap ms _ _ _ (CNodeCap id guard gsize) =
text "CNodeCap" <+> printID id <+> num guard <+> num gsize <+>
printCNodeSize ms id
printCap _ _ _ _ (TCBCap id) = text "TcbCap" <+> printID id
printCap _ _ _ _ IRQControlCap = text "IrqControlCap"
printCap _ irqNode _ _ (IRQHandlerCap id) =
text "IrqHandlerCap" <+> num (fromJust (lookupElem id irqNode))
printCap _ _ _ _ DomainCap = text "DomainCap"
printCap ms _ _ real (FrameCap id rights asid cached _) = text "FrameCap" <+>
-- is_device flag, assumed always false. FIXME: add to model?
text "False" <+>
printID id <+> printRights rights <+> printFrameSize id ms <+>
printReal real <+> printMaybeAsid asid <+>
text (if cached then "" else isaComment "uncached")
printCap _ _ _ real (PTCap id asid) =
text "PageTableCap" <+> printID id <+> printReal real <+>
printMaybeAsid asid
printCap _ _ _ real (PDCap id asid) =
text "PageDirectoryCap" <+> printID id <+> printReal real <+>
printMaybeAsid asid
printCap _ _ _ _ ASIDControlCap = text "AsidControlCap"
printCap ms _ _ _ (ASIDPoolCap id) =
text "AsidPoolCap" <+> printID id <+> asidHigh
-- In capDL, asid_high is a property of the asid_pool (as it should be),
-- but in DSpec it is a property of ASIDPoolCaps. We do the translation here.
where asidHigh =
case Map.lookup id ms of
Just (ASIDPool _ (Just asidHigh)) -> text (hex asidHigh)
_ -> text "undefined"
printCap _ _ _ _ cap = text $ "undefined " ++ isaComment ("unsupported cap: " ++ show cap)
printCapMapping :: ObjMap Word -> IRQMap -> CoverMap -> Bool -> (Word, Cap) -> Doc
printCapMapping ms irqNode covers real (slot, cap) =
num slot <+> text "\\<mapsto>"
<+> text "Types_D." <> printCap ms irqNode covers real cap
printCapMap :: ObjMap Word -> IRQMap -> CoverMap -> Bool -> CapMap Word -> Doc
printCapMap ms irqNode covers real slots =
case map (printCapMapping ms irqNode covers real) (Map.toList slots) of
[] -> text "Map.empty"
xs -> brackets $ vcat $ punctuate comma xs
printCaps' :: ObjMap Word -> ObjID -> IRQMap -> CoverMap -> Bool -> CapMap Word -> Doc
printCaps' ms id irqNode covers real slots = constdefs name "cdl_cap_map" $+$
doubleQuotes (text name <+> equiv <+> printCapMap ms irqNode covers real slots)
where name = capsName id
printCaps :: ObjMap Word -> ObjID -> IRQMap -> CoverMap -> KernelObject Word -> Doc
printCaps ms id irqNode covers (PT slots) =
printCaps' ms id irqNode covers False slots $+$ text ""
printCaps ms id irqNode covers (PD slots) =
printCaps' ms id irqNode covers False slots $+$ text ""
printCaps ms id irqNode covers (ASIDPool slots _) =
printCaps' ms id irqNode covers False slots $+$ text ""
printCaps ms id irqNode covers obj
| hasSlots obj = printCaps' ms id irqNode covers True (slots obj) $+$ text ""
| otherwise = empty
getAddress' :: ObjMap Word -> [Cap] -> Word -> Cap -> (Word, Cap) -> Maybe Word
getAddress' ms seen current_word goal_cap (slot, cap) =
getAddress ms seen (current_word + slot) goal_cap cap
getAddress :: ObjMap Word -> [Cap] -> Word -> Cap -> Cap -> Maybe Word
getAddress ms seen current_word goal_cap cap@(CNodeCap objID guard gsz)
| cap `elem` seen = Nothing
| otherwise =
let (CNode slots sz _) = fromJust $ Map.lookup objID ms
radix_sz = fromIntegral sz
level_sz = fromIntegral $ gsz + sz
new_word = shift current_word level_sz + shift guard radix_sz
in case lookupElem goal_cap slots of
Just slot -> Just (new_word + slot)
Nothing -> listToMaybe $ mapMaybe
(getAddress' ms (cap:seen) new_word goal_cap)
(Map.toList slots)
getAddress _ _ _ _ _ = Nothing
hasFaultEndpoint :: Maybe Word -> String
hasFaultEndpoint fault =
case fault of
Just _ -> "True"
Nothing -> "False"
printObjID :: ObjectSizeMap -> Map.Map ObjID Word -> IRQMap -> (ObjID, KernelObject Word) -> Doc
printObjID objSizeMap objAddrs irqNode (id, _) =
constdefs name "cdl_object_id" $+$
doubleQuotes (printID id <+> equiv <+> obj_id)
$+$ text ""
where name = showID id ++ "_id"
obj_id = getID objSizeMap objAddrs irqNode id
printObjIDs :: ObjectSizeMap -> Map.Map ObjID Word -> ObjMap Word -> IRQMap -> Doc
printObjIDs objSizeMap objAddrs ms irqs =
vcat (map (printObjID objSizeMap objAddrs irqs) (Map.toList ms))
$+$ text ""
printObj' :: ObjMap Word -> ObjID -> KernelObject Word -> Doc
printObj' _ _ Endpoint = text "Endpoint"
printObj' _ _ Notification = text "Notification"
printObj' _ id (TCB _ fault _ dom _) = text "Tcb" <+>
record (fsep $ punctuate comma $ map text
["cdl_tcb_caps = " ++ capsName id,
"cdl_tcb_fault_endpoint = " ++ maybe "0" show fault,
"cdl_tcb_intent = undefined",
"cdl_tcb_has_fault = " ++ hasFaultEndpoint fault,
"cdl_tcb_domain = " ++ show dom])
printObj' _ id (CNode _ bits _) = text "CNode" <+>
record (fsep $ punctuate comma $ map text
["cdl_cnode_caps = " ++ capsName id,
"cdl_cnode_size_bits = " ++ show bits])
printObj' _ id (ASIDPool _ _) = text "AsidPool" <+>
record (fsep $ punctuate comma $ map text
["cdl_asid_pool_caps = " ++ capsName id])
printObj' _ id (PT _) = text "PageTable" <+>
record (fsep $ punctuate comma $ map text
["cdl_page_table_caps = " ++ capsName id])
printObj' _ id (PD _) = text "PageDirectory" <+>
record (fsep $ punctuate comma $ map text
["cdl_page_directory_caps = " ++ capsName id])
printObj' _ _ (Frame vmSzBits _ _) = text "Frame" <+>
record (fsep $ punctuate comma $ map text
["cdl_frame_size_bits = " ++ show vmSzBits])
printObj' _ _ obj = text $ "undefined " ++ isaComment ("unsupported obj: " ++ show obj)
printLemmaObjectSlots :: ObjID -> Doc
printLemmaObjectSlots id =
lemma (objName++"_object_slots")
("object_slots "++objName++" = "++slots)
["by (simp add: "++objName++"_def object_slots_def)"]
where objName = showID id
slots = capsName id
printObj :: ObjMap Word -> IRQMap -> CoverMap -> (ObjID, KernelObject Word) -> Doc
printObj ms irqNode covers (id, obj) = printCaps ms id irqNode covers obj $+$
constdefs name "cdl_object" $+$
doubleQuotes (text name <+> equiv <+> text "Types_D." <> printObj' ms id obj)
$+$ text "" $+$
(if hasSlots obj then printLemmaObjectSlots id $+$ text "" else text "")
where name = showID id
printObjs :: ObjMap Word -> IRQMap -> CoverMap -> [(ObjID, KernelObject Word)] -> Doc
printObjs ms irqNode covers isaCdlObjects =
vcat (map (printObj ms irqNode covers) isaCdlObjects)
printObjMapping :: (ObjID, KernelObject Word) -> Doc
printObjMapping (id, _) = printID id <+> text ("\\<mapsto> " ++ showID id)
printObjMap :: ObjMap Word -> [(ObjID, KernelObject Word)] -> Doc
printObjMap _ isaCdlObjects =
case map printObjMapping isaCdlObjects of
[] -> text "Map.empty"
xs -> brackets $ fsep $ punctuate comma xs
-- Map of all objects. We use the FastMap builder for scalability.
printObjects :: [(ObjID, KernelObject Word)] -> Doc
printObjects isaCdlObjects =
text "local_setup {*" $+$
text "FastMap.define_map (FastMap.name_opts_default \"objects\")" $+$
nest 2 (
nest 2 (brackets (vcat $ punctuate comma $ map binding isaCdlObjects)) $+$
text "@{term \"id :: cdl_object_id \\<Rightarrow> cdl_object_id\"}" $+$
text "@{thms ids}" $+$
text "false"
) $+$
text "*}"
where binding (id, _) = text $ "(@{term \"" ++ showID id ++ "_id\"}, " ++
"@{term \"" ++ showID id ++ "\"})"
printIrqMapping :: (Word, Doc) -> Doc
printIrqMapping (irqID, id) =
text (hex irqID) <+> text ":=" <+> id
{- The capDL formal model doesn't use a partial mapping for
- irqs, which is nonsensical: no platform has all possible
- "cdl_irq" values. This is a bug to be fixed.
- For now, we just use "undefined" as the default mapping, and
- add IRQs from our input model on top of that.
-}
-- Map each known IRQ to its IRQ node address.
printIRQsMap :: ObjMap Word -> IRQMap -> Doc
printIRQsMap _ irqNode =
let irqs = Map.map printID irqNode
irqMap = parens $ fsep $ punctuate comma $ map printIrqMapping $
Map.toList irqs
baseIrqs = text "undefined"
allIrqs | Map.null irqs = baseIrqs -- irqMap would be "()"
| otherwise = baseIrqs <+> irqMap
in allIrqs
printIRQs :: ObjMap Word -> IRQMap -> Doc
printIRQs ms irqNode =
constdefs "irqs" "cdl_irq \\<Rightarrow> cdl_object_id" $+$
doubleQuotes (text "irqs" <+> equiv <+> printIRQsMap ms irqNode)
makeASIDTable :: ObjMap Word -> CapMap Word
makeASIDTable ms = foldl' addToASIDTable Map.empty (Map.toList ms)
where addToASIDTable asidTable (objID, ASIDPool _ (Just asidHigh)) =
Map.insert asidHigh (ASIDPoolCap objID) asidTable
addToASIDTable asidTable _ = asidTable
printASIDTable :: ObjMap Word -> IRQMap -> CoverMap -> Doc
printASIDTable ms irqs covers =
constdefs "asid_table" "cdl_cap_map" $+$
doubleQuotes (text "asid_table" <+> equiv <+>
printCapMap ms irqs covers False (makeASIDTable ms))
printCapRef :: CapRef -> Doc
printCapRef (obj, slot) = parens $ printID obj <> comma <+> num slot
printCDTMapping :: (CapRef, CapRef) -> Doc
printCDTMapping (parent, child) =
printCapRef parent <+> text "\\<mapsto>" <+> printCapRef child
printCDTMap :: CDT -> Doc
printCDTMap cdt =
case map printCDTMapping (Map.toList cdt) of
[] -> text "Map.empty"
xs -> brackets $ fsep $ punctuate comma xs
printCDT :: CDT -> Doc
printCDT cdt = constdefs "cdt" "cdl_cdt" $+$
doubleQuotes (text "cdt" <+> equiv <+> printCDTMap cdt)
printCDLState :: Arch -> Doc
printCDLState arch =
record $ fsep $ punctuate comma $ map text ["cdl_arch = " ++ show arch,
"cdl_objects = objects", "cdl_cdt = cdt",
"cdl_current_thread = undefined", "cdl_irq_node = irqs",
"cdl_asid_table = asid_table", "cdl_current_domain = undefined"]
printSimps :: ObjMap Word -> [(ObjID, KernelObject Word)] -> Doc
printSimps ms isaCdlObjects =
text "lemmas ids = " $+$ vcat (map objIDDef $ Map.toList ms) $+$ text "" $+$
text "lemmas cap_defs = " $+$ vcat (map capsDef objsWithCaps) $+$ text "" $+$
text "lemmas obj_defs = " $+$ vcat (map objDef isaCdlObjects) $+$ text ""
where objIDDef (id, _) = printID id <> text "_def"
capsDef (id, _) = text (capsName id) <> text "_def"
objDef (id, _) = text (showID id) <> text "_def"
objsWithCaps = filter (\(_, obj) -> hasSlots obj) isaCdlObjects
printState :: Arch -> Doc
printState arch = constdefs "state" "cdl_state" $+$
doubleQuotes (text "state" <+> equiv <+> printCDLState arch)
printFileName :: String -> Doc
printFileName file = text $ dropExtension $ takeFileName file
printHeader :: String -> Doc
printHeader name =
text "theory" <+> doubleQuotes (printFileName name) $+$ text "imports" $+$
nest 2 (text "\"DSpec.Types_D\"" $+$
text "\"Lib.FastMap\"" $+$
text ("\"DPolicy.Dpolicy\" " ++ isaComment "@{const ptr_range}")) $+$
text "begin"
printFooter :: Doc
printFooter = text "end"
printIsabelle :: String -> ObjectSizeMap -> Model Word -> Doc
printIsabelle name objSizeMap (Model (arch@ARM11) ms irqNode cdt untypedCovers) =
printHeader name $+$ text "" $+$
printObjIDs objSizeMap objAddrs ms irqNode $+$
printObjs ms irqNode untypedCovers isaCdlObjects $+$
text "" $+$
printSimps ms isaCdlObjects $+$ text "" $+$
printObjects isaCdlObjects $+$ text "" $+$
printIRQs ms irqNode $+$ text "" $+$
printASIDTable ms irqNode untypedCovers $+$ text "" $+$
printCDT cdt $+$ text "" $+$
printState arch $+$ text "" $+$
printFooter
where objAddrs = case getPhysAddrs objSizeMap ms untypedCovers of
Left (msg, ids) ->
error $ "Failed to allocate objects from untypeds: " ++
msg ++ " (" ++ intercalate ", " (map show ids) ++ ")"
Right x -> x
getAddr' = getAddr objSizeMap objAddrs irqNode
-- Isabelle capDL objects. These are sorted by id because the
-- FastMap builder expects a sorted list as input.
isaCdlObjects = sortBy (comparing $ getAddr' . fst) $
filter (notUntyped . snd) $
Map.toList ms
printIsabelle _ _ _ =
error "Currently only the ARM11 architecture is supported when parsing to Isabelle"