Add local type inference to use in optimizations

This commit is contained in:
Alexey Andreev 2017-01-09 18:49:02 +03:00
parent 5d1e558401
commit 645b2b7cd5
10 changed files with 921 additions and 18 deletions

View File

@ -90,6 +90,7 @@
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" /> <option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="PackageAccessibility" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="PackageAccessibility" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="RawUseOfParameterizedType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantThrows" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="RedundantThrows" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SameParameterValue" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="SameParameterValue" enabled="true" level="WARNING" enabled_by_default="true">
<scope name="classlib-emu" level="WEAK WARNING" enabled="true" /> <scope name="classlib-emu" level="WEAK WARNING" enabled="true" />

View File

@ -21,10 +21,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public interface DependencyInfo { public interface DependencyInfo {
ClassReaderSource getClassSource(); ClassReaderSource getClassSource();

View File

@ -191,11 +191,11 @@ public class DependencyNode implements ValueDependencyInfo {
if (DependencyChecker.shouldLog) { if (DependencyChecker.shouldLog) {
arrayItemNode.tag = tag + "["; arrayItemNode.tag = tag + "[";
} }
arrayItemNode.addConsumer(this::propagate);
} }
return arrayItemNode; return arrayItemNode;
} }
@Override
public DependencyNode getClassValueNode() { public DependencyNode getClassValueNode() {
if (classValueNode == null) { if (classValueNode == null) {
classValueNode = new DependencyNode(dependencyChecker, degree); classValueNode = new DependencyNode(dependencyChecker, degree);
@ -203,7 +203,6 @@ public class DependencyNode implements ValueDependencyInfo {
if (DependencyChecker.shouldLog) { if (DependencyChecker.shouldLog) {
classValueNode.tag = tag + "@"; classValueNode.tag = tag + "@";
} }
classValueNode.addConsumer(this::propagate);
} }
return classValueNode; return classValueNode;
} }

View File

@ -20,10 +20,6 @@ import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class MethodDependency implements MethodDependencyInfo { public class MethodDependency implements MethodDependencyInfo {
private DependencyChecker dependencyChecker; private DependencyChecker dependencyChecker;
DependencyNode[] variableNodes; DependencyNode[] variableNodes;

View File

@ -17,10 +17,6 @@ package org.teavm.dependency;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public interface MethodDependencyInfo { public interface MethodDependencyInfo {
ValueDependencyInfo[] getVariables(); ValueDependencyInfo[] getVariables();

View File

@ -15,10 +15,6 @@
*/ */
package org.teavm.dependency; package org.teavm.dependency;
/**
*
* @author Alexey Andreev
*/
public interface ValueDependencyInfo { public interface ValueDependencyInfo {
String[] getTypes(); String[] getTypes();

View File

@ -0,0 +1,465 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.model.analysis;
import com.carrotsearch.hppc.IntObjectMap;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.FieldDependencyInfo;
import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.dependency.ValueDependencyInfo;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.Incoming;
import org.teavm.model.Instruction;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Phi;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.CloneArrayInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.instructions.RaiseInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
public class ClassInference {
private DependencyInfo dependencyInfo;
private Graph assignmentGraph;
private Graph cloneGraph;
private Graph arrayGraph;
private Graph itemGraph;
private List<IntObjectMap<ValueType>> casts;
private IntObjectMap<IntSet> exceptionMap;
private VirtualCallSite[][] virtualCallSites;
private List<Task> initialTasks;
private List<List<Set<String>>> types;
private List<Set<String>> finalTypes;
public ClassInference(DependencyInfo dependencyInfo) {
this.dependencyInfo = dependencyInfo;
}
public void infer(Program program, MethodReference methodReference) {
buildGraphs(program);
MethodDependencyInfo thisMethodDep = dependencyInfo.getMethod(methodReference);
for (String thisType : thisMethodDep.getVariable(0).getTypes()) {
initialTasks.add(new Task(0, 0, thisType));
}
types = new ArrayList<>(program.variableCount());
for (int i = 0; i < program.variableCount(); ++i) {
List<Set<String>> variableTypes = new ArrayList<>();
types.add(variableTypes);
for (int j = 0; j < 3; ++j) {
variableTypes.add(new LinkedHashSet<>());
}
}
propagate(program);
assignmentGraph = null;
cloneGraph = null;
arrayGraph = null;
itemGraph = null;
casts = null;
exceptionMap = null;
virtualCallSites = null;
finalTypes = new ArrayList<>(program.variableCount());
for (int i = 0; i < program.variableCount(); ++i) {
finalTypes.add(types.get(i).get(0));
}
types = null;
}
public String[] classesOf(int variableIndex) {
return finalTypes.get(variableIndex).toArray(new String[0]);
}
private void buildGraphs(Program program) {
GraphBuildingVisitor visitor = new GraphBuildingVisitor(program.variableCount(), dependencyInfo);
for (BasicBlock block : program.getBasicBlocks()) {
visitor.currentBlock = block;
for (Phi phi : block.getPhis()) {
visitor.visit(phi);
}
for (Instruction insn : block) {
insn.acceptVisitor(visitor);
}
}
assignmentGraph = visitor.assignmentGraphBuilder.build();
cloneGraph = visitor.cloneGraphBuilder.build();
arrayGraph = visitor.arrayGraphBuilder.build();
itemGraph = visitor.itemGraphBuilder.build();
casts = visitor.casts;
exceptionMap = visitor.exceptionMap;
initialTasks = visitor.tasks;
virtualCallSites = new VirtualCallSite[program.variableCount()][];
for (int i = 0; i < virtualCallSites.length; ++i) {
List<VirtualCallSite> buildCallSites = visitor.virtualCallSites.get(i);
if (buildCallSites != null) {
virtualCallSites[i] = buildCallSites.toArray(new VirtualCallSite[0]);
}
}
}
private void propagate(Program program) {
ClassReaderSource classSource = dependencyInfo.getClassSource();
Queue<Task> queue = new ArrayDeque<>();
queue.addAll(initialTasks);
initialTasks = null;
while (!queue.isEmpty()) {
Task task = queue.remove();
if (task.degree < 0) {
BasicBlock block = program.basicBlockAt(task.variable);
for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
if (tryCatch.getExceptionType() == null
|| classSource.isSuperType(tryCatch.getExceptionType(), task.className).orElse(false)) {
Variable exception = tryCatch.getHandler().getExceptionVariable();
if (exception != null) {
queue.add(new Task(exception.getIndex(), 0, task.className));
}
break;
}
}
continue;
}
List<Set<String>> variableTypes = types.get(task.variable);
if (task.degree >= variableTypes.size()) {
continue;
}
Set<String> typeSet = variableTypes.get(task.degree);
if (!typeSet.add(task.className)) {
continue;
}
for (int successor : assignmentGraph.outgoingEdges(task.variable)) {
queue.add(new Task(successor, task.degree, task.className));
int itemDegree = task.degree + 1;
if (itemDegree < variableTypes.size()) {
for (String type : variableTypes.get(itemDegree)) {
queue.add(new Task(successor, itemDegree, type));
}
}
List<Set<String>> successorVariableTypes = types.get(successor);
if (itemDegree < successorVariableTypes.size()) {
for (String type : successorVariableTypes.get(itemDegree)) {
queue.add(new Task(task.variable, itemDegree, type));
}
}
}
if (task.degree > 0) {
for (int predecessor : assignmentGraph.incomingEdges(task.variable)) {
queue.add(new Task(predecessor, task.degree, task.className));
}
for (int successor : itemGraph.outgoingEdges(task.variable)) {
queue.add(new Task(successor, task.degree - 1, task.className));
}
} else {
for (int successor : cloneGraph.outgoingEdges(task.variable)) {
queue.add(new Task(successor, 0, task.className));
}
IntSet blocks = exceptionMap.get(task.variable);
if (blocks != null) {
for (int block : blocks.toArray()) {
queue.add(new Task(block, -1, task.className));
}
}
VirtualCallSite[] callSites = virtualCallSites[task.variable];
if (callSites != null) {
for (VirtualCallSite callSite : callSites) {
MethodReference rawMethod = new MethodReference(task.className,
callSite.method.getDescriptor());
MethodReader resolvedMethod = classSource.resolve(rawMethod);
if (resolvedMethod == null) {
continue;
}
MethodReference resolvedMethodRef = resolvedMethod.getReference();
if (callSite.resolvedMethods.add(resolvedMethodRef)) {
MethodDependencyInfo methodDep = dependencyInfo.getMethod(resolvedMethodRef);
if (callSite.receiver >= 0) {
readValue(methodDep.getResult(), program.variableAt(callSite.receiver), queue);
}
for (int i = 0; i < callSite.arguments.length; ++i) {
writeValue(methodDep.getVariable(i + 1), program.variableAt(callSite.arguments[i]),
queue);
}
for (String type : methodDep.getThrown().getTypes()) {
queue.add(new Task(callSite.block, -1, type));
}
}
}
}
}
for (int successor : arrayGraph.outgoingEdges(task.variable)) {
queue.add(new Task(successor, task.degree + 1, task.className));
}
IntObjectMap<ValueType> variableCasts = casts.get(task.variable);
if (variableCasts != null) {
ValueType type = task.className.startsWith("[")
? ValueType.parse(task.className)
: ValueType.object(task.className);
for (int target : variableCasts.keys().toArray()) {
ValueType targetType = variableCasts.get(target);
if (classSource.isSuperType(targetType, type).orElse(false)) {
queue.add(new Task(target, 0, task.className));
}
}
}
}
}
static class GraphBuildingVisitor extends AbstractInstructionVisitor {
DependencyInfo dependencyInfo;
GraphBuilder assignmentGraphBuilder;
GraphBuilder cloneGraphBuilder;
GraphBuilder arrayGraphBuilder;
GraphBuilder itemGraphBuilder;
List<IntObjectMap<ValueType>> casts = new ArrayList<>();
IntObjectMap<IntSet> exceptionMap = new IntObjectOpenHashMap<>();
List<Task> tasks = new ArrayList<>();
List<List<VirtualCallSite>> virtualCallSites = new ArrayList<>();
BasicBlock currentBlock;
GraphBuildingVisitor(int variableCount, DependencyInfo dependencyInfo) {
this.dependencyInfo = dependencyInfo;
assignmentGraphBuilder = new GraphBuilder(variableCount);
cloneGraphBuilder = new GraphBuilder(variableCount);
arrayGraphBuilder = new GraphBuilder(variableCount);
itemGraphBuilder = new GraphBuilder(variableCount);
casts = new ArrayList<>(variableCount);
for (int i = 0; i < variableCount; ++i) {
casts.add(new IntObjectOpenHashMap<>());
}
virtualCallSites = new ArrayList<>(Collections.nCopies(variableCount, null));
}
public void visit(Phi phi) {
for (Incoming incoming : phi.getIncomings()) {
assignmentGraphBuilder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
}
}
@Override
public void visit(ClassConstantInstruction insn) {
tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.Class"));
}
@Override
public void visit(StringConstantInstruction insn) {
tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.String"));
}
@Override
public void visit(AssignInstruction insn) {
assignmentGraphBuilder.addEdge(insn.getAssignee().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(CastInstruction insn) {
casts.get(insn.getValue().getIndex()).put(insn.getReceiver().getIndex(), insn.getTargetType());
}
@Override
public void visit(RaiseInstruction insn) {
IntSet blockIndexes = exceptionMap.get(insn.getException().getIndex());
if (blockIndexes == null) {
blockIndexes = new IntOpenHashSet();
exceptionMap.put(insn.getException().getIndex(), blockIndexes);
}
blockIndexes.add(currentBlock.getIndex());
}
@Override
public void visit(ConstructArrayInstruction insn) {
tasks.add(new Task(insn.getReceiver().getIndex(), 0, ValueType.arrayOf(insn.getItemType()).toString()));
}
@Override
public void visit(ConstructInstruction insn) {
tasks.add(new Task(insn.getReceiver().getIndex(), 0, insn.getType()));
}
@Override
public void visit(ConstructMultiArrayInstruction insn) {
ValueType type = insn.getItemType();
for (int i = 0; i < insn.getDimensions().size(); ++i) {
type = ValueType.arrayOf(type);
}
tasks.add(new Task(insn.getReceiver().getIndex(), 0, type.toString()));
}
@Override
public void visit(GetFieldInstruction insn) {
FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField());
ValueDependencyInfo valueDep = fieldDep.getValue();
readValue(valueDep, insn.getReceiver(), tasks);
}
@Override
public void visit(PutFieldInstruction insn) {
FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField());
ValueDependencyInfo valueDep = fieldDep.getValue();
writeValue(valueDep, insn.getValue(), tasks);
}
@Override
public void visit(CloneArrayInstruction insn) {
cloneGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(UnwrapArrayInstruction insn) {
assignmentGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(GetElementInstruction insn) {
itemGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(PutElementInstruction insn) {
arrayGraphBuilder.addEdge(insn.getValue().getIndex(), insn.getArray().getIndex());
}
@Override
public void visit(InvokeInstruction insn) {
if (insn.getType() == InvocationType.VIRTUAL) {
int instance = insn.getInstance().getIndex();
List<VirtualCallSite> callSites = virtualCallSites.get(instance);
if (callSites == null) {
callSites = new ArrayList<>();
virtualCallSites.set(instance, callSites);
}
VirtualCallSite callSite = new VirtualCallSite();
callSite.method = insn.getMethod();
callSite.arguments = new int[insn.getArguments().size()];
for (int i = 0; i < insn.getArguments().size(); ++i) {
callSite.arguments[i] = insn.getArguments().get(i).getIndex();
}
callSite.receiver = insn.getReceiver() != null ? insn.getReceiver().getIndex() : -1;
callSite.block = currentBlock.getIndex();
callSites.add(callSite);
return;
}
MethodDependencyInfo methodDep = dependencyInfo.getMethod(insn.getMethod());
if (insn.getReceiver() != null) {
readValue(methodDep.getResult(), insn.getReceiver(), tasks);
}
for (int i = 0; i < insn.getArguments().size(); ++i) {
writeValue(methodDep.getVariable(i + 1), insn.getArguments().get(i), tasks);
}
for (String type : methodDep.getThrown().getTypes()) {
tasks.add(new Task(currentBlock.getIndex(), -1, type));
}
}
}
private static void readValue(ValueDependencyInfo valueDep, Variable receiver, Collection<Task> tasks) {
int depth = 0;
boolean hasArrayType;
do {
for (String type : valueDep.getTypes()) {
tasks.add(new Task(receiver.getIndex(), depth, type));
}
depth++;
hasArrayType = valueDep.hasArrayType();
valueDep = valueDep.getArrayItem();
} while (hasArrayType);
}
private static void writeValue(ValueDependencyInfo valueDep, Variable source, Collection<Task> tasks) {
int depth = 0;
while (valueDep.hasArrayType()) {
depth++;
valueDep = valueDep.getArrayItem();
for (String type : valueDep.getTypes()) {
tasks.add(new Task(source.getIndex(), depth, type));
}
}
}
static class Task {
int variable;
int degree;
String className;
Task(int variable, int degree, String className) {
this.variable = variable;
this.degree = degree;
this.className = className;
}
}
static class VirtualCallSite {
Set<MethodReference> resolvedMethods = new HashSet<>();
MethodReference method;
int[] arguments;
int receiver;
int block;
}
}

View File

@ -0,0 +1,291 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.dependency;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.common.DisjointSet;
import org.teavm.diagnostics.Problem;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
import org.teavm.model.analysis.ClassInference;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
import org.teavm.parsing.ClasspathClassHolderSource;
import org.teavm.vm.TeaVM;
import org.teavm.vm.TeaVMBuilder;
import org.teavm.vm.TeaVMPhase;
import org.teavm.vm.TeaVMProgressFeedback;
import org.teavm.vm.TeaVMProgressListener;
public class DependencyTest {
@Rule
public final TestName testName = new TestName();
private static ClassHolderSource classSource;
@BeforeClass
public static void prepare() {
classSource = new ClasspathClassHolderSource(DependencyTest.class.getClassLoader());
}
@AfterClass
public static void cleanup() {
classSource = null;
}
@Test
public void virtualCall() {
doTest();
}
@Test
public void instanceOf() {
doTest();
}
@Test
public void catchException() {
doTest();
}
@Test
public void propagateException() {
doTest();
}
@Test
public void arrays() {
doTest();
}
@Test
public void arraysPassed() {
doTest();
}
@Test
public void arraysRetrieved() {
doTest();
}
private void doTest() {
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget())
.setClassLoader(DependencyTest.class.getClassLoader())
.setClassSource(classSource)
.build();
vm.setProgressListener(new TeaVMProgressListener() {
@Override
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
return phase == TeaVMPhase.DEPENDENCY_CHECKING
? TeaVMProgressFeedback.CONTINUE
: TeaVMProgressFeedback.CANCEL;
}
@Override
public TeaVMProgressFeedback progressReached(int progress) {
return TeaVMProgressFeedback.CONTINUE;
}
});
vm.installPlugins();
MethodReference testMethod = new MethodReference(DependencyTestData.class,
testName.getMethodName(), void.class);
vm.entryPoint(testMethod).withValue(0, DependencyTestData.class.getName());
vm.build(fileName -> new ByteArrayOutputStream(), "out");
List<Problem> problems = vm.getProblemProvider().getSevereProblems();
if (!problems.isEmpty()) {
Problem problem = problems.get(0);
Assert.fail("Error at " + problem.getLocation().getSourceLocation() + ": " + problem.getText());
}
MethodHolder method = classSource.get(testMethod.getClassName()).getMethod(testMethod.getDescriptor());
List<Assertion> assertions = collectAssertions(method);
processAssertions(assertions, vm.getDependencyInfo().getMethod(testMethod), vm.getDependencyInfo(),
method.getProgram());
}
private void processAssertions(List<Assertion> assertions, MethodDependencyInfo methodDep,
DependencyInfo dependencyInfo, Program program) {
ClassInference classInference = new ClassInference(dependencyInfo);
classInference.infer(program, methodDep.getReference());
for (Assertion assertion : assertions) {
ValueDependencyInfo valueDep = methodDep.getVariable(assertion.value);
String[] actualTypes = valueDep.getTypes();
String[] expectedTypes = assertion.expectedTypes.clone();
Arrays.sort(actualTypes);
Arrays.sort(expectedTypes);
Assert.assertArrayEquals("Assertion at " + assertion.location, expectedTypes, actualTypes);
actualTypes = classInference.classesOf(assertion.value);
Arrays.sort(actualTypes);
Assert.assertArrayEquals("Assertion at " + assertion.location + " (class inference)",
expectedTypes, actualTypes);
}
}
private List<Assertion> collectAssertions(MethodHolder method) {
Program program = method.getProgram();
AliasCollector aliasCollector = new AliasCollector(program.variableCount());
for (BasicBlock block : program.getBasicBlocks()) {
for (Instruction instruction : block) {
instruction.acceptVisitor(aliasCollector);
}
}
int[] aliases = aliasCollector.disjointSet.pack(program.variableCount());
AssertionCollector assertionCollector = new AssertionCollector(aliases);
for (BasicBlock block : program.getBasicBlocks()) {
for (Instruction instruction : block) {
instruction.acceptVisitor(assertionCollector);
}
}
assertionCollector.postProcess();
return assertionCollector.assertions;
}
static class AliasCollector extends AbstractInstructionVisitor {
DisjointSet disjointSet = new DisjointSet();
AliasCollector(int variableCount) {
for (int i = 0; i < variableCount; ++i) {
disjointSet.create();
}
}
@Override
public void visit(AssignInstruction insn) {
disjointSet.union(insn.getReceiver().getIndex(), insn.getAssignee().getIndex());
}
@Override
public void visit(UnwrapArrayInstruction insn) {
disjointSet.union(insn.getReceiver().getIndex(), insn.getArray().getIndex());
}
}
static class AssertionCollector extends AbstractInstructionVisitor {
static final MethodReference assertionMethod = new MethodReference(MetaAssertions.class,
"assertTypes", Object.class, Class[].class, void.class);
int[] aliases;
List<Assertion> assertions = new ArrayList<>();
IntSet[] arrayContent;
ValueType[] classConstants;
AssertionCollector(int[] aliases) {
this.aliases = aliases;
classConstants = new ValueType[aliases.length];
arrayContent = new IntSet[aliases.length];
}
void postProcess() {
int[] aliasInstances = new int[aliases.length];
Arrays.fill(aliasInstances, -1);
for (int i = 0; i < aliases.length; ++i) {
int alias = aliases[i];
if (aliasInstances[alias] < 0) {
aliasInstances[alias] = i;
}
}
for (Assertion assertion : assertions) {
IntSet items = arrayContent[assertion.array];
if (items != null) {
Set<String> expectedClasses = new HashSet<>();
for (int item : items.toArray()) {
ValueType constant = classConstants[item];
if (constant != null) {
String expectedClass;
if (constant instanceof ValueType.Object) {
expectedClass = ((ValueType.Object) constant).getClassName();
} else {
expectedClass = constant.toString();
}
expectedClasses.add(expectedClass);
}
}
assertion.expectedTypes = expectedClasses.toArray(new String[0]);
} else {
assertion.expectedTypes = new String[0];
}
assertion.value = aliasInstances[assertion.value];
}
}
@Override
public void visit(InvokeInstruction insn) {
if (insn.getMethod().equals(assertionMethod)) {
Assertion assertion = new Assertion();
assertion.value = aliases[insn.getArguments().get(0).getIndex()];
assertion.array = aliases[insn.getArguments().get(1).getIndex()];
assertion.location = insn.getLocation();
assertions.add(assertion);
}
}
@Override
public void visit(ClassConstantInstruction insn) {
classConstants[aliases[insn.getReceiver().getIndex()]] = insn.getConstant();
}
@Override
public void visit(PutElementInstruction insn) {
int array = aliases[insn.getArray().getIndex()];
int value = aliases[insn.getValue().getIndex()];
IntSet items = arrayContent[array];
if (items == null) {
items = new IntOpenHashSet();
arrayContent[array] = items;
}
items.add(value);
}
}
private static class Assertion {
int value;
int array;
TextLocation location;
String[] expectedTypes;
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.dependency;
public class DependencyTestData {
public void virtualCall() {
MetaAssertions.assertTypes(getI(0).foo(), String.class, Integer.class, Class.class);
}
public void instanceOf() {
MetaAssertions.assertTypes((String) getI(0).foo(), String.class);
}
public void catchException() throws Exception {
try {
throw createException(0);
} catch (IndexOutOfBoundsException e) {
MetaAssertions.assertTypes(e, IndexOutOfBoundsException.class);
} catch (RuntimeException e) {
MetaAssertions.assertTypes(e, UnsupportedOperationException.class, IllegalArgumentException.class);
}
}
public void propagateException() {
try {
catchException();
} catch (Throwable e) {
MetaAssertions.assertTypes(e, Exception.class);
}
}
public void arrays() {
Object[] array = { new String("123"), new Integer(123), String.class };
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
}
public void arraysPassed() {
Object[] array = new Object[3];
fillArray(array);
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
Object[] array2 = new Object[3];
staticArrayField = array2;
fillStaticArray();
MetaAssertions.assertTypes(array2[0], Long.class, RuntimeException.class);
}
public void arraysRetrieved() {
Object[] array = createArray();
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
staticArrayField = new Object[3];
Object[] array2 = staticArrayField;
fillStaticArray();
MetaAssertions.assertTypes(array2[0], Long.class, RuntimeException.class);
}
static Object[] staticArrayField;
private Object[] createArray() {
Object[] array = new Object[3];
fillArray(array);
return array;
}
private void fillArray(Object[] array) {
array[0] = "123";
array[1] = 123;
array[2] = String.class;
}
private void fillStaticArray() {
staticArrayField[0] = 42L;
staticArrayField[0] = new RuntimeException();
}
private I getI(int index) {
switch (index) {
case 0:
return new A();
case 1:
return new B();
default:
return new C();
}
}
private Exception createException(int index) {
switch (index) {
case 0:
throw new IndexOutOfBoundsException();
case 1:
throw new UnsupportedOperationException();
case 2:
throw new IllegalArgumentException();
default:
return new Exception();
}
}
interface I {
Object foo();
}
class A implements I {
@Override
public Object foo() {
return "123";
}
}
class B implements I {
@Override
public Object foo() {
return Object.class;
}
}
class C implements I {
@Override
public Object foo() {
return 123;
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.dependency;
public final class MetaAssertions {
private MetaAssertions() {
}
public static void assertTypes(Object value, Class<?>... types) {
// do nothing, process by TeaVM test infrastructure
}
}