Add tests for escape analysis/scalar replacement. Fix found bugs

This commit is contained in:
Alexey Andreev 2017-01-21 22:46:52 +03:00
parent e4fab2be41
commit d3bed47b1d
22 changed files with 514 additions and 51 deletions

View File

@ -19,19 +19,20 @@ import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.teavm.common.DisjointSet;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.Incoming;
import org.teavm.model.Instruction;
@ -39,6 +40,7 @@ import org.teavm.model.MethodReference;
import org.teavm.model.Phi;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.AssignInstruction;
@ -47,7 +49,6 @@ import org.teavm.model.instructions.BranchingInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.CloneArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
@ -67,17 +68,13 @@ import org.teavm.model.util.LivenessAnalyzer;
import org.teavm.model.util.UsageExtractor;
public class EscapeAnalysis {
private ClassReaderSource classSource;
private int[] definitionClasses;
private boolean[] escapingVars;
private FieldReference[][] fields;
public EscapeAnalysis(ClassReaderSource classSource) {
this.classSource = classSource;
}
private Map<FieldReference, ValueType> fieldTypes;
public void analyze(Program program, MethodReference methodReference) {
InstructionEscapeVisitor visitor = new InstructionEscapeVisitor(program.variableCount(), classSource);
InstructionEscapeVisitor visitor = new InstructionEscapeVisitor(program.variableCount());
for (int i = 0; i <= methodReference.parameterCount(); ++i) {
visitor.escapingVars[i] = true;
}
@ -91,9 +88,9 @@ public class EscapeAnalysis {
}
}
definitionClasses = visitor.definitionClasses.pack(program.variableCount());
escapingVars = new boolean[program.variableCount()];
fieldTypes = visitor.fieldTypes;
for (int i = 0; i < program.variableCount(); ++i) {
if (visitor.escapingVars[i]) {
escapingVars[definitionClasses[i]] = true;
@ -101,6 +98,7 @@ public class EscapeAnalysis {
}
analyzePhis(program);
propagateFields(program, visitor.fields);
fields = packFields(visitor.fields);
}
@ -108,6 +106,10 @@ public class EscapeAnalysis {
return escapingVars[definitionClasses[var]];
}
public ValueType getFieldType(FieldReference field) {
return fieldTypes.get(field);
}
public FieldReference[] getFields(int var) {
FieldReference[] varFields = fields[definitionClasses[var]];
return varFields != null ? varFields.clone() : null;
@ -123,10 +125,13 @@ public class EscapeAnalysis {
IntSet sharedIncomingVars = new IntOpenHashSet();
BitSet usedVars = getUsedVarsInBlock(livenessAnalyzer, block);
for (Phi phi : block.getPhis()) {
if (escapes(phi.getReceiver().getIndex())) {
queue.addLast(phi.getReceiver().getIndex());
}
for (Incoming incoming : phi.getIncomings()) {
int var = incoming.getValue().getIndex();
graphBuilder.addEdge(var, phi.getReceiver().getIndex());
if (escapingVars[definitionClasses[var]] || !sharedIncomingVars.add(var) || usedVars.get(var)) {
if (escapes(var) || !sharedIncomingVars.add(var) || usedVars.get(var)) {
queue.addLast(var);
}
}
@ -142,6 +147,9 @@ public class EscapeAnalysis {
for (int successor : graph.outgoingEdges(var)) {
queue.addLast(successor);
}
for (int predecessor : graph.incomingEdges(var)) {
queue.addLast(predecessor);
}
}
}
}
@ -167,6 +175,52 @@ public class EscapeAnalysis {
return usedVars;
}
private void propagateFields(Program program, List<Set<FieldReference>> fields) {
class Task {
int index;
FieldReference field;
Task(int index, FieldReference field) {
this.index = index;
this.field = field;
}
}
Queue<Task> queue = new ArrayDeque<>();
GraphBuilder graphBuilder = new GraphBuilder(program.variableCount());
for (BasicBlock block : program.getBasicBlocks()) {
for (Phi phi : block.getPhis()) {
for (Incoming incoming : phi.getIncomings()) {
graphBuilder.addEdge(phi.getReceiver().getIndex(), incoming.getValue().getIndex());
Set<FieldReference> receiverFields = fields.get(phi.getReceiver().getIndex());
if (receiverFields != null) {
for (FieldReference field : receiverFields) {
queue.add(new Task(phi.getReceiver().getIndex(), field));
}
receiverFields.clear();
}
}
}
}
Graph graph = graphBuilder.build();
while (!queue.isEmpty()) {
Task task = queue.remove();
Set<FieldReference> taskFields = fields.get(task.index);
if (taskFields == null) {
taskFields = new LinkedHashSet<>();
fields.set(task.index, taskFields);
}
if (!taskFields.add(task.field)) {
continue;
}
for (int successor : graph.outgoingEdges(task.index)) {
queue.add(new Task(successor, task.field));
}
}
}
private FieldReference[][] packFields(List<Set<FieldReference>> fields) {
List<Set<FieldReference>> joinedFields = new ArrayList<>(Collections.nCopies(fields.size(), null));
@ -193,13 +247,12 @@ public class EscapeAnalysis {
}
private static class InstructionEscapeVisitor extends AbstractInstructionVisitor {
ClassReaderSource classSource;
DisjointSet definitionClasses;
boolean[] escapingVars;
List<Set<FieldReference>> fields;
Map<FieldReference, ValueType> fieldTypes = new HashMap<>();
public InstructionEscapeVisitor(int variableCount, ClassReaderSource classSource) {
this.classSource = classSource;
public InstructionEscapeVisitor(int variableCount) {
fields = new ArrayList<>(Collections.nCopies(variableCount, null));
definitionClasses = new DisjointSet();
for (int i = 0; i < variableCount; ++i) {
@ -208,21 +261,6 @@ public class EscapeAnalysis {
escapingVars = new boolean[variableCount];
}
@Override
public void visit(ConstructInstruction insn) {
ClassReader cls = classSource.get(insn.getType());
if (cls == null) {
escapingVars[insn.getReceiver().getIndex()] = true;
}
while (cls != null) {
for (FieldReader field : cls.getFields()) {
addField(insn.getReceiver(), field.getReference());
}
cls = cls.getParent() != null ? classSource.get(cls.getParent()) : null;
}
}
@Override
public void visit(NullConstantInstruction insn) {
escapingVars[insn.getReceiver().getIndex()] = true;
@ -275,16 +313,16 @@ public class EscapeAnalysis {
@Override
public void visit(GetFieldInstruction insn) {
escapingVars[insn.getReceiver().getIndex()] = true;
addField(insn.getInstance(), insn.getField());
addField(insn.getInstance(), insn.getField(), insn.getFieldType());
}
@Override
public void visit(PutFieldInstruction insn) {
escapingVars[insn.getValue().getIndex()] = true;
addField(insn.getInstance(), insn.getField());
addField(insn.getInstance(), insn.getField(), insn.getFieldType());
}
private void addField(Variable instance, FieldReference field) {
private void addField(Variable instance, FieldReference field, ValueType fieldType) {
if (instance == null) {
return;
}
@ -294,6 +332,7 @@ public class EscapeAnalysis {
fields.set(instance.getIndex(), fieldSet);
}
fieldSet.add(field);
fieldTypes.put(field, fieldType);
}
@Override

View File

@ -21,8 +21,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.Incoming;
import org.teavm.model.Instruction;
@ -59,15 +57,23 @@ public class ScalarReplacement implements MethodOptimization {
Collections.nCopies(program.variableCount(), null));
MethodReference methodReference = context.getMethod().getReference();
EscapeAnalysis escapeAnalysis = new EscapeAnalysis(context.getClassSource());
EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
escapeAnalysis.analyze(program, methodReference);
boolean canPerform = false;
for (int i = 0; i < fieldMappings.size(); ++i) {
FieldReference[] fields = escapeAnalysis.getFields(i);
if (!escapeAnalysis.escapes(i) && fields != null) {
Variable instanceVar = program.variableAt(i);
Map<FieldReference, Variable> fieldMapping = new LinkedHashMap<>();
for (FieldReference field : fields) {
fieldMapping.put(field, program.createVariable());
Variable var = program.createVariable();
if (instanceVar.getDebugName() != null) {
var.setDebugName(instanceVar.getDebugName() + "$" + field.getFieldName());
}
if (instanceVar.getLabel() != null) {
var.setLabel(instanceVar.getLabel() + "$" + field.getFieldName());
}
fieldMapping.put(field, var);
}
fieldMappings.set(i, fieldMapping);
canPerform = true;
@ -77,8 +83,7 @@ public class ScalarReplacement implements MethodOptimization {
return false;
}
ScalarReplacementVisitor visitor = new ScalarReplacementVisitor(escapeAnalysis, context.getClassSource(),
fieldMappings);
ScalarReplacementVisitor visitor = new ScalarReplacementVisitor(escapeAnalysis, fieldMappings);
for (BasicBlock block : program.getBasicBlocks()) {
for (Instruction instruction : block) {
instruction.acceptVisitor(visitor);
@ -116,7 +121,7 @@ public class ScalarReplacement implements MethodOptimization {
phiReplacement.getIncomings().add(incomingReplacement);
}
additionalPhis.add(phi);
additionalPhis.add(phiReplacement);
}
block.getPhis().remove(i--);
}
@ -134,13 +139,11 @@ public class ScalarReplacement implements MethodOptimization {
static class ScalarReplacementVisitor extends AbstractInstructionVisitor {
private EscapeAnalysis escapeAnalysis;
private ClassReaderSource classSource;
private List<Map<FieldReference, Variable>> fieldMappings;
public ScalarReplacementVisitor(EscapeAnalysis escapeAnalysis, ClassReaderSource classSource,
public ScalarReplacementVisitor(EscapeAnalysis escapeAnalysis,
List<Map<FieldReference, Variable>> fieldMappings) {
this.escapeAnalysis = escapeAnalysis;
this.classSource = classSource;
this.fieldMappings = fieldMappings;
}
@ -149,9 +152,9 @@ public class ScalarReplacement implements MethodOptimization {
int var = insn.getReceiver().getIndex();
if (!escapeAnalysis.escapes(var) && escapeAnalysis.getFields(var) != null) {
for (FieldReference fieldRef : escapeAnalysis.getFields(var)) {
FieldReader field = classSource.resolve(fieldRef);
ValueType fieldType = escapeAnalysis.getFieldType(fieldRef);
Variable receiver = fieldMappings.get(insn.getReceiver().getIndex()).get(fieldRef);
Instruction initializer = generateDefaultValue(field.getType(), receiver);
Instruction initializer = generateDefaultValue(fieldType, receiver);
initializer.setLocation(initializer.getLocation());
insn.insertPrevious(initializer);
}

View File

@ -318,7 +318,7 @@ class ListingLexer {
if (c < ' ') {
throw new ListingParseException("Unexpected character in string literal: " + c, index);
}
sb.append(c);
sb.append((char) c);
nextChar();
break;
}

View File

@ -235,12 +235,9 @@ public class PhiUpdater {
int[] successors = domGraph.outgoingEdges(index);
IntSet successorSet = IntOpenHashSet.from(successors);
for (Incoming output : phiOutputs.get(index)) {
if (successorSet.contains(output.getPhi().getBasicBlock().getIndex())) {
Variable var = output.getValue();
output.setValue(use(var));
}
Variable var = output.getValue();
output.setValue(use(var));
}
for (int j = successors.length - 1; j >= 0; --j) {

View File

@ -0,0 +1,113 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.model.optimization.test;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListingParseUtils;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.optimization.MethodOptimizationContext;
import org.teavm.model.optimization.ScalarReplacement;
import org.teavm.model.text.ListingBuilder;
import org.teavm.model.util.ProgramUtils;
public class ScalarReplacementTest {
private static final String PREFIX = "model/optimization/scalar-replacement/";
@Rule
public TestName name = new TestName();
@Test
public void simple() {
doTest();
}
@Test
public void phi() {
doTest();
}
@Test
public void escapingPhi() {
doTest();
}
@Test
public void escapingPhiSource() {
doTest();
}
@Test
public void escapingPhiReceiver() {
doTest();
}
@Test
public void escapingSharedPhiSource() {
doTest();
}
@Test
public void copy() {
doTest();
}
private void doTest() {
String originalPath = PREFIX + name.getMethodName() + ".original.txt";
String expectedPath = PREFIX + name.getMethodName() + ".expected.txt";
Program original = ListingParseUtils.parseFromResource(originalPath);
Program expected = ListingParseUtils.parseFromResource(expectedPath);
performScalarReplacement(original);
String originalText = new ListingBuilder().buildListing(original, "");
String expectedText = new ListingBuilder().buildListing(expected, "");
Assert.assertEquals(expectedText, originalText);
}
private void performScalarReplacement(Program program) {
ClassHolder testClass = new ClassHolder("TestClass");
MethodHolder testMethod = new MethodHolder("testMethod", ValueType.VOID);
testMethod.setProgram(ProgramUtils.copy(program));
testClass.addMethod(testMethod);
MethodOptimizationContext context = new MethodOptimizationContext() {
@Override
public MethodReader getMethod() {
return testMethod;
}
@Override
public DependencyInfo getDependencyInfo() {
return null;
}
@Override
public ClassReaderSource getClassSource() {
return null;
}
};
new ScalarReplacement().optimize(context, program);
}
}

View File

@ -50,6 +50,11 @@ public class PhiUpdaterTest {
doTest();
}
@Test
public void phiIncoming() {
doTest();
}
private void doTest() {
String originalPath = PREFIX + name.getMethodName() + ".original.txt";
String expectedPath = PREFIX + name.getMethodName() + ".expected.txt";

View File

@ -0,0 +1,15 @@
var @this as this
$start
@a$foo := 0
@a$bar := 0
@tmp1 := 23
@a$foo_1 := @tmp1
@b$foo := @a$foo_1
@b$bar := @a$bar
@tmp2 := 42
@b$bar_1 := @tmp2
@r := @b$foo
@s := @b$bar_1
@t := @a$bar
return @r

View File

@ -0,0 +1,13 @@
var @this as this
$start
@a := new A
@tmp1 := 23
field A.foo @a := @tmp1 as I
@b := @a
@tmp2 := 42
field A.bar @b := @tmp2 as I
@r := field A.foo @b as I
@s := field A.bar @b as I
@t := field A.bar @a as I
return @r

View File

@ -0,0 +1,23 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
@s := field B.bar @b as I
return @r

View File

@ -0,0 +1,23 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
@s := field B.bar @b as I
return @r

View File

@ -0,0 +1,19 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
return @c

View File

@ -0,0 +1,19 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
return @c

View File

@ -0,0 +1,23 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
field B.s := @b as `LB;`
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
return @r

View File

@ -0,0 +1,23 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
field B.s := @b as `LB;`
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
return @r

View File

@ -0,0 +1,24 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
@x := new B
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@d := phi @a from $zero, @x from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
return @r

View File

@ -0,0 +1,24 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
@x := new B
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@d := phi @a from $zero, @x from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
return @r

View File

@ -0,0 +1,27 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a$foo := null
@a$baz := 0.0F
@a$bar := 0
@tmp1 := null
@a$foo_1 := @tmp1
goto $joint
$nonzero
@b$bar := 0
@b$boo := 0
@b$baz := 0.0F
@tmp2 := 23
@b$bar_1 := @tmp2
@b$boo_1 := @tmp2
goto $joint
$joint
@c$baz := phi @a$baz from $zero, @b$baz from $nonzero
@c$bar := phi @a$bar from $zero, @b$bar_1 from $nonzero
@tmp3 := 12.0F
@c$baz_1 := @tmp3
@r := @c$bar
return @r

View File

@ -0,0 +1,22 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := new A
@tmp1 := null
field A.foo @a := @tmp1 as `Ljava/lang/Object;`
goto $joint
$nonzero
@b := new B
@tmp2 := 23
field B.bar @b := @tmp2 as I
field B.boo @b := @tmp2 as I
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
@tmp3 := 12.0F
field C.baz @c := @tmp3 as F
@r := field B.bar @c as I
return @r

View File

@ -0,0 +1,11 @@
var @this as this
$start
@x$foo := null
@x$bar := 0
@a := 'qwe'
@x$foo_1 := @a
@b := 123
@x$bar_1 := @b
@y := @x$bar_1
return @y

View File

@ -0,0 +1,10 @@
var @this as this
$start
@x := new X
@a := 'qwe'
field X.foo @x := @a as `Ljava/lang/String;`
@b := 123
field X.bar @x := @b as I
@y := field X.bar @x as I
return @y

View File

@ -0,0 +1,15 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := 0
goto $joint
$nonzero
@b := 1
@b_1 := 2
goto $joint
$joint
@c := phi @a from $zero, @b_1 from $nonzero
return @c

View File

@ -0,0 +1,15 @@
var @this as this
$start
@cond := invokeStatic `Foo.bar()I`
if @cond == 0 then goto $zero else goto $nonzero
$zero
@a := 0
goto $joint
$nonzero
@b := 1
@b := 2
goto $joint
$joint
@c := phi @a from $zero, @b from $nonzero
return @c