indigo-0.6.0: src/Indigo/Compilation/Sequential.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
-- | 'Instruction' datatype and its compilations.
--
-- The idea behind this module is to provide an intermediate representation that
-- can:
--
-- - be generated from the frontend freer monad
-- - be compiled to the backend 'IndigoState'
-- - be easy to analyze, manipulate and modify
--
-- This is meant to be the common ground for modular optimizations on the code
-- before this is handed off to the backend and eventually translated to
-- Michelson.
module Indigo.Compilation.Sequential
( Block
, Instruction (..)
, IndigoSeqCaseClause (..)
, CaseBranch (..)
-- * Translations
, SequentialHooks (..)
, stmtHookL
, InstrCollector (..)
, indigoMtoSequential
, sequentialToLorentz
-- * Case machinery
, updateClauses
, mapMClauses
) where
import Prelude
import Data.Vinyl.Core (RMap(..))
import Indigo.Backend
import Indigo.Common.SIS
import Indigo.Common.State hiding ((>>))
import Indigo.Common.State qualified as St
import Indigo.Common.Var
import Indigo.Compilation.Sequential.Types
import Indigo.Frontend.Internal.Statement qualified as S
import Indigo.Frontend.Program (IndigoM(..), interpretProgram)
import Indigo.Lorentz hiding (comment)
import Morley.Michelson.Typed qualified as MT
----------------------------------------------------------------------------
-- Translations
----------------------------------------------------------------------------
-- | Transformation from 'IndigoM' to a 'Block' of 'Instruction's.
--
-- Requires the first non-used 'RefId' and returns the next one.
indigoMtoSequential
:: RefId
-> SequentialHooks
-> IndigoM a
-> (Block, RefId)
indigoMtoSequential refId hook code =
let InstrCollector {..} = snd $ instrCollect refId hook code
in (instrList, nextRef)
-- | Collects instructions starting from an 'IndigoM'.
-- Returns an 'InstrCollector' as well as the return value for that 'IndigoM'.
instrCollect :: RefId -> SequentialHooks -> IndigoM a -> (a, InstrCollector)
instrCollect ref hooks (IndigoM imCode) =
let instrColl = InstrCollector ref [] hooks
(res, resColl) = usingState instrColl $ interpretProgram collectStatement imCode
in (res, InstrCollector (nextRef resColl) (reverse $ instrList resColl) hooks)
-- | Collects instructions starting from 'S.StatementF'.
-- IMPORTANT: the instructions are collected in the opposite order (as a stack).
collectStatement :: S.StatementF IndigoM a -> State InstrCollector a
collectStatement = \case
S.LiftIndigoState is -> appendNewInstr $ LiftIndigoState is
S.CalledFrom callStk iM -> do
InstrCollector nRef _prevInstrs hooks <- get
let (res, inner) = instrCollect nRef hooks iM
modify $ \s -> s {nextRef = nextRef inner}
res <$ shStmtHook hooks callStk (instrList inner)
S.NewVar ex -> do
var <- mkNextVar
appendNewInstr $ AssignVar var ex
return var
S.SetVar vx ex -> appendNewInstr $ SetVar vx ex
S.VarModification upd vx ey -> appendNewInstr $ VarModification upd vx ey
S.SetField vStore fname exF -> appendNewInstr $ SetField vStore fname exF
S.LambdaCall1 lKind lName vIm ex -> withLambdaKind lKind $ do
(var, block, ret, retVars) <- collectInLambda vIm
appendNewInstr $ LambdaCall1 lKind lName ex var block ret retVars
return retVars
S.Scope (iM :: IndigoM ret) -> do
retVars <- allocateVars @ret mkNextVar
(ret, block) <- collectInner iM
appendNewInstr $ Scope block ret retVars
return retVars
S.If ex (iMa :: IndigoM ret) iMb -> do
retVars <- allocateVars @ret mkNextVar
(retA, blockA) <- collectInner iMa
(retB, blockB) <- collectInner iMb
appendNewInstr $ If ex blockA retA blockB retB retVars
return retVars
S.IfSome ex (vIMa :: Var x -> IndigoM ret) iMb -> do
retVars <- allocateVars @ret mkNextVar
varX <- mkNextVar
(retA, blockA) <- collectInner $ vIMa varX
(retB, blockB) <- collectInner iMb
appendNewInstr $ IfSome ex varX blockA retA blockB retB retVars
return retVars
S.IfRight ex (vIMa :: Var x -> IndigoM ret) vIMb -> do
retVars <- allocateVars @ret mkNextVar
varR <- mkNextVar
(retA, blockA) <- collectInner $ vIMa varR
varL <- mkNextVar
(retB, blockB) <- collectInner $ vIMb varL
appendNewInstr $ IfRight ex varR blockA retA varL blockB retB retVars
return retVars
S.IfCons ex (vvIMa :: Var x -> Var (List x) -> IndigoM ret) iMb -> do
retVars <- allocateVars @ret mkNextVar
varX <- mkNextVar
varLX <- mkNextVar
(retA, blockA) <- collectInner $ vvIMa varX varLX
(retB, blockB) <- collectInner iMb
appendNewInstr $ IfCons ex varX varLX blockA retA blockB retB retVars
return retVars
S.Case grd clauses -> do
retVars <- allocateClausesVars clauses
blockClauses <- collectClauses clauses
appendNewInstr $ Case grd blockClauses retVars
return retVars
S.EntryCase proxy grd clauses -> do
retVars <- allocateClausesVars clauses
blockClauses <- collectClauses clauses
appendNewInstr $ EntryCase proxy grd blockClauses retVars
return retVars
S.EntryCaseSimple grd clauses -> do
retVars <- allocateClausesVars clauses
blockClauses <- collectClauses clauses
appendNewInstr $ EntryCaseSimple grd blockClauses retVars
return retVars
S.While ex iM -> do
((), block) <- collectInner iM
appendNewInstr $ While ex block
S.WhileLeft ex vIm -> do
varL <- mkNextVar
varR <- mkNextVar
((), block) <- collectInner $ vIm varL
appendNewInstr $ WhileLeft ex varL block varR
return varR
S.ForEach ex vIm -> do
varIop <- mkNextVar
((), block) <- collectInner $ vIm varIop
appendNewInstr $ ForEach ex varIop block
S.ContractName tx iM -> do
((), block) <- collectInner iM
appendNewInstr $ ContractName tx block
S.DocGroup dg iM -> do
((), block) <- collectInner iM
appendNewInstr $ DocGroup dg block
S.ContractGeneral iM -> do
((), block) <- collectInner iM
appendNewInstr $ ContractGeneral block
S.FinalizeParamCallingDoc vIm param -> do
varCp <- mkNextVar
((), block) <- collectInner $ vIm varCp
appendNewInstr $ FinalizeParamCallingDoc varCp block param
S.TransferTokens ex exm exc ->
appendNewInstr $ TransferTokens ex exm exc
S.SetDelegate ex ->
appendNewInstr $ SetDelegate ex
S.CreateContract ctrc exk exm exs -> do
varAddr <- mkNextVar
appendNewInstr $ CreateContract ctrc exk exm exs varAddr
return varAddr
S.SelfCalling proxy ep -> do
varCR <- mkNextVar
appendNewInstr $ SelfCalling proxy ep varCR
return varCR
S.ContractCalling proxy epRef exAddr -> do
varMcr <- mkNextVar
appendNewInstr $ ContractCalling proxy epRef exAddr varMcr
return varMcr
S.Emit tag ex ->
appendNewInstr $ Emit tag ex
S.Fail (_ :: Proxy ret) failure -> do
appendNewInstr $ Fail failure
-- Note: because this is a failing instr, this vars are effectively never used
allocateVars @ret mkNextVar
S.FailOver (_ :: Proxy ret) failure ex -> do
appendNewInstr $ FailOver failure ex
-- Note: because this is a failing instr, this vars are effectively never used
allocateVars @ret mkNextVar
-- | Continues collecting 'Instruction's from an inner 'IndigoM' (e.g. scoped).
-- This keeps advancing the ref counter as well.
collectInner :: IndigoM ret -> State InstrCollector (ret, Block)
collectInner iM = do
iColl <- get
let (ret, InstrCollector newRef block _) = instrCollect (nextRef iColl) (seqHooks iColl) iM
put $ iColl {nextRef = newRef}
return (ret, block)
-- | Just a common set of steps used by collection of single-arg lambda's values.
collectInLambda
:: ScopeCodeGen ret
=> (Var arg -> IndigoM ret)
-> State InstrCollector (Var arg, Block, ret, RetVars ret)
collectInLambda vIm = do
var <- mkNextVar
(ret :: ret, block) <- collectInner $ vIm var
retVars <- allocateVars @ret mkNextVar
return (var, block, ret, retVars)
-- | Append a new 'Instruction' to the head of the list in the state.
appendNewInstr :: Instruction -> State InstrCollector ()
appendNewInstr is = modify $ \iColl -> iColl {instrList = is : instrList iColl}
-- | Creates a new var. This simply advances the ref counter and updates it.
mkNextVar :: State InstrCollector (Var a)
mkNextVar = do
iColl <- get
let ref = nextRef iColl
put $ iColl {nextRef = ref + 1}
return $ Var ref
-- | Translation from a 'Block' and an initial 'MetaData' to Lorentz.
sequentialToLorentz
:: MetaData inp
-> (Block, RefId)
-> inp :-> inp
sequentialToLorentz md block =
runSIS (sequentialToSIS block) md St.cleanGenCode
-- | Translation from a 'Block' to a 'SomeIndigoState'.
sequentialToSIS :: (Block, RefId) -> SomeIndigoState inp
sequentialToSIS ([], _) = toSIS St.nopState
sequentialToSIS (x : xs, refId) = instrToSIS refId x `thenSIS` sequentialToSIS (xs, refId)
-- | Translation from a single 'Instruction' to a 'SomeIndigoState'.
instrToSIS :: RefId -> Instruction -> SomeIndigoState inp
instrToSIS nextRef = \case
LiftIndigoState sis -> sis
Comment txt -> toSIS $ comment $ MT.JustComment txt
AssignVar vx ex -> toSIS $ assignVar vx ex
SetVar vx ex -> toSIS $ setVar nextRef vx ex
VarModification upd vx ey -> toSIS $ updateVar nextRef upd vx ey
SetField vSt lName ex -> toSIS $ setField nextRef vSt lName ex
LambdaCall1 lKind _lName ex var block ret retVars ->
withLambdaKind lKind $
toSIS $ scope (sequentialToSIS (AssignVar var ex : block, nextRef)) ret retVars
CreateLambda1 lamMd _var body ret varLam ->
toSIS $ createLambda1Generic varLam ret lamMd (sequentialToSIS (body, nextRef))
ExecLambda1 lKind (Proxy :: Proxy ret) ex varLam retVars ->
toSIS $ executeLambda1 @ret lKind nextRef retVars varLam ex
Scope block ret retVars ->
toSIS $ scope (sequentialToSIS (block, nextRef)) ret retVars
If ex blockA retA blockB retB retVars ->
toSIS $ if_ ex (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars
IfSome ex varX blockA retA blockB retB retVars ->
toSIS $ ifSome ex varX (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars
IfRight ex varR blockA retA varL blockB retB retVars ->
toSIS $ ifRight ex varR (sequentialToSIS (blockA, nextRef)) retA varL (sequentialToSIS (blockB, nextRef)) retB retVars
IfCons ex varX varLX blockA retA blockB retB retVars ->
toSIS $ ifCons ex varX varLX (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars
Case grd blockClauses retVars ->
toSIS $ caseRec grd (clausesToBackend nextRef blockClauses) retVars
EntryCase proxy grd blockClauses retVars ->
toSIS $ entryCaseRec proxy grd (clausesToBackend nextRef blockClauses) retVars
EntryCaseSimple grd blockClauses retVars ->
toSIS $ entryCaseSimpleRec grd (clausesToBackend nextRef blockClauses) retVars
While ex block ->
toSIS $ while ex (sequentialToSIS (block, nextRef))
WhileLeft ex varL block varR ->
toSIS $ whileLeft ex varL (sequentialToSIS (block, nextRef)) varR
ForEach ex varIop block ->
toSIS $ forEach ex varIop (sequentialToSIS (block, nextRef))
ContractName tx block ->
docGroup (DName tx) (sequentialToSIS (block, nextRef))
DocGroup dg block ->
docGroup dg (sequentialToSIS (block, nextRef))
ContractGeneral block ->
docGroup DGeneralInfoSection (sequentialToSIS (block, nextRef))
FinalizeParamCallingDoc varCp block param ->
finalizeParamCallingDoc varCp (sequentialToSIS (block, nextRef)) param
TransferTokens ex exm exc ->
toSIS $ transferTokens ex exm exc
SetDelegate ex ->
toSIS $ setDelegate ex
CreateContract ctrc exk exm exs varAddr ->
toSIS $ createContract ctrc exk exm exs varAddr
SelfCalling (Proxy :: Proxy p) ep varCR ->
toSIS $ selfCalling @p ep varCR
ContractCalling (Proxy :: Proxy (cp, vd)) epRef exAddr varMcr ->
toSIS $ contractCalling @cp @vd epRef exAddr varMcr
Emit tag ex ->
toSIS $ emit tag ex
Fail failure -> failure
FailOver failure ex -> failure ex
----------------------------------------------------------------------------
-- Case machinery
----------------------------------------------------------------------------
-- | Convert clauses from their "sequential" representation to the "backend" one.
clausesToBackend
:: forall ret dt . RMap dt
=> RefId
-> Rec (IndigoSeqCaseClause ret) dt
-> Rec (IndigoCaseClauseL ret) dt
clausesToBackend nextRef = rmap $
\(OneFieldIndigoSeqCaseClause cName (CaseBranch vx block ret)) ->
cName /-> (IndigoClause vx (sequentialToSIS (block, nextRef)) ret)
-- | Allocate vars for the return value(s) of a clause-like 'Instruction'.
allocateClausesVars
:: forall ret dt. ReturnableValue ret
=> Rec (S.IndigoMCaseClauseL IndigoM ret) dt
-> State InstrCollector (RetVars ret)
allocateClausesVars _ = allocateVars @ret mkNextVar
-- | Collects clauses of a case-like statement.
collectClauses
:: Rec (S.IndigoMCaseClauseL IndigoM ret) dt
-> State InstrCollector (Rec (IndigoSeqCaseClause ret) dt)
collectClauses RNil = return RNil
collectClauses ((S.OneFieldIndigoMCaseClauseL cName clause) :& xs) = do
varX <- mkNextVar
(ret, block) <- collectInner $ clause varX
let clauseX = OneFieldIndigoSeqCaseClause cName (CaseBranch varX block ret)
clauseXs <- collectClauses xs
return $ clauseX :& clauseXs
-- | Applies the given 'Block' to 'Block' transformation to the inner code block
-- of every case clause.
updateClauses
:: (Block -> Block)
-> Rec (IndigoSeqCaseClause ret) dt
-> Rec (IndigoSeqCaseClause ret) dt
updateClauses _ RNil = RNil
updateClauses f (x :& xs) = case x of
OneFieldIndigoSeqCaseClause cName (CaseBranch vx block ret) ->
OneFieldIndigoSeqCaseClause cName (CaseBranch vx (f block) ret)
:& updateClauses f xs
-- | Applies the given monadic function giving it the inner code block of each
-- case clause, in order.
mapMClauses :: Monad m => (Block -> m ()) -> Rec (IndigoSeqCaseClause ret) dt -> m ()
mapMClauses _ RNil = return ()
mapMClauses f (x :& xs) = case x of
OneFieldIndigoSeqCaseClause _cName (CaseBranch _ block _) ->
f block >> mapMClauses f xs