Merge "Use ApiLevelException for Invoke Polymorphic error"
diff --git a/build.gradle b/build.gradle
index 9a48a4d..63513ce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -114,7 +114,7 @@
dependencies {
compile 'net.sf.jopt-simple:jopt-simple:4.6'
- compile 'com.googlecode.json-simple:json-simple:1.1.1'
+ compile 'com.googlecode.json-simple:json-simple:1.1'
compile group: 'com.google.guava', name: 'guava', version: '19.0'
compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
@@ -326,12 +326,15 @@
// In order to build without dependencies, pass the exclude_deps property using:
// gradle -Pexclude_deps R8
if (!project.hasProperty('exclude_deps')) {
- // Relocating dependencies to avoid conflicts.
- relocate 'com.google', 'com.android.tools.r8.com.google'
+ // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+ // to avoid rewriting unrelated strings.
+ relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+ relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
- relocate 'org', 'com.android.tools.r8.org'
+ relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+ relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+ relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
- relocate 'junit', 'com.android.tools.r8.junit'
// Also include dependencies
configurations = [project.configurations.compile]
}
@@ -348,12 +351,15 @@
// In order to build without dependencies, pass the exclude_deps property using:
// gradle -Pexclude_deps D8
if (!project.hasProperty('exclude_deps')) {
- // Relocating dependencies to avoid conflicts.
- relocate 'com.google', 'com.android.tools.r8.com.google'
+ // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+ // to avoid rewriting unrelated strings.
+ relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+ relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
- relocate 'org', 'com.android.tools.r8.org'
+ relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+ relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+ relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
- relocate 'junit', 'com.android.tools.r8.junit'
// Also include dependencies
configurations = [project.configurations.compile]
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2d2760e..5321808 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
*/
public final class D8 {
- private static final String VERSION = "v0.1.0";
+ private static final String VERSION = "v0.2.0";
private static final int STATUS_ERROR = 1;
private D8() {}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 91d2337..328f215 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
public class R8 {
- private static final String VERSION = "v0.1.0";
+ private static final String VERSION = "v0.2.0";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
@@ -270,9 +270,10 @@
proguardSeedsData = bytes.toByteArray();
}
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
// Recompute the subtyping information.
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
new AbstractMethodRemover(appInfo).run();
new AnnotationRemover(appInfo.withLiveness(), options).run();
}
@@ -295,11 +296,13 @@
// Class merging requires inlining.
if (!options.skipClassMerging && options.inlineAccessors) {
timing.begin("ClassMerger");
- graphLense = new SimpleClassMerger(application, appInfo.withLiveness(), graphLense,
- timing).run();
+ SimpleClassMerger classMerger = new SimpleClassMerger(application,
+ appInfo.withLiveness(), graphLense, timing);
+ graphLense = classMerger.run();
timing.end();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, classMerger.getRemovedClasses());
}
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
// Collect switch maps and ordinals maps.
new SwitchMapCollector(appInfo.withLiveness(), options).run();
@@ -333,8 +336,10 @@
Enqueuer enqueuer = new Enqueuer(appInfo);
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, pruner.getRemovedClasses());
// Print reasons on the application after pruning, so that we reflect the actual result.
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
reasonPrinter.run(application);
@@ -345,7 +350,8 @@
}
// Only perform discard-checking if tree-shaking is turned on.
- if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
+ if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()
+ && options.useDiscardedChecker) {
new DiscardedChecker(rootSet, application).run();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ec040a5..775c1b3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -32,6 +31,7 @@
private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
private final List<Path> proguardConfigFiles = new ArrayList<>();
private Optional<Boolean> treeShaking = Optional.empty();
+ private Optional<Boolean> discardedChecker = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
private boolean ignoreMissingClasses = false;
private Path packageDistributionFile = null;
@@ -58,6 +58,14 @@
}
/**
+ * Enable/disable discareded checker.
+ */
+ public Builder setDiscardedChecker(boolean useDiscardedChecker) {
+ discardedChecker = Optional.of(useDiscardedChecker);
+ return self();
+ }
+
+ /**
* Enable/disable minification. This overrides any settings in proguard configuration files.
*/
public Builder setMinification(boolean useMinification) {
@@ -194,11 +202,14 @@
addLibraryFiles(configuration.getLibraryjars());
}
+ // TODO(b/64802420): setProguardMapFile if configuration.hasApplyMappingFile
+
if (packageDistributionFile != null) {
getAppBuilder().setPackageDistributionFile(packageDistributionFile);
}
boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
+ boolean useDiscardedChecker = discardedChecker.orElse(true);
boolean useMinification = minification.orElse(configuration.isObfuscating());
return new R8Command(
@@ -211,6 +222,7 @@
getMode(),
getMinApiLevel(),
useTreeShaking,
+ useDiscardedChecker,
useMinification,
ignoreMissingClasses);
}
@@ -236,6 +248,7 @@
" # shaking/minification).",
" --pg-map <file> # Proguard map <file>.",
" --no-tree-shaking # Force disable tree shaking of unreachable classes.",
+ " --no-discarded-checker # Force disable the discarded checker (when tree shaking).",
" --no-minification # Force disable minification of names.",
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
@@ -248,6 +261,7 @@
private final Path mainDexListOutput;
private final ProguardConfiguration proguardConfiguration;
private final boolean useTreeShaking;
+ private final boolean useDiscardedChecker;
private final boolean useMinification;
private final boolean ignoreMissingClasses;
@@ -305,6 +319,8 @@
builder.setMinApiLevel(Integer.valueOf(args[++i]));
} else if (arg.equals("--no-tree-shaking")) {
builder.setTreeShaking(false);
+ } else if (arg.equals("--no-discarded-checker")) {
+ builder.setDiscardedChecker(false);
} else if (arg.equals("--no-minification")) {
builder.setMinification(false);
} else if (arg.equals("--main-dex-rules")) {
@@ -359,6 +375,7 @@
CompilationMode mode,
int minApiLevel,
boolean useTreeShaking,
+ boolean useDiscardedChecker,
boolean useMinification,
boolean ignoreMissingClasses) {
super(inputApp, outputPath, outputMode, mode, minApiLevel);
@@ -369,6 +386,7 @@
this.mainDexListOutput = mainDexListOutput;
this.proguardConfiguration = proguardConfiguration;
this.useTreeShaking = useTreeShaking;
+ this.useDiscardedChecker = useDiscardedChecker;
this.useMinification = useMinification;
this.ignoreMissingClasses = ignoreMissingClasses;
}
@@ -379,6 +397,7 @@
mainDexListOutput = null;
proguardConfiguration = null;
useTreeShaking = false;
+ useDiscardedChecker = false;
useMinification = false;
ignoreMissingClasses = false;
}
@@ -387,6 +406,10 @@
return useTreeShaking;
}
+ public boolean useDiscardedChecker() {
+ return useDiscardedChecker;
+ }
+
public boolean useMinification() {
return useMinification;
}
@@ -401,6 +424,8 @@
internal.skipMinification = !useMinification() || !proguardConfiguration.isObfuscating();
assert internal.useTreeShaking;
internal.useTreeShaking = useTreeShaking();
+ assert internal.useDiscardedChecker;
+ internal.useDiscardedChecker = useDiscardedChecker();
assert !internal.ignoreMissingClasses;
internal.ignoreMissingClasses = ignoreMissingClasses;
for (String pattern : proguardConfiguration.getAttributesRemovalPatterns()) {
diff --git a/src/main/java/com/android/tools/r8/code/Base1Format.java b/src/main/java/com/android/tools/r8/code/Base1Format.java
index b62853d..9d88f53 100644
--- a/src/main/java/com/android/tools/r8/code/Base1Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base1Format.java
@@ -5,6 +5,8 @@
public abstract class Base1Format extends Instruction {
+ public static final int SIZE = 1;
+
public Base1Format(BytecodeStream stream) {
super(stream);
}
@@ -12,6 +14,6 @@
protected Base1Format() {}
public int getSize() {
- return 1;
+ return SIZE;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Base2Format.java b/src/main/java/com/android/tools/r8/code/Base2Format.java
index f8a48ac..1241e6a 100644
--- a/src/main/java/com/android/tools/r8/code/Base2Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base2Format.java
@@ -5,6 +5,8 @@
public abstract class Base2Format extends Instruction {
+ public static final int SIZE = 2;
+
protected Base2Format() {}
public Base2Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 2;
+ return SIZE;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Base3Format.java b/src/main/java/com/android/tools/r8/code/Base3Format.java
index 34bda57..c1618f5 100644
--- a/src/main/java/com/android/tools/r8/code/Base3Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base3Format.java
@@ -5,6 +5,8 @@
public abstract class Base3Format extends Instruction {
+ public static final int SIZE = 3;
+
protected Base3Format() {}
public Base3Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 3;
+ return SIZE;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base4Format.java b/src/main/java/com/android/tools/r8/code/Base4Format.java
index 7cdf1c5..f3448fa 100644
--- a/src/main/java/com/android/tools/r8/code/Base4Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base4Format.java
@@ -5,6 +5,8 @@
public abstract class Base4Format extends Instruction {
+ public static final int SIZE = 4;
+
protected Base4Format() {}
public Base4Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 4;
+ return SIZE;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base5Format.java b/src/main/java/com/android/tools/r8/code/Base5Format.java
index cc67572..10ddc5e 100644
--- a/src/main/java/com/android/tools/r8/code/Base5Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base5Format.java
@@ -5,6 +5,8 @@
public abstract class Base5Format extends Instruction {
+ public static final int SIZE = 5;
+
protected Base5Format() {}
public Base5Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 5;
+ return SIZE;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 80dea00..7dc77de 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -317,6 +317,8 @@
DexType result = types.get(descriptor);
if (result == null) {
result = new DexType(descriptor);
+ assert result.isArrayType() || result.isClassType() || result.isPrimitiveType() ||
+ result.isVoidType();
assert !internalSentinels.contains(result);
types.put(descriptor, result);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index a371684..0d81ea4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -288,8 +288,12 @@
}
public boolean isPrimitiveType() {
- char firstChar = (char) descriptor.content[0];
- return firstChar != 'L' && firstChar != '[';
+ return isPrimitiveType((char) descriptor.content[0]);
+ }
+
+ private boolean isPrimitiveType(char c) {
+ return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
+ || c == 'D';
}
public boolean isVoidType() {
@@ -310,19 +314,7 @@
if (!isArrayType()) {
return false;
}
- switch (descriptor.content[1]) {
- case 'Z': // boolean
- case 'B': // byte
- case 'S': // short
- case 'C': // char
- case 'I': // int
- case 'F': // float
- case 'J': // long
- case 'D': // double
- return true;
- default:
- return false;
- }
+ return isPrimitiveType((char) descriptor.content[1]);
}
public int elementSizeForPrimitiveArrayType() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index c7b55d5..fbf6658 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -17,7 +17,7 @@
public Argument(Value outValue) {
super(outValue);
- outValue.markAsArgument();;
+ outValue.markAsArgument();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 8a001ae..49adcb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -59,26 +59,26 @@
int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
int result = foldIntegers(left, right);
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.INT, value, result);
} else if (type == NumericType.LONG) {
long left = leftValue().getConstInstruction().asConstNumber().getLongValue();
long right = rightValue().getConstInstruction().asConstNumber().getLongValue();
long result = foldLongs(left, right);
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.LONG, value, result);
} else if (type == NumericType.FLOAT) {
float left = leftValue().getConstInstruction().asConstNumber().getFloatValue();
float right = rightValue().getConstInstruction().asConstNumber().getFloatValue();
float result = foldFloat(left, right);
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
} else {
assert type == NumericType.DOUBLE;
double left = leftValue().getConstInstruction().asConstNumber().getDoubleValue();
double right = rightValue().getConstInstruction().asConstNumber().getDoubleValue();
double result = foldDouble(left, right);
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 5f1b33f..c9e26b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -543,6 +543,13 @@
return unlinkSingleSuccessor();
}
+ public void detachAllSuccessors() {
+ for (BasicBlock successor : successors) {
+ successor.predecessors.remove(this);
+ }
+ successors.clear();
+ }
+
public List<BasicBlock> unlink(BasicBlock successor, DominatorTree dominator) {
assert successors.contains(successor);
assert successor.predecessors.contains(this);
@@ -575,10 +582,6 @@
for (Value value : instruction.getDebugValues()) {
value.removeDebugUser(instruction);
}
- Value previousLocalValue = instruction.getPreviousLocalValue();
- if (previousLocalValue != null) {
- previousLocalValue.removeDebugUser(instruction);
- }
}
}
}
@@ -935,7 +938,8 @@
*
* <p>The constructed basic block has no predecessors and no successors.
*
- * @param blockNumber the block number of the goto block
+ * @param blockNumber the block number of the block
+ * @param theIf the if instruction
*/
public static BasicBlock createIfBlock(int blockNumber, If theIf) {
BasicBlock block = new BasicBlock();
@@ -945,6 +949,32 @@
return block;
}
+ /**
+ * Create a new basic block with an instruction followed by an if instruction.
+ *
+ * <p>The constructed basic block has no predecessors and no successors.
+ *
+ * @param blockNumber the block number of the block
+ * @param theIf the if instruction
+ * @param instruction the instruction to place before the if instruction
+ */
+ public static BasicBlock createIfBlock(int blockNumber, If theIf, Instruction instruction) {
+ BasicBlock block = new BasicBlock();
+ block.add(instruction);
+ block.add(theIf);
+ block.close(null);
+ block.setNumber(blockNumber);
+ return block;
+ }
+
+ public static BasicBlock createSwitchBlock(int blockNumber, Switch theSwitch) {
+ BasicBlock block = new BasicBlock();
+ block.add(theSwitch);
+ block.close(null);
+ block.setNumber(blockNumber);
+ return block;
+ }
+
public boolean isTrivialGoto() {
return instructions.size() == 1 && exit().isGoto();
}
@@ -1144,7 +1174,7 @@
// Remove the move-exception instruction.
move = entry().asMoveException();
position = move.getPosition();
- assert move.getPreviousLocalValue() == null;
+ assert move.getDebugValues().isEmpty();
getInstructions().remove(0);
}
// Create new predecessor blocks.
@@ -1158,8 +1188,7 @@
BasicBlock newBlock = new BasicBlock();
newPredecessors.add(newBlock);
if (hasMoveException) {
- Value value = new Value(
- valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
+ Value value = new Value(valueNumberGenerator.next(), MoveType.OBJECT, move.getLocalInfo());
values.add(value);
MoveException newMove = new MoveException(value);
newBlock.add(newMove);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 968e560..f5bc204 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.IteratorUtils;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
@@ -114,10 +115,6 @@
for (Value value : current.getDebugValues()) {
value.removeDebugUser(current);
}
- Value previousLocalValue = current.getPreviousLocalValue();
- if (previousLocalValue != null) {
- previousLocalValue.removeDebugUser(current);
- }
listIterator.remove();
current = null;
}
@@ -166,15 +163,9 @@
}
}
- private BasicBlock peekPrevious(ListIterator<BasicBlock> blocksIterator) {
- BasicBlock block = blocksIterator.previous();
- blocksIterator.next();
- return block;
- }
-
public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
List<BasicBlock> blocks = code.blocks;
- assert blocksIterator == null || peekPrevious(blocksIterator) == block;
+ assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
int blockNumber = code.getHighestBlockNumber() + 1;
BasicBlock newBlock;
@@ -209,6 +200,9 @@
blocks.add(blocks.indexOf(block) + 1, newBlock);
} else {
blocksIterator.add(newBlock);
+ // Ensure that calling remove() will remove the block just added.
+ blocksIterator.previous();
+ blocksIterator.next();
}
return newBlock;
@@ -217,7 +211,7 @@
public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blocksIterator) {
// Split at the current cursor position.
BasicBlock newBlock = split(code, blocksIterator);
- assert blocksIterator == null || peekPrevious(blocksIterator) == newBlock;
+ assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == newBlock;
// Skip the requested number of instructions and split again.
InstructionListIterator iterator = newBlock.listIterator();
for (int i = 0; i < instructions; i++) {
@@ -350,7 +344,7 @@
assert invoke.inValues().size() == arguments.size();
for (int i = 0; i < invoke.inValues().size(); i++) {
// TODO(zerny): Support inlining in --debug mode.
- assert arguments.get(i).getDebugInfo() == null;
+ assert arguments.get(i).getLocalInfo() == null;
if ((i == 0) && (downcast != null)) {
Value invokeValue = invoke.inValues().get(0);
Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 9b54353..aeb0a51 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -180,7 +180,7 @@
}
}
assert result == -1 || result == 0 || result == 1;
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.INT, value, result);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 1880668..e376361 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -33,10 +33,7 @@
public static ConstNumber copyOf(IRCode code, ConstNumber original) {
Value newValue =
- new Value(
- code.valueNumberGenerator.next(),
- original.outType(),
- original.getDebugInfo());
+ new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
return new ConstNumber(original.type, newValue, original.getRawValue());
}
@@ -80,6 +77,14 @@
return value == 0;
}
+ public boolean isIntegerZero() {
+ return type == ConstType.INT && getIntValue() == 0;
+ }
+
+ public boolean isIntegerOne() {
+ return type == ConstType.INT && getIntValue() == 1;
+ }
+
public boolean isIntegerNegativeOne(NumericType type) {
assert type == NumericType.INT || type == NumericType.LONG;
if (type == NumericType.INT) {
@@ -121,6 +126,33 @@
}
}
+ // Estimated size of the resulting dex instruction in code units.
+ public static int estimatedDexSize(ConstType type, long value) {
+ if (MoveType.fromConstType(type) == MoveType.SINGLE) {
+ assert NumberUtils.is32Bit(value);
+ if (NumberUtils.is4Bit(value)) {
+ return Const4.SIZE;
+ } else if (NumberUtils.is16Bit(value)) {
+ return Const16.SIZE;
+ } else if ((value & 0x0000ffffL) == 0) {
+ return ConstHigh16.SIZE;
+ } else {
+ return Const.SIZE;
+ }
+ } else {
+ assert MoveType.fromConstType(type) == MoveType.WIDE;
+ if (NumberUtils.is16Bit(value)) {
+ return ConstWide16.SIZE;
+ } else if ((value & 0x0000ffffffffffffL) == 0) {
+ return ConstWideHigh16.SIZE;
+ } else if (NumberUtils.is32Bit(value)) {
+ return ConstWide32.SIZE;
+ } else {
+ return ConstWide.SIZE;
+ }
+ }
+ }
+
@Override
public int maxInValueRegister() {
assert false : "Const has no register arguments.";
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 824d75c..21ed16e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -155,14 +156,11 @@
values.add(phi);
for (Value value : phi.getOperands()) {
values.add(value);
- if (value.isPhi()) {
- Phi phiOperand = value.asPhi();
- assert phiOperand.getBlock().getPhis().contains(phiOperand);
- assert phiOperand.uniquePhiUsers().contains(phi);
- } else {
- Instruction definition = value.definition;
- assert definition.outValue() == value;
- }
+ assert value.uniquePhiUsers().contains(phi);
+ }
+ for (Value value : phi.getDebugValues()) {
+ values.add(value);
+ value.debugPhiUsers().contains(phi);
}
}
for (Instruction instruction : block.getInstructions()) {
@@ -171,33 +169,41 @@
if (outValue != null) {
values.add(outValue);
assert outValue.definition == instruction;
- Value previousLocalValue = outValue.getPreviousLocalValue();
- if (previousLocalValue != null) {
- values.add(previousLocalValue);
- assert previousLocalValue.debugUsers().contains(instruction);
- }
}
for (Value value : instruction.inValues()) {
values.add(value);
assert value.uniqueUsers().contains(instruction);
- if (value.isPhi()) {
- Phi phi = value.asPhi();
- assert phi.getBlock().getPhis().contains(phi);
- } else {
- Instruction definition = value.definition;
- assert definition.outValue() == value;
- }
+ }
+ for (Value value : instruction.getDebugValues()) {
+ values.add(value);
+ assert value.debugUsers().contains(instruction);
}
}
}
for (Value value : values) {
+ assert verifyValue(value);
assert consistentValueUses(value);
}
return true;
}
+ private boolean verifyValue(Value value) {
+ assert value.isPhi() ? verifyPhi(value.asPhi()) : verifyDefinition(value);
+ return true;
+ }
+
+ private boolean verifyPhi(Phi phi) {
+ assert phi.getBlock().getPhis().contains(phi);
+ return true;
+ }
+
+ private boolean verifyDefinition(Value value) {
+ assert value.definition.outValue() == value;
+ return true;
+ }
+
private boolean consistentValueUses(Value value) {
for (Instruction user : value.uniqueUsers()) {
assert user.inValues().contains(value);
@@ -206,10 +212,13 @@
assert phiUser.getOperands().contains(value);
assert phiUser.getBlock().getPhis().contains(phiUser);
}
- if (value.debugUsers() != null) {
+ if (value.getLocalInfo() != null) {
for (Instruction debugUser : value.debugUsers()) {
- assert debugUser.getPreviousLocalValue() == value
- || debugUser.getDebugValues().contains(value);
+ assert debugUser.getDebugValues().contains(value);
+ }
+ for (Phi phiUser : value.debugPhiUsers()) {
+ assert verifyPhi(phiUser);
+ assert phiUser.getDebugValues().contains(value);
}
}
return true;
@@ -367,8 +376,8 @@
return arguments;
}
- public Value createValue(MoveType moveType, Value.DebugInfo debugInfo) {
- return new Value(valueNumberGenerator.next(), moveType, debugInfo);
+ public Value createValue(MoveType moveType, DebugLocalInfo local) {
+ return new Value(valueNumberGenerator.next(), moveType, local);
}
public Value createValue(MoveType moveType) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index b533452..3a068f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -115,6 +115,11 @@
builder.addIf(this);
}
+ // Estimated size of the resulting dex instruction in code units.
+ public static int estimatedDexSize() {
+ return 2;
+ }
+
@Override
public String toString() {
return super.toString() + " " + type + " block " + getTrueTarget().getNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e33c796..e99dbf1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -15,9 +14,11 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public abstract class Instruction {
@@ -25,7 +26,7 @@
protected final List<Value> inValues = new ArrayList<>();
private BasicBlock block = null;
private int number = -1;
- private List<Value> debugValues = null;
+ private Set<Value> debugValues = null;
protected Instruction(Value outValue) {
setOutValue(outValue);
@@ -65,17 +66,13 @@
outValue = value;
if (outValue != null) {
outValue.definition = this;
- Value previousLocalValue = getPreviousLocalValue();
- if (previousLocalValue != null) {
- previousLocalValue.addDebugUser(this);
- }
}
}
public void addDebugValue(Value value) {
assert value.getLocalInfo() != null;
if (debugValues == null) {
- debugValues = new ArrayList<>();
+ debugValues = new HashSet<>();
}
debugValues.add(value);
value.addDebugUser(this);
@@ -97,36 +94,24 @@
public abstract void buildDex(DexBuilder builder);
- public void replaceValue(Value oldValue, Value newValue) {
+ public void replaceValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
for (int i = 0; i < inValues.size(); i++) {
if (oldValue == inValues.get(i)) {
inValues.set(i, newValue);
newValue.addUser(this);
- oldValue.removeUser(this);
+ toRemove.add(this);
}
}
}
- public void replaceDebugPhi(Phi phi, Value value) {
- if (debugValues != null) {
- for (int i = 0; i < debugValues.size(); i++) {
- if (phi == debugValues.get(i)) {
- if (value.getLocalInfo() == null) {
- debugValues.remove(i);
- } else {
- debugValues.set(i, value);
- value.addDebugUser(this);
- }
- }
+ public void replaceDebugValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+ if (debugValues.remove(oldValue)) {
+ toRemove.add(this);
+ if (newValue.getLocalInfo() != null) {
+ // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+ addDebugValue(newValue);
}
- }
- if (phi == getPreviousLocalValue()) {
- if (value.getDebugInfo() == null) {
- replacePreviousLocalValue(null);
- } else {
- replacePreviousLocalValue(value);
- value.addDebugUser(this);
- }
+ // TODO(zerny): Else: Insert a write if replacing a phi with associated debug-local info.
}
}
@@ -323,24 +308,12 @@
public abstract int maxOutValueRegister();
- public DebugInfo getDebugInfo() {
- return outValue == null ? null : outValue.getDebugInfo();
- }
-
public DebugLocalInfo getLocalInfo() {
return outValue == null ? null : outValue.getLocalInfo();
}
- public Value getPreviousLocalValue() {
- return outValue == null ? null : outValue.getPreviousLocalValue();
- }
-
- public List<Value> getDebugValues() {
- return debugValues != null ? debugValues : ImmutableList.of();
- }
-
- public void replacePreviousLocalValue(Value value) {
- outValue.replacePreviousLocalValue(value);
+ public Set<Value> getDebugValues() {
+ return debugValues != null ? debugValues : ImmutableSet.of();
}
public boolean isArrayGet() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 431d338..97f1885 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -95,7 +95,8 @@
* @param code the IR code for the block this iterator originates from.
* @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
* just after the block for which this is the instruction iterator. After this method returns it
- * will be positioned just after the basic block returned.
+ * will be positioned just after the basic block returned. Calling {@link #remove} without
+ * further navigation will remove that block.
* @return Returns the new block with the instructions after the cursor.
*/
BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator);
@@ -118,9 +119,9 @@
* @param code the IR code for the block this iterator originates from.
* @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
* just after the block for this is the instruction iterator. After this method returns it will be
- * positioned just after the second block inserted. That is after the successor of the block
- * returned.
- * @return Returns the new block with the instructions right after the cursor.
+ * positioned just after the second block inserted. Calling {@link #remove} without further
+ * navigation will remove that block.
+ * @return Returns the new block with the instructions after the cursor.
*/
// TODO(sgjesse): Refactor to avoid the need for passing code and blockIterator.
BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator);
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 52a0f55..770278a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -37,7 +37,7 @@
int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
int result = foldIntegers(left, right);
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.INT, value, result);
} else {
assert type == NumericType.LONG;
@@ -50,7 +50,7 @@
right = rightValue().getConstInstruction().asConstNumber().getLongValue();
}
long result = foldLongs(left, right);
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.LONG, value, result);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 59022de..f858d70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -31,20 +31,20 @@
assert canBeFolded();
if (type == NumericType.INT) {
int result = -source().getConstInstruction().asConstNumber().getIntValue();
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.INT, value, result);
} else if (type == NumericType.LONG) {
long result = -source().getConstInstruction().asConstNumber().getLongValue();
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.LONG, value, result);
} else if (type == NumericType.FLOAT) {
float result = -source().getConstInstruction().asConstNumber().getFloatValue();
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
} else {
assert type == NumericType.DOUBLE;
double result = -source().getConstInstruction().asConstNumber().getDoubleValue();
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index cae6118..4c42b80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -27,12 +27,12 @@
assert canBeFolded();
if (type == NumericType.INT) {
int result = ~(source().getConstInstruction().asConstNumber().getIntValue());
- Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+ Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
return new ConstNumber(ConstType.INT, value, result);
} else {
assert type == NumericType.LONG;
long result = ~source().getConstInstruction().asConstNumber().getLongValue();
- Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+ Value value = code.createValue(MoveType.WIDE, getLocalInfo());
return new ConstNumber(ConstType.LONG, value, result);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 7e05dc9..96069bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -10,16 +10,19 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
public class Phi extends Value {
private final BasicBlock block;
private final List<Value> operands = new ArrayList<>();
+ private Set<Value> debugValues = null;
// Trivial phis are eliminated during IR construction. When a trivial phi is eliminated
// we need to update all references to it. A phi can be referenced from phis, instructions
@@ -36,7 +39,7 @@
private MoveType outType = null;
public Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local) {
- super(number, type, local == null ? null : new DebugInfo(local, null));
+ super(number, type, local);
this.block = block;
block.addPhi(this);
}
@@ -96,6 +99,15 @@
removeTrivialPhi();
}
+ public void addDebugValue(Value value) {
+ assert value.getLocalInfo() != null;
+ if (debugValues == null) {
+ debugValues = new HashSet<>();
+ }
+ debugValues.add(value);
+ value.addDebugPhiUser(this);
+ }
+
private void throwUndefinedValueError() {
throw new CompilationError(
"Undefined value encountered during compilation. "
@@ -143,20 +155,25 @@
current.removePhiUser(this);
}
- // Removing the phi user from the current value leads to concurrent modification errors
- // during trivial phi elimination. It is safe to not remove the phi user from current
- // since current will be unreachable after trivial phi elimination.
- // TODO(ager): can we unify the these replace methods and avoid the concurrent modification
- // issue?
- private void replaceTrivialPhi(Value current, Value newValue) {
+ void replaceTrivialPhi(Value current, Value newValue, List<Phi> toRemove) {
for (int i = 0; i < operands.size(); i++) {
if (operands.get(i) == current) {
operands.set(i, newValue);
newValue.addPhiUser(this);
+ toRemove.add(this);
}
}
}
+ void replaceTrivialDebugPhi(Value current, Value newValue, List<Phi> toRemove) {
+ assert current.getLocalInfo() != null;
+ assert current.getLocalInfo() == newValue.getLocalInfo();
+ if (debugValues.remove(current)) {
+ addDebugValue(newValue);
+ toRemove.add(this);
+ }
+ }
+
public boolean isTrivialPhi() {
Value same = null;
for (Value op : operands) {
@@ -188,28 +205,17 @@
same = op;
}
assert isTrivialPhi();
+ assert same != null : "ill-defined phi";
// Removing this phi, so get rid of it as a phi user from all of the operands to avoid
// recursively getting back here with the same phi. If the phi has itself as an operand
// that also removes the self-reference.
for (Value op : operands) {
op.removePhiUser(this);
}
- // Replace this phi with the unique value in all users.
- for (Instruction user : uniqueUsers()) {
- user.replaceValue(this, same);
- }
- for (Phi user : uniquePhiUsers()) {
- user.replaceTrivialPhi(this, same);
- }
- if (debugUsers() != null) {
- for (Instruction user : debugUsers()) {
- user.replaceDebugPhi(this, same);
- }
- }
// If IR construction is taking place, update the definition users.
if (definitionUsers != null) {
for (Map<Integer, Value> user : definitionUsers) {
- for (Map.Entry<Integer, Value> entry : user.entrySet()) {
+ for (Entry<Integer, Value> entry : user.entrySet()) {
if (entry.getValue() == this) {
entry.setValue(same);
if (same.isPhi()) {
@@ -219,9 +225,14 @@
}
}
}
- // Try to simplify phi users that might now have become trivial.
- for (Phi user : uniquePhiUsers()) {
- user.removeTrivialPhi();
+ {
+ Set<Phi> phiUsersToSimplify = uniquePhiUsers();
+ // Replace this phi with the unique value in all users.
+ replaceInUsers(same);
+ // Try to simplify phi users that might now have become trivial.
+ for (Phi user : phiUsersToSimplify) {
+ user.removeTrivialPhi();
+ }
}
// Get rid of the phi itself.
block.removePhi(this);
@@ -316,4 +327,8 @@
public boolean needsRegister() {
return true;
}
+
+ public Set<Value> getDebugValues() {
+ return debugValues != null ? debugValues : ImmutableSet.of();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 6b47ba2..6def63a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
+import com.google.common.primitives.Ints;
+import java.util.List;
public class Switch extends JumpInstruction {
@@ -48,29 +50,73 @@
}
// Number of targets if this switch is emitted as a packed switch.
- private long numberOfTargetsIfPacked() {
+ private static long numberOfTargetsIfPacked(int keys[]) {
return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
}
- private boolean canBePacked() {
+ public static boolean canBePacked(int keys[]) {
// The size of a switch payload is stored in an ushort in the Dex file.
- return numberOfTargetsIfPacked() <= Constants.U16BIT_MAX;
+ return numberOfTargetsIfPacked(keys) <= Constants.U16BIT_MAX;
+ }
+
+ // Number of targets if this switch is emitted as a packed switch.
+ public static int numberOfTargetsForPacked(int keys[]) {
+ assert canBePacked(keys);
+ return (int) numberOfTargetsIfPacked(keys);
+ }
+
+ // Size of the switch payload if emitted as packed (in code units).
+ static public long packedPayloadSize(int keys[]) {
+ return (numberOfTargetsForPacked(keys) * 2) + 4;
+ }
+
+ // Size of the switch payload if emitted as sparse (in code units).
+ public static long sparsePayloadSize(int keys[]) {
+ return (keys.length * 4) + 2;
+ }
+
+ /**
+ * Size of the switch payload instruction for the given keys. This will be the payload
+ * size for the smallest encoding of the provided keys.
+ *
+ * @param keys the switch keys
+ * @return Size of the switch payload instruction in code units
+ */
+ public static long payloadSize(List<Integer> keys) {
+ return payloadSize(Ints.toArray(keys));
+ }
+
+ /**
+ * Size of the switch payload instruction for the given keys.
+ *
+ * @see #payloadSize(List)
+ */
+ public static long payloadSize(int keys[]) {
+ long sparse = sparsePayloadSize(keys);
+ if (canBePacked(keys)) {
+ return Math.min(sparse, packedPayloadSize(keys));
+ } else {
+ return sparse;
+ }
+ }
+
+ private boolean canBePacked() {
+ return canBePacked(keys);
}
// Number of targets if this switch is emitted as a packed switch.
private int numberOfTargetsForPacked() {
- assert canBePacked();
- return (int) numberOfTargetsIfPacked();
+ return numberOfTargetsForPacked(keys);
}
// Size of the switch payload if emitted as packed (in code units).
private long packedPayloadSize() {
- return (numberOfTargetsForPacked() * 2) + 4;
+ return packedPayloadSize(keys);
}
// Size of the switch payload if emitted as sparse (in code units).
private long sparsePayloadSize() {
- return (keys.length * 4) + 2;
+ return sparsePayloadSize(keys);
}
private boolean emitPacked() {
@@ -121,6 +167,10 @@
return keys[index];
}
+ public int[] getKeys() {
+ return keys;
+ }
+
public int[] targetBlockIndices() {
return targetBlockIndices;
}
@@ -192,6 +242,7 @@
StringBuilder builder = new StringBuilder(super.toString()+ "\n");
for (int i = 0; i < numberOfKeys(); i++) {
builder.append(" ");
+ builder.append(getKey(i));
builder.append(" -> ");
builder.append(targetBlock(i).getNumber());
builder.append("\n");
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 44bd9d4..dfa8548 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -18,39 +18,17 @@
public class Value {
- /**
- * Immutable view of the debug info associated with an SSA value.
- *
- * Used during IR building and to construct replacement values.
- */
- public static class DebugInfo {
- private final DebugLocalInfo local;
- private final Value previousLocalValue;
-
- public DebugInfo(DebugLocalInfo local, Value previousLocalValue) {
- assert local != null;
- this.local = local;
- this.previousLocalValue = previousLocalValue;
- }
- }
-
- // Actual internal data for the debug information of locals.
+ // Lazily allocated internal data for the debug information of locals.
// This is wrapped in a class to avoid multiple pointers in the value structure.
private static class DebugData {
final DebugLocalInfo local;
- Value previousLocalValue;
- Set<Instruction> debugUsers = new HashSet<>();
+ Set<Instruction> users = new HashSet<>();
+ Set<Phi> phiUsers = new HashSet<>();
List<Instruction> localStarts = new ArrayList<>();
List<Instruction> localEnds = new ArrayList<>();
- DebugData(DebugInfo info) {
- this(info.local, info.previousLocalValue);
- }
-
- DebugData(DebugLocalInfo local, Value previousLocalValue) {
- assert previousLocalValue == null || !previousLocalValue.isUninitializedLocal();
+ DebugData(DebugLocalInfo local) {
this.local = local;
- this.previousLocalValue = previousLocalValue;
}
}
@@ -70,13 +48,14 @@
private boolean neverNull = false;
private boolean isThis = false;
private boolean isArgument = false;
+ private boolean knownToBeBoolean = false;
private LongInterval valueRange;
private final DebugData debugData;
- public Value(int number, MoveType type, DebugInfo debugInfo) {
+ public Value(int number, MoveType type, DebugLocalInfo local) {
this.number = number;
this.type = type;
- this.debugData = debugInfo == null ? null : new DebugData(debugInfo);
+ this.debugData = local == null ? null : new DebugData(local);
}
public boolean isFixedRegisterValue() {
@@ -95,26 +74,10 @@
return type.requiredRegisters();
}
- public DebugInfo getDebugInfo() {
- return debugData == null ? null : new DebugInfo(debugData.local, debugData.previousLocalValue);
- }
-
public DebugLocalInfo getLocalInfo() {
return debugData == null ? null : debugData.local;
}
- public Value getPreviousLocalValue() {
- return debugData == null ? null : debugData.previousLocalValue;
- }
-
- public void replacePreviousLocalValue(Value value) {
- if (value == null || value.isUninitializedLocal()) {
- debugData.previousLocalValue = null;
- } else {
- debugData.previousLocalValue = value;
- }
- }
-
public List<Instruction> getDebugLocalStarts() {
return debugData.localStarts;
}
@@ -189,10 +152,11 @@
}
public Set<Instruction> debugUsers() {
- if (debugData == null) {
- return null;
- }
- return Collections.unmodifiableSet(debugData.debugUsers);
+ return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+ }
+
+ public Set<Phi> debugPhiUsers() {
+ return debugData == null ? null : Collections.unmodifiableSet(debugData.phiUsers);
}
public int numberOfUsers() {
@@ -211,18 +175,30 @@
return uniquePhiUsers().size();
}
+ public int numberOfAllNonDebugUsers() {
+ return numberOfUsers() + numberOfPhiUsers();
+ }
+
public int numberOfDebugUsers() {
- return debugData == null ? 0 : debugData.debugUsers.size();
+ return debugData == null ? 0 : debugData.users.size();
+ }
+
+ public int numberOfDebugPhiUsers() {
+ return debugData == null ? 0 : debugData.phiUsers.size();
+ }
+
+ public int numberOfAllDebugUsers() {
+ return numberOfDebugUsers() + numberOfDebugPhiUsers();
}
public int numberOfAllUsers() {
- return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
+ return numberOfAllNonDebugUsers() + numberOfAllDebugUsers();
}
public boolean isUsed() {
return !users.isEmpty()
|| !phiUsers.isEmpty()
- || ((debugData != null) && !debugData.debugUsers.isEmpty());
+ || ((debugData != null) && !debugData.users.isEmpty());
}
public boolean usedInMonitorOperation() {
@@ -250,7 +226,7 @@
phiUsers.clear();
uniquePhiUsers = null;
if (debugData != null) {
- debugData.debugUsers.clear();
+ debugData.users.clear();
}
}
@@ -268,7 +244,14 @@
if (isUninitializedLocal()) {
return;
}
- debugData.debugUsers.add(user);
+ debugData.users.add(user);
+ }
+
+ public void addDebugPhiUser(Phi user) {
+ if (isUninitializedLocal()) {
+ return;
+ }
+ debugData.phiUsers.add(user);
}
public boolean isUninitializedLocal() {
@@ -280,7 +263,11 @@
}
public void removeDebugUser(Instruction user) {
- debugData.debugUsers.remove(user);
+ debugData.users.remove(user);
+ }
+
+ public void removeDebugPhiUser(Phi user) {
+ debugData.phiUsers.remove(user);
}
public boolean hasUsersInfo() {
@@ -293,7 +280,8 @@
phiUsers = null;
uniquePhiUsers = null;
if (debugData != null) {
- debugData.debugUsers = null;
+ debugData.users = null;
+ debugData.phiUsers = null;
}
}
@@ -321,22 +309,52 @@
}
if (debugData != null) {
for (Instruction user : debugUsers()) {
- user.getDebugValues().replaceAll(v -> {
- if (v == this) {
- newValue.addDebugUser(user);
- return newValue;
- }
- return v;
- });
- if (user.getPreviousLocalValue() == this) {
- newValue.addDebugUser(user);
- user.replacePreviousLocalValue(newValue);
+ if (user.getDebugValues().remove(this)) {
+ user.addDebugValue(newValue);
+ }
+ }
+ for (Phi user : debugPhiUsers()) {
+ if (user.getDebugValues().remove(this)) {
+ user.addDebugValue(newValue);
}
}
}
clearUsers();
}
+ public void replaceInUsers(Value newValue) {
+ if (!uniqueUsers().isEmpty()) {
+ List<Instruction> toRemove = new ArrayList<>(uniqueUsers().size());
+ for (Instruction user : uniqueUsers()) {
+ user.replaceValue(this, newValue, toRemove);
+ }
+ toRemove.forEach(this::removeUser);
+ }
+ if (!uniquePhiUsers().isEmpty()) {
+ List<Phi> toRemove = new ArrayList<>(uniquePhiUsers().size());
+ for (Phi user : uniquePhiUsers()) {
+ user.replaceTrivialPhi(this, newValue, toRemove);
+ }
+ toRemove.forEach(this::removePhiUser);
+ }
+ if (debugData != null) {
+ if (!debugUsers().isEmpty()) {
+ List<Instruction> toRemove = new ArrayList<>(debugUsers().size());
+ for (Instruction user : debugUsers()) {
+ user.replaceDebugValue(this, newValue, toRemove);
+ }
+ toRemove.forEach(this::removeDebugUser);
+ }
+ if (!debugPhiUsers().isEmpty()) {
+ List<Phi> toRemove = new ArrayList<>(debugPhiUsers().size());
+ for (Phi user : debugPhiUsers()) {
+ user.replaceTrivialDebugPhi(this, newValue, toRemove);
+ }
+ toRemove.forEach(this::removeDebugPhiUser);
+ }
+ }
+ }
+
public void setLiveIntervals(LiveIntervals intervals) {
assert liveIntervals == null;
liveIntervals = intervals;
@@ -477,6 +495,14 @@
return isArgument;
}
+ public void setKnownToBeBoolean(boolean knownToBeBoolean) {
+ this.knownToBeBoolean = knownToBeBoolean;
+ }
+
+ public boolean knownToBeBoolean() {
+ return knownToBeBoolean;
+ }
+
public void markAsThis() {
assert isArgument;
assert !isThis;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4b7de62..e10c690 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -76,7 +76,6 @@
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Ushr;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.utils.InternalOptions;
@@ -270,6 +269,7 @@
private final InternalOptions options;
// Pending local changes.
+ private Value previousLocalValue = null;
private List<Value> debugLocalStarts = new ArrayList<>();
private List<Value> debugLocalReads = new ArrayList<>();
private List<Value> debugLocalEnds = new ArrayList<>();
@@ -490,16 +490,21 @@
public void addThisArgument(int register) {
DebugLocalInfo local = getCurrentLocal(register);
- DebugInfo info = local == null ? null : new DebugInfo(local, null);
- Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, info);
+ Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, local);
addInstruction(new Argument(value));
value.markAsThis();
}
public void addNonThisArgument(int register, MoveType moveType) {
DebugLocalInfo local = getCurrentLocal(register);
- DebugInfo info = local == null ? null : new DebugInfo(local, null);
- Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, info);
+ Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, local);
+ addInstruction(new Argument(value));
+ }
+
+ public void addBooleanNonThisArgument(int register) {
+ DebugLocalInfo local = getCurrentLocal(register);
+ Value value = writeRegister(register, MoveType.SINGLE, ThrowingInfo.NO_THROW, local);
+ value.setKnownToBeBoolean(true);
addInstruction(new Argument(value));
}
@@ -616,6 +621,7 @@
Value in1 = readRegister(array, MoveType.OBJECT);
Value in2 = readRegister(index, MoveType.SINGLE);
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
ArrayGet instruction = new ArrayGet(type, out, in1, in2);
assert instruction.instructionTypeCanThrow();
add(instruction);
@@ -886,6 +892,7 @@
DexField field) {
Value in = readRegister(object, MoveType.OBJECT);
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
InstanceGet instruction = new InstanceGet(type, out, in, field);
assert instruction.instructionTypeCanThrow();
addInstruction(instruction);
@@ -1104,7 +1111,7 @@
public void addMoveException(int dest) {
Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
- assert out.getDebugInfo() == null;
+ assert out.getLocalInfo() == null;
MoveException instruction = new MoveException(out);
assert !instruction.instructionTypeCanThrow();
if (!currentBlock.getInstructions().isEmpty()) {
@@ -1124,6 +1131,16 @@
invoke.setOutValue(writeRegister(dest, type, ThrowingInfo.CAN_THROW));
}
+ public void addBooleanMoveResult(MoveType type, int dest) {
+ List<Instruction> instructions = currentBlock.getInstructions();
+ Invoke invoke = instructions.get(instructions.size() - 1).asInvoke();
+ assert invoke.outValue() == null;
+ assert invoke.instructionTypeCanThrow();
+ Value outValue = writeRegister(dest, type, ThrowingInfo.CAN_THROW);
+ outValue.setKnownToBeBoolean(true);
+ invoke.setOutValue(outValue);
+ }
+
public void addNeg(NumericType type, int dest, int value) {
Value in = readNumericRegister(value, type);
Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -1175,6 +1192,7 @@
public void addStaticGet(MemberType type, int dest, DexField field) {
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
StaticGet instruction = new StaticGet(type, out, field);
assert instruction.instructionTypeCanThrow();
addInstruction(instruction);
@@ -1509,21 +1527,18 @@
// This special write register is needed when changing the scoping of a local variable.
// See addDebugLocalStart and addDebugLocalEnd.
- private Value writeRegister(int register, MoveType type, ThrowingInfo throwing, DebugInfo info) {
+ private Value writeRegister(
+ int register, MoveType type, ThrowingInfo throwing, DebugLocalInfo local) {
checkRegister(register);
- Value value = new Value(valueNumberGenerator.next(), type, info);
+ Value value = new Value(valueNumberGenerator.next(), type, local);
currentBlock.writeCurrentDefinition(register, value, throwing);
return value;
}
public Value writeRegister(int register, MoveType type, ThrowingInfo throwing) {
DebugLocalInfo local = getCurrentLocal(register);
- DebugInfo info = null;
- if (local != null) {
- Value previousLocal = readRegisterIgnoreLocal(register, type);
- info = new DebugInfo(local, previousLocal.getLocalInfo() != local ? null : previousLocal);
- }
- return writeRegister(register, type, throwing, info);
+ previousLocalValue = local == null ? null : readRegisterIgnoreLocal(register, type);
+ return writeRegister(register, type, throwing, local);
}
public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
@@ -1604,6 +1619,12 @@
if (!options.debug) {
return;
}
+ // Add a use if this instruction is overwriting a previous value of the same local.
+ if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
+ assert ir.outValue() != null;
+ ir.addDebugValue(previousLocalValue);
+ }
+ previousLocalValue = null;
if (debugLocalStarts.isEmpty() && debugLocalReads.isEmpty() && debugLocalEnds.isEmpty()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 116a2f0..668e220 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.Descriptor;
@@ -261,7 +262,8 @@
@Override
public void buildPrelude(IRBuilder builder) {
// Record types for arguments.
- Map<Integer, MoveType> initializedLocals = recordArgumentTypes();
+ Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
+ Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
// Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
for (Object o : node.localVariables) {
LocalVariableNode local = (LocalVariableNode) o;
@@ -295,7 +297,11 @@
}
// Add debug information for all locals at the initial label.
if (initialLabel != null) {
- state.openLocals(initialLabel);
+ for (Local local : state.openLocals(initialLabel)) {
+ if (!argumentLocals.containsKey(local.slot.register)) {
+ builder.addDebugLocalStart(local.slot.register, local.info);
+ }
+ }
}
// Build the actual argument instructions now that type and debug information is known
// for arguments.
@@ -332,7 +338,11 @@
for (Type type : parameterTypes) {
MoveType moveType = moveType(type);
Slot slot = state.readLocal(argumentRegister, type);
- builder.addNonThisArgument(slot.register, moveType);
+ if (type == Type.BOOLEAN_TYPE) {
+ builder.addBooleanNonThisArgument(slot.register);
+ } else {
+ builder.addNonThisArgument(slot.register, moveType);
+ }
argumentRegister += moveType.requiredRegisters();
}
}
@@ -1731,9 +1741,10 @@
state.push(Type.DOUBLE_TYPE);
} else if (insn.cst instanceof Integer) {
state.push(Type.INT_TYPE);
- } else {
- assert insn.cst instanceof Float;
+ } else if (insn.cst instanceof Float) {
state.push(Type.FLOAT_TYPE);
+ } else {
+ throw new CompilationError("Unsupported constant: " + insn.cst.toString());
}
}
@@ -2549,7 +2560,11 @@
// Move the result to the "top of stack".
Type returnType = Type.getReturnType(methodDesc);
if (returnType != Type.VOID_TYPE) {
- builder.addMoveResult(moveType(returnType), state.push(returnType));
+ if (returnType == Type.BOOLEAN_TYPE) {
+ builder.addBooleanMoveResult(moveType(returnType), state.push(returnType));
+ } else {
+ builder.addMoveResult(moveType(returnType), state.push(returnType));
+ }
}
}
@@ -2721,10 +2736,11 @@
} else if (insn.cst instanceof Integer) {
int dest = state.push(Type.INT_TYPE);
builder.addIntConst(dest, (Integer) insn.cst);
- } else {
- assert insn.cst instanceof Float;
+ } else if (insn.cst instanceof Float) {
int dest = state.push(Type.FLOAT_TYPE);
builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst));
+ } else {
+ throw new CompilationError("Unsupported constant: " + insn.cst.toString());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index a7c40d1..71de2b6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -51,7 +51,7 @@
if (insn.outValue() == null) {
return null;
} else {
- return code.createValue(insn.outType(), insn.getDebugInfo());
+ return code.createValue(insn.outType(), insn.getLocalInfo());
}
}
@@ -105,7 +105,7 @@
// Fix up the return type if needed.
if (actualTarget.proto.returnType != invokedMethod.proto.returnType
&& newInvoke.outValue() != null) {
- Value newValue = code.createValue(newInvoke.outType(), invoke.getDebugInfo());
+ Value newValue = code.createValue(newInvoke.outType(), invoke.getLocalInfo());
newInvoke.outValue().replaceUsers(newValue);
CheckCast cast = new CheckCast(
newValue,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index bfaee32..a4de54f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -134,6 +134,11 @@
}
if (b.isPrimitiveType()) {
+ if (a == factory.objectType) {
+ // `a` is java.lang.Object in which case we assume it represented by
+ // proper boxed type.
+ return true;
+ }
// `a` is a boxed type for `a*` which can be
// widened to primitive type `b`.
DexType unboxedA = getPrimitiveFromBoxed(a);
@@ -313,9 +318,16 @@
// type, the value must be unboxed and converted to the resulting type via primitive
// widening conversion.
if (toTypePrimitive) {
- DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(fromType);
+ DexType boxedType = fromType;
+ if (boxedType == factory().objectType) {
+ // We are in situation when from(=java.lang.Object) is being adjusted to a
+ // primitive type, in which case we assume it is of proper box type.
+ boxedType = getBoxedForPrimitiveType(toType);
+ register = castToBoxedType(register, boxedType);
+ }
+ DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(boxedType);
if (fromTypeAsPrimitive != null) {
- int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, fromType);
+ int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, boxedType);
return addPrimitiveWideningConversion(unboxedRegister, fromTypeAsPrimitive, toType);
}
}
@@ -461,6 +473,11 @@
return result;
}
+ private int castToBoxedType(int register, DexType boxType) {
+ add(builder -> builder.addCheckCast(register, boxType));
+ return register;
+ }
+
private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) {
// Generate factory method fo boxing.
DexItemFactory factory = factory();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 815efe4..d44ecd9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -37,6 +37,7 @@
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.ConstType;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
@@ -63,6 +64,7 @@
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
@@ -72,8 +74,13 @@
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -336,8 +343,128 @@
}
}
+ // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
+ public static class SwitchBuilder {
+ private Value value;
+ private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
+ private BasicBlock fallthrough;
+ private int blockNumber;
+
+ public SwitchBuilder setValue(Value value) {
+ this.value = value;
+ return this;
+ }
+
+ public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
+ keyToTarget.put(key, target);
+ return this;
+ }
+
+ public SwitchBuilder setFallthrough(BasicBlock fallthrough) {
+ this.fallthrough = fallthrough;
+ return this;
+ }
+
+ public SwitchBuilder setBlockNumber(int blockNumber) {
+ this.blockNumber = blockNumber;
+ return this;
+ }
+
+ public BasicBlock build() {
+ final int NOT_FOUND = -1;
+ Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
+ targetToSuccessorIndex.defaultReturnValue(NOT_FOUND);
+
+ int[] keys = new int[keyToTarget.size()];
+ int[] targetBlockIndices = new int[keyToTarget.size()];
+ // Sort keys descending.
+ int count = 0;
+ IntIterator iter = keyToTarget.keySet().iterator();
+ while (iter.hasNext()) {
+ int key = iter.nextInt();
+ BasicBlock target = keyToTarget.get(key);
+ Integer targetIndex =
+ targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size());
+ keys[count] = key;
+ targetBlockIndices[count] = targetIndex;
+ count++;
+ }
+ Integer fallthroughIndex =
+ targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
+ Switch newSwitch = new Switch(value, keys, targetBlockIndices, fallthroughIndex);
+ BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch);
+ for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
+ newSwitchBlock.link(successor);
+ }
+ return newSwitchBlock;
+ }
+ }
+
+ /**
+ * Covert the switch instruction to a sequence of if instructions checking for a specified
+ * set of keys, followed by a new switch with the remaining keys.
+ */
+ private void convertSwitchToSwitchAndIfs(
+ IRCode code, ListIterator<BasicBlock> blocksIterator, BasicBlock originalBlock,
+ InstructionListIterator iterator, Switch theSwitch, List<Integer> keysToRemove) {
+ // Split the switch instruction into its own block and remove it.
+ iterator.previous();
+ BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
+ assert !originalSwitchBlock.hasCatchHandlers();
+ assert originalSwitchBlock.getInstructions().size() == 1;
+ blocksIterator.remove();
+
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
+ // Collect targets for the keys to peel off, and create a new switch instruction without
+ // these keys.
+ SwitchBuilder switchBuilder = new SwitchBuilder();
+ List<BasicBlock> peeledOffTargets = new ArrayList<>();
+ for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
+ BasicBlock target = theSwitch.targetBlock(i);
+ if (!keysToRemove.contains(theSwitch.getKey(i))) {
+ switchBuilder.addKeyAndTarget(theSwitch.getKey(i), theSwitch.targetBlock(i));
+ } else {
+ peeledOffTargets.add(target);
+ }
+ }
+ assert peeledOffTargets.size() == keysToRemove.size();
+ switchBuilder.setValue(theSwitch.value());
+ switchBuilder.setFallthrough(theSwitch.fallthroughBlock());
+ switchBuilder.setBlockNumber(nextBlockNumber++);
+ theSwitch.getBlock().detachAllSuccessors();
+ BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
+ assert theSwitch.getBlock().getPredecessors().size() == 0;
+ assert theSwitch.getBlock().getSuccessors().size() == 0;
+ assert block == originalBlock;
+
+ BasicBlock newSwitchBlock = switchBuilder.build();
+
+ // Create if blocks for each of the peeled off keys, and link them into the graph.
+ BasicBlock predecessor = originalBlock;
+ for (int i = 0; i < keysToRemove.size(); i++) {
+ int key = keysToRemove.get(i);
+ BasicBlock peeledOffTarget = peeledOffTargets.get(i);
+ ConstNumber keyConst = code.createIntConstant(key);
+ If theIf = new If(Type.EQ, ImmutableList.of(keyConst.dest(), theSwitch.value()));
+ BasicBlock ifBlock = BasicBlock.createIfBlock(nextBlockNumber++, theIf, keyConst);
+ predecessor.link(ifBlock);
+ ifBlock.link(peeledOffTarget);
+ predecessor = ifBlock;
+ blocksIterator.add(ifBlock);
+ assert !peeledOffTarget.getPredecessors().contains(theSwitch.getBlock());
+ }
+ predecessor.link(newSwitchBlock);
+ blocksIterator.add(newSwitchBlock);
+
+ // The switch fallthrough block is still the same, and it is right after the new switch block.
+ assert newSwitchBlock.exit().fallthroughBlock() == IteratorUtils.peekNext(blocksIterator);
+ }
+
public void rewriteSwitch(IRCode code) {
- for (BasicBlock block : code.blocks) {
+ ListIterator<BasicBlock> blocksIterator = code.listIterator();
+ while (blocksIterator.hasNext()) {
+ BasicBlock block = blocksIterator.next();
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
@@ -361,6 +488,54 @@
If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
iterator.replaceCurrentInstruction(theIf);
}
+ } else {
+ // Split keys into outliers and sequences.
+ List<List<Integer>> sequences = new ArrayList<>();
+ List<Integer> outliers = new ArrayList<>();
+
+ List<Integer> current = new ArrayList<>();
+ int[] keys = theSwitch.getKeys();
+ int previousKey = keys[0];
+ current.add(previousKey);
+ for (int i = 1; i < keys.length; i++) {
+ assert current.size() > 0;
+ assert current.get(current.size() - 1) == previousKey;
+ int key = keys[i];
+ if (((long) key - (long) previousKey) > 1) {
+ if (current.size() == 1) {
+ outliers.add(previousKey);
+ } else {
+ sequences.add(current);;
+ }
+ current = new ArrayList<>();
+ }
+ current.add(key);
+ previousKey = key;
+ }
+ if (current.size() == 1) {
+ outliers.add(previousKey);
+ } else {
+ sequences.add(current);
+ }
+
+ // Only check for rewrite if there is one sequence and one or two outliers.
+ if (sequences.size() == 1 && outliers.size() <= 2) {
+ // Get the existing dex size for the payload (excluding the switch itself).
+ long currentSize = Switch.payloadSize(keys);
+ // Estimate the dex size of the rewritten payload and the additional if instructions.
+ long rewrittenSize = Switch.payloadSize(sequences.get(0));
+ for (Integer outlier : outliers) {
+ rewrittenSize += ConstNumber.estimatedDexSize(
+ ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ rewrittenSize += If.estimatedDexSize();
+ }
+ // Rewrite if smaller.
+ if (rewrittenSize < currentSize) {
+ convertSwitchToSwitchAndIfs(
+ code, blocksIterator, block, iterator, theSwitch, outliers);
+ assert code.isConsistentSSA();
+ }
+ }
}
}
}
@@ -797,6 +972,7 @@
while (it.hasNext()) {
Instruction current = it.next();
if (current.isCheckCast()
+ && current.getLocalInfo() == null
&& current.outValue() != null && current.outValue().isUsed()
&& current.outValue().numberOfPhiUsers() == 0) {
CheckCast checkCast = current.asCheckCast();
@@ -928,13 +1104,15 @@
assert instruction.outValue().numberOfUsers() != 0;
ConstNumber constNumber = instruction.asConstNumber();
Value constantValue = instruction.outValue();
+ List<Instruction> toRemove = new ArrayList<>(constantValue.uniqueUsers().size());
for (Instruction user : constantValue.uniqueUsers()) {
ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
InstructionListIterator iterator = user.getBlock().listIterator(user);
iterator.previous();
iterator.add(newCstNum);
- user.replaceValue(constantValue, newCstNum.outValue());
+ user.replaceValue(constantValue, newCstNum.outValue(), toRemove);
}
+ toRemove.forEach(constantValue::removeUser);
}
}
} else {
@@ -1257,10 +1435,16 @@
// First rewrite zero comparison.
rewriteIfWithConstZero(block);
+ if (simplifyKnownBooleanCondition(dominator, block)) {
+ continue;
+ }
+
// Simplify if conditions when possible.
If theIf = block.exit().asIf();
List<Value> inValues = theIf.inValues();
+
int cond;
+
if (inValues.get(0).isConstNumber()
&& (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
// Zero test with a constant of comparison between between two constants.
@@ -1297,22 +1481,83 @@
BasicBlock target = theIf.targetFromCondition(cond);
BasicBlock deadTarget =
target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
- List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
- for (BasicBlock removedBlock : removedBlocks) {
- if (!removedBlock.isMarked()) {
- removedBlock.mark();
- }
- }
- assert theIf == block.exit();
- block.replaceLastInstruction(new Goto());
- assert block.exit().isGoto();
- assert block.exit().asGoto().getTarget() == target;
+ rewriteIfToGoto(dominator, block, theIf, target, deadTarget);
}
}
code.removeMarkedBlocks();
assert code.isConsistentSSA();
}
+
+ /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
+ *
+ * ifeqz booleanValue ifnez booleanValue
+ * / \ / \
+ * \ / \ /
+ * phi(0, 1) phi(1, 0)
+ *
+ * which can be replaced by a fallthrough and the phi value can be replaced
+ * with the boolean value itself.
+ */
+ private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+ If theIf = block.exit().asIf();
+ Value testValue = theIf.inValues().get(0);
+ if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
+ BasicBlock trueBlock = theIf.getTrueTarget();
+ BasicBlock falseBlock = theIf.fallthroughBlock();
+ if (trueBlock.isTrivialGoto() &&
+ falseBlock.isTrivialGoto() &&
+ trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) {
+ BasicBlock targetBlock = trueBlock.getSuccessors().get(0);
+ if (targetBlock.getPredecessors().size() == 2) {
+ int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock);
+ int falseIndex = trueIndex == 0 ? 1 : 0;
+ int deadPhis = 0;
+ // Locate the phis that have the same value as the boolean and replace them
+ // by the boolean in all users.
+ for (Phi phi : targetBlock.getPhis()) {
+ Value trueValue = phi.getOperand(trueIndex);
+ Value falseValue = phi.getOperand(falseIndex);
+ if (trueValue.isConstNumber() && falseValue.isConstNumber()) {
+ ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber();
+ ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber();
+ if ((theIf.getType() == Type.EQ &&
+ trueNumber.isIntegerZero() &&
+ falseNumber.isIntegerOne()) ||
+ (theIf.getType() == Type.NE &&
+ trueNumber.isIntegerOne() &&
+ falseNumber.isIntegerZero())) {
+ phi.replaceUsers(testValue);
+ deadPhis++;
+ }
+ }
+ }
+ // If all phis were removed, there is no need for the diamond shape anymore
+ // and it can be rewritten to a goto to one of the branches.
+ if (deadPhis == targetBlock.getPhis().size()) {
+ rewriteIfToGoto(dominator, block, theIf, trueBlock, falseBlock);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void rewriteIfToGoto(DominatorTree dominator, BasicBlock block, If theIf,
+ BasicBlock target, BasicBlock deadTarget) {
+ List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
+ for (BasicBlock removedBlock : removedBlocks) {
+ if (!removedBlock.isMarked()) {
+ removedBlock.mark();
+ }
+ }
+ assert theIf == block.exit();
+ block.replaceLastInstruction(new Goto());
+ assert block.exit().isGoto();
+ assert block.exit().asGoto().getTarget() == target;
+ }
+
private void rewriteIfWithConstZero(BasicBlock block) {
If theIf = block.exit().asIf();
if (theIf.isZeroTest()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 3a1902e..50f224c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -61,10 +61,6 @@
for (Value debugValue : instruction.getDebugValues()) {
updateWorklist(worklist, debugValue);
}
- Value previousLocalValue = instruction.getPreviousLocalValue();
- if (previousLocalValue != null) {
- updateWorklist(worklist, previousLocalValue);
- }
}
private static void removeDeadPhis(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a34c8cb..241106c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -75,7 +75,7 @@
MoveType moveType = instruction.outValue().outType();
if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
assert moveType != MoveType.OBJECT;
- Value value = code.createValue(moveType, instruction.getDebugInfo());
+ Value value = code.createValue(moveType, instruction.getLocalInfo());
replacement = new ConstNumber(
ConstType.fromMoveType(moveType), value, rule.getReturnValue().getSingleValue());
}
@@ -84,7 +84,7 @@
DexField field = rule.getReturnValue().getField();
DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
if (staticField != null) {
- Value value = code.createValue(moveType, instruction.getDebugInfo());
+ Value value = code.createValue(moveType, instruction.getLocalInfo());
replacement = staticField.staticValue.asConstInstruction(false, value);
} else {
throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 460fe92..6cd453b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1841,6 +1841,8 @@
int predIndex = succ.getPredecessors().indexOf(block);
for (Phi phi : succ.getPhis()) {
live.add(phi.getOperand(predIndex));
+ assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+ live.addAll(phi.getDebugValues());
}
}
ListIterator<Instruction> iterator =
@@ -1855,17 +1857,8 @@
live.add(use);
}
}
- if (options.debug) {
- for (Value use : instruction.getDebugValues()) {
- assert use.needsRegister();
- live.add(use);
- }
- Value use = instruction.getPreviousLocalValue();
- if (use != null) {
- assert use.needsRegister();
- live.add(use);
- }
- }
+ assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+ live.addAll(instruction.getDebugValues());
}
for (Phi phi : block.getPhis()) {
live.remove(phi);
@@ -1942,6 +1935,8 @@
for (Phi phi : successor.getPhis()) {
live.remove(phi);
phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
+ assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+ phiOperands.addAll(phi.getDebugValues());
}
}
live.addAll(phiOperands);
@@ -1990,14 +1985,6 @@
addLiveRange(use, block, number);
}
}
- Value use = instruction.getPreviousLocalValue();
- if (use != null) {
- assert use.needsRegister();
- if (!live.contains(use)) {
- live.add(use);
- addLiveRange(use, block, number);
- }
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index c1e8515..f4bd577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -9,7 +9,10 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
@@ -40,6 +43,8 @@
private final AppInfoWithLiveness appInfo;
private final RootSet rootSet;
private final PackageObfuscationMode packageObfuscationMode;
+ private final boolean isAccessModificationAllowed;
+ private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
private final Set<String> usedPackagePrefixes = Sets.newHashSet();
private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
@@ -63,6 +68,7 @@
this.appInfo = appInfo;
this.rootSet = rootSet;
this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
+ this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
this.classDictionary = options.proguardConfiguration.getClassObfuscationDictionary();
this.keepInnerClassStructure = options.attributeRemoval.signature;
@@ -78,7 +84,7 @@
// Collect names we have to keep.
timing.begin("reserve");
for (DexClass clazz : classes) {
- if (rootSet.noObfuscation.contains(clazz)) {
+ if (rootSet.noObfuscation.contains(clazz.type)) {
assert !renaming.containsKey(clazz.type);
registerClassAsUsed(clazz.type);
}
@@ -94,6 +100,12 @@
}
timing.end();
+ timing.begin("rename-dangling-types");
+ for (DexClass clazz : classes) {
+ renameDanglingTypes(clazz);
+ }
+ timing.end();
+
timing.begin("rename-generic");
renameTypesInGenericSignatures();
timing.end();
@@ -105,6 +117,35 @@
return Collections.unmodifiableMap(renaming);
}
+ private void renameDanglingTypes(DexClass clazz) {
+ clazz.forEachMethod(this::renameDanglingTypesInMethod);
+ clazz.forEachField(this::renameDanglingTypesInField);
+ }
+
+ private void renameDanglingTypesInField(DexEncodedField field) {
+ renameDanglingType(field.field.type);
+ }
+
+ private void renameDanglingTypesInMethod(DexEncodedMethod method) {
+ DexProto proto = method.method.proto;
+ renameDanglingType(proto.returnType);
+ for (DexType type : proto.parameters.values) {
+ renameDanglingType(type);
+ }
+ }
+
+ private void renameDanglingType(DexType type) {
+ if (appInfo.wasPruned(type)
+ && !renaming.containsKey(type)
+ && !rootSet.noObfuscation.contains(type)) {
+ // We have a type that is defined in the program source but is only used in a proto or
+ // return type. As we don't need the class, we can rename it to anything as long as it is
+ // unique.
+ assert appInfo.definitionFor(type) == null;
+ renaming.put(type, topLevelState.nextTypeName());
+ }
+ }
+
private void renameTypesInGenericSignatures() {
for (DexClass clazz : appInfo.classes()) {
rewriteGenericSignatures(clazz.annotations.annotations,
@@ -164,6 +205,11 @@
* Registers the given package prefix and all of parent packages as used.
*/
private void registerPackagePrefixesAsUsed(String packagePrefix) {
+ // If -allowaccessmodification is not set, we may keep classes in their original packages,
+ // accounting for package-private accesses.
+ if (!isAccessModificationAllowed) {
+ noObfuscationPrefixes.add(packagePrefix);
+ }
String usedPrefix = packagePrefix;
while (usedPrefix.length() > 0) {
usedPackagePrefixes.add(usedPrefix);
@@ -209,7 +255,9 @@
private Namespace getStateForClass(DexClass clazz) {
String packageName = getPackageBinaryNameFromJavaType(clazz.type.getPackageDescriptor());
// Check whether the given class should be kept.
- if (rootSet.keepPackageName.contains(clazz)) {
+ // or check whether the given class belongs to a package that is kept for another class.
+ if (rootSet.keepPackageName.contains(clazz)
+ || noObfuscationPrefixes.contains(packageName)) {
return states.computeIfAbsent(packageName, Namespace::new);
}
Namespace state = topLevelState;
@@ -320,6 +368,7 @@
do {
candidate = appInfo.dexItemFactory.createString(nextSuggestedNameForClass());
} while (usedTypeNames.contains(candidate));
+ usedTypeNames.add(candidate);
return candidate;
}
@@ -342,6 +391,7 @@
do {
candidate = nextSuggestedNameForSubpackage();
} while (usedPackagePrefixes.contains(candidate));
+ usedPackagePrefixes.add(candidate);
return candidate;
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 951790c..a26e135 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -36,10 +36,6 @@
public NamingLens run(Timing timing) {
assert !options.skipMinification;
- // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
- if (!options.proguardConfiguration.isAccessModificationAllowed()) {
- throw new CompilationError("Minification requires allowaccessmodification.");
- }
timing.begin("MinifyClasses");
Map<DexType, DexString> classRenaming =
new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 524207e..ff8adee 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -927,13 +928,13 @@
SortedSet<DexField> collectFieldsRead() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
}
SortedSet<DexField> collectFieldsWritten() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
}
private static class Action {
@@ -1099,6 +1100,10 @@
* Map from the class of an extension to the state it produced.
*/
public final Map<Class, Object> extensions;
+ /**
+ * A set of types that have been removed by the {@link TreePruner}.
+ */
+ public final Set<DexType> prunedTypes;
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
@@ -1124,11 +1129,13 @@
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.extensions = enqueuer.extensionsState;
+ this.prunedTypes = Collections.emptySet();
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
- private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application) {
+ private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
+ Collection<DexType> removedClasses) {
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedTypes = previous.instantiatedTypes;
@@ -1151,6 +1158,7 @@
this.staticInvokes = previous.staticInvokes;
this.extensions = previous.extensions;
this.alwaysInline = previous.alwaysInline;
+ this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1178,6 +1186,7 @@
this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
this.alwaysInline = previous.alwaysInline;
this.extensions = previous.extensions;
+ this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1209,6 +1218,13 @@
return builder.build();
}
+ private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
+ ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ builder.addAll(first);
+ builder.addAll(second);
+ return builder.build();
+ }
+
@SuppressWarnings("unchecked")
public <T> T getExtension(Class extension, T defaultValue) {
if (extensions.containsKey(extension)) {
@@ -1237,14 +1253,23 @@
* Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
* given DexApplication object.
*/
- public AppInfoWithLiveness prunedCopyFrom(DexApplication application) {
- return new AppInfoWithLiveness(this, application);
+ public AppInfoWithLiveness prunedCopyFrom(DexApplication application,
+ Collection<DexType> removedClasses) {
+ return new AppInfoWithLiveness(this, application, removedClasses);
}
public AppInfoWithLiveness rewrittenWithLense(GraphLense lense) {
assert lense.isContextFree();
return new AppInfoWithLiveness(this, lense);
}
+
+ /**
+ * Returns true if the given type was part of the original program but has been removed during
+ * tree shaking.
+ */
+ public boolean wasPruned(DexType type) {
+ return prunedTypes.contains(type);
+ }
}
private static class SetWithReason<T> {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index e39faa0..f47adbd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -31,6 +31,7 @@
private Path printUsageFile;
private boolean printMapping;
private Path printMappingFile;
+ private Path applyMappingFile = null;
private boolean verbose = false;
private final List<String> attributesRemovalPatterns = new ArrayList<>();
private final Set<ProguardTypeMatcher> dontWarnPatterns = new HashSet<>();
@@ -104,6 +105,10 @@
this.printMappingFile = file;
}
+ public void setApplyMappingFile(Path file) {
+ this.applyMappingFile = file;
+ }
+
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
@@ -155,6 +160,7 @@
printUsageFile,
printMapping,
printMappingFile,
+ applyMappingFile,
verbose,
attributesRemovalPatterns,
dontWarnPatterns,
@@ -180,6 +186,7 @@
private final Path printUsageFile;
private final boolean printMapping;
private final Path printMappingFile;
+ private final Path applyMappingFile;
private final boolean verbose;
private final ImmutableList<String> attributesRemovalPatterns;
private final ImmutableSet<ProguardTypeMatcher> dontWarnPatterns;
@@ -204,6 +211,7 @@
Path printUsageFile,
boolean printMapping,
Path printMappingFile,
+ Path applyMappingFile,
boolean verbose,
List<String> attributesRemovalPatterns,
Set<ProguardTypeMatcher> dontWarnPatterns,
@@ -226,6 +234,7 @@
this.printUsageFile = printUsageFile;
this.printMapping = printMapping;
this.printMappingFile = printMappingFile;
+ this.applyMappingFile = applyMappingFile;
this.verbose = verbose;
this.attributesRemovalPatterns = ImmutableList.copyOf(attributesRemovalPatterns);
this.dontWarnPatterns = ImmutableSet.copyOf(dontWarnPatterns);
@@ -280,6 +289,14 @@
return printMappingFile;
}
+ public boolean hasApplyMappingFile() {
+ return applyMappingFile != null;
+ }
+
+ public Path getApplyMappingFile() {
+ return applyMappingFile;
+ }
+
public boolean isIgnoreWarnings() {
return ignoreWarnings;
}
@@ -347,7 +364,8 @@
false /* printUsage */,
null /* printUsageFile */,
false /* printMapping */,
- null /* outputMapping */,
+ null /* printMappingFile */,
+ null /* applyMapping */,
false /* verbose */,
ImmutableList.of() /* attributesRemovalPatterns */,
ImmutableSet.of() /* dontWarnPatterns */,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 4fb7357..5292a45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -219,6 +219,10 @@
if (isOptionalArgumentGiven()) {
configurationBuilder.setPrintMappingFile(parseFileName());
}
+ } else if (acceptString("applymapping")) {
+ configurationBuilder.setApplyMappingFile(parseFileName());
+ // TODO(b/64802420): warn until it is fully implemented.
+ warnIgnoringOptions("applymapping");
} else if (acceptString("assumenosideeffects")) {
ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 0fa5051..5e7b42d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -452,7 +452,7 @@
dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
.put(definition, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- noObfuscation.add(definition);
+ noObfuscation.add(type);
}
private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
@@ -487,7 +487,11 @@
noOptimization.add(item);
}
if (!modifiers.allowsObfuscation) {
- noObfuscation.add(item);
+ if (item instanceof DexClass) {
+ noObfuscation.add(((DexClass) item).type);
+ } else {
+ noObfuscation.add(item);
+ }
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
@@ -520,38 +524,24 @@
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
- private boolean legalNoObfuscationItem(DexItem item) {
- if (!(item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
- assert item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField;
- return true;
- }
-
- private boolean legalNoObfuscationItems(Set<DexItem> items) {
- items.forEach(this::legalNoObfuscationItem);
- return true;
- }
-
- private boolean legalDependentNoShrinkingItem(DexItem item) {
- if (!(item instanceof DexType
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
+ private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
+ return item instanceof DexType
+ || item instanceof DexEncodedMethod
+ || item instanceof DexEncodedField;
+ }
+
+ private boolean legalNoObfuscationItems(Set<DexItem> items) {
+ assert items.stream().allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
private boolean legalDependentNoShrinkingItems(
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
- dependentNoShrinking.keySet().forEach(this::legalDependentNoShrinkingItem);
+ assert dependentNoShrinking.keySet().stream()
+ .allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index c04b29a..193ebdf 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -655,4 +655,8 @@
return result;
}
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(mergedClasses.keySet());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 64294bd..153f604 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,13 +8,17 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -23,7 +27,8 @@
private DexApplication application;
private final AppInfoWithLiveness appInfo;
private final InternalOptions options;
- private UsagePrinter usagePrinter;
+ private final UsagePrinter usagePrinter;
+ private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
public TreePruner(
DexApplication application, AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -64,6 +69,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing class: " + clazz);
}
+ prunedTypes.add(clazz.type);
usagePrinter.printUnusedClass(clazz);
} else {
newClasses.add(clazz);
@@ -190,4 +196,8 @@
}
return reachableFields.toArray(new DexEncodedField[reachableFields.size()]);
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(prunedTypes);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 97a3679..4c6c56f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -586,6 +586,15 @@
}
/**
+ * Inform whether ProGuard map has already been set or not.
+ *
+ * ProGuard option -applymapping will override R8/Dissemble option -pg-map.
+ */
+ public boolean hasProguardMap() {
+ return proguardMap != null;
+ }
+
+ /**
* Set proguard-map file.
*/
public Builder setProguardMapFile(Path file) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b03721f..9f3488c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -91,6 +91,7 @@
public OutputMode outputMode = OutputMode.Indexed;
public boolean useTreeShaking = true;
+ public boolean useDiscardedChecker = true;
public boolean printCfg = false;
public String printCfgFile;
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
new file mode 100644
index 0000000..3cc3245
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -0,0 +1,23 @@
+// 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.utils;
+
+import java.util.ListIterator;
+
+public class IteratorUtils {
+ public static <T> T peekPrevious(ListIterator<T> iterator) {
+ T previous = iterator.previous();
+ T next = iterator.next();
+ assert previous == next;
+ return previous;
+ }
+
+ public static <T> T peekNext(ListIterator<T> iterator) {
+ T next = iterator.next();
+ T previous = iterator.previous();
+ assert previous == next;
+ return next;
+ }
+}
diff --git a/src/test/examples/naming101/keep-rules-102.txt b/src/test/examples/naming101/keep-rules-102.txt
new file mode 100644
index 0000000..b1c0cda
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-102.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-repackageclasses 'naming101.a'
+
+-keep public class **.a {
+ *;
+}
+
+-keep,allowobfuscation class * {
+ *;
+}
diff --git a/src/test/examples/naming101/keep-rules-104.txt b/src/test/examples/naming101/keep-rules-104.txt
new file mode 100644
index 0000000..6866dee
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-104.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-flattenpackagehierarchy 'naming101'
+
+-keep public class **.a {
+ *;
+}
+
+-keep,allowobfuscation class * {
+ *;
+}
diff --git a/src/test/examples/shaking17/AbstractProgramClass.java b/src/test/examples/shaking17/AbstractProgramClass.java
new file mode 100644
index 0000000..d3b696b
--- /dev/null
+++ b/src/test/examples/shaking17/AbstractProgramClass.java
@@ -0,0 +1,9 @@
+// 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 shaking17;
+
+public abstract class AbstractProgramClass extends shakinglib.AbstractLibraryClass {
+
+ public abstract int abstractMethod();
+}
diff --git a/src/test/examples/shaking17/Shaking.java b/src/test/examples/shaking17/Shaking.java
new file mode 100644
index 0000000..78cc495
--- /dev/null
+++ b/src/test/examples/shaking17/Shaking.java
@@ -0,0 +1,16 @@
+// 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 shaking17;
+
+public class Shaking {
+
+ public static void main(String[] args) {
+ Subclass subclass = new Subclass();
+ callTheMethod(subclass);
+ }
+
+ private static void callTheMethod(AbstractProgramClass abstractProgramClass) {
+ System.out.print(abstractProgramClass.abstractMethod());
+ }
+}
diff --git a/src/test/examples/shaking17/Subclass.java b/src/test/examples/shaking17/Subclass.java
new file mode 100644
index 0000000..237dca9
--- /dev/null
+++ b/src/test/examples/shaking17/Subclass.java
@@ -0,0 +1,12 @@
+// 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 shaking17;
+
+public class Subclass extends AbstractProgramClass {
+
+ @Override
+ public int abstractMethod() {
+ return 42;
+ }
+}
diff --git a/src/test/examples/shaking17/keep-rules.txt b/src/test/examples/shaking17/keep-rules.txt
new file mode 100644
index 0000000..27dd8f3
--- /dev/null
+++ b/src/test/examples/shaking17/keep-rules.txt
@@ -0,0 +1,9 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking17.Shaking {
+ public static void main(...);
+}
diff --git a/src/test/examples/shakinglib/AbstractLibraryClass.java b/src/test/examples/shakinglib/AbstractLibraryClass.java
new file mode 100644
index 0000000..614b53e
--- /dev/null
+++ b/src/test/examples/shakinglib/AbstractLibraryClass.java
@@ -0,0 +1,9 @@
+// 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 shakinglib;
+
+public abstract class AbstractLibraryClass {
+
+ public abstract int abstractMethod();
+}
diff --git a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
index b9d2752..18f8d64 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
@@ -13,6 +13,10 @@
int i, Integer I, long l, Long L, float f, Float F, double d, Double D);
}
+ interface it<T> {
+ T t();
+ }
+
interface iz {
boolean f();
}
@@ -135,6 +139,11 @@
private static void checkDouble(StringBuffer builder) {
builder
+ .append(((id) new it<Double>() {
+ @Override public Double t() {
+ return (double) (Integer.MAX_VALUE) + 1;
+ }
+ }::t).f()).append(' ')
.append(((id) ValueAdjustments::b).f()).append(' ')
.append(((id) ValueAdjustments::B).f()).append(' ')
.append(((id) ValueAdjustments::s).f()).append(' ')
@@ -153,6 +162,11 @@
private static void checkFloat(StringBuffer builder) {
builder
+ .append(((if_) new it<Float>() {
+ @Override public Float t() {
+ return (float) (Short.MAX_VALUE) + 1;
+ }
+ }::t).f()).append(' ')
.append(((if_) ValueAdjustments::b).f()).append(' ')
.append(((if_) ValueAdjustments::B).f()).append(' ')
.append(((if_) ValueAdjustments::s).f()).append(' ')
@@ -169,6 +183,11 @@
private static void checkLong(StringBuffer builder) {
builder
+ .append(((ij) new it<Long>() {
+ @Override public Long t() {
+ return (long) (Integer.MAX_VALUE) + 1;
+ }
+ }::t).f()).append(' ')
.append(((ij) ValueAdjustments::b).f()).append(' ')
.append(((ij) ValueAdjustments::B).f()).append(' ')
.append(((ij) ValueAdjustments::s).f()).append(' ')
@@ -183,6 +202,11 @@
private static void checkInt(StringBuffer builder) {
builder
+ .append(((ii) new it<Integer>() {
+ @Override public Integer t() {
+ return Short.MAX_VALUE + 1;
+ }
+ }::t).f()).append(' ')
.append(((ii) ValueAdjustments::b).f()).append(' ')
.append(((ii) ValueAdjustments::B).f()).append(' ')
.append(((ii) ValueAdjustments::s).f()).append(' ')
@@ -195,6 +219,11 @@
private static void checkShort(StringBuffer builder) {
builder
+ .append(((is) new it<Short>() {
+ @Override public Short t() {
+ return 256;
+ }
+ }::t).f()).append(' ')
.append(((is) ValueAdjustments::b).f()).append(' ')
.append(((is) ValueAdjustments::B).f()).append(' ')
.append(((is) ValueAdjustments::s).f()).append(' ')
@@ -203,18 +232,33 @@
private static void checkChar(StringBuffer builder) {
builder
+ .append(((ic) new it<Character>() {
+ @Override public Character t() {
+ return 'C';
+ }
+ }::t).f()).append(' ')
.append(((ic) ValueAdjustments::c).f()).append(' ')
.append(((ic) ValueAdjustments::C).f()).append('\n');
}
private static void checkByte(StringBuffer builder) {
builder
+ .append(((ib) new it<Byte>() {
+ @Override public Byte t() {
+ return 11;
+ }
+ }::t).f()).append(' ')
.append(((ib) ValueAdjustments::b).f()).append(' ')
.append(((ib) ValueAdjustments::B).f()).append('\n');
}
private static void checkBoolean(StringBuffer builder) {
builder
+ .append(((iz) new it<Boolean>() {
+ @Override public Boolean t() {
+ return true;
+ }
+ }::t).f()).append(' ')
.append(((iz) ValueAdjustments::z).f()).append(' ')
.append(((iz) ValueAdjustments::Z).f()).append('\n');
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 3ca9b1b..4d707a8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -19,6 +19,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -30,7 +31,7 @@
public class TestBase {
@Rule
- public TemporaryFolder temp = new TemporaryFolder();
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
/**
* Write lines of text to a temporary file.
@@ -114,6 +115,15 @@
/**
* Compile an application with R8.
*/
+ protected AndroidApp compileWithR8(AndroidApp app)
+ throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+ R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
+ return ToolHelper.runR8(command);
+ }
+
+ /**
+ * Compile an application with R8.
+ */
protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
@@ -215,6 +225,13 @@
* Run application on Art with the specified main class and provided arguments.
*/
protected String runOnArt(AndroidApp app, Class mainClass, String... args) throws IOException {
+ return runOnArt(app, mainClass, Arrays.asList(args));
+ }
+
+ /**
+ * Run application on Art with the specified main class and provided arguments.
+ */
+ protected String runOnArt(AndroidApp app, Class mainClass, List<String> args) throws IOException {
Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
app.writeToZip(out, OutputMode.Indexed);
return ToolHelper.runArtNoVerificationErrors(
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
rename to src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index a449e21..3aadc15 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -16,7 +16,12 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
-
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -24,20 +29,12 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-
/**
- * Simple test that compiles framework.jar with D8 a number of times with
- * various number of threads available to the compiler.
- * This test also tests the hidden marker inserted into classes.dex.
+ * Simple test that compiles framework.jar with D8 a number of times with various number of threads
+ * available to the compiler. This test also tests the hidden marker inserted into classes.dex.
*/
-@RunWith( Parameterized.class )
-public class D8FrameworkTest {
+@RunWith(Parameterized.class)
+public class D8FrameworkDexPassthroughMarkerTest {
private static final Path FRAMEWORK_JAR =
Paths.get("tools/linux/art-5.1.1/product/mako/system/framework/framework.jar");
@@ -52,7 +49,7 @@
private final int threads;
- public D8FrameworkTest(int threads) {
+ public D8FrameworkDexPassthroughMarkerTest(int threads) {
this.threads = threads;
}
@@ -72,7 +69,9 @@
options.numberOfThreads = threads;
});
DexApplication dexApp =
- new ApplicationReader(app, new InternalOptions(), new Timing("D8FrameworkTest")).read();
+ new ApplicationReader(
+ app, new InternalOptions(), new Timing("D8FrameworkDexPassthroughMarkerTest"))
+ .read();
Marker readMarker = dexApp.dexItemFactory.extractMarker();
assertEquals(marker, readMarker);
}
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
new file mode 100644
index 0000000..4cc5818
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
@@ -0,0 +1,42 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017DesugaredVerificationTest extends CompilationTestBase {
+ private static final int MIN_SDK = 24;
+ private static final String JAR = "third_party/framework/framework_14082017_desugared.jar";
+
+ @Test
+ public void verifyDebugBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+
+ @Test
+ public void verifyReleaseBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.RELEASE)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
new file mode 100644
index 0000000..87786df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
@@ -0,0 +1,42 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017VerificationTest extends CompilationTestBase {
+ private static final int MIN_SDK = 24;
+ private static final String JAR = "third_party/framework/framework_14082017.jar";
+
+ @Test
+ public void verifyDebugBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+
+ @Test
+ public void verifyReleaseBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.RELEASE)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index f88ea49..74e4e9b 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index efa812a..8c72ddd 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.CompilationMode;
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 08e90c0..6eebf6b 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.regalloc;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.ConstNumber;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 671698b..813ea1b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.CompilationError;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 87d350c..4710e69 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import com.android.tools.r8.errors.CompilationError;
import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index e1ace71..cbf40a2 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,11 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
-import com.android.tools.r8.CompilationException;
import com.android.tools.r8.errors.CompilationError;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 7f65c60..ba5e370 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
@@ -141,11 +140,17 @@
return out.toByteArray();
}
+ public List<byte[]> buildClasses() throws Exception {
+ List<byte[]> result = new ArrayList<>();
+ for (ClassBuilder clazz : classes) {
+ result.add(compile(clazz));
+ }
+ return result;
+ }
+
public AndroidApp build() throws Exception {
AndroidApp.Builder builder = AndroidApp.builder();
- for (ClassBuilder clazz : classes) {
- builder.addClassProgramData(compile(clazz));
- }
+ builder.addClassProgramData(buildClasses());
return builder.build();
}
@@ -154,7 +159,6 @@
}
public DexApplication read(InternalOptions options) throws Exception {
- DexItemFactory factory = new DexItemFactory();
Timing timing = new Timing("JasminTest");
return new ApplicationReader(build(), options, timing).read();
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 801f6d1..619f808 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -302,7 +302,7 @@
Path secondaryDexFile = testDir.resolve("classes2.dex");
assertTrue(Files.exists(primaryDexFile));
boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
- assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+ assertEquals(hasSecondaryDexFile, Files.exists(secondaryDexFile));
byte[] content = Files.readAllBytes(primaryDexFile);
if (ref == null) {
ref = content;
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index b8e22d2..5975483 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -17,12 +17,14 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.BiConsumer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,6 +47,16 @@
inspection.accept(dexItemFactory, naming);
}
+ private static final List<String> CLASSES_IN_NAMING044 = ImmutableList.of(
+ "Lnaming044/A;", "Lnaming044/B;", "Lnaming044/sub/SubA;", "Lnaming044/sub/SubB;");
+
+ private static final List<String> CLASSES_IN_NAMING101 = ImmutableList.of(
+ "Lnaming101/c;", "Lnaming101/d;",
+ "Lnaming101/a/a;", "Lnaming101/a/c;",
+ "Lnaming101/a/b/c",
+ "Lnaming101/b/a'", "Lnaming101/b/b;"
+ );
+
@Parameters(name = "test: {0} keep: {1}")
public static Collection<Object[]> data() {
List<String> tests = Arrays.asList("naming044", "naming101");
@@ -60,6 +72,8 @@
inspections.put("naming101:keep-rules-003.txt", PackageNamingTest::test101_rule003);
inspections.put("naming101:keep-rules-004.txt", PackageNamingTest::test101_rule004);
inspections.put("naming101:keep-rules-005.txt", PackageNamingTest::test101_rule005);
+ inspections.put("naming101:keep-rules-102.txt", PackageNamingTest::test101_rule102);
+ inspections.put("naming101:keep-rules-104.txt", PackageNamingTest::test101_rule104);
return createTests(tests, inspections);
}
@@ -68,8 +82,23 @@
return CharMatcher.is('/').countIn(descriptor);
}
+ private static void verifyUniqueNaming(
+ DexItemFactory dexItemFactory,
+ NamingLens naming,
+ List<String> klasses) {
+ Set<String> renamedNames = Sets.newHashSet();
+ for (String klass : klasses) {
+ DexType k = dexItemFactory.createType(klass);
+ String rename = naming.lookupDescriptor(k).toSourceString();
+ assertFalse(renamedNames.contains(rename));
+ renamedNames.add(rename);
+ }
+ }
+
// repackageclasses ''
private static void test044_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
// All classes are moved to the top-level package, hence no package separator.
DexType b = dexItemFactory.createType("Lnaming044/B;");
assertFalse(naming.lookupDescriptor(b).toSourceString().contains("/"));
@@ -86,6 +115,8 @@
// repackageclasses 'p44.x'
private static void test044_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
// All classes are moved to a single package, so they all have the same package prefix.
DexType a = dexItemFactory.createType("Lnaming044/A;");
assertTrue(naming.lookupDescriptor(a).toSourceString().startsWith("Lp44/x/"));
@@ -103,6 +134,8 @@
// flattenpackagehierarchy ''
private static void test044_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
// All packages are moved to the top-level package, hence only one package separator.
DexType b = dexItemFactory.createType("Lnaming044/B;");
assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
@@ -145,6 +178,8 @@
}
private static void test044_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
// All packages are renamed somehow. Need to check package hierarchy is consistent.
DexType a = dexItemFactory.createType("Lnaming044/A;");
assertEquals(1, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
@@ -171,6 +206,8 @@
}
private static void test101_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
// All classes are moved to the top-level package, hence no package separator.
DexType c = dexItemFactory.createType("Lnaming101/c;");
assertFalse(naming.lookupDescriptor(c).toSourceString().contains("/"));
@@ -183,9 +220,14 @@
}
private static void test101_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
- // Check naming101.a.a is kept due to **.a
- DexType a = dexItemFactory.createType("Lnaming101/a/a;");
- assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+ // Check naming101.*.a is kept due to **.a
+ DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+ assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+ DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+ assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
// Repackaged to naming101.a, but naming101.a.a exists to make a name conflict.
// Thus, everything else should not be renamed to 'a',
// except for naming101.b.a, which is also kept due to **.a
@@ -204,6 +246,8 @@
}
private static void test101_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
// All packages are moved to the top-level package, hence only one package separator.
DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
assertEquals(1, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -217,9 +261,14 @@
}
private static void test101_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
- // Check naming101.a.a is kept due to **.a
- DexType a = dexItemFactory.createType("Lnaming101/a/a;");
- assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+ // Check naming101.*.a is kept due to **.a
+ DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+ assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+ DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+ assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
// Flattened to naming101, hence all other classes will be in naming101.* package.
// Due to naming101.a.a, prefix naming101.a is already used. So, any other classes,
// except for naming101.a.c, should not have naming101.a as package.
@@ -237,6 +286,8 @@
}
private static void test101_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
// All packages are renamed somehow. Need to check package hierarchy is consistent.
DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
assertEquals(2, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -250,4 +301,60 @@
getParentPackagePrefix(
getParentPackagePrefix(naming.lookupDescriptor(abc).toSourceString())));
}
+
+ private static void test101_rule102(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+ // Check naming101.*.a is kept due to **.a
+ DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+ assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+ DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+ assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+ // Due to package-private access, classes in the same package should remain as-is.
+ DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+ assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+ DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+ assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+ // All other classes can be repackaged to naming101.a, but naming101.a.a exists to make a name
+ // conflict. Thus, those should not be renamed to 'a'.
+ List<String> klasses = ImmutableList.of(
+ "Lnaming101/c;",
+ "Lnaming101/d;",
+ "Lnaming101/a/b/c;");
+ for (String klass : klasses) {
+ DexType k = dexItemFactory.createType(klass);
+ String renamedName = naming.lookupDescriptor(k).toSourceString();
+ assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+ assertNotEquals("Lnaming101/a/a;", renamedName);
+ }
+ }
+
+ private static void test101_rule104(DexItemFactory dexItemFactory, NamingLens naming) {
+ verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+ // Check naming101.*.a is kept due to **.a
+ DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+ assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+ DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+ assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+ // Due to package-private access, classes in the same package should remain as-is.
+ DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+ assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+ DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+ assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+ // All other packages are flattened to naming101, hence all other classes will be in
+ // naming101.* package. Due to naming101.a.a, prefix naming101.a is already used. So,
+ // remaining classes should not have naming101.a as package.
+ List<String> klasses = ImmutableList.of(
+ "Lnaming101/c;",
+ "Lnaming101/d;",
+ "Lnaming101/a/b/c;");
+ for (String klass : klasses) {
+ DexType k = dexItemFactory.createType(klass);
+ String renamedName = naming.lookupDescriptor(k).toSourceString();
+ assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ea28214..ac0178c 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -69,6 +69,10 @@
VALID_PROGUARD_DIR + "package-obfuscation-5.flags";
private static final String PACKAGE_OBFUSCATION_6 =
VALID_PROGUARD_DIR + "package-obfuscation-6.flags";
+ private static final String APPLY_MAPPING =
+ VALID_PROGUARD_DIR + "applymapping.flags";
+ private static final String APPLY_MAPPING_WITHOUT_FILE =
+ INVALID_PROGUARD_DIR + "applymapping-without-file.flags";
private static final String DONT_SHRINK =
VALID_PROGUARD_DIR + "dontshrink.flags";
private static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES =
@@ -339,6 +343,25 @@
}
@Test
+ public void parseApplyMapping() throws IOException, ProguardRuleParserException {
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+ parser.parse(Paths.get(APPLY_MAPPING));
+ ProguardConfiguration config = parser.getConfig();
+ assertTrue(config.hasApplyMappingFile());
+ }
+
+ @Test
+ public void parseApplyMappingWithoutFile() throws IOException, ProguardRuleParserException {
+ try {
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+ parser.parse(Paths.get(APPLY_MAPPING_WITHOUT_FILE));
+ fail("Expect to fail due to the lack of file name.");
+ } catch (ProguardRuleParserException e) {
+ assertTrue(e.getMessage().contains("File name expected"));
+ }
+ }
+
+ @Test
public void parseIncluding() throws IOException, ProguardRuleParserException {
new ProguardConfigurationParser(new DexItemFactory()).parse(Paths.get(INCLUDING));
}
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index be28b38..0ab229f 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 51b6ab5..ab6d6e2 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -478,6 +478,12 @@
inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
}
+ private static void abstractMethodRemains(DexInspector inspector) {
+ ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
+ Assert.assertTrue(programClass.isPresent());
+ Assert.assertTrue(
+ programClass.method("int", "abstractMethod", Collections.emptyList()).isPresent());
+ }
private static void assumenosideeffects1CheckOutput(String output1, String output2) {
Assert.assertEquals("noSideEffectVoid\nnoSideEffectInt\n", output1);
@@ -620,6 +626,7 @@
"shaking14",
"shaking15",
"shaking16",
+ "shaking17",
"inlining",
"minification",
"minifygeneric",
@@ -684,22 +691,17 @@
.put("shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
inspections
.put("shaking11:keep-rules-keep-method.txt", TreeShakingTest::shaking11BothMethodsKept);
- inspections
- .put("shaking12:keep-rules.txt",
+ inspections.put("shaking12:keep-rules.txt",
TreeShakingTest::shaking12OnlyInstantiatedClassesHaveConstructors);
- inspections
- .put("shaking13:keep-rules.txt",
+ inspections.put("shaking13:keep-rules.txt",
TreeShakingTest::shaking13EnsureFieldWritesCorrect);
- inspections
- .put("shaking14:keep-rules.txt",
+ inspections.put("shaking14:keep-rules.txt",
TreeShakingTest::shaking14EnsureRightStaticMethodsLive);
- inspections.put("shaking15:keep-rules.txt",
- TreeShakingTest::shaking15testDictionary);
- inspections
- .put("annotationremoval:keep-rules.txt",
+ inspections.put("shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
+ inspections.put("shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
+ inspections.put("annotationremoval:keep-rules.txt",
TreeShakingTest::annotationRemovalHasNoInnerClassAnnotations);
- inspections
- .put("annotationremoval:keep-rules-keep-innerannotation.txt",
+ inspections.put("annotationremoval:keep-rules-keep-innerannotation.txt",
TreeShakingTest::annotationRemovalHasAllInnerClassAnnotations);
inspections
.put("simpleproto1:keep-rules.txt", TreeShakingTest::simpleproto1UnusedFieldIsGone);
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
new file mode 100644
index 0000000..f47025a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
@@ -0,0 +1,43 @@
+// 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.smali;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CheckSwitchInTestClass {
+ public static void main(String[] args) throws Exception {
+ // Load the generated Jasmin class, and get the test method.
+ Class<?> test = CheckSwitchInTestClass.class.getClassLoader().loadClass("Test");
+ Method method = test.getMethod("test", int.class);
+
+ // Get keys and default value from arguments.
+ List<Integer> keys = new ArrayList<>();
+ for (int i = 0; i < args.length - 1; i++) {
+ keys.add(Integer.parseInt(args[i]));
+ }
+ int defaultValue = Integer.parseInt(args[args.length - 1]);
+
+ // Run over all keys and test a small interval around each.
+ long delta = 2;
+ for (Integer key : keys) {
+ for (long potential = key - delta; potential < key + delta; potential++) {
+ if (Integer.MIN_VALUE <= potential && potential <= Integer.MAX_VALUE) {
+ int testKey = (int) potential;
+ int result = ((Integer) method.invoke(null, testKey));
+ int expect = defaultValue;
+ if (keys.contains(testKey)) {
+ expect = testKey;
+ }
+ if (result != expect) {
+ System.out.println("Expected " + expect + " but got " + result);
+ System.exit(1);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 27ee140..f35dd21 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
@@ -46,18 +47,13 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import org.antlr.runtime.RecognitionException;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-public class SmaliTestBase {
+public class SmaliTestBase extends TestBase {
public static final String DEFAULT_CLASS_NAME = "Test";
public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
public static final String DEFAULT_METHOD_NAME = "method";
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
public static class MethodSignature {
public final String clazz;
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index 7f89da7..c256920 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -21,9 +21,14 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
public class SwitchRewritingTest extends SmaliTestBase {
@@ -456,4 +461,77 @@
// class file max.
runLargerSwitchJarTest(0, 1, 5503, null);
}
-}
+
+ private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
+ throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ StringBuilder x = new StringBuilder();
+ StringBuilder y = new StringBuilder();
+ for (Integer key : keys) {
+ x.append(key).append(" : case_").append(key).append("\n");
+ y.append("case_").append(key).append(":\n");
+ y.append(" ldc ").append(key).append("\n");
+ y.append(" goto return_\n");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload_0",
+ " lookupswitch",
+ x.toString(),
+ " default : case_default",
+ y.toString(),
+ " case_default:",
+ " ldc " + defaultValue,
+ " return_:",
+ " ireturn");
+
+ // Add the Jasmin class and a class from Java source with the main method.
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ appBuilder.addClassProgramData(builder.buildClasses());
+ appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
+ AndroidApp app = compileWithR8(appBuilder.build());
+
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+ DexCode code = method.getMethod().getCode().asDexCode();
+
+ int packedSwitches = 0;
+ int sparseSwitches = 0;
+ int ifs = 0;
+ for (Instruction instruction : code.instructions) {
+ if (instruction instanceof PackedSwitch) {
+ packedSwitches++;
+ }
+ if (instruction instanceof SparseSwitch) {
+ sparseSwitches++;
+ }
+ if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+ ifs++;
+ }
+ }
+
+ assertEquals(1, packedSwitches);
+ assertEquals(0, sparseSwitches);
+ assertEquals(expectedIfs, ifs);
+
+ // Run the code
+ List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+ args.add(Integer.toString(defaultValue));
+ runOnArt(app, CheckSwitchInTestClass.class, args);
+ }
+
+ @Test
+ public void convertCasesToIf() throws Exception {
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1);
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1);
+ runConvertCasesToIf(ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1);
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
index 7f3c3e1..7af5708 100644
--- a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8Command;
diff --git a/src/test/proguard/invalid/applymapping-without-file.flags b/src/test/proguard/invalid/applymapping-without-file.flags
new file mode 100644
index 0000000..ef5abe5
--- /dev/null
+++ b/src/test/proguard/invalid/applymapping-without-file.flags
@@ -0,0 +1,6 @@
+# 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.
+
+# file name should be followed.
+-applymapping
diff --git a/src/test/proguard/valid/applymapping.flags b/src/test/proguard/valid/applymapping.flags
new file mode 100644
index 0000000..8d427f9
--- /dev/null
+++ b/src/test/proguard/valid/applymapping.flags
@@ -0,0 +1,5 @@
+# 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.
+
+-applymapping bogus-file-name.ext
diff --git a/tools/gmscore_data.py b/tools/gmscore_data.py
index 47d9024..80e7853 100644
--- a/tools/gmscore_data.py
+++ b/tools/gmscore_data.py
@@ -90,6 +90,7 @@
},
'proguarded' : {
'inputs': ['%s_proguard.jar' % V9_PREFIX],
+ 'main-dex-list': os.path.join(V9_BASE, 'main_dex_list.txt'),
'pgmap': '%s_proguard.map' % V9_PREFIX,
'min-api' : ANDROID_L_API,
}
@@ -109,6 +110,7 @@
},
'proguarded' : {
'inputs': ['%s_proguard.jar' % V10_PREFIX],
+ 'main-dex-list': os.path.join(V10_BASE, 'main_dex_list.txt') ,
'pgmap': '%s_proguard.map' % V10_PREFIX,
'min-api' : ANDROID_L_API,
}
@@ -121,5 +123,11 @@
'%s/proguardsettings/GmsCore_proguard.config' % THIRD_PARTY],
'min-api' : ANDROID_L_API,
},
+ 'proguarded' : {
+ 'inputs': ['%s_proguard.jar' % LATEST_PREFIX],
+ 'main-dex-list': os.path.join(LATEST_BASE, 'main_dex_list.txt') ,
+ 'pgmap': '%s_proguard.map' % LATEST_PREFIX,
+ 'min-api' : ANDROID_L_API,
+ }
},
}
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index e150278..63865d0 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -25,7 +25,7 @@
result.add_option('--version',
help = '',
default = 'v9',
- choices = ['v9', 'v10'])
+ choices = ['v9', 'v10', 'latest'])
result.add_option('--type',
help = '',
default = 'proguarded',
@@ -58,6 +58,9 @@
if not os.path.exists(outdir):
os.makedirs(outdir)
+ if 'main-dex-list' in values:
+ args.extend(['--main-dex-list', values['main-dex-list']])
+
if options.d8_flags:
args.extend(options.d8_flags.split(' '))