From 81cc3c156e8e9698f7aae1962bb4af93f9fa5138 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 30 Oct 2019 16:09:17 +0300 Subject: [PATCH] Add optimization that eliminates repeated access to fields --- .../teavm/model/analysis/AliasAnalysis.java | 244 +++++++++++ .../MethodOptimizationContext.java | 3 + .../RepeatedFieldReadElimination.java | 404 ++++++++++++++++++ core/src/main/java/org/teavm/vm/TeaVM.java | 11 +- .../RepeatedFieldReadEliminationTest.java | 151 +++++++ .../test/ScalarReplacementTest.java | 6 + ...lwaysInvalidateExternalObject.expected.txt | 13 + ...lwaysInvalidateExternalObject.original.txt | 13 + .../fieldStoreInDifferentObjects.expected.txt | 10 + .../fieldStoreInDifferentObjects.original.txt | 10 + .../fieldStoreInvalidates.expected.txt | 10 + .../fieldStoreInvalidates.original.txt | 10 + .../invalidateInOneBranch.expected.txt | 18 + .../invalidateInOneBranch.original.txt | 18 + .../invocationInvalidates.expected.txt | 8 + .../invocationInvalidates.original.txt | 8 + .../mergeInAliasAnalysis.expected.txt | 22 + .../mergeInAliasAnalysis.original.txt | 22 + .../simple.expected.txt | 10 + .../simple.original.txt | 10 + ...gExternalObjectInvalidatesAll.expected.txt | 15 + ...gExternalObjectInvalidatesAll.original.txt | 15 + .../volatileField.expected.txt | 7 + .../volatileField.original.txt | 7 + 24 files changed, 1044 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java create mode 100644 core/src/main/java/org/teavm/model/optimization/RepeatedFieldReadElimination.java create mode 100644 core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.original.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.expected.txt create mode 100644 core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.original.txt diff --git a/core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java b/core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java new file mode 100644 index 000000000..c281b871c --- /dev/null +++ b/core/src/main/java/org/teavm/model/analysis/AliasAnalysis.java @@ -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); + } + } + } +} diff --git a/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java b/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java index d29e74ebc..0151f6e2c 100644 --- a/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java +++ b/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java @@ -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(); } diff --git a/core/src/main/java/org/teavm/model/optimization/RepeatedFieldReadElimination.java b/core/src/main/java/org/teavm/model/optimization/RepeatedFieldReadElimination.java new file mode 100644 index 000000000..e86c8bb5f --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/RepeatedFieldReadElimination.java @@ -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>> 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> cacheVars = new IntObjectHashMap<>(); + Deque 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> invalidFieldsByBlock = invalidFields.get(block.getIndex()); + if (invalidFieldsByBlock != null) { + for (IntObjectCursor> 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 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 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 removedFieldsByInstance = state.removedCacheFields.get(instance); + if (removedFieldsByInstance == null || !removedFieldsByInstance.containsKey(field)) { + Set 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> instanceCursor : cacheVars) { + int instance = instanceCursor.key; + ObjectIntMap cacheVarsByInstance = instanceCursor.value; + for (ObjectIntCursor 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 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> 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 addedFieldsByInstance = state.addedCacheFields.get(instance); + if (addedFieldsByInstance == null || !addedFieldsByInstance.contains(field)) { + ObjectIntMap 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> instanceCursor : state.addedCacheFields) { + int instance = instanceCursor.key; + ObjectIntMap cachedFieldsByInstances = cacheVars.get(instance); + if (cachedFieldsByInstances != null) { + for (FieldReference field : instanceCursor.value) { + cachedFieldsByInstances.remove(field); + } + if (cachedFieldsByInstances.isEmpty()) { + cacheVars.remove(instance); + } + } + } + + for (IntObjectCursor> instanceCursor : state.removedCacheFields) { + int instance = instanceCursor.key; + ObjectIntMap cacheVarsByInstance = cacheVars.get(instance); + if (cacheVarsByInstance == null) { + cacheVarsByInstance = new ObjectIntHashMap<>(); + cacheVars.put(instance, cacheVarsByInstance); + } + for (ObjectIntCursor 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> removedCacheFields = new IntObjectHashMap<>(); + IntObjectMap> 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> invalidFieldsByBlock = invalidFields.get(target); + if (invalidFieldsByBlock == null) { + invalidFieldsByBlock = new IntObjectHashMap<>(); + invalidFields.set(target, invalidFieldsByBlock); + } + + Set 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; + } + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index caac2a797..66a671807 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -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 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()) { diff --git a/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java b/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java new file mode 100644 index 000000000..da5c62bda --- /dev/null +++ b/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java @@ -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); + } +} diff --git a/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java b/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java index 2087e4d96..4817fc3c7 100644 --- a/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java +++ b/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java @@ -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); diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.expected.txt new file mode 100644 index 000000000..b3f6c38aa --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.original.txt new file mode 100644 index 000000000..39e90bc35 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/alwaysInvalidateExternalObject.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.expected.txt new file mode 100644 index 000000000..c397ced3b --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.original.txt new file mode 100644 index 000000000..ec17abe55 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInDifferentObjects.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.expected.txt new file mode 100644 index 000000000..7730aee59 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.original.txt new file mode 100644 index 000000000..7730aee59 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/fieldStoreInvalidates.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.expected.txt new file mode 100644 index 000000000..a32c82532 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.original.txt new file mode 100644 index 000000000..87454d669 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invalidateInOneBranch.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.expected.txt new file mode 100644 index 000000000..301c01a24 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.original.txt new file mode 100644 index 000000000..301c01a24 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/invocationInvalidates.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.expected.txt new file mode 100644 index 000000000..8803b215a --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.original.txt new file mode 100644 index 000000000..8c6ef8fa3 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/mergeInAliasAnalysis.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.expected.txt new file mode 100644 index 000000000..400d18604 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.original.txt new file mode 100644 index 000000000..8c7123809 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/simple.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.expected.txt new file mode 100644 index 000000000..2fe5de7aa --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.original.txt new file mode 100644 index 000000000..80a2b0b73 --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/updatingExternalObjectInvalidatesAll.original.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.expected.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.expected.txt new file mode 100644 index 000000000..3cf0ac3eb --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.expected.txt @@ -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 \ No newline at end of file diff --git a/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.original.txt b/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.original.txt new file mode 100644 index 000000000..3cf0ac3eb --- /dev/null +++ b/core/src/test/resources/model/optimization/repeated-field-read-elimination/volatileField.original.txt @@ -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 \ No newline at end of file