replace DFAEngine implementation: use topological order

- add createGraph(flow) to ControlFlowUtil;
- use DFSTBuilder to get SCCs for a control flow;
- dfa iterates all components once;
- dfa iterates strongly-connected component before convergence;
- deprecated method DfaInstance#isForward() is removed;
- remove unused DFAEngine#performDfa(info);
- remove unused ControlFlowUtil#postOrder.

New dfa analysis does not have a convergence issue in merge points.
(IDEA-CR-28352)
This commit is contained in:
Vladimir Koshelev
2018-01-18 17:30:10 +03:00
committed by Vladimir Koshelev
parent f2e6fca603
commit 35b77e8d95
6 changed files with 403 additions and 113 deletions
@@ -21,9 +21,13 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.IntStack;
import com.intellij.util.graph.Graph;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* @author oleg
@@ -34,35 +38,27 @@ public class ControlFlowUtil {
private ControlFlowUtil() {
}
public static int[] postOrder(Instruction[] flow) {
final int length = flow.length;
int[] result = new int[length];
boolean[] visited = new boolean[length];
Arrays.fill(visited, false);
final IntStack stack = new IntStack(length);
@NotNull
public static Graph<Instruction> createGraph(@NotNull final Instruction[] flow) {
return new Graph<Instruction>() {
@NotNull
final private List<Instruction> myList = Arrays.asList(flow);
int N = 0;
for (int i = 0; i < length; i++) { //graph might not be connected
if (!visited[i]) {
visited[i] = true;
stack.clear();
stack.push(i);
while (!stack.empty()) {
final int num = stack.pop();
result[N++] = num;
for (Instruction succ : flow[num].allSucc()) {
final int succNum = succ.num();
if (!visited[succNum]) {
visited[succNum] = true;
stack.push(succNum);
}
}
}
@Override
public Collection<Instruction> getNodes() {
return myList;
}
}
LOG.assertTrue(N == length);
return result;
@Override
public Iterator<Instruction> getIn(Instruction n) {
return n.allPred().iterator();
}
@Override
public Iterator<Instruction> getOut(Instruction n) {
return n.allSucc().iterator();
}
};
}
public static int findInstructionNumberByElement(final Instruction[] flow, final PsiElement element){
@@ -141,7 +137,7 @@ public class ControlFlowUtil {
}
}
public static enum Operation {
public enum Operation {
/**
* CONTINUE is used to ignore previous elements processing for the node, however it doesn't stop the iteration process
*/
@@ -18,12 +18,16 @@ import com.intellij.codeInsight.controlflow.ControlFlowUtil;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.graph.DFSTBuilder;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
public class DFAEngine<E> {
private static final Logger LOG = Logger.getInstance(DFAEngine.class.getName());
private static final double TIME_LIMIT = 10e9; // In nanoseconds, 10e9 = 1 sec
private static final long TIME_LIMIT = 1_000_000_000L; // In nanoseconds, 1_000_000_000 = 1 sec
private final Instruction[] myFlow;
@@ -38,99 +42,101 @@ public class DFAEngine<E> {
mySemilattice = semilattice;
}
public List<E> performDFA() throws DFALimitExceededException {
final ArrayList<E> info = new ArrayList<>(myFlow.length);
return performDFA(info);
}
public List<E> performDFA(final List<E> info) throws DFALimitExceededException {
if (LOG.isDebugEnabled()) {
LOG.debug("Performing DFA\n" + "Instance: " + myDfa + " Semilattice: " + mySemilattice + "\nCon");
}
// initializing dfa
final E initial = myDfa.initial();
for (int i = 0; i < myFlow.length; i++) {
final int length = myFlow.length;
for (int i = 0; i < length; i++) {
info.add(i, initial);
}
final boolean[] visited = new boolean[myFlow.length];
final int[] order = ControlFlowUtil.postOrder(myFlow);
// Count limit for number of iterations per worklist
// Count limit for loops
final int limit = getIterationLimit();
int dfaCount = 0;
final long startTime = System.nanoTime();
DFSTBuilder<Instruction> dfsTBuilder = new DFSTBuilder<>(ControlFlowUtil.createGraph(myFlow));
for (int i = 0; i < myFlow.length; i++) {
// Check if canceled
ProgressManager.checkCanceled();
int[] instructionNumToNNumber = new int[myFlow.length];
for (int i = 0; i < myFlow.length; ++i) {
instructionNumToNNumber[dfsTBuilder.getNodeByNNumber(i).num()] = i;
}
final int[] lastUpdate = new int[length];
int count = 0;
if (System.nanoTime() - startTime > TIME_LIMIT) {
if (LOG.isDebugEnabled()) {
LOG.debug("Time limit exceeded");
}
throw new DFALimitExceededException("Time limit exceeded");
}
// Iteration count per one worklist
int count = 0;
final Instruction instruction = myFlow[order[i]];
final int number = instruction.num();
if (!visited[number]) {
final Queue<Instruction> worklist = new LinkedList<>();
worklist.add(instruction);
visited[number] = true;
while (true) {
// Check if canceled
ProgressManager.checkCanceled();
// It is essential to apply this check!!!
// This gives us more chances that resulting info will be closer to expected result
// Also it is used as indicator that "equals" method is implemented correctly in E
count++;
if (count > limit) {
if (LOG.isDebugEnabled()) {
LOG.debug("Iteration count exceeded on worklist");
}
throw new DFALimitExceededException("Iteration count exceeded on worklist");
}
final Instruction currentInstruction = worklist.poll();
if (currentInstruction == null) {
break;
}
final int currentNumber = currentInstruction.num();
final E oldE = info.get(currentNumber);
final E joinedE = join(currentInstruction, info);
final E newE = myDfa.fun(joinedE, currentInstruction);
if (!mySemilattice.eq(newE, oldE)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Number: " + currentNumber + " old: " + oldE.toString() + " new: " + newE.toString());
}
info.set(currentNumber, newE);
for (Instruction next : getNext(currentInstruction)) {
worklist.add(next);
visited[next.num()] = true;
}
}
List<Instruction> instructionsWithBackEdges = new ArrayList<>();
for (Collection<Instruction> component : dfsTBuilder.getComponents()) {
List<Instruction> sortedInstructions = new ArrayList<>(component);
// component returns its instructions using getNodeByTNumber
// unfortunately its ordering is not suitable for dataflow goals because
// it does not start order in a SCC from entry nodes
// so We should resort nodes in a SCC by NNumber
sortedInstructions.sort(Comparator.comparingInt(it -> instructionNumToNNumber[it.num()]));
instructionsWithBackEdges.clear();
for (Instruction instruction : sortedInstructions) {
applyTransferFunction(info, instruction);
if (instruction.allPred().stream().anyMatch(predecessor ->
instructionNumToNNumber[predecessor.num()] > instructionNumToNNumber[instruction.num()])) {
instructionsWithBackEdges.add(instruction);
}
}
// Move to another worklist
dfaCount += count;
int iteration = 0;
while (true) {
++iteration;
final int currentIteration = iteration;
boolean anyUpdates = false;
for (Instruction instruction : instructionsWithBackEdges) {
if (applyTransferFunction(info, instruction)) {
lastUpdate[instruction.num()] = currentIteration;
anyUpdates = true;
count++;
}
}
if (!anyUpdates) {
break;
}
for (Instruction instruction : sortedInstructions) {
if (instruction.allPred().stream().anyMatch(it -> lastUpdate[it.num()] == currentIteration)
&& applyTransferFunction(info, instruction)) {
lastUpdate[instruction.num()] = currentIteration;
count++;
}
}
if (count > limit || (System.nanoTime() - startTime) > TIME_LIMIT) {
if (LOG.isDebugEnabled()) {
LOG.debug("Iteration count exceeded on worklist");
}
throw new DFALimitExceededException("Iteration count exceeded on worklist");
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Done in: " + (System.nanoTime() - startTime) / 10e6 + "ms. Ratio: " + dfaCount / myFlow.length);
LOG.debug("Done in: " + (System.nanoTime() - startTime) / 10e6 + "ms. Ratio: " + count / length);
}
return info;
}
private boolean applyTransferFunction(List<E> info, Instruction currentInstruction) {
ProgressManager.checkCanceled();
final int currentNumber = currentInstruction.num();
final E oldE = info.get(currentNumber);
final E joinedE = join(currentInstruction, info);
final E newE = myDfa.fun(joinedE, currentInstruction);
if (!mySemilattice.eq(newE, oldE)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Number: " + currentNumber + " old: " + oldE.toString() + " new: " + newE.toString());
}
info.set(currentNumber, newE);
return true;
}
return false;
}
/**
* Count limit for dfa number of iterations.
@@ -146,15 +152,11 @@ public class DFAEngine<E> {
}
private E join(final Instruction instruction, final List<E> info) {
final Iterable<? extends Instruction> prev = myDfa.isForward() ? instruction.allPred() : instruction.allSucc();
final Iterable<? extends Instruction> prev = instruction.allPred();
final ArrayList<E> prevInfos = new ArrayList<>();
for (Instruction i : prev) {
prevInfos.add(info.get(i.num()));
}
return mySemilattice.join(prevInfos);
}
private Collection<Instruction> getNext(final Instruction curr) {
return myDfa.isForward() ? curr.allSucc() : curr.allPred();
}
}
@@ -25,9 +25,4 @@ public interface DfaInstance<E> {
@NotNull
E initial();
/**
* @deprecated
*/
boolean isForward();
}
@@ -107,8 +107,4 @@ public class PyReachingDefsDfaInstance implements DfaMapInstance<ScopeVariable>
public DFAMap<ScopeVariable> initial() {
return INITIAL_MAP;
}
public boolean isForward() {
return true;
}
}
@@ -0,0 +1,297 @@
f1 = 10
print(f1)
if f1:
a1 = 1
a2 = 2
a3 = 3
a4 = 4
a5 = 5
a6 = 6
elif f1:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
a1 = 1
a2 = 2
a3 = 3
a4 = 4
a5 = 5
elif f1:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
a1 = 1
a2 = 2
a3 = 3
a4 = 4
elif f1:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
a1 = 1
a2 = 2
a3 = 3
elif f1:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
a1 = 1
a2 = 2
elif f1:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
a1 = 1
else:
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
print(f1)
@@ -197,6 +197,10 @@ public class PyUnboundLocalVariableInspectionTest extends PyInspectionTestCase {
doTest();
}
public void testTooLargeToAnalyze() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {