# Problem

When developing program passes on TVM IR (the one once was Halide IR), it is 
normal to ask for all sorts of information requiring program analysis, for 
example, live variable analysis for dead code elimination. This requirement 
becomes urgent when TVM has to directly issue intrinsic and the subsequent 
processing stage (for example LLVM) cannot analyze these outputs. 
# Consideration

*  Program Analysis Functionality must be consistent with the immutable style 
of TVM IR/AST
*  A Simple Framework is required to eliminate as much boilerplate as possible 
when the programmer tries to come up with other types of data-flow analysis
*  The interface must be easy to adapt to

We first start with a concrete example -- Live Variable Analysis.

###  Live Variable Analysis

I will take Live Variable Analysis as an example. I will define a functor, 
**inputting** *an AST* and *the set of live variable* after the AST is 
executed, **outputting** *the set of live variable* before the AST is executed.

```cpp
class LiveAnalysisFunctor : public ExprFunctor<set(const Expr&, const set&)>,
                             public StmtFunctor<set(const Stmt&, const set&)>
```

This functor can be easily defined inductively on the *AST*, demonstrated in 
pseudo-code
```haskell
-- the function below taking AST and post-live-variables
LiveAnalysisFunctor (Block{stmtX,stmtY}) postAlive = 
     let intermediateAlive = LiveAnalysisFunctor stmtY postAlive
     in LiveAnalysisFunctor stmtX intermediateAlive
```
In other words, we propagate up the live variable from the end to the 
beginning, **according to the execution order of TVM IR**

When encountering *if*-statement, we do similarly
```haskell
LiveAnalysisFunctor (IfStmt{cond,branchThen, branchElse}) postAlive = 
     let beforeThen = LiveAnalysisFunctor branchThen postAlive
          beforeElse   = LiveAnalysisFunctor branchElse postAlive
          afterConditional = Union(beforeThen, beforeElse)
     in LiveAnalysisFunctor cond afterConditional
```
The computation of *for*-statement is the only that requiring fixpoint 
computation. 

Also, we know at the endpoint, the live variables will be empty. 

Several (only relevant to live variable analysis) trivial fact:
* two variables are the same when they are declared at the same place in the 
program (since we have scopes in the AST, we also have variables with the same 
name)
* We have to distinguish, for each variable/FunctionRef, whether it is def or 
use

#### Memorization

You can see that in the current situation, to have the live variable 
information at arbitrary point, we need to calculate from very end to the point 
(and if that point is inside a for-loop, we need to make sure the fixpoint is 
reached). Thus it is suggested we only compute one time and saving the result. 
Fortunately, the above functor is pure, thus we can try to cache the above 
functor into a standard dictionary/map data structure, and thus *an AST* and 
*the set of live variable* as **key**, and *the set of live variable* before 
the AST as **value**. 


The memorization strategy should be able to apply to any *pure* IRFunctor:
```cpp
template <functor>
class MemorizedVersion<functor> : functor
```

During the computation of the liveness of the very beginning of the program, 
the whole program will be visited by the Functor and thus will be cached into 
the dictionary.

However, we may only need the information when fixpoint is reached, which means 
we can keep forgetting the old value every time a new pair of *AST* and 
*PostAlive* is computed while the *AST* has already once inserted into the 
dictionary with other *PostAlive'*. 

Ultimately, we want to transform the above dictionary into another dictionary 
taking *AST* as **key**, and the *live variable before executing AST* and *live 
variable after executing AST* as **value**. Of course, this dictionary has a 
clear specification that "only when the end of the whole program has empty set 
as live variables, each point in the AST has the live variable set as indicated 
in the dictionary".

Also note that, it is possible to query everywhere in the program about the 
Liveness Infomation, as long as the Functor is properly defined everywhere.

# Proposal

We generalize the above functor into forward/backward data-flow analysis 
'framework' to remove boilerplate code as much as possible

```cpp
// forward, functional, DFA
template <class lattice,        // the type lattice, also the 
prestate/poststate type
      class ltProof,      // the proof that lattice is a lattice, a subtype of 
IsLattice<lattice>
      const ltProof& ltOp> // lattice Operations, bind the operation at compile 
time
class ffdfa : public ExprFunctor<lattice(const Expr&, const lattice&)>,
              public StmtFunctor<lattice(const Stmt&, const lattice&)> {
  static_assert(std::is_base_of<IsLattice<lattice>, ltProof>::value, "ltProof 
must be a class derived from IsLattice<>");
...
}

template<class T>
class IsLattice { // classical lattice definition, T is lattice under the 
following operation
  public:
    // a least upperbound of two
    T join(const T& a, const T& b) = 0;
    // a greatest lower bound of two
    T meet(const T& a, const T& b) = 0;
    // highest
    const T& top() = 0;
    // whole set
    const T& bottom() = 0;
    bool equal(const T& a, const T& b) = 0;
};
```

I will indicate the usage of `IsLattice` in the comment section. This part (the 
way defining lattice) is actually quite fluid and more about the programming 
style.

There will also be another class `bfdfa`.

Memorization part:
```cpp

template <typename R, // Return type of the functor that want to be memorized
            typename KType,  // key type of the dictionary
            MemorizingPolicy m, // currently only have policy 'override'
            typename ParentFunctor, // the functor that want to be memorized
            typename ...Args, 
            typename ...InitArgs> // initialization argument to used when 
initializing the dictionary
class RecordingExprFunctor<KType(const Expr& n, Args... args),  // expect  
function that map the input to the KeyType
                          ParentFunctor,
                          m> : public ParentFunctor { ...
using ThisFunctionType = R(const Expr& n, Args... args);
static_assert(std::is_base_of<ExprFunctor<ThisFunctionType>, 
ParentFunctor>::value, "ParentFunctor must be of ExprFunctor Type");


// we propose to have a unique_ptr to the memorizing dictionary (named as "A", 
that record the evaluation of the functor), so that reuse of MemorizedVersion 
will not act on the already filled dictionary (after each memorization, 
std::move it out).

// we will have another dictionary (called "B" here), that map KeyType to the 
input type


R VisitExpr(const Expr& n, Args... args) override;

// Everytime VisitExpr is called with input ‘x’, it will first check if the 
current dict B has the KeyType and if it has and the key is also mapped to the 
"same" input 'x', then we can just query the dict A
// Otherwise we need to evaluate and according to the above MemorizingPolicy 
'override', we will insert(replace) the new(already existent) the evaluated 
value into dict A

// Designed in this twisted way so that if KeyType = AST, InputType=<AST x 
Lattice>, we can capture the final value of the fixpoint computation.

// Of course here if KeyType = InputType, it will become trivial memorization 
and don't need dict B
}

// Similar for StmtFunctor

// Then IRFunctor
template <typename R, 
            typename KType, 
            MemorizingPolicy m,
            typename ParentFunctor,
            typename ...Args, 
            typename ...InitArgs>
class RecordingIRFunctor<KType(const NodeRef& n, Args... args), 
                          ParentFunctor,
                          m> : RecordingExprFunctor<KType(const Expr& n, 
Args... args), ParentFunctor, m>,
                               RecordingStmtFunctor<KType(const Stmt& n, 
Args... args), ParentFunctor, m> {...
}
```

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/apache/incubator-tvm/issues/4468

Reply via email to