fir-0.1.0.0: src/FIR/Validation/Definitions.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Module: FIR.Validation.Definitions
Validate individual top-level "FIR.Definition.Definition"s.
For instance, this checks that uniform buffer objects are
decorated with the required binding and descriptor set indices.
Does __not__ perform validation of entry point interfaces,
i.e. input/output variables. See "FIR.Validation.Interface".
This is because it is not possible to validate layout using only the definitions,
as the layout validity of an entry point interface depends on
the specific entry-point it is used by.
For instance, an arrayed input means something different in
a tessellation evaluation shader than in a vertex shader,
because of implicit arrayness levels. See "FIR.Validation.Arrayness".
-}
module FIR.Validation.Definitions
( ValidDefinitions )
where
-- base
import Data.Kind
( Type, Constraint )
import GHC.TypeLits
( Symbol
, TypeError, ErrorMessage(..)
)
import GHC.TypeNats
( Nat )
-- fir
import Data.Type.Maybe
( FromMaybe )
import Data.Type.Map
( (:->)((:->)), Map
, InsertWithAppend, UnionWithAppend
)
import FIR.Definition
( Definition(..)
, TriagedDefinitions
, TrieDefinitions
)
import FIR.Prim.Array
( Array, RuntimeArray )
import FIR.Prim.Image
( Image )
import FIR.Prim.RayTracing
( AccelerationStructure )
import FIR.Prim.Struct
( Struct )
import FIR.Prim.Types
( HasOpaqueType )
import FIR.ProgramState
( TLInterfaceVariable )
import FIR.Validation.Images
( ImageUsageFromProperties )
import qualified SPIRV.Decoration as SPIRV
( Decoration(..) )
import qualified SPIRV.Image as SPIRV
( ImageUsage )
import qualified SPIRV.ExecutionMode as SPIRV
( ExecutionMode )
import qualified SPIRV.Stage as SPIRV
( ExecutionModel, NamedExecutionModel )
import qualified SPIRV.Image
( ImageUsage(..) )
import qualified SPIRV.Storage as SPIRV
( StorageClass )
import qualified SPIRV.Storage as Storage
( StorageClass(..), RayStorage(ShaderRecordBuffer) )
------------------------------------------------------------------------
-- | Validate user-supplied top-level 'FIR.Definition.Definition's.
--
-- Note that this only validates global variables and their layout,
-- as other kinds of validation are performed elsewhere.
-- See for instance 'FIR.Definition.StartState'.
type ValidDefinitions ( defs :: [ Symbol :-> Definition ] )
= ( ValidTriagedDefinitions ( TrieDefinitions defs ) :: Constraint)
-- | Validate that descriptor set and bindings don't overlap
type family NonOverlappingDescriptors ( globs :: [ SPIRV.StorageClass :-> [ Symbol :-> TLInterfaceVariable ] ] )
:: Constraint
where
NonOverlappingDescriptors list = EnsureNoDuplicates (BuildDescriptorList list)
type family BuildDescriptorList ( globs :: [ SPIRV.StorageClass :-> [ Symbol :-> TLInterfaceVariable ] ] ) :: Map (Nat, Nat) [Symbol]
where
BuildDescriptorList '[] = '[]
BuildDescriptorList ( (storage ':-> vars) ': globs ) = UnionWithAppend (GetDescriptor vars) (BuildDescriptorList globs)
type family AppendMaybeDescriptor ( mbDescNo :: Maybe Nat ) ( mbBdNo :: Maybe Nat ) ( name :: Symbol ) ( bds :: Map (Nat,Nat) [Symbol] ) :: Map (Nat,Nat) [Symbol] where
AppendMaybeDescriptor ( Just descNo ) mbBdNo name bds = InsertWithAppend '( descNo, FromMaybe mbBdNo 0 ) name bds
-- Ignore bindings which don't have a "DescriptorSet" decoration.
AppendMaybeDescriptor _ _ _ bds = bds
type family GetDescriptor ( globs :: [ Symbol :-> TLInterfaceVariable ] ) :: Map (Nat, Nat) [Symbol]
where
GetDescriptor '[] = '[]
GetDescriptor ( (k ':-> '( decs, ty )) ': globs ) =
AppendMaybeDescriptor ( ExtractDescriptorSet decs ) ( ExtractBinding decs ) k ( GetDescriptor globs )
type family ExtractDescriptorSet ( decs :: [ SPIRV.Decoration Nat ] ) :: Maybe Nat
where
ExtractDescriptorSet '[] = 'Nothing
ExtractDescriptorSet (SPIRV.DescriptorSet n ': globs) = 'Just n
ExtractDescriptorSet (_ ': globs) = ExtractDescriptorSet globs
type family ExtractBinding ( decs :: [ SPIRV.Decoration Nat ] ) :: Maybe Nat
where
ExtractBinding '[] = 'Nothing
ExtractBinding (SPIRV.Binding n ': globs) = 'Just n
ExtractBinding (_ ': globs) = ExtractBinding globs
type family EnsureNoDuplicates (as :: Map (Nat,Nat) [Symbol]) :: Constraint where
EnsureNoDuplicates '[] = ()
EnsureNoDuplicates ( ( '( descNo, bdNo ) ':-> names ) ': bds ) = ( NoDuplicates descNo bdNo names, EnsureNoDuplicates bds )
type family NoDuplicates ( descNo :: Nat ) ( bdNo :: Nat ) ( names :: [Symbol] ) :: Constraint where
NoDuplicates _ _ '[] = ()
NoDuplicates _ _ '[_] = ()
NoDuplicates descNo bdNo names = TypeError
( Text "More than one descriptor with DescriptorSet " :<>: ShowType descNo
:<>: Text " and Binding " :<>: ShowType bdNo :<>: Text ":"
:$$: ShowType names
)
type family ValidTriagedDefinitions ( defs :: TriagedDefinitions ) :: Constraint where
ValidTriagedDefinitions '( _, eps, globs )
= ( ValidGlobals globs, NonOverlappingDescriptors globs, NoDuplicateEntryPoints eps )
-- * not validating functions: no validation necessary
-- * execution modes are validated as part of the computation of entry point info
type family NoDuplicateEntryPoints
( eps :: [ Symbol :-> ( SPIRV.ExecutionModel, [ SPIRV.ExecutionMode Nat ] ) ] )
:: Constraint
where
NoDuplicateEntryPoints '[] = ()
NoDuplicateEntryPoints ( ( k ':-> '( em, _ ) ) ': eps )
= ( EntryPointNotDeclaredIn k em eps, NoDuplicateEntryPoints eps )
type family EntryPointNotDeclaredIn
( k :: Symbol ) ( em :: SPIRV.ExecutionModel )
( eps :: [ Symbol :-> ( SPIRV.ExecutionModel, [ SPIRV.ExecutionMode Nat ] ) ] )
:: Constraint
where
EntryPointNotDeclaredIn _ _ '[] = ()
EntryPointNotDeclaredIn k em ( ( k ':-> '( em, _ ) ) ': _ )
= TypeError
( Text "Duplicate entry point declaration: "
:<>: Text ( SPIRV.NamedExecutionModel k em ) :<>: Text "."
)
EntryPointNotDeclaredIn k em ( _ ': eps ) = EntryPointNotDeclaredIn k em eps
type family ValidGlobals
( globs :: [ SPIRV.StorageClass :-> [ Symbol :-> TLInterfaceVariable ] ] )
:: Constraint
where
ValidGlobals '[] = ()
ValidGlobals ( ( Storage.Input ':-> _ ) ': globs )
-- no validity checking for input variables (see "FIR.Validation.Interface")
= ValidGlobals globs
ValidGlobals ( ( Storage.Output ':-> _ ) ': globs )
-- no validity checking for output variables (see "FIR.Validation.Interface")
= ValidGlobals globs
ValidGlobals ( ( storage ':-> vars ) ': globs )
= ( MapValidGlobal storage vars
, ValidGlobals globs
)
type family MapValidGlobal
( storage :: SPIRV.StorageClass )
( globals :: [ Symbol :-> TLInterfaceVariable ] )
:: Constraint
where
MapValidGlobal _ '[] = ()
MapValidGlobal storage ( ( k ':-> '( decs, ty ) ) ': globals )
= ( ValidGlobal k storage decs ty
, MapValidGlobal storage globals
)
type family ValidGlobal
( name :: Symbol )
( storage :: SPIRV.StorageClass )
( decs :: [ SPIRV.Decoration Nat ] )
( ty :: Type )
:: Constraint
where
ValidGlobal name Storage.UniformConstant decs (Image props)
= ( ValidUniformDecorations
( Text "Image named " :<>: ShowType name )
decs
(Image props)
, ValidImageDecorations name (ImageUsageFromProperties props) decs
)
ValidGlobal name Storage.UniformConstant decs AccelerationStructure
= ValidUniformDecorations
( Text "Acceleration structure named " :<>: ShowType name )
decs
AccelerationStructure
ValidGlobal name Storage.UniformConstant _ otherTy
= TypeError
( Text "Uniform constant global named " :<>: ShowType name
:<>: Text " points to unexpected type "
:<>: ShowType otherTy :<>: Text "."
)
ValidGlobal name Storage.Image decs (Image props)
= ( ValidUniformDecorations
( Text "Storage image named " :<>: ShowType name )
decs
(Image props)
, ValidImageDecorations name (ImageUsageFromProperties props) decs
)
ValidGlobal name Storage.Image _ nonImageTy
= TypeError
( Text "Image global named " :<>: ShowType name
:<>: Text " expected to point to an image, but points to "
:<>: ShowType nonImageTy :<>: Text " instead."
)
ValidGlobal name Storage.Uniform decs (Struct as)
= ( ValidUniformDecorations
( Text "Uniform buffer named " :<>: ShowType name )
decs
(Struct as)
, NoOpaqueTypes name ( Text "buffer" ) as
)
ValidGlobal name Storage.Uniform decs (Array n (Struct as))
= ( ValidUniformDecorations
( Text "Uniform buffer named " :<>: ShowType name )
decs
(Array n (Struct as))
, NoOpaqueTypes name ( Text "buffer" ) as
)
ValidGlobal name Storage.Uniform _ ty
= TypeError
( Text "Uniform buffer named " :<>: ShowType name
:<>: Text " should be backed by a struct or a fixed-size array containing a struct;"
:$$: Text "found type " :<>: ShowType ty :<>: Text " instead."
)
ValidGlobal name Storage.StorageBuffer decs (Struct as)
= ( ValidUniformDecorations
( Text "Uniform storage buffer named " :<>: ShowType name )
decs
(Struct as)
, NoOpaqueTypesExceptRuntimeArrayLast name as
)
ValidGlobal name Storage.StorageBuffer decs (Array n (Struct as))
= ( ValidUniformDecorations
( Text "Uniform storage buffer named " :<>: ShowType name )
decs
(Array n (Struct as))
, NoOpaqueTypes name ( Text "storage buffer" ) as
)
ValidGlobal name Storage.StorageBuffer decs (RuntimeArray a)
= TypeError
( Text "Uniform storage buffer named " :<>: ShowType name
:<>: Text " should be backed by a struct or a fixed-size array containing a struct;"
:$$: Text "found type " :<>: ShowType (RuntimeArray a) :<>: Text " instead."
:$$: Text "To use a dynamically sized array in a storage buffer, define a structure"
:<>: Text " whose last member is a RuntimeArray."
)
ValidGlobal name Storage.StorageBuffer _ ty
= TypeError
( Text "Uniform storage buffer named " :<>: ShowType name
:<>: Text " should be backed by a struct or fixed-size array containing a struct;"
:$$: Text "found type " :<>: ShowType ty :<>: Text " instead."
)
ValidGlobal name Storage.PushConstant _ (Struct as)
= NoOpaqueTypes name ( Text "push constant" ) as
ValidGlobal name Storage.PushConstant _ ty
= TypeError
( Text "Push constant named " :<>: ShowType name
:<>: Text " should be backed by a struct;"
:$$: Text "found type " :<>: ShowType ty :<>: Text " instead."
)
ValidGlobal name ( 'Storage.RayStorage Storage.ShaderRecordBuffer ) _ (Struct as)
= NoOpaqueTypesExceptRuntimeArrayLast name as
ValidGlobal name ( 'Storage.RayStorage Storage.ShaderRecordBuffer ) _ ty
= TypeError
( Text "Shader record buffer named " :<>: ShowType name
:<>: Text " should be backed by a struct;"
:$$: Text "found type " :<>: ShowType ty :<>: Text " instead."
)
-- TODO: more validation for ray-tracing storage classes
ValidGlobal _ _ _ _ = ()
type family ValidUniformDecorations
( name :: ErrorMessage )
( decs :: [ SPIRV.Decoration Nat ] )
( ty :: Type )
:: Constraint
where
ValidUniformDecorations name decs _
= ( HasBinding name decs, HasDescriptorSet name decs )
type family HasBinding
( name :: ErrorMessage )
( decs :: [ SPIRV.Decoration Nat ] )
:: Constraint
where
HasBinding _ ( SPIRV.Binding _ ': _ ) = ()
HasBinding name ( _ ': decs ) = HasBinding name decs
HasBinding name '[]
= TypeError
( name :<>: Text " is missing a 'Binding' decoration." )
type family HasDescriptorSet
( name :: ErrorMessage )
( decs :: [ SPIRV.Decoration Nat ] )
:: Constraint
where
HasDescriptorSet _ ( SPIRV.DescriptorSet _ ': _ ) = ()
HasDescriptorSet name ( _ ': decs ) = HasDescriptorSet name decs
HasDescriptorSet name '[]
= TypeError
( name :<>: Text " is missing a 'DescriptorSet' decoration." )
type family HasLocation
( name :: Symbol )
( decs :: [ SPIRV.Decoration Nat ] )
:: Constraint
where
HasLocation _ ( SPIRV.Location _ ': _ ) = ()
HasLocation name ( _ ': decs ) = HasLocation name decs
HasLocation name '[]
= TypeError
( ShowType name :<>: Text " is missing a 'Location' decoration." )
-- TODO: I'm not sure what the valid decorations are here...
type family ValidImageDecorations
( name :: Symbol )
( imageTy :: SPIRV.ImageUsage )
( decs :: [ SPIRV.Decoration Nat] )
:: Constraint
where
ValidImageDecorations _ _ '[] = ()
ValidImageDecorations name ty ( SPIRV.DescriptorSet _ ': decs )
= ValidImageDecorations name ty decs
ValidImageDecorations name ty ( SPIRV.Binding _ ': decs )
= ValidImageDecorations name ty decs
ValidImageDecorations name ty ( SPIRV.NonReadable ': decs )
= ValidImageDecorations name ty decs
ValidImageDecorations name SPIRV.Image.Storage ( SPIRV.NonWritable ': decs )
= ValidImageDecorations name SPIRV.Image.Storage decs
ValidImageDecorations name _ ( dec ': _ )
= TypeError
( Text "Unexpected decoration " :<>: ShowType dec
:<>: Text " applied to image named " :<>: ShowType name
:<>: Text "."
)
type family NoOpaqueTypes
( name :: Symbol )
( loc :: ErrorMessage )
( as :: [ Symbol :-> Type ] )
:: Constraint
where
NoOpaqueTypes _ _ '[] = ()
NoOpaqueTypes name loc ((k ':-> a) ': as) =
( ErrorIfOpaque (HasOpaqueType a) name loc k a
, NoOpaqueTypes name loc as
)
type family ErrorIfOpaque (hasOpaque :: Bool) (name :: Symbol) (loc :: ErrorMessage) (k :: Symbol) (a :: Type) :: Constraint where
ErrorIfOpaque True name loc k a =
TypeError
( Text "Structure member " :<>: ShowType k :<>: Text " cannot appear"
:<>: Text " in " :<>: loc :<>: Text " named " :<>: ShowType name :<>: Text "."
:$$: Text "Its type " :<>: ShowType a
:$$: Text "is opaque (or it contains an opaque type)."
)
ErrorIfOpaque _ _ _ _ _ = ()
type family NoOpaqueTypesExceptRuntimeArrayLast ( name :: Symbol ) ( as :: [ Symbol :-> Type ] ) :: Constraint where
NoOpaqueTypesExceptRuntimeArrayLast _ '[] = ()
NoOpaqueTypesExceptRuntimeArrayLast name ( ( k ':-> RuntimeArray a ) ': '[] ) =
ErrorIfOpaqueRuntimeArray (HasOpaqueType a) name k a
NoOpaqueTypesExceptRuntimeArrayLast name ( ( k ':-> RuntimeArray a ) ': _ ) =
TypeError
( Text "Unexpected runtime array named " :<>: ShowType k
:<>: Text " within storage buffer named " :<>: Text name :<>: Text "."
:$$: Text "Runtime arrays must appear last in the structure."
)
NoOpaqueTypesExceptRuntimeArrayLast name ( ( k ':-> a ) ': as ) =
( NoOpaqueTypes name ( Text "storage buffer" ) '[ k ':-> a ]
, NoOpaqueTypesExceptRuntimeArrayLast name as
)
type family ErrorIfOpaqueRuntimeArray (hasOpaque :: Bool) (name :: Symbol) (k :: Symbol) (a :: Type) :: Constraint where
ErrorIfOpaqueRuntimeArray True name k a =
TypeError
( Text "Structure member " :<>: ShowType k :<>: Text " cannot appear"
:<>: Text " in storage buffer named " :<>: ShowType name :<>: Text "."
:$$: Text "It is a runtime array whose element type"
:$$: ShowType a
:$$: Text "is opaque, or contains an opaque type."
)
ErrorIfOpaqueRuntimeArray _ _ _ _ = ()