diff --git a/core/src/main/java/org/teavm/common/GraphBuilder.java b/core/src/main/java/org/teavm/common/GraphBuilder.java index 0d639aad5..f5d7055c6 100644 --- a/core/src/main/java/org/teavm/common/GraphBuilder.java +++ b/core/src/main/java/org/teavm/common/GraphBuilder.java @@ -22,10 +22,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -/** - * - * @author Alexey Andreev - */ public class GraphBuilder { private GraphImpl builtGraph; private List addedEdges = new ArrayList<>(); diff --git a/core/src/main/java/org/teavm/model/analysis/EscapeAnalysis.java b/core/src/main/java/org/teavm/model/analysis/EscapeAnalysis.java new file mode 100644 index 000000000..940396f26 --- /dev/null +++ b/core/src/main/java/org/teavm/model/analysis/EscapeAnalysis.java @@ -0,0 +1,367 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model.analysis; + +import com.carrotsearch.hppc.IntArrayDeque; +import com.carrotsearch.hppc.IntDeque; +import com.carrotsearch.hppc.IntOpenHashSet; +import com.carrotsearch.hppc.IntSet; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.teavm.common.DisjointSet; +import org.teavm.common.Graph; +import org.teavm.common.GraphBuilder; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.Incoming; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Phi; +import org.teavm.model.Program; +import org.teavm.model.TryCatchBlock; +import org.teavm.model.Variable; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.BinaryBranchingInstruction; +import org.teavm.model.instructions.BranchingInstruction; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.CloneArrayInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.GetElementInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.IsInstanceInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.MonitorExitInstruction; +import org.teavm.model.instructions.NullCheckInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.instructions.PutElementInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.instructions.UnwrapArrayInstruction; +import org.teavm.model.util.InstructionTransitionExtractor; +import org.teavm.model.util.LivenessAnalyzer; +import org.teavm.model.util.UsageExtractor; + +public class EscapeAnalysis { + private ClassReaderSource classSource; + private int[] definitionClasses; + private boolean[] escapingVars; + private FieldReference[][] fields; + + public EscapeAnalysis(ClassReaderSource classSource) { + this.classSource = classSource; + } + + public void analyze(Program program, MethodReference methodReference) { + InstructionEscapeVisitor visitor = new InstructionEscapeVisitor(program.variableCount(), classSource); + for (int i = 0; i <= methodReference.parameterCount(); ++i) { + visitor.escapingVars[i] = true; + } + + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction insn : block) { + insn.acceptVisitor(visitor); + } + if (block.getExceptionVariable() != null) { + visitor.escapingVars[block.getExceptionVariable().getIndex()] = true; + } + } + + + definitionClasses = visitor.definitionClasses.pack(program.variableCount()); + escapingVars = new boolean[program.variableCount()]; + for (int i = 0; i < program.variableCount(); ++i) { + if (visitor.escapingVars[i]) { + escapingVars[definitionClasses[i]] = true; + } + } + analyzePhis(program); + + fields = packFields(visitor.fields); + } + + public boolean escapes(int var) { + return escapingVars[definitionClasses[var]]; + } + + public FieldReference[] getFields(int var) { + FieldReference[] varFields = fields[definitionClasses[var]]; + return varFields != null ? varFields.clone() : null; + } + + private void analyzePhis(Program program) { + LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(); + livenessAnalyzer.analyze(program); + + GraphBuilder graphBuilder = new GraphBuilder(program.variableCount()); + IntDeque queue = new IntArrayDeque(); + for (BasicBlock block : program.getBasicBlocks()) { + IntSet sharedIncomingVars = new IntOpenHashSet(); + BitSet usedVars = getUsedVarsInBlock(livenessAnalyzer, block); + for (Phi phi : block.getPhis()) { + for (Incoming incoming : phi.getIncomings()) { + int var = incoming.getValue().getIndex(); + graphBuilder.addEdge(var, phi.getReceiver().getIndex()); + if (escapingVars[definitionClasses[var]] || !sharedIncomingVars.add(var) || usedVars.get(var)) { + queue.addLast(var); + } + } + } + } + Graph graph = graphBuilder.build(); + + IntSet visited = new IntOpenHashSet(); + while (!queue.isEmpty()) { + int var = queue.removeFirst(); + if (visited.add(var)) { + escapingVars[definitionClasses[var]] = true; + for (int successor : graph.outgoingEdges(var)) { + queue.addLast(successor); + } + } + } + } + + private BitSet getUsedVarsInBlock(LivenessAnalyzer liveness, BasicBlock block) { + BitSet usedVars = new BitSet(); + InstructionTransitionExtractor transitionExtractor = new InstructionTransitionExtractor(); + block.getLastInstruction().acceptVisitor(transitionExtractor); + for (BasicBlock successor : transitionExtractor.getTargets()) { + usedVars.or(liveness.liveIn(successor.getIndex())); + } + for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { + usedVars.or(liveness.liveIn(tryCatch.getHandler().getIndex())); + } + + UsageExtractor useExtractor = new UsageExtractor(); + for (Instruction instruction : block) { + instruction.acceptVisitor(useExtractor); + for (Variable variable : useExtractor.getUsedVariables()) { + usedVars.set(variable.getIndex()); + } + } + return usedVars; + } + + private FieldReference[][] packFields(List> fields) { + List> joinedFields = new ArrayList<>(Collections.nCopies(fields.size(), null)); + + for (int i = 0; i < fields.size(); ++i) { + if (fields.get(i) == null) { + continue; + } + int j = definitionClasses[i]; + Set fieldSet = joinedFields.get(j); + if (fieldSet == null) { + fieldSet = new LinkedHashSet<>(); + joinedFields.set(j, fieldSet); + } + fieldSet.addAll(fields.get(i)); + } + + FieldReference[][] packedFields = new FieldReference[fields.size()][]; + for (int i = 0; i < packedFields.length; ++i) { + if (joinedFields.get(i) != null) { + packedFields[i] = joinedFields.get(i).toArray(new FieldReference[0]); + } + } + return packedFields; + } + + private static class InstructionEscapeVisitor extends AbstractInstructionVisitor { + ClassReaderSource classSource; + DisjointSet definitionClasses; + boolean[] escapingVars; + List> fields; + + public InstructionEscapeVisitor(int variableCount, ClassReaderSource classSource) { + this.classSource = classSource; + fields = new ArrayList<>(Collections.nCopies(variableCount, null)); + definitionClasses = new DisjointSet(); + for (int i = 0; i < variableCount; ++i) { + definitionClasses.create(); + } + escapingVars = new boolean[variableCount]; + } + + @Override + public void visit(ConstructInstruction insn) { + ClassReader cls = classSource.get(insn.getType()); + if (cls == null) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + + while (cls != null) { + for (FieldReader field : cls.getFields()) { + addField(insn.getReceiver(), field.getReference()); + } + cls = cls.getParent() != null ? classSource.get(cls.getParent()) : null; + } + } + + @Override + public void visit(NullConstantInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + + @Override + public void visit(ClassConstantInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + + @Override + public void visit(StringConstantInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + + @Override + public void visit(CloneArrayInstruction insn) { + escapingVars[insn.getArray().getIndex()] = true; + escapingVars[insn.getReceiver().getIndex()] = true; + } + + @Override + public void visit(UnwrapArrayInstruction insn) { + definitionClasses.union(insn.getReceiver().getIndex(), insn.getArray().getIndex()); + } + + @Override + public void visit(AssignInstruction insn) { + definitionClasses.union(insn.getReceiver().getIndex(), insn.getAssignee().getIndex()); + } + + @Override + public void visit(CastInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + escapingVars[insn.getValue().getIndex()] = true; + } + + @Override + public void visit(ExitInstruction insn) { + if (insn.getValueToReturn() != null) { + escapingVars[insn.getValueToReturn().getIndex()] = true; + } + } + + @Override + public void visit(RaiseInstruction insn) { + escapingVars[insn.getException().getIndex()] = true; + } + + @Override + public void visit(GetFieldInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + addField(insn.getInstance(), insn.getField()); + } + + @Override + public void visit(PutFieldInstruction insn) { + escapingVars[insn.getValue().getIndex()] = true; + addField(insn.getInstance(), insn.getField()); + } + + private void addField(Variable instance, FieldReference field) { + if (instance == null) { + return; + } + Set fieldSet = fields.get(instance.getIndex()); + if (fieldSet == null) { + fieldSet = new LinkedHashSet<>(); + fields.set(instance.getIndex(), fieldSet); + } + fieldSet.add(field); + } + + @Override + public void visit(GetElementInstruction insn) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + + @Override + public void visit(PutElementInstruction insn) { + escapingVars[insn.getValue().getIndex()] = true; + } + + @Override + public void visit(InvokeInstruction insn) { + if (insn.getInstance() != null) { + escapingVars[insn.getInstance().getIndex()] = true; + } + for (Variable arg : insn.getArguments()) { + escapingVars[arg.getIndex()] = true; + } + if (insn.getReceiver() != null) { + escapingVars[insn.getReceiver().getIndex()] = true; + } + } + + @Override + public void visit(IsInstanceInstruction insn) { + escapingVars[insn.getValue().getIndex()] = true; + } + + @Override + public void visit(NullCheckInstruction insn) { + definitionClasses.union(insn.getValue().getIndex(), insn.getReceiver().getIndex()); + } + + @Override + public void visit(MonitorEnterInstruction insn) { + escapingVars[insn.getObjectRef().getIndex()] = true; + } + + @Override + public void visit(MonitorExitInstruction insn) { + escapingVars[insn.getObjectRef().getIndex()] = true; + } + + @Override + public void visit(BranchingInstruction insn) { + switch (insn.getCondition()) { + case NULL: + case NOT_NULL: + escapingVars[insn.getOperand().getIndex()] = true; + break; + default: + break; + } + } + + @Override + public void visit(BinaryBranchingInstruction insn) { + switch (insn.getCondition()) { + case REFERENCE_EQUAL: + case REFERENCE_NOT_EQUAL: + escapingVars[insn.getFirstOperand().getIndex()] = true; + escapingVars[insn.getSecondOperand().getIndex()] = true; + break; + default: + break; + } + } + } +} diff --git a/core/src/main/java/org/teavm/model/instructions/NullConstantInstruction.java b/core/src/main/java/org/teavm/model/instructions/NullConstantInstruction.java index 3cdcca69f..ac6cd361d 100644 --- a/core/src/main/java/org/teavm/model/instructions/NullConstantInstruction.java +++ b/core/src/main/java/org/teavm/model/instructions/NullConstantInstruction.java @@ -18,10 +18,6 @@ package org.teavm.model.instructions; import org.teavm.model.Instruction; import org.teavm.model.Variable; -/** - * - * @author Alexey Andreev - */ public class NullConstantInstruction extends Instruction { private Variable receiver; diff --git a/core/src/main/java/org/teavm/model/optimization/ScalarReplacement.java b/core/src/main/java/org/teavm/model/optimization/ScalarReplacement.java new file mode 100644 index 000000000..13ca652e3 --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/ScalarReplacement.java @@ -0,0 +1,245 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model.optimization; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.Incoming; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Phi; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.analysis.EscapeAnalysis; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.DoubleConstantInstruction; +import org.teavm.model.instructions.FloatConstantInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.IntegerConstantInstruction; +import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.util.PhiUpdater; + +public class ScalarReplacement implements MethodOptimization { + @Override + public boolean optimize(MethodOptimizationContext context, Program program) { + boolean changed = false; + while (performOnce(context, program)) { + changed = true; + } + return changed; + } + + private boolean performOnce(MethodOptimizationContext context, Program program) { + List> fieldMappings = new ArrayList<>( + Collections.nCopies(program.variableCount(), null)); + + MethodReference methodReference = context.getMethod().getReference(); + EscapeAnalysis escapeAnalysis = new EscapeAnalysis(context.getClassSource()); + escapeAnalysis.analyze(program, methodReference); + boolean canPerform = false; + for (int i = 0; i < fieldMappings.size(); ++i) { + FieldReference[] fields = escapeAnalysis.getFields(i); + if (!escapeAnalysis.escapes(i) && fields != null) { + Map fieldMapping = new LinkedHashMap<>(); + for (FieldReference field : fields) { + fieldMapping.put(field, program.createVariable()); + } + fieldMappings.set(i, fieldMapping); + canPerform = true; + } + } + if (!canPerform) { + return false; + } + + ScalarReplacementVisitor visitor = new ScalarReplacementVisitor(escapeAnalysis, context.getClassSource(), + fieldMappings); + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + instruction.acceptVisitor(visitor); + } + List additionalPhis = new ArrayList<>(); + for (int i = 0; i < block.getPhis().size(); ++i) { + Phi phi = block.getPhis().get(i); + if (escapeAnalysis.escapes(phi.getReceiver().getIndex())) { + continue; + } + + FieldReference[] fields = escapeAnalysis.getFields(phi.getReceiver().getIndex()); + if (fields == null) { + continue; + } + + for (FieldReference field : fields) { + boolean allIncomingsInitialized = true; + for (Incoming incoming : phi.getIncomings()) { + if (fieldMappings.get(incoming.getValue().getIndex()).get(field) == null) { + allIncomingsInitialized = false; + } + } + + if (!allIncomingsInitialized) { + continue; + } + Phi phiReplacement = new Phi(); + phiReplacement.setReceiver(fieldMappings.get(phi.getReceiver().getIndex()).get(field)); + + for (Incoming incoming : phi.getIncomings()) { + Incoming incomingReplacement = new Incoming(); + incomingReplacement.setSource(incoming.getSource()); + incomingReplacement.setValue(fieldMappings.get(incoming.getValue().getIndex()).get(field)); + phiReplacement.getIncomings().add(incomingReplacement); + } + + additionalPhis.add(phi); + } + block.getPhis().remove(i--); + } + block.getPhis().addAll(additionalPhis); + } + + Variable[] arguments = new Variable[methodReference.parameterCount() + 1]; + for (int i = 0; i < arguments.length; ++i) { + arguments[i] = program.variableAt(i); + } + new PhiUpdater().updatePhis(program, arguments); + + return true; + } + + static class ScalarReplacementVisitor extends AbstractInstructionVisitor { + private EscapeAnalysis escapeAnalysis; + private ClassReaderSource classSource; + private List> fieldMappings; + + public ScalarReplacementVisitor(EscapeAnalysis escapeAnalysis, ClassReaderSource classSource, + List> fieldMappings) { + this.escapeAnalysis = escapeAnalysis; + this.classSource = classSource; + this.fieldMappings = fieldMappings; + } + + @Override + public void visit(ConstructInstruction insn) { + int var = insn.getReceiver().getIndex(); + if (!escapeAnalysis.escapes(var) && escapeAnalysis.getFields(var) != null) { + for (FieldReference fieldRef : escapeAnalysis.getFields(var)) { + FieldReader field = classSource.resolve(fieldRef); + Variable receiver = fieldMappings.get(insn.getReceiver().getIndex()).get(fieldRef); + Instruction initializer = generateDefaultValue(field.getType(), receiver); + initializer.setLocation(initializer.getLocation()); + insn.insertPrevious(initializer); + } + insn.delete(); + } + } + + @Override + public void visit(GetFieldInstruction insn) { + if (insn.getInstance() != null && !escapeAnalysis.escapes(insn.getInstance().getIndex())) { + Variable var = fieldMappings.get(insn.getInstance().getIndex()).get(insn.getField()); + AssignInstruction assignment = new AssignInstruction(); + assignment.setReceiver(insn.getReceiver()); + assignment.setAssignee(var); + assignment.setLocation(insn.getLocation()); + insn.replace(assignment); + } + } + + @Override + public void visit(PutFieldInstruction insn) { + if (insn.getInstance() != null && !escapeAnalysis.escapes(insn.getInstance().getIndex())) { + Variable var = fieldMappings.get(insn.getInstance().getIndex()).get(insn.getField()); + AssignInstruction assignment = new AssignInstruction(); + assignment.setReceiver(var); + assignment.setAssignee(insn.getValue()); + assignment.setLocation(insn.getLocation()); + insn.replace(assignment); + } + } + + @Override + public void visit(AssignInstruction insn) { + if (escapeAnalysis.escapes(insn.getAssignee().getIndex())) { + return; + } + + FieldReference[] fields = escapeAnalysis.getFields(insn.getReceiver().getIndex()); + if (fields == null) { + return; + } + for (FieldReference field : fields) { + Variable assignee = fieldMappings.get(insn.getAssignee().getIndex()).get(field); + if (assignee == null) { + continue; + } + Variable receiver = fieldMappings.get(insn.getReceiver().getIndex()).get(field); + AssignInstruction assignment = new AssignInstruction(); + assignment.setReceiver(receiver); + assignment.setAssignee(assignee); + assignment.setLocation(insn.getLocation()); + insn.insertPrevious(assignment); + } + insn.delete(); + } + + private Instruction generateDefaultValue(ValueType type, Variable receiver) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + case BYTE: + case SHORT: + case CHARACTER: + case INTEGER: { + IntegerConstantInstruction insn = new IntegerConstantInstruction(); + insn.setReceiver(receiver); + return insn; + } + case LONG: { + LongConstantInstruction insn = new LongConstantInstruction(); + insn.setReceiver(receiver); + return insn; + } + case FLOAT: { + FloatConstantInstruction insn = new FloatConstantInstruction(); + insn.setReceiver(receiver); + return insn; + } + case DOUBLE: { + DoubleConstantInstruction insn = new DoubleConstantInstruction(); + insn.setReceiver(receiver); + return insn; + } + } + } + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(receiver); + return insn; + } + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 634bb2521..6e5e1d3e6 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -60,6 +60,7 @@ import org.teavm.model.optimization.LoopInvariantMotion; import org.teavm.model.optimization.MethodOptimization; import org.teavm.model.optimization.MethodOptimizationContext; import org.teavm.model.optimization.RedundantJumpElimination; +import org.teavm.model.optimization.ScalarReplacement; import org.teavm.model.optimization.UnreachableBasicBlockElimination; import org.teavm.model.optimization.UnusedVariableElimination; import org.teavm.model.text.ListingBuilder; @@ -551,6 +552,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { optimizations.add(new RedundantJumpElimination()); optimizations.add(new ArrayUnwrapMotion()); if (optimizationLevel.ordinal() >= TeaVMOptimizationLevel.ADVANCED.ordinal()) { + optimizations.add(new ScalarReplacement()); //optimizations.add(new LoopInversion()); optimizations.add(new LoopInvariantMotion()); }