blob: 04bdf842c00774d337205710b9d7a88b08047457 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.StringUtils;
import it.unimi.dsi.fastutil.Hash.Strategy;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap.FastSortedEntrySet;
import java.util.ArrayList;
import java.util.List;
/**
* Canonicalize constants.
*/
public class ConstantCanonicalizer {
// Threshold to limit the number of constant canonicalization.
private static final int MAX_CANONICALIZED_CONSTANT = 22;
private int numberOfConstNumberCanonicalization = 0;
private int numberOfConstStringCanonicalization = 0;
private int numberOfDexItemBasedConstStringCanonicalization = 0;
private int numberOfConstClassCanonicalization = 0;
private final Object2IntMap<Long> histogramOfCanonicalizationCandidatesPerMethod;
public ConstantCanonicalizer() {
if (Log.ENABLED) {
histogramOfCanonicalizationCandidatesPerMethod = new Object2IntArrayMap<>();
} else {
histogramOfCanonicalizationCandidatesPerMethod = null;
}
}
public void logResults() {
assert Log.ENABLED;
Log.info(getClass(),
"# const-number canonicalization: %s", numberOfConstNumberCanonicalization);
Log.info(getClass(),
"# const-string canonicalization: %s", numberOfConstStringCanonicalization);
Log.info(getClass(),
"# item-based const-string canonicalization: %s",
numberOfDexItemBasedConstStringCanonicalization);
Log.info(getClass(),
"# const-class canonicalization: %s", numberOfConstClassCanonicalization);
assert histogramOfCanonicalizationCandidatesPerMethod != null;
Log.info(getClass(), "------ histogram of constant canonicalization candidates ------");
histogramOfCanonicalizationCandidatesPerMethod.forEach((length, count) -> {
Log.info(getClass(),
"%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
});
}
public void canonicalize(AppView<?> appView, IRCode code) {
Object2ObjectLinkedOpenCustomHashMap<ConstInstruction, List<Value>> valuesDefinedByConstant =
new Object2ObjectLinkedOpenCustomHashMap<>(
new Strategy<ConstInstruction>() {
@Override
public int hashCode(ConstInstruction constInstruction) {
assert constInstruction.isConstNumber()
|| constInstruction.isConstString()
|| constInstruction.isDexItemBasedConstString()
|| constInstruction.isConstClass();
if (constInstruction.isConstNumber()) {
return Long.hashCode(constInstruction.asConstNumber().getRawValue())
+ 13 * constInstruction.outType().hashCode();
}
if (constInstruction.isConstString()) {
return constInstruction.asConstString().getValue().hashCode();
}
if (constInstruction.isDexItemBasedConstString()) {
return constInstruction.asDexItemBasedConstString().getItem().hashCode();
}
return constInstruction.asConstClass().getValue().hashCode();
}
@Override
public boolean equals(ConstInstruction a, ConstInstruction b) {
// Constants with local info must not be canonicalized and must be filtered.
assert a == null || !a.outValue().hasLocalInfo();
assert b == null || !b.outValue().hasLocalInfo();
return a == b
|| (a != null && b != null && a.identicalNonValueNonPositionParts(b));
}
});
// Collect usages of constants that can be canonicalized.
for (Instruction current : code.instructions()) {
// Interested in ConstNumber, (DexItemBased)?ConstString, and ConstClass
if (!current.isConstNumber()
&& !current.isConstString()
&& !current.isDexItemBasedConstString()
&& !current.isConstClass()) {
continue;
}
// Do not canonicalize ConstClass that may have side effects. Its original instructions
// will not be removed by dead code remover due to the side effects.
if (current.isConstClass()
&& current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
continue;
}
// Do not canonicalize ConstString instructions if there are monitor operations in the code.
// That could lead to unbalanced locking and could lead to situations where OOM exceptions
// could leave a synchronized method without unlocking the monitor.
if ((current.isConstString() || current.isDexItemBasedConstString())
&& code.metadata().mayHaveMonitorInstruction()) {
continue;
}
// Constants with local info must not be canonicalized and must be filtered.
if (current.outValue().hasLocalInfo()) {
continue;
}
// Constants that are used by invoke range are not canonicalized to be compliant with the
// optimization splitRangeInvokeConstant that gives the register allocator more freedom in
// assigning register to ranged invokes which can greatly reduce the number of register
// needed (and thereby code size as well). Thus no need to do a transformation that should
// be removed later by another optimization.
if (constantUsedByInvokeRange(current.asConstInstruction())) {
continue;
}
List<Value> oldValuesDefinedByConstant =
valuesDefinedByConstant.computeIfAbsent(
current.asConstInstruction(), k -> new ArrayList<>());
oldValuesDefinedByConstant.add(current.outValue());
}
if (valuesDefinedByConstant.isEmpty()) {
return;
}
// Double-check the entry block does not have catch handlers.
// Otherwise, we need to split it before moving canonicalized const-string, which may throw.
assert !code.entryBlock().hasCatchHandlers();
final Position firstNonNonePosition = code.findFirstNonNonePosition();
FastSortedEntrySet<ConstInstruction, List<Value>> entries =
valuesDefinedByConstant.object2ObjectEntrySet();
// Sort the most frequently used constant first and exclude constant use only one time, such
// as the {@code MAX_CANONICALIZED_CONSTANT} will be canonicalized into the entry block.
if (Log.ENABLED && Log.isLoggingEnabledFor(ConstantCanonicalizer.class)) {
Long numOfCandidates = entries.stream().filter(a -> a.getValue().size() > 1).count();
synchronized (histogramOfCanonicalizationCandidatesPerMethod) {
int count = histogramOfCanonicalizationCandidatesPerMethod.getOrDefault(numOfCandidates, 0);
histogramOfCanonicalizationCandidatesPerMethod.put(numOfCandidates, count + 1);
}
}
entries.stream()
.filter(a -> a.getValue().size() > 1)
.sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size()))
.limit(MAX_CANONICALIZED_CONSTANT)
.forEach((entry) -> {
ConstInstruction canonicalizedConstant = entry.getKey().asConstInstruction();
assert canonicalizedConstant.isConstNumber()
|| canonicalizedConstant.isConstString()
|| canonicalizedConstant.isDexItemBasedConstString()
|| canonicalizedConstant.isConstClass();
ConstInstruction newConst;
if (canonicalizedConstant.isConstNumber()) {
if (Log.ENABLED) {
numberOfConstNumberCanonicalization++;
}
newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
} else if (canonicalizedConstant.isConstString()) {
if (Log.ENABLED) {
numberOfConstStringCanonicalization++;
}
newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString());
} else if (canonicalizedConstant.isDexItemBasedConstString()) {
if (Log.ENABLED) {
numberOfDexItemBasedConstStringCanonicalization++;
}
newConst = DexItemBasedConstString.copyOf(
code, canonicalizedConstant.asDexItemBasedConstString());
} else {
if (Log.ENABLED) {
numberOfConstClassCanonicalization++;
}
newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
}
newConst.setPosition(firstNonNonePosition);
insertCanonicalizedConstant(code, newConst);
for (Value outValue : entry.getValue()) {
outValue.replaceUsers(newConst.outValue());
}
});
code.removeAllTrivialPhis();
assert code.isConsistentSSA();
}
private static void insertCanonicalizedConstant(
IRCode code, ConstInstruction canonicalizedConstant) {
BasicBlock entryBlock = code.entryBlock();
// Insert the constant instruction at the start of the block right after the argument
// instructions. It is important that the const instruction is put before any instruction
// that can throw exceptions (since the value could be used on the exceptional edge).
InstructionListIterator it = entryBlock.listIterator(code);
while (it.hasNext()) {
if (!it.next().isArgument()) {
it.previous();
break;
}
}
it.add(canonicalizedConstant);
}
private static boolean constantUsedByInvokeRange(ConstInstruction constant) {
for (Instruction user : constant.outValue().uniqueUsers()) {
if (user.isInvoke() && user.asInvoke().requiredArgumentRegisters() > 5) {
return true;
}
}
return false;
}
}