BytecodeAnalysis: partial purity solution before serialization

All purity dependencies within current class are solved before serialization.
This reduces index space and solve complexity after reading (in particular for some methods complete solution could be found analyzing their declaring class only). Also this may reduce the necessity to load some dependencies. E.g. if X::a calls X::b and Y::c (in any order) and X::b is not pure then X::a is solved to be not pure and class Y could be not analyzed at all.
This commit is contained in:
Tagir Valeev
2017-06-08 15:24:11 +07:00
parent b155bb5361
commit 755dcc6044
3 changed files with 54 additions and 15 deletions
@@ -71,6 +71,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
Map<EKey, Equations> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
allEquations = solvePartially(allEquations);
allEquations.forEach((methodKey, equations) -> map.put(methodKey.method.hashed(md), hash(equations, md)));
}
catch (ProcessCanceledException e) {
@@ -85,6 +86,20 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
return map;
}
private static Map<EKey, Equations> solvePartially(Map<EKey, Equations> map) {
PuritySolver solver = new PuritySolver();
EntryStream.of(map).mapToKey((key, eqs) -> new EKey(key.method, Pure, eqs.stable, false))
.flatMapValues(eqs -> eqs.results.stream().map(drp -> drp.result))
.selectValues(Effects.class)
.forKeyValue(solver::addEquation);
Map<EKey, Effects> solved = solver.solve();
Map<EKey, Effects> partiallySolvedPurity =
StreamEx.of(solved, solver.pending).flatMapToEntry(Function.identity()).removeValues(Effects::isTop).toMap();
return EntryStream.of(map)
.mapToValue((key, eqs) -> eqs.update(Pure, partiallySolvedPurity.get(new EKey(key.method, Pure, eqs.stable, false))))
.toMap();
}
private static Equations hash(Equations equations, MessageDigest md) {
return new Equations(ContainerUtil.map(equations.results, drp -> hash(drp, md)), equations.stable);
}
@@ -15,12 +15,11 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Stream;
/**
@@ -129,6 +128,15 @@ class Equations {
public int hashCode() {
return 31 * results.hashCode() + (stable ? 1 : 0);
}
@NotNull
Equations update(Direction direction, Effects newResult) {
List<DirectionResultPair> newPairs = StreamEx.of(this.results)
.map(drp -> drp.updateForDirection(direction, newResult))
.nonNull()
.toList();
return new Equations(newPairs, this.stable);
}
}
class DirectionResultPair {
@@ -159,6 +167,16 @@ class DirectionResultPair {
public String toString() {
return Direction.fromInt(directionKey) + "->" + result;
}
@Nullable
DirectionResultPair updateForDirection(Direction direction, Result newResult) {
if (this.directionKey == direction.asInt()) {
return newResult == null ? null : new DirectionResultPair(direction.asInt(), newResult);
}
else {
return this;
}
}
}
interface Result {}
@@ -227,6 +245,8 @@ final class Pending implements Result {
}
final class Effects implements Result {
static final Set<EffectQuantum> TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum);
@NotNull final DataValue returnValue;
@NotNull final Set<EffectQuantum> effects;
@@ -239,6 +259,10 @@ final class Effects implements Result {
return Stream.concat(returnValue.dependencies(), effects.stream().flatMap(EffectQuantum::dependencies));
}
public boolean isTop() {
return returnValue == DataValue.UnknownDataValue1 && effects.equals(TOP_EFFECTS);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -254,6 +278,7 @@ final class Effects implements Result {
@Override
public String toString() {
return "Effects[" + effects.size() + "|" + returnValue + "]";
Object effectsPresentation = effects.isEmpty() ? "Pure" : effects.size() == 1 ? effects.iterator().next() : effects.size();
return "Effects[" + effectsPresentation + "|" + returnValue + "]";
}
}
@@ -66,7 +66,7 @@ public class PurityAnalysis {
for (EffectQuantum effectQuantum : quanta) {
if (effectQuantum != null) {
if (effectQuantum == EffectQuantum.TopEffectQuantum) {
return returnValue == DataValue.UnknownDataValue1 ? null : new Equation(key, new Effects(returnValue, PuritySolver.TOP_EFFECTS));
return returnValue == DataValue.UnknownDataValue1 ? null : new Equation(key, new Effects(returnValue, Effects.TOP_EFFECTS));
}
effects.add(effectQuantum);
}
@@ -559,11 +559,10 @@ class DataInterpreter extends Interpreter<DataValue> {
}
final class PuritySolver {
static final Set<EffectQuantum> TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum);
private HashMap<EKey, Effects> solved = new HashMap<>();
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();
private final Stack<EKey> moving = new Stack<>();
private HashMap<EKey, Effects> pending = new HashMap<>();
HashMap<EKey, Effects> pending = new HashMap<>();
void addEquation(EKey key, Effects effects) {
Set<EKey> depKeys = effects.dependencies().collect(Collectors.toSet());
@@ -593,7 +592,7 @@ final class PuritySolver {
}
else {
propagateKeys = new EKey[]{key.mkStable(), key};
propagateEffects = new Effects[]{effects, new Effects(DataValue.UnknownDataValue1, TOP_EFFECTS)};
propagateEffects = new Effects[]{effects, new Effects(DataValue.UnknownDataValue1, Effects.TOP_EFFECTS)};
}
for (int i = 0; i < propagateKeys.length; i++) {
EKey pKey = propagateKeys[i];
@@ -615,7 +614,7 @@ final class PuritySolver {
EffectQuantum.CallQuantum call = substitute((EffectQuantum.CallQuantum)dEffect, pKey, pEffects);
if (call.key.equals(pKey)) {
delta = substitute(pEffects, call.data, call.isStatic);
if(delta.equals(TOP_EFFECTS)) {
if(delta.equals(Effects.TOP_EFFECTS)) {
newEffects = delta;
break;
}
@@ -630,7 +629,7 @@ final class PuritySolver {
EffectQuantum.ReturnChangeQuantum retChange = (EffectQuantum.ReturnChangeQuantum)dEffect;
if (retChange.key.equals(pKey)) {
if(pEffects.returnValue != DataValue.LocalDataValue) {
newEffects = delta = TOP_EFFECTS;
newEffects = delta = Effects.TOP_EFFECTS;
break;
}
continue;
@@ -639,8 +638,8 @@ final class PuritySolver {
newEffects.add(dEffect);
}
if (TOP_EFFECTS.equals(delta) && returnValue.equals(DataValue.UnknownDataValue1)) {
solved.put(dKey, new Effects(returnValue, TOP_EFFECTS));
if (Effects.TOP_EFFECTS.equals(delta) && returnValue.equals(DataValue.UnknownDataValue1)) {
solved.put(dKey, new Effects(returnValue, Effects.TOP_EFFECTS));
moving.push(dKey);
}
else {
@@ -682,7 +681,7 @@ final class PuritySolver {
}
private static Set<EffectQuantum> substitute(Effects effects, DataValue[] data, boolean isStatic) {
if (effects.effects.isEmpty() || TOP_EFFECTS.equals(effects.effects)) {
if (effects.effects.isEmpty() || Effects.TOP_EFFECTS.equals(effects.effects)) {
return effects.effects;
}
Set<EffectQuantum> newEffects = new HashSet<>(effects.effects.size());
@@ -710,7 +709,7 @@ final class PuritySolver {
newEffects.add(new EffectQuantum.ReturnChangeQuantum(((DataValue.ReturnDataValue)arg).key));
continue;
}
return TOP_EFFECTS;
return Effects.TOP_EFFECTS;
}
return newEffects;
}