packages feed

futhark-0.27.1: src/Language/Futhark/TypeChecker/UnionFind.hs

module Language.Futhark.TypeChecker.UnionFind
  ( TyVarNode,
    TyVarSol (..),
    makeTyVarNode,
    makeTyParamNode,
    find,
    getSol,
    getKey,
    assignNewSol,
    union,
    unionNewSol,
  )
where

import Control.Monad (when)
import Control.Monad.ST (ST)
import Data.STRef
  ( STRef,
    modifySTRef',
    newSTRef,
    readSTRef,
    writeSTRef,
  )
import Language.Futhark (Liftedness, Loc)
import Language.Futhark.TypeChecker.Constraints
  ( CtType,
    Level,
    TyVar,
    TyVarInfo,
  )

type Type = CtType ()

-- | A (partial) solution for a type variable.
data TyVarSol
  = -- | Has been assigned this type.
    Solved Type
  | -- | Is an explicit (rigid) type parameter in the source program.
    Param Level Liftedness Loc
  | -- | Is unsolved but has this constraint.
    Unsolved (TyVarInfo ())
  deriving (Show, Eq)

-- | A node in the union-find graph containing information about a type
-- variable.
newtype TyVarNode s = Node (STRef s (NodeInfo s)) deriving (Eq)

data NodeInfo s
  = Link !(TyVarNode s)
  | Repr !ReprInfo

data ReprInfo = ReprInfo
  { solution :: !TyVarSol,
    key :: !TyVar
  }

-- | Create a fresh node of a type variable and return it. A fresh node
-- is in the equivalence class that contains only itself.
makeTyVarNode :: TyVar -> TyVarInfo () -> ST s (TyVarNode s)
makeTyVarNode tv constraint = do
  let r =
        ReprInfo
          { solution = Unsolved constraint,
            key = tv
          }
  ref <- newSTRef $ Repr r
  pure $ Node ref

-- | Create a fresh node of a type parameter and return it. A fresh node
-- is in the equivalence class that contains only itself.
makeTyParamNode :: TyVar -> Level -> Liftedness -> Loc -> ST s (TyVarNode s)
makeTyParamNode tv lvl lft loc = do
  let r =
        ReprInfo
          { solution = Param lvl lft loc,
            key = tv
          }
  ref <- newSTRef $ Repr r
  pure $ Node ref

-- | @find node@ returns the representative of @node@'s
-- equivalence class and the information associated with
-- this equivalence class.
--
-- This method performs the path compression.
find :: TyVarNode s -> ST s (TyVarNode s, ReprInfo)
find node@(Node ref) = do
  node_info <- readSTRef ref
  case node_info of
    -- Input node is representative.
    Repr repr_info -> pure (node, repr_info)
    -- Input node's parent is another node.
    Link parent -> do
      a@(repr, _) <- find parent
      when (repr /= parent) $
        -- Performing path compression.
        writeSTRef ref $
          Link repr
      pure a

-- | Return the solution associated with the argument node's
-- equivalence class.
getSol :: TyVarNode s -> ST s TyVarSol
getSol node = solution . snd <$> find node

-- | Return the name of the representative type variable.
getKey :: TyVarNode s -> ST s TyVar
getKey node = key . snd <$> find node

-- | Assign a new solution/type to the node's equivalence class.
--
-- Precondition: The node is in an equivalence class representing an
-- unsolved/flexible type variable.
assignNewSol :: TyVarNode s -> TyVarSol -> ST s ()
assignNewSol node new_sol = do
  (Node ref, repr_info) <- find node
  modifySTRef' ref $ const . Repr $ repr_info {solution = new_sol}

-- | Join the equivalence classes of the nodes. The resulting equivalence
-- class has the same solution and key as the second argument.
union :: TyVarNode s -> TyVarNode s -> ST s ()
union n1 n2 = do
  Node ref <- fst <$> find n1
  root2 <- fst <$> find n2

  writeSTRef ref $ Link root2

-- | Join the equivalence classes of the nodes. The resulting equivalence
-- class has the same key as the second argument while @new_sol@ is the
-- new solution.
unionNewSol :: TyVarNode s -> TyVarNode s -> TyVarSol -> ST s ()
unionNewSol n1 n2 new_sol = do
  Node ref1 <- fst <$> find n1
  (root2@(Node ref2), repr_info) <- find n2

  modifySTRef' ref2 $ const . Repr $ repr_info {solution = new_sol}
  writeSTRef ref1 $ Link root2