packages feed

fir-0.1.0.0: src/FIR/AST/Effs.hs

{-# OPTIONS_GHC -fno-warn-missing-pattern-synonym-signatures -Wno-missing-signatures #-}

{-# LANGUAGE BlockArguments      #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE PatternSynonyms     #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeOperators       #-}

{-|
Module: FIR.AST.Effs

Effectful operations of the AST:
  * defining new variables/functions/entry-points,
  * management of indexed state.
-}

module FIR.AST.Effs where

-- base
import Data.Kind
  ( Type )
import Data.Proxy
  ( Proxy )
import qualified GHC.Stack
  ( HasCallStack )
import GHC.TypeLits
  ( Symbol, KnownSymbol, symbolVal )
import GHC.TypeNats
  ( Nat )

-- variant
import Data.Variant.EGADT
  ( pattern VF )

-- fir
import Data.Constraint.All
  ( All )
import Data.Type.Known
  ( Known, knownValue )
import FIR.AST.Display
  ( Display(toTreeArgs), named )
import FIR.AST.Type
  ( AugType(Val, (:-->)), Eff, FunArgs, Nullary )
import FIR.Binding
  ( BindingsMap
  , FunctionAugType, Var, Permissions
  )
import FIR.Prim.Types
  ( PrimTy, KnownVars )
import FIR.ProgramState
  ( ProgramState, TLInterface )
import FIR.Validation.Bindings
  ( ValidDef, ValidFunDef, FunctionTypes
  , AddBinding, AddFunBinding
  , FunctionDefinitionStartState, FunctionDefinitionEndState
  , ValidEntryPoint, SetInterface
  , GetExecutionInfo
  , EntryPointStartState, EntryPointEndState
  , Embeddable
  )
import qualified SPIRV.Stage  as SPIRV
  ( ExecutionModel, ExecutionInfo )

------------------------------------------------------------

pattern Let                     = VF LetF
pattern Def           k ps      = VF ( DefF           k ps )
pattern FunDef        name as b = VF ( FunDefF        name as b )
pattern FunCall       name as b = VF ( FunCallF       name as b )
pattern DefEntryPoint name info = VF ( DefEntryPointF name info )
pattern Locally                 = VF LocallyF
pattern Embed                   = VF EmbedF

-- | Let binding.
data LetF ( ast :: AugType -> Type ) ( t :: AugType ) where
  LetF :: GHC.Stack.HasCallStack => LetF ast ( Val a :--> Eff i i a )

-- | Defining a new constant/variable.
data DefF ( ast :: AugType -> Type ) ( t :: AugType ) where
  DefF
    :: forall
        ( k   :: Symbol           )
        ( ps  :: Permissions      )
        ( a   :: Type             )
        ( i   :: ProgramState     )
        ( ast :: AugType -> Type  )
    . ( GHC.Stack.HasCallStack
      , KnownSymbol k
      , Known Permissions ps
      , PrimTy a
      , ValidDef k i a
      )
    => Proxy k  -- ^ Variable name.
    -> Proxy ps -- ^ Permissions (read,write,...).
    -> DefF ast ( Val a :--> Eff i (AddBinding k (Var ps a) i) a )

-- | Defining a new function.
--
-- Meaning of type variables:
-- * name: function name,
-- * as: named function arguments (usually inferred),
-- * b: function return type (usually inferred),
-- * j_bds: bindings state at end of function definition (usually inferred),
-- * i: monadic state at function definition site (usually inferred).
data FunDefF ( ast :: AugType -> Type ) ( t :: AugType ) where
  FunDefF
    :: forall
         ( name  :: Symbol          )
         ( as    :: BindingsMap     )
         ( b     :: Type            )
         ( j_bds :: BindingsMap     )
         ( i     :: ProgramState    )
         ( ast   :: AugType -> Type )
    . ( GHC.Stack.HasCallStack
      , KnownSymbol name
      , KnownVars as
      , PrimTy b
      , ValidFunDef name as i j_bds
      , '(as,b) ~ FunctionTypes name i
      )
    => Proxy name -- ^ Funtion name.
    -> Proxy as   -- ^ Function argument types.
    -> Proxy b    -- ^ Function return type.
    -> FunDefF ast
        ( Eff 
          ( FunctionDefinitionStartState name as       i )
          ( FunctionDefinitionEndState   name as j_bds i )
          b
        :--> Eff i ( AddFunBinding name as b i ) ( FunctionHandle name as b )
        )

data FunctionHandle (name :: Symbol) (as :: BindingsMap) (b :: Type)

data FunCallF ( ast :: AugType -> Type ) ( t :: AugType ) where
  FunCallF 
    :: ( KnownSymbol name
       , KnownVars as
       , PrimTy b
       , All Nullary (FunArgs (FunctionAugType as b))
       )
    => Proxy name
    -> Proxy as
    -> Proxy b
    -> FunCallF ast ( Val (FunctionHandle name as b) :--> FunctionAugType as b )

-- | Defining a new entry point.
--
-- An entry point is like a function definition with no arguments and Unit return type.
-- Code within an entry point is given access to additional builtins.
--
-- Meaning of type variables:
-- * name: entry point name,
-- * stage: entry point stage,
-- * stageInfo: entry point stage info (usually inferred),
-- * j_bds: bindings state at end of entry point definition (usually inferred),
-- * j_iface: entry point interface (usually inferred),
-- * i: monadic state at entry point definition site (usually inferred).
data DefEntryPointF ( ast :: AugType -> Type ) ( t :: AugType ) where
  DefEntryPointF
    :: forall
        ( name      :: Symbol )
        ( stage     :: SPIRV.ExecutionModel          )
        ( stageInfo :: SPIRV.ExecutionInfo Nat stage )
        ( j_bds     :: BindingsMap  )
        ( j_iface   :: TLInterface  )
        ( i         :: ProgramState )
        ( ast       :: AugType -> Type )
    . ( GHC.Stack.HasCallStack
      , KnownSymbol name
      , Known SPIRV.ExecutionModel stage
      , Known (SPIRV.ExecutionInfo Nat stage) stageInfo
      , ValidEntryPoint name stageInfo i j_bds
      , stageInfo ~ GetExecutionInfo name stage i
      )
    => Proxy name      -- ^ Entry point name.
    -> Proxy stageInfo -- ^ Entry point stage info.
    -> DefEntryPointF ast
        (  Eff 
          ( EntryPointStartState name stageInfo               i )
          ( EntryPointEndState   name stageInfo j_bds j_iface i )
          ()
        :--> ( Eff i ( SetInterface name stageInfo j_iface i ) () )
        )


data StateF ( ast :: AugType -> Type ) ( t :: AugType ) where
  -- | Encapsulate local state.
  LocallyF :: StateF ast ( Eff i j a :--> Eff i i a )

  -- | Embed a computation into one with larger state.
  EmbedF :: Embeddable i j => StateF ast ( Eff i i a :--> Eff j j a )

------------------------------------------------------------
-- displaying

instance Display (LetF ast) where
  toTreeArgs = named \ LetF -> "Let"
instance Display (DefF ast) where
  toTreeArgs = named \(DefF k _) ->
    "Def @" ++ symbolVal k
instance Display (FunDefF ast) where
  toTreeArgs = named \(FunDefF k _ _) ->
    "FunDef @" ++ symbolVal k
instance Display (FunCallF ast) where
  toTreeArgs = named \(FunCallF k _ _) ->
    "FunCall @" ++ symbolVal k
instance Display (DefEntryPointF ast) where
  toTreeArgs = named \(DefEntryPointF _ (_ :: Proxy (stageInfo :: SPIRV.ExecutionInfo Nat stage) )) ->
    "Entry @(" ++ show (knownValue @stage) ++ ")"
instance Display (StateF ast) where
  toTreeArgs = named \ case
    LocallyF -> "Locally"
    EmbedF   -> "Embed"