packages feed

fir-0.1.0.0: src/CodeGen/Applicative.hs

{-# LANGUAGE AllowAmbiguousTypes    #-}
{-# LANGUAGE BlockArguments         #-}
{-# LANGUAGE ConstraintKinds        #-}
{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE DerivingVia            #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE GADTs                  #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE OverloadedStrings      #-}
{-# LANGUAGE PatternSynonyms        #-}
{-# LANGUAGE PolyKinds              #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE StandaloneDeriving     #-}
{-# LANGUAGE TypeApplications       #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators          #-}
{-# LANGUAGE UndecidableInstances   #-}
{-# LANGUAGE ViewPatterns           #-}

{-|
Module: CodeGen.Applicative

Code generation for (functor and) applicative operations:

  * vectors use vectorised instructions (whenever possible),
  * matrices use vectorised operations on their columns (whenever possible),
  * arrays use loops.

-}

module CodeGen.Applicative ( ) where

-- base
import Prelude
  hiding ( Num(..), Integral(..)
         , Fractional(..), Floating(..), RealFloat(..)
         )
import qualified Prelude
import Data.Foldable
  ( toList )
import Data.Kind
  ( Type )
import Data.Proxy
  ( Proxy(Proxy) )
import Data.Type.Equality
  ( (:~:)(Refl) )
import Data.Word
  ( Word32 )
import GHC.TypeNats
  ( Nat, KnownNat )
import Unsafe.Coerce
  ( unsafeCoerce )

-- lens
import Control.Lens
  ( assign )

-- mtl
import Control.Monad.Except
  ( throwError )

-- text-short
import qualified Data.Text.Short as ShortText
  ( pack )

-- variant
import Data.Variant.VariantF
  ( VariantF, ApplyAll, BottomUp(toBottomUp) )
import Data.Variant.EGADT
  ( EGADT(..), HVariantF(..) )

-- fir
import CodeGen.Application
  ( Application(Applied)
  , ASTs(NilAST, ConsAST)
  , pattern SnocAST
  , pattern Nullary
  )
import CodeGen.Array
  ( createArray )
import {-# SOURCE #-} CodeGen.CodeGen
  ( CodeGen(codeGenArgs), codeGen )
import CodeGen.Composite
  ( compositeConstruct )
import CodeGen.Instruction
  ( ID, Pointer(..) )
import CodeGen.Monad
  ( CGMonad )
import CodeGen.Pointers
  ( newVariable )
import CodeGen.State
  ( _localBinding, scoped )
import Control.Type.Optic
  ( AnIndex
  , ReifiedGetter(view)
  )
import Data.Constraint.All
  ( All(allDict), AllDict(NilDict, ConsDict) )
import Data.Type.Known
  ( knownValue )
import Data.Type.List
  ( Snoc )
import FIR.AST
  ( Code, AST
  , Syntactic(fromAST)
  , primOp
  , AppF(..), LitF(..)
  , ApplicativeF(..)
  , PrimOpF(..), MkVectorF(..)
  , UnsafeCoerceF(..)
  , pattern (:$), pattern Lam, pattern Pure, pattern MkID
  , pattern MkVector, pattern UnMat
  , pattern UnsafeCoerce, pattern Lit
  )
import FIR.AST.Type
  ( AugType(Val,(:-->)), UnderlyingType
  , FunArgs, FunRes, ApplyFun
  , ApplyFAug, MapApplyFAug
  )
import FIR.Prim.Array
  ( Array )
import FIR.Prim.Op
  ( PrimOp(..), SPrimOp(SMul)
  , Vectorise(Vectorise), VecPrimOpType(VecPrimOpType)
  , PrimTyVal
  )
import FIR.Prim.Types
  ( PrimTy, primTy
  , IntegralTy
  , PrimFunc(primFuncSing, distDict), DistDict(DistDict)
  , SPrimFunc(..)
  )
import FIR.Syntax.AST
  ( ) -- 'PrimFunc' instances, optic instances
import Math.Algebra.Class
  ( Semiring((*)), Integral )
import Math.Linear
  ( V, M, Semimodule((*^)) )
import qualified SPIRV.PrimTy  as SPIRV

----------------------------------------------------------------------------
-- Dispatch code-generation on AST constructors.

instance CodeGen AST => CodeGen (ApplicativeF AST) where

  codeGenArgs ( Applied ( ApF pxf (Pure _ f :: AST (Val (f a) :--> ApplyFAug f b )) (Pure _ a :: AST (Val (f a))) ) as )
    | ( _ :: AST t ) <- f
    , ( _ :: AST v ) <- a
    , Refl <- ( unsafeCoerce Refl :: t :~: ( Val a :--> b ) )
    , Refl <- ( unsafeCoerce Refl :: v :~: Val a )
    , ConsDict <- allDict @PrimTyVal @(Val a ': FunArgs b)
    = codeGenArgs ( Applied ( PureF pxf (f :$ a) ) as )
  codeGenArgs ( Applied ( ApF ( _ :: Proxy f) f a ) as ) =
    codeGenArgs ( Applied f (a `ConsAST` as) )

  codeGenArgs ( Applied (PureF ( _ :: Proxy f) (a :: AST a)) (as :: ASTs fas) )
    | Refl <- ( unsafeCoerce Refl :: ApplyFun a fas :~: Val (UnderlyingType (FunRes a)) )
    , Refl <- ( unsafeCoerce Refl :: fas :~: MapApplyFAug f (FunArgs a) )
    = idiom (makeIdiom @f @a @(FunArgs a) a as)


makeIdiom
  :: forall f a as fas
  .  ( fas ~ MapApplyFAug f as, All PrimTyVal as )
  => AST a -> ASTs fas -> Idiom f as (ApplyFun a fas)
makeIdiom v NilAST
  | Refl <- ( unsafeCoerce Refl :: ApplyFun a fas :~: a )
  , Refl <- ( unsafeCoerce Refl :: as :~: '[] )
  = PureIdiom v
makeIdiom h as@(ConsAST _ _) = case snocDict ( allDict @PrimTyVal @as ) of
  sDict@SnocDict
    | ( _ :: SnocDict PrimTyVal (init_as `Snoc` last_as) ) <- sDict
    , ( _ :: Proxy init_fas ) <- Proxy @( MapApplyFAug f init_as )
    , Refl <- ( unsafeCoerce Refl :: fas :~: ( init_fas `Snoc` (ApplyFAug f last_as) ) )
    , ( bs `SnocAST` b ) <- as
    , ( nxt :: Idiom f init_as (ApplyFun a init_fas ) ) <- makeIdiom h bs
    , Refl <- ( unsafeCoerce Refl :: ApplyFun a init_fas :~: ( Val (UnderlyingType last_as) :--> ApplyFun a fas ) )
    -> ApIdiom nxt b

data Idiom (f :: Type -> Type) (as :: [AugType]) (b :: AugType) where
  PureIdiom :: forall f b. AST b -> Idiom f '[] b
  ApIdiom   :: PrimTy a => Idiom f as (Val a :--> b) -> AST (Val (f a)) -> Idiom f (as `Snoc` (Val a)) b
deriving stock instance Show (Idiom f as b)

data SnocDict c as where
  NilSnocDict :: SnocDict c '[]
  SnocDict    :: (All c as, c a) => SnocDict c (as `Snoc` a)

snocDict :: forall c as. AllDict c as -> SnocDict c as
snocDict NilDict = NilSnocDict
snocDict consDict@ConsDict = case consDict of
  ( _ :: AllDict c (b ': bs) ) -> case snocDict (allDict @c @bs) of
    NilSnocDict -> SnocDict
    sDict@SnocDict -> case sDict of
      ( _ :: SnocDict c (xs `Snoc` x ) )
        | Refl <- ( unsafeCoerce Refl :: as :~: ( (b ': xs) `Snoc` x) )
        -> SnocDict

----------------------------------------------------------------------------
-- Code-generation of an idiomatic expression.

idiom
  :: forall f bs r
  .  ( CodeGen AST, PrimFunc f, PrimTy r )
  => Idiom f bs (Val r) -> CGMonad ( ID, SPIRV.PrimTy )
idiom (PureIdiom b) = do
  elt@(eltID, eltTy) <- codeGen b
  case primFuncSing @f of
    sFuncVector@SFuncVector ->
      case sFuncVector of
        ( _ :: SPrimFunc (V n) ) ->
          let n :: Prelude.Num t => t
              n = Prelude.fromIntegral (knownValue @n)
          in compositeConstruct
               ( SPIRV.Vector n eltTy )
               ( replicate n eltID )
    sFuncMatrix@SFuncMatrix ->
      case sFuncMatrix of
        ( _ :: SPrimFunc (M m n) ) ->
          do
            constituentTy <-
              case eltTy of
                SPIRV.Scalar ty -> pure ty
                ty -> throwError 
                        ( "codeGen: matrix contains non-scalars of type "
                         <> ShortText.pack (show ty)
                        )
            let colDim :: Word32
                colDim = knownValue @m
                rowDim :: Prelude.Num t => t
                rowDim = Prelude.fromIntegral (knownValue @n)
            col <- fst <$> idiom ( PureIdiom @(V m) (MkID elt :: Code r) )
            compositeConstruct
              ( SPIRV.Matrix colDim rowDim constituentTy )
              ( replicate rowDim col )
    sFuncArray@SFuncArray ->
      case sFuncArray of
          ( _ :: SPrimFunc (Array n) ) ->
            let n :: Prelude.Num t => t
                n = Prelude.fromIntegral (knownValue @n)
            in compositeConstruct
                 ( SPIRV.Array n eltTy )
                 ( replicate n eltID )
idiom apIdiom@(ApIdiom idi a) =
  case primFuncSing @f of
    sFuncVector@SFuncVector ->
      case sFuncVector of
        ( _ :: SPrimFunc (V n) ) ->
          case applyIdiomV @n apIdiom of
            -- attempt to directly vectorise operation if possible
            Just res -> codeGen res
            -- otherwise, revert to doing it component by component
            _ -> do 
              ids <- toList <$> traverse codeGen (applyIdiom idi a)
              eltTy <- case ids of
                  ((_, ty) : _) -> pure ty
                  _             -> throwError "codeGen: empty vector"
              compositeConstruct
                ( SPIRV.Vector (knownValue @n) eltTy )
                ( map fst ids )
    sFuncMatrix@SFuncMatrix ->
      case sFuncMatrix of
        ( _ :: SPrimFunc (M m n) ) ->
          let idiom_cols :: V n (Idiom (V m) bs (Val r))
              idiom_cols = distributeMatrixIdiom apIdiom
          in do
               cols <- toList <$> traverse idiom idiom_cols
               eltTy <- case cols of
                 ((_, SPIRV.Vector _ (SPIRV.Scalar ty)) : _)
                     -> pure ty
                 _   -> throwError "codeGen: invalid matrix type, columns are not vectors of scalars"
               compositeConstruct
                 ( SPIRV.Matrix (knownValue @m) (knownValue @n) eltTy )
                 ( map fst cols )
    sFuncArray@SFuncArray ->
      case sFuncArray of
        ( _ :: SPrimFunc (Array n) ) ->
          let arrayFunction = applyIdiomArray @n @Word32 idi a
              arrayLg = knownValue @n
              eltTy = primTy @r
          in do
            Pointer resArrPtrID resArrPtrTy <- newVariable ( SPIRV.Array arrayLg eltTy )
            ( res, _ ) <- scoped do
              assign ( _localBinding "__tmp_array"      ) ( Just ( resArrPtrID, SPIRV.Pointer resArrPtrTy ) )
              assign ( _localBinding "__tmp_arrayIndex" ) Nothing
              codeGen ( createArray @n @"__tmp_array" @"__tmp_arrayIndex" @r arrayFunction )
            return res


applyIdiom
  :: forall f as a b
  .  ( PrimFunc f, PrimTy a )
  => Idiom f as (Val a :--> b) -> AST (Val (f a)) -> f (AST b)
applyIdiom i a
  | DistDict <- distDict @f @(AST (Val a))
  = case i of
    PureIdiom f    ->   fromAST f                     <$> ( fromAST a :: f (AST (Val a)) )
    ApIdiom   f a' -> ( fromAST <$> applyIdiom f a' ) <*> ( fromAST a :: f (AST (Val a)) )

distributeMatrixIdiom
  :: forall m n as b.
     ( KnownNat m, KnownNat n )
  => Idiom (M m n) as b -> V n (Idiom (V m) as b)
distributeMatrixIdiom (PureIdiom f)
  = fmap PureIdiom . pure @(V n) $ f
distributeMatrixIdiom (ApIdiom f a)
  = ApIdiom <$> distributeMatrixIdiom f <*> fromAST ( UnMat :$ a )


applyIdiomArray
  :: forall n ix as a b
  .  ( KnownNat n
     , Integral ix, IntegralTy ix
     , PrimTy a
     )
  => Idiom (Array n) as (Val a :--> b) -> Code (Array n a) -> ( Code ix -> AST b )
applyIdiomArray (PureIdiom f) arr i =
  fromAST f (view @(AnIndex (Code ix)) i arr)
applyIdiomArray (ApIdiom f arr') arr i =
  let f' :: Code a -> AST b
      f' = fromAST ( applyIdiomArray f arr' i )
  in f' (view @(AnIndex (Code ix)) i arr)

---------------------------------------------------------------------------------------
-- awful hacks to use native SPIR-V vectorised instructions if possible

newtype FakeScalar n a = FakeScalar a
  deriving stock Show

type family Vectorisation (n :: Nat) (a :: AugType) = (r :: AugType) | r -> a where
  Vectorisation n (Val a :--> b) = Val (FakeScalar n a) :--> Vectorisation n b
  Vectorisation n (Val b)        = Val b

applyIdiomV
  :: forall n as b
  .  ( KnownNat n, PrimTy b )
  => Idiom (V n) as (Val b) -> Maybe (Code (V n b))
applyIdiomV idi = sanitiseVectorisation @n ( unsafeVectorise idi )

unsafeVectorise :: forall n as b. Idiom (V n) as b -> AST (Vectorisation n b)
unsafeVectorise (PureIdiom b) = unsafeCoerce b
unsafeVectorise (ApIdiom f (v :: Code (V n a)))
  = fromAST
      ( unsafeVectorise f )
      ( ( UnsafeCoerce :$ v ) :: AST (Val (FakeScalar n a)) )

class KnownNat n => SanitiseVectorisation (n :: Nat) (ast :: AugType -> Type) where
  sanitiseVectorisationArgs :: PrimTy r => Application ast f (Val r) -> Maybe (Code (V n r))

sanitiseVectorisation
  :: forall n r ast
  .  ( SanitiseVectorisation n ast, PrimTy r )
  => ast (Val r) -> Maybe (Code (V n r))
sanitiseVectorisation r = sanitiseVectorisationArgs (Nullary r)

instance ( KnownNat n, BottomUp (SanitiseVectorisation n) vs ) => SanitiseVectorisation n (VariantF vs) where
  sanitiseVectorisationArgs (Applied f args) =
    toBottomUp @(SanitiseVectorisation n) ( \x -> sanitiseVectorisationArgs $ Applied x args ) f

deriving via VariantF ( ApplyAll (EGADT vs :: AugType -> Type) vs )
  instance ( KnownNat n, BottomUp (SanitiseVectorisation n) ( ApplyAll (EGADT vs) vs ) )
        => SanitiseVectorisation n (EGADT vs)

instance SanitiseVectorisation n AST => SanitiseVectorisation n (AppF AST) where
  sanitiseVectorisationArgs (Applied (AppF (Lam f) a) as) = sanitiseVectorisationArgs (Applied (f a) as)
  sanitiseVectorisationArgs (Applied (AppF f a) as) = sanitiseVectorisationArgs (Applied f (a `ConsAST` as))

instance {-# OVERLAPPABLE #-} KnownNat n => SanitiseVectorisation n ast where
  sanitiseVectorisationArgs = const Nothing

instance KnownNat n => SanitiseVectorisation n (MkVectorF AST) where
  sanitiseVectorisationArgs ( Applied (MkVectorF vec) NilAST )
    = Just ( unsafeCoerce $ MkVector vec )

instance KnownNat n => SanitiseVectorisation n (UnsafeCoerceF AST) where
  sanitiseVectorisationArgs ( Applied UnsafeCoerceF (vec `ConsAST` NilAST) )
    = Just ( unsafeCoerce vec )

instance KnownNat n => SanitiseVectorisation n (LitF AST) where
  sanitiseVectorisationArgs ( Applied (LitF a) NilAST )
    = Just ( Pure (Proxy @(V n)) (Lit ( unsafeCoerce a)) )

instance SanitiseVectorisation n AST => SanitiseVectorisation n (PrimOpF AST) where
  sanitiseVectorisationArgs ( Applied (PrimOpF ( _ :: Proxy a ) ( _ :: Proxy op )) as )
    -- special case for 'vector times scalar' optimisation
    |  Just smul@SMul <- opSing @_ @_ @op @a
    , ( _ :: SPrimOp a mul_op ) <- smul
    , ( v1 `ConsAST` v2 `ConsAST` NilAST ) <- as
    = case ( sanitiseVectorisation @n v1, sanitiseVectorisation @n v2 ) of
      ( Just (Pure _ (b1 :: AST vb1)), Just (Pure _ (b2 :: AST vb2)) )
        | Refl <- ( unsafeCoerce Refl :: vb1 :~: Val (UnderlyingType vb1) )
        , Refl <- ( unsafeCoerce Refl :: vb2 :~: Val (UnderlyingType vb2) )
        , ( _ :: Proxy b1 ) <- Proxy @(UnderlyingType vb1)
        , ( _ :: Proxy b2 ) <- Proxy @(UnderlyingType vb2)
        -> let b1b2 :: Code a
               b1b2 = unsafeCoerce b1 * unsafeCoerce b2
               b1b2' :: Code b1
               b1b2' = unsafeCoerce b1b2
           in Just $ Pure ( Proxy @(V n) ) b1b2'
      ( Just (Pure _ b1), Just sv2 )
        -> let b1' :: Code a
               b1' = unsafeCoerce b1
               sv2' :: Code (V n a)
               sv2' = unsafeCoerce sv2
           in Just $ b1' *^ sv2'
      ( Just sv1, Just (Pure _ b2) )
        -> let sv1' :: Code (V n a)
               sv1' = unsafeCoerce sv1
               b2' :: Code a
               b2' = unsafeCoerce b2
           in Just $ b2' *^ sv1'
      ( Just sv1, Just sv2 )
        -> let
              b1, b2 :: Code a
              b1 = unsafeCoerce sv1
              b2 = unsafeCoerce sv2
           in applyvPrimOp
                ( primOp @(V n a) @('Vectorise mul_op) )
                ( b1 `ConsAST` b2 `ConsAST` NilAST )
      _ -> Nothing
    | Just ( VecPrimOpType ( _ :: Proxy v_a) ) <- vectorisation @_ @_ @op @a @n
    , ( _ :: Proxy f  ) <- Proxy @(PrimOpAugType op a)
    , ( _ :: Proxy vf ) <- Proxy @(PrimOpAugType ('Vectorise op) v_a)
    , Refl <- ( unsafeCoerce Refl :: FunRes f :~: Val (UnderlyingType (FunRes f)) )
    , Refl <- ( unsafeCoerce Refl :: ApplyFAug (V n) f :~: vf )
    = applyvPrimOp @n @f @(UnderlyingType (FunRes f)) ( primOp @v_a @('Vectorise op) ) as
    | otherwise
    = Nothing

applyvPrimOp
  :: forall (n :: Nat) f r
  .  ( KnownNat n, FunRes f ~ Val r, All PrimTyVal (FunArgs f) )
  => AST (ApplyFAug (V n) f) -> ASTs (FunArgs f) -> Maybe (Code (V n r))
applyvPrimOp vPrimOp as = case allDict @PrimTyVal @(FunArgs f) of
  NilDict
    | Refl <- ( unsafeCoerce Refl :: f :~: Val (UnderlyingType f) )
    -> Just vPrimOp
  ConsDict -> case as of
    ( ( x :: AST (Val x) ) `ConsAST` xs )
      | Refl <- ( unsafeCoerce Refl :: f :~: ( Val x :--> SkipFirstArg f ) )
      , Just v <- sanitiseVectorisation @n x
      -> applyvPrimOp ( vPrimOp :$ v ) xs
    _ -> Nothing

type family SkipFirstArg (x :: AugType) :: AugType where
  SkipFirstArg (_ :--> b) = b