Add optimization that eliminates repeated access to fields

This commit is contained in:
Alexey Andreev 2019-10-30 16:09:17 +03:00
parent 511160935f
commit 81cc3c156e
24 changed files with 1044 additions and 1 deletions

View File

@ -0,0 +1,244 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.model.analysis;
import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.IntSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.model.BasicBlock;
import org.teavm.model.Incoming;
import org.teavm.model.Instruction;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.Phi;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
public class AliasAnalysis {
private Graph interferenceGraph;
private boolean[] variablesWithExternalObject;
private int[] arrayOfVariablesWithExternalObject;
public void analyze(Program program, MethodDescriptor methodDescriptor) {
DfgBuildVisitor visitor = prepare(program, methodDescriptor);
IntSet[] instances = propagate(visitor, program.variableCount());
buildInterferenceGraph(instances, visitor.constructedObjectCounter);
}
public int[] affectedVariables(int variable) {
return interferenceGraph.outgoingEdges(variable);
}
public boolean affectsEverything(int variable) {
return variablesWithExternalObject[variable];
}
public int[] getExternalObjects() {
return arrayOfVariablesWithExternalObject;
}
private DfgBuildVisitor prepare(Program program, MethodDescriptor methodDescriptor) {
DfgBuildVisitor visitor = new DfgBuildVisitor(program.variableCount());
for (int i = 1; i <= methodDescriptor.parameterCount(); ++i) {
if (methodDescriptor.parameterType(i - 1) instanceof ValueType.Object) {
visitor.queue.addLast(i);
visitor.queue.addLast(0);
}
}
for (BasicBlock block : program.getBasicBlocks()) {
for (Phi phi : block.getPhis()) {
for (Incoming incoming : phi.getIncomings()) {
visitor.builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
}
}
if (block.getExceptionVariable() != null) {
visitor.queue.addLast(block.getExceptionVariable().getIndex());
visitor.queue.addLast(0);
}
for (Instruction instruction : block) {
instruction.acceptVisitor(visitor);
}
}
return visitor;
}
private IntSet[] propagate(DfgBuildVisitor visitor, int variableCount) {
Graph dfg = visitor.builder.build();
IntDeque queue = visitor.queue;
IntSet[] instances = new IntSet[variableCount];
while (!queue.isEmpty()) {
int v = queue.removeFirst();
int instance = queue.removeFirst();
IntSet instancesByVar = instances[v];
if (instancesByVar == null) {
instancesByVar = new IntHashSet();
instances[v] = instancesByVar;
}
if (instancesByVar.contains(instance) || instancesByVar.contains(0)) {
continue;
}
if (instance == 0) {
instancesByVar.clear();
}
instancesByVar.add(instance);
for (int successor : dfg.outgoingEdges(v)) {
if (instances[successor] == null
|| (!instances[successor].contains(instance) && !instances[successor].contains(0))) {
queue.addLast(successor);
queue.addLast(instance);
}
}
}
return instances;
}
private void buildInterferenceGraph(IntSet[] instances, int instanceCount) {
GraphBuilder builder = new GraphBuilder(instances.length);
variablesWithExternalObject = new boolean[instances.length];
IntSet setOfVariablesWithExternalObject = new IntHashSet();
IntSet[] instanceBackMap = new IntSet[instanceCount];
for (int i = 0; i < instances.length; i++) {
IntSet instancesByVar = instances[i];
if (instancesByVar == null) {
continue;
}
for (IntCursor cursor : instancesByVar) {
int instance = cursor.value;
if (instance == 0) {
variablesWithExternalObject[i] = true;
setOfVariablesWithExternalObject.add(i);
} else {
IntSet variables = instanceBackMap[instance];
if (variables == null) {
variables = new IntHashSet();
instanceBackMap[instance] = variables;
}
variables.add(i);
}
}
}
for (int v = 0; v < instances.length; v++) {
builder.addEdge(v, v);
IntSet instancesByVar = instances[v];
if (instancesByVar == null) {
continue;
}
IntSet set;
if (instancesByVar.size() == 1) {
set = instanceBackMap[instancesByVar.iterator().next().value];
} else {
IntHashSet hashSet = new IntHashSet();
for (IntCursor cursor : instancesByVar) {
hashSet.addAll(instanceBackMap[cursor.value]);
}
set = hashSet;
}
if (set == null) {
continue;
}
int[] array = set.toArray();
for (int i = 0; i < array.length - 1; ++i) {
for (int j = i + 1; j < array.length; ++j) {
builder.addEdge(array[i], array[j]);
builder.addEdge(array[j], array[i]);
}
}
}
interferenceGraph = builder.build();
arrayOfVariablesWithExternalObject = setOfVariablesWithExternalObject.toArray();
}
static class DfgBuildVisitor extends AbstractInstructionVisitor {
GraphBuilder builder;
int constructedObjectCounter = 1;
IntDeque queue = new IntArrayDeque();
DfgBuildVisitor(int variableCount) {
builder = new GraphBuilder(variableCount);
}
@Override
public void visit(CastInstruction insn) {
builder.addEdge(insn.getValue().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(AssignInstruction insn) {
builder.addEdge(insn.getAssignee().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(NullCheckInstruction insn) {
builder.addEdge(insn.getValue().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(ConstructInstruction insn) {
queue.addLast(insn.getReceiver().getIndex());
queue.addLast(constructedObjectCounter++);
}
@Override
public void visit(InvokeInstruction insn) {
if (insn.getReceiver() != null && insn.getMethod().getReturnType() instanceof ValueType.Object) {
queue.addLast(insn.getReceiver().getIndex());
queue.addLast(0);
}
}
@Override
public void visit(GetFieldInstruction insn) {
queue.addLast(insn.getReceiver().getIndex());
queue.addLast(0);
}
@Override
public void visit(GetElementInstruction insn) {
if (insn.getType() == ArrayElementType.OBJECT) {
queue.addLast(insn.getReceiver().getIndex());
queue.addLast(0);
}
}
}
}

View File

@ -16,10 +16,13 @@
package org.teavm.model.optimization;
import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReader;
public interface MethodOptimizationContext {
MethodReader getMethod();
DependencyInfo getDependencyInfo();
ClassReaderSource getClassSource();
}

View File

@ -0,0 +1,404 @@
/*
* Copyright 2019 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;
import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntObjectHashMap;
import com.carrotsearch.hppc.IntObjectMap;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.carrotsearch.hppc.cursors.IntObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectIntCursor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.teavm.common.DominatorTree;
import org.teavm.common.Graph;
import org.teavm.common.GraphUtils;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.Instruction;
import org.teavm.model.Program;
import org.teavm.model.analysis.AliasAnalysis;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.util.ProgramUtils;
public class RepeatedFieldReadElimination implements MethodOptimization {
private static final int ENTER = 0;
private static final int EXIT = 1;
private ClassReaderSource classSource;
private boolean[] everythingInvalid;
private List<IntObjectHashMap<Set<FieldReference>>> invalidFields = new ArrayList<>();
private AliasAnalysis aliasAnalysis;
private boolean changed;
@Override
public boolean optimize(MethodOptimizationContext context, Program program) {
classSource = context.getClassSource();
Graph cfg = ProgramUtils.buildControlFlowGraph(program);
DominatorTree dom = GraphUtils.buildDominatorTree(cfg);
aliasAnalysis = new AliasAnalysis();
aliasAnalysis.analyze(program, context.getMethod().getDescriptor());
insertInvalidationPoints(cfg, dom, program);
new Traversal(program, dom, cfg).perform();
return changed;
}
class Traversal {
Program program;
IntDeque worklist = new IntArrayDeque();
IntObjectMap<ObjectIntMap<FieldReference>> cacheVars = new IntObjectHashMap<>();
Deque<State> stateStack = new ArrayDeque<>();
Graph domGraph;
InstructionAnalyzer instructionAnalyzer = new InstructionAnalyzer();
Traversal(Program program, DominatorTree dom, Graph cfg) {
this.program = program;
worklist.addLast(0);
worklist.addLast(ENTER);
domGraph = GraphUtils.buildDominatorGraph(dom, cfg.size());
}
void perform() {
while (!worklist.isEmpty()) {
int operation = worklist.removeLast();
int blockIndex = worklist.removeLast();
BasicBlock block = program.basicBlockAt(blockIndex);
switch (operation) {
case ENTER:
enterBlock(block);
break;
case EXIT:
exitBlock();
break;
}
}
}
private void enterBlock(BasicBlock block) {
stateStack.addLast(new State());
invalidatePreparedFields(block);
for (Instruction instruction : block) {
if (instruction instanceof GetFieldInstruction) {
handleGetField((GetFieldInstruction) instruction);
} else {
instructionAnalyzer.reset();
instruction.acceptVisitor(instructionAnalyzer);
if (instructionAnalyzer.invalidatesAll) {
invalidateAllFields();
} else if (instructionAnalyzer.invalidatedField != null) {
FieldReference field = instructionAnalyzer.invalidatedField;
int instance = instructionAnalyzer.instance;
if (!isVolatile(field)) {
invalidateField(instance, field);
storeIntoCache(instance, field, instructionAnalyzer.newValue);
}
}
}
}
worklist.addLast(block.getIndex());
worklist.addLast(EXIT);
for (int successor : domGraph.outgoingEdges(block.getIndex())) {
worklist.addLast(successor);
worklist.addLast(ENTER);
}
}
private void invalidatePreparedFields(BasicBlock block) {
if (everythingInvalid[block.getIndex()]) {
invalidateAllFields();
return;
}
IntObjectHashMap<Set<FieldReference>> invalidFieldsByBlock = invalidFields.get(block.getIndex());
if (invalidFieldsByBlock != null) {
for (IntObjectCursor<Set<FieldReference>> cursor : invalidFieldsByBlock) {
int instance = cursor.key;
for (FieldReference field : cursor.value) {
invalidateField(instance, field);
}
}
}
}
private void handleGetField(GetFieldInstruction instruction) {
FieldReference field = instruction.getField();
if (isVolatile(field)) {
return;
}
int instanceIndex = instruction.getInstance() != null ? instruction.getInstance().getIndex() : -1;
ObjectIntMap<FieldReference> cacheVarsByInstance = cacheVars.get(instanceIndex);
if (cacheVarsByInstance == null) {
cacheVarsByInstance = new ObjectIntHashMap<>();
cacheVars.put(instanceIndex, cacheVarsByInstance);
}
int cachedVar = cacheVarsByInstance.getOrDefault(field, -1);
if (cachedVar >= 0) {
AssignInstruction assign = new AssignInstruction();
assign.setReceiver(instruction.getReceiver());
assign.setAssignee(program.variableAt(cachedVar));
assign.setLocation(instruction.getLocation());
instruction.replace(assign);
changed = true;
} else {
cachedVar = instruction.getReceiver().getIndex();
cacheVarsByInstance.put(field, cachedVar);
markFieldAsAdded(instanceIndex, field);
}
}
private void storeIntoCache(int instance, FieldReference field, int value) {
ObjectIntMap<FieldReference> cacheVarsByInstance = cacheVars.get(instance);
if (cacheVarsByInstance == null) {
cacheVarsByInstance = new ObjectIntHashMap<>();
cacheVars.put(instance, cacheVarsByInstance);
}
cacheVarsByInstance.put(field, value);
markFieldAsAdded(instance, field);
}
private void markFieldAsAdded(int instance, FieldReference field) {
State state = currentState();
ObjectIntMap<FieldReference> removedFieldsByInstance = state.removedCacheFields.get(instance);
if (removedFieldsByInstance == null || !removedFieldsByInstance.containsKey(field)) {
Set<FieldReference> fields = state.addedCacheFields.get(instance);
if (fields == null) {
fields = new HashSet<>();
state.addedCacheFields.put(instance, fields);
}
fields.add(field);
}
}
private void invalidateAllFields() {
State state = currentState();
for (IntObjectCursor<ObjectIntMap<FieldReference>> instanceCursor : cacheVars) {
int instance = instanceCursor.key;
ObjectIntMap<FieldReference> cacheVarsByInstance = instanceCursor.value;
for (ObjectIntCursor<FieldReference> fieldCursor : cacheVarsByInstance) {
FieldReference field = fieldCursor.key;
int value = fieldCursor.value;
markFieldAsRemoved(state, instance, field, value);
}
}
cacheVars.clear();
}
private void invalidateField(int instance, FieldReference field) {
if (instance == -1) {
invalidateSingleField(instance, field);
return;
}
if (aliasAnalysis.affectsEverything(instance)) {
invalidateFieldOnAllInstances(field);
} else {
for (int affectedVar : aliasAnalysis.affectedVariables(instance)) {
invalidateSingleField(affectedVar, field);
}
for (int affectedVar : aliasAnalysis.getExternalObjects()) {
invalidateSingleField(affectedVar, field);
}
}
}
private void invalidateSingleField(int instance, FieldReference field) {
ObjectIntMap<FieldReference> cacheVarsByInstance = cacheVars.get(instance);
if (cacheVarsByInstance == null || !cacheVarsByInstance.containsKey(field)) {
return;
}
int value = cacheVarsByInstance.remove(field);
State state = currentState();
markFieldAsRemoved(state, instance, field, value);
}
private void invalidateFieldOnAllInstances(FieldReference field) {
for (IntObjectCursor<ObjectIntMap<FieldReference>> instanceCursor : cacheVars) {
int instance = instanceCursor.key;
int value = instanceCursor.value.getOrDefault(field, -1);
if (value >= 0) {
instanceCursor.value.remove(field);
State state = currentState();
markFieldAsRemoved(state, instance, field, value);
}
}
}
private void markFieldAsRemoved(State state, int instance, FieldReference field, int value) {
Set<FieldReference> addedFieldsByInstance = state.addedCacheFields.get(instance);
if (addedFieldsByInstance == null || !addedFieldsByInstance.contains(field)) {
ObjectIntMap<FieldReference> removedFieldsByInstance = state.removedCacheFields.get(instance);
if (removedFieldsByInstance == null) {
removedFieldsByInstance = new ObjectIntHashMap<>();
state.removedCacheFields.put(instance, removedFieldsByInstance);
}
if (!removedFieldsByInstance.containsKey(field)) {
removedFieldsByInstance.put(field, value);
}
}
}
private void exitBlock() {
State state = stateStack.removeLast();
for (IntObjectCursor<Set<FieldReference>> instanceCursor : state.addedCacheFields) {
int instance = instanceCursor.key;
ObjectIntMap<FieldReference> cachedFieldsByInstances = cacheVars.get(instance);
if (cachedFieldsByInstances != null) {
for (FieldReference field : instanceCursor.value) {
cachedFieldsByInstances.remove(field);
}
if (cachedFieldsByInstances.isEmpty()) {
cacheVars.remove(instance);
}
}
}
for (IntObjectCursor<ObjectIntMap<FieldReference>> instanceCursor : state.removedCacheFields) {
int instance = instanceCursor.key;
ObjectIntMap<FieldReference> cacheVarsByInstance = cacheVars.get(instance);
if (cacheVarsByInstance == null) {
cacheVarsByInstance = new ObjectIntHashMap<>();
cacheVars.put(instance, cacheVarsByInstance);
}
for (ObjectIntCursor<FieldReference> fieldCursor : instanceCursor.value) {
FieldReference field = fieldCursor.key;
int value = fieldCursor.value;
cacheVarsByInstance.put(field, value);
}
}
}
private State currentState() {
return stateStack.getLast();
}
}
static class State {
IntObjectMap<ObjectIntMap<FieldReference>> removedCacheFields = new IntObjectHashMap<>();
IntObjectMap<Set<FieldReference>> addedCacheFields = new IntObjectHashMap<>();
}
private void insertInvalidationPoints(Graph cfg, DominatorTree dom, Program program) {
int[][] domFrontiers = GraphUtils.findDominanceFrontiers(cfg, dom);
everythingInvalid = new boolean[program.basicBlockCount()];
invalidFields.addAll(Collections.nCopies(program.basicBlockCount(), null));
IntArrayDeque worklist = new IntArrayDeque();
InstructionAnalyzer instructionAnalyzer = new InstructionAnalyzer();
for (BasicBlock block : program.getBasicBlocks()) {
int[] frontiers = domFrontiers[block.getIndex()];
if (frontiers.length == 0) {
continue;
}
for (Instruction instruction : block) {
instructionAnalyzer.reset();
instruction.acceptVisitor(instructionAnalyzer);
if (instructionAnalyzer.invalidatesAll) {
worklist.addLast(frontiers);
while (!worklist.isEmpty()) {
int target = worklist.removeFirst();
if (!everythingInvalid[target]) {
everythingInvalid[target] = true;
invalidFields.set(target, null);
worklist.addLast(domFrontiers[target]);
}
}
} else if (instructionAnalyzer.invalidatedField != null
&& !isVolatile(instructionAnalyzer.invalidatedField)) {
worklist.addLast(frontiers);
int instance = instructionAnalyzer.instance;
FieldReference field = instructionAnalyzer.invalidatedField;
while (!worklist.isEmpty()) {
int target = worklist.removeFirst();
if (everythingInvalid[target]) {
continue;
}
IntObjectHashMap<Set<FieldReference>> invalidFieldsByBlock = invalidFields.get(target);
if (invalidFieldsByBlock == null) {
invalidFieldsByBlock = new IntObjectHashMap<>();
invalidFields.set(target, invalidFieldsByBlock);
}
Set<FieldReference> invalidFieldsByVar = invalidFieldsByBlock.get(instance);
if (invalidFieldsByVar == null) {
invalidFieldsByVar = new HashSet<>();
invalidFieldsByBlock.put(instance, invalidFieldsByVar);
}
if (invalidFieldsByVar.add(field)) {
worklist.addLast(domFrontiers[target]);
}
}
}
}
}
}
private boolean isVolatile(FieldReference fieldRef) {
FieldReader field = classSource.resolve(fieldRef);
return field == null || field.hasModifier(ElementModifier.VOLATILE);
}
static class InstructionAnalyzer extends AbstractInstructionVisitor {
boolean invalidatesAll;
FieldReference invalidatedField;
int instance;
int newValue;
void reset() {
invalidatesAll = false;
invalidatedField = null;
instance = -1;
}
@Override
public void visit(PutFieldInstruction insn) {
invalidatedField = insn.getField();
instance = insn.getInstance() != null ? insn.getInstance().getIndex() : -1;
newValue = insn.getValue().getIndex();
}
@Override
public void visit(InvokeInstruction insn) {
invalidatesAll = true;
}
}
}

View File

@ -89,6 +89,7 @@ import org.teavm.model.optimization.MethodOptimization;
import org.teavm.model.optimization.MethodOptimizationContext;
import org.teavm.model.optimization.RedundantJumpElimination;
import org.teavm.model.optimization.RedundantNullCheckElimination;
import org.teavm.model.optimization.RepeatedFieldReadElimination;
import org.teavm.model.optimization.ScalarReplacement;
import org.teavm.model.optimization.UnreachableBasicBlockElimination;
import org.teavm.model.optimization.UnusedVariableElimination;
@ -622,7 +623,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
InliningStrategy inliningStrategy;
if (optimizationLevel == TeaVMOptimizationLevel.FULL) {
inliningStrategy = new DefaultInliningStrategy(14, 7, 300, false);
inliningStrategy = new DefaultInliningStrategy(20, 7, 300, false);
} else {
inliningStrategy = new DefaultInliningStrategy(100, 7, 300, true);
}
@ -750,6 +751,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public DependencyInfo getDependencyInfo() {
return dependencyAnalyzer;
}
@Override
public ClassReaderSource getClassSource() {
return dependencyAnalyzer.getClassSource();
}
}
private List<MethodOptimization> getOptimizations() {
@ -761,6 +767,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
//optimizations.add(new LoopInversion());
optimizations.add(new LoopInvariantMotion());
}
if (optimizationLevel.ordinal() >= TeaVMOptimizationLevel.FULL.ordinal()) {
optimizations.add(new RepeatedFieldReadElimination());
}
optimizations.add(new GlobalValueNumbering(optimizationLevel == TeaVMOptimizationLevel.SIMPLE));
optimizations.add(new RedundantNullCheckElimination());
if (optimizationLevel.ordinal() >= TeaVMOptimizationLevel.ADVANCED.ordinal()) {

View File

@ -0,0 +1,151 @@
/*
* Copyright 2019 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.AccessLevel;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.ListingParseUtils;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MutableClassHolderSource;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.optimization.MethodOptimizationContext;
import org.teavm.model.optimization.RepeatedFieldReadElimination;
import org.teavm.model.text.ListingBuilder;
import org.teavm.model.util.ProgramUtils;
public class RepeatedFieldReadEliminationTest {
private static final String PREFIX = "model/optimization/repeated-field-read-elimination/";
@Rule
public TestName name = new TestName();
@Test
public void simple() {
doTest();
}
@Test
public void volatileField() {
doTest();
}
@Test
public void fieldStoreInvalidates() {
doTest();
}
@Test
public void fieldStoreInDifferentObjects() {
doTest();
}
@Test
public void invalidateInOneBranch() {
doTest();
}
@Test
public void invocationInvalidates() {
doTest();
}
@Test
public void alwaysInvalidateExternalObject() {
doTest();
}
@Test
public void updatingExternalObjectInvalidatesAll() {
doTest();
}
@Test
public void mergeInAliasAnalysis() {
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);
performOptimization(original);
String originalText = new ListingBuilder().buildListing(original, "");
String expectedText = new ListingBuilder().buildListing(expected, "");
Assert.assertEquals(expectedText, originalText);
}
private void performOptimization(Program program) {
MutableClassHolderSource classSource = new MutableClassHolderSource();
ClassHolder testClass = new ClassHolder("TestClass");
MethodHolder testMethod = new MethodHolder("testMethod", ValueType.VOID);
testMethod.setProgram(ProgramUtils.copy(program));
testClass.addMethod(testMethod);
classSource.putClassHolder(testClass);
ClassHolder foo = new ClassHolder("Foo");
FieldHolder intField = new FieldHolder("intField");
intField.setLevel(AccessLevel.PUBLIC);
intField.setType(ValueType.INTEGER);
foo.addField(intField);
FieldHolder volatileField = new FieldHolder("volatileField");
volatileField.setLevel(AccessLevel.PUBLIC);
volatileField.setType(ValueType.INTEGER);
volatileField.getModifiers().add(ElementModifier.VOLATILE);
foo.addField(volatileField);
MethodHolder getFoo = new MethodHolder("getFoo", ValueType.object("Foo"));
getFoo.getModifiers().add(ElementModifier.STATIC);
getFoo.getModifiers().add(ElementModifier.NATIVE);
foo.addMethod(getFoo);
classSource.putClassHolder(foo);
MethodOptimizationContext context = new MethodOptimizationContext() {
@Override
public MethodReader getMethod() {
return testMethod;
}
@Override
public DependencyInfo getDependencyInfo() {
return null;
}
@Override
public ClassReaderSource getClassSource() {
return classSource;
}
};
new RepeatedFieldReadElimination().optimize(context, program);
}
}

View File

@ -21,6 +21,7 @@ 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;
@ -110,6 +111,11 @@ public class ScalarReplacementTest {
public DependencyInfo getDependencyInfo() {
return null;
}
@Override
public ClassReaderSource getClassSource() {
return null;
}
};
new ScalarReplacement().optimize(context, program);

View File

@ -0,0 +1,13 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @p as I
@b := field Foo.intField @q as I
@v := 23
field Foo.intField @o := @v as I
@a1 := @a
@b1 := field Foo.intField @q as I
return

View File

@ -0,0 +1,13 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @p as I
@b := field Foo.intField @q as I
@v := 23
field Foo.intField @o := @v as I
@a1 := field Foo.intField @p as I
@b1 := field Foo.intField @q as I
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@a := field Foo.intField @o as I
@v := 23
field Foo.intField @p := @v as I
@b := @a
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@a := field Foo.intField @o as I
@v := 23
field Foo.intField @p := @v as I
@b := field Foo.intField @o as I
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@p := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
@v := 23
field Foo.intField @p := @v as I
@b := field Foo.intField @o as I
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@p := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
@v := 23
field Foo.intField @p := @v as I
@b := field Foo.intField @o as I
return

View File

@ -0,0 +1,18 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@p := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
if @a == 0 then goto $zero else goto $nonzero
$zero
@b := @a
goto $join
$nonzero
@c := @a
@v := 23
field Foo.intField @p := @v as I
goto $join
$join
@d := field Foo.intField @o as I
return

View File

@ -0,0 +1,18 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@p := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
if @a == 0 then goto $zero else goto $nonzero
$zero
@b := field Foo.intField @o as I
goto $join
$nonzero
@c := field Foo.intField @o as I
@v := 23
field Foo.intField @p := @v as I
goto $join
$join
@d := field Foo.intField @o as I
return

View File

@ -0,0 +1,8 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
invokeStatic `Foo.getFoo()LFoo;`
@b := field Foo.intField @o as I
return

View File

@ -0,0 +1,8 @@
var @this as this
$start
@o := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
invokeStatic `Foo.getFoo()LFoo;`
@b := field Foo.intField @o as I
return

View File

@ -0,0 +1,22 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := new Foo
@a := field Foo.intField @o as I
@b := field Foo.intField @p as I
@c := field Foo.intField @q as I
if @a == 0 then goto $zero else goto $nonzero
$zero
goto $join
$nonzero
goto $join
$join
@j := phi @o from $zero, @p from $nonzero
@v := 23
field Foo.intField @j := @v as I
@a1 := field Foo.intField @o as I
@b1 := field Foo.intField @p as I
@c1 := @c
return

View File

@ -0,0 +1,22 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := new Foo
@a := field Foo.intField @o as I
@b := field Foo.intField @p as I
@c := field Foo.intField @q as I
if @a == 0 then goto $zero else goto $nonzero
$zero
goto $join
$nonzero
goto $join
$join
@j := phi @o from $zero, @p from $nonzero
@v := 23
field Foo.intField @j := @v as I
@a1 := field Foo.intField @o as I
@b1 := field Foo.intField @p as I
@c1 := field Foo.intField @q as I
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := new Foo
@a := field Foo.intField @o as I
@b := @a
@c := 23
field Foo.intField @o := @c as I
@d := @c
return

View File

@ -0,0 +1,10 @@
var @this as this
$start
@o := new Foo
@a := field Foo.intField @o as I
@b := field Foo.intField @o as I
@c := 23
field Foo.intField @o := @c as I
@d := field Foo.intField @o as I
return

View File

@ -0,0 +1,15 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
@b := field Foo.intField @p as I
@c := field Foo.intField @q as I
@v := 23
field Foo.intField @q := @v as I
@a1 := field Foo.intField @o as I
@b1 := field Foo.intField @p as I
@c1 := @v
return

View File

@ -0,0 +1,15 @@
var @this as this
$start
@o := new Foo
@p := new Foo
@q := invokeStatic `Foo.getFoo()LFoo;`
@a := field Foo.intField @o as I
@b := field Foo.intField @p as I
@c := field Foo.intField @q as I
@v := 23
field Foo.intField @q := @v as I
@a1 := field Foo.intField @o as I
@b1 := field Foo.intField @p as I
@c1 := field Foo.intField @q as I
return

View File

@ -0,0 +1,7 @@
var @this as this
$start
@o := new Foo
@a := field Foo.volatileField @o as I
@b := field Foo.volatileField @o as I
return

View File

@ -0,0 +1,7 @@
var @this as this
$start
@o := new Foo
@a := field Foo.volatileField @o as I
@b := field Foo.volatileField @o as I
return