Improve inliner: use simlified heuristics in advanced optimization mode, remove methods that were fully inlined

This commit is contained in:
Alexey Andreev 2019-02-12 19:30:57 +03:00
parent 89189c7454
commit 4de1c51e1a
17 changed files with 461 additions and 104 deletions

View File

@ -309,7 +309,7 @@ public class TObject {
@Async @Async
private native void waitImpl(long timeout, int nanos) throws TInterruptedException; private native void waitImpl(long timeout, int nanos) throws TInterruptedException;
public final void waitImpl(long timeout, int nanos, final AsyncCallback<Void> callback) { public final void waitImpl(long timeout, int nanos, AsyncCallback<Void> callback) {
final NotifyListenerImpl listener = new NotifyListenerImpl(this, callback, monitor.count); final NotifyListenerImpl listener = new NotifyListenerImpl(this, callback, monitor.count);
monitor.notifyListeners.add(listener); monitor.notifyListeners.add(listener);
TThread.currentThread().interruptHandler = listener; TThread.currentThread().interruptHandler = listener;

View File

@ -239,6 +239,10 @@ public class Decompiler {
|| methodsToSkip.contains(method.getReference())) { || methodsToSkip.contains(method.getReference())) {
continue; continue;
} }
if (!method.hasModifier(ElementModifier.NATIVE) && method.getProgram() == null) {
continue;
}
MethodNode methodNode = decompile(method); MethodNode methodNode = decompile(method);
clsNode.getMethods().add(methodNode); clsNode.getMethods().add(methodNode);
} }

View File

@ -60,6 +60,8 @@ import org.teavm.cache.MethodNodeCache;
import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation; 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.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyListener;
import org.teavm.dependency.DependencyType; import org.teavm.dependency.DependencyType;
@ -100,6 +102,8 @@ import org.teavm.vm.spi.TeaVMHostExtension;
public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private static final NumberFormat STATS_NUM_FORMAT = new DecimalFormat("#,##0"); private static final NumberFormat STATS_NUM_FORMAT = new DecimalFormat("#,##0");
private static final NumberFormat STATS_PERCENT_FORMAT = new DecimalFormat("0.000 %"); 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 TeaVMTargetController controller;
private boolean minifying = true; private boolean minifying = true;
@ -116,7 +120,6 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private ClassInitializerInsertionTransformer clinitInsertionTransformer; private ClassInitializerInsertionTransformer clinitInsertionTransformer;
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>(); private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>();
private boolean classScoped; private boolean classScoped;
@Override @Override
public List<ClassHolderTransformer> getTransformers() { public List<ClassHolderTransformer> getTransformers() {
return Collections.emptyList(); return Collections.emptyList();
@ -261,6 +264,17 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
if (stackTraceIncluded) { if (stackTraceIncluded) {
includeStackTraceMethods(dependencyAnalyzer); 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) { public static void includeStackTraceMethods(DependencyAnalyzer dependencyAnalyzer) {

View File

@ -45,6 +45,7 @@ import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.common.ServiceRepository; import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.MethodDependencyInfo; import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
@ -847,6 +848,11 @@ public class Renderer implements RenderingManager {
this.statementRenderer = statementRenderer; this.statementRenderer = statementRenderer;
} }
@Override
public DependencyInfo getDependency() {
return context.getDependencyInfo();
}
@Override @Override
public void visit(NativeMethodNode methodNode) { public void visit(NativeMethodNode methodNode) {
try { try {

View File

@ -18,6 +18,7 @@ package org.teavm.backend.javascript.spi;
import java.util.Properties; import java.util.Properties;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.common.ServiceRepository; import org.teavm.common.ServiceRepository;
import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ListableClassReaderSource;
@ -43,6 +44,8 @@ public interface GeneratorContext extends ServiceRepository {
Diagnostics getDiagnostics(); Diagnostics getDiagnostics();
DependencyInfo getDependency();
void typeToClassString(SourceWriter writer, ValueType type); void typeToClassString(SourceWriter writer, ValueType type);
void useLongLibrary(); void useLongLibrary();

View File

@ -337,7 +337,7 @@ class DependencyGraphBuilder {
} }
MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); MethodDependency methodDep = dependencyAnalyzer.linkMethod(method);
methodDep.addLocation(callLocation); methodDep.addLocation(callLocation);
methodDep.use(); methodDep.use(false);
if (methodDep.isMissing()) { if (methodDep.isMissing()) {
return; return;
} }

View File

@ -40,7 +40,7 @@ class FastInstructionAnalyzer extends AbstractInstructionAnalyzer {
} }
MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); MethodDependency methodDep = dependencyAnalyzer.linkMethod(method);
methodDep.addLocation(callLocation); methodDep.addLocation(callLocation);
methodDep.use(); methodDep.use(false);
} }
@Override @Override

View File

@ -35,6 +35,7 @@ public class MethodDependency implements MethodDependencyInfo {
MethodHolder method; MethodHolder method;
private MethodReference reference; private MethodReference reference;
boolean used; boolean used;
boolean external;
DependencyPlugin dependencyPlugin; DependencyPlugin dependencyPlugin;
boolean dependencyPluginAttached; boolean dependencyPluginAttached;
private List<LocationListener> locationListeners; private List<LocationListener> locationListeners;
@ -151,11 +152,23 @@ public class MethodDependency implements MethodDependencyInfo {
} }
public void use() { public void use() {
use(true);
}
void use(boolean external) {
if (!used) { if (!used) {
used = true; used = true;
if (!isMissing()) { if (!isMissing()) {
dependencyAnalyzer.scheduleMethodAnalysis(this); dependencyAnalyzer.scheduleMethodAnalysis(this);
} }
} }
if (external) {
this.external = true;
}
}
@Override
public boolean isCalled() {
return external;
} }
} }

View File

@ -34,5 +34,7 @@ public interface MethodDependencyInfo {
boolean isUsed(); boolean isUsed();
boolean isCalled();
boolean isMissing(); boolean isMissing();
} }

View File

@ -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<? extends VariableReader> arguments, InvocationType type) {
complexity++;
if (instance != null) {
complexity++;
}
}
@Override
public void choose(VariableReader condition, List<? extends SwitchTableEntryReader> 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--;
}
}
}

View File

@ -16,54 +16,117 @@
package org.teavm.model.optimization; package org.teavm.model.optimization;
import com.carrotsearch.hppc.IntArrayList; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.BasicBlockReader;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier;
import org.teavm.model.Incoming; import org.teavm.model.Incoming;
import org.teavm.model.Instruction; import org.teavm.model.Instruction;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.Phi; import org.teavm.model.Phi;
import org.teavm.model.Program; import org.teavm.model.Program;
import org.teavm.model.ProgramReader;
import org.teavm.model.TryCatchBlock; import org.teavm.model.TryCatchBlock;
import org.teavm.model.VariableReader;
import org.teavm.model.analysis.ClassInference; import org.teavm.model.analysis.ClassInference;
import org.teavm.model.instructions.AbstractInstructionReader;
import org.teavm.model.instructions.AssignInstruction; 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.ExitInstruction;
import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.InitClassInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.JumpInstruction; import org.teavm.model.instructions.JumpInstruction;
import org.teavm.model.instructions.SwitchInstruction;
import org.teavm.model.util.BasicBlockMapper; import org.teavm.model.util.BasicBlockMapper;
import org.teavm.model.util.InstructionVariableMapper; import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.ProgramUtils;
import org.teavm.model.util.TransitionExtractor; import org.teavm.model.util.TransitionExtractor;
public class Inlining { public class Inlining {
private static final int DEFAULT_THRESHOLD = 17;
private static final int MAX_DEPTH = 7;
private IntArrayList depthsByBlock; private IntArrayList depthsByBlock;
private Set<Instruction> instructionsToSkip; private Set<Instruction> instructionsToSkip;
private ClassHierarchy hierarchy; private ClassHierarchy hierarchy;
private ClassReaderSource classes; private ListableClassReaderSource classes;
private DependencyInfo dependencyInfo; private DependencyInfo dependencyInfo;
private InliningStrategy strategy;
private MethodUsageCounter usageCounter;
private Set<MethodReference> methodsUsedOnce = new HashSet<>();
public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo) { public Inlining(ClassHierarchy hierarchy, DependencyInfo dependencyInfo, InliningStrategy strategy,
ListableClassReaderSource classes, Predicate<MethodReference> externalMethods) {
this.hierarchy = hierarchy; this.hierarchy = hierarchy;
this.classes = hierarchy.getClassSource(); this.classes = classes;
this.dependencyInfo = dependencyInfo; 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<MethodReference> cursor : usageCounter.methodUsageCount.keys()) {
if (usageCounter.methodUsageCount.get(cursor.value) == 1) {
methodsUsedOnce.add(cursor.value);
}
}
}
public List<MethodReference> getOrder() {
List<MethodReference> order = new ArrayList<>();
Set<MethodReference> 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<MethodReference> order, Set<MethodReference> visited) {
if (!visited.add(method)) {
return;
}
Set<MethodReference> 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) { public void apply(Program program, MethodReference method) {
@ -73,7 +136,7 @@ public class Inlining {
} }
instructionsToSkip = new HashSet<>(); instructionsToSkip = new HashSet<>();
while (applyOnce(program)) { while (applyOnce(program, method)) {
devirtualize(program, method, dependencyInfo); devirtualize(program, method, dependencyInfo);
} }
depthsByBlock = null; depthsByBlock = null;
@ -82,8 +145,12 @@ public class Inlining {
new UnreachableBasicBlockEliminator().optimize(program); new UnreachableBasicBlockEliminator().optimize(program);
} }
private boolean applyOnce(Program program) { private boolean applyOnce(Program program, MethodReference method) {
List<PlanEntry> plan = buildPlan(program, 0); InliningStep step = strategy.start(method, program);
if (step == null) {
return false;
}
List<PlanEntry> plan = buildPlan(program, -1, step);
if (plan.isEmpty()) { if (plan.isEmpty()) {
return false; return false;
} }
@ -98,6 +165,11 @@ public class Inlining {
} }
private void execPlanEntry(Program program, PlanEntry planEntry, int offset) { 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); BasicBlock block = program.basicBlockAt(planEntry.targetBlock + offset);
InvokeInstruction invoke = (InvokeInstruction) planEntry.targetInstruction; InvokeInstruction invoke = (InvokeInstruction) planEntry.targetInstruction;
BasicBlock splitBlock = program.createBasicBlock(); BasicBlock splitBlock = program.createBasicBlock();
@ -219,24 +291,18 @@ public class Inlining {
execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex()); execPlan(program, planEntry.innerPlan, firstInlineBlock.getIndex());
} }
private List<PlanEntry> buildPlan(Program program, int depth) { private List<PlanEntry> buildPlan(Program program, int depth, InliningStep step) {
if (depth >= MAX_DEPTH) {
return Collections.emptyList();
}
List<PlanEntry> plan = new ArrayList<>(); List<PlanEntry> plan = new ArrayList<>();
int ownComplexity = getComplexity(program);
int originalDepth = depth; int originalDepth = depth;
ContextImpl context = new ContextImpl();
for (BasicBlock block : program.getBasicBlocks()) { for (BasicBlock block : program.getBasicBlocks()) {
if (!block.getTryCatchBlocks().isEmpty()) { if (!block.getTryCatchBlocks().isEmpty()) {
continue; continue;
} }
if (originalDepth == 0) { if (originalDepth < 0) {
depth = depthsByBlock.get(block.getIndex()); depth = depthsByBlock.get(block.getIndex());
if (depth >= MAX_DEPTH) {
continue;
}
} }
for (Instruction insn : block) { for (Instruction insn : block) {
@ -254,27 +320,28 @@ public class Inlining {
MethodReader invokedMethod = getMethod(invoke.getMethod()); MethodReader invokedMethod = getMethod(invoke.getMethod());
if (invokedMethod == null || invokedMethod.getProgram() == null if (invokedMethod == null || invokedMethod.getProgram() == null
|| invokedMethod.getProgram().basicBlockCount() == 0) { || invokedMethod.getProgram().basicBlockCount() == 0
|| invokedMethod.hasModifier(ElementModifier.SYNCHRONIZED)) {
instructionsToSkip.add(insn); instructionsToSkip.add(insn);
continue; continue;
} }
Program invokedProgram = ProgramUtils.copy(invokedMethod.getProgram()); context.depth = depth;
int complexityThreshold = DEFAULT_THRESHOLD; InliningStep innerStep = step.tryInline(invokedMethod.getReference(), invokedMethod.getProgram(),
if (ownComplexity < DEFAULT_THRESHOLD) { context);
complexityThreshold += DEFAULT_THRESHOLD; if (innerStep == null) {
}
if (getComplexity(invokedProgram) > complexityThreshold) {
instructionsToSkip.add(insn); instructionsToSkip.add(insn);
continue; continue;
} }
Program invokedProgram = ProgramUtils.copy(invokedMethod.getProgram());
PlanEntry entry = new PlanEntry(); PlanEntry entry = new PlanEntry();
entry.targetBlock = block.getIndex(); entry.targetBlock = block.getIndex();
entry.targetInstruction = insn; entry.targetInstruction = insn;
entry.program = invokedProgram; entry.program = invokedProgram;
entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1)); entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1, innerStep));
entry.depth = depth; entry.depth = depth;
entry.method = invokedMethod.getReference();
plan.add(entry); plan.add(entry);
} }
} }
@ -288,34 +355,6 @@ public class Inlining {
return cls != null ? cls.getMethod(methodRef.getDescriptor()) : null; 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) { private void devirtualize(Program program, MethodReference method, DependencyInfo dependencyInfo) {
ClassInference inference = new ClassInference(dependencyInfo, hierarchy); ClassInference inference = new ClassInference(dependencyInfo, hierarchy);
inference.infer(program, method); inference.infer(program, method);
@ -350,8 +389,43 @@ public class Inlining {
private class PlanEntry { private class PlanEntry {
int targetBlock; int targetBlock;
Instruction targetInstruction; Instruction targetInstruction;
MethodReference method;
Program program; Program program;
int depth; int depth;
final List<PlanEntry> innerPlan = new ArrayList<>(); final List<PlanEntry> innerPlan = new ArrayList<>();
} }
static class MethodUsageCounter extends AbstractInstructionReader {
ObjectIntMap<MethodReference> methodUsageCount = new ObjectIntHashMap<>();
Map<MethodReference, Set<MethodReference>> methodDependencies = new LinkedHashMap<>();
Predicate<MethodReference> externalMethods;
MethodReference currentMethod;
MethodUsageCounter(Predicate<MethodReference> externalMethods) {
this.externalMethods = externalMethods;
}
@Override
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> 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;
}
}
} }

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -70,9 +70,11 @@ import org.teavm.model.ValueType;
import org.teavm.model.optimization.ArrayUnwrapMotion; import org.teavm.model.optimization.ArrayUnwrapMotion;
import org.teavm.model.optimization.ClassInitElimination; import org.teavm.model.optimization.ClassInitElimination;
import org.teavm.model.optimization.ConstantConditionElimination; import org.teavm.model.optimization.ConstantConditionElimination;
import org.teavm.model.optimization.DefaultInliningStrategy;
import org.teavm.model.optimization.Devirtualization; import org.teavm.model.optimization.Devirtualization;
import org.teavm.model.optimization.GlobalValueNumbering; import org.teavm.model.optimization.GlobalValueNumbering;
import org.teavm.model.optimization.Inlining; import org.teavm.model.optimization.Inlining;
import org.teavm.model.optimization.InliningStrategy;
import org.teavm.model.optimization.LoopInvariantMotion; import org.teavm.model.optimization.LoopInvariantMotion;
import org.teavm.model.optimization.MethodOptimization; import org.teavm.model.optimization.MethodOptimization;
import org.teavm.model.optimization.MethodOptimizationContext; import org.teavm.model.optimization.MethodOptimizationContext;
@ -430,8 +432,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
compileProgressValue = 0; compileProgressValue = 0;
compileProgressLimit = dependencyAnalyzer.getReachableClasses().size(); compileProgressLimit = dependencyAnalyzer.getReachableClasses().size();
if (optimizationLevel == TeaVMOptimizationLevel.ADVANCED) { if (optimizationLevel == TeaVMOptimizationLevel.ADVANCED) {
compileProgressLimit *= 3;
} else if (optimizationLevel == TeaVMOptimizationLevel.FULL) {
compileProgressLimit *= 4; compileProgressLimit *= 4;
} else { } else {
compileProgressLimit *= 2; compileProgressLimit *= 2;
@ -538,34 +538,44 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
} }
private void inline(ListableClassHolderSource classes) { private void inline(ListableClassHolderSource classes) {
if (optimizationLevel != TeaVMOptimizationLevel.FULL) { if (optimizationLevel != TeaVMOptimizationLevel.ADVANCED) {
return; return;
} }
Map<MethodReference, Program> inlinedPrograms = new HashMap<>(); InliningStrategy inliningStrategy;
Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyAnalyzer); if (optimizationLevel == TeaVMOptimizationLevel.FULL) {
for (String className : classes.getClassNames()) { inliningStrategy = new DefaultInliningStrategy(17, 7, false);
ClassHolder cls = classes.get(className); } else {
for (MethodHolder method : cls.getMethods()) { inliningStrategy = new DefaultInliningStrategy(100, 5, true);
if (method.getProgram() != null) { }
Program program = ProgramUtils.copy(method.getProgram());
Inlining inlining = new Inlining(new ClassHierarchy(classes), dependencyAnalyzer, inliningStrategy,
classes, this::isExternal);
List<MethodReference> 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); MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method);
inlining.apply(program, method.getReference()); inlining.apply(program, method.getReference());
new UnusedVariableElimination().optimize(context, program); new UnusedVariableElimination().optimize(context, program);
inlinedPrograms.put(method.getReference(), program);
} }
} }
reportCompileProgress(++compileProgressValue);
if (wasCancelled()) {
break;
}
}
for (String className : classes.getClassNames()) { int newProgress = initialValue + classCount * i / methodReferences.size();
ClassHolder cls = classes.get(className); if (newProgress > compileProgressValue) {
for (MethodHolder method : cls.getMethods()) { compileProgressValue = newProgress;
if (method.getProgram() != null) { reportCompileProgress(++compileProgressValue);
method.setProgram(inlinedPrograms.get(method.getReference())); if (wasCancelled()) {
break;
} }
} }
} }
@ -716,6 +726,22 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
.collect(Collectors.toSet()); .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("<init>") || method.getName().equals("<clinit>")) {
return false;
}
return virtualMethods == null || virtualMethods.contains(method)
|| additionalVirtualMethods.stream().anyMatch(p -> p.test(method));
}
private TeaVMTargetController targetController = new TeaVMTargetController() { private TeaVMTargetController targetController = new TeaVMTargetController() {
@Override @Override
public boolean wasCancelled() { public boolean wasCancelled() {
@ -774,11 +800,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
@Override @Override
public boolean isVirtual(MethodReference method) { public boolean isVirtual(MethodReference method) {
if (method.getName().equals("<init>") || method.getName().equals("<clinit>")) { return TeaVM.this.isVirtual(method);
return false;
}
return virtualMethods == null || virtualMethods.contains(method)
|| additionalVirtualMethods.stream().anyMatch(p -> p.test(method));
} }
@Override @Override

View File

@ -26,16 +26,15 @@ import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.platform.async.AsyncCallback; import org.teavm.platform.async.AsyncCallback;
public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor { public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor {
private static final MethodReference completeMethod = new MethodReference(AsyncCallback.class, "complete", private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class);
Object.class, void.class); private static final MethodDescriptor errorMethod = new MethodDescriptor("error", Throwable.class, void.class);
private static final MethodReference errorMethod = new MethodReference(AsyncCallback.class, "error",
Throwable.class, void.class);
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { 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.outdent().append("}").softNewLine();
writer.append("var callback").ws().append("=").ws().append("function()").ws().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(); .append("function(val)").ws().append("{").indent().softNewLine();
writer.append("thread.attribute").ws().append('=').ws().append("val;").softNewLine(); writer.append("thread.attribute").ws().append('=').ws().append("val;").softNewLine();
writer.append("$rt_setThread(javaThread);").softNewLine(); writer.append("$rt_setThread(javaThread);").softNewLine();
writer.append("thread.resume();").softNewLine(); writer.append("thread.resume();").softNewLine();
writer.outdent().append("};").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(); .append("function(e)").ws().append("{").indent().softNewLine();
writer.append("thread.attribute").ws().append('=').ws().append("$rt_exception(e);").softNewLine(); writer.append("thread.attribute").ws().append('=').ws().append("$rt_exception(e);").softNewLine();
writer.append("$rt_setThread(javaThread);").softNewLine(); writer.append("$rt_setThread(javaThread);").softNewLine();
@ -78,7 +77,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
} }
writer.append("callback);").softNewLine(); writer.append("callback);").softNewLine();
writer.outdent().append("}").ws().append("catch($e)").ws().append("{").indent().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(); .softNewLine();
writer.outdent().append("}").softNewLine(); writer.outdent().append("}").softNewLine();
writer.outdent().append("});").softNewLine(); writer.outdent().append("});").softNewLine();
@ -127,6 +126,13 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
@Override @Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) { 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);
} }
} }

View File

@ -25,6 +25,7 @@ import org.teavm.backend.javascript.spi.InjectorContext;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
@ -108,15 +109,21 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
} }
} }
private void generatePrepareNewInstance(GeneratorContext context, SourceWriter writer) private void generatePrepareNewInstance(GeneratorContext context, SourceWriter writer) throws IOException {
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(); writer.append("var c").ws().append("=").ws().append("'$$constructor$$';").softNewLine();
for (String clsName : context.getClassSource().getClassNames()) { if (newInstanceMethod != null) {
ClassReader cls = context.getClassSource().get(clsName); for (String clsName : newInstanceMethod.getResult().getTypes()) {
MethodReader method = cls.getMethod(new MethodDescriptor("<init>", void.class)); ClassReader cls = context.getClassSource().get(clsName);
if (method != null) { if (cls == null) {
writer.appendClass(clsName).append("[c]").ws().append("=").ws() continue;
.appendMethodBody(method.getReference()).append(";").softNewLine(); }
MethodReader method = cls.getMethod(new MethodDescriptor("<init>", void.class));
if (method != null) {
writer.appendClass(clsName).append("[c]").ws().append("=").ws()
.appendMethodBody(method.getReference()).append(";").softNewLine();
}
} }
} }
} }