fir-0.1.0.0: src/CodeGen/Pointers.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module: CodeGen.Pointers
Code generation for load/store instruction using pointers.
Values always have logical types, whereas memory can impose a layout on the types it contains.
Loading and storing converts between the two.
-}
module CodeGen.Pointers
( Safeness(Safe, Unsafe)
, Indices(RTInds, CTInds)
, newVariable, temporaryVariable
, accessChain
, load, loadInstruction
, store, storeInstruction
, relayout
) where
-- base
import Data.Word
( Word32 )
-- containers
import qualified Data.Map.Strict as Map
-- mtl
import Control.Monad.Except
( throwError )
-- lens
import Control.Lens
( use, assign, view )
-- text-short
import Data.Text.Short
( ShortText )
import qualified Data.Text.Short as ShortText
( pack )
-- fir
import CodeGen.Binary
( instruction )
import CodeGen.Composite
( accessedLaidOutTy )
import CodeGen.IDs
( typeID, laidOutTypeID, constID )
import CodeGen.Instruction
( Args(..), toArgs
, ID, TyID, Instruction(..)
, Pointer(..), Variable(..)
)
import CodeGen.Monad
( CGMonad
, MonadFresh(fresh)
, note
)
import CodeGen.State
( PointerState(Fresh)
, _interfaceBinding
, _knownConstants
, _spirvVersion
, currentFunctionContext
, addLocalVariable
, lookupTemporaryPointer, setTemporaryPointer
)
import FIR.Prim.Types
( SPrimTy(..), SScalarTy(..)
, PrimTy(primTySing), ScalarTy(scalarTySing)
, AConstant(AConstant)
)
import FIR.ProgramState
( FunctionContext(TopLevel, InEntryPoint) )
import qualified SPIRV.Layout as SPIRV
import qualified SPIRV.Operation as SPIRV.Op
import qualified SPIRV.PrimTy as SPIRV
import qualified SPIRV.Stage as SPIRV
import qualified SPIRV.Storage as Storage
import qualified SPIRV.Version as SPIRV
----------------------------------------------------------------------------
-- safeness of pointer access (whether indices known to be within bounds)
data Safeness
= Unsafe
| Safe
deriving stock ( Eq, Show )
instance Semigroup Safeness where
Safe <> x = x
_ <> _ = Unsafe
data Indices
= CTInds [Word32] -- compile-time indices
| RTInds [ID] -- run-time indices
deriving stock Show
----------------------------------------------------------------------------
-- creating pointers
-- | Create a new local variable of the current function, holding values of the given type.
newVariable :: SPIRV.PrimTy -> CGMonad Pointer
newVariable ty
= do
v <- fresh
ptrTyID <- typeID ( SPIRV.Pointer ptrTy )
let ptr = Pointer v ptrTy
addLocalVariable ( Variable ptr ptrTyID )
pure ptr
where
ptrTy :: SPIRV.PointerTy
ptrTy = SPIRV.PointerTy Storage.Function SPIRV.logical ty
-- | Obtain a local variable holding a copy of the given value, in the current block.
temporaryVariable :: (ID, SPIRV.PrimTy) -> CGMonad (Pointer, PointerState)
temporaryVariable (baseID, baseTy)
= do
mbTemp <- lookupTemporaryPointer baseID
case mbTemp of
Just temp -> pure temp
Nothing -> do
ptr <- newVariable baseTy
storeInstruction ptr baseID
setTemporaryPointer baseID (ptr, Fresh)
pure (ptr, Fresh)
indicesIDs :: Indices -> CGMonad [ID]
indicesIDs (CTInds ws) = traverse constID ws
indicesIDs (RTInds is) = pure is
-- create a pointer into a composite object with a list of successive indices
accessChain :: Pointer -> Safeness -> Indices -> CGMonad Pointer
accessChain (Pointer basePtrID (SPIRV.PointerTy storage baseLayout baseTy)) safe indices
= do
is <- indicesIDs indices
(eltLayout, eltTy) <-
note ( "'accessChain': could not compute accessee type.\n\
\base: " <> ShortText.pack (show baseTy) <> "\n\
\indices: " <> ShortText.pack (show indices) <> "."
)
=<< ( flip accessedLaidOutTy (baseLayout, baseTy) <$> reverseLookupIndices indices )
let opAccessChain
= case safe of
Safe -> SPIRV.Op.InBoundsAccessChain
_ -> SPIRV.Op.AccessChain
accessPtrTy = SPIRV.PointerTy storage eltLayout eltTy
accessPtrTyID <- typeID (SPIRV.Pointer accessPtrTy)
v <- fresh
instruction
Instruction
{ operation = opAccessChain
, resTy = Just accessPtrTyID
, resID = Just v
, args = Arg basePtrID
$ toArgs is
}
pure (Pointer v accessPtrTy)
reverseLookupIndices :: Indices -> CGMonad [ Maybe Word32 ]
reverseLookupIndices (CTInds is) = pure (map Just is)
reverseLookupIndices (RTInds is) = traverse reverseConstantLookup is
reverseConstantLookup :: ID -> CGMonad (Maybe Word32)
reverseConstantLookup c = do
lookups <- filter ( (== Just c) . resID . snd ) . Map.assocs <$> use _knownConstants
case lookups of
( (AConstant (a :: ty), _) : _ )
-> case primTySing @ty of
SScalar ->
case scalarTySing @ty of
SWord8 -> pure . Just $ fromIntegral a
SWord16 -> pure . Just $ fromIntegral a
SWord32 -> pure . Just $ fromIntegral a
SWord64 -> pure . Just $ fromIntegral a
SInt8 -> pure . Just $ fromIntegral a
SInt16 -> pure . Just $ fromIntegral a
SInt32 -> pure . Just $ fromIntegral a
SInt64 -> pure . Just $ fromIntegral a
_ -> pure Nothing
_ -> pure Nothing
_ -> pure Nothing
----------------------------------------------------------------------------
-- load/store through pointers
-- | Load a value through the pointer of the variable with the given name.
load :: ShortText -> Pointer -> CGMonad (ID, SPIRV.PrimTy)
load loadeeName ptr@(Pointer loadeeID (SPIRV.PointerTy storage _ _))
= do
context <- currentFunctionContext
case context of
TopLevel -> throwError "codeGen: load operation not allowed at top level"
InEntryPoint entryPointName stageInfo _
| storage == Storage.Input
-- add this variable to the interface of the entry point
-> assign
( _interfaceBinding entryPointName (SPIRV.modelOf stageInfo) loadeeName )
( Just loadeeID )
_ -> pure ()
loadInstruction ptr
-- | Load a value through a pointer.
loadInstruction :: Pointer -> CGMonad (ID, SPIRV.PrimTy)
loadInstruction (Pointer loadeeID (SPIRV.PointerTy _ lay ty))
= do tyID <- laidOutTypeID lay ty
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.Load
, resTy = Just tyID
, resID = Just v
, args = Arg loadeeID EndArgs
}
(, ty) <$> relayout lay SPIRV.logical (v, ty)
-- | Store a value through the pointer of the variable with the given name.
store :: ShortText -> Pointer -> ID -> CGMonad ()
store storeeName ptr@(Pointer ptrID (SPIRV.PointerTy storage _ _)) storeeID
= do
context <- currentFunctionContext
case context of
TopLevel -> throwError "codeGen: store operation not allowed at top level"
InEntryPoint entryPointName stageInfo _
| storage == Storage.Output
-- add this variable to the interface of the entry point
-> assign
( _interfaceBinding entryPointName (SPIRV.modelOf stageInfo) storeeName )
( Just ptrID )
_ -> pure ()
storeInstruction ptr storeeID
-- | Store a value through a pointer.
storeInstruction :: Pointer -> ID -> CGMonad ()
storeInstruction (Pointer ptrID (SPIRV.PointerTy _ lay ty)) storeeID
= do
laidOutStoreeID <- relayout SPIRV.logical lay (storeeID, ty)
instruction
Instruction
{ operation = SPIRV.Op.Store
, resTy = Nothing
, resID = Nothing
, args = Arg ptrID
$ Arg laidOutStoreeID EndArgs
}
----------------------------------------------------------------------------
-- converting between layouts
-- | Convert a value of the given type from one layout to another.
relayout :: SPIRV.Layout -> SPIRV.Layout -> (ID, SPIRV.PrimTy) -> CGMonad ID
relayout from to (v, ty) = do
fromTyID <- laidOutTypeID from ty
toTyID <- laidOutTypeID to ty
if fromTyID == toTyID
then pure v
else do
ver <- view _spirvVersion
if ver >= SPIRV.Version 1 4
then
newValue SPIRV.Op.CopyLogical toTyID ( Arg v EndArgs )
else do
components <- case ty of
SPIRV.Array size eltTy
-> pure $ replicate ( fromIntegral size ) eltTy
SPIRV.Struct eltTys
-> pure $ map snd eltTys
_ -> throwError
( "'relayout': cannot convert a value of type " <> ShortText.pack ( show ty )
<> " from layout " <> ShortText.pack ( show from )
<> " to layout " <> ShortText.pack ( show to ) <> "."
)
let
fromComp, toComp :: SPIRV.Layout
fromComp = SPIRV.componentLayout ty from
toComp = SPIRV.componentLayout ty to
convertedComps <-
traverse
( \ ( i, compTy ) -> do
compTyID <- laidOutTypeID fromComp compTy
comp <- newValue SPIRV.Op.CompositeExtract compTyID ( Arg v $ Arg i EndArgs )
relayout fromComp toComp ( comp, compTy )
)
( zip [ ( 0 :: Word32 ) .. ] components )
newValue SPIRV.Op.CompositeConstruct toTyID ( toArgs convertedComps )
where
newValue :: SPIRV.Op.Operation -> TyID -> Args -> CGMonad ID
newValue op resTyID operands = do
u <- fresh
instruction
Instruction
{ operation = op
, resTy = Just resTyID
, resID = Just u
, args = operands
}
pure u