packages feed

hic-0.0.0.1: src/Language/Cimple/Analysis/Refined/Inference/Substitution.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Strict              #-}
{-# LANGUAGE TupleSections       #-}
module Language.Cimple.Analysis.Refined.Inference.Substitution
    ( substitute
    , substitutePtrTarget
    , substituteReturnType
    , collectRefinableVars
    , refreshInstance
    , refreshSignature
    , register
    ) where

import           Control.Monad                                    (zipWithM)
import           Control.Monad.State.Strict                       (State, get,
                                                                   gets, modify)
import           Data.Hashable                                    (hash)
import qualified Data.IntMap.Strict                               as IntMap
import           Data.Map.Strict                                  (Map)
import qualified Data.Map.Strict                                  as Map
import           Data.Maybe                                       (fromMaybe)
import           Data.Set                                         (Set)
import qualified Data.Set                                         as Set
import           Data.Word                                        (Word32)

import           Language.Cimple.Analysis.Refined.Context
import           Language.Cimple.Analysis.Refined.Inference.Types
import           Language.Cimple.Analysis.Refined.Inference.Utils
import           Language.Cimple.Analysis.Refined.PathContext
import           Language.Cimple.Analysis.Refined.State
import           Language.Cimple.Analysis.Refined.Transition
import           Language.Cimple.Analysis.Refined.Types

register :: AnyRigidNodeF TemplateId Word32 -> State TranslatorState Word32
register (AnyRigidNodeF (RReference ref n o q)) = do
    st <- get
    let isTargetBot i = case Map.lookup i (tsNodes st) of
            Just (AnyRigidNodeF (RTerminal SBottom)) -> True
            _                                        -> False
    let resIsBot = case ref of
            Ptr (TargetObject i) -> isTargetBot i
            Arr e _              -> isTargetBot e
            _                    -> False
    if resIsBot then return 0 -- SBottom
    else do
        nid <- gets tsNextId
        dtraceM ("Registering ID " ++ show nid ++ ": Reference " ++ show (n, o, q))
        modify $ \s -> (addNode nid (AnyRigidNodeF (RReference ref n o q)) s) { tsNextId = nid + 1 }
        return nid
register node = do
    nid <- gets tsNextId
    dtraceM ("Registering ID " ++ show nid ++ ": " ++ show node)
    modify $ \s -> (addNode nid node s) { tsNextId = nid + 1 }
    return nid

substitute :: (TemplateId -> State TranslatorState (Maybe Word32)) -> Word32 -> State TranslatorState Word32
substitute lookupFunc nid = do
    st <- get
    case Map.lookup nid (tsSubstCache st) of
        Just res -> return res
        Nothing -> do
            -- Pre-insert the original ID to terminate recursion.
            -- If we find it again, we haven't finished substituting it yet,
            -- but returning the ID itself is safe as it represents a fixed point
            -- for Equi-recursive types when no substitution is triggered deeper.
            modify $ \s -> s { tsSubstCache = Map.insert nid nid (tsSubstCache s) }

            res <- case Map.lookup nid (tsNodes st) of
                Just (AnyRigidNodeF (RObject s q)) -> do
                    mSubst <- case s of
                        VVar tid _idx | isParameter tid || isRefinable tid -> lookupFunc tid
                        _                                                  -> return Nothing
                    case mSubst of
                        Just actualId -> do
                            dtraceM ("substitute: " ++ show nid ++ " -> " ++ show actualId)
                            return actualId
                        Nothing -> do
                            newS <- case s of
                                VNominal name params -> VNominal name <$> mapM (substitute lookupFunc) params
                                VExistential tids body -> VExistential tids <$> substitute lookupFunc body
                                VVariant m -> VVariant <$> mapM (substitute lookupFunc) m
                                VProperty a pk -> VProperty <$> substitute lookupFunc a <*> pure pk
                                VSizeExpr ts -> VSizeExpr <$> mapM (\(a, c) -> (, c) <$> substitute lookupFunc a) ts
                                _ -> return s
                            if s == newS then return nid
                            else register $ AnyRigidNodeF (RObject newS q)

                Just (AnyRigidNodeF (RReference ref n o q)) -> do
                    newRef <- case ref of
                        Ptr target -> Ptr <$> substitutePtrTarget lookupFunc target
                        Arr e dims -> Arr <$> substitute lookupFunc e <*> mapM (substitute lookupFunc) dims
                    if ref == newRef then return nid
                    else register $ AnyRigidNodeF (RReference newRef n o q)

                Just (AnyRigidNodeF (RFunction args ret)) -> do
                    newArgs <- mapM (substitute lookupFunc) args
                    newRet <- substituteReturnType lookupFunc ret
                    if args == newArgs && ret == newRet then return nid
                    else register $ AnyRigidNodeF (RFunction newArgs newRet)

                _ -> return nid

            modify $ \s -> s { tsSubstCache = Map.insert nid res (tsSubstCache s) }
            return res

substitutePtrTarget :: (TemplateId -> State TranslatorState (Maybe Word32)) -> PtrTarget TemplateId Word32 -> State TranslatorState (PtrTarget TemplateId Word32)
substitutePtrTarget lookupFunc target = case target of
    TargetObject o -> TargetObject <$> substitute lookupFunc o
    TargetFunction args ret -> TargetFunction <$> mapM (substitute lookupFunc) args <*> substituteReturnType lookupFunc ret
    TargetOpaque tid | isRefinable tid -> do
        res <- lookupFunc tid
        case res of
            Just actualId -> do
                st <- get
                case Map.lookup actualId (tsNodes st) of
                    Just (AnyRigidNodeF (RObject (VVar tid' _) _)) -> return $ TargetOpaque tid'
                    _ -> return $ TargetObject actualId
            Nothing -> return target
    _ -> return target

substituteReturnType :: (TemplateId -> State TranslatorState (Maybe Word32)) -> ReturnType Word32 -> State TranslatorState (ReturnType Word32)
substituteReturnType lookupFunc = \case
    RetVal v -> RetVal <$> substitute lookupFunc v
    RetVoid -> return RetVoid

collectRefinableVars :: Word32 -> State TranslatorState (Set TemplateId)
collectRefinableVars nid = do
    modify $ \s -> s { tsSubstCache = Map.empty } -- Use tsSubstCache as a visit set? No, let's just use local state.
    collectRefinableVars' Set.empty nid

collectRefinableVars' :: Set Word32 -> Word32 -> State TranslatorState (Set TemplateId)
collectRefinableVars' visited nid
    | nid `Set.member` visited = return Set.empty
    | otherwise = do
        st <- get
        case Map.lookup nid (tsNodes st) of
            Just (AnyRigidNodeF n) -> foldMapVar (Set.insert nid visited) n
            Nothing                -> return Set.empty
  where
    foldMapVar :: Set Word32 -> RigidNodeF k TemplateId Word32 -> State TranslatorState (Set TemplateId)
    foldMapVar vset = \case
        RObject s _ -> case s of
            VVar tid _ | isParameter tid -> return $ Set.singleton tid
            VNominal _ ps -> Set.unions <$> mapM (collectRefinableVars' vset) ps
            VExistential _ body -> collectRefinableVars' vset body
            VVariant m -> Set.unions <$> mapM (collectRefinableVars' vset) (IntMap.elems m)
            VProperty a _ -> collectRefinableVars' vset a
            VSizeExpr ts -> Set.unions <$> mapM (collectRefinableVars' vset . fst) ts
            _ -> return Set.empty
        RReference r _ _ _ -> case r of
            Ptr t -> case t of
                TargetObject o -> collectRefinableVars' vset o
                TargetFunction args ret -> do
                    as <- mapM (collectRefinableVars' vset) args
                    rs <- case ret of { RetVal v -> collectRefinableVars' vset v; RetVoid -> return Set.empty }
                    return $ Set.unions (rs:as)
                TargetOpaque tid | isRefinable tid -> return $ Set.singleton tid
                _ -> return Set.empty
            Arr e dims -> do
                es <- collectRefinableVars' vset e
                ds <- mapM (collectRefinableVars' vset) dims
                return $ Set.unions (es:ds)
        RFunction args ret -> do
            as <- mapM (collectRefinableVars' vset) args
            rs <- case ret of { RetVal v -> collectRefinableVars' vset v; RetVoid -> return Set.empty }
            return $ Set.unions (rs:as)
        RTerminal _ -> return Set.empty

refreshVars :: (Word32 -> Word32 -> TemplateId) -> [TemplateId] -> State TranslatorState (Map TemplateId Word32)
refreshVars mkTid vars = do
    st <- get
    let nextId = tsNextId st
    Map.fromList <$> zipWithM (\i tid -> do
        nodeId <- register $ AnyRigidNodeF (RObject (VVar (mkTid nextId (fromIntegral i)) Nothing) (Quals False))
        return (tid, nodeId)
        ) [0..length vars - 1] vars

refreshInstance :: Word32 -> State TranslatorState Word32
refreshInstance nid = do
    vars <- collectRefinableVars nid
    nid' <- if Set.null vars then return nid
    else do
        let varList = Set.toList vars
        freshMap <- refreshVars (\nextId i -> TIdInstance (toInteger (nextId + i))) varList

        modify $ \s -> s { tsSubstCache = Map.empty }
        let lookupFunc tid = return $ Map.lookup tid freshMap
        substitute lookupFunc nid

    st' <- get
    case Map.lookup nid' (tsNodes st') of
        Just (AnyRigidNodeF (RTerminal _)) -> return nid'
        _ -> do
            freshId <- gets tsNextId
            modify $ \s -> s { tsNextId = freshId + 1 }
            let tid = TIdInstance (toInteger freshId)
            let node = AnyRigidNodeF (RObject (VVar tid Nothing) (Quals False))
            modify (addNode freshId node)
            modify (addConstraint (PathContext Map.empty Map.empty) freshId nid')
            return freshId


refreshSignature :: [Word32] -> ReturnType Word32 -> State TranslatorState ([Word32], ReturnType Word32, Map Word32 Word32)
refreshSignature params ret = do
    let allIds = params ++ case ret of { RetVal v -> [v]; RetVoid -> [] }
    vars <- Set.unions <$> mapM collectRefinableVars allIds
    dtraceM ("refreshSignature: allIds=" ++ show allIds ++ ", refinableVars=" ++ show vars)
    if Set.null vars then return (params, ret, Map.empty)
    else do
        st <- get
        let varList = Set.toList vars
        let varToNode = Map.fromList [ (tid, nid) | (nid, AnyRigidNodeF (RObject (VVar tid _) _)) <- Map.toList (tsNodes st), tid `Set.member` vars ]

        let h = fromIntegral (hash allIds)
        freshMap <- refreshVars (\nextId i -> TIdSkolem h h (nextId + i)) varList

        modify $ \s -> s { tsSubstCache = Map.empty }
        let lookupFunc tid = return $ Map.lookup tid freshMap
        params' <- mapM (substitute lookupFunc) params
        ret' <- substituteReturnType lookupFunc ret

        let nodeMapping = Map.fromList [ (origId, freshId) | (tid, freshId) <- Map.toList freshMap, Just origId <- [Map.lookup tid varToNode] ]
        dtraceM ("refreshSignature: nodeMapping=" ++ show nodeMapping)

        return (params', ret', nodeMapping)