From 4de1c51e1aad906754851806a341d89b68175dab Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 12 Feb 2019 19:30:57 +0300 Subject: [PATCH] Improve inliner: use simlified heuristics in advanced optimization mode, remove methods that were fully inlined --- .../org/teavm/classlib/java/lang/TObject.java | 2 +- .../teavm/ast/decompilation/Decompiler.java | 4 + .../backend/javascript/JavaScriptTarget.java | 16 +- .../javascript/rendering/Renderer.java | 6 + .../javascript/spi/GeneratorContext.java | 3 + .../dependency/DependencyGraphBuilder.java | 2 +- .../dependency/FastInstructionAnalyzer.java | 2 +- .../teavm/dependency/MethodDependency.java | 13 ++ .../dependency/MethodDependencyInfo.java | 2 + .../optimization/DefaultInliningStrategy.java | 136 +++++++++++++ .../teavm/model/optimization/Inlining.java | 190 ++++++++++++------ .../model/optimization/InliningContext.java | 24 +++ .../model/optimization/InliningStep.java | 23 +++ .../model/optimization/InliningStrategy.java | 23 +++ core/src/main/java/org/teavm/vm/TeaVM.java | 74 ++++--- .../platform/plugin/AsyncMethodGenerator.java | 22 +- .../platform/plugin/PlatformGenerator.java | 23 ++- 17 files changed, 461 insertions(+), 104 deletions(-) create mode 100644 core/src/main/java/org/teavm/model/optimization/DefaultInliningStrategy.java create mode 100644 core/src/main/java/org/teavm/model/optimization/InliningContext.java create mode 100644 core/src/main/java/org/teavm/model/optimization/InliningStep.java create mode 100644 core/src/main/java/org/teavm/model/optimization/InliningStrategy.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index 28e48f318..9a81ffc9b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -309,7 +309,7 @@ public class TObject { @Async private native void waitImpl(long timeout, int nanos) throws TInterruptedException; - public final void waitImpl(long timeout, int nanos, final AsyncCallback callback) { + public final void waitImpl(long timeout, int nanos, AsyncCallback callback) { final NotifyListenerImpl listener = new NotifyListenerImpl(this, callback, monitor.count); monitor.notifyListeners.add(listener); TThread.currentThread().interruptHandler = listener; diff --git a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java index 5ee4ab010..2df701ff9 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java +++ b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java @@ -239,6 +239,10 @@ public class Decompiler { || methodsToSkip.contains(method.getReference())) { continue; } + if (!method.hasModifier(ElementModifier.NATIVE) && method.getProgram() == null) { + continue; + } + MethodNode methodNode = decompile(method); clsNode.getMethods().add(methodNode); } diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index dde24ec30..f9205db94 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -60,6 +60,8 @@ import org.teavm.cache.MethodNodeCache; import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.SourceLocation; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyType; @@ -100,6 +102,8 @@ import org.teavm.vm.spi.TeaVMHostExtension; public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private static final NumberFormat STATS_NUM_FORMAT = new DecimalFormat("#,##0"); private static final NumberFormat STATS_PERCENT_FORMAT = new DecimalFormat("0.000 %"); + private static final MethodReference CURRENT_THREAD = new MethodReference(Thread.class, + "currentThread", Thread.class); private TeaVMTargetController controller; private boolean minifying = true; @@ -116,7 +120,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private ClassInitializerInsertionTransformer clinitInsertionTransformer; private List customVirtualMethods = new ArrayList<>(); private boolean classScoped; - @Override public List getTransformers() { return Collections.emptyList(); @@ -261,6 +264,17 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { if (stackTraceIncluded) { includeStackTraceMethods(dependencyAnalyzer); } + + dependencyAnalyzer.addDependencyListener(new AbstractDependencyListener() { + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getReference().equals(CURRENT_THREAD)) { + method.use(); + } + agent.linkMethod(new MethodReference(Thread.class, "setCurrentThread", Thread.class, void.class)) + .use(); + } + }); } public static void includeStackTraceMethods(DependencyAnalyzer dependencyAnalyzer) { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index b3aec0663..752128d6d 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -45,6 +45,7 @@ import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.common.ServiceRepository; import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter; +import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.MethodDependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassReader; @@ -847,6 +848,11 @@ public class Renderer implements RenderingManager { this.statementRenderer = statementRenderer; } + @Override + public DependencyInfo getDependency() { + return context.getDependencyInfo(); + } + @Override public void visit(NativeMethodNode methodNode) { try { diff --git a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java index fa612086a..10285a51d 100644 --- a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java @@ -18,6 +18,7 @@ package org.teavm.backend.javascript.spi; import java.util.Properties; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.common.ServiceRepository; +import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassReaderSource; import org.teavm.model.ListableClassReaderSource; @@ -43,6 +44,8 @@ public interface GeneratorContext extends ServiceRepository { Diagnostics getDiagnostics(); + DependencyInfo getDependency(); + void typeToClassString(SourceWriter writer, ValueType type); void useLongLibrary(); diff --git a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index c613ccf7b..ae176ff44 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -337,7 +337,7 @@ class DependencyGraphBuilder { } MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); methodDep.addLocation(callLocation); - methodDep.use(); + methodDep.use(false); if (methodDep.isMissing()) { return; } diff --git a/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java index 9c7f2d261..c6ace2756 100644 --- a/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java @@ -40,7 +40,7 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { } MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); methodDep.addLocation(callLocation); - methodDep.use(); + methodDep.use(false); } @Override diff --git a/core/src/main/java/org/teavm/dependency/MethodDependency.java b/core/src/main/java/org/teavm/dependency/MethodDependency.java index 4acab4e8e..b68523316 100644 --- a/core/src/main/java/org/teavm/dependency/MethodDependency.java +++ b/core/src/main/java/org/teavm/dependency/MethodDependency.java @@ -35,6 +35,7 @@ public class MethodDependency implements MethodDependencyInfo { MethodHolder method; private MethodReference reference; boolean used; + boolean external; DependencyPlugin dependencyPlugin; boolean dependencyPluginAttached; private List locationListeners; @@ -151,11 +152,23 @@ public class MethodDependency implements MethodDependencyInfo { } public void use() { + use(true); + } + + void use(boolean external) { if (!used) { used = true; if (!isMissing()) { dependencyAnalyzer.scheduleMethodAnalysis(this); } } + if (external) { + this.external = true; + } + } + + @Override + public boolean isCalled() { + return external; } } diff --git a/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java b/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java index 71e2398b3..fc9bbc2d1 100644 --- a/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java +++ b/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java @@ -34,5 +34,7 @@ public interface MethodDependencyInfo { boolean isUsed(); + boolean isCalled(); + boolean isMissing(); } diff --git a/core/src/main/java/org/teavm/model/optimization/DefaultInliningStrategy.java b/core/src/main/java/org/teavm/model/optimization/DefaultInliningStrategy.java new file mode 100644 index 000000000..8314f038c --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/DefaultInliningStrategy.java @@ -0,0 +1,136 @@ +/* + * 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 java.util.List; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.model.instructions.BinaryBranchingCondition; +import org.teavm.model.instructions.BranchingCondition; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.SwitchTableEntryReader; + +public class DefaultInliningStrategy implements InliningStrategy { + private final int complexityThreshold; + private final int depthThreshold; + private final boolean onceUsedOnly; + + public DefaultInliningStrategy(int complexityThreshold, int depthThreshold, boolean onceUsedOnly) { + this.complexityThreshold = complexityThreshold; + this.depthThreshold = depthThreshold; + this.onceUsedOnly = onceUsedOnly; + } + + @Override + public InliningStep start(MethodReference method, ProgramReader program) { + int complexity = getComplexity(program); + if (complexity > complexityThreshold) { + return null; + } + + ComplexityHolder complexityHolder = new ComplexityHolder(); + complexityHolder.complexity = complexity; + return new InliningStepImpl(complexityHolder); + } + + static int getComplexity(ProgramReader program) { + int complexity = 0; + ComplexityCounter counter = new ComplexityCounter(); + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlockReader block = program.basicBlockAt(i); + counter.complexity = 0; + block.readAllInstructions(counter); + complexity += block.instructionCount() + counter.complexity; + } + return complexity; + } + + class InliningStepImpl implements InliningStep { + ComplexityHolder complexityHolder; + + InliningStepImpl(ComplexityHolder complexityHolder) { + this.complexityHolder = complexityHolder; + } + + @Override + public InliningStep tryInline(MethodReference method, ProgramReader program, InliningContext context) { + if (context.getDepth() > depthThreshold || (onceUsedOnly && !context.isUsedOnce(method))) { + return null; + } + + int complexity = getComplexity(program); + if (complexityHolder.complexity + complexity > complexityThreshold) { + return null; + } + + complexityHolder.complexity += complexity; + return new InliningStepImpl(complexityHolder); + } + } + + static class ComplexityHolder { + int complexity; + } + + static class ComplexityCounter extends AbstractInstructionReader { + int complexity; + + @Override + public void nop() { + complexity--; + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + complexity++; + if (instance != null) { + complexity++; + } + } + + @Override + public void choose(VariableReader condition, List table, + BasicBlockReader defaultTarget) { + complexity += 3; + } + + @Override + public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent, + BasicBlockReader alternative) { + complexity += 2; + } + + @Override + public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second, + BasicBlockReader consequent, BasicBlockReader alternative) { + complexity += 2; + } + + @Override + public void jump(BasicBlockReader target) { + complexity--; + } + + @Override + public void exit(VariableReader valueToReturn) { + complexity--; + } + } +} diff --git a/core/src/main/java/org/teavm/model/optimization/Inlining.java b/core/src/main/java/org/teavm/model/optimization/Inlining.java index 7e94a2fa2..2c769497c 100644 --- a/core/src/main/java/org/teavm/model/optimization/Inlining.java +++ b/core/src/main/java/org/teavm/model/optimization/Inlining.java @@ -16,54 +16,117 @@ package org.teavm.model.optimization; import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import com.carrotsearch.hppc.cursors.ObjectCursor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.teavm.dependency.DependencyInfo; import org.teavm.model.BasicBlock; +import org.teavm.model.BasicBlockReader; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; import org.teavm.model.Incoming; import org.teavm.model.Instruction; +import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Phi; import org.teavm.model.Program; +import org.teavm.model.ProgramReader; import org.teavm.model.TryCatchBlock; +import org.teavm.model.VariableReader; import org.teavm.model.analysis.ClassInference; +import org.teavm.model.instructions.AbstractInstructionReader; import org.teavm.model.instructions.AssignInstruction; -import org.teavm.model.instructions.BinaryBranchingInstruction; -import org.teavm.model.instructions.BranchingInstruction; -import org.teavm.model.instructions.EmptyInstruction; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.JumpInstruction; -import org.teavm.model.instructions.SwitchInstruction; import org.teavm.model.util.BasicBlockMapper; import org.teavm.model.util.InstructionVariableMapper; import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.TransitionExtractor; public class Inlining { - private static final int DEFAULT_THRESHOLD = 17; - private static final int MAX_DEPTH = 7; private IntArrayList depthsByBlock; private Set instructionsToSkip; private ClassHierarchy hierarchy; - private ClassReaderSource classes; + private ListableClassReaderSource classes; private DependencyInfo dependencyInfo; + private InliningStrategy strategy; + private MethodUsageCounter usageCounter; + private Set methodsUsedOnce = new HashSet<>(); - public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo) { + public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo, InliningStrategy strategy, + ListableClassReaderSource classes, Predicate externalMethods) { this.hierarchy = hierarchy; - this.classes = hierarchy.getClassSource(); + this.classes = classes; this.dependencyInfo = dependencyInfo; + this.strategy = strategy; + usageCounter = new MethodUsageCounter(externalMethods); + + for (String className : classes.getClassNames()) { + ClassReader cls = classes.get(className); + for (MethodReader method : cls.getMethods()) { + ProgramReader program = method.getProgram(); + if (program != null) { + usageCounter.currentMethod = method.getReference(); + for (BasicBlockReader block : program.getBasicBlocks()) { + block.readAllInstructions(usageCounter); + } + } + } + } + + for (ObjectCursor cursor : usageCounter.methodUsageCount.keys()) { + if (usageCounter.methodUsageCount.get(cursor.value) == 1) { + methodsUsedOnce.add(cursor.value); + } + } + } + + public List getOrder() { + List order = new ArrayList<>(); + Set visited = new HashSet<>(); + for (String className : classes.getClassNames()) { + ClassReader cls = classes.get(className); + for (MethodReader method : cls.getMethods()) { + if (method.getProgram() != null) { + computeOrder(method.getReference(), order, visited); + } + } + } + Collections.reverse(order); + return order; + } + + private void computeOrder(MethodReference method, List order, Set visited) { + if (!visited.add(method)) { + return; + } + Set invokedMethods = usageCounter.methodDependencies.get(method); + if (invokedMethods != null) { + for (MethodReference invokedMethod : invokedMethods) { + computeOrder(invokedMethod, order, visited); + } + } + order.add(method); + } + + public boolean hasUsages(MethodReference method) { + return usageCounter.methodUsageCount.getOrDefault(method, -1) != 0; } public void apply(Program program, MethodReference method) { @@ -73,7 +136,7 @@ public class Inlining { } instructionsToSkip = new HashSet<>(); - while (applyOnce(program)) { + while (applyOnce(program, method)) { devirtualize(program, method, dependencyInfo); } depthsByBlock = null; @@ -82,8 +145,12 @@ public class Inlining { new UnreachableBasicBlockEliminator().optimize(program); } - private boolean applyOnce(Program program) { - List plan = buildPlan(program, 0); + private boolean applyOnce(Program program, MethodReference method) { + InliningStep step = strategy.start(method, program); + if (step == null) { + return false; + } + List plan = buildPlan(program, -1, step); if (plan.isEmpty()) { return false; } @@ -98,6 +165,11 @@ public class Inlining { } private void execPlanEntry(Program program, PlanEntry planEntry, int offset) { + int usageCount = usageCounter.methodUsageCount.getOrDefault(planEntry.method, -1); + if (usageCount > 0) { + usageCounter.methodUsageCount.put(planEntry.method, usageCount - 1); + } + BasicBlock block = program.basicBlockAt(planEntry.targetBlock + offset); InvokeInstruction invoke = (InvokeInstruction) planEntry.targetInstruction; BasicBlock splitBlock = program.createBasicBlock(); @@ -219,24 +291,18 @@ public class Inlining { execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex()); } - private List buildPlan(Program program, int depth) { - if (depth >= MAX_DEPTH) { - return Collections.emptyList(); - } + private List buildPlan(Program program, int depth, InliningStep step) { List plan = new ArrayList<>(); - int ownComplexity = getComplexity(program); int originalDepth = depth; + ContextImpl context = new ContextImpl(); for (BasicBlock block : program.getBasicBlocks()) { if (!block.getTryCatchBlocks().isEmpty()) { continue; } - if (originalDepth == 0) { + if (originalDepth < 0) { depth = depthsByBlock.get(block.getIndex()); - if (depth >= MAX_DEPTH) { - continue; - } } for (Instruction insn : block) { @@ -254,27 +320,28 @@ public class Inlining { MethodReader invokedMethod = getMethod(invoke.getMethod()); if (invokedMethod == null || invokedMethod.getProgram() == null - || invokedMethod.getProgram().basicBlockCount() == 0) { + || invokedMethod.getProgram().basicBlockCount() == 0 + || invokedMethod.hasModifier(ElementModifier.SYNCHRONIZED)) { instructionsToSkip.add(insn); continue; } - Program invokedProgram = ProgramUtils.copy(invokedMethod.getProgram()); - int complexityThreshold = DEFAULT_THRESHOLD; - if (ownComplexity < DEFAULT_THRESHOLD) { - complexityThreshold += DEFAULT_THRESHOLD; - } - if (getComplexity(invokedProgram) > complexityThreshold) { + context.depth = depth; + InliningStep innerStep = step.tryInline(invokedMethod.getReference(), invokedMethod.getProgram(), + context); + if (innerStep == null) { instructionsToSkip.add(insn); continue; } + Program invokedProgram = ProgramUtils.copy(invokedMethod.getProgram()); PlanEntry entry = new PlanEntry(); entry.targetBlock = block.getIndex(); entry.targetInstruction = insn; entry.program = invokedProgram; - entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1)); + entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1, innerStep)); entry.depth = depth; + entry.method = invokedMethod.getReference(); plan.add(entry); } } @@ -288,34 +355,6 @@ public class Inlining { return cls != null ? cls.getMethod(methodRef.getDescriptor()) : null; } - private int getComplexity(Program program) { - int complexity = 0; - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - int nopCount = 0; - int invokeCount = 0; - for (Instruction insn : block) { - if (insn instanceof EmptyInstruction) { - nopCount++; - } else if (insn instanceof InvokeInstruction) { - InvokeInstruction invoke = (InvokeInstruction) insn; - invokeCount += invoke.getArguments().size(); - if (invoke.getInstance() != null) { - invokeCount++; - } - } - } - complexity += block.instructionCount() - nopCount + invokeCount; - Instruction lastInsn = block.getLastInstruction(); - if (lastInsn instanceof SwitchInstruction) { - complexity += 3; - } else if (lastInsn instanceof BinaryBranchingInstruction || lastInsn instanceof BranchingInstruction) { - complexity += 2; - } - } - return complexity; - } - private void devirtualize(Program program, MethodReference method, DependencyInfo dependencyInfo) { ClassInference inference = new ClassInference(dependencyInfo, hierarchy); inference.infer(program, method); @@ -350,8 +389,43 @@ public class Inlining { private class PlanEntry { int targetBlock; Instruction targetInstruction; + MethodReference method; Program program; int depth; final List innerPlan = new ArrayList<>(); } + + static class MethodUsageCounter extends AbstractInstructionReader { + ObjectIntMap methodUsageCount = new ObjectIntHashMap<>(); + Map> methodDependencies = new LinkedHashMap<>(); + Predicate externalMethods; + MethodReference currentMethod; + + MethodUsageCounter(Predicate externalMethods) { + this.externalMethods = externalMethods; + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + if (type == InvocationType.SPECIAL && !externalMethods.test(method)) { + methodUsageCount.put(method, methodUsageCount.get(method) + 1); + methodDependencies.computeIfAbsent(currentMethod, k -> new LinkedHashSet<>()).add(method); + } + } + } + + class ContextImpl implements InliningContext { + int depth; + + @Override + public boolean isUsedOnce(MethodReference method) { + return methodsUsedOnce.contains(method); + } + + @Override + public int getDepth() { + return depth; + } + } } diff --git a/core/src/main/java/org/teavm/model/optimization/InliningContext.java b/core/src/main/java/org/teavm/model/optimization/InliningContext.java new file mode 100644 index 000000000..f0094a20a --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/InliningContext.java @@ -0,0 +1,24 @@ +/* + * 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 org.teavm.model.MethodReference; + +public interface InliningContext { + boolean isUsedOnce(MethodReference method); + + int getDepth(); +} diff --git a/core/src/main/java/org/teavm/model/optimization/InliningStep.java b/core/src/main/java/org/teavm/model/optimization/InliningStep.java new file mode 100644 index 000000000..792005cfc --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/InliningStep.java @@ -0,0 +1,23 @@ +/* + * 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 org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; + +public interface InliningStep { + InliningStep tryInline(MethodReference method, ProgramReader program, InliningContext context); +} diff --git a/core/src/main/java/org/teavm/model/optimization/InliningStrategy.java b/core/src/main/java/org/teavm/model/optimization/InliningStrategy.java new file mode 100644 index 000000000..93dc18dc0 --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/InliningStrategy.java @@ -0,0 +1,23 @@ +/* + * 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 org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; + +public interface InliningStrategy { + InliningStep start(MethodReference method, ProgramReader program); +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 1bc008e22..2be2e7992 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -70,9 +70,11 @@ import org.teavm.model.ValueType; import org.teavm.model.optimization.ArrayUnwrapMotion; import org.teavm.model.optimization.ClassInitElimination; import org.teavm.model.optimization.ConstantConditionElimination; +import org.teavm.model.optimization.DefaultInliningStrategy; import org.teavm.model.optimization.Devirtualization; import org.teavm.model.optimization.GlobalValueNumbering; import org.teavm.model.optimization.Inlining; +import org.teavm.model.optimization.InliningStrategy; import org.teavm.model.optimization.LoopInvariantMotion; import org.teavm.model.optimization.MethodOptimization; import org.teavm.model.optimization.MethodOptimizationContext; @@ -430,8 +432,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { compileProgressValue = 0; compileProgressLimit = dependencyAnalyzer.getReachableClasses().size(); if (optimizationLevel == TeaVMOptimizationLevel.ADVANCED) { - compileProgressLimit *= 3; - } else if (optimizationLevel == TeaVMOptimizationLevel.FULL) { compileProgressLimit *= 4; } else { compileProgressLimit *= 2; @@ -538,34 +538,44 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } private void inline(ListableClassHolderSource classes) { - if (optimizationLevel != TeaVMOptimizationLevel.FULL) { + if (optimizationLevel != TeaVMOptimizationLevel.ADVANCED) { return; } - Map inlinedPrograms = new HashMap<>(); - Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyAnalyzer); - for (String className : classes.getClassNames()) { - ClassHolder cls = classes.get(className); - for (MethodHolder method : cls.getMethods()) { - if (method.getProgram() != null) { - Program program = ProgramUtils.copy(method.getProgram()); + InliningStrategy inliningStrategy; + if (optimizationLevel == TeaVMOptimizationLevel.FULL) { + inliningStrategy = new DefaultInliningStrategy(17, 7, false); + } else { + inliningStrategy = new DefaultInliningStrategy(100, 5, true); + } + + Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyAnalyzer, inliningStrategy, + classes, this::isExternal); + List methodReferences = inlining.getOrder(); + int classCount = classes.getClassNames().size(); + int initialValue = compileProgressValue; + for (int i = 0; i < methodReferences.size(); i++) { + MethodReference methodReference = methodReferences.get(i); + ClassHolder cls = classes.get(methodReference.getClassName()); + MethodHolder method = cls.getMethod(methodReference.getDescriptor()); + + if (method.getProgram() != null) { + if (!inlining.hasUsages(methodReference)) { + method.setProgram(null); + } else { + Program program = method.getProgram(); MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method); inlining.apply(program, method.getReference()); new UnusedVariableElimination().optimize(context, program); - inlinedPrograms.put(method.getReference(), program); } } - reportCompileProgress(++compileProgressValue); - if (wasCancelled()) { - break; - } - } - for (String className : classes.getClassNames()) { - ClassHolder cls = classes.get(className); - for (MethodHolder method : cls.getMethods()) { - if (method.getProgram() != null) { - method.setProgram(inlinedPrograms.get(method.getReference())); + int newProgress = initialValue + classCount * i / methodReferences.size(); + if (newProgress > compileProgressValue) { + compileProgressValue = newProgress; + reportCompileProgress(++compileProgressValue); + if (wasCancelled()) { + break; } } } @@ -716,6 +726,22 @@ public class TeaVM implements TeaVMHost, ServiceRepository { .collect(Collectors.toSet()); } + boolean isExternal(MethodReference method) { + MethodDependencyInfo dep = dependencyAnalyzer.getMethod(method); + if (dep != null && dep.isCalled()) { + return true; + } + return isVirtual(method); + } + + boolean isVirtual(MethodReference method) { + if (method.getName().equals("") || method.getName().equals("")) { + return false; + } + return virtualMethods == null || virtualMethods.contains(method) + || additionalVirtualMethods.stream().anyMatch(p -> p.test(method)); + } + private TeaVMTargetController targetController = new TeaVMTargetController() { @Override public boolean wasCancelled() { @@ -774,11 +800,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { @Override public boolean isVirtual(MethodReference method) { - if (method.getName().equals("") || method.getName().equals("")) { - return false; - } - return virtualMethods == null || virtualMethods.contains(method) - || additionalVirtualMethods.stream().anyMatch(p -> p.test(method)); + return TeaVM.this.isVirtual(method); } @Override diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java index 9a653740f..27022390a 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java @@ -26,16 +26,15 @@ import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.platform.async.AsyncCallback; public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor { - private static final MethodReference completeMethod = new MethodReference(AsyncCallback.class, "complete", - Object.class, void.class); - private static final MethodReference errorMethod = new MethodReference(AsyncCallback.class, "error", - Throwable.class, void.class); + private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class); + private static final MethodDescriptor errorMethod = new MethodDescriptor("error", Throwable.class, void.class); @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { @@ -52,13 +51,13 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua writer.outdent().append("}").softNewLine(); writer.append("var callback").ws().append("=").ws().append("function()").ws().append("{};").softNewLine(); - writer.append("callback.").appendMethod(completeMethod.getDescriptor()).ws().append("=").ws() + writer.append("callback.").appendMethod(completeMethod).ws().append("=").ws() .append("function(val)").ws().append("{").indent().softNewLine(); writer.append("thread.attribute").ws().append('=').ws().append("val;").softNewLine(); writer.append("$rt_setThread(javaThread);").softNewLine(); writer.append("thread.resume();").softNewLine(); writer.outdent().append("};").softNewLine(); - writer.append("callback.").appendMethod(errorMethod.getDescriptor()).ws().append("=").ws() + writer.append("callback.").appendMethod(errorMethod).ws().append("=").ws() .append("function(e)").ws().append("{").indent().softNewLine(); writer.append("thread.attribute").ws().append('=').ws().append("$rt_exception(e);").softNewLine(); writer.append("$rt_setThread(javaThread);").softNewLine(); @@ -78,7 +77,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua } writer.append("callback);").softNewLine(); writer.outdent().append("}").ws().append("catch($e)").ws().append("{").indent().softNewLine(); - writer.append("callback.").appendMethod(errorMethod.getDescriptor()).append("($rt_exception($e));") + writer.append("callback.").appendMethod(errorMethod).append("($rt_exception($e));") .softNewLine(); writer.outdent().append("}").softNewLine(); writer.outdent().append("});").softNewLine(); @@ -127,6 +126,13 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua @Override public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) { - return methodRef.equals(completeMethod) || methodRef.equals(errorMethod); + ClassReader cls = context.getClassSource().get(methodRef.getClassName()); + if (cls == null) { + return false; + } + if (!cls.getInterfaces().contains(AsyncCallback.class.getName())) { + return false; + } + return methodRef.getDescriptor().equals(completeMethod) || methodRef.getDescriptor().equals(errorMethod); } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index 8ceaea4d1..42b687dfa 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -25,6 +25,7 @@ import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; +import org.teavm.dependency.MethodDependencyInfo; import org.teavm.model.ClassReader; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; @@ -108,15 +109,21 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin } } - private void generatePrepareNewInstance(GeneratorContext context, SourceWriter writer) - throws IOException { + private void generatePrepareNewInstance(GeneratorContext context, SourceWriter writer) throws IOException { + MethodDependencyInfo newInstanceMethod = context.getDependency().getMethod( + new MethodReference(Platform.class, "newInstanceImpl", PlatformClass.class, Object.class)); writer.append("var c").ws().append("=").ws().append("'$$constructor$$';").softNewLine(); - for (String clsName : context.getClassSource().getClassNames()) { - ClassReader cls = context.getClassSource().get(clsName); - MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); - if (method != null) { - writer.appendClass(clsName).append("[c]").ws().append("=").ws() - .appendMethodBody(method.getReference()).append(";").softNewLine(); + if (newInstanceMethod != null) { + for (String clsName : newInstanceMethod.getResult().getTypes()) { + ClassReader cls = context.getClassSource().get(clsName); + if (cls == null) { + continue; + } + MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); + if (method != null) { + writer.appendClass(clsName).append("[c]").ws().append("=").ws() + .appendMethodBody(method.getReference()).append(";").softNewLine(); + } } } }