fir-0.1.0.0: src/CodeGen/CFG.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NamedWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
{-|
Module: CodeGen.CFG
Code generation for control flow operations: branching, selections, loops.
See also "CodeGen.Phi" for ϕ-functions necessary for such constructs when using SSA form.
-}
module CodeGen.CFG
( newBlock, locally, embed )
where
-- base
import Prelude
hiding ( break )
import Control.Arrow
( second )
import Control.Monad
( unless, when )
import Data.Foldable
( for_ )
import Data.Maybe
( fromMaybe, isJust )
import Data.Proxy
( Proxy )
import Data.Traversable
( for )
import Data.Word
( Word32 )
-- containers
import Data.Map
( Map )
import qualified Data.Map.Strict as Map
import Data.Set
( Set )
import qualified Data.Set as Set
( fromList, member, toList, isSubsetOf )
-- lens
import Control.Lens
( view, use, assign )
-- mtl
import Control.Monad.Except
( throwError )
-- text-short
import Data.Text.Short
( ShortText )
import qualified Data.Text.Short as ShortText
( pack, unpack )
-- fir
import CodeGen.Application
( ASTs(NilAST, ConsAST), UCode(UCode)
, Application(Applied)
)
import CodeGen.Binary
( instruction, structuralInstruction )
import {-# SOURCE #-} CodeGen.CodeGen
( CodeGen(codeGenArgs), codeGen )
import CodeGen.Debug
( whenAsserting )
import CodeGen.IDs
( typeID, constID )
import CodeGen.Instruction
( Args(Arg, EndArgs), toArgs
, ID(ID), Instruction(..)
, Pair(Pair)
)
import CodeGen.Monad
( MonadFresh(fresh)
, CGMonad
)
import CodeGen.Phi
( phiInstruction, phiInstructions )
import CodeGen.State
( LexicalEnv(localBindings)
, EarlyExit(..), LoopExit(..), LoopBlockIDs(..)
, _localBindings, _localBinding, _writableBindings
, _spirvVersion
, scoped
, withLoopFrame, inLoopBodyScope, innermostLoop, loopDepth, addLoopExit
, currentBlockID, enterBlock
)
import Data.Containers.Traversals
( traverseWithKey_ )
import Data.Type.Known
( knownValue )
import Data.Type.Map
( Keys )
import FIR.AST
( AST, Code
, SelectionF(..), LoopF(..), StateF(..)
, pattern (:$), pattern Return
)
import FIR.AST.Type
( AugType(Val), Eff, Nullary )
import FIR.Prim.Types
( IntegralTy, primTy )
import FIR.ProgramState
( Bindings )
import qualified SPIRV.Control as SPIRV
( SelectionControl, pattern NoSelectionControl
, LoopControl, pattern NoLoopControl
)
import qualified SPIRV.Operation as SPIRV.Op
import qualified SPIRV.PrimTy as SPIRV
( PrimTy(..) )
import qualified SPIRV.Version as SPIRV
( Version(..) )
----------------------------------------------------------------------------
-- blocks and branching
-- | Start a new block with the given ID.
block :: ID -> CGMonad ()
block blockID = do
structuralInstruction
Instruction
{ operation = SPIRV.Op.Label
, resTy = Nothing
, resID = Just blockID
, args = EndArgs
}
enterBlock blockID
newBlock :: CGMonad ()
newBlock = fresh >>= block
-- | Whether the code currently being generated is reachable.
inReachableCode :: CGMonad Bool
inReachableCode = isJust <$> currentBlockID
branch :: ID -> CGMonad ()
branch branchID
= instruction
Instruction
{ operation = SPIRV.Op.Branch
, resID = Nothing
, resTy = Nothing
, args = Arg branchID EndArgs
}
branchConditional :: ID -> ID -> ID -> CGMonad ()
branchConditional b t f
= instruction
Instruction
{ operation = SPIRV.Op.BranchConditional
, resTy = Nothing
, resID = Nothing
, args = Arg b
$ Arg t
$ Arg f EndArgs
}
unreachable :: CGMonad ()
unreachable
= instruction
Instruction
{ operation = SPIRV.Op.Unreachable
, resTy = Nothing
, resID = Nothing
, args = EndArgs
}
select :: ID -> (ID, SPIRV.PrimTy) -> (ID, SPIRV.PrimTy) -> CGMonad (ID, SPIRV.PrimTy)
select c (o1, ty1) (o2, ty2) = do
( whenAsserting . when (ty1 /= ty2) )
( throwError
( "select: objects of different types: \n\
\object 1: " <> ShortText.pack (show ty1) <> "\n\
\object 2: " <> ShortText.pack (show ty2) <> "."
)
)
v <- fresh
resTyID <- typeID ty1
instruction
Instruction
{ operation = SPIRV.Op.Select
, resTy = Just resTyID
, resID = Just v
, args = Arg c
$ Arg o1
$ Arg o2
EndArgs
}
pure (v, ty1)
selectionMerge :: ID -> Maybe SPIRV.SelectionControl -> CGMonad ()
selectionMerge mergeBlockID mbControl
= instruction
Instruction
{ operation = SPIRV.Op.SelectionMerge
, resTy = Nothing
, resID = Nothing
, args = Arg mergeBlockID
$ Arg mbControl
EndArgs
}
loopMerge :: ID -> ID -> SPIRV.LoopControl Word32 -> CGMonad ()
loopMerge mergeBlockID loopBlockID loopControl
= instruction
Instruction
{ operation = SPIRV.Op.LoopMerge
, resTy = Nothing
, resID = Nothing
, args = Arg mergeBlockID
$ Arg loopBlockID
$ Arg loopControl
EndArgs
}
multiWaySwitch :: IntegralTy s => ID -> ID -> [Pair s ID] -> CGMonad ()
multiWaySwitch scrut defBlock ids
= instruction
Instruction
{ operation = SPIRV.Op.Switch
, resTy = Nothing
, resID = Nothing
, args = Arg scrut
$ Arg defBlock
$ toArgs ids
}
-- | The values of the bindings (satisfying the predicate) at the start of a block,
-- given the bindings at the end of each of its predecessors.
-- Emits ϕ-instructions as necessary.
mergeBindings
:: ( ShortText -> Bool )
-> [ ( ID, Map ShortText (ID, SPIRV.PrimTy) ) ]
-> CGMonad ( Map ShortText (ID, SPIRV.PrimTy) )
mergeBindings isRelevant preds = case preds of
[] -> pure Map.empty
[ ( _, bds ) ] -> pure ( Map.filterWithKey ( \ name _ -> isRelevant name ) bds )
_ -> do
phis <- phiInstructions isRelevant ( map fst preds ) ( map snd preds )
let
common :: Map ShortText (ID, SPIRV.PrimTy)
common = Map.filterWithKey ( \ name _ -> isRelevant name )
$ foldr1 Map.intersection ( map snd preds )
pure ( Map.mapWithKey ( \ name ( ident, ty ) -> ( Map.findWithDefault ident name phis, ty ) ) common )
-- | Update the values of the given bindings.
updateBindings :: Map ShortText (ID, SPIRV.PrimTy) -> CGMonad ()
updateBindings = traverseWithKey_ \ name bd -> assign ( _localBinding name ) ( Just bd )
----------------------------------------------------------------------------
-- code generation for selections (pure or branching)
instance CodeGen AST => CodeGen (SelectionF AST) where
-- if statement
codeGenArgs (Applied IfF (c `ConsAST` (t :: Code a) `ConsAST ` f `ConsAST` NilAST)) = do
ver <- view _spirvVersion
if canUseSelection (primTy @a) ver
then selection c t f
else ifM c (UCode t) (UCode f)
where
canUseSelection :: SPIRV.PrimTy -> SPIRV.Version -> Bool
canUseSelection (SPIRV.Scalar _) _ = True
canUseSelection (SPIRV.Pointer _) _ = True
canUseSelection ty ver
| ver < SPIRV.Version 1 4 = False
| SPIRV.Matrix {} <- ty = True
| SPIRV.Vector {} <- ty = True
| SPIRV.Array {} <- ty = True
| SPIRV.Struct {} <- ty = True
| otherwise = False
-- monadic if statement
codeGenArgs (Applied IfMF (c `ConsAST` t `ConsAST ` f `ConsAST` NilAST)) = ifM c (UCode t) (UCode f)
-- switch statement
codeGenArgs (Applied (SwitchF scrut def cases) NilAST) =
codeGen
( SwitchMF
( Return :$ scrut )
( Return :$ def )
( map (second (Return :$)) cases )
)
-- monadic switch statement
codeGenArgs (Applied (SwitchMF scrut def cases) NilAST) = switch scrut def (map (second UCode) cases)
selection :: ( CodeGen AST, Nullary a ) => Code Bool -> AST a -> AST a -> CGMonad (ID, SPIRV.PrimTy)
selection cond x y = do
(condID, condTy) <- codeGen cond
( whenAsserting . unless ( condTy == SPIRV.Boolean) )
( throwError
$ "codeGen: ASSERT failed, 'select' expected boolean conditional, but got "
<> ShortText.pack (show condTy)
)
o1 <- codeGen x
o2 <- codeGen y
select condID o1 o2
ifM :: ( CodeGen AST, Nullary b ) => AST b -> UCode -> UCode -> CGMonad (ID, SPIRV.PrimTy)
ifM cond bodyTrue bodyFalse
= branchingSelection conditionalHeader [bodyTrue, bodyFalse]
where
conditionalHeader :: [ID] -> ID -> CGMonad ()
conditionalHeader [trueBlockID, falseBlockID] mergeBlockID = do
(condID, condTy) <- locally (codeGen cond)
( whenAsserting . unless ( condTy == SPIRV.Boolean ) )
( throwError
$ "codeGen: ASSERT failed, 'if' expected boolean conditional, but got "
<> ShortText.pack (show condTy)
)
selectionMerge mergeBlockID SPIRV.NoSelectionControl
branchConditional condID trueBlockID falseBlockID
conditionalHeader ids _ =
throwError
( "codeGen: if statement with "
<> ShortText.pack ( show ( length ids ))
<> " branches"
)
switch :: ( CodeGen AST, IntegralTy t, Nullary s, Nullary a)
=> AST s -> AST a -> [(t, UCode)] -> CGMonad (ID, SPIRV.PrimTy)
switch scrut def cases
= branchingSelection switchHeader (UCode def : map snd cases)
where
switchHeader :: [ID] -> ID -> CGMonad ()
switchHeader [] _ = whenAsserting (throwError "codeGen: ASSERT failed, 'switch' statement missing default case")
switchHeader ( defaultBlockID : caseBlockIDs ) mergeBlockID = do
(scrutID, _) <- locally (codeGen scrut)
selectionMerge mergeBlockID SPIRV.NoSelectionControl
multiWaySwitch scrutID defaultBlockID
(zipWith ( \(t,_) i -> Pair (t,i) ) cases caseBlockIDs)
branchingSelection :: CodeGen AST => ( [ID] -> ID -> CGMonad () ) -> [ UCode ] -> CGMonad (ID, SPIRV.PrimTy)
branchingSelection mkHeader cases = do
live <- inReachableCode
headerBlockID <- fresh
caseBlockIDs <- traverse (\x -> (,x) <$> fresh) cases
mergeBlockID <- fresh
bindingsBefore <- use _localBindings
-- header block
when live do
branch headerBlockID
block headerBlockID
mkHeader ( map fst caseBlockIDs ) mergeBlockID
-- code generation for each of the cases
results <-
for caseBlockIDs \ (caseBlockID, UCode caseBody) -> do
( ( bodyRes, mbEndBlock ), env ) <- scoped do
when live ( block caseBlockID )
bodyRes <- codeGen caseBody
mbEndBlock <- currentBlockID
branch mergeBlockID
pure ( bodyRes, mbEndBlock )
pure ( bodyRes, ( , localBindings env ) <$> mbEndBlock )
( firstRes, resType ) <- case results of
[] -> throwError "codeGen: empty list of branches in conditional"
( ( firstRes@( _, ty ), _ ) : others )
-> do ( whenAsserting . unless ( all ( ( == ty ) . snd . fst ) others ) )
( throwError "codeGen: ASSERT failed, branches of conditional return different types" )
pure ( firstRes, ty )
let
-- the cases which branch to the merge block
preds :: [ ( ID, ( (ID, SPIRV.PrimTy), Map ShortText (ID, SPIRV.PrimTy) ) ) ]
preds = [ ( endBlock, ( bodyRes, bds ) ) | ( bodyRes, Just ( endBlock, bds ) ) <- results ]
if not live
then pure firstRes -- (unreachable code)
else do
-- merge block
block mergeBlockID
case preds of
[] -> do
-- No case reaches the merge block: the code that follows is unreachable.
unreachable
pure firstRes
_ -> do
-- emit the appropriate ϕ-instructions, and update the bindings
updateBindings
=<< mergeBindings ( `Map.member` bindingsBefore ) [ ( b, bds ) | ( b, ( _, bds ) ) <- preds ]
-- return the result
if resType == SPIRV.Unit
then pure (ID 0, resType) -- ID should never be used
else do
res <- fresh
phiInstruction
( res, resType )
[ Pair ( resID, b ) | ( b, ( ( resID, _ ), _ ) ) <- preds ]
pure (res, resType)
----------------------------------------------------------------------------
-- code generation for loops
instance CodeGen AST => CodeGen (LoopF AST) where
codeGenArgs ( Applied WhileF ( c `ConsAST` body `ConsAST` NilAST ) ) = while ( Just c ) body
codeGenArgs ( Applied LoopF ( body `ConsAST` NilAST ) ) = while ( Nothing :: Maybe ( AST ( Val Bool ) ) ) body
codeGenArgs ( Applied ( BreakF ( _ :: Proxy n ) ) NilAST ) = break ( knownValue @n )
codeGenArgs ( Applied ( BreakContinueF ( _ :: Proxy n ) ) NilAST ) = breakContinue ( knownValue @n )
-- | Code generation for a loop (with an optional condition in the loop header).
--
-- Every modifiable binding gets a ϕ-instruction in the loop header. Its operand for the
-- back edge is only known after generating code for the loop body, which refers to the
-- ϕ-instruction: we tie the knot lazily.
while :: ( CodeGen AST, Nullary c, Nullary b ) => Maybe ( AST c ) -> AST b -> CGMonad (ID, SPIRV.PrimTy)
while mbCond loopBody = do
mbBefore <- currentBlockID
case mbBefore of
Nothing -> unreachableLoop
Just beforeBlockID -> reachableLoop beforeBlockID
pure (ID 0, SPIRV.Unit) -- ID should never be used
where
-- (generates code only to obtain the results; nothing is emitted)
unreachableLoop :: CGMonad ()
unreachableLoop = do
loopIDs <- LoopBlockIDs <$> fresh <*> fresh
_ <- scoped $ withLoopFrame loopIDs do
for_ mbCond ( locally . codeGen )
inLoopBodyScope ( codeGen loopBody )
pure ()
reachableLoop :: ID -> CGMonad ()
reachableLoop beforeBlockID = mdo
headerBlockID <- fresh
loopBlockID <- fresh
mergeBlockID <- fresh -- block where control flow merges back
continueBlockID <- fresh -- block at the end of the loop body from which we can continue for further loops
branch headerBlockID
-- Header block.
block headerBlockID
bindingsBefore <- use _localBindings
writable <- use _writableBindings
let
-- Bindings that the loop might modify.
loopCarried :: Map ShortText (ID, SPIRV.PrimTy)
loopCarried = bindingsBefore `Map.restrictKeys` writable
-- value of a binding at the end of the continue block (see below)
continueEndID :: ShortText -> ID
continueEndID name =
maybe
( error $ "codeGen: binding " <> ShortText.unpack name <> " missing at the end of a loop" )
fst
( Map.lookup name continueEnd )
headerPhis <-
Map.traverseWithKey
( \ name ( beforeID, ty ) -> do
v <- fresh
-- NB: the second operand must not be forced during code generation.
phiInstruction ( v, ty )
[ Pair ( beforeID , beforeBlockID )
, Pair ( continueEndID name, continueBlockID )
]
pure ( v, ty )
)
loopCarried
updateBindings headerPhis
loopMerge mergeBlockID continueBlockID SPIRV.NoLoopControl
-- The loop frame collects the early exits from the condition and the body.
( ( headerEndBindings, mbHeaderEndBlockID, mbCondID, mbBodyEndBlock, bodyEndBindings ), loopExits ) <-
withLoopFrame ( LoopBlockIDs { continueBlockID, mergeBlockID } ) do
-- Condition.
mbCondition <- case mbCond of
Nothing -> do
branch loopBlockID
pure Nothing
Just cond -> do
-- The condition gets its own block, as it might contain control flow.
condBlockID <- fresh
branch condBlockID
block condBlockID
(condID, condTy) <- locally (codeGen cond)
( whenAsserting . when ( condTy /= SPIRV.Boolean ) )
( throwError
$ "codeGen: ASSERT failed, while loop expected boolean conditional, but got "
<> ShortText.pack (show condTy)
)
pure ( Just condID )
condEndBindings <- use _localBindings
mbCondEndBlock <- currentBlockID
for_ mbCondition \ condID -> branchConditional condID loopBlockID mergeBlockID
-- Loop body.
( mbEndBlock, env ) <- inLoopBodyScope $ scoped do
block loopBlockID
_ <- codeGen loopBody
mbEndBlock <- currentBlockID
branch continueBlockID
pure mbEndBlock
pure ( condEndBindings, mbCondEndBlock, mbCondition, mbEndBlock, localBindings env )
-- Continue block.
block continueBlockID
let
continuePreds :: [ ( ID, Map ShortText (ID, SPIRV.PrimTy) ) ]
continuePreds
= [ ( b, bodyEndBindings ) | Just b <- [ mbBodyEndBlock ] ]
++ [ ( b, bds ) | ( b, ( ContinueLoop, bds ) ) <- Map.toList loopExits ]
for_ continuePreds \ ( _, bds ) ->
unless ( Map.keysSet loopCarried `Set.isSubsetOf` Map.keysSet bds ) $
throwError "codeGen: early exit from a scope in which bindings modified by the loop are not visible"
continueEnd <- case continuePreds of
-- unreachable continue block: use the ϕ-instructions themselves
[] -> pure headerPhis
_ -> mergeBindings ( `Map.member` loopCarried ) continuePreds
branch headerBlockID
-- Merge block (first block after the loop).
block mergeBlockID
let
-- with the early exit that remains to be performed (if any)
mergePreds :: [ ( ID, Maybe EarlyExit, Map ShortText (ID, SPIRV.PrimTy) ) ]
mergePreds
= [ ( b, Nothing, headerEndBindings ) | isJust mbCondID, Just b <- [ mbHeaderEndBlockID ] ]
++ [ ( b, mbEarlyExit, bds ) | ( b, ( ExitLoop mbEarlyExit, bds ) ) <- Map.toList loopExits ]
case mergePreds of
-- Nothing exits the loop: the code that follows is unreachable.
[] -> unreachable
_ -> do
updateBindings
=<< mergeBindings ( `Map.member` bindingsBefore ) [ ( b, bds ) | ( b, _, bds ) <- mergePreds ]
continueEarlyExits [ ( b, mbEarlyExit ) | ( b, mbEarlyExit, _ ) <- mergePreds ]
-- | Perform the remaining early exits from the merge block of a loop,
-- given the predecessors of the merge block and the early exit that remains for each.
--
-- Emits ϕ-instructions: must be called at the start of the merge block.
continueEarlyExits :: [ ( ID, Maybe EarlyExit ) ] -> CGMonad ()
continueEarlyExits preds = do
let
-- Selector value for each early exit to perform (0 for none).
selectors :: Map EarlyExit Word32
selectors = Map.fromList $ zip ( Set.toList . Set.fromList $ [ ee | ( _, Just ee ) <- preds ] ) [ 1 .. ]
unless ( null selectors ) do
selectorID <- fresh
selectorValues <-
traverse
( \ ( b, mbEarlyExit ) -> ( , b ) <$> constID ( maybe 0 ( selectors Map.! ) mbEarlyExit ) )
preds
phiInstruction ( selectorID, primTy @Word32 ) ( map Pair selectorValues )
caseBlockIDs <- traverse ( const fresh ) selectors
afterBlockID <- fresh
selectionMerge afterBlockID SPIRV.NoSelectionControl
multiWaySwitch selectorID afterBlockID
( Map.elems $ Map.intersectionWith ( \ i caseBlockID -> Pair ( i, caseBlockID ) ) selectors caseBlockIDs )
traverseWithKey_ ( \ ee caseBlockID -> block caseBlockID *> earlyExit ee ) caseBlockIDs
block afterBlockID
-- | Break out of the given number of loops.
break :: Word32 -> CGMonad (ID, SPIRV.PrimTy)
break 0 = pure (ID 0, SPIRV.Unit) -- ID should never be used
break nbLoopsToBreak = do
depth <- loopDepth
when ( depth < fromIntegral nbLoopsToBreak ) $
throwError
( "codeGen: 'break' statement inside too few loops.\n\
\breaking " <> ShortText.pack ( show nbLoopsToBreak ) <> " loops,\n\
\but only inside " <> ShortText.pack ( show depth ) <> " loops."
)
earlyExit ( Break nbLoopsToBreak )
pure (ID 0, SPIRV.Unit) -- ID should never be used
-- | Break out of the given number of loops, and then continue to the next iteration
-- of the enclosing loop.
breakContinue :: Word32 -> CGMonad (ID, SPIRV.PrimTy)
breakContinue nbLoopsToBreak = do
depth <- loopDepth
when ( depth <= fromIntegral nbLoopsToBreak ) $
throwError
( "codeGen: 'breakContinue' statement inside too few loops.\n\
\continuing from start of loop after breaking " <> ShortText.pack ( show nbLoopsToBreak ) <> " loops,\n\
\but only inside " <> ShortText.pack ( show depth ) <> " loops."
)
earlyExit ( BreakContinue nbLoopsToBreak )
pure (ID 0, SPIRV.Unit) -- ID should never be used
-- | Perform an early exit.
--
-- Structured control flow only allows branching to the continue or merge block of the
-- innermost loop: to exit several loops, we exit the innermost loop, and perform the
-- remaining early exit from its merge block. The same goes for an early exit from the
-- condition of a loop, which is lexically outside of the loop.
earlyExit :: EarlyExit -> CGMonad ()
earlyExit ee = do
mbLoop <- innermostLoop
case mbLoop of
Nothing -> throwError "codeGen: early exit outside of a loop"
Just ( LoopBlockIDs { continueBlockID, mergeBlockID }, inBody ) -> do
let
( loopExit, destBlockID ) = case ee of
_ | not inBody -> ( ExitLoop ( Just ee ) , mergeBlockID )
BreakContinue 0 -> ( ContinueLoop , continueBlockID )
BreakContinue n -> ( ExitLoop ( Just ( BreakContinue ( n - 1 ) ) ), mergeBlockID )
Break n
| n <= 1 -> ( ExitLoop Nothing , mergeBlockID )
| otherwise -> ( ExitLoop ( Just ( Break ( n - 1 ) ) ) , mergeBlockID )
mbBlock <- currentBlockID
for_ mbBlock \ currentBlock -> do
currentBindings <- use _localBindings
addLoopExit currentBlock loopExit currentBindings
branch destBlockID
----------------------------------------------------------------------------
-- code-generation for locally / embed
instance CodeGen AST => CodeGen (StateF AST) where
codeGenArgs (Applied LocallyF (a `ConsAST` NilAST)) =
locally (codeGen a)
codeGenArgs (Applied EmbedF ( ( a :: AST ( Eff i i a ) ) `ConsAST` NilAST)) =
embed ( Set.fromList $ knownValue @(Keys (Bindings i)) ) (codeGen a)
locally :: CGMonad a -> CGMonad a
locally action = do
bindingsBefore <- use _localBindings
( res, env ) <- scoped action
let
bindingsAfter :: Map ShortText (ID, SPIRV.PrimTy)
bindingsAfter = localBindings env
-- keep the updated values of local bindings,
-- but only for bindings that were defined before the "locally" block
assign _localBindings ( bindingsAfter `Map.intersection` bindingsBefore )
whenAsserting do
let
weirdos :: Map ShortText ( (ID, SPIRV.PrimTy), (ID, SPIRV.PrimTy) )
weirdos = Map.mapMaybe id
$ Map.intersectionWith ( \ ( id1, ty1 ) ( id2, ty2 ) -> if ty1 == ty2 then Nothing else Just ( ( id1, ty1 ), ( id2, ty2 ) ) )
bindingsBefore bindingsAfter
unless ( null weirdos ) $
throwError ( "'codeGen': ASSERT failed, error in code-generation for 'locally'\nweirdos = " <> ShortText.pack ( show weirdos ) )
pure res
embed :: Set ShortText -> CGMonad a -> CGMonad a
embed innerStartNames action = do
bindingsBefore <- use _localBindings
( res, env ) <- scoped do
assign _localBindings ( Map.restrictKeys bindingsBefore innerStartNames )
action
let
bindingsAfter :: Map ShortText (ID, SPIRV.PrimTy)
bindingsAfter = localBindings env
-- discard the updated values of local bindings that shadowed previous bindings
newBindings :: Map ShortText (ID, SPIRV.PrimTy)
newBindings =
Map.mapWithKey
( \ name aft -> fromMaybe aft (shadows name innerStartNames bindingsBefore ) )
( bindingsAfter `Map.intersection` bindingsBefore
<> Map.withoutKeys bindingsBefore innerStartNames
)
assign _localBindings newBindings
pure res
shadows :: ShortText -> Set ShortText -> Map ShortText (ID, SPIRV.PrimTy) -> Maybe (ID, SPIRV.PrimTy)
shadows name innerStartNames prevs
| not ( name `Set.member` innerStartNames )
, Just prev <- Map.lookup name prevs
= Just prev
| otherwise
= Nothing