li.zhe.hua added inline comments.

================
Comment at: 
clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp:168-169
+// back edge block. (That is, all paths from the entry block to the back edge
+// block must go through `Block`.) It also means that there are only two
+// predecessors; the other is along the path from the entry block to `Block`.
+static const CFGBlock *findBackEdge(const CFGBlock *Block) {
----------------
sgatev wrote:
> li.zhe.hua wrote:
> > xazax.hun wrote:
> > > Is this also true when we have multiple `continue` statements in the loop?
> > Yes. The end of the loop, and each of the `continue` statements, target the 
> > back edge block. They all get funneled through that back edge to `Block`, 
> > such that `Block` only has two predecessors. However, I haven't verified 
> > this in the CFG code, only by not finding a counterexample.
> Does that hold for back edges stemming from `goto` statements?
Ah, `goto` does throw a wrench in all of this. Specifically, it is problematic 
only for `do ... while` loops, where the labeled statement is the first 
statement of the `do` block.

I took out the sentence, took out the `assert`, and added a `FIXME` for 
`prepareBackEdges`.


================
Comment at: 
clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp:228-234
+  // If we are at the start of a loop, we will have two precessors, but we 
don't
+  // want to join these two predecessors. Instead, we want to take the back 
edge
+  // block (i.e. the result of the previous iteration) and use that directly as
+  // the input block.
+  //
+  // For the first iteration of loop, the "zeroth" iteration state is set up by
+  // `prepareBackEdges`.
----------------
xazax.hun wrote:
> xazax.hun wrote:
> > li.zhe.hua wrote:
> > > xazax.hun wrote:
> > > > Could you elaborate on this? Let's consider this loop:
> > > > 
> > > > ```
> > > >  Pred
> > > >    |
> > > >    v
> > > >  LoopHeader <---BackEdge
> > > > ```
> > > > 
> > > > Do we ignore the state coming from `Pred` on purpose? Is that sound?
> > > > 
> > > > I would expect the analysis to always compute `join(PredState, 
> > > > BackEdgeState)`, and I would expect the widening to happen between the 
> > > > previous iteration of `BackEdgeState` and the current iteration of 
> > > > `BackEdgeState`. So, I wonder if we already invoke the widening 
> > > > operator along back edges, wouldn't the regular logic work just fine? 
> > > > Do I miss something?
> > > > 
> > > > Do we ignore the state coming from `Pred` on purpose? Is that sound?
> > > 
> > > We don't, and this is what the comment
> > > 
> > > ```
> > >   // For the first iteration of loop, the "zeroth" iteration state is set 
> > > up by
> > >   // `prepareBackEdges`.
> > > ```
> > > failed to explain. After transferring `PredState`, we copy `PredState` 
> > > into `BackEdgeState`, which is done in `prepareBackEdges`.
> > > 
> > > > I would expect the analysis to always compute `join(PredState, 
> > > > BackEdgeState)`
> > > 
> > > I'm not sure I see that we should always join `PredState` and 
> > > `BackEdgeState`. Execution doesn't go from `Pred` into the Nth iteration 
> > > of the loop, it only goes from `Pred` into the first iteration of the 
> > > loop, e.g. the predecessor for the 4th iteration of the loop is only the 
> > > back-edge from the 3rd iteration of the loop, not `Pred`.
> > > 
> > > Let me know if this makes sense.
> > > I'm not sure I see that we should always join `PredState` and 
> > > `BackEdgeState`. Execution doesn't go from `Pred` into the Nth iteration 
> > > of the loop, it only goes from `Pred` into the first iteration of the 
> > > loop, e.g. the predecessor for the 4th iteration of the loop is only the 
> > > back-edge from the 3rd iteration of the loop, not `Pred`.
> > > 
> > > Let me know if this makes sense.
> > 
> > The analysis state of the dataflow analysis supposed to overestimate all of 
> > the paths. Consider the following loop and imagine we want to calculate 
> > intervals for integer variables:
> > 
> > ```
> > int i = 0;
> > while (...) {
> >   [[program point A]];
> >   ++i;
> > }
> > ```
> > 
> > During the analysis of this loop, the value `i ==0` flows to `[[program 
> > point A]]`. This is the motivation to join the state from the back edge and 
> > from PredState. As far as I understand, you want to achieve this by copying 
> > PredState to the BackEdgeState before the first iteration. But this also 
> > means that we will use the widening operator to combine PredState with the 
> > state after N iterations instead of the regular join operation. I am not 
> > entirely sure whether these two approaches always produce the same results. 
> > 
> > 
> A contrived example (just came up with this in a couple minutes so feel free 
> to double check if it is OK):
> Consider:
> ```
> int i = 0;
> while (...) {
>   if (i == 0)
>     i = -2;
>   ++i;
> }
> ```
> 
> Some states:
> ```
> PredState = i : [0, 0]
> BackEdgeState (after first iteration) = i : [-1, -1]
> ```
> 
> And the results of join vs widen:
> ```
> PredState.join(BackEdgeState) = i : [-1, 0]
> PredState.widen(BackEdge) = i : [-inf, 0]
> ```
> 
> The extra widening with the PredState can result in extra imprecision. 
So, my understanding is that widening introduces imprecision as a trade-off for 
convergence. Yes, in the contrived example, joining converges after a few 
iterations, but in other cases it never converges.

---

Looking at the Rival and Yi book, there are no intervening joins with the 
predecessor for the first loop as we analyze the loop. This is roughly where I 
drew from in terms of this implementation.

```
analysis(iter{p}, a) = { R <- a;
                         repeat
                             T <- R;
                             R <- widen(R, analysis(p, R));
                         until inclusion(R, T)
                         return T;
```



================
Comment at: 
clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp:270
+
+TEST(DataflowAnalysisTest, ConvergesOnWidenAnalysis) {
+  std::string Code = R"(
----------------
sgatev wrote:
> There's already a set of "widening" tests - 
> http://google3/third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp;l=527-712;rcl=462638952
> 
> What do you think about refactoring those so that we have tests that exercise 
> the framework with both `join` and `widen`?
This commit implements widening only for the lattice. Those tests use the 
`NoopLattice`, so their behavior is not expected to change with this commit. 
Splitting `merge` into a `join` and `widen` is out-of-scope for this commit, 
and is what I am hoping to work on next.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D131646/new/

https://reviews.llvm.org/D131646

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to