Add nullness analysis

This commit is contained in:
Alexey Andreev 2016-12-27 00:28:37 +03:00
parent ae5e1e4962
commit 9dc4b47253
18 changed files with 791 additions and 181 deletions

View File

@ -62,6 +62,7 @@ public class Variable implements VariableReader {
this.debugName = debugName; this.debugName = debugName;
} }
@Override
public String getLabel() { public String getLabel() {
return label; return label;
} }

View File

@ -22,5 +22,7 @@ public interface VariableReader {
String getDebugName(); String getDebugName();
String getLabel();
int getRegister(); int getRegister();
} }

View File

@ -15,17 +15,75 @@
*/ */
package org.teavm.model.analysis; package org.teavm.model.analysis;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Set;
import org.teavm.model.BasicBlock;
import org.teavm.model.Instruction;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.Phi;
import org.teavm.model.Program; import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.TryCatchJoint;
import org.teavm.model.Variable;
import org.teavm.model.util.DefinitionExtractor;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.PhiUpdater;
public class NullnessInformation { public class NullnessInformation {
NullnessInformation() { private Program program;
private BitSet synthesizedVariables;
private PhiUpdater phiUpdater;
private BitSet notNullVariables;
private BitSet nullVariables;
NullnessInformation(Program program, BitSet synthesizedVariables, PhiUpdater phiUpdater, BitSet notNullVariables,
BitSet nullVariables) {
this.program = program;
this.synthesizedVariables = synthesizedVariables;
this.phiUpdater = phiUpdater;
this.notNullVariables = notNullVariables;
this.nullVariables = nullVariables;
}
public boolean isNotNull(Variable variable) {
return notNullVariables.get(variable.getIndex());
}
public boolean isNull(Variable variable) {
return nullVariables.get(variable.getIndex());
} }
public void dispose() { public void dispose() {
Set<Phi> phisToRemove = new HashSet<>(phiUpdater.getSynthesizedPhis());
Set<TryCatchJoint> jointsToRemove = new HashSet<>(phiUpdater.getSynthesizedJoints());
DefinitionExtractor defExtractor = new DefinitionExtractor();
InstructionVariableMapper variableMapper = new InstructionVariableMapper(var -> {
int source = phiUpdater.getSourceVariable(var.getIndex());
return source >= 0 ? program.variableAt(source) : var;
});
for (BasicBlock block : program.getBasicBlocks()) {
block.getPhis().removeIf(phisToRemove::contains);
for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
tryCatch.getJoints().removeIf(jointsToRemove::contains);
}
for (Instruction insn : block) {
insn.acceptVisitor(defExtractor);
if (Arrays.stream(defExtractor.getDefinedVariables())
.anyMatch(var -> synthesizedVariables.get(var.getIndex()))) {
insn.delete();
} else {
insn.acceptVisitor(variableMapper);
}
}
}
} }
public static NullnessInformation build(Program program) { public static NullnessInformation build(Program program, MethodDescriptor methodDescriptor) {
NullnessInformation instance = new NullnessInformation(); NullnessInformationBuilder builder = new NullnessInformationBuilder(program, methodDescriptor);
return instance; builder.build();
return new NullnessInformation(program, builder.synthesizedVariables, builder.phiUpdater,
builder.notNullVariables, builder.nullVariables);
} }
} }

View File

@ -15,11 +15,18 @@
*/ */
package org.teavm.model.analysis; package org.teavm.model.analysis;
import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
import org.teavm.common.Graph; import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder; import org.teavm.common.GraphBuilder;
import org.teavm.common.GraphUtils;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.Incoming; import org.teavm.model.Incoming;
import org.teavm.model.Instruction; import org.teavm.model.Instruction;
@ -32,7 +39,7 @@ import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor; import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.ArrayLengthInstruction; import org.teavm.model.instructions.ArrayLengthInstruction;
import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.BranchingCondition; import org.teavm.model.instructions.BinaryBranchingInstruction;
import org.teavm.model.instructions.BranchingInstruction; import org.teavm.model.instructions.BranchingInstruction;
import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.CloneArrayInstruction; import org.teavm.model.instructions.CloneArrayInstruction;
@ -40,7 +47,6 @@ import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction; import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction; import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.GetFieldInstruction; import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InstructionVisitor;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.MonitorEnterInstruction; import org.teavm.model.instructions.MonitorEnterInstruction;
import org.teavm.model.instructions.MonitorExitInstruction; import org.teavm.model.instructions.MonitorExitInstruction;
@ -49,20 +55,22 @@ import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.PutFieldInstruction; import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction; import org.teavm.model.instructions.UnwrapArrayInstruction;
import org.teavm.model.util.DominatorWalker;
import org.teavm.model.util.DominatorWalkerCallback;
import org.teavm.model.util.PhiUpdater; import org.teavm.model.util.PhiUpdater;
class NullnessInformationBuilder { class NullnessInformationBuilder {
private Program program; private Program program;
private MethodDescriptor methodDescriptor; private MethodDescriptor methodDescriptor;
private BitSet notNullVariables = new BitSet(); BitSet notNullVariables = new BitSet();
private BitSet nullVariables = new BitSet(); BitSet nullVariables = new BitSet();
private BitSet synthesizedVariables = new BitSet(); BitSet synthesizedVariables = new BitSet();
private PhiUpdater phiUpdater; PhiUpdater phiUpdater;
private List<NullConstantInstruction> nullInstructions = new ArrayList<>(); private List<NullConstantInstruction> nullInstructions = new ArrayList<>();
private List<NullCheckInstruction> notNullInstructions = new ArrayList<>(); private List<NullCheckInstruction> notNullInstructions = new ArrayList<>();
private Graph assignmentGraph; private Graph assignmentGraph;
private int[] nullPredecessorsLeft;
private int[] notNullPredecessorsLeft; private int[] notNullPredecessorsLeft;
private int[] sccIndexes;
NullnessInformationBuilder(Program program, MethodDescriptor methodDescriptor) { NullnessInformationBuilder(Program program, MethodDescriptor methodDescriptor) {
this.program = program; this.program = program;
@ -72,12 +80,13 @@ class NullnessInformationBuilder {
void build() { void build() {
extendProgram(); extendProgram();
buildAssignmentGraph(); buildAssignmentGraph();
findKnownNullness(); propagateNullness();
} }
private void extendProgram() { private void extendProgram() {
insertAdditionalVariables(); insertAdditionalVariables();
notNullVariables.set(0);
Variable[] parameters = new Variable[methodDescriptor.parameterCount() + 1]; Variable[] parameters = new Variable[methodDescriptor.parameterCount() + 1];
for (int i = 0; i < parameters.length; ++i) { for (int i = 0; i < parameters.length; ++i) {
parameters[i] = program.variableAt(i); parameters[i] = program.variableAt(i);
@ -89,55 +98,28 @@ class NullnessInformationBuilder {
} }
private void insertAdditionalVariables() { private void insertAdditionalVariables() {
DominatorWalker walker = new DominatorWalker(program);
NullExtensionVisitor ev = new NullExtensionVisitor(); NullExtensionVisitor ev = new NullExtensionVisitor();
for (BasicBlock block : program.getBasicBlocks()) { walker.walk(ev);
Instruction lastInstruction = block.getLastInstruction();
if (lastInstruction instanceof BranchingInstruction) {
BranchingInstruction branching = (BranchingInstruction) lastInstruction;
if (branching.getCondition() == BranchingCondition.NULL) {
insertNullAndNotNull(branching.getOperand(), branching.getConsequent(), branching.getAlternative());
} else if (branching.getCondition() == BranchingCondition.NOT_NULL) {
insertNullAndNotNull(branching.getOperand(), branching.getAlternative(), branching.getConsequent());
}
}
for (Instruction instruction = block.getFirstInstruction(); instruction != null;) {
Instruction next = instruction.getNext();
instruction.acceptVisitor(ev);
instruction = next;
}
}
} }
private void collectAdditionalVariables() { private void collectAdditionalVariables() {
for (NullConstantInstruction nullInstruction : nullInstructions) { for (NullConstantInstruction nullInstruction : nullInstructions) {
nullVariables.set(nullInstruction.getReceiver().getIndex()); nullVariables.set(nullInstruction.getReceiver().getIndex());
synthesizedVariables.set(nullInstruction.getReceiver().getIndex());
} }
for (NullCheckInstruction notNullInstruction : notNullInstructions) { for (NullCheckInstruction notNullInstruction : notNullInstructions) {
notNullVariables.set(notNullInstruction.getReceiver().getIndex()); notNullVariables.set(notNullInstruction.getReceiver().getIndex());
synthesizedVariables.set(notNullInstruction.getReceiver().getIndex());
} }
synthesizedVariables.or(nullVariables);
synthesizedVariables.or(notNullVariables);
nullInstructions.clear(); nullInstructions.clear();
notNullInstructions.clear(); notNullInstructions.clear();
} }
private void insertNullAndNotNull(Variable variable, BasicBlock nullBlock, BasicBlock notNullBlock) {
NullCheckInstruction notNullInstruction = new NullCheckInstruction();
notNullInstruction.setValue(variable);
notNullInstruction.setReceiver(variable);
notNullBlock.addFirst(notNullInstruction);
notNullInstructions.add(notNullInstruction);
NullConstantInstruction nullInstruction = new NullConstantInstruction();
nullInstruction.setReceiver(variable);
nullBlock.addFirst(nullInstruction);
nullInstructions.add(nullInstruction);
}
private void buildAssignmentGraph() { private void buildAssignmentGraph() {
GraphBuilder builder = new GraphBuilder(); GraphBuilder builder = new GraphBuilder(program.variableCount());
for (BasicBlock block : program.getBasicBlocks()) { for (BasicBlock block : program.getBasicBlocks()) {
for (Phi phi : block.getPhis()) { for (Phi phi : block.getPhis()) {
for (Incoming incoming : phi.getIncomings()) { for (Incoming incoming : phi.getIncomings()) {
@ -161,25 +143,93 @@ class NullnessInformationBuilder {
} }
assignmentGraph = builder.build(); assignmentGraph = builder.build();
// TODO: handle SCCs sccIndexes = new int[program.variableCount()];
if (assignmentGraph.size() > 0) {
int[][] sccs = GraphUtils.findStronglyConnectedComponents(assignmentGraph, new int[]{0});
for (int i = 0; i < sccs.length; ++i) {
for (int sccNode : sccs[i]) {
sccIndexes[sccNode] = i + 1;
}
}
}
nullPredecessorsLeft = new int[assignmentGraph.size()];
notNullPredecessorsLeft = new int[assignmentGraph.size()]; notNullPredecessorsLeft = new int[assignmentGraph.size()];
for (int i = 0; i < assignmentGraph.size(); ++i) { for (int i = 0; i < assignmentGraph.size(); ++i) {
nullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
notNullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i); notNullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
} if (sccIndexes[i] > 0) {
} for (int predecessor : assignmentGraph.outgoingEdges(i)) {
if (sccIndexes[predecessor] == sccIndexes[i]) {
private void findKnownNullness() { notNullPredecessorsLeft[i]--;
for (BasicBlock block : program.getBasicBlocks()) { }
for (Instruction instruction : block) { }
instruction.acceptVisitor(nullnessVisitor);
} }
} }
} }
class NullExtensionVisitor extends AbstractInstructionVisitor { private void propagateNullness() {
if (assignmentGraph.size() == 0) {
return;
}
IntDeque deque = new IntArrayDeque();
for (int i = notNullVariables.nextSetBit(0); i >= 0; i = notNullVariables.nextSetBit(i + 1)) {
deque.addLast(i);
}
boolean[] visited = new boolean[program.variableCount()];
while (!deque.isEmpty()) {
int node = deque.removeFirst();
if (visited[node]) {
continue;
}
visited[node] = true;
for (int successor : assignmentGraph.outgoingEdges(node)) {
if (sccIndexes[successor] == 0 || sccIndexes[successor] != sccIndexes[node]) {
if (--notNullPredecessorsLeft[successor] == 0) {
deque.addLast(successor);
}
}
}
}
}
class NullExtensionVisitor extends AbstractInstructionVisitor implements DominatorWalkerCallback<State> {
State currentState;
BasicBlock currentBlock;
IntIntMap nullSuccessors = new IntIntOpenHashMap();
IntIntMap notNullSuccessors = new IntIntOpenHashMap();
@Override
public State visit(BasicBlock block) {
currentState = new State();
currentBlock = block;
if (nullSuccessors.containsKey(block.getIndex())) {
int varIndex = nullSuccessors.remove(block.getIndex());
insertNullInstruction(program.variableAt(varIndex));
}
if (notNullSuccessors.containsKey(block.getIndex())) {
int varIndex = notNullSuccessors.remove(block.getIndex());
insertNotNullInstruction(null, program.variableAt(varIndex));
}
for (Instruction insn : block) {
insn.acceptVisitor(this);
}
return currentState;
}
@Override
public void endVisit(BasicBlock block, State state) {
for (int rollbackToNull : state.newlyNonNull.toArray()) {
notNullVariables.clear(rollbackToNull);
}
for (int rollbackToNotNull : state.newlyNull.toArray()) {
nullVariables.clear(rollbackToNotNull);
}
}
@Override @Override
public void visit(GetFieldInstruction insn) { public void visit(GetFieldInstruction insn) {
if (insn.getInstance() != null) { if (insn.getInstance() != null) {
@ -226,16 +276,11 @@ class NullnessInformationBuilder {
insertNotNullInstruction(insn, insn.getObjectRef()); insertNotNullInstruction(insn, insn.getObjectRef());
} }
private void insertNotNullInstruction(Instruction currentInstruction, Variable var) { @Override
NullCheckInstruction insn = new NullCheckInstruction(); public void visit(StringConstantInstruction insn) {
insn.setReceiver(var); notNullVariables.set(insn.getReceiver().getIndex());
insn.setValue(var);
notNullInstructions.add(insn);
currentInstruction.insertNext(insn);
} }
}
private InstructionVisitor nullnessVisitor = new AbstractInstructionVisitor() {
@Override @Override
public void visit(ClassConstantInstruction insn) { public void visit(ClassConstantInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex()); notNullVariables.set(insn.getReceiver().getIndex());
@ -247,8 +292,9 @@ class NullnessInformationBuilder {
} }
@Override @Override
public void visit(StringConstantInstruction insn) { public void visit(AssignInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex()); notNullVariables.set(insn.getReceiver().getIndex(), notNullVariables.get(insn.getAssignee().getIndex()));
nullVariables.set(insn.getReceiver().getIndex(), nullVariables.get(insn.getAssignee().getIndex()));
} }
@Override @Override
@ -268,7 +314,95 @@ class NullnessInformationBuilder {
@Override @Override
public void visit(NullCheckInstruction insn) { public void visit(NullCheckInstruction insn) {
super.visit(insn); notNullVariables.set(insn.getReceiver().getIndex());
} }
};
@Override
public void visit(BranchingInstruction insn) {
switch (insn.getCondition()) {
case NOT_NULL:
notNullSuccessors.put(insn.getConsequent().getIndex(), insn.getOperand().getIndex());
nullSuccessors.put(insn.getAlternative().getIndex(), insn.getOperand().getIndex());
break;
case NULL:
nullSuccessors.put(insn.getConsequent().getIndex(), insn.getOperand().getIndex());
notNullSuccessors.put(insn.getAlternative().getIndex(), insn.getOperand().getIndex());
break;
default:
break;
}
}
@Override
public void visit(BinaryBranchingInstruction insn) {
Variable first = insn.getFirstOperand();
Variable second = insn.getSecondOperand();
if (nullVariables.get(first.getIndex())) {
first = second;
} else if (!nullVariables.get(second.getIndex())) {
return;
}
switch (insn.getCondition()) {
case REFERENCE_EQUAL:
notNullSuccessors.put(insn.getConsequent().getIndex(), first.getIndex());
nullSuccessors.put(insn.getAlternative().getIndex(), first.getIndex());
break;
case REFERENCE_NOT_EQUAL:
nullSuccessors.put(insn.getConsequent().getIndex(), first.getIndex());
notNullSuccessors.put(insn.getAlternative().getIndex(), first.getIndex());
break;
default:
break;
}
}
private void insertNotNullInstruction(Instruction currentInstruction, Variable var) {
if (notNullVariables.get(var.getIndex())) {
return;
}
NullCheckInstruction insn = new NullCheckInstruction();
insn.setReceiver(var);
insn.setValue(var);
notNullInstructions.add(insn);
if (currentInstruction != null) {
currentInstruction.insertNext(insn);
} else {
currentBlock.addFirst(insn);
}
markAsNonNull(var);
}
private void insertNullInstruction(Variable var) {
if (nullVariables.get(var.getIndex())) {
return;
}
NullConstantInstruction insn = new NullConstantInstruction();
insn.setReceiver(var);
nullInstructions.add(insn);
currentBlock.addFirst(insn);
markAsNull(var);
}
private void markAsNonNull(Variable var) {
if (notNullVariables.get(var.getIndex())) {
return;
}
notNullVariables.set(var.getIndex());
currentState.newlyNonNull.add(var.getIndex());
}
private void markAsNull(Variable var) {
if (nullVariables.get(var.getIndex())) {
return;
}
nullVariables.set(var.getIndex());
currentState.newlyNull.add(var.getIndex());
}
}
static class State {
IntSet newlyNonNull = new IntOpenHashSet();
IntSet newlyNull = new IntOpenHashSet();
}
} }

View File

@ -16,8 +16,10 @@
package org.teavm.model.text; package org.teavm.model.text;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.teavm.model.*; import org.teavm.model.*;
import org.teavm.model.instructions.*; import org.teavm.model.instructions.*;
@ -25,9 +27,29 @@ import org.teavm.model.instructions.*;
class InstructionStringifier implements InstructionReader { class InstructionStringifier implements InstructionReader {
private TextLocation location; private TextLocation location;
private StringBuilder sb; private StringBuilder sb;
private String[] variableLabels;
InstructionStringifier(StringBuilder sb) { InstructionStringifier(StringBuilder sb, ProgramReader program) {
this.sb = sb; this.sb = sb;
variableLabels = new String[program.variableCount()];
Set<String> occupiedLabels = new HashSet<>();
for (int i = 0; i < program.variableCount(); ++i) {
VariableReader var = program.variableAt(i);
String suggestedName = var.getLabel() != null ? var.getLabel() : Integer.toString(i);
if (!occupiedLabels.add(suggestedName)) {
int suffix = 1;
String base = suggestedName + "_";
do {
suggestedName = base + suffix++;
} while (!occupiedLabels.add(suggestedName));
}
variableLabels[i] = suggestedName;
}
}
public String getVariableLabel(int index) {
return variableLabels[index];
} }
public TextLocation getLocation() { public TextLocation getLocation() {
@ -44,40 +66,58 @@ class InstructionStringifier implements InstructionReader {
sb.append("nop"); sb.append("nop");
} }
InstructionStringifier append(String str) {
sb.append(str);
return this;
}
InstructionStringifier append(int value) {
sb.append(value);
return this;
}
InstructionStringifier append(char value) {
sb.append(value);
return this;
}
InstructionStringifier appendLocalVar(VariableReader var) {
return append("@").append(variableLabels[var.getIndex()]);
}
@Override @Override
public void classConstant(VariableReader receiver, ValueType cst) { public void classConstant(VariableReader receiver, ValueType cst) {
sb.append("@").append(receiver.getIndex()).append(" := classOf "); appendLocalVar(receiver).append(" := classOf ").escapeIdentifierIfNeeded(cst.toString());
escapeIdentifierIfNeeded(cst.toString(), sb);
} }
@Override @Override
public void nullConstant(VariableReader receiver) { public void nullConstant(VariableReader receiver) {
sb.append("@").append(receiver.getIndex()).append(" := null"); appendLocalVar(receiver).append(" := null");
} }
@Override @Override
public void integerConstant(VariableReader receiver, int cst) { public void integerConstant(VariableReader receiver, int cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst); appendLocalVar(receiver).append(" := " + cst);
} }
@Override @Override
public void longConstant(VariableReader receiver, long cst) { public void longConstant(VariableReader receiver, long cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append("L"); appendLocalVar(receiver).append(" := " + cst + "L");
} }
@Override @Override
public void floatConstant(VariableReader receiver, float cst) { public void floatConstant(VariableReader receiver, float cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append('F'); appendLocalVar(receiver).append(" := " + cst + 'F');
} }
@Override @Override
public void doubleConstant(VariableReader receiver, double cst) { public void doubleConstant(VariableReader receiver, double cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst); appendLocalVar(receiver).append(" := " + cst);
} }
@Override @Override
public void stringConstant(VariableReader receiver, String cst) { public void stringConstant(VariableReader receiver, String cst) {
sb.append("@").append(receiver.getIndex()).append(" := '"); appendLocalVar(receiver).append(" := '");
escapeStringLiteral(cst, sb); escapeStringLiteral(cst, sb);
sb.append("'"); sb.append("'");
} }
@ -114,7 +154,7 @@ class InstructionStringifier implements InstructionReader {
} }
} }
private static void escapeIdentifierIfNeeded(String s, StringBuilder sb) { private InstructionStringifier escapeIdentifierIfNeeded(String s) {
boolean needsEscaping = false; boolean needsEscaping = false;
if (s.isEmpty()) { if (s.isEmpty()) {
needsEscaping = true; needsEscaping = true;
@ -133,87 +173,88 @@ class InstructionStringifier implements InstructionReader {
} else { } else {
sb.append(s); sb.append(s);
} }
return this;
} }
@Override @Override
public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second, public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second,
NumericOperandType type) { NumericOperandType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(first.getIndex()).append(" "); appendLocalVar(receiver).append(" := ").appendLocalVar(first).append(" ");
switch (op) { switch (op) {
case ADD: case ADD:
sb.append("+"); append("+");
break; break;
case AND: case AND:
sb.append("&"); append("&");
break; break;
case COMPARE: case COMPARE:
sb.append("compareTo"); append("compareTo");
break; break;
case DIVIDE: case DIVIDE:
sb.append("/"); append("/");
break; break;
case MODULO: case MODULO:
sb.append("%"); append("%");
break; break;
case MULTIPLY: case MULTIPLY:
sb.append("*"); append("*");
break; break;
case OR: case OR:
sb.append("|"); append("|");
break; break;
case SHIFT_LEFT: case SHIFT_LEFT:
sb.append("<<"); append("<<");
break; break;
case SHIFT_RIGHT: case SHIFT_RIGHT:
sb.append(">>"); append(">>");
break; break;
case SHIFT_RIGHT_UNSIGNED: case SHIFT_RIGHT_UNSIGNED:
sb.append(">>>"); append(">>>");
break; break;
case SUBTRACT: case SUBTRACT:
sb.append("-"); append("-");
break; break;
case XOR: case XOR:
sb.append("^"); append("^");
break; break;
} }
sb.append(" @").append(second.getIndex()); append(" ").appendLocalVar(second);
} }
@Override @Override
public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) { public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) {
sb.append("@").append(receiver.getIndex()).append(" := -").append(" @").append(operand.getIndex()); appendLocalVar(receiver).append(" := -").append(" ").appendLocalVar(operand);
} }
@Override @Override
public void assign(VariableReader receiver, VariableReader assignee) { public void assign(VariableReader receiver, VariableReader assignee) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(assignee.getIndex()); appendLocalVar(receiver).append(" := ").appendLocalVar(assignee);
} }
@Override @Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) { public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex()) appendLocalVar(receiver).append(" := cast ").appendLocalVar(value).append(" to ")
.append(" to "); .escapeIdentifierIfNeeded(targetType.toString());
escapeIdentifierIfNeeded(targetType.toString(), sb);
} }
@Override @Override
public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType, public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType,
NumericOperandType targetType) { NumericOperandType targetType) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex()) appendLocalVar(receiver).append(" := cast ").appendLocalVar(value)
.append(" from ").append(sourceType).append(" to ").append(targetType); .append(" from ").append(sourceType.toString()).append(" to ").append(targetType.toString());
} }
@Override @Override
public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type, public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type,
CastIntegerDirection direction) { CastIntegerDirection direction) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex()); appendLocalVar(receiver).append(" := cast ").appendLocalVar(value);
switch (direction) { switch (direction) {
case FROM_INTEGER: case FROM_INTEGER:
sb.append(" from int to ").append(type.name().toLowerCase(Locale.ROOT)); append(" from int to ").append(type.name().toLowerCase(Locale.ROOT));
break; break;
case TO_INTEGER: case TO_INTEGER:
sb.append(" from ").append(type.name().toLowerCase(Locale.ROOT)).append(" to int"); append(" from ").append(type.name().toLowerCase(Locale.ROOT)).append(" to int");
break; break;
} }
} }
@ -221,7 +262,7 @@ class InstructionStringifier implements InstructionReader {
@Override @Override
public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent, public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent,
BasicBlockReader alternative) { BasicBlockReader alternative) {
sb.append("if @").append(operand.getIndex()).append(" "); append("if ").appendLocalVar(operand).append(" ");
switch (cond) { switch (cond) {
case EQUAL: case EQUAL:
sb.append("== 0"); sb.append("== 0");
@ -248,174 +289,164 @@ class InstructionStringifier implements InstructionReader {
sb.append("=== null"); sb.append("=== null");
break; break;
} }
sb.append(" then goto $").append(consequent.getIndex()).append(" else goto $").append(alternative.getIndex()); append(" then goto $").append(consequent.getIndex()).append(" else goto $").append(alternative.getIndex());
} }
@Override @Override
public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second, public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second,
BasicBlockReader consequent, BasicBlockReader alternative) { BasicBlockReader consequent, BasicBlockReader alternative) {
sb.append("if @").append(first.getIndex()).append(" "); append("if ").appendLocalVar(first).append(" ");
switch (cond) { switch (cond) {
case EQUAL: case EQUAL:
sb.append("=="); append("==");
break; break;
case REFERENCE_EQUAL: case REFERENCE_EQUAL:
sb.append("==="); append("===");
break; break;
case NOT_EQUAL: case NOT_EQUAL:
sb.append("!="); append("!=");
break; break;
case REFERENCE_NOT_EQUAL: case REFERENCE_NOT_EQUAL:
sb.append("!=="); append("!==");
break; break;
} }
sb.append("@").append(second.getIndex()).append(" then goto $").append(consequent.getIndex()) appendLocalVar(second).append(" then goto $").append(consequent.getIndex())
.append(" else goto $").append(alternative.getIndex()); .append(" else goto $").append(alternative.getIndex());
} }
@Override @Override
public void jump(BasicBlockReader target) { public void jump(BasicBlockReader target) {
sb.append("goto $").append(target.getIndex()); append("goto $").append(target.getIndex());
} }
@Override @Override
public void choose(VariableReader condition, List<? extends SwitchTableEntryReader> table, public void choose(VariableReader condition, List<? extends SwitchTableEntryReader> table,
BasicBlockReader defaultTarget) { BasicBlockReader defaultTarget) {
sb.append("switch @").append(condition.getIndex()).append(" "); append("switch ").appendLocalVar(condition).append(" ");
for (int i = 0; i < table.size(); ++i) { for (int i = 0; i < table.size(); ++i) {
if (i > 0) { if (i > 0) {
sb.append(" "); append(" ");
} }
SwitchTableEntryReader entry = table.get(i); SwitchTableEntryReader entry = table.get(i);
sb.append("case ").append(entry.getCondition()).append(" goto $").append(entry.getTarget().getIndex()); append("case ").append(entry.getCondition()).append(" goto $").append(entry.getTarget().getIndex());
} }
sb.append(" else goto $").append(defaultTarget.getIndex()); sb.append(" else goto $").append(defaultTarget.getIndex());
} }
@Override @Override
public void exit(VariableReader valueToReturn) { public void exit(VariableReader valueToReturn) {
sb.append("return"); append("return");
if (valueToReturn != null) { if (valueToReturn != null) {
sb.append(" @").append(valueToReturn.getIndex()); append(" ").appendLocalVar(valueToReturn);
} }
} }
@Override @Override
public void raise(VariableReader exception) { public void raise(VariableReader exception) {
sb.append("throw @").append(exception.getIndex()); append("throw ").appendLocalVar(exception);
} }
@Override @Override
public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
sb.append("@").append(receiver.getIndex()).append(" := new "); appendLocalVar(receiver).append(" := new ").escapeIdentifierIfNeeded(itemType.toString())
escapeIdentifierIfNeeded(itemType.toString(), sb); .append("[").appendLocalVar(size).append(']');
sb.append("[@").append(size.getIndex()).append(']');
} }
@Override @Override
public void createArray(VariableReader receiver, ValueType itemType, List<? extends VariableReader> dimensions) { public void createArray(VariableReader receiver, ValueType itemType, List<? extends VariableReader> dimensions) {
sb.append("@").append(receiver.getIndex()).append(" := newArray "); appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(itemType.toString());
escapeIdentifierIfNeeded(itemType.toString(), sb); append("[");
sb.append("[");
for (int i = 0; i < dimensions.size(); ++i) { for (int i = 0; i < dimensions.size(); ++i) {
if (i > 0) { if (i > 0) {
sb.append(", "); append(", ");
} }
sb.append("@").append(dimensions.get(i).getIndex()); appendLocalVar(dimensions.get(i));
} }
sb.append("]"); append("]");
} }
@Override @Override
public void create(VariableReader receiver, String type) { public void create(VariableReader receiver, String type) {
sb.append("@").append(receiver.getIndex()).append(" := newArray "); appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(type);
escapeIdentifierIfNeeded(type, sb);
} }
@Override @Override
public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) { public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) {
sb.append("@").append(receiver.getIndex()).append(" := field "); appendLocalVar(receiver).append(" := field ").escapeIdentifierIfNeeded(field.toString());
escapeIdentifierIfNeeded(field.toString(), sb);
if (instance != null) { if (instance != null) {
sb.append(" @").append(instance.getIndex()); append(" ").appendLocalVar(instance);
} }
sb.append(" as "); append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
escapeIdentifierIfNeeded(fieldType.toString(), sb);
} }
@Override @Override
public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) { public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) {
sb.append("field "); append("field ").escapeIdentifierIfNeeded(field.toString());
escapeIdentifierIfNeeded(field.toString(), sb);
if (instance != null) { if (instance != null) {
sb.append(" @").append(instance.getIndex()); append(" ").appendLocalVar(instance);
} }
sb.append(" := @").append(value.getIndex()); append(" := ").appendLocalVar(value).append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
sb.append(" as ");
escapeIdentifierIfNeeded(fieldType.toString(), sb);
} }
@Override @Override
public void arrayLength(VariableReader receiver, VariableReader array) { public void arrayLength(VariableReader receiver, VariableReader array) {
sb.append("@").append(receiver.getIndex()).append(" := lengthOf @").append(array.getIndex()); appendLocalVar(receiver).append(" := lengthOf ").appendLocalVar(array);
} }
@Override @Override
public void cloneArray(VariableReader receiver, VariableReader array) { public void cloneArray(VariableReader receiver, VariableReader array) {
sb.append("@").append(receiver.getIndex()).append(" := clone @").append(array.getIndex()); appendLocalVar(receiver).append(" := clone ").appendLocalVar(array);
} }
@Override @Override
public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) {
sb.append("@").append(receiver.getIndex()).append(" := data @").append(array.getIndex()).append(" as ") appendLocalVar(receiver).append(" := data ").appendLocalVar(array).append(" as ")
.append(elementType.name().toLowerCase(Locale.ROOT)); .append(elementType.name().toLowerCase(Locale.ROOT));
} }
@Override @Override
public void getElement(VariableReader receiver, VariableReader array, VariableReader index, public void getElement(VariableReader receiver, VariableReader array, VariableReader index,
ArrayElementType type) { ArrayElementType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(array.getIndex()).append("[@") appendLocalVar(receiver).append(" := ").appendLocalVar(array).append("[").appendLocalVar(index).append("]")
.append(index.getIndex()).append("]").append(" as " + type.name().toLowerCase(Locale.ROOT)); .append(" as " + type.name().toLowerCase(Locale.ROOT));
} }
@Override @Override
public void putElement(VariableReader array, VariableReader index, VariableReader value, ArrayElementType type) { public void putElement(VariableReader array, VariableReader index, VariableReader value, ArrayElementType type) {
sb.append("@").append(array.getIndex()).append("[@").append(index.getIndex()).append("] := @") appendLocalVar(array).append("[").appendLocalVar(index).append("] := ").appendLocalVar(value)
.append(value.getIndex()).append(" as " + type.name().toLowerCase()); .append(" as " + type.name().toLowerCase(Locale.ROOT));
} }
@Override @Override
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments, InvocationType type) { List<? extends VariableReader> arguments, InvocationType type) {
if (receiver != null) { if (receiver != null) {
sb.append("@").append(receiver.getIndex()).append(" := "); appendLocalVar(receiver).append(" := ");
} }
if (instance == null) { if (instance == null) {
sb.append("invokeStatic "); append("invokeStatic ");
} else { } else {
switch (type) { switch (type) {
case SPECIAL: case SPECIAL:
sb.append("invoke "); append("invoke ");
break; break;
case VIRTUAL: case VIRTUAL:
sb.append("invokeVirtual "); append("invokeVirtual ");
break; break;
} }
} }
escapeIdentifierIfNeeded(method.toString(), sb); escapeIdentifierIfNeeded(method.toString());
if (instance != null) { if (instance != null) {
sb.append(' '); append(' ').appendLocalVar(instance);
sb.append("@").append(instance.getIndex());
} }
for (int i = 0; i < arguments.size(); ++i) { for (int i = 0; i < arguments.size(); ++i) {
if (instance != null || i > 0) { if (instance != null || i > 0) {
sb.append(","); append(",");
} }
sb.append(' '); append(' ').appendLocalVar(arguments.get(i));
sb.append("@").append(arguments.get(i).getIndex());
} }
} }
@ -424,17 +455,17 @@ class InstructionStringifier implements InstructionReader {
List<? extends VariableReader> arguments, MethodHandle bootstrapMethod, List<? extends VariableReader> arguments, MethodHandle bootstrapMethod,
List<RuntimeConstant> bootstrapArguments) { List<RuntimeConstant> bootstrapArguments) {
if (receiver != null) { if (receiver != null) {
sb.append("@").append(receiver.getIndex()).append(" := "); appendLocalVar(receiver).append(" := ");
} }
if (instance != null) { if (instance != null) {
sb.append("@").append(instance.getIndex()).append("."); appendLocalVar(instance).append(".");
} }
sb.append(method.getName()).append("("); append(method.getName()).append("(");
sb.append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", "))); append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", ")));
sb.append(") "); append(") ");
sb.append("[").append(convert(bootstrapMethod)).append('('); append("[").append(convert(bootstrapMethod)).append('(');
sb.append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", "))); append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", ")));
sb.append(")"); append(")");
} }
private String convert(MethodHandle handle) { private String convert(MethodHandle handle) {
@ -488,28 +519,27 @@ class InstructionStringifier implements InstructionReader {
@Override @Override
public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { public void isInstance(VariableReader receiver, VariableReader value, ValueType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(value.getIndex()) appendLocalVar(receiver).append(" := ").appendLocalVar(value).append(" instanceOf ")
.append(" instanceOf "); .escapeIdentifierIfNeeded(type.toString());
escapeIdentifierIfNeeded(type.toString(), sb);
} }
@Override @Override
public void initClass(String className) { public void initClass(String className) {
sb.append("initClass ").append(className); append("initClass ").append(className);
} }
@Override @Override
public void nullCheck(VariableReader receiver, VariableReader value) { public void nullCheck(VariableReader receiver, VariableReader value) {
sb.append("@").append(receiver.getIndex()).append(" := nullCheck @").append(value.getIndex()); appendLocalVar(receiver).append(" := nullCheck ").appendLocalVar(value);
} }
@Override @Override
public void monitorEnter(VariableReader objectRef) { public void monitorEnter(VariableReader objectRef) {
sb.append("monitorEnter @").append(objectRef.getIndex()); append("monitorEnter ").appendLocalVar(objectRef);
} }
@Override @Override
public void monitorExit(VariableReader objectRef) { public void monitorExit(VariableReader objectRef) {
sb.append("monitorExit @").append(objectRef.getIndex()); append("monitorExit ").appendLocalVar(objectRef);
} }
} }

View File

@ -24,13 +24,13 @@ public class ListingBuilder {
public String buildListing(ProgramReader program, String prefix) { public String buildListing(ProgramReader program, String prefix) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
StringBuilder insnSb = new StringBuilder(); StringBuilder insnSb = new StringBuilder();
InstructionStringifier stringifier = new InstructionStringifier(insnSb); InstructionStringifier stringifier = new InstructionStringifier(insnSb, program);
for (int i = 0; i < program.variableCount(); ++i) { for (int i = 0; i < program.variableCount(); ++i) {
VariableReader var = program.variableAt(i); VariableReader var = program.variableAt(i);
if (var == null || var.getDebugName() == null) { if (var == null || var.getDebugName() == null) {
continue; continue;
} }
sb.append(prefix).append("var @").append(i); sb.append(prefix).append("var @").append(stringifier.getVariableLabel(i));
sb.append('\n'); sb.append('\n');
} }
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
@ -41,20 +41,21 @@ public class ListingBuilder {
} }
if (block.getExceptionVariable() != null) { if (block.getExceptionVariable() != null) {
sb.append(" @").append(block.getExceptionVariable().getIndex()).append(" = exception\n"); sb.append(" ").append(stringifier.getVariableLabel(block.getExceptionVariable().getIndex()))
.append(" = exception\n");
} }
for (PhiReader phi : block.readPhis()) { for (PhiReader phi : block.readPhis()) {
sb.append(prefix).append(" "); sb.append(prefix).append(" ");
sb.append("@").append(phi.getReceiver().getIndex()).append(" := phi "); sb.append("@").append(stringifier.getVariableLabel(phi.getReceiver().getIndex())).append(" := phi ");
List<? extends IncomingReader> incomings = phi.readIncomings(); List<? extends IncomingReader> incomings = phi.readIncomings();
for (int j = 0; j < incomings.size(); ++j) { for (int j = 0; j < incomings.size(); ++j) {
if (j > 0) { if (j > 0) {
sb.append(", "); sb.append(", ");
} }
IncomingReader incoming = incomings.get(j); IncomingReader incoming = incomings.get(j);
sb.append("@").append(incoming.getValue().getIndex()).append(" from ") sb.append("@").append(stringifier.getVariableLabel(incoming.getValue().getIndex()))
.append("$").append(incoming.getSource().getIndex()); .append(" from ").append("$").append(incoming.getSource().getIndex());
} }
sb.append("\n"); sb.append("\n");
} }
@ -86,8 +87,10 @@ public class ListingBuilder {
sb.append(" goto $").append(tryCatch.getHandler().getIndex()); sb.append(" goto $").append(tryCatch.getHandler().getIndex());
sb.append("\n"); sb.append("\n");
for (TryCatchJointReader joint : tryCatch.readJoints()) { for (TryCatchJointReader joint : tryCatch.readJoints()) {
sb.append(" @").append(joint.getReceiver().getIndex()).append(" := ephi "); sb.append(" @").append(stringifier.getVariableLabel(joint.getReceiver().getIndex()))
sb.append(joint.readSourceVariables().stream().map(sourceVar -> "@" + sourceVar.getIndex()) .append(" := ephi ");
sb.append(joint.readSourceVariables().stream()
.map(sourceVar -> "@" + stringifier.getVariableLabel(sourceVar.getIndex()))
.collect(Collectors.joining(", "))); .collect(Collectors.joining(", ")));
sb.append("\n"); sb.append("\n");
} }

View File

@ -72,6 +72,7 @@ import org.teavm.model.instructions.MonitorEnterInstruction;
import org.teavm.model.instructions.MonitorExitInstruction; import org.teavm.model.instructions.MonitorExitInstruction;
import org.teavm.model.instructions.NegateInstruction; import org.teavm.model.instructions.NegateInstruction;
import org.teavm.model.instructions.NullCheckInstruction; import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.NumericOperandType; import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.PutElementInstruction; import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.PutFieldInstruction; import org.teavm.model.instructions.PutFieldInstruction;
@ -349,6 +350,13 @@ public class ListingParser {
case IDENTIFIER: { case IDENTIFIER: {
String keyword = (String) lexer.getTokenValue(); String keyword = (String) lexer.getTokenValue();
switch (keyword) { switch (keyword) {
case "null": {
lexer.nextToken();
NullConstantInstruction insn = new NullConstantInstruction();
insn.setReceiver(receiver);
addInstruction(insn);
break;
}
case "phi": case "phi":
lexer.nextToken(); lexer.nextToken();
parsePhi(receiver); parsePhi(receiver);

View File

@ -0,0 +1,63 @@
/*
* Copyright 2016 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.util;
import org.teavm.common.DominatorTree;
import org.teavm.common.Graph;
import org.teavm.common.GraphUtils;
import org.teavm.model.BasicBlock;
import org.teavm.model.Program;
public class DominatorWalker {
private Program program;
private Graph domGraph;
public DominatorWalker(Program program) {
this.program = program;
Graph cfg = ProgramUtils.buildControlFlowGraph(program);
DominatorTree dom = GraphUtils.buildDominatorTree(cfg);
domGraph = GraphUtils.buildDominatorGraph(dom, cfg.size());
}
public <T> void walk(DominatorWalkerCallback<T> callback) {
int[] stack = new int[program.basicBlockCount() * 2];
Object[] stateStack = new Object[stack.length];
boolean[] backward = new boolean[stack.length];
int head = 1;
while (head > 0) {
int node = stack[--head];
BasicBlock block = program.basicBlockAt(node);
if (backward[head]) {
@SuppressWarnings("unchecked")
T state = (T) stateStack[head];
callback.endVisit(block, state);
} else if (callback.filter(block)) {
stack[head] = node;
backward[head] = true;
stateStack[head] = callback.visit(block);
head++;
int[] successors = domGraph.outgoingEdges(node);
for (int i = successors.length - 1; i >= 0; --i) {
stack[head] = successors[i];
backward[head] = false;
head++;
}
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2016 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.util;
import org.teavm.model.BasicBlock;
/**
* An object that receives information from {@link DominatorWalker}
*
* @param <T> type of state that can be saved for each visited node.
*/
public interface DominatorWalkerCallback<T> {
/**
* Called before visiting block. This method should tell whether this block and all of its descendant blocks
* should be visited.
*/
default boolean filter(BasicBlock block) {
return true;
}
/**
* Visits block and returns visit state. This state is saved by walker and then passed to
* {@link #endVisit(BasicBlock, Object)} after all of this block's descendants visited.
*
* @return state that further will be passed to {@link #endVisit(BasicBlock, Object)}
*/
T visit(BasicBlock block);
default void endVisit(BasicBlock block, T state) {
}
}

View File

@ -105,7 +105,7 @@ public class PhiUpdater {
return variableToSourceMap.get(var); return variableToSourceMap.get(var);
} }
public List<Phi> getSynthesizedPhisBy() { public List<Phi> getSynthesizedPhis() {
return synthesizedPhis; return synthesizedPhis;
} }
@ -232,6 +232,7 @@ public class PhiUpdater {
for (Phi phi : synthesizedPhisByBlock.get(index)) { for (Phi phi : synthesizedPhisByBlock.get(index)) {
Variable var = program.createVariable(); Variable var = program.createVariable();
var.setDebugName(phi.getReceiver().getDebugName()); var.setDebugName(phi.getReceiver().getDebugName());
var.setLabel(phi.getReceiver().getLabel());
mapVariable(phi.getReceiver().getIndex(), var); mapVariable(phi.getReceiver().getIndex(), var);
phisByReceiver.put(var.getIndex(), phi); phisByReceiver.put(var.getIndex(), phi);
phi.setReceiver(var); phi.setReceiver(var);
@ -269,6 +270,7 @@ public class PhiUpdater {
for (TryCatchJoint joint : synthesizedJointsByBlock.get(index).get(i)) { for (TryCatchJoint joint : synthesizedJointsByBlock.get(index).get(i)) {
Variable var = program.createVariable(); Variable var = program.createVariable();
var.setDebugName(joint.getReceiver().getDebugName()); var.setDebugName(joint.getReceiver().getDebugName());
var.setLabel(joint.getReceiver().getLabel());
mapVariable(joint.getReceiver().getIndex(), var); mapVariable(joint.getReceiver().getIndex(), var);
joint.setReceiver(var); joint.setReceiver(var);
jointsByReceiver.put(var.getIndex(), joint); jointsByReceiver.put(var.getIndex(), joint);
@ -277,7 +279,8 @@ public class PhiUpdater {
variableMap = regularVariableMap; variableMap = regularVariableMap;
int[] successors = domGraph.outgoingEdges(index); int[] successors = domGraph.outgoingEdges(index);
for (int successor : successors) { for (int j = successors.length - 1; j >= 0; --j) {
int successor = successors[j];
Task next = new Task(); Task next = new Task();
next.variables = (catchSuccessors.contains(successor) ? catchVariableMap : variableMap).clone(); next.variables = (catchSuccessors.contains(successor) ? catchVariableMap : variableMap).clone();
next.block = program.basicBlockAt(successor); next.block = program.basicBlockAt(successor);
@ -473,7 +476,10 @@ public class PhiUpdater {
if (!usedDefinitions[var.getIndex()]) { if (!usedDefinitions[var.getIndex()]) {
usedDefinitions[var.getIndex()] = true; usedDefinitions[var.getIndex()] = true;
} else { } else {
Variable old = var;
var = program.createVariable(); var = program.createVariable();
var.setDebugName(old.getDebugName());
var.setLabel(old.getLabel());
} }
return var; return var;

View File

@ -16,8 +16,10 @@
package org.teavm.model.util; package org.teavm.model.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.teavm.common.Graph; import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder; import org.teavm.common.GraphBuilder;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
@ -219,4 +221,24 @@ public final class ProgramUtils {
} }
return places; return places;
} }
public static void makeUniqueLabels(Program program) {
Set<String> occupiedLabels = new HashSet<>();
for (int i = 0; i < program.variableCount(); ++i) {
Variable var = program.variableAt(i);
if (var.getLabel() == null) {
continue;
}
String suggestedName = var.getLabel();
if (!occupiedLabels.add(suggestedName)) {
int suffix = 1;
String base = suggestedName + "_";
do {
suggestedName = base + suffix++;
} while (!occupiedLabels.add(suggestedName));
}
var.setLabel(suggestedName);
}
}
} }

View File

@ -0,0 +1,155 @@
/*
* Copyright 2016 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.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import com.carrotsearch.hppc.ObjectByteMap;
import com.carrotsearch.hppc.ObjectByteOpenHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.analysis.NullnessInformation;
import org.teavm.model.text.ListingBuilder;
import org.teavm.model.text.ListingParseException;
import org.teavm.model.text.ListingParser;
import org.teavm.model.util.ProgramUtils;
public class NullnessAnalysisTest {
@Rule
public TestName name = new TestName();
private static final String NOT_NULL_DIRECTIVE = "// NOT_NULL ";
private static final String NULLABLE_DIRECTIVE = "// NULLABLE ";
@Test
public void simple() {
test();
}
@Test
public void phiJoin() {
test();
}
@Test
public void branch() {
test();
}
private void test() {
String baseName = "model/analysis/" + name.getMethodName();
String originalResourceName = baseName + ".original.txt";
String extendedResourceName = baseName + ".extended.txt";
Program originalProgram = parseResource(originalResourceName);
Program extendedProgram = parseResource(extendedResourceName);
ListingBuilder listingBuilder = new ListingBuilder();
String listingBeforeExtension = listingBuilder.buildListing(originalProgram, "");
NullnessInformation information = NullnessInformation.build(originalProgram,
new MethodDescriptor("foo", ValueType.VOID));
ProgramUtils.makeUniqueLabels(originalProgram);
String actualListing = listingBuilder.buildListing(originalProgram, "");
String expectedListing = listingBuilder.buildListing(extendedProgram, "");
assertEquals(expectedListing, actualListing);
ObjectByteMap<String> expectedNullness = extractExpectedNullness(extendedResourceName);
Map<String, Variable> variablesByLabel = variablesByLabel(originalProgram);
for (ObjectCursor<String> varNameCursor : expectedNullness.keys()) {
String varName = varNameCursor.value;
Variable var = variablesByLabel.get(varName);
assertNotNull("Variable " + varName + " is missing", var);
boolean notNull = expectedNullness.get(varName) != 0;
assertEquals("Variable " + varName + " non-null", notNull, information.isNotNull(var));
}
information.dispose();
String listingAfterDispose = listingBuilder.buildListing(originalProgram, "");
assertEquals(listingBeforeExtension, listingAfterDispose);
}
private Program parseResource(String name) {
ClassLoader classLoader = NullnessAnalysisTest.class.getClassLoader();
try (InputStream input = classLoader.getResourceAsStream(name);
Reader reader = new InputStreamReader(input, "UTF-8")) {
return new ListingParser().parse(reader);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ListingParseException e) {
throw new RuntimeException("at " + e.getIndex() + "", e);
}
}
private ObjectByteMap<String> extractExpectedNullness(String name) {
ClassLoader classLoader = NullnessAnalysisTest.class.getClassLoader();
try (InputStream input = classLoader.getResourceAsStream(name);
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
ObjectByteMap<String> result = new ObjectByteOpenHashMap<>();
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
int index = line.indexOf(NOT_NULL_DIRECTIVE);
if (index >= 0) {
String variable = line.substring(index + NOT_NULL_DIRECTIVE.length()).trim();
result.put(variable, (byte) 1);
}
index = line.indexOf(NULLABLE_DIRECTIVE);
if (index >= 0) {
String variable = line.substring(index + NULLABLE_DIRECTIVE.length()).trim();
result.put(variable, (byte) 0);
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Map<String, Variable> variablesByLabel(Program program) {
Map<String, Variable> result = new HashMap<>();
for (int i = 0; i < program.variableCount(); ++i) {
Variable var = program.variableAt(i);
if (var.getLabel() != null) {
result.put(var.getLabel(), var);
}
}
return result;
}
}

View File

@ -0,0 +1,21 @@
var @this as this
$start
@v := invokeStatic `Foo.get()LFoo;`
if @v === null then goto $ifNull else goto $ifNotNull
$ifNull
@v_1 := null
invokeVirtual `Foo.bar()V` @v_1
@v_2 := nullCheck @v_1
goto $join
$ifNotNull
@v_3 := nullCheck @v
invokeVirtual `Foo.baz()V` @v_3
goto $join
$join
return
// NULLABLE v
// NULLABLE v_1
// NOT_NULL v_2
// NOT_NULL v_3

View File

@ -0,0 +1,13 @@
var @this as this
$start
@v := invokeStatic `Foo.get()LFoo;`
if @v === null then goto $ifNull else goto $ifNotNull
$ifNull
invokeVirtual `Foo.bar()V` @v
goto $join
$ifNotNull
invokeVirtual `Foo.baz()V` @v
goto $join
$join
return

View File

@ -0,0 +1,21 @@
$start
if @cond === null then goto $ifNull else goto $ifNotNull
$ifNull
@cond_1 := null
@a := 'qwe'
goto $join
$ifNotNull
@cond_2 := nullCheck @cond
@b := invokeStatic `org.test.Foo.bar()Ljava/lang/String;`
@b_1 := nullCheck @b
goto $join
$join
@c := phi @a from $ifNull, @b from $ifNotNull
@d := phi @a from $ifNull, @b_1 from $ifNotNull
return @c
// NULLABLE: b
// NOT_NULL: b_1
// NOT_NULL: a
// NULLABLE: c
// NOT_NULL: d

View File

@ -0,0 +1,13 @@
$start
if @cond === null then goto $ifNull else goto $ifNotNull
$ifNull
@a := 'qwe'
goto $join
$ifNotNull
@b := invokeStatic `org.test.Foo.bar()Ljava/lang/String;`
@b_1 := nullCheck @b
goto $join
$join
@c := phi @a from $ifNull, @b from $ifNotNull
@d := phi @a from $ifNull, @b_1 from $ifNotNull
return @c

View File

@ -0,0 +1,10 @@
var @this as this
$0
@a := invokeStatic `Foo.bar()Ljava/lang/Object;`
invokeVirtual `java.lang.Object.toString()Ljava/lang/String;` @a
@a_1 := nullCheck @a
return @a_1
// NULLABLE a
// NOT_NULL a_1

View File

@ -0,0 +1,6 @@
var @this as this
$0
@a := invokeStatic `Foo.bar()Ljava/lang/Object;`
invokeVirtual `java.lang.Object.toString()Ljava/lang/String;` @a
return @a