------------------------------------------------------------------------ -- | -- Module : Lang.Crucible.SSAConversion -- Description : Allows converting from RTL to SSA representation. -- Copyright : (c) Galois, Inc 2014 -- License : BSD3 -- Maintainer : Joe Hendrix -- Stability : provisional -- -- This module provides a function for converting from the RTL to SSA -- Crucible representation. ------------------------------------------------------------------------ {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} module Lang.Crucible.CFG.SSAConversion ( toSSA ) where import Control.Exception (assert) import Control.Lens ((&)) import Control.Monad.State.Strict import Data.Bimap (Bimap) import qualified Data.Bimap as Bimap import qualified Data.Foldable as Fold import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (isJust, fromMaybe) import Data.Parameterized.Some import Data.Parameterized.TraversableFC import Data.Sequence (Seq) import qualified Data.Sequence as Seq import Data.Set (Set) import qualified Data.Set as Set import Data.Type.Equality import qualified Prettyprinter as Pretty import What4.FunctionName (FunctionName) import What4.ProgramLoc import Lang.Crucible.Analysis.Reachable import qualified Lang.Crucible.CFG.Core as C import qualified Lang.Crucible.CFG.Expr as C import Lang.Crucible.CFG.Reg import Lang.Crucible.FunctionHandle import Lang.Crucible.Panic (panic) #ifdef UNSAFE_OPS -- We deliberately import Context.Unsafe as it is the only one that supports -- the unsafe coerces between an index and its extension. import Data.Parameterized.Context.Unsafe as Ctx (Assignment) import Data.Parameterized.Context as Ctx hiding (Assignment) import Data.Parameterized.Map (MapF) import qualified Data.Parameterized.Map as MapF import Unsafe.Coerce #else import Data.Parameterized.Context as Ctx #endif ------------------------------------------------------------------------ -- Utilities -- | Given a list of pairs returns a map that maps each value appearing -- in the first element to the second element in the set of pairs -- containing it. nextSetMap :: (Ord x, Ord y) => [(x,y)] -> Map x (Set y) nextSetMap l = execState (traverse go l) Map.empty where go (x,y) = modify $ Map.insertWith Set.union x (Set.singleton y) ------------------------------------------------------------------------ -- Input -- | An input is a wrapper around a value that also knows if the -- value was obtained as the output from a previous block. -- -- * The first argument is true if the value was created from a previous block -- * The second is the value itself. data Input s = Input { inputGeneratedPrev :: !Bool -- ^ Stores true if the value was created from a previous block. , inputValue :: !(Some (Value s)) } instance Show (Input s) where showsPrec p r = showsPrec p (inputValue r) instance Eq (Input s) where x == y = inputValue x == inputValue y isOutputFromBlock :: BlockID s -> Some (Value s) -> Bool isOutputFromBlock (LambdaID l) (Some (AtomValue a)) | LambdaArg l' <- atomSource a = isJust (testEquality l l') isOutputFromBlock _ _ = False mkInput :: BlockID s -> Some (Value s) -> Input s mkInput b v = Input { inputGeneratedPrev = isOutputFromBlock b v , inputValue = v } instance Ord (Input s) where -- LambdaArg introduced in this block should be last. compare x y = case (inputGeneratedPrev x, inputGeneratedPrev y) of (True, True ) -> assert (inputValue x == inputValue y) EQ (True, False) -> GT (False, True ) -> LT (False, False) -> compare (inputValue x) (inputValue y) ------------------------------------------------------------------------ -- BlockInput data BlockInput ext s blocks ret args = BInput { binputID :: !(C.BlockID blocks args) -- | Arguments expected by block. , binputArgs :: !(Assignment (Value s) args) , binputStmts :: !(Seq (Posd (Stmt ext s))) , binputTerm :: !(Posd (ExtendedTermStmt s blocks ret)) } -- The Breakpoint non-terminator statement becomes a jump during SSA conversion. -- This datatype temporarily adds breakpoint as a terminator statement. data ExtendedTermStmt s blocks ret where BaseTermStmt :: TermStmt s ret -> ExtendedTermStmt s blocks ret BreakStmt :: JumpInfo s blocks -> ExtendedTermStmt s blocks ret type BlockInputAssignment ext s blocks ret = Assignment (BlockInput ext s blocks ret) extBlockInputAssignment :: BlockInputAssignment ext s blocks ret a -> BlockInputAssignment ext s (blocks ::> tp) ret a extBlockInput :: BlockInput ext s blocks ret args -> BlockInput ext s (blocks ::> tp) ret arg extBreakpoints :: Bimap BreakpointName (Some (C.BlockID blocks)) -> Bimap BreakpointName (Some (C.BlockID (blocks ::> tp))) #ifdef UNSAFE_OPS extBlockInputAssignment = unsafeCoerce extBlockInput = unsafeCoerce extBreakpoints = unsafeCoerce #else extBlockInputAssignment = fmapFC extBlockInput extBlockInput bi = bi { binputID = C.extendBlockID (binputID bi) } extBreakpoints = Bimap.mapR (mapSome C.extendBlockID) #endif ------------------------------------------------------------------------ -- inferRegAssignment inferRegAssignment :: Set (Input s) -> Some (Assignment (Value s)) inferRegAssignment s = Ctx.fromList (inputValue <$> Set.toList s) ------------------------------------------------------------------------ -- JumpInfo data JumpInfo s blocks where JumpInfo :: C.BlockID blocks types -> C.CtxRepr types -> Assignment (Value s) types -> JumpInfo s blocks emptyJumpInfoMap :: JumpInfoMap s blocks lookupJumpInfo :: Label s -> JumpInfoMap s blocks -> Maybe (JumpInfo s blocks) insertJumpInfo :: Label s -> JumpInfo s blocks -> JumpInfoMap s blocks -> JumpInfoMap s blocks #ifdef UNSAFE_OPS type JumpInfoMap s blocks = Map (Label s) (JumpInfo s blocks) extJumpInfoMap :: JumpInfoMap s blocks -> JumpInfoMap s (blocks ::> args) extJumpInfoMap = unsafeCoerce emptyJumpInfoMap = Map.empty lookupJumpInfo = Map.lookup insertJumpInfo = Map.insert #else data JumpInfoMap s blocks = forall blocks'. JumpInfoMap { _jimDiff :: !(Diff blocks' blocks) , _jimMap :: !(Map (Label s) (JumpInfo s blocks')) , _jimThunk :: Map (Label s) (JumpInfo s blocks) -- NB! don't make this strict } emptyJumpInfoMap = JumpInfoMap noDiff Map.empty Map.empty extJumpInfoMap :: JumpInfoMap s blocks -> JumpInfoMap s (blocks ::> args) extJumpInfoMap (JumpInfoMap diff mp _) = let diff' = extendRight diff in JumpInfoMap diff' mp (fmap (extJumpInfo diff') mp) lookupJumpInfo l (JumpInfoMap diff mp _) = fmap (extJumpInfo diff) (Map.lookup l mp) --lookupJumpInfo l mp = Map.lookup l (jimThunk mp) insertJumpInfo l ji (JumpInfoMap _ _ thk) = let mp = Map.insert l ji thk in JumpInfoMap noDiff mp mp -- | Extend jump target extJumpInfo :: Diff blocks' blocks -> JumpInfo s blocks' -> JumpInfo s blocks extJumpInfo diff (JumpInfo b typs a) = JumpInfo (C.extendBlockID' diff b) typs a #endif ------------------------------------------------------------------------ -- SwitchInfo data SwitchInfo s blocks tp where SwitchInfo :: C.BlockID blocks (args ::> tp) -> C.CtxRepr args -> Assignment (Value s) args -> SwitchInfo s blocks tp emptySwitchInfoMap :: SwitchInfoMap s blocks insertSwitchInfo :: LambdaLabel s tp -> SwitchInfo s blocks tp -> SwitchInfoMap s blocks -> SwitchInfoMap s blocks lookupSwitchInfo :: LambdaLabel s tp -> SwitchInfoMap s blocks -> Maybe (SwitchInfo s blocks tp) #ifdef UNSAFE_OPS {- instance CoercibleF (SwitchInfo s blocks) where coerceF x = Data.Coerce.coerce x -} newtype SwitchInfoMap s blocks = SwitchInfoMap (MapF (LambdaLabel s) (SwitchInfo s blocks)) emptySwitchInfoMap = SwitchInfoMap MapF.empty extSwitchInfoMap :: SwitchInfoMap s blocks -> SwitchInfoMap s (blocks ::> args) extSwitchInfoMap = unsafeCoerce insertSwitchInfo l si (SwitchInfoMap m) = SwitchInfoMap (MapF.insert l si m) lookupSwitchInfo l (SwitchInfoMap m) = MapF.lookup l m #else newtype SwitchInfoMap s blocks = SwitchInfoMap (Map (Some (LambdaLabel s)) (SomeSwitchInfo s blocks)) data SomeSwitchInfo s blocks = forall tp. SomeSwitchInfo (C.TypeRepr tp) (SwitchInfo s blocks tp) mapSomeSI :: (forall tp. SwitchInfo s b tp -> SwitchInfo s b' tp) -> SomeSwitchInfo s b -> SomeSwitchInfo s b' mapSomeSI f (SomeSwitchInfo tp si) = SomeSwitchInfo tp (f si) emptySwitchInfoMap = SwitchInfoMap Map.empty extSwitchInfoMap :: SwitchInfoMap s blocks -> SwitchInfoMap s (blocks ::> args) extSwitchInfoMap (SwitchInfoMap m) = SwitchInfoMap $ fmap (mapSomeSI extSwitchInfo) m insertSwitchInfo l si (SwitchInfoMap m) = SwitchInfoMap $ Map.insert (Some l) (SomeSwitchInfo (typeOfAtom (lambdaAtom l)) si) m lookupSwitchInfo l (SwitchInfoMap m) = case Map.lookup (Some l) m of Nothing -> Nothing Just (SomeSwitchInfo tr si) -> Just $ case testEquality tr (typeOfAtom (lambdaAtom l)) of Just Refl -> si Nothing -> error "Lang.Crucible.SSAConversion.lookupSwitchInfo: type mismatch!" -- | Extend switch target extSwitchInfo :: SwitchInfo s blocks tp -> SwitchInfo s (blocks::>args) tp extSwitchInfo (SwitchInfo b typs a) = SwitchInfo (C.extendBlockID b) typs a #endif extBlockInfo :: BlockInfo ext s ret blocks -> BlockInput ext s (blocks ::> args) ret args -> BlockInfo ext s ret (blocks ::> args) extBlockInfo bi binput = do let blocks' = extBlockInputAssignment $ biBlocks bi let jump_info' = extJumpInfoMap $ biJumpInfo bi let switch_info' = extSwitchInfoMap $ biSwitchInfo bi let breakpoints' = extBreakpoints $ biBreakpoints bi BI { biBlocks = extend blocks' binput , biJumpInfo = jump_info' , biSwitchInfo = switch_info' , biBreakpoints = breakpoints' } ------------------------------------------------------------------------ -- PredMap newtype PredMap ext s ret = PredMap (Map (BlockID s) [Block ext s ret]) instance Show (PredMap ext s ret) where show (PredMap m) = show (fmap blockID <$> m) -- | Return labels that may jump to given label. getPredecessorLabels :: BlockID s -> PredMap ext s ret -> [Block ext s ret] getPredecessorLabels l (PredMap m) = fromMaybe [] (Map.lookup l m) -- | Maps each block to the set of blocks that jump to it. blockPredMap :: [Block ext s ret] -> PredMap ext s ret blockPredMap l = PredMap (Set.toList <$> nextSetMap pairs) where pairs = [ (n, b) | b <- l , n <- fromMaybe [] (termNextLabels (pos_val (blockTerm b))) ] ------------------------------------------------------------------------ -- BlockInputMap type BlockInputMap s = Map (BlockID s) (Set (Input s)) -- | Return inputs expected by block. inputsForBlock :: Block ext s ret -> Set (Input s) inputsForBlock b = Set.map (mkInput (blockID b)) (blockKnownInputs b) -- | Define map that maps labels to the set of registers they need. initialInputMap :: [Block ext s ret] -> BlockInputMap s initialInputMap blocks = Map.fromList $ [ (blockID b, inputsForBlock b) | b <- blocks ] -- | Return map that stores arguments needed by each block. completeInputs :: forall ext s ret . [Block ext s ret] -> BlockInputMap s completeInputs blocks = do let block_map = Map.fromList [ (blockID b, b) | b <- blocks ] -- pred_map maps each label to its predecessors. let pred_map = blockPredMap blocks let go :: Set (BlockID s) -- Set of blocks to revisit. -> BlockInputMap s -- Map from block labels to arguments corresponding block needs at end. -> BlockInputMap s go s0 input_map = case Set.maxView s0 of Nothing -> input_map Just (next_label, rest_labels) -> do let inputs = case Map.lookup next_label input_map of Just i -> i Nothing -> panic "Crucible.CFG.SSAConversion" [ "Unable to get label from input map" ] let resolve_pred :: [Block ext s ret] -> Set (BlockID s) -> BlockInputMap s -> BlockInputMap s resolve_pred [] s m = go s m resolve_pred (prev_block:r) s m = do let prev_label = blockID prev_block -- Get list of inputs already computed for block. let prev_inputs = case Map.lookup prev_label m of Just previ -> previ Nothing -> panic "Crucible.CFG.SSAConversion" [ "Unable to get prev_label from input map" ] -- Compute the inputs needed at the start of prev_block let new_inputs = Set.map (mkInput (blockID prev_block)) $ (`Set.difference` blockAssignedValues prev_block) $ Set.map inputValue inputs let all_inputs = Set.union prev_inputs new_inputs if Set.isSubsetOf new_inputs prev_inputs then resolve_pred r s m else do let m' = Map.insert prev_label all_inputs m resolve_pred r (Set.insert prev_label s) m' let prev_blocks = getPredecessorLabels next_label pred_map resolve_pred prev_blocks rest_labels input_map -- Compute arguments to each block. go (Map.keysSet block_map) (initialInputMap blocks) ------------------------------------------------------------------------ -- Infer information about SSA. -- | Information that is statically inferred from the block structure. data BlockInfo ext s ret blocks = BI { biBlocks :: !(Assignment (BlockInput ext s blocks ret) blocks) , biJumpInfo :: !(JumpInfoMap s blocks) , biSwitchInfo :: !(SwitchInfoMap s blocks) , biBreakpoints :: !(Bimap BreakpointName (Some (C.BlockID blocks))) } -- | This infers the information given a set of blocks. inferBlockInfo :: forall ext s ret . [Block ext s ret] -> Some (BlockInfo ext s ret) inferBlockInfo blocks = seq input_map $ resolveBlocks bi0 blocks where input_map = completeInputs blocks bi0 = BI { biBlocks = empty , biJumpInfo = emptyJumpInfoMap , biSwitchInfo = emptySwitchInfoMap , biBreakpoints = Bimap.empty } resolveBlocks :: BlockInfo ext s ret blocks -> [Block ext s ret] -> Some (BlockInfo ext s ret) resolveBlocks bi [] = Some bi resolveBlocks bi (b:rest) = do let sz = size (biBlocks bi) let untyped_id = blockID b let inputs = case Map.lookup untyped_id input_map of Just i -> i Nothing -> panic "Crucible.CFG.SSAConversion.inferBlockInfo" [ "Unable to get untyped_id from input map" ] case inferRegAssignment inputs of Some ra -> do let crepr = fmapFC typeOfValue ra case untyped_id of LabelID l -> do let block_id = C.BlockID (nextIndex sz) let block_term = (blockTerm b) { pos_val = BaseTermStmt $ pos_val $ blockTerm b } let binput = BInput { binputID = block_id , binputArgs = ra , binputStmts = blockStmts b , binputTerm = block_term } let bi' = extBlockInfo bi binput let ji = JumpInfo block_id crepr ra let bi'' = bi' { biJumpInfo = insertJumpInfo l ji (biJumpInfo bi') } splitLastBlockInputOnBreakpoints bi'' rest LambdaID l -> do let block_id = C.BlockID (nextIndex sz) let lastArg = AtomValue (lambdaAtom l) let block_term = (blockTerm b) { pos_val = BaseTermStmt $ pos_val $ blockTerm b } let binput = BInput { binputID = block_id , binputArgs = ra :> lastArg , binputStmts = blockStmts b , binputTerm = block_term } let bi' = extBlockInfo bi binput let si = SwitchInfo block_id crepr ra let bi'' = bi' { biSwitchInfo = insertSwitchInfo l si (biSwitchInfo bi') } splitLastBlockInputOnBreakpoints bi'' rest splitLastBlockInputOnBreakpoints :: BlockInfo ext s ret blocks -> [Block ext s ret] -> Some (BlockInfo ext s ret) splitLastBlockInputOnBreakpoints bi rest | first_binputs :> last_binput <- biBlocks bi , (first_stmts, break_stmt Seq.:<| last_stmts) <- Seq.breakl isBreakpoint (binputStmts last_binput) , Breakpoint nm args <- pos_val break_stmt = do let block_id = C.BlockID $ nextIndex $ size $ biBlocks bi let first_binputs' = extBlockInputAssignment $ first_binputs let jump_info = JumpInfo block_id (fmapFC typeOfValue args) args let last_binput' = (extBlockInput last_binput) { binputStmts = first_stmts , binputTerm = break_stmt { pos_val = BreakStmt jump_info } } let new_binput = (extBlockInput last_binput) { binputID = block_id , binputArgs = args , binputStmts = last_stmts } let new_breakpoints = do let try_new_breakpoints = Bimap.tryInsert nm (Some block_id) $ extBreakpoints $ biBreakpoints bi if Bimap.pairMember (nm, (Some block_id)) try_new_breakpoints then try_new_breakpoints else error $ "Duplicate breakpoint: " ++ show nm let bi' = BI { biBlocks = first_binputs' :> last_binput' :> new_binput , biJumpInfo = extJumpInfoMap $ biJumpInfo bi , biSwitchInfo = extSwitchInfoMap $ biSwitchInfo bi , biBreakpoints = new_breakpoints } splitLastBlockInputOnBreakpoints bi' rest splitLastBlockInputOnBreakpoints bi rest = resolveBlocks bi rest isBreakpoint :: Posd (Stmt ext s) -> Bool isBreakpoint = \case Posd _ Breakpoint{} -> True _ -> False ------------------------------------------------------------------------ -- Translates from RTL with inference information to SSA. data MaybeF f tp where JustF :: f tp -> MaybeF f tp NothingF :: MaybeF f tp -- | Map each core SSA binding to the expression that generated it if it -- was generated by an expression. type RegExprs ext ctx = Assignment (MaybeF (C.Expr ext ctx)) ctx #ifdef UNSAFE_OPS extendRegExprs :: MaybeF (C.Expr ext ctx) tp -> RegExprs ext ctx -> RegExprs ext (ctx ::> tp) extendRegExprs r e = unsafeCoerce (e :> r) -- | Maps values in mutable representation to the current value in the SSA form. newtype TypedRegMap s ctx = TypedRegMap { _typedRegMap :: MapF (Value s) (C.Reg ctx) } -- | Resolve a register resolveReg :: TypedRegMap s ctx -> Value s tp -> C.Reg ctx tp resolveReg (TypedRegMap m) r = fromMaybe (error msg) (MapF.lookup r m) where msg = "Cannot find (unsafe) reg value " ++ show r ++ " " ++ "in TypedRegMap: " ++ (show m) -- | Resolve an atom resolveAtom :: TypedRegMap s ctx -> Atom s tp -> C.Reg ctx tp resolveAtom (TypedRegMap m) r = fromMaybe (error msg) (MapF.lookup (AtomValue r) m) where msg = "Cannot find (unsafe) atom value " ++ show r ++ "." regMapFromAssignment :: forall s args . Assignment (Value s) args -> TypedRegMap s args regMapFromAssignment a = TypedRegMap $ forIndex (size a) go MapF.empty where go :: MapF (Value s) (C.Reg args) -> Index args tp -> MapF (Value s) (C.Reg args) go m i = MapF.insert (a ! i) (C.Reg i) m extendRegMap :: TypedRegMap s ctx -> TypedRegMap s (ctx ::> tp) extendRegMap = unsafeCoerce -- | Assign existing register to atom in typed RegMap. bindValueReg :: Value s tp -> C.Reg ctx tp -> TypedRegMap s ctx -> TypedRegMap s ctx bindValueReg r cr (TypedRegMap m) = TypedRegMap $ MapF.insert r cr m #else extendRegExprs :: MaybeF (C.Expr ext ctx) tp -> RegExprs ext ctx -> RegExprs ext (ctx ::> tp) extendRegExprs r e = fmapFC ext e :> ext r where ext :: MaybeF (C.Expr ctx) tp' -> MaybeF (C.Expr (ctx ::> tp)) tp' ext NothingF = NothingF ext (JustF (C.App app)) = JustF (C.App (C.mapApp C.extendReg app)) data SomeReg ctx where SomeReg :: C.TypeRepr tp -> C.Reg ctx tp -> SomeReg ctx newtype TypedRegMap s ctx = TypedRegMap { _typedRegMap :: Map (Some (Value s)) (SomeReg ctx) } -- | Resolve a register resolveReg :: TypedRegMap s ctx -> Value s tp -> C.Reg ctx tp resolveReg (TypedRegMap m) r = creg where creg = case Map.lookup (Some r) m of Nothing -> error msg Just (SomeReg tr r') -> case testEquality tr (typeOfValue r) of Nothing -> error msg Just Refl -> r' msg = "Cannot find (safe) reg value " ++ show r -- | Resolve an atom resolveAtom :: TypedRegMap s ctx -> Atom s tp -> C.Reg ctx tp resolveAtom m a = resolveReg m (AtomValue a) regMapFromAssignment :: forall s args . Assignment (Value s) args -> TypedRegMap s args regMapFromAssignment a = TypedRegMap $ forIndex (size a) go Map.empty where go :: Map (Some (Value s)) (SomeReg args) -> Index args tp -> Map (Some (Value s)) (SomeReg args) go m i = let r = a ! i in Map.insert (Some r) (SomeReg (typeOfValue r) (C.Reg i)) m extendRegMap :: TypedRegMap s ctx -> TypedRegMap s (ctx ::> tp) extendRegMap (TypedRegMap m) = TypedRegMap $ fmap (\(SomeReg tr x) -> SomeReg tr (C.extendReg x)) m -- | Assign existing register to atom in typed RegMap. bindValueReg :: Value s tp -> C.Reg ctx tp -> TypedRegMap s ctx -> TypedRegMap s ctx bindValueReg r cr (TypedRegMap m) = TypedRegMap $ Map.insert (Some r) (SomeReg (typeOfValue r) cr) m #endif -- | Assign new register to value in typed reg map. assignRegister :: Value s tp -> Size ctx -> TypedRegMap s ctx -> TypedRegMap s (ctx ::> tp) assignRegister r sz m = bindValueReg r (C.Reg (nextIndex sz)) (extendRegMap m) copyValue :: Value s tp -- ^ Assign -> Value s tp -> TypedRegMap s ctx -> TypedRegMap s ctx copyValue r r' m = bindValueReg r (resolveReg m r') m resolveJumpTarget :: BlockInfo ext s ret blocks -> TypedRegMap s ctx -> Label s -> C.JumpTarget blocks ctx resolveJumpTarget bi reg_map next_lbl = do case lookupJumpInfo next_lbl (biJumpInfo bi) of Nothing -> error "Could not find label in resolveJumpTarget" Just (JumpInfo next_id types inputs) -> do let args = fmapFC (resolveReg reg_map) inputs C.JumpTarget next_id types args -- | Resolve a lambda label into a typed jump target. resolveLambdaAsJump :: BlockInfo ext s ret blocks -> TypedRegMap s ctx -> LambdaLabel s tp -> C.Reg ctx tp -> C.JumpTarget blocks ctx resolveLambdaAsJump bi reg_map next_lbl output = case lookupSwitchInfo next_lbl (biSwitchInfo bi) of Nothing -> error "Could not find label in resolveLambdaAsJump" Just (SwitchInfo block_id types inputs) -> do let types' = types :> typeOfAtom (lambdaAtom next_lbl) let args = fmapFC (resolveReg reg_map) inputs let args' = args `extend` output C.JumpTarget block_id types' args' -- | Resolve a lambda label into a typed switch target. resolveLambdaAsSwitch :: BlockInfo ext s ret blocks -> TypedRegMap s ctx -> LambdaLabel s tp -> C.SwitchTarget blocks ctx tp resolveLambdaAsSwitch bi reg_map next_lbl = case lookupSwitchInfo next_lbl (biSwitchInfo bi) of Nothing -> error "Could not find label in resolveLambdaAsSwitch" Just (SwitchInfo block_id types inputs) -> do let args = fmapFC (resolveReg reg_map) inputs C.SwitchTarget block_id types args -- | Resolve an untyped terminal statement to a typed one. resolveTermStmt :: BlockInfo ext s ret blocks -> TypedRegMap s ctx -> RegExprs ext ctx -- ^ Maps registers to associated expressions. -> ExtendedTermStmt s blocks ret -> C.TermStmt blocks ret ctx resolveTermStmt bi reg_map bindings (BaseTermStmt t0) = case t0 of Jump l -> C.Jump (resolveJumpTarget bi reg_map l) Br c x y -> do let c_r = resolveAtom reg_map c case bindings ! C.regIndex c_r of JustF (C.App (C.BoolLit True)) -> C.Jump (resolveJumpTarget bi reg_map x) JustF (C.App (C.BoolLit False)) -> C.Jump (resolveJumpTarget bi reg_map y) _ -> C.Br c_r (resolveJumpTarget bi reg_map x) (resolveJumpTarget bi reg_map y) MaybeBranch tp e j n -> do let e_r = resolveAtom reg_map e case bindings ! C.regIndex e_r of JustF (C.App (C.JustValue _ je)) -> C.Jump (resolveLambdaAsJump bi reg_map j je) JustF (C.App (C.NothingValue _)) -> C.Jump (resolveJumpTarget bi reg_map n) _ -> C.MaybeBranch tp e_r (resolveLambdaAsSwitch bi reg_map j) (resolveJumpTarget bi reg_map n) VariantElim ctx e s -> do let e_r = resolveAtom reg_map e case bindings ! C.regIndex e_r of JustF (C.App (C.InjectVariant _ idx x)) -> C.Jump (resolveLambdaAsJump bi reg_map (s Ctx.! idx) x) _ -> C.VariantElim ctx e_r (fmapFC (resolveLambdaAsSwitch bi reg_map) s) Return e -> C.Return (resolveAtom reg_map e) TailCall f ctx args -> do C.TailCall (resolveAtom reg_map f) ctx (fmapFC (resolveAtom reg_map) args) ErrorStmt e -> C.ErrorStmt (resolveAtom reg_map e) Output l e -> C.Jump (resolveLambdaAsJump bi reg_map l (resolveAtom reg_map e)) resolveTermStmt _ reg_map _ (BreakStmt (JumpInfo next_id types inputs)) = do let args = fmapFC (resolveReg reg_map) inputs C.Jump $ C.JumpTarget next_id types args #ifdef UNSAFE_OPS type AppRegMap ext ctx = MapF (C.App ext (C.Reg ctx)) (C.Reg ctx) appRegMap_extend :: AppRegMap ext ctx -> AppRegMap ext (ctx ::> tp) appRegMap_extend = unsafeCoerce appRegMap_insert :: ( TraversableFC (C.ExprExtension ext) , OrdFC (C.ExprExtension ext) ) => C.App ext (C.Reg ctx) tp -> C.Reg (ctx ::> tp) tp -> AppRegMap ext ctx -> AppRegMap ext (ctx ::> tp) appRegMap_insert k v m = MapF.insert (fmapFC C.extendReg k) v (appRegMap_extend m) appRegMap_lookup :: ( OrdFC (C.ExprExtension ext) ) => C.App ext (C.Reg ctx) tp -> AppRegMap ext ctx -> Maybe (C.Reg ctx tp) appRegMap_lookup = MapF.lookup appRegMap_empty :: AppRegMap ext ctx appRegMap_empty = MapF.empty #else type AppRegMap ext ctx = Map (Some (C.App ext (C.Reg ctx))) (SomeReg ctx) appRegMap_extend :: AppRegMap ext ctx -> AppRegMap ext (ctx ::> tp) appRegMap_extend = Map.fromList . fmap f . Map.toList where f (Some app, SomeReg tp reg) = (Some (C.mapApp C.extendReg app), SomeReg tp (C.extendReg reg)) appRegMap_insert :: OrdFC (C.ExprExtension ext) => C.App ext (C.Reg ctx) tp -> C.Reg (ctx::>tp) tp -> AppRegMap ext ctx -> AppRegMap ext (ctx ::> tp) appRegMap_insert k v m = Map.insert (Some (C.mapApp C.extendReg k)) (SomeReg (C.appType k) v) (appRegMap_extend m) appRegMap_lookup :: C.App ext (C.Reg ctx) tp -> AppRegMap ext ctx -> Maybe (C.Reg ctx tp) appRegMap_lookup app m = case Map.lookup (Some app) m of Nothing -> Nothing Just (SomeReg tp r) | Just Refl <- testEquality tp (C.appType app) -> Just r _ -> error "appRegMap_lookup: impossible!" appRegMap_empty :: AppRegMap ext ctx appRegMap_empty = Map.empty #endif -- | Resolve a list of statements to a typed list. resolveStmts :: C.IsSyntaxExtension ext => FunctionName -> BlockInfo ext s ret blocks -> Size ctx -> TypedRegMap s ctx -> RegExprs ext ctx -- ^ Maps registers back to the expression that generated them (if any) -> AppRegMap ext ctx -- ^ Maps applications to register that stores their value. -- Used to eliminate redundant operations. -> [Posd (Stmt ext s)] -> Posd (ExtendedTermStmt s blocks ret) -> C.StmtSeq ext blocks ret ctx resolveStmts nm bi _ reg_map bindings _ [] (Posd p t) = do C.TermStmt (mkProgramLoc nm p) (resolveTermStmt bi reg_map bindings t) resolveStmts nm bi sz reg_map bindings appMap (Posd p s0:rest) t = do let pl = mkProgramLoc nm p case s0 of SetReg r a -> do let reg_map' = reg_map & copyValue (RegValue r) (AtomValue a) resolveStmts nm bi sz reg_map' bindings appMap rest t WriteGlobal v a -> do C.ConsStmt pl (C.WriteGlobal v (resolveAtom reg_map a)) (resolveStmts nm bi sz reg_map bindings appMap rest t) WriteRef r a -> do C.ConsStmt pl (C.WriteRefCell (resolveAtom reg_map r) (resolveAtom reg_map a)) (resolveStmts nm bi sz reg_map bindings appMap rest t) DropRef r -> do C.ConsStmt pl (C.DropRefCell (resolveAtom reg_map r)) (resolveStmts nm bi sz reg_map bindings appMap rest t) DefineAtom a av -> do case av of ReadReg r -> do let reg_map' = reg_map & copyValue (AtomValue a) (RegValue r) resolveStmts nm bi sz reg_map' bindings appMap rest t EvalExt estmt -> do let estmt' = fmapFC (resolveAtom reg_map) estmt let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz -- No expression to associate with this value. let bindings' = bindings & extendRegExprs NothingF -- No App to memoize in this case. let appMap' = appMap & appRegMap_extend C.ConsStmt pl (C.ExtendAssign estmt') (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) ReadGlobal v -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz -- No expression to associate with this value. let bindings' = bindings & extendRegExprs NothingF -- No App to memoize in this case. let appMap' = appMap & appRegMap_extend C.ConsStmt pl (C.ReadGlobal v) (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) NewRef v -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz -- No expression to associate with this value. let bindings' = bindings & extendRegExprs NothingF -- No App to memoize in this case. let appMap' = appMap & appRegMap_extend -- Resolve the atom let v' = resolveAtom reg_map v C.ConsStmt pl (C.NewRefCell (typeOfAtom v) v') (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) NewEmptyRef tp -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz -- No expression to associate with this value. let bindings' = bindings & extendRegExprs NothingF -- No App to memoize in this case. let appMap' = appMap & appRegMap_extend -- Resolve the atom C.ConsStmt pl (C.NewEmptyRefCell tp) (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) ReadRef r -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz -- No expression to associate with this value. let bindings' = bindings & extendRegExprs NothingF -- No App to memoize in this case. let appMap' = appMap & appRegMap_extend -- Resolve the atom let r' = resolveAtom reg_map r C.ConsStmt pl (C.ReadRefCell r') (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) EvalApp (fmapFC (resolveAtom reg_map) -> e) | Just cr <- appRegMap_lookup e appMap -> do let reg_map' = bindValueReg (AtomValue a) cr reg_map resolveStmts nm bi sz reg_map' bindings appMap rest t | otherwise -> do let e' = C.App e let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz let bindings' = bindings & extendRegExprs (JustF e') let appMap' = appMap & appRegMap_insert e (C.Reg (nextIndex sz)) let stmt = C.SetReg (typeOfAtom a) e' C.ConsStmt pl stmt (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) FreshConstant bt cnm -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz let bindings' = bindings & extendRegExprs NothingF let appMap' = appMap & appRegMap_extend let stmt = C.FreshConstant bt cnm C.ConsStmt pl stmt (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) FreshFloat fi cnm -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz let bindings' = bindings & extendRegExprs NothingF let appMap' = appMap & appRegMap_extend let stmt = C.FreshFloat fi cnm C.ConsStmt pl stmt (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) FreshNat cnm -> do let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz let bindings' = bindings & extendRegExprs NothingF let appMap' = appMap & appRegMap_extend let stmt = C.FreshNat cnm C.ConsStmt pl stmt (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) Call h args _ -> do let return_type = typeOfAtom a let h' = resolveAtom reg_map h let arg_types = fmapFC typeOfAtom args let args' = fmapFC (resolveAtom reg_map) args let stmt = C.CallHandle return_type h' arg_types args' let sz' = incSize sz let reg_map' = reg_map & assignRegister (AtomValue a) sz let bindings' = bindings & extendRegExprs NothingF let appMap' = appMap & appRegMap_extend C.ConsStmt pl stmt (resolveStmts nm bi sz' reg_map' bindings' appMap' rest t) Print e -> do C.ConsStmt pl (C.Print (resolveAtom reg_map e)) (resolveStmts nm bi sz reg_map bindings appMap rest t) Assert c m -> C.ConsStmt pl (C.Assert (resolveAtom reg_map c) (resolveAtom reg_map m)) (resolveStmts nm bi sz reg_map bindings appMap rest t) Assume c m -> C.ConsStmt pl (C.Assume (resolveAtom reg_map c) (resolveAtom reg_map m)) (resolveStmts nm bi sz reg_map bindings appMap rest t) -- breakpoint statements are eliminated during the inferBlockInfo phase Breakpoint{} -> error $ "Unexpected breakpoint at position " ++ show p ++ ": " ++ show (Pretty.pretty s0) data SomeBlockMap ext ret where SomeBlockMap :: Ctx.Index blocks tp -> Bimap BreakpointName (Some (C.BlockID blocks)) -> C.BlockMap ext blocks ret -> SomeBlockMap ext ret resolveBlockMap :: forall ext s ret . C.IsSyntaxExtension ext => FunctionName -> Label s -> [Block ext s ret] -> SomeBlockMap ext ret resolveBlockMap nm entry blocks = do let resolveBlock :: BlockInfo ext s ret blocks -> BlockInput ext s blocks ret args -> C.Block ext blocks ret args resolveBlock bi bin = do let sz = size (binputArgs bin) let regs = regMapFromAssignment (binputArgs bin) let regExprs = Ctx.replicate sz NothingF let appMap = appRegMap_empty let stmts = Fold.toList $ binputStmts bin let term = binputTerm bin C.Block { C.blockID = binputID bin , C.blockInputs = fmapFC typeOfValue (binputArgs bin) , C._blockStmts = resolveStmts nm bi sz regs regExprs appMap stmts term } case inferBlockInfo blocks of Some bi -> case lookupJumpInfo entry (biJumpInfo bi) of Nothing -> error "Missing initial block." Just (JumpInfo (C.BlockID idx) _ _) -> SomeBlockMap idx (biBreakpoints bi) $ fmapFC (resolveBlock bi) (biBlocks bi) ------------------------------------------------------------------------ -- SomeCFG -- | Convert a CFG in RTL form into a Core CFG in SSA form. -- -- This prunes the CFG so that only reachable blocks are returned. toSSA :: C.IsSyntaxExtension ext => CFG ext s init ret -> C.SomeCFG ext init ret toSSA g = do let h = cfgHandle g let initTypes = cfgArgTypes g let entry = cfgEntryLabel g let blocks = cfgBlocks g case resolveBlockMap (handleName h) entry blocks of SomeBlockMap idx breakpoints block_map -> do let b = block_map ! idx case C.blockInputs b `testEquality` initTypes of Nothing -> error $ "Input block type " ++ show (C.blockInputs b) ++ " does not match expected " ++ show initTypes ++ ":\nwhile SSA converting function " ++ show h Just Refl -> do let g' = C.CFG { C.cfgHandle = h , C.cfgBlockMap = block_map , C.cfgEntryBlockID = C.BlockID idx , C.cfgBreakpoints = breakpoints } reachableCFG g'