fir-0.1.0.0: src/CodeGen/Composite.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module: CodeGen.Composite
Code generation for composite objects (vectors, matrices, arrays and structures): construction and extraction.
-}
module CodeGen.Composite
( compositeConstruct
, compositeInsert, compositeExtract
, accessedTy, accessedLaidOutTy
, vectorShuffle, vectorSwizzle
, productConstruct
)
where
-- base
import Data.Foldable
( toList )
import Data.Proxy
( Proxy )
import Data.Word
( Word32 )
import GHC.TypeNats
( natVal )
-- mtl
import Control.Monad.Except
( throwError )
-- split
import Data.List.Split
( chunksOf )
-- text-short
import Data.Text.Short
( ShortText )
import qualified Data.Text.Short as ShortText
( pack )
-- vector-sized
import qualified Data.Vector.Sized as Vector
( toList, knownLength' )
-- fir
import CodeGen.Application
( Application(Applied)
, pattern Nullary
, ASTs(NilAST, ConsAST)
)
import CodeGen.Binary
( instruction )
import {-# SOURCE #-} CodeGen.CodeGen
( CodeGen(codeGenArgs), codeGen )
import CodeGen.Debug
( whenAsserting )
import CodeGen.IDs
( bindingID, typeID, undefID )
import CodeGen.Instruction
( Args(..), toArgs
, ID, Instruction(..)
)
import CodeGen.Monad
( CGMonad, MonadFresh(fresh), note )
import Data.Type.Known
( knownValue )
import FIR.AST
( AST
, MkVectorF(..), GradedMappendF(..)
, MatF(..)
, StructF(..), ArrayF(..), ArrayLengthF(..)
)
import FIR.AST.Type
( AugType(Val) )
import FIR.Prim.Array
( Array(MkArray) )
import FIR.Prim.Struct
( traverseStructASTs )
import FIR.Prim.Types
( primTy )
import Math.Linear
( V )
import qualified SPIRV.Layout as SPIRV
import qualified SPIRV.Operation as SPIRV.Op
import qualified SPIRV.PrimTy as SPIRV
import SPIRV.PrimTy
( PrimTy(..) )
import qualified SPIRV.ScalarTy as SPIRV
( ScalarTy(..), Signedness(..), Width(..) )
----------------------------------------------------------------------------
-- Code generation for constructing composite objects.
instance CodeGen AST => CodeGen (MkVectorF AST) where
codeGenArgs ( Applied (MkVectorF (vec :: V n (AST (Val a))) ) NilAST ) = do
let n = knownValue @n
compositeTy <- case primTy @a of
SPIRV.Scalar s ->
pure $ SPIRV.Vector n (SPIRV.Scalar s)
SPIRV.Vector m (SPIRV.Scalar s) ->
pure $ SPIRV.Matrix m n s
x ->
throwError
( "codeGen: unexpected vector constituent "
<> ShortText.pack ( show x )
)
( compositeConstruct compositeTy . map fst . toList )
=<< traverse codeGen vec
instance CodeGen AST => CodeGen (MatF AST) where
codeGenArgs ( Applied MatF (a `ConsAST` NilAST) ) = codeGenArgs ( Nullary a )
codeGenArgs ( Applied UnMatF (a `ConsAST` NilAST) ) = codeGenArgs ( Nullary a )
instance CodeGen AST => CodeGen (StructF AST) where
codeGenArgs ( Applied structF@(StructF struct) NilAST ) = case structF of
( _ :: StructF AST (Val struct) ) ->
compositeConstruct (primTy @struct)
=<< traverseStructASTs @AST (fmap fst . codeGen) struct
instance CodeGen AST => CodeGen (ArrayF AST) where
codeGenArgs ( Applied arrF@(ArrayF (MkArray vec)) NilAST ) = case arrF of
( _ :: ArrayF AST (Val (Array n a)) ) ->
let
n :: Word32
n = Vector.knownLength' vec ( fromIntegral . natVal )
arrTy :: SPIRV.PrimTy
arrTy = SPIRV.Array n ( primTy @a )
in
compositeConstruct arrTy
=<< traverse (fmap fst . codeGen) (Vector.toList vec)
instance CodeGen (ArrayLengthF AST) where
codeGenArgs ( Applied (ArrayLengthF (_ :: Proxy structName) (_ :: Proxy pos)) NilAST ) = do
let
structName :: ShortText
structName = knownValue @structName
arrayPos :: Word32
arrayPos = knownValue @pos
(structBdID, _) <- bindingID structName
let
word32Ty :: SPIRV.PrimTy
word32Ty = SPIRV.Scalar ( SPIRV.Integer SPIRV.Unsigned SPIRV.W32 )
word32TyID <- typeID word32Ty
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.ArrayLength
, resTy = Just word32TyID
, resID = Just v
, args = Arg structBdID
$ Arg arrayPos
$ EndArgs
}
pure ( v, word32Ty )
instance CodeGen AST => CodeGen (GradedMappendF AST) where
codeGenArgs ( Applied GradedMappendF ( a `ConsAST` b `ConsAST` NilAST ) ) = do
(a_ID, a_ty) <- codeGen a
(b_ID, b_ty) <- codeGen b
case (a_ty, b_ty) of
( SPIRV.Vector i (SPIRV.Scalar s), SPIRV.Vector j (SPIRV.Scalar t)) ->
if s /= t
then throwError "codeGen: graded semigroup operation on incompatible vectors"
else compositeConstruct (SPIRV.Vector (i+j) (SPIRV.Scalar s)) [a_ID, b_ID]
( SPIRV.Matrix m n s, SPIRV.Matrix m' n' s' ) ->
if m /= m' || s /= s'
then throwError "codeGen: graded semigroup operation on incompatible matrices"
else do
cols1 <- traverse ( \i -> fst <$> compositeExtract (a_ID, a_ty) [i] ) [0..n -1]
cols2 <- traverse ( \i -> fst <$> compositeExtract (b_ID, b_ty) [i] ) [0..n'-1]
compositeConstruct (SPIRV.Matrix m (n+n') s) (cols1 ++ cols2)
( SPIRV.Struct as, SPIRV.Struct bs ) ->
do
let lg = fromIntegral (length as)
lg' = fromIntegral (length bs)
elts1 <- traverse ( \i -> fst <$> compositeExtract (a_ID, a_ty) [i] ) [0..lg -1]
elts2 <- traverse ( \i -> fst <$> compositeExtract (b_ID, b_ty) [i] ) [0..lg'-1]
compositeConstruct
( SPIRV.Struct (as ++ bs) )
( elts1 ++ elts2 )
( SPIRV.Array lg eltTy, SPIRV.Array lg' eltTy' ) ->
if eltTy /= eltTy'
then throwError "codeGen: graded semigroup operation on incompatible arrays"
else do
-- should probably use a loop instead of this, but nevermind
elts1 <- traverse ( \i -> fst <$> compositeExtract (a_ID, a_ty) [i] ) [0..lg -1]
elts2 <- traverse ( \i -> fst <$> compositeExtract (b_ID, b_ty) [i] ) [0..lg'-1]
compositeConstruct
( SPIRV.Array (lg + lg') eltTy )
( elts1 ++ elts2 )
_ -> throwError "codeGen: unsupported graded semigroup operation"
----------------------------------------------------------------------------
-- SPIR-V instructions.
compositeConstruct :: SPIRV.PrimTy -> [ ID ] -> CGMonad (ID, SPIRV.PrimTy)
compositeConstruct compositeType constituents
= do tyID <- typeID compositeType
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.CompositeConstruct
, resTy = Just tyID
, resID = Just v
, args = toArgs constituents
}
pure (v, compositeType)
compositeExtract
:: (ID, SPIRV.PrimTy)
-> [ Word32 ]
-> CGMonad (ID, SPIRV.PrimTy)
compositeExtract (compositeID, compositeTy) indices
= do
constituentTy <-
note ( "'compositeExtract': could not compute accessee type.\n\
\base: " <> ShortText.pack (show compositeTy) <> "\n\
\indices: " <> ShortText.pack (show indices) <> "."
)
( accessedTy ( fmap Just indices ) compositeTy )
constituentTyID <- typeID constituentTy
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.CompositeExtract
, resTy = Just constituentTyID
, resID = Just v
, args = Arg compositeID
$ toArgs indices
}
pure (v, constituentTy)
compositeInsert
:: (ID, SPIRV.PrimTy)
-> (ID, SPIRV.PrimTy)
-> [ Word32 ]
-> CGMonad (ID, SPIRV.PrimTy)
compositeInsert (inserteeID, inserteeTy) (compositeID, compositeTy) indices
= do
compositeTyID <- typeID compositeTy
constituentTy <-
note ( "'compositeInsert': could not compute constituent type.\n\
\base: " <> ShortText.pack (show compositeTy) <> "\n\
\indices: " <> ShortText.pack (show indices) <> "."
)
( accessedTy ( fmap Just indices ) compositeTy )
constituentTyID <- typeID constituentTy
correctInserteeID <-
if constituentTy == inserteeTy
then pure inserteeID
else do
u <- fresh
instruction
Instruction
{ operation = SPIRV.Op.BitCast
, resTy = Just constituentTyID
, resID = Just u
, args = Arg inserteeID EndArgs
}
pure u
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.CompositeInsert
, resTy = Just compositeTyID
, resID = Just v
, args = Arg correctInserteeID
$ Arg compositeID
$ toArgs indices
}
pure (v, compositeTy)
-- | The type of the component at the given indices of a value of the given type.
accessedTy :: [ Maybe Word32 ] -> SPIRV.PrimTy -> Maybe SPIRV.PrimTy
accessedTy is ty = snd <$> accessedLaidOutTy is ( SPIRV.logical, ty )
-- | The type of the component at the given indices of a type with the given layout,
-- together with the layout of that component.
accessedLaidOutTy :: [ Maybe Word32 ] -> ( SPIRV.Layout, SPIRV.PrimTy ) -> Maybe ( SPIRV.Layout, SPIRV.PrimTy )
accessedLaidOutTy [] base = Just base
accessedLaidOutTy (i : is) ( lay, baseTy ) = do
compTy <- componentTy i baseTy
accessedLaidOutTy is ( SPIRV.componentLayout baseTy lay, compTy )
where
componentTy :: Maybe Word32 -> SPIRV.PrimTy -> Maybe SPIRV.PrimTy
componentTy _ (SPIRV.Vector {eltTy}) = Just eltTy
componentTy _ (SPIRV.Matrix {rows, entryTy}) = Just (SPIRV.Vector rows (SPIRV.Scalar entryTy))
componentTy _ (SPIRV.Array {eltTy}) = Just eltTy
componentTy _ (SPIRV.RuntimeArray {eltTy}) = Just eltTy
componentTy (Just j) (SPIRV.Struct {eltTys})
| j' < length eltTys = Just ( snd $ eltTys !! j' )
where j' = fromIntegral j
componentTy _ _
= Nothing
----------------------------------------------------------------------------
-- vector shuffle/swizzle
vectorShuffle :: ( (ID, SPIRV.PrimTy), [Word32] )
-> ( (ID, SPIRV.PrimTy), [Word32] )
-> CGMonad (ID, SPIRV.PrimTy)
vectorShuffle ((u_ID, SPIRV.Vector n s), is) ((v_ID, SPIRV.Vector _ t), js)
| s /= t = throwError "vectorShuffle: incompatible vector components"
| otherwise =
do
v <- fresh
let indices = is ++ map (+n) js
shuffleTy = SPIRV.Vector ( fromIntegral (length is + length js) ) s
shuffleTyID <- typeID shuffleTy
instruction
Instruction
{ operation = SPIRV.Op.VectorShuffle
, resTy = Just shuffleTyID
, resID = Just v
, args = Arg u_ID
$ Arg v_ID
$ toArgs indices
}
pure (v, shuffleTy)
vectorShuffle _ _
= throwError "vectorShuffle used on non-vectors"
vectorSwizzle :: (ID, SPIRV.PrimTy) -> [Word32] -> CGMonad (ID, SPIRV.PrimTy)
vectorSwizzle v@(_, vecTy) is = do
whenAsserting $
case vecTy of
SPIRV.Vector {} -> pure ()
_ -> throwError ( "vectorSwizzle: ASSERT failed, swizzle on non-vector: " <> ShortText.pack ( show v ) )
case is of
[] -> throwError "vectorSwizzle: empty swizzle"
[i] -> compositeExtract v [i]
_ -> do
undef <- (,vecTy) <$> undefID vecTy
vectorShuffle (v, is) (undef, [])
-- using 'undefined' here prevents any code duplication that could result from
-- 'vectorShuffle (v, is) (v, [])'
-- (e.g. if 'v' is the result of an expensive computation, which could get inlined twice)
----------------------------------------------------------------------------
-- products
-- like 'compositeConstruct', except it checks that the types are correct
-- (for matrices, it constructs the composite in two steps when needed)
productConstruct :: SPIRV.PrimTy -> [ ( ID, SPIRV.PrimTy ) ] -> CGMonad (ID, SPIRV.PrimTy)
productConstruct p as = case p of
SPIRV.Matrix {..}
| all ( (== SPIRV.Scalar entryTy) . snd ) as
&& fromIntegral (length as) == rows * cols
-> do
columns <- traverse (compositeConstruct colTy . map fst) (chunksOf (fromIntegral rows) as)
compositeConstruct p (map fst columns)
| all ( (== colTy) . snd ) as
&& fromIntegral (length as) == cols
-> compositeConstruct p (map fst as)
| otherwise
-> throwError $
"'productConstruct': type mismatch for constructing matrix\n"
<> "attempting to construct matrix with " <> ShortText.pack (show rows)
<> " rows and " <> ShortText.pack (show cols) <> " columns of type "
<> ShortText.pack (show entryTy) <> ",\n"
<> "using components of types " <> ShortText.pack (show (map snd as))
where
colTy :: SPIRV.PrimTy
colTy = SPIRV.Vector rows (SPIRV.Scalar entryTy)
SPIRV.Vector {..}
| all ( (== eltTy) . snd ) as
&& fromIntegral (length as) == size
-> compositeConstruct p (map fst as)
| otherwise
-> throwError $
"'productConstruct': type mismatch for constructing vector\n"
<> "attempting to construct vector with " <> ShortText.pack (show size)
<> " components of type " <> ShortText.pack (show eltTy) <> ",\n"
<> "using components of types " <> ShortText.pack (show (map snd as))
SPIRV.Array {..}
| all ( (== eltTy) . snd ) as
&& fromIntegral (length as) == size
-> compositeConstruct p (map fst as)
| otherwise
-> throwError $
"'productConstruct': type mismatch for constructing array\n"
<> "attempting to construct array of size " <> ShortText.pack (show size)
<> " with elements of type " <> ShortText.pack (show eltTy) <> ",\n"
<> "using components of types " <> ShortText.pack (show (map snd as))
SPIRV.Struct {..}
| and ( zipWith (\(_, ty1) (_,t2) -> ty1 == t2) eltTys as )
&& length as == length eltTys
-> compositeConstruct p (map fst as)
| otherwise
-> throwError $
"'productConstruct': type mismatch for constructing structure\n"
<> "attempting to construct structure with elements of type "
<> ShortText.pack (show (map snd eltTys)) <> ",\n"
<> "using components of types " <> ShortText.pack (show (map snd as))
otherTy
-> throwError $
"'productConstruct': unexpected non-product type "
<> ShortText.pack ( show otherTy )