aos/tools/fof/IL/Paka/Optimizer.lhs
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

225 lines
7.9 KiB
Plaintext

%if false
Copyright (c) 2009, ETH Zurich.
All rights reserved.
This file is distributed under the terms in the attached LICENSE file.
If you do not find this file, copies can be found by writing to:
ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
%endif
%include polycode.fmt
%if false
> module IL.Paka.Optimizer where
> import Data.List
> import qualified Data.Map as Map
> import IL.Paka.Syntax
> import IL.Paka.Compile
%endif
\section{@IL.Paka@ Code Optimizer}
\label{sec:il_paka_optimizer}
The currently implemented optimizer is a naive redundant assignment
simplifier, which happens to do constant propagation at the same
time. It is naive in the several dimensions. An important one is that
it is entirely hard-coded, while we all know that optimization is
simply a matter of dataflow analysis. So, at some point, we should use
a more generic framework for that. It is also naive because it does
not try to reach a fix-point: it is single phase, while it is obvious
that more assignments could still be eliminated in subsequent
phases. Finally, it is naive because any case that was not easy to
deal with have been discarded: more redundant assignments could be
removed if the logic were more precise.
The purpose of that module is to show that ``it is possible to do
optimization''. It is a proof of concept. Now, it is Future Work
(Chapter~\ref{chap:future_work}) to get a clever optimization
framework. The ease I had in implementing that stuff convince me that
we are not far from this heaven.
So, if you want optimized Paka code, you will only get a slightly less
redundant code:
> optimizePaka :: PakaCode -> PakaCode
> optimizePaka = optimizeAssgmtElim
Because this analysis is intra-procedural, we go over each function
and apply an intra-procedural optimizer:
> optimizeAssgmtElim :: PakaCode -> PakaCode
> optimizeAssgmtElim code = code { functions = optFunc }
> where funcs = functions code
> optFunc = Map.mapMaybe (\(b,c,d,e,f,fun) -> Just (b,c,d,e,f, assgmtElim fun)) funcs
\subsection{Implementation}
This optimizer is quite easy to implement, assuming we have the right
tools at hand. That is, assuming that we are able to replace a
variable |x| by a variable |y| in a code |k| -- using
|replace (Var x) (Var y) k| --, that we are able to say if a
variable |y| is either a constant or never used in a code |k| -- using
|isUsed flatten y k| --, and that we are able to say if a variable |x|
is used without side-effects in a code |k| --
using |isUsed flattenS x k|.
The intra-procedural optimizer will turn an |ILPaka| into a better
|ILPaka|:
> assgmtElim :: ILPaka -> ILPaka
The interesting case is obviously the variable assignment: a value
|y| is assigned to a variable |x|. We remove that assignment and
replace |x| by |y| if and only if |y| is never used again and |x| is
not involved in some weird computation. Otherwise, we go ahead.
A small issue here is that we ask for |y| to be never used
again. That's quite restrictive. This results in being able to carry
only 4 assignment eliminations on today's Hamlet and Fugu inputs. This
is shame, compared to the numerous opportunities. To solve that issue,
we would have to extend or re-design |isUsed| to allow the definition
of more fine-grained predicates, such as ``is overwritten''.
> assgmtElim (PStatement a@(PAssign (Var x) _ [Var y]) k) =
> if (not (isUsed flatten y k))
> && (not (isUsed flattenS x k)) then
> assgmtElim $ replace (Var x) (Var y) k
> else
> PStatement a $
> assgmtElim k
All other assignments that do not fit this scheme, or the
instructions are skipped:
> assgmtElim (PStatement a k) =
> PStatement a $
> assgmtElim k
Finally, control-flow operators are simply iterated over:
> assgmtElim (PIf c t ifT ifF k) =
> PIf
> (assgmtElim c)
> t
> (assgmtElim ifT)
> (assgmtElim ifF)
> (assgmtElim k)
> assgmtElim (PWhile c t l k) =
> PWhile
> (assgmtElim c)
> t
> (assgmtElim l)
> (assgmtElim k)
> assgmtElim (PDoWhile l c t k) =
> PDoWhile
> (assgmtElim l)
> (assgmtElim c)
> t
> (assgmtElim k)
> assgmtElim (PSwitch t cases d k) =
> PSwitch
> t
> (map (\(a,b) -> (a, assgmtElim b)) cases)
> (assgmtElim d)
> (assgmtElim k)
> assgmtElim x = x
\subsection{Code predication}
First, if I correctly remember my Software Testing lecture, a
\emph{use site} is a place where a variable is read. In opposition to
a \emph{def site} where a variable is written to. Well, then the
following is misleading.
|isUsed f x k| tells you that |x| has been found in a use or def site
of |k| in a situation where it played a role caught by |f|. To
simplify, |isUsed flatten| will catch any kind of use or def. |isUsed
flattenS| will catch a use or def in a |Complex| state.
As for the implementation, it is simply going over |ILPaka| terms and
doing the necessary on |PStatement|.
> isUsed :: (PakaVarName -> Maybe String) -> String -> ILPaka -> Bool
> isUsed p var PVoid = False
> isUsed p var (PClosing (PReturn k)) = Just var == (flatten $ pakaValName k)
> isUsed p var (PClosing _) = False
> isUsed p var (PStatement s k) = isUsedStmt s || isUsed p var k
> where isUsedStmt (PAssign t _ ls) =
> Just var `elem` map flatten (t : ls)
> isUsedStmt (PInstruction _ ls) =
> Just var `elem` map flatten ls
> isUsed p var (PIf c t ifT ifF k)
> = (Just var == (flatten $ pakaValName t)) ||
> (isUsed p var c || isUsed p var ifT
> || isUsed p var ifF || isUsed p var k)
> isUsed p var (PWhile c t l k)
> = (Just var == (flatten $ pakaValName t)) ||
> isUsed p var c || isUsed p var l || isUsed p var k
> isUsed p var (PDoWhile l c t k)
> = (Just var == (flatten $ pakaValName t)) ||
> isUsed p var c || isUsed p var l || isUsed p var k
> isUsed p var (PSwitch t c d k)
> = (Just var == (flatten $ pakaValName t)) ||
> foldl' (\a (_,b) -> a || isUsed p var b) False c
> || isUsed p var d || isUsed p var k
In light of the explanation above, the definition of |flatten| and
|flattenS| should be obvious. Aren't they?
> flatten :: PakaVarName -> Maybe String
> flatten (Var s) = Just $ s
> flatten (Ptr x) = flatten x
> flatten (Deref x) = flatten x
> flatten (Complex x) = flatten x
> flatten (K _) = Nothing
>
> flattenS :: PakaVarName -> Maybe String
> flattenS (Var s) = Nothing
> flattenS (Ptr x) = Nothing
> flattenS (Deref x) = Nothing
> flattenS (Complex x) = flatten x
> flattenS (K _) = Nothing
\subsection{Code transformation}
As for |replace|, it is by now standard: go over the terms, hunt the
|dest|, and kill it with |source|. It is surgical striking, in its
full glory.
> replace :: PakaVarName -> PakaVarName -> ILPaka -> ILPaka
> replace dest source (PStatement (PAssign dst stmt srcs) k) =
> PStatement (PAssign dst stmt srcs')
> (replace dest source k)
> where srcs' = replaceL dest source srcs
> replace dest source (PStatement (PInstruction stmt srcs) k) =
> PStatement (PInstruction stmt srcs')
> (replace dest source k)
> where srcs' = replaceL dest source srcs
> replace dest source (PIf c t ifT ifF k) =
> PIf (replace dest source c) t
> (replace dest source ifT)
> (replace dest source ifF)
> (replace dest source k)
> replace dest source (PWhile c t l k) =
> PWhile (replace dest source c)
> t
> (replace dest source l)
> (replace dest source k)
> replace dest source (PDoWhile l c t k) =
> PDoWhile (replace dest source l)
> (replace dest source c)
> t
> (replace dest source k)
> replace dest source (PSwitch t cases d k) =
> PSwitch t
> (map (\(a,b) -> (a, replace dest source b)) cases)
> (replace dest source d)
> (replace dest source k)
> replace dest source x = x
>
> replaceL x y = map (\z -> if z == x then y else z)