This is an archive of the discontinued LLVM Phabricator instance.

[Polly] [DependenceInfo] change WAR, WAW generation to correct semantics.
ClosedPublic

Authored by bollu on Mar 27 2017, 2:12 AM.

Details

Summary

Change of WAR, WAW generation:

  • buildFlow(Sink, MustSource, MaySource, Sink) treates any flow of the form sink <- may source <- must source as a *may* dependence.
  • we used to call:
Flow = buildFlow(MustWrite, MustWrite, Read, Schedule);
WAW = isl_union_flow_get_must_dependence(Flow);
WAR = isl_union_flow_get_may_dependence(Flow);
  • This caused some WAW dependences to be treated as WAR dependences.
  • Incorrect semantics.
  • Now, we call WAR and WAW correctly.

Correct WAW:

Flow = buildFlow(Write, MustWrite, MayWrite, Schedule);
WAW = isl_union_flow_get_may_dependence(Flow);
isl_union_flow_free(Flow);
  • Straightforward call.

Correct WAR:

Flow = buildFlow(Write, Read, Write, Schedule);
WAR = isl_union_flow_get_must_dependence(Flow);
isl_union_flow_free(Flow);
  • We want the "shortest" WAR possible (exact dependences).
  • We mark all the writes as may-source, reads as must-souce.
  • Then, we ask for *must* dependence.
  • This removes all the reads that flow through a write before reaching a sink.
  • Leaves with direct (R -> W).
  • This affects reduction generation since RED is built using WAW and WAR.

New StrictWAW for Reductions:

  • We used to call: Flow = buildFlow(MustWrite, MustWrite, Read, Schedule); WAW = isl_union_flow_get_must_dependence(Flow); WAR = isl_union_flow_get_may_dependence(Flow);
  • This *is* the right model of WAW we need for reductions, just not in general.
  • Reductions need to track only *strict* WAW, without any reads in between.

Example for strict WAW:

example-strict-waw.cpp
void f(int *A, int *B) {
    for(int i = 0; i <= 100; i++) {
    S0:    *A += i; --WAW (S0 -> S0) --*
                                       |
        if (i >= 98) {               WAR (S0 -> S1)
    S1:        *B = *A; <--------------*
        }
    }
}
  • Since the writes to S0 happen *between* reads at S1, the entire loop is not a legal reduction. It is only a reduction in (0 <= i <= 98).
  • To detect these sorts of patterns, we need to generate strict WAW that do not have reads between them.

Explanation: Why the new WAR dependences in tests are correct:

  • We no longer set WAR = WAR - WAW
  • Hence, we will have WAR dependences that were originally removed.
  • These may look incorrect, but in fact make sense.

Code:

new-war-dependence.ll
;    void manyreductions(long *A) {
;      for (long i = 0; i < 1024; i++)
;        for (long j = 0; j < 1024; j++)
; S0:          *A += 42;
;
;      for (long i = 0; i < 1024; i++)
;        for (long j = 0; j < 1024; j++)
; S1:          *A += 42;
;       ...

WAR dependence:

{  S0[1023, 1023] -> S1[0, 0] }
  • Between S0[1023, 1023] and S1[0, 0], we will have the dependences:
dependence-incorrect
        S0[1023, 1023]:
    *-- tmp = *A (load0)--*
WAR 2   add = tmp + 42    |
    *-> *A = add (store0) |
                         WAR 1
        S1[0, 0]:         |
        tmp = *A (load1)  |
        add = tmp + 42    |
        A = add (store1)<-*
  • One may assume that WAR2 *hides* WAR1 (since store0 happens before store1). However, within a statement, Polly has no idea about the ordering of loads and stores.
  • Hence, according to Polly, the code may have looked like this:
dependence-correct
S0[1023, 1023]:
A = add (store0)
tmp = A (load0) ---*
add = A + 42       |
                 WAR 1
S1[0, 0]:          |
tmp = A (load1)    |
add = A + 42       |
A = add (store1) <-*
  • So, Polly generates (correct) WAR dependences. It does not make sense to remove these dependences, since they are correct with respect to Polly's model.

Event Timeline

bollu created this revision.Mar 27 2017, 2:12 AM
bollu edited the summary of this revision. (Show Details)Mar 27 2017, 2:28 AM
grosser edited edge metadata.Mar 27 2017, 11:03 PM

Cool. This looks good from my side. Maybe Michael has a comment.

I am OK with a compile time regression here. We should first get it correct, then tune the compile time.

Meinersbur edited edge metadata.Mar 28 2017, 4:50 PM

Thanks for this patch. I already mentioned to Tobias was that this code looked incorrect, but he explained to me that this was taken over from something Sven did and I did not investigate further. The new way of computing the dependencies is much more understandable and you explained well why changes are necessary.

Also thanks for the detailed summary. I usually care more about comments in the code that the commit message since I cannot lookup the whole history of the file when I look at the code. The summary is good for explaining the differences to the previous state. However, think your comments are sufficient.

Change of WAR, WAW generation:

  • buildFlow(Sink, MustSource, MaySource, Sink) treates any flow of the form sink <- may source <- must source as a *may* dependence.
  • we used to call:
Flow = buildFlow(MustWrite, MustWrite, Read, Schedule);
WAW = isl_union_flow_get_must_dependence(Flow);
WAR = isl_union_flow_get_may_dependence(Flow);
  • This caused some WAW dependences to be treated as WAR dependences.
  • Incorrect semantics.

Do you have examples for when this happened?

New StrictWAW for Reductions:

  • We used to call: Flow = buildFlow(MustWrite, MustWrite, Read, Schedule); WAW = isl_union_flow_get_must_dependence(Flow); WAR = isl_union_flow_get_may_dependence(Flow);
  • This *is* the right model of WAW we need for reductions, just not in general.

Why?

  • Reductions need to track only *strict* WAW, without any reads in between.

See inline comment.

Explanation: Why the new WAR dependences in tests are correct:

  • We no longer set WAR = WAR - WAW
  • Hence, we will have WAR dependences that were originally removed.
  • These may look incorrect, but in fact make sense.

Code:

new-war-dependence.ll
;    void manyreductions(long *A) {
;      for (long i = 0; i < 1024; i++)
;        for (long j = 0; j < 1024; j++)
; S0:          *A += 42;
;
;      for (long i = 0; i < 1024; i++)
;        for (long j = 0; j < 1024; j++)
; S1:          *A += 42;
;       ...

WAR dependence:

{  S0[1023, 1023] -> S1[0, 0] }
  • Between S0[1023, 1023] and S1[0, 0], we will have the dependences:
dependence-incorrect
        S0[1023, 1023]:
    *-- tmp = *A (load0)--*
WAR 2   add = tmp + 42    |
    *-> *A = add (store0) |
                         WAR 1
        S1[0, 0]:         |
        tmp = *A (load1)  |
        add = tmp + 42    |
        A = add (store1)<-*
  • One may assume that WAR2 *hides* WAR1 (since store0 happens before store1). However, within a statement, Polly has no idea about the ordering of loads and stores.

This is a general problem in Polly. We need to implicitly assume an order of reads and writes within a single statement instance. The most sane way to do this is to always assume that loads are done first, as in your examples.

However, this isn't necessarily so. Code is just as valid if it is done the other way around:

*A = ...
... = *A

this is a kind of self-dependency we can ignore because such self-dependencies do not influence scheduling. So we have to assume the worst case that the read is visible outside, even if it can only read the value written in itself. But we have to take care for that the isl_flow understands it the same way, which I currently doesn't know how it does it. Do you?

  • Hence, according to Polly, the code may have looked like this:
dependence-correct
S0[1023, 1023]:
A = add (store0)
tmp = A (load0) ---*
add = A + 42       |
                 WAR 1
S1[0, 0]:          |
tmp = A (load1)    |
add = A + 42       |
A = add (store1) <-*

Did you mix up the order in the first block?

I am surprised that Tobias just greenlit this patch. There are some issues that he consistently remarks in my patches, but I personally do not care (that much) about:

  • Unrelated whitespace changes
  • Unrelated NFC changes: An additional null-check for MaySrc, but buildFlow is never called with nullptr; ScheduleOptimizer.cpp
  • pattern-matching-based-opts_3.ll has been changed, but just the beginning braces been removed (did you use my automatic update tool?)
  • There are at least 2 independent changes that can be made separate patches:
    • Modification of how WAW is computed.
    • Modification of how RAW is computed.
    • Introduction of StrictWAW
lib/Analysis/DependenceInfo.cpp
402–403

This set is used in both branches and should be hoisted before branching.

405

Could you clarify that the first <- is a WAW dependency and only the second is a WAR dependency? Also mention that in this examples all writes are MUST writes. If W1 is a may-write, there should be a W2->R dependency (correct?)

408

reads themselves do not have side-effects (assuming segfaulting is undefined behaviour)

413–414

Is this correct? With the goal mentioned in the above comment, I would assume this:

Flow = buildFlow(Write, MustWrite, Read, Schedule);
WAR = isl_union_flow_get_non_must_dependence(Flow);
415

What is this WAW (S0 -> S0) within this arrow? If it is the self-dependency (which should be a separate arrow), shouldn't it be a WAR?

423

"... writes at/in S0 ..." (S0 is no write target)

Also nitpick: double space in comment.

423–424

It is a reduction over the complete domain. The only issue is that some intermediate value is grabbed. An implementation could use the reduction to compute the final value of *A, but leave all operations of *A up to i >= 98 (the difference would be clearer if the condition was i == 1) in there. Normally the operations would become dead because the final value would be computed in another way, just not here. ScalarEvolution would do that. The grabbed value could also be computed by a second reduction. Not saying it makes sense, but the claim it is not a reduction anymore is incorrect.

428–429

Do may-writes cause problems with reductions? There are handles just like must-writes here.

Also, a statement

*A += x;

is a read and a write to *A (read it, update it, then write it back). That is, logically there is always read between two writes.

I have to look up what isl does when the source and sink are at the same timepoint.

438–441

As both branches compute this, it should be put just after the conditionals.

lib/Transform/ScheduleOptimizer.cpp
784–789

Why this change?

test/DependenceInfo/reduction_privatization_deps.ll
8–9

I don't get this additional comment. S2 was never considered a reduction, no?

test/DependenceInfo/reduction_simple_privatization_deps_w_parameter.ll
6

Not aligned to the other indentions anymore.

bollu updated this revision to Diff 93471.Mar 30 2017, 5:54 AM

Updated style changes in DependenceInfo.cpp, test cases.

bollu added a comment.Mar 30 2017, 9:27 AM

Regarding WAR dependences:

In "Presburger formulas and Polyhedral Compilation", Section 6.3, it's written that:

K := sink
Y := may-source
T := must-source

must-dependences := {k→(i→a):i→a∈K ∧  k→a∈T  ∧  k<i  ∧  ¬(∃j:j→a∈(T∪Y)  ∧  k<j<i)}
may-dependences := {k→(i→a):i→a∈K  ∧  k→a∈(T∪Y)  ∧  k<i  ∧  ¬(∃j:j→a∈T  ∧  k<j<i)}

The current code is:

current.cpp
Flow = buildFlow(Write, Read, Write, Schedule);
WAR = isl_union_flow_get_must_dependence(Flow);
  • For a must-dependence, we get elements in the must-source that have no may-source or must-source dependence between them and the sink.
  • In this interpretation, my call leads to the semantics: "Give all elements in Read that have no Read or Write between them and the sink.

you suggested using:

suggestion.cpp
Flow = buildFlow(Write, MustWrite, Read, Schedule);
WAR = isl_union_flow_get_non_must_dependence(Flow);
  • For a may-source, we get elements form the may source U must-source that have no must-source dependence between them and the sink.-
  • your call leads to the semantics: "Give all elements in Read U Write that have no Read between them and the sink"
  • This gives write -> sink (write) like-dependences which are incorrect

I believe that mine is the correct semantics.

  • If there is read0 -> read1 -> sink, then the read in between (read1) should be the RAW, so it is correct to not be allowed
  • If there is read0 -> write0 -> sink, then read0 -> write0 should be the RAW, so it is correct to not be allowed.

Please do correct me if I have misinterpreted the meaning of the two invocations.

lib/Analysis/DependenceInfo.cpp
405

yes, there are all must-writes. I will clarify this.

408

hm, yes. What I meant was "a read from our statement S0 that leads to a write somewhere else (S1)"

415

It is meant to be a self dependence. Let me clean up the design a little bit. There will be a WAW from S0[i] -> S0[i + 1]?

423–424

I think this depends on our definition of what a reduction is.

If we consider the same block of code and say that everything is a reduction from 0 <= i <= 100, then we should allow free reordering of statements from 0 <= i <= 100. (We subtract RED dependences from RAW, WAW`, and WAR dependences to allow reordering of the reduction statements.

In this case, if we allow reordering, the value written to *B may be incorrect. This was what I meant by is not a reduction,

428–429
  1. I'm not totally sure on how May-writes interact with reductions. I suppose it could be argued that for now, we should only use must-writes.
  1. for
*A += x

buildFlow appears to do the correct thing even though there is the read between the two writes. However, according to the spec in Presburger Sets and Relations, I'm not entirely sure what is supposed to happen. I will read and find out.

lib/Transform/ScheduleOptimizer.cpp
784–789

This was from when I was trying to fuse WAR and WAW into one "False" dependence. Will revert.

784–789

I mis-remembered. Not having this causes test cases to fail (9 of them). From what I can tell, checking that WAR dependences was empty is some sort of performance optimisation. However, now that we have more WAR dependences than we did previous (since I removed WAR = WAR - WAW), this heuristic no longer applies.

test/DependenceInfo/generate_may_write_dependence_info.ll
63

@Meinersbur This is an example where we find WAW dependences that we did not find before.

test/DependenceInfo/reduction_privatization_deps.ll
8–9

Yes, it is not. It was a comment I had put in to explain the behaviour to myself. WIl remove it

test/ScheduleOptimizer/pattern-matching-based-opts_3.ll
80 ↗(On Diff #93105)

This missed me. There used to be a conflict with the pattern matching and the generated output. I'll revert this.

lib/Transform/ScheduleOptimizer.cpp
784–789

This was from when I was trying to fuse WAR and WAW into one "False" dependence. Will revert.

Regarding WAR dependences:

In "Presburger formulas and Polyhedral Compilation", Section 6.3, it's written that:

K := sink
Y := may-source
T := must-source

must-dependences := {k→(i→a):i→a∈K ∧  k→a∈T  ∧  k<i  ∧  ¬(∃j:j→a∈(T∪Y)  ∧  k<j<i)}
may-dependences := {k→(i→a):i→a∈K  ∧  k→a∈(T∪Y)  ∧  k<i  ∧  ¬(∃j:j→a∈T  ∧  k<j<i)}

OK, thank you for looking up isl's defintion. For some reason I expected that dependencies to must-sources would also always be must-dependencies. That was my misconception.

I think the following points still have to be addressed:

  • Must and may-sources are handled exactly the same way for WAR (that might be intendent, but I would like to know why)
  • WAR-dependencies in reductions.
  • How read and write accesses to the same element in the same statement are handeled, especially in reductions.
lib/Analysis/DependenceInfo.cpp
371

My opinion is still that reads do not imply side-effects. Consider using a different explanation why they do not count as reduction.

378–381

There is a RAW self-dependency here as well. You should at least mention that.

E.g. for the test case reduction_simple_iv.ll without reductions, I get the following dependencies:

RAW dependences:
        { Stmt_for_cond[i0] -> Stmt_for_cond[1 + i0] : 0 <= i0 <= 99 }
WAR dependences:
        { Stmt_for_cond[i0] -> Stmt_for_cond[1 + i0] : 0 <= i0 <= 99 }
WAW dependences:
        { Stmt_for_cond[i0] -> Stmt_for_cond[1 + i0] : 0 <= i0 <= 99 }
417–418

The point I was trying to make was the different handling of may-writes. May-writes, at least in flow-dependencies, do not break other dependencies. I'd naively expect the same for anti-dependencies. What is your argument to handle may-writes exactly as must-writes?

That is, for a sequence

must-W2 (sink) <- may-W1 (sink) <- R (source)

I'd naively expect the dependencies

WAR = { W2-> R, W1 -> R }

423–424

Reductions are usually defined as folding as a list of values into a single value. That is, your example contains two reductions with the following values at the end:

*A = sum { A*, 0, ..., 100 }

and

*B = sum { A*, 0, ..., 100 }

(they are the same because *B = *A in the last iteration)
That is, I do not understand this as no reductions, but two reductions that share some instructions to compute them. This does not change the fact that *A is computing a reduction. The order of computation is also irrelevant for being a reduction. It is also irrelevant which modifications can be done on the loop that break the computation. The original code still computed a reduction and stored it into *A, respectively *B.

I suggest to reformulate it in a way saying that intermediate values a used by other computation and therefore cannot arbitrarily modify how reduction *A is computed without duplicating the instructions needed computing *A and *B.

480

Can you undo this change in the next update? Thanks.

lib/Transform/ScheduleOptimizer.cpp
784–789

We maybe should check with Roman about this.

test/DependenceInfo/generate_may_write_dependence_info.ll
63

OK, thanks.

test/DependenceInfo/reduction_privatization_deps.ll
8–9

Still not removed?

bollu updated this revision to Diff 93754.Apr 1 2017, 12:35 PM

NFC: style cleanup

bollu added a comment.Apr 1 2017, 1:26 PM

1. Must and may-sources are handled exactly the same way for WAR (that might be intended, but I would like to know why)

The point I was trying to make was the different handling of may-writes. May-writes, at least in flow-dependencies, do not break other dependencies. I'd naively expect the same for anti-dependencies. What is your argument to handle may-writes exactly as must-writes?

That is, for a sequence

must-W2 (sink) <- may-W1 (sink) <- R (source)

I'd naively expect the dependencies

WAR = { W2-> R, W1 -> R }

I see what you mean now. I think you are right, May-Writes should not interfere with Must-Writes. The correct call in that case would be:

correct-war-build.cpp
Flow = buildFlow(Write, Read, MustWrite, Schedule);
WAR = isl_flow_get_must_dependence(Flow);

That way, Must-Writes will block each other, while May-writes will not be allowed to block other writes (both Must and May). Does this work?

2. WAR-dependencies in reductions.

  • The reduction detection algorithm has not changed. It still uses RAW and WAW dependences, not WAR.
  • We subtract reduction dependences out from the WAR dependences. Before, this was not needed since we used to do WAR = WAR - WAW (but this is not correct semantics).
  • So, we remove reduction dependences from WAR to allow freedom of rearrangement

3. How read and write accesses to the same element in the same statement are handled, especially in reductions

  • the same element in the same statement are not tracked by ISL.
simple_reduction.ll
; RUN: opt %loadPolly -analyze < %s | FileCheck %s
;
; FIXME: Edit the run line and add checks!
;
; XFAIL: *
;
;    static const int N = 3000;
;
;    void f(int *sum, int A[N]) {
;      for (int i = 0; i < N; i++) {
;        *sum += A[i];
;      }
;    }
;
source_filename = "testbed.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"

define void @f(i32* %sum, i32* %A) {
entry:
  br label %for.cond

for.cond:                                         ; preds = %for.inc, %entry
  %indvars.iv = phi i64 [ %indvars.iv.next, %for.inc ], [ 0, %entry ]
  %exitcond = icmp ne i64 %indvars.iv, 3000
  br i1 %exitcond, label %S0, label %for.end

S0:                                         ; preds = %for.cond
  %arrayidx = getelementptr inbounds i32, i32* %A, i64 %indvars.iv
  %tmp = load i32, i32* %arrayidx, align 4
  %tmp1 = load i32, i32* %sum, align 4
  %add = add nsw i32 %tmp1, %tmp
  store i32 %add, i32* %sum, align 4
  br label %for.inc

for.inc:                                          ; preds = %S0
  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
  br label %for.cond

for.end:                                          ; preds = %for.cond
  ret void
}
dependences
Wrapped Dependences:
    RAW dependences:
        { [Stmt_S0[i0] -> MemRef_sum[0]] -> [Stmt_S0[o0] -> MemRef_sum[0]] : i0 >= 0 and i0 < o0 <= 2999 }
    WAR dependences:
        { [Stmt_S0[i0] -> MemRef_sum[0]] -> [Stmt_S0[1 + i0] -> MemRef_sum[0]] : 0 <= i0 <= 2998 }
    WAW dependences:
        { [Stmt_S0[i0] -> MemRef_sum[0]] -> [Stmt_S0[o0] -> MemRef_sum[0]] : i0 >= 0 and i0 < o0 <= 2999 }
  • There is no WAR generated from S0[i] -> S0[i] even though technically *sum = *sum + A[i] does contain a WAR for sum.
WAR-current-code.cpp
Flow = buildFlow(Write, Read, Write, Schedule);
WAR = isl_union_flow_get_must_dependence(Flow);
  • This is because isl_union_flow_get_must_dependence specifies that the sink must be strictly less than the source according to the schedule order.
  • In this case, the sink is equal to the source in the schedule order.
  • Math: See (k<i) (must-dependences := {k→(i→a):i→a∈K ∧ k→a∈T ∧ ***k<i*** ∧ ¬(∃j:j→a∈(T∪Y) ∧ k<j<i)}).
  • This will hold true for WAR, WAW, RAW.
lib/Analysis/DependenceInfo.cpp
378–381

I did not believe it was necessary to describe it since it was unimportant to the discussion that the comment was having. However, I'll add it since you want me to.

480

done.

test/DependenceInfo/reduction_privatization_deps.ll
8–9

removed.

test/DependenceInfo/reduction_simple_privatization_deps_w_parameter.ll
6

It's fixed now, correct?

bollu added a comment.Apr 1 2017, 1:36 PM

Interestingly enough, changing WAR such that may-writes do not block Writes (may + must) does not lead to a single regression. However, this feels like correct behaviour, so I am updating the code to reflect this.
@grosser: do you have any thoughts on this?

WAR is now:

Flow = buildFlow(Write, Read, MustWrite, Schedule);
WAR = isl_union_flow_get_must_dependence(Flow);
bollu updated this revision to Diff 93758.Apr 1 2017, 4:32 PM

Change how WAR is computed. Also change the StrictWAW explanation to focus
on the fact that the reduction variable is being captured outside the reduction

bollu updated this revision to Diff 93759.Apr 1 2017, 4:34 PM

Add test to check that may-writes to not block must-writes in WAR dependence
generation.

bollu added a comment.Apr 1 2017, 4:35 PM

@Meinersbur: ping, I have made the necessary changes to WAR. I've also edited the explanation to make more sense when it comes to reductions. Could you take another look?

gareevroman added inline comments.Apr 2 2017, 2:55 AM
lib/Transform/ScheduleOptimizer.cpp
784–789

I think it's fine now.

Meinersbur accepted this revision.Apr 3 2017, 9:15 AM

1. Must and may-sources are handled exactly the same way for WAR (that might be intended, but I would like to know why)

That way, Must-Writes will block each other, while May-writes will not be allowed to block other writes (both Must and May). Does this work?

Yes.

2. WAR-dependencies in reductions.

  • The reduction detection algorithm has not changed. It still uses RAW and WAW dependences, not WAR.
  • We subtract reduction dependences out from the WAR dependences. Before, this was not needed since we used to do WAR = WAR - WAW (but this is not correct semantics).
  • So, we remove reduction dependences from WAR to allow freedom of rearrangement

Yes, the detection itself has indeed not changed, so I assume it is fine. Although my feeling is still that it should check WAR, not WAW.

3. How read and write accesses to the same element in the same statement are handled, especially in reductions

I tried this example:

to see what happens when the reduction is used in the statement itself. It is actually not recognized as a reduction candidate in the first place. The code that checks for uses in the same statement is checkForReductions().

Could you maybe add a line for StrictWAW so future me won't be confused again?

For all other dependencies, we get an overapproximation, so it should be fine. Problem is, we cannot model the order of accesses within a statement.

This revision is now accepted and ready to land.Apr 3 2017, 9:15 AM
bollu added a comment.Apr 3 2017, 11:35 AM

What line would you like me to add for StrictWAW?

What line would you like me to add for StrictWAW?

Mention that if *A is captured in the same statement/BB as the reduction operation (that is, not made conditional by if (i >= 98)), the reduction in question will already rejected/not added to the candidate list by ScopInfo::checkForReductions(), which verifies that exactly one read and one write in the statement access the reduction variable.

bollu updated this revision to Diff 94016.Apr 4 2017, 2:07 AM

Added short explanation in StrictWAW as to how reductions are guaranteed to have only one load and one store in a statement

bollu added a comment.Apr 4 2017, 2:09 AM

@Meinersbur: Done, added the comment. Is there anything else to be fixed?

Tobias usually wants functionally independent parts committed separately (here: StrictWAW, changing how WAW is computed and changing how RAW is computed), but since he already gave his OK, you are free to commit.

lib/Analysis/DependenceInfo.cpp
417–427

nice!

bollu updated this revision to Diff 94061.Apr 4 2017, 5:57 AM

removed whitespace in new testcase.

bollu closed this revision.Apr 4 2017, 11:19 AM

Closed by commit 490659bdcf7557513f29542c59ce01af72cdc25b