mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2025-01-08 16:04:10 -08:00
Add nullness analysis
This commit is contained in:
parent
ae5e1e4962
commit
9dc4b47253
|
@ -62,6 +62,7 @@ public class Variable implements VariableReader {
|
|||
this.debugName = debugName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
|
|
@ -22,5 +22,7 @@ public interface VariableReader {
|
|||
|
||||
String getDebugName();
|
||||
|
||||
String getLabel();
|
||||
|
||||
int getRegister();
|
||||
}
|
||||
|
|
|
@ -15,17 +15,75 @@
|
|||
*/
|
||||
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.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 {
|
||||
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() {
|
||||
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) {
|
||||
NullnessInformation instance = new NullnessInformation();
|
||||
return instance;
|
||||
public static NullnessInformation build(Program program, MethodDescriptor methodDescriptor) {
|
||||
NullnessInformationBuilder builder = new NullnessInformationBuilder(program, methodDescriptor);
|
||||
builder.build();
|
||||
return new NullnessInformation(program, builder.synthesizedVariables, builder.phiUpdater,
|
||||
builder.notNullVariables, builder.nullVariables);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,18 @@
|
|||
*/
|
||||
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.BitSet;
|
||||
import java.util.List;
|
||||
import org.teavm.common.Graph;
|
||||
import org.teavm.common.GraphBuilder;
|
||||
import org.teavm.common.GraphUtils;
|
||||
import org.teavm.model.BasicBlock;
|
||||
import org.teavm.model.Incoming;
|
||||
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.ArrayLengthInstruction;
|
||||
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.ClassConstantInstruction;
|
||||
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.ConstructMultiArrayInstruction;
|
||||
import org.teavm.model.instructions.GetFieldInstruction;
|
||||
import org.teavm.model.instructions.InstructionVisitor;
|
||||
import org.teavm.model.instructions.InvokeInstruction;
|
||||
import org.teavm.model.instructions.MonitorEnterInstruction;
|
||||
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.StringConstantInstruction;
|
||||
import org.teavm.model.instructions.UnwrapArrayInstruction;
|
||||
import org.teavm.model.util.DominatorWalker;
|
||||
import org.teavm.model.util.DominatorWalkerCallback;
|
||||
import org.teavm.model.util.PhiUpdater;
|
||||
|
||||
class NullnessInformationBuilder {
|
||||
private Program program;
|
||||
private MethodDescriptor methodDescriptor;
|
||||
private BitSet notNullVariables = new BitSet();
|
||||
private BitSet nullVariables = new BitSet();
|
||||
private BitSet synthesizedVariables = new BitSet();
|
||||
private PhiUpdater phiUpdater;
|
||||
BitSet notNullVariables = new BitSet();
|
||||
BitSet nullVariables = new BitSet();
|
||||
BitSet synthesizedVariables = new BitSet();
|
||||
PhiUpdater phiUpdater;
|
||||
private List<NullConstantInstruction> nullInstructions = new ArrayList<>();
|
||||
private List<NullCheckInstruction> notNullInstructions = new ArrayList<>();
|
||||
private Graph assignmentGraph;
|
||||
private int[] nullPredecessorsLeft;
|
||||
private int[] notNullPredecessorsLeft;
|
||||
private int[] sccIndexes;
|
||||
|
||||
NullnessInformationBuilder(Program program, MethodDescriptor methodDescriptor) {
|
||||
this.program = program;
|
||||
|
@ -72,12 +80,13 @@ class NullnessInformationBuilder {
|
|||
void build() {
|
||||
extendProgram();
|
||||
buildAssignmentGraph();
|
||||
findKnownNullness();
|
||||
propagateNullness();
|
||||
}
|
||||
|
||||
private void extendProgram() {
|
||||
insertAdditionalVariables();
|
||||
|
||||
notNullVariables.set(0);
|
||||
Variable[] parameters = new Variable[methodDescriptor.parameterCount() + 1];
|
||||
for (int i = 0; i < parameters.length; ++i) {
|
||||
parameters[i] = program.variableAt(i);
|
||||
|
@ -89,55 +98,28 @@ class NullnessInformationBuilder {
|
|||
}
|
||||
|
||||
private void insertAdditionalVariables() {
|
||||
DominatorWalker walker = new DominatorWalker(program);
|
||||
NullExtensionVisitor ev = new NullExtensionVisitor();
|
||||
for (BasicBlock block : program.getBasicBlocks()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
walker.walk(ev);
|
||||
}
|
||||
|
||||
private void collectAdditionalVariables() {
|
||||
for (NullConstantInstruction nullInstruction : nullInstructions) {
|
||||
nullVariables.set(nullInstruction.getReceiver().getIndex());
|
||||
synthesizedVariables.set(nullInstruction.getReceiver().getIndex());
|
||||
}
|
||||
for (NullCheckInstruction notNullInstruction : notNullInstructions) {
|
||||
notNullVariables.set(notNullInstruction.getReceiver().getIndex());
|
||||
synthesizedVariables.set(notNullInstruction.getReceiver().getIndex());
|
||||
}
|
||||
synthesizedVariables.or(nullVariables);
|
||||
synthesizedVariables.or(notNullVariables);
|
||||
|
||||
nullInstructions.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() {
|
||||
GraphBuilder builder = new GraphBuilder();
|
||||
GraphBuilder builder = new GraphBuilder(program.variableCount());
|
||||
|
||||
for (BasicBlock block : program.getBasicBlocks()) {
|
||||
for (Phi phi : block.getPhis()) {
|
||||
for (Incoming incoming : phi.getIncomings()) {
|
||||
|
@ -161,25 +143,93 @@ class NullnessInformationBuilder {
|
|||
}
|
||||
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()];
|
||||
for (int i = 0; i < assignmentGraph.size(); ++i) {
|
||||
nullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
|
||||
notNullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void findKnownNullness() {
|
||||
for (BasicBlock block : program.getBasicBlocks()) {
|
||||
for (Instruction instruction : block) {
|
||||
instruction.acceptVisitor(nullnessVisitor);
|
||||
if (sccIndexes[i] > 0) {
|
||||
for (int predecessor : assignmentGraph.outgoingEdges(i)) {
|
||||
if (sccIndexes[predecessor] == sccIndexes[i]) {
|
||||
notNullPredecessorsLeft[i]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
public void visit(GetFieldInstruction insn) {
|
||||
if (insn.getInstance() != null) {
|
||||
|
@ -226,16 +276,11 @@ class NullnessInformationBuilder {
|
|||
insertNotNullInstruction(insn, insn.getObjectRef());
|
||||
}
|
||||
|
||||
private void insertNotNullInstruction(Instruction currentInstruction, Variable var) {
|
||||
NullCheckInstruction insn = new NullCheckInstruction();
|
||||
insn.setReceiver(var);
|
||||
insn.setValue(var);
|
||||
notNullInstructions.add(insn);
|
||||
currentInstruction.insertNext(insn);
|
||||
@Override
|
||||
public void visit(StringConstantInstruction insn) {
|
||||
notNullVariables.set(insn.getReceiver().getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
private InstructionVisitor nullnessVisitor = new AbstractInstructionVisitor() {
|
||||
@Override
|
||||
public void visit(ClassConstantInstruction insn) {
|
||||
notNullVariables.set(insn.getReceiver().getIndex());
|
||||
|
@ -247,8 +292,9 @@ class NullnessInformationBuilder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void visit(StringConstantInstruction insn) {
|
||||
notNullVariables.set(insn.getReceiver().getIndex());
|
||||
public void visit(AssignInstruction insn) {
|
||||
notNullVariables.set(insn.getReceiver().getIndex(), notNullVariables.get(insn.getAssignee().getIndex()));
|
||||
nullVariables.set(insn.getReceiver().getIndex(), nullVariables.get(insn.getAssignee().getIndex()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -268,7 +314,95 @@ class NullnessInformationBuilder {
|
|||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
package org.teavm.model.text;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.teavm.model.*;
|
||||
import org.teavm.model.instructions.*;
|
||||
|
@ -25,9 +27,29 @@ import org.teavm.model.instructions.*;
|
|||
class InstructionStringifier implements InstructionReader {
|
||||
private TextLocation location;
|
||||
private StringBuilder sb;
|
||||
private String[] variableLabels;
|
||||
|
||||
InstructionStringifier(StringBuilder sb) {
|
||||
InstructionStringifier(StringBuilder sb, ProgramReader program) {
|
||||
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() {
|
||||
|
@ -44,40 +66,58 @@ class InstructionStringifier implements InstructionReader {
|
|||
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
|
||||
public void classConstant(VariableReader receiver, ValueType cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := classOf ");
|
||||
escapeIdentifierIfNeeded(cst.toString(), sb);
|
||||
appendLocalVar(receiver).append(" := classOf ").escapeIdentifierIfNeeded(cst.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nullConstant(VariableReader receiver) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := null");
|
||||
appendLocalVar(receiver).append(" := null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void integerConstant(VariableReader receiver, int cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst);
|
||||
appendLocalVar(receiver).append(" := " + cst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void longConstant(VariableReader receiver, long cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append("L");
|
||||
appendLocalVar(receiver).append(" := " + cst + "L");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void floatConstant(VariableReader receiver, float cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append('F');
|
||||
appendLocalVar(receiver).append(" := " + cst + 'F');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doubleConstant(VariableReader receiver, double cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst);
|
||||
appendLocalVar(receiver).append(" := " + cst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stringConstant(VariableReader receiver, String cst) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := '");
|
||||
appendLocalVar(receiver).append(" := '");
|
||||
escapeStringLiteral(cst, sb);
|
||||
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;
|
||||
if (s.isEmpty()) {
|
||||
needsEscaping = true;
|
||||
|
@ -133,87 +173,88 @@ class InstructionStringifier implements InstructionReader {
|
|||
} else {
|
||||
sb.append(s);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second,
|
||||
NumericOperandType type) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := @").append(first.getIndex()).append(" ");
|
||||
appendLocalVar(receiver).append(" := ").appendLocalVar(first).append(" ");
|
||||
switch (op) {
|
||||
case ADD:
|
||||
sb.append("+");
|
||||
append("+");
|
||||
break;
|
||||
case AND:
|
||||
sb.append("&");
|
||||
append("&");
|
||||
break;
|
||||
case COMPARE:
|
||||
sb.append("compareTo");
|
||||
append("compareTo");
|
||||
break;
|
||||
case DIVIDE:
|
||||
sb.append("/");
|
||||
append("/");
|
||||
break;
|
||||
case MODULO:
|
||||
sb.append("%");
|
||||
append("%");
|
||||
break;
|
||||
case MULTIPLY:
|
||||
sb.append("*");
|
||||
append("*");
|
||||
break;
|
||||
case OR:
|
||||
sb.append("|");
|
||||
append("|");
|
||||
break;
|
||||
case SHIFT_LEFT:
|
||||
sb.append("<<");
|
||||
append("<<");
|
||||
break;
|
||||
case SHIFT_RIGHT:
|
||||
sb.append(">>");
|
||||
append(">>");
|
||||
break;
|
||||
case SHIFT_RIGHT_UNSIGNED:
|
||||
sb.append(">>>");
|
||||
append(">>>");
|
||||
break;
|
||||
case SUBTRACT:
|
||||
sb.append("-");
|
||||
append("-");
|
||||
break;
|
||||
case XOR:
|
||||
sb.append("^");
|
||||
append("^");
|
||||
break;
|
||||
}
|
||||
sb.append(" @").append(second.getIndex());
|
||||
append(" ").appendLocalVar(second);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public void assign(VariableReader receiver, VariableReader assignee) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := @").append(assignee.getIndex());
|
||||
appendLocalVar(receiver).append(" := ").appendLocalVar(assignee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex())
|
||||
.append(" to ");
|
||||
escapeIdentifierIfNeeded(targetType.toString(), sb);
|
||||
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value).append(" to ")
|
||||
.escapeIdentifierIfNeeded(targetType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType,
|
||||
NumericOperandType targetType) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex())
|
||||
.append(" from ").append(sourceType).append(" to ").append(targetType);
|
||||
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value)
|
||||
.append(" from ").append(sourceType.toString()).append(" to ").append(targetType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type,
|
||||
CastIntegerDirection direction) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex());
|
||||
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value);
|
||||
switch (direction) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +262,7 @@ class InstructionStringifier implements InstructionReader {
|
|||
@Override
|
||||
public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent,
|
||||
BasicBlockReader alternative) {
|
||||
sb.append("if @").append(operand.getIndex()).append(" ");
|
||||
append("if ").appendLocalVar(operand).append(" ");
|
||||
switch (cond) {
|
||||
case EQUAL:
|
||||
sb.append("== 0");
|
||||
|
@ -248,174 +289,164 @@ class InstructionStringifier implements InstructionReader {
|
|||
sb.append("=== null");
|
||||
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
|
||||
public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second,
|
||||
BasicBlockReader consequent, BasicBlockReader alternative) {
|
||||
sb.append("if @").append(first.getIndex()).append(" ");
|
||||
append("if ").appendLocalVar(first).append(" ");
|
||||
switch (cond) {
|
||||
case EQUAL:
|
||||
sb.append("==");
|
||||
append("==");
|
||||
break;
|
||||
case REFERENCE_EQUAL:
|
||||
sb.append("===");
|
||||
append("===");
|
||||
break;
|
||||
case NOT_EQUAL:
|
||||
sb.append("!=");
|
||||
append("!=");
|
||||
break;
|
||||
case REFERENCE_NOT_EQUAL:
|
||||
sb.append("!==");
|
||||
append("!==");
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jump(BasicBlockReader target) {
|
||||
sb.append("goto $").append(target.getIndex());
|
||||
append("goto $").append(target.getIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void choose(VariableReader condition, List<? extends SwitchTableEntryReader> table,
|
||||
BasicBlockReader defaultTarget) {
|
||||
sb.append("switch @").append(condition.getIndex()).append(" ");
|
||||
append("switch ").appendLocalVar(condition).append(" ");
|
||||
for (int i = 0; i < table.size(); ++i) {
|
||||
if (i > 0) {
|
||||
sb.append(" ");
|
||||
append(" ");
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(VariableReader valueToReturn) {
|
||||
sb.append("return");
|
||||
append("return");
|
||||
if (valueToReturn != null) {
|
||||
sb.append(" @").append(valueToReturn.getIndex());
|
||||
append(" ").appendLocalVar(valueToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raise(VariableReader exception) {
|
||||
sb.append("throw @").append(exception.getIndex());
|
||||
append("throw ").appendLocalVar(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := new ");
|
||||
escapeIdentifierIfNeeded(itemType.toString(), sb);
|
||||
sb.append("[@").append(size.getIndex()).append(']');
|
||||
appendLocalVar(receiver).append(" := new ").escapeIdentifierIfNeeded(itemType.toString())
|
||||
.append("[").appendLocalVar(size).append(']');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createArray(VariableReader receiver, ValueType itemType, List<? extends VariableReader> dimensions) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := newArray ");
|
||||
escapeIdentifierIfNeeded(itemType.toString(), sb);
|
||||
sb.append("[");
|
||||
appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(itemType.toString());
|
||||
append("[");
|
||||
|
||||
for (int i = 0; i < dimensions.size(); ++i) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
append(", ");
|
||||
}
|
||||
sb.append("@").append(dimensions.get(i).getIndex());
|
||||
appendLocalVar(dimensions.get(i));
|
||||
}
|
||||
sb.append("]");
|
||||
append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(VariableReader receiver, String type) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := newArray ");
|
||||
escapeIdentifierIfNeeded(type, sb);
|
||||
appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := field ");
|
||||
escapeIdentifierIfNeeded(field.toString(), sb);
|
||||
appendLocalVar(receiver).append(" := field ").escapeIdentifierIfNeeded(field.toString());
|
||||
if (instance != null) {
|
||||
sb.append(" @").append(instance.getIndex());
|
||||
append(" ").appendLocalVar(instance);
|
||||
}
|
||||
sb.append(" as ");
|
||||
escapeIdentifierIfNeeded(fieldType.toString(), sb);
|
||||
append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) {
|
||||
sb.append("field ");
|
||||
escapeIdentifierIfNeeded(field.toString(), sb);
|
||||
append("field ").escapeIdentifierIfNeeded(field.toString());
|
||||
if (instance != null) {
|
||||
sb.append(" @").append(instance.getIndex());
|
||||
append(" ").appendLocalVar(instance);
|
||||
}
|
||||
sb.append(" := @").append(value.getIndex());
|
||||
sb.append(" as ");
|
||||
escapeIdentifierIfNeeded(fieldType.toString(), sb);
|
||||
append(" := ").appendLocalVar(value).append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arrayLength(VariableReader receiver, VariableReader array) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := lengthOf @").append(array.getIndex());
|
||||
appendLocalVar(receiver).append(" := lengthOf ").appendLocalVar(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneArray(VariableReader receiver, VariableReader array) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := clone @").append(array.getIndex());
|
||||
appendLocalVar(receiver).append(" := clone ").appendLocalVar(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getElement(VariableReader receiver, VariableReader array, VariableReader index,
|
||||
ArrayElementType type) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := @").append(array.getIndex()).append("[@")
|
||||
.append(index.getIndex()).append("]").append(" as " + type.name().toLowerCase(Locale.ROOT));
|
||||
appendLocalVar(receiver).append(" := ").appendLocalVar(array).append("[").appendLocalVar(index).append("]")
|
||||
.append(" as " + type.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putElement(VariableReader array, VariableReader index, VariableReader value, ArrayElementType type) {
|
||||
sb.append("@").append(array.getIndex()).append("[@").append(index.getIndex()).append("] := @")
|
||||
.append(value.getIndex()).append(" as " + type.name().toLowerCase());
|
||||
appendLocalVar(array).append("[").appendLocalVar(index).append("] := ").appendLocalVar(value)
|
||||
.append(" as " + type.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
|
||||
List<? extends VariableReader> arguments, InvocationType type) {
|
||||
if (receiver != null) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ");
|
||||
appendLocalVar(receiver).append(" := ");
|
||||
}
|
||||
if (instance == null) {
|
||||
sb.append("invokeStatic ");
|
||||
append("invokeStatic ");
|
||||
} else {
|
||||
switch (type) {
|
||||
case SPECIAL:
|
||||
sb.append("invoke ");
|
||||
append("invoke ");
|
||||
break;
|
||||
case VIRTUAL:
|
||||
sb.append("invokeVirtual ");
|
||||
append("invokeVirtual ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
escapeIdentifierIfNeeded(method.toString(), sb);
|
||||
escapeIdentifierIfNeeded(method.toString());
|
||||
if (instance != null) {
|
||||
sb.append(' ');
|
||||
sb.append("@").append(instance.getIndex());
|
||||
append(' ').appendLocalVar(instance);
|
||||
}
|
||||
|
||||
for (int i = 0; i < arguments.size(); ++i) {
|
||||
if (instance != null || i > 0) {
|
||||
sb.append(",");
|
||||
append(",");
|
||||
}
|
||||
sb.append(' ');
|
||||
sb.append("@").append(arguments.get(i).getIndex());
|
||||
append(' ').appendLocalVar(arguments.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,17 +455,17 @@ class InstructionStringifier implements InstructionReader {
|
|||
List<? extends VariableReader> arguments, MethodHandle bootstrapMethod,
|
||||
List<RuntimeConstant> bootstrapArguments) {
|
||||
if (receiver != null) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := ");
|
||||
appendLocalVar(receiver).append(" := ");
|
||||
}
|
||||
if (instance != null) {
|
||||
sb.append("@").append(instance.getIndex()).append(".");
|
||||
appendLocalVar(instance).append(".");
|
||||
}
|
||||
sb.append(method.getName()).append("(");
|
||||
sb.append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", ")));
|
||||
sb.append(") ");
|
||||
sb.append("[").append(convert(bootstrapMethod)).append('(');
|
||||
sb.append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", ")));
|
||||
sb.append(")");
|
||||
append(method.getName()).append("(");
|
||||
append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", ")));
|
||||
append(") ");
|
||||
append("[").append(convert(bootstrapMethod)).append('(');
|
||||
append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", ")));
|
||||
append(")");
|
||||
}
|
||||
|
||||
private String convert(MethodHandle handle) {
|
||||
|
@ -488,28 +519,27 @@ class InstructionStringifier implements InstructionReader {
|
|||
|
||||
@Override
|
||||
public void isInstance(VariableReader receiver, VariableReader value, ValueType type) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := @").append(value.getIndex())
|
||||
.append(" instanceOf ");
|
||||
escapeIdentifierIfNeeded(type.toString(), sb);
|
||||
appendLocalVar(receiver).append(" := ").appendLocalVar(value).append(" instanceOf ")
|
||||
.escapeIdentifierIfNeeded(type.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initClass(String className) {
|
||||
sb.append("initClass ").append(className);
|
||||
append("initClass ").append(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nullCheck(VariableReader receiver, VariableReader value) {
|
||||
sb.append("@").append(receiver.getIndex()).append(" := nullCheck @").append(value.getIndex());
|
||||
appendLocalVar(receiver).append(" := nullCheck ").appendLocalVar(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void monitorEnter(VariableReader objectRef) {
|
||||
sb.append("monitorEnter @").append(objectRef.getIndex());
|
||||
append("monitorEnter ").appendLocalVar(objectRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void monitorExit(VariableReader objectRef) {
|
||||
sb.append("monitorExit @").append(objectRef.getIndex());
|
||||
append("monitorExit ").appendLocalVar(objectRef);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,13 @@ public class ListingBuilder {
|
|||
public String buildListing(ProgramReader program, String prefix) {
|
||||
StringBuilder sb = 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) {
|
||||
VariableReader var = program.variableAt(i);
|
||||
if (var == null || var.getDebugName() == null) {
|
||||
continue;
|
||||
}
|
||||
sb.append(prefix).append("var @").append(i);
|
||||
sb.append(prefix).append("var @").append(stringifier.getVariableLabel(i));
|
||||
sb.append('\n');
|
||||
}
|
||||
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
||||
|
@ -41,20 +41,21 @@ public class ListingBuilder {
|
|||
}
|
||||
|
||||
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()) {
|
||||
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();
|
||||
for (int j = 0; j < incomings.size(); ++j) {
|
||||
if (j > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
IncomingReader incoming = incomings.get(j);
|
||||
sb.append("@").append(incoming.getValue().getIndex()).append(" from ")
|
||||
.append("$").append(incoming.getSource().getIndex());
|
||||
sb.append("@").append(stringifier.getVariableLabel(incoming.getValue().getIndex()))
|
||||
.append(" from ").append("$").append(incoming.getSource().getIndex());
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
|
@ -86,8 +87,10 @@ public class ListingBuilder {
|
|||
sb.append(" goto $").append(tryCatch.getHandler().getIndex());
|
||||
sb.append("\n");
|
||||
for (TryCatchJointReader joint : tryCatch.readJoints()) {
|
||||
sb.append(" @").append(joint.getReceiver().getIndex()).append(" := ephi ");
|
||||
sb.append(joint.readSourceVariables().stream().map(sourceVar -> "@" + sourceVar.getIndex())
|
||||
sb.append(" @").append(stringifier.getVariableLabel(joint.getReceiver().getIndex()))
|
||||
.append(" := ephi ");
|
||||
sb.append(joint.readSourceVariables().stream()
|
||||
.map(sourceVar -> "@" + stringifier.getVariableLabel(sourceVar.getIndex()))
|
||||
.collect(Collectors.joining(", ")));
|
||||
sb.append("\n");
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ import org.teavm.model.instructions.MonitorEnterInstruction;
|
|||
import org.teavm.model.instructions.MonitorExitInstruction;
|
||||
import org.teavm.model.instructions.NegateInstruction;
|
||||
import org.teavm.model.instructions.NullCheckInstruction;
|
||||
import org.teavm.model.instructions.NullConstantInstruction;
|
||||
import org.teavm.model.instructions.NumericOperandType;
|
||||
import org.teavm.model.instructions.PutElementInstruction;
|
||||
import org.teavm.model.instructions.PutFieldInstruction;
|
||||
|
@ -349,6 +350,13 @@ public class ListingParser {
|
|||
case IDENTIFIER: {
|
||||
String keyword = (String) lexer.getTokenValue();
|
||||
switch (keyword) {
|
||||
case "null": {
|
||||
lexer.nextToken();
|
||||
NullConstantInstruction insn = new NullConstantInstruction();
|
||||
insn.setReceiver(receiver);
|
||||
addInstruction(insn);
|
||||
break;
|
||||
}
|
||||
case "phi":
|
||||
lexer.nextToken();
|
||||
parsePhi(receiver);
|
||||
|
|
63
core/src/main/java/org/teavm/model/util/DominatorWalker.java
Normal file
63
core/src/main/java/org/teavm/model/util/DominatorWalker.java
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -105,7 +105,7 @@ public class PhiUpdater {
|
|||
return variableToSourceMap.get(var);
|
||||
}
|
||||
|
||||
public List<Phi> getSynthesizedPhisBy() {
|
||||
public List<Phi> getSynthesizedPhis() {
|
||||
return synthesizedPhis;
|
||||
}
|
||||
|
||||
|
@ -232,6 +232,7 @@ public class PhiUpdater {
|
|||
for (Phi phi : synthesizedPhisByBlock.get(index)) {
|
||||
Variable var = program.createVariable();
|
||||
var.setDebugName(phi.getReceiver().getDebugName());
|
||||
var.setLabel(phi.getReceiver().getLabel());
|
||||
mapVariable(phi.getReceiver().getIndex(), var);
|
||||
phisByReceiver.put(var.getIndex(), phi);
|
||||
phi.setReceiver(var);
|
||||
|
@ -269,6 +270,7 @@ public class PhiUpdater {
|
|||
for (TryCatchJoint joint : synthesizedJointsByBlock.get(index).get(i)) {
|
||||
Variable var = program.createVariable();
|
||||
var.setDebugName(joint.getReceiver().getDebugName());
|
||||
var.setLabel(joint.getReceiver().getLabel());
|
||||
mapVariable(joint.getReceiver().getIndex(), var);
|
||||
joint.setReceiver(var);
|
||||
jointsByReceiver.put(var.getIndex(), joint);
|
||||
|
@ -277,7 +279,8 @@ public class PhiUpdater {
|
|||
variableMap = regularVariableMap;
|
||||
|
||||
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();
|
||||
next.variables = (catchSuccessors.contains(successor) ? catchVariableMap : variableMap).clone();
|
||||
next.block = program.basicBlockAt(successor);
|
||||
|
@ -473,7 +476,10 @@ public class PhiUpdater {
|
|||
if (!usedDefinitions[var.getIndex()]) {
|
||||
usedDefinitions[var.getIndex()] = true;
|
||||
} else {
|
||||
Variable old = var;
|
||||
var = program.createVariable();
|
||||
var.setDebugName(old.getDebugName());
|
||||
var.setLabel(old.getLabel());
|
||||
}
|
||||
|
||||
return var;
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
package org.teavm.model.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.teavm.common.Graph;
|
||||
import org.teavm.common.GraphBuilder;
|
||||
import org.teavm.model.BasicBlock;
|
||||
|
@ -219,4 +221,24 @@ public final class ProgramUtils {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
21
core/src/test/resources/model/analysis/branch.extended.txt
Normal file
21
core/src/test/resources/model/analysis/branch.extended.txt
Normal 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
|
13
core/src/test/resources/model/analysis/branch.original.txt
Normal file
13
core/src/test/resources/model/analysis/branch.original.txt
Normal 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
|
21
core/src/test/resources/model/analysis/phiJoin.extended.txt
Normal file
21
core/src/test/resources/model/analysis/phiJoin.extended.txt
Normal 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
|
13
core/src/test/resources/model/analysis/phiJoin.original.txt
Normal file
13
core/src/test/resources/model/analysis/phiJoin.original.txt
Normal 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
|
10
core/src/test/resources/model/analysis/simple.extended.txt
Normal file
10
core/src/test/resources/model/analysis/simple.extended.txt
Normal 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
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user