mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Add optimization that eliminates repeated access to fields
This commit is contained in:
parent
511160935f
commit
81cc3c156e
244
core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java
Normal file
244
core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user