{- CAO Compiler
Copyright (C) 2014 Cryptography and Information Security Group, HASLab - INESC TEC and Universidade do Minho
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . -}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ViewPatterns #-}
{- |
Module : $Header$
Description : CAO program simplification.
Copyright : (C) 2014 Cryptography and Information Security Group, HASLab - INESC TEC and Universidade do Minho
License : GPL
Maintainer : Paulo Silva
Stability : experimental
Portability : non-portable
The simplification step aims at reducing the mismatch between CAO and C or,
more precisely, the C backend. Compilers that generate assembly code
traditionally use an intermediate representation known as three-address code,
in which every instruction is in its simpler form with two operand addresses
and one result address. Since we are targeting a software API as a backend, our
format is quite different but shares some of the same principles. Operations
in the backend expect variables or constants as parameters and a variable as
result. This means that nested expressions must be extracted and replaced by
an auxiliary variable. For instance, the following assignment of an arithmetic
expression:
> a := 3 * b + 2 * c - 4;
should be transformed to:
@
def t0 : int;
def t1 : int;
def t2 : int;
t0 := 3 * b;
t1 := 2 * c;
t2 := t0 + t1;
a := t2 - 4;
@
In general, for assignments of results from binary and unary operations, we
must obey the following format in which @op2@ is a binary operator, @op1@ is an
unary operator, @var@ is a variable and @e1@ and @e2@ are either variables or
constants:
@
:=
/ \
var op2
/ \
e1 e2
@
@
:=
/ \
var op1
|
e1
@
Similar formats were defined for the other operations, leading to a normalized
code format, ready to be translated to C. This normalization process also
includes the following actions:
* The initialization of global variables is removed from their declaration and
put in a global init procedure.
* Simultaneous variable declarations are transformed to multiple simple variable
declarations.
* The initializations of variable declarations are removed from declarations
and added as independent statements, except for container initializations
(vectors and matrices). Here, the natural way of simplifying this kind of
initializations would be a position-wise assignment. However, this would
invalidate block initialization during translation. Therefore, only expressions
inside container initializations are simplified.
* Parallel assignments are transformed to several simple assignments
(except parallel assignments from multiple function results which can only be
resolved during translation with the introduction of references).
* All other expressions are simplified in order that operands may be either
variables or constants.
-}
module Language.CAO.Transformation.Simplify (
simplifyCaoAST
) where
import Control.Monad.State
import Data.DList ()
import qualified Data.DList as DL
import qualified Data.List as Lst
import Data.Maybe
import Data.Set ()
import qualified Data.Set as Set
import Language.CAO.Common.Error
import Language.CAO.Common.Fresh
import Language.CAO.Common.Monad
import Language.CAO.Common.Polynomial
import Language.CAO.Common.SrcLoc
import Language.CAO.Common.State
import Language.CAO.Common.Utils
import Language.CAO.Common.Var
import Language.CAO.Index
import Language.CAO.Index.Utils
import Language.CAO.Syntax
import Language.CAO.Syntax.Utils
import Language.CAO.Type
import Language.CAO.Type.Utils
--
--The left value code is not correctly handled during the translation:
-- seq i := 1 to 30 {
-- vec[i+3-n][n*3*i+i] := vec[i+3-n][n*3*i+i];
-- }
--
--Without renaming this temporary variable generation schema fails if there
--is any identifier of the form "t0", "t1", ... on the code
type SS = LStmt Var
type ConstDef = LStmt Var
type VarDeclaration = LStmt Var
-- CaoAST ----------------------------------------------------------------------
-- | Applies the simplification step to the AST. Takes as parameter, the name
-- of the global inititialization procedure.
simplifyCaoAST :: CaoMonad m => String -> Prog Var -> m (Prog Var)
simplifyCaoAST initProcName (Prog defs _) = withSimplifyST $ do
(defs', stmts, cdecl, vdecl) <- concatMapAndUnzip4M simplifyDef defs
-- The global initialization procedure is only necessary if there is
-- something to initialize.
let initDef = if null stmts && null cdecl && null vdecl
then Nothing
else let
-- Written global variables inside the init procedure, i.e.,
-- initialization of global variables.
-- Testing for assignments is necessary [See note 8]
wvars = Set.toList $ Set.filter isGlobalVar $
fvs $ filter (isAssignStmt . unLoc) stmts
fName = globalInit initProcName wvars
body = funcBody cdecl vdecl stmts
in Just $ Fun (genLoc fName) [] [] body
return $ Prog defs' initDef
-- Definition ------------------------------------------------------------------
{-
This function returns:
* A list of global definitions. Since multiple variable definitions are
transformed into several individual declarations, as list is needed.
* A list of assignments used as initialization of global declarations in the
global initialization procedure. We should notice that besides assign
statements, also container declarations are used [See note 8].
* A list of auxiliary constant definitions (declaration + initialization)
to be used in the global initialization procedure.
* A list of auxiliary variable declarations to be used in the global
initialization procedure.
-}
simplifyDef
:: CaoMonad m
=> LDef Var
-> m ( [LDef Var]
, [SS]
, [ConstDef]
, [VarDeclaration]
)
simplifyDef (L l (VarDef vd)) = do
(vd', stmts, index, decl) <- simplifyVarDeclaration vd
return (map (L l . VarDef) vd', stmts, index, decl)
simplifyDef (L l (FunDef f)) = do
f' <- simplifyFunc f
return (L l (FunDef f') : [], [], [], [])
simplifyDef d@(L _ (TyDef _)) = return (d : [], [], [], [])
simplifyDef (L l (ConstDef cd)) = do
(cd', index) <- simplifyConstDeclaration cd True
return (L l (ConstDef cd') : [], [], DL.toList index, [])
-- Func ------------------------------------------------------------------------
{-
Note 1:
The introduction of depedent types implies that type expression have to be
simplified, too. For instance, the index of type:
@vector[3 * n + 1] of int@
must be broken down to simple expressions:
@
t0 := 3 * n;
t1 := t0 + 1;
vector [t1] of int;
@
To simplify this process, a environment was introduced to hold new types for
variables. This means that the simplification process is done only once and
then all uses of the same variable are immediately retyped. This environment
has to be reset everytime a function body is processed.
-}
simplifyFunc :: CaoMonad m =>
Fun Var -> m (Fun Var)
simplifyFunc (Fun fname args rtype body) = do
resetSimplifyST -- [See Note 1]
(body', index, decl) <- simplifyStatements body
let body'' = funcBody' index decl body'
return (Fun fname args rtype body'')
{-
Note 2:
The order of statements inside a function is important since there are
dependencies between declarations, definitions and assignments. Thus, the
body is divided in logical blocks, marked by annotations. The overall schema
is the following:
[ Index (simbolic variable) declarations ]
Nop EndIndex
[ Auxiliary variable declarations ]
Nop EndAux
[ Assignemnts to variables ]
This schema is needed because
- indexes may be used in the declaration of variables.
- subsequent phases of the compiler pipeline must know where each block ends.
(better explain this point).
-}
funcBody :: [ConstDef] -> [VarDeclaration] -> [SS] -> [LStmt Var]
funcBody index decl body = fBody (++) (:) index decl body
funcBody' :: DL.DList ConstDef
-> DL.DList VarDeclaration
-> DL.DList SS -> [LStmt Var]
funcBody' index decl body = DL.toList $
fBody DL.append DL.cons index decl body
fBody :: (t -> t1 -> t2)
-> (Located (Stmt id) -> t2 -> t1)
-> t -> t -> t2 -> t2
fBody append cons index decl body =
index
`append`
((genLoc $ Nop EndIndex) `cons` (decl
`append`
((genLoc $ Nop EndAux) `cons` body)))
-- Statement -------------------------------------------------------------------
-- For statements, this is mostly traversal code.
simplifyStatements
:: CaoMonad m
=> [LStmt Var]
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
simplifyStatements = concatMapAndUnzip3MD simplifyStatement
simplifyStatement
:: CaoMonad m
=> LStmt Var
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
simplifyStatement (L l s) = simplifyStmt l s
simplifyStmt
:: CaoMonad m
=> SrcLoc -> Stmt Var
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
simplifyStmt l (VDecl vd) = simplifyLocalVarDeclaration l vd
simplifyStmt l (CDecl cd) = do
(cd', cdecls) <- simplifyConstDeclaration cd False
return (DL.empty, cdecls `DL.snoc` L l (CDecl cd') , DL.empty)
simplifyStmt l (Assign lv' e') =
case (lv', e') of
(lv:[], e:[] ) -> simplifyAssignment l lv e
(_:_:_, e:[] ) -> simplifyTupleAssignment l lv' e
(_:_:_, _:_:_) -> simplifyMultipleAssignment l lv' e'
_ -> error ".\
\: unexpected case in assignment"
-- XXX: Is it necessary to update type annotations?
simplifyStmt l (FCallS fid exps) = do
(exps', stmts, cdecl, vdecl) <- simplifyExps exps
return (stmts `DL.snoc` L l (FCallS fid (DL.toList exps')), cdecl, vdecl)
simplifyStmt l (Ret exps) = do
(exps', stmts, cdecl, vdecl) <- simplifyExps exps
return (stmts `DL.snoc` L l (Ret (DL.toList exps')), cdecl, vdecl)
simplifyStmt l (Ite i t e) = do
(cond', stmts, cdecl1, vdecl1) <- simplifyExpChoice i
(i', cdecl2, vdecl2) <- simplifyStatements t
(e', cdecl3, vdecl3) <- simplifyM e
return ( stmts `DL.snoc` L l (Ite cond' (DL.toList i') e')
, cdecl1 `DL.append` cdecl2 `DL.append` cdecl3
, vdecl1 `DL.append` vdecl2 `DL.append` vdecl3)
where
simplifyM Nothing = return (Nothing, DL.empty, DL.empty)
simplifyM (Just s) = do
(e'', cdecl, vdecl) <- simplifyStatements s
return (Just (DL.toList e''), cdecl, vdecl)
simplifyStmt l (While cond wstms) = do
(cond', stmts, cdecl1, vdecl1) <- simplifyExpChoice cond
(wstms', cdecl2, vdecl2) <- simplifyStatements wstms
-- The condition has to be added to the end of the body [See Note 3]
let wbody = DL.toList $ wstms' `DL.append` stmts
return ( stmts `DL.snoc` L l (While cond' wbody)
, cdecl1 `DL.append` cdecl2
, vdecl1 `DL.append` vdecl2)
--- XXX: type annotations
-- The simplification of the bound values can make pointer from integers
-- depending of the backend
simplifyStmt l (Seq (SeqIter v s e b r) sstms) = do
-- A type annotation was added because 'simplifyExpChoice' expects it.
-- The bounds must always be of type RInt
(s', st1, cdecl1, vdecl1) <- simplifyExpChoice (annL RInt s)
(e', st2, cdecl2, vdecl2) <- simplifyExpChoice (annL RInt e)
(b', st3, cdecl3, vdecl3) <- simplifyM b
(sstms' , cdecl4, vdecl4) <- simplifyStatements sstms
-- All variables/constants that do not depend on the index may be declared
-- only once outside the body of the sequence.
-- Otherwise, they have to be declared inside the body of the function
let (cdeclS, cdeclO) = innerConsts v $ DL.toList cdecl4 -- [See Note]
(vdeclS, vdeclO) = innerVars (v : declaredConsts cdeclS) $ DL.toList vdecl4
sstms'' = cdeclS ++ vdeclS ++ DL.toList sstms'
cdeclO' = DL.fromList cdeclO
vdeclO' = DL.fromList vdeclO
return ( st1 `DL.append` st2 `DL.append` st3 `DL.snoc`
L l ( Seq (SeqIter v (unTypL s') (unTypL e') b' r) sstms'' )
, cdecl1 `DL.append` cdecl2 `DL.append` cdecl3 `DL.append` cdeclO'
, vdecl1 `DL.append` vdecl2 `DL.append` vdecl3 `DL.append` vdeclO')
where
simplifyM Nothing = return (Nothing, DL.empty, DL.empty, DL.empty)
simplifyM (Just mb) = do
(b', st3, cdecl, vdecl) <- simplifyExpChoice (annL RInt mb)
return (Just (unTypL b'), st3, cdecl, vdecl)
simplifyStmt l (Nop a) = return (DL.singleton (L l (Nop a)), DL.empty, DL.empty)
{-
Note:
Declarations inside sequences can depend on the iteration variable. Moreover,
declarations can also depend on constants which depend themselves on the
iteration variable.
This way, we have to compute the transitive closure of dependencies starting
in the iteration variable. After having the set of all constants, we can also
determine the list of all variables which have dependencies.
All variables whose type dependends on the iteration variable must remain
inside the body of the sequence. All other can be removed to outside the body
and shared between all iterations.
-}
declaredConsts
:: [ConstDef]
-> [Var]
declaredConsts = catMaybes . map (constDecl . unLoc)
where
constDecl (CDecl (ConstD (L _ cd) _ _)) = Just cd
constDecl _ = Nothing
innerConsts
:: Var -> [ConstDef]
-> ([ConstDef], [ConstDef])
innerConsts i cdecls = let
-- The base case are the constants that depend on the index variable
(base, rest) = Lst.partition (Set.member i . fvs) cdecls
in fixpoint rest base
where
fixpoint rest [] = ([], rest)
fixpoint rest base = let
(base' , rest' ) = innerVars (declaredConsts base) rest
(base'', rest'') = fixpoint rest' base'
in (base ++ base'', rest'')
innerVars
:: [Var] -> [VarDeclaration]
-> ([VarDeclaration], [VarDeclaration])
innerVars consts vdecls = Lst.partition (mbr . fvs) vdecls
where
mbr vs = any (\c -> Set.member c vs) consts
{-
Note 3:
If we face a condition on a while statement which is not in the simplified form,
we must simplify it to basic operations. However, unlike if statements, it
is not enough to add them before the beginning of the cycle. We have also to
add this to the end of the body of the cycle, because the condition has to be
calculated in every iteration. Otherwise, we just have an infinit loop whenever
the condition is true for the first values.
For instance:
@
while (3 * i + j < i * j) {
...
i := i + 1;
j := j + 1;
}
@
must be simplified to:
@
t0 := 3 * i;
t1 := t0 + j;
t2 := i * j;
cond := t1 < t2;
while (cond) {
...
i := i + 1;
j := j + 1;
t0 := 3 * i;
t1 := t0 + j;
t2 := i * j;
cond := t1 < t2;
}
@
-}
-- LValue ----------------------------------------------------------------------
simplifyLValue
:: CaoMonad m
=> LVal Var
-> m ( LVal Var
, DL.DList SS
, DL.DList ConstDef
, DL.DList VarDeclaration)
simplifyLValue (LVVar (L l v)) = do
(v', cdecl) <- simplifyVar v
return (LVVar (L l v'), DL.empty, cdecl, DL.empty)
simplifyLValue (LVStruct lv fld) = do
(lv', stmts, cdecl, vdecl) <- simplifyLValue lv
-- XXX: type annotation of fld
return (LVStruct lv' fld, stmts, cdecl, vdecl)
simplifyLValue (LVCont ty lv p) = do
(ty', cdeclt) <- simplifyType ty
(lv', stmts1, cdecl1, vdecl1) <- simplifyLValue lv
(p' , stmts2, cdecl2, vdecl2) <- simplifyPat p
return ( LVCont ty' lv' p'
, stmts1 `DL.append` stmts2
, cdeclt `DL.append` cdecl1 `DL.append` cdecl2
, vdecl1 `DL.append` vdecl2)
-- Assignments -----------------------------------------------------------------
simplifyAssignment
:: CaoMonad m
=> SrcLoc -> LVal Var -> TLExpr Var
-> m ( DL.DList (LStmt Var)
, DL.DList ConstDef
, DL.DList VarDeclaration)
simplifyAssignment loc lv e = do
(lv', stmts1, cdecl1, vdecl1) <- simplifyLValue lv
(e', stmts2, cdecl2, vdecl2) <-
-- When we have a simple left variable, we just have to simplify the assigned
-- expression, and add a new assignment in the end. The use of 'simplifyExp'
-- guarantees that, for instance, 3 + v, is not further simplified.
if' (isSimpleLVal lv') simplifyExp simplifyExpChoice e
return ( stmts1 `DL.append` stmts2 `DL.snoc` L loc (Assign (lv':[]) (e':[]))
, cdecl1 `DL.append` cdecl2
, vdecl1 `DL.append` vdecl2)
{-
Note 6:
The simplification of parallel assignments is trickier because of
its semantics. The assigned value is always the value before the
assignment. Thus, the following example:
@ a, b := b, a; @
is, in fact, the swap of the values between variables 'a' and 'b'.
This has to be expanded to:
@
t0 := b;
t1 := a;
b := t1;
a := t0;
@
to maintain the semantics. However, the code is more complex
and more variables are introduced.
-}
-- Precondition: |lvs| > 1, |exps| > 1, |lvs| = |exps|
simplifyMultipleAssignment
:: CaoMonad m
=> SrcLoc -> [LVal Var] -> [TLExpr Var]
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
simplifyMultipleAssignment _ [] [] = return (DL.empty, DL.empty, DL.empty)
simplifyMultipleAssignment loc (lv:lvs) (e:exps) = do
(lv', vdecl) <- newLVar (typeOf lv)
-- "Frozzing" the values
(stmt1, cdecl1, vdecl1) <- simplifyAssignment loc lv' e
-- Handling the rest of the variables
(stmt2, cdecl2, vdecl2) <- simplifyMultipleAssignment loc lvs exps
-- Assigning the values
(stmt3, cdecl3, vdecl3) <- simplifyAssignment loc lv (toExp lv')
return ( stmt1 `DL.append` stmt2 `DL.append` stmt3
, cdecl1 `DL.append` cdecl2 `DL.append` cdecl3
, vdecl `DL.cons` (vdecl1 `DL.append` vdecl2 `DL.append` vdecl3))
simplifyMultipleAssignment _ _ _ = caoError defSrcLoc $ mkUnknownErr
".\
\: not expected case"
-- Precondition: |lvs| > 1 |e| = 1, e is a function call
simplifyTupleAssignment
:: CaoMonad m
=> SrcLoc -> [LVal Var] -> TLExpr Var
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
-- We need a special case for simultaneous casts of function results, since
-- this cannot be handled by 'simplifyExp'.
simplifyTupleAssignment loc lvs expr =
case expr of
L lc (TyE _ (Cast b tds@(_:_:_) ex@(L _ (TyE _ (FunCall _ _))))) -> do
(ex' , stmts , cdecl1, vdecl1) <- simplifyExp ex
(lvs', assign, cdecl2, vdecl2) <-
concatMapAndUnzip4MD (auxCast lc b) $
zip3 tds lvs (fromTuple $ typeOf ex)
return ( stmts `DL.append` (genLoc (Assign (DL.toList lvs') [ex'])
`DL.cons` assign)
, cdecl1 `DL.append` cdecl2
, vdecl1 `DL.append` vdecl2)
e -> do
(e' , stmts , cdecl1, vdecl1) <- simplifyExp e
(lvs', assign, cdecl2, vdecl2) <- concatMapAndUnzip4MD auxLv lvs
return ( stmts `DL.append` (genLoc (Assign (DL.toList lvs') [e'])
`DL.cons` assign)
, cdecl1 `DL.append` cdecl2
, vdecl1 `DL.append` vdecl2)
where
auxCast lc b (td, lv, te) = let
tlv = typeOf lv
-- TODO: The sintactic equalify is too weak
-- The typechecker could provide an annotation
in if tlv == te
then auxLv lv
else do
(lv', ldecl) <- newLVar te
(assign, cdecl, vdecl) <- simplifyAssignment loc lv $
annL tlv $ L lc $ Cast b [td] (toExp lv')
return (DL.singleton lv', assign, cdecl, ldecl `DL.cons` vdecl)
auxLv lv = if isSimpleLVal lv
then return (DL.singleton lv, DL.empty, DL.empty, DL.empty)
else do
(lv', ldecl) <- newLVar $ typeOf lv
(assign, cdecl, vdecl) <- simplifyAssignment loc lv (toExp lv')
return (DL.singleton lv', assign, cdecl, ldecl `DL.cons` vdecl)
-- ConstDef --------------------------------------------------------------------
simplifyConstDeclaration
:: CaoMonad m
=> ConstDecl Var -> Bool
-> m (ConstDecl Var, DL.DList ConstDef)
simplifyConstDeclaration (ConstD (L l n) b ce) _ =
case ce of
ConstInit _ -> do
let Just e = indConst n
(index, cdecl) <- simplifyIndexChoice e
let n' = setIndConst index n
return (ConstD (L l n') b (ConstInit (ind2Expr index)), cdecl)
_ -> return (ConstD (L l n) b None, DL.empty)
simplifyConstDeclaration _ _ = internalError
"simplifyConstDeclaration" "Not expected multiple constant declarations"
-- VarDeclaration --------------------------------------------------------------
{-
Note 8:
The declaration of variables, may include an optional definition, that should
not appear in the simplified form.
The type of variables must also be simplified so that later usage may benifit
of an already simplified type.
In global variables which are containers, the declaration, is like an
assignment, and the order has to be preserved:
- declaration of auxiliary variables
- simplification of values
- declaration of an auxiliary container of the same type, initialized with
the simplified values
- assignment of the auxiliary container to the global container
For instance, in the declaration:
@ def v3 : vector[3] of register int := { a, b, v1[a] }; @
we have this generated code in the body of the init procedure:
@
def c_t53 : register int;
c_b := 3;
c_t53 := c_v1[c_a];
def c_t54 : vector[3] of register int := {c_a, c_b, c_t53};
c_v3 := c_t54;
@
Putting the declaration next to the other declarations, we would obtain:
@
def c_t53 : register int;
def c_t54 : vector[3] of register int := {c_a, c_b, c_t53};
c_b := 3;
c_t53 := c_v1[c_a];
c_v3 := c_t54;
@
This uses variables before their definition, namely 'c_b' and 'c_t53'.
-}
-- Global Variables
-- XXX: b -> type2TypeDecl?
simplifyVarDeclaration
:: CaoMonad m
=> VarDecl Var
-> m ( [VarDecl Var]
, [SS]
, [ConstDef]
, [VarDeclaration]
)
simplifyVarDeclaration (VarD (L l n) d Nothing) = do
(n', cdecl) <- simplifyVar n
return ([VarD (L l n') d Nothing], [], DL.toList cdecl, [])
simplifyVarDeclaration (VarD (L l x) b (Just e)) = do
(x', cdecl1) <- simplifyVar x
(e', ss, cdecl2, vdecl) <- simplifyExpChoice e
let assign = genLoc $ Assign [LVVar (L l x')] [e']
return ( VarD (L l x') b Nothing : []
, DL.toList $ ss `DL.snoc` assign
, DL.toList $ cdecl1 `DL.append` cdecl2
, DL.toList vdecl)
simplifyVarDeclaration (MultiD xs b) =
concatMapAndUnzip4M (\ x -> simplifyVarDeclaration (VarD x b Nothing)) xs
simplifyVarDeclaration (ContD (L l lx) b es) = do
let ty = varType lx
(ty', cdecl1) <- simplifyType ty
let lx' = L l $ setType ty' lx
(es', ss, cdecl2, vdecl) <- simplifyExps es
tv <- freshVar Local ty'
return ( VarD lx' b Nothing : []
, DL.toList $ ss
`DL.snoc`
-- Declaration used as assignment [See note 8]
(genLoc $ VDecl $ ContD (genLoc tv) b (DL.toList es'))
`DL.snoc`
(genLoc $ Assign [LVVar lx'] [genLoc $ annTyE ty' $ Var tv ])
, DL.toList $ cdecl1 `DL.append` cdecl2
, DL.toList vdecl
)
-- Local Variables
simplifyLocalVarDeclaration
:: CaoMonad m
=> SrcLoc -> VarDecl Var
-> m (DL.DList (LStmt Var), DL.DList ConstDef, DL.DList VarDeclaration)
simplifyLocalVarDeclaration loc (VarD (L l n) _ Nothing) = do
(n', cdecl) <- simplifyVar n
--- XXX: reTypVar??
reTypVar n'
return (DL.singleton $ L loc $ VDecl $ VarD (L l n') (type2TyDecl (varType n')) Nothing
, cdecl
, DL.empty)
simplifyLocalVarDeclaration loc (VarD (L l x) _ (Just e)) = do
(x', cdecl) <- simplifyVar x
(e', ss, cdecl2, vdecls) <- simplifyExpChoice e
let assign = genLoc $ Assign [LVVar (L l x')] [e']
reTypVar x'
return (L loc (VDecl $ VarD (L l x') (type2TyDecl (varType x')) Nothing)
`DL.cons` (ss `DL.snoc` assign)
, cdecl `DL.append` cdecl2
, vdecls)
simplifyLocalVarDeclaration loc (MultiD xs b) = do
concatMapAndUnzip3MD
(\ x -> simplifyLocalVarDeclaration loc (VarD x b Nothing)) xs
simplifyLocalVarDeclaration loc (ContD (L l x) _ es) = do
(x', cdecl) <- simplifyVar x
(es', ss, cdecl2, vdecls) <- simplifyExps es
reTypVar x'
return ( ss `DL.snoc`
(L loc $ VDecl $ ContD (L l x') (type2TyDecl (varType x')) (DL.toList es'))
, cdecl `DL.append` cdecl2
, vdecls)
-- Exp -------------------------------------------------------------------------
simplifyExps
:: CaoMonad m
=> [TLExpr Var]
-> m ( DL.DList (TLExpr Var)
, DL.DList SS
, DL.DList ConstDef
, DL.DList VarDeclaration
)
simplifyExps = fold4M
simplifyExpChoice
DL.cons DL.append DL.append DL.append
(DL.empty, DL.empty, DL.empty, DL.empty)
{-
Note 4:
There are two simplification functions to expressions, with a little different
behavior: (the naming is not the better one)
- simplifyExp: Expressions are only simplified if they are operations on other
operations. This means that variables and constants as operands are left
as they are.
- simplifyExpChoice: Only constants and variables are left as they are;
all other expressions are assigned to a new variable.
-}
simplifyExp
:: CaoMonad m
=> TLExpr Var
-> m (TLExpr Var, DL.DList SS, DL.DList ConstDef, DL.DList VarDeclaration)
simplifyExp (L l e) = do
(e', as, cdecl, vdecl) <- simplExp e
return (L l e', as, cdecl, vdecl)
-- XXX: should the type annotation be modified using simplifyType?
simplExp
:: CaoMonad m
=> TExpr Var
-> m (TExpr Var, DL.DList SS, DL.DList ConstDef, DL.DList VarDeclaration)
simplExp (TyE t l@(Lit _)) = do
(t', cdecl) <- simplifyType t
return (TyE t' l, DL.empty, cdecl, DL.empty)
simplExp (TyE _ (Var v)) = do
(v', cdecl) <- simplifyTVar v
return (v', DL.empty, cdecl, DL.empty)
simplExp (TyE t (FunCall f es)) = do
-- XXX: annotation on f?
(t', cdecl) <- simplifyType t
(es', stmts, cdecls, vdecls) <- simplifyExps es
return ( TyE t' (FunCall f (DL.toList es'))
, stmts
, cdecl `DL.append` cdecls
, vdecls)
simplExp (TyE t (StructProj ea n)) = do
-- XXX: annotation on n
(t', cdecl) <- simplifyType t
(ea', stmts, cdecls, vdecls) <- simplifyExpChoice ea
return (TyE t' (StructProj ea' n), stmts, cdecl `DL.append` cdecls, vdecls)
simplExp (TyE t (UnaryOp op e)) = do
(t', cdecl) <- simplifyType t
(e', ss, cdecls, vdecls) <- simplifyExpChoice e
return (TyE t' (UnaryOp op e'), ss, cdecl `DL.append` cdecls, vdecls)
simplExp (TyE t (BinaryOp op l r)) = do
(t', cdecl) <- simplifyType t
((l',r'), ss, cdecls, vdecls) <- simplifyBinaryExp l r
return (TyE t' (BinaryOp op l' r'), ss, cdecl `DL.append` cdecls, vdecls)
simplExp (TyE ty (Access e p)) = do
(ty', cdecl) <- simplifyType ty
(e', ss1, cdecls1, vdecls1) <- simplifyExpChoice e
(p', ss2, cdecls2, vdecls2) <- simplifyPat p
return (TyE ty' (Access e' p')
, ss1 `DL.append` ss2
, cdecl `DL.append` cdecls1 `DL.append` cdecls2
, vdecls1 `DL.append` vdecls2)
-- XXX: update type declaration
simplExp (TyE ty (Cast b td e)) = do
(ty', cdecl) <- simplifyType ty
(e', stmts, cdecls, vdecl) <- simplifyExpChoice e
return ( TyE ty' (Cast b td e')
, stmts
, cdecl `DL.append` cdecls
, vdecl)
-- Simplifies both operands of a binary expression
simplifyBinaryExp :: CaoMonad m => TLExpr Var -> TLExpr Var
-> m ((TLExpr Var, TLExpr Var)
, DL.DList SS
, DL.DList ConstDef
, DL.DList VarDeclaration)
simplifyBinaryExp l r = do
(l', stmts1, index1, decl1) <- simplifyExpChoice l
(r', stmts2, index2, decl2) <- simplifyExpChoice r
return ((l', r')
, stmts1 `DL.append` stmts2
, index1 `DL.append` index2
, decl1 `DL.append` decl2)
simplifyExpChoice
:: CaoMonad m
=> TLExpr Var
-> m (TLExpr Var, DL.DList SS, DL.DList ConstDef, DL.DList VarDeclaration)
-- Literals: there is no need to introduce a new variable
simplifyExpChoice (L loc (TyE t l@(Lit _))) = do
(t', cdecl) <- simplifyType t
return (L loc (TyE t' l), DL.empty, cdecl, DL.empty)
-- Variables: there is no need to introduce a new variable. Type annotations
-- are updated.
simplifyExpChoice (L l (TyE _ (Var v))) = do
(v', cdecl) <- simplifyTVar v
return (L l v', DL.empty, cdecl, DL.empty)
simplifyExpChoice e = do
(e', stmts, cdecl, decl1) <- simplifyExp e
(ve, assign, decl2) <- assignToNewVar e'
return (ve, stmts `DL.snoc` assign, cdecl, decl1 `DL.snoc` decl2)
--------------------------------------------------------------------------------
-- Accesses: just boilerplate
simplifyPat
:: CaoMonad m => APat Var
-> m (APat Var, DL.DList SS, DL.DList ConstDef, DL.DList VarDeclaration)
simplifyPat (VectP r) = do
(r', ss, cdecls, vdecls) <- simplifyRowPat r
return (VectP r', ss, cdecls, vdecls)
simplifyPat (MatP r c) = do
(r', ss1, cdecls1, vdecls1) <- simplifyRowPat r
(c', ss2, cdecls2, vdecls2) <- simplifyRowPat c
return ( MatP r' c'
, ss1 `DL.append` ss2
, cdecls1 `DL.append` cdecls2
, vdecls1 `DL.append` vdecls2)
simplifyRowPat
:: CaoMonad m
=> RowAPat Var
-> m (RowAPat Var, DL.DList SS, DL.DList ConstDef, DL.DList VarDeclaration)
simplifyRowPat (CElem e) = do
(e', ss, cdecls, vdecls) <- simplifyExpChoice e
return (CElem e', ss, cdecls, vdecls)
simplifyRowPat (CRange e1 e2) = do
(e1', ss1, cdecls1, vdecls1) <- simplifyExpChoice e1
(e2', ss2, cdecls2, vdecls2) <- simplifyExpChoice e2
return ( CRange e1' e2'
, ss1 `DL.append` ss2
, cdecls1 `DL.append` cdecls2
, vdecls1 `DL.append` vdecls2)
--------------------------------------------------------------------------------
-- Types
simplifyVar
:: CaoMonad m
=> Var
-> m (Var, DL.DList ConstDef)
simplifyVar v = do
mv <- lookupReTypVar v
case mv of
Nothing -> do
(t, cdecl) <- simplifyType $ varType v
return (setType t v, cdecl)
Just v' -> return (v', DL.empty)
simplifyTVar
:: CaoMonad m
=> Var
-> m (TExpr Var, DL.DList ConstDef)
simplifyTVar v = do
(v', cdecl) <- simplifyVar v
return (annTyE (varType v') $ Var v', cdecl)
simplifyType
:: CaoMonad m
=> Type Var
-> m (Type Var, DL.DList ConstDef)
simplifyType (Tuple tlst) = do
(tlst', cdecls) <- fold2M' simplifyType (flip DL.snoc) DL.append (DL.empty, DL.empty) tlst
return (Tuple $ DL.toList tlst', cdecls)
simplifyType (Bits s n) = do
(n', cdecls) <- simplifyIndexChoice n
return (Bits s n', cdecls)
simplifyType (Vector n t) = do
(n', cdecls1) <- simplifyIndexChoice n
(t', cdecls2) <- simplifyType t
return (Vector n' t', cdecls1 `DL.append` cdecls2)
simplifyType (Matrix n m t) = do
(n', cdecls1) <- simplifyIndexChoice n
(m', cdecls2) <- simplifyIndexChoice m
(t', cdecls3) <- simplifyType t
return (Matrix n' m' t', cdecls1 `DL.append` cdecls2 `DL.append` cdecls3)
simplifyType (Mod Nothing Nothing (Pol [Mon (CoefI m) EZero])) = do
(m', cdecls) <- simplifyIndexChoice m
return (Mod Nothing Nothing (Pol [Mon (CoefI m') EZero]), cdecls)
simplifyType t = return (t, DL.empty)
simplifyIndex
:: CaoMonad m
=> IExpr Var
-> m (IExpr Var, DL.DList ConstDef)
simplifyIndex n@(IInt _) = return (n, DL.empty)
simplifyIndex v@(IInd _) = return (v, DL.empty)
simplifyIndex (IArith op e1 e2) = do
(e1', stmts1) <- simplifyIndexChoice e1
(e2', stmts2) <- simplifyIndexChoice e2
return (IArith op e1' e2', stmts1 `DL.append` stmts2)
simplifyIndex (ISym e) = do
(e', stmts) <- simplifyIndexChoice e
return (ISym e', stmts)
simplifyIndex (ISum slst) = simplifySum slst
-- This function takes a sum of terms and returns a tree of binary additions.
simplifySum
:: CaoMonad m
=> [IExpr Var] -- List of terms
-> m (IExpr Var, DL.DList ConstDef)
simplifySum [] = internalError "simplifySum" "Empty sum!"
-- When we have the sum of just one term, we can remove the sum
simplifySum [e] = simplifyIndex e
-- The general base case has two terms
simplifySum [e1, e2] = do
(e1', stmts1) <- simplifyIndexChoice e1
(e2', stmts2) <- simplifyIndexChoice e2
return (ISum [e1', e2'], stmts1 `DL.append` stmts2)
simplifySum (e:lest) = do
(e', stmts1) <- simplifyIndexChoice e
(lest', stmts2) <- simplifySum lest
(iv, cdecl) <- newIndexDef lest'
return (ISum [e', iv], stmts1 `DL.append` stmts2 `DL.snoc` cdecl)
simplifyIndexChoice
:: CaoMonad m
=> IExpr Var
-> m (IExpr Var, DL.DList ConstDef)
simplifyIndexChoice n@(IInt _) = return (n, DL.empty)
simplifyIndexChoice v@(IInd _) = return (v, DL.empty)
simplifyIndexChoice e = do
(e', cdecls) <- simplifyIndex e
(iv, cdecl) <- newIndexDef e'
return (iv, cdecls `DL.snoc` cdecl)
--------------------------------------------------------------------------------
-- Variable generation
-- Generates a new local variable with the respective declaration.
newVariable :: CaoMonad m => Type Var -> m (Var, VarDeclaration)
newVariable typ = do
tv <- freshVar Local typ
let decl = genLoc $ VDecl $ VarD (genLoc tv) (type2TyDecl typ) Nothing
return (tv, decl)
-- New left variable with the respective declaration
newLVar :: CaoMonad m => Type Var -> m (LVal Var, VarDeclaration)
newLVar = liftM (mapFst (LVVar . genLoc)) . newVariable
-- Given an expression, returns a new variable with the same type, together with
-- its declaration and an assignment of the expression.
-- E.g.
-- Literal 3 of type int
-- assignToNewVar 3 -> (t0, def t0 : int, t0 := 3)
-- Sum of two integers
-- assignToNewVar (3+i) -> (t0, def t0: int, t0 := 3 + i)
-- Here, SS introduces an assignment
assignToNewVar :: CaoMonad m => TLExpr Var -> m (TLExpr Var, SS, VarDeclaration)
assignToNewVar e = do
let ty = typeOf e
(tv, decl) <- newVariable ty
let assign = genLoc $ Assign [LVVar (genLoc tv)] [e]
return (genLoc $ TyE ty $ Var tv, assign, decl)
{-
Note 7:
The first version of this functions was introducing ordinary variables to
simplify type expression. Altough this would generate valid C code (because
the static library does not know anything about index constants), this breaks
the correctness of the intermediate CAO code. Moreover, the optimization stage
loses this important meta-information.
To maintain the correctness of the intermediate CAO program, a new local index
constant has to be declared and defined. Since, by definition, constants cannot
be assigned, their value has to be set during declaration (declaration and
definition are simultaneous), and can only depend on other constants.
-}
newIndexDef :: CaoMonad m => IExpr Var -> m (IExpr Var, ConstDef)
newIndexDef e = do
let ty = typeOf e
tv <- freshIndex Local ty
let decl = genLoc $ CDecl $
ConstD (genLoc tv) (type2TyDecl ty) (ConstInit (ind2Expr e))
return (IInd tv, decl)
--------------------------------------------------------------------------------
-- Auxiliary functions
toExp :: LVal Var -> TLExpr Var
toExp (LVVar (L l v)) = L l $ annTyE (varType v) $ Var v
toExp _ = error ".\
\: undefined case"
moduleName :: String
moduleName = ""
internalError :: String -> String -> a
internalError funcName msg = error $
moduleName ++ ".<" ++ funcName ++ ">: " ++ msg