Merge "Create field and method arrays directly in class merger"
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6549848..63c1173 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.4-dev";
+ public static final String LABEL = "1.3.5-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 845eecd..69126be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -25,10 +25,9 @@
import com.android.tools.r8.dex.JumboStringRewriter;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
@@ -45,13 +44,11 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -621,51 +618,6 @@
}
}
- public static final class ParameterUsagesInfo {
- private ImmutableList<ParameterUsage> parametersUsages;
-
- public ParameterUsagesInfo(List<ParameterUsage> usages) {
- assert !usages.isEmpty();
- parametersUsages = ImmutableList.copyOf(usages);
- assert parametersUsages.size() ==
- parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
- }
-
- public static abstract class ParameterUsage {
- public final int index;
-
- ParameterUsage(int index) {
- this.index = index;
- }
- }
-
- public static class SingleCallOfArgumentMethod extends ParameterUsage {
- public final Invoke.Type type;
- public final DexMethod method;
-
- public SingleCallOfArgumentMethod(int index, Type type, DexMethod method) {
- super(index);
- this.type = type;
- this.method = method;
- }
- }
-
- public static class NotUsed extends ParameterUsage {
- public NotUsed(int index) {
- super(index);
- }
- }
-
- ParameterUsage getParameterUsage(int parameter) {
- for (ParameterUsage usage : parametersUsages) {
- if (usage.index == parameter) {
- return usage;
- }
- }
- return null;
- }
- }
-
public static class OptimizationInfo {
private int returnedArgument = -1;
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java
new file mode 100644
index 0000000..84cf331
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2018, 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.graph;
+
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class ParameterUsagesInfo {
+ private ImmutableList<ParameterUsage> parametersUsages;
+
+ public ParameterUsagesInfo(List<ParameterUsage> usages) {
+ assert !usages.isEmpty();
+ parametersUsages = ImmutableList.copyOf(usages);
+ assert parametersUsages.size() ==
+ parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
+ }
+
+ ParameterUsage getParameterUsage(int parameter) {
+ for (ParameterUsage usage : parametersUsages) {
+ if (usage.index == parameter) {
+ return usage;
+ }
+ }
+ return null;
+ }
+
+ public final static class ParameterUsage {
+ public final int index;
+ public final Set<Type> ifZeroTest;
+ public final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
+ public final boolean returnValue;
+
+ ParameterUsage(int index, Set<Type> ifZeroTest,
+ List<Pair<Invoke.Type, DexMethod>> callsReceiver, boolean returnValue) {
+ this.index = index;
+ this.ifZeroTest = ifZeroTest.isEmpty()
+ ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest);
+ this.callsReceiver = callsReceiver.isEmpty()
+ ? Collections.emptyList() : ImmutableList.copyOf(callsReceiver);
+ this.returnValue = returnValue;
+ }
+
+ public boolean notUsed() {
+ return ifZeroTest == null && callsReceiver == null && !returnValue;
+ }
+ }
+
+ public static class ParameterUsageBuilder {
+ private final int index;
+ private final Value arg;
+ private final Set<Type> ifZeroTestTypes = new HashSet<>();
+ private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
+ private boolean returnValue = false;
+
+ public ParameterUsageBuilder(Value arg, int index) {
+ this.arg = arg;
+ this.index = index;
+ }
+
+ // Returns false if the instruction is not supported.
+ public boolean note(Instruction instruction) {
+ if (instruction.isInvokeMethodWithReceiver() &&
+ instruction.inValues().lastIndexOf(arg) == 0) {
+ callsOnReceiver.add(new Pair<>(
+ instruction.asInvokeMethodWithReceiver().getType(),
+ instruction.asInvokeMethodWithReceiver().getInvokedMethod()));
+ return true;
+ }
+
+ if (instruction.isIf() && instruction.asIf().isZeroTest()) {
+ assert instruction.inValues().size() == 1 && instruction.inValues().get(0) == arg;
+ ifZeroTestTypes.add(instruction.asIf().getType());
+ return true;
+ }
+
+ if (instruction.isReturn()) {
+ assert instruction.inValues().size() == 1 && instruction.inValues().get(0) == arg;
+ returnValue = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public ParameterUsage build() {
+ return new ParameterUsage(index, ifZeroTestTypes, callsOnReceiver, returnValue);
+ }
+ }
+}
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 819c6f2..5bed061 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
@@ -659,6 +659,7 @@
for (Instruction instruction : getInstructions()) {
if (instruction.outValue != null) {
instruction.outValue.clearUsers();
+ instruction.setOutValue(null);
}
for (Value value : instruction.inValues) {
value.removeUser(instruction);
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 58b61af..1adcd11 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
@@ -390,7 +390,7 @@
ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
if (normalExits.isEmpty()) {
assert inlineeCanThrow;
- // TODO(sgjesse): Remove this restriction.
+ // TODO(sgjesse): Remove this restriction (see b/64432527).
assert !invokeBlock.hasCatchHandlers();
blocksToRemove.addAll(
invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
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 cb5aad0..72728a8 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
@@ -138,13 +138,10 @@
if (predecessors.size() <= 1) {
continue;
}
- // If any of the edges to the block are critical, we need to insert new blocks on each
- // containing the move-exception instruction which must remain the first instruction.
- if (block.entry() instanceof MoveException) {
- nextBlockNumber = block.splitCriticalExceptionEdges(
- nextBlockNumber, valueNumberGenerator, newBlocks::add);
- continue;
- }
+
+ // Exceptional edges are given unique header blocks and can have at most one predecessor.
+ assert !block.entry().isMoveException();
+
for (int predIndex = 0; predIndex < predecessors.size(); predIndex++) {
BasicBlock pred = predecessors.get(predIndex);
if (!pred.hasOneNormalExit()) {
@@ -164,6 +161,27 @@
blocks.addAll(newBlocks);
}
+ public boolean verifySplitCriticalEdges() {
+ for (BasicBlock block : blocks) {
+ // If there are multiple incoming edges, check each has a split block.
+ List<BasicBlock> predecessors = block.getPredecessors();
+ if (predecessors.size() > 1) {
+ for (BasicBlock predecessor : predecessors) {
+ assert predecessor.hasOneNormalExit();
+ assert predecessor.getSuccessors().get(0) == block;
+ }
+ }
+ // If there are outgoing exceptional edges, check that each has a split block.
+ if (block.hasCatchHandlers()) {
+ for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
+ assert handler.getPredecessors().size() == 1;
+ assert handler.getPredecessors().get(0) == block;
+ }
+ }
+ }
+ return true;
+ }
+
/**
* Trace blocks and attempt to put fallthrough blocks immediately after the block that
* falls through. When we fail to do that we create a new fallthrough block with an explicit
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 309e1d0..4cbebcf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -586,7 +586,7 @@
}
@Override
- public int getMoveExceptionRegister() {
+ public int getMoveExceptionRegister(int instructionIndex) {
return CfState.Slot.STACK_OFFSET;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 31ee706..b59794b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -21,12 +21,12 @@
import com.android.tools.r8.code.InvokeSuperRange;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.MoveException;
import com.android.tools.r8.code.MoveResult;
import com.android.tools.r8.code.MoveResultObject;
import com.android.tools.r8.code.MoveResultWide;
import com.android.tools.r8.code.SwitchPayload;
import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexCode.Try;
@@ -185,14 +185,19 @@
}
@Override
- public int getMoveExceptionRegister() {
- // No register, move-exception is manually entered during construction.
+ public int getMoveExceptionRegister(int instructionIndex) {
+ Instruction instruction = code.instructions[instructionIndex];
+ if (instruction instanceof MoveException) {
+ MoveException moveException = (MoveException) instruction;
+ return moveException.AA;
+ }
return -1;
}
@Override
public Position getDebugPositionAtOffset(int offset) {
- throw new Unreachable();
+ DexDebugEntry entry = getDebugEntryAtOffset(offset);
+ return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
}
@Override
@@ -225,23 +230,30 @@
}
}
+ private DexDebugEntry getDebugEntryAtOffset(int offset) {
+ DexDebugEntry current = null;
+ if (debugEntries != null) {
+ for (DexDebugEntry entry : debugEntries) {
+ if (entry.address > offset) {
+ break;
+ }
+ current = entry;
+ }
+ }
+ return current;
+ }
+
private void updateDebugPosition(int instructionIndex, IRBuilder builder) {
if (debugEntries == null || debugEntries.isEmpty()) {
return;
}
- DexDebugEntry current = null;
int offset = instructionOffset(instructionIndex);
- for (DexDebugEntry entry : debugEntries) {
- if (entry.address > offset) {
- break;
- }
- current = entry;
- }
- if (current == null) {
+ DexDebugEntry entry = getDebugEntryAtOffset(offset);
+ if (entry == null) {
currentPosition = preamblePosition;
} else {
- currentPosition = getCanonicalPositionAppendCaller(current);
- if (current.lineEntry && current.address == offset) {
+ currentPosition = getCanonicalPositionAppendCaller(entry);
+ if (entry.lineEntry && entry.address == offset) {
builder.addDebugPosition(currentPosition);
}
}
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 5be54f5..963cf41 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
@@ -93,6 +93,8 @@
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -142,6 +144,13 @@
}
}
+ private static class SplitBlockWorklistItem extends WorklistItem {
+
+ public SplitBlockWorklistItem(BasicBlock block) {
+ super(block, -1);
+ }
+ }
+
/**
* Representation of lists of values that can be used as keys in maps. A list of
* values is equal to another list of values if it contains exactly the same values
@@ -224,6 +233,10 @@
return all;
}
+ boolean hasJustOneNormalExit() {
+ return normalSuccessors.size() == 1 && exceptionalSuccessors.isEmpty();
+ }
+
BlockInfo split(
int blockStartOffset, int fallthroughOffset, Int2ReferenceMap<BlockInfo> targets) {
BlockInfo fallthroughInfo = new BlockInfo();
@@ -279,6 +292,7 @@
// Mapping from instruction offsets to basic-block targets.
private final Int2ReferenceSortedMap<BlockInfo> targets = new Int2ReferenceAVLTreeMap<>();
+ private final Reference2IntMap<BasicBlock> offsets = new Reference2IntOpenHashMap<>();
// Worklist of reachable blocks.
private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
@@ -296,7 +310,6 @@
private final LinkedList<BasicBlock> blocks = new LinkedList<>();
private BasicBlock currentBlock = null;
- private final List<BasicBlock.Pair> needGotoToCatchBlocks = new ArrayList<>();
final private ValueNumberGenerator valueNumberGenerator;
private final DexEncodedMethod method;
private final AppInfo appInfo;
@@ -365,7 +378,9 @@
source.setUp();
// Create entry block (at a non-targetable address).
- targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
+ BlockInfo initialBlockInfo = new BlockInfo();
+ targets.put(INITIAL_BLOCK_OFFSET, initialBlockInfo);
+ offsets.put(initialBlockInfo.block, INITIAL_BLOCK_OFFSET);
// Process reachable code paths starting from instruction 0.
int instCount = source.instructionCount();
@@ -411,9 +426,6 @@
// Check that the last block is closed and does not fall off the end.
assert currentBlock == null;
- // Handle where a catch handler hits the same block as the fallthrough.
- handleFallthroughToCatchBlock();
-
// Verify that we have properly filled all blocks
// Must be after handle-catch (which has delayed edges),
// but before handle-exit (which does not maintain predecessor counts).
@@ -434,9 +446,8 @@
// Package up the IR code.
IRCode ir = new IRCode(options, method, blocks, valueNumberGenerator, hasDebugPositions);
- // Split critical edges to make sure that we have a place to insert phi moves if
- // necessary.
- ir.splitCriticalEdges();
+ // Verify critical edges are split so we have a place to insert phi moves if necessary.
+ assert ir.verifySplitCriticalEdges();
for (BasicBlock block : blocks) {
block.deduplicatePhis();
@@ -550,6 +561,12 @@
// Process synthesized move-exception block specially.
if (item instanceof MoveExceptionWorklistItem) {
processMoveExceptionItem((MoveExceptionWorklistItem) item);
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+ continue;
+ }
+ // Split blocks are just pending close.
+ if (item instanceof SplitBlockWorklistItem) {
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
continue;
}
// Build IR for each dex instruction in the block.
@@ -571,20 +588,20 @@
private void processMoveExceptionItem(MoveExceptionWorklistItem moveExceptionItem) {
// TODO(zerny): Link with outer try-block handlers, if any. b/65203529
- int moveExceptionDest = source.getMoveExceptionRegister();
- assert moveExceptionDest >= 0;
int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
- Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
- Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
- MoveException moveException = new MoveException(out);
- moveException.setPosition(position);
- currentBlock.add(moveException);
+ int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
+ if (moveExceptionDest >= 0) {
+ Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
+ Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
+ MoveException moveException = new MoveException(out);
+ moveException.setPosition(position);
+ currentBlock.add(moveException);
+ }
Goto exit = new Goto();
currentBlock.add(exit);
BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
currentBlock.link(targetBlock);
addToWorklist(targetBlock, targetIndex);
- closeCurrentBlock();
}
// Helper to resolve switch payloads and build switch instructions (dex code only).
@@ -976,11 +993,8 @@
public void addGoto(int targetOffset) {
addInstruction(new Goto());
BasicBlock targetBlock = getTarget(targetOffset);
- if (currentBlock.hasCatchSuccessor(targetBlock)) {
- needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, targetBlock));
- } else {
- currentBlock.link(targetBlock);
- }
+ assert !currentBlock.hasCatchSuccessor(targetBlock);
+ currentBlock.link(targetBlock);
addToWorklist(targetBlock, source.instructionIndex(targetOffset));
closeCurrentBlock();
}
@@ -1292,25 +1306,20 @@
}
public void addMoveException(int dest) {
- Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.NO_THROW);
- assert !out.hasLocalInfo();
- MoveException instruction = new MoveException(out);
- assert !instruction.instructionTypeCanThrow();
- if (currentBlock.getInstructions().size() == 1 && currentBlock.entry().isDebugPosition()) {
- InstructionListIterator it = currentBlock.listIterator();
- Instruction entry = it.next();
- assert entry.getPosition().equals(source.getCurrentPosition());
- attachLocalValues(instruction);
- it.replaceCurrentInstruction(instruction);
- return;
+ assert !currentBlock.getPredecessors().isEmpty();
+ assert currentBlock.getPredecessors().stream().allMatch(b -> b.entry().isMoveException());
+ assert verifyValueIsMoveException(readRegister(dest, ValueType.OBJECT));
+ }
+
+ private static boolean verifyValueIsMoveException(Value value) {
+ if (value.isPhi()) {
+ for (Value operand : value.asPhi().getOperands()) {
+ assert operand.definition.isMoveException();
+ }
+ } else {
+ assert value.definition.isMoveException();
}
- if (!currentBlock.getInstructions().isEmpty()) {
- throw new CompilationError("Invalid MoveException instruction encountered. "
- + "The MoveException instruction is not the first instruction in the block in "
- + method.qualifiedName()
- + ".");
- }
- addInstruction(instruction);
+ return true;
}
public void addMoveResult(int dest) {
@@ -1513,7 +1522,7 @@
public void addThrow(int value) {
Value in = readRegister(value, ValueType.OBJECT);
addInstruction(new Throw(in));
- closeCurrentBlock();
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
}
public void addOr(NumericType type, int dest, int left, int right) {
@@ -1805,24 +1814,47 @@
if (!throwingInstructionInCurrentBlock) {
return;
}
- BasicBlock block = new BasicBlock();
+ // Note that when splitting the block in two we must update the CFG information so that we can
+ // correctly identify if the normal exits of the constructed block must be split once it is
+ // closed.
+ int currentBlockOffset = getOffset(currentBlock);
+ BlockInfo currentBlockInfo = getBlockInfo(currentBlockOffset);
+
+ BlockInfo info = new BlockInfo();
+ BasicBlock block = info.block;
block.setNumber(nextBlockNumber++);
blocks.add(block);
- block.incrementUnfilledPredecessorCount();
+
+ // Compute some unused offset for the new block and link it in the CFG.
int freshOffset = INITIAL_BLOCK_OFFSET - 1;
while (targets.containsKey(freshOffset)) {
freshOffset--;
}
- targets.put(freshOffset, null);
+ targets.put(freshOffset, info);
+ offsets.put(block, freshOffset);
+
+ // Copy over the exceptional successors.
for (int offset : source.getCurrentCatchHandlers().getUniqueTargets()) {
+ info.addExceptionalSuccessor(offset);
BlockInfo target = targets.get(offset);
assert !target.block.isSealed();
target.block.incrementUnfilledPredecessorCount();
target.addExceptionalPredecessor(freshOffset);
}
+
+ // Move all normal successors to the new block.
+ currentBlockInfo.normalSuccessors.forEach(info::addNormalSuccessor);
+ currentBlockInfo.normalSuccessors.clear();
+
+ // Link the two blocks.
addInstruction(new Goto());
currentBlock.link(block);
- closeCurrentBlock();
+ block.incrementUnfilledPredecessorCount();
+ currentBlockInfo.addNormalSuccessor(freshOffset);
+ info.addNormalPredecessor(currentBlockOffset);
+
+ // The new block can only have a single predecessor and so we don't need to split between them.
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
setCurrentBlock(block);
}
@@ -1843,29 +1875,19 @@
assert !throwingInstructionInCurrentBlock;
throwingInstructionInCurrentBlock = true;
List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
- int moveExceptionDest = source.getMoveExceptionRegister();
- if (moveExceptionDest < 0) {
- for (int targetOffset : catchHandlers.getAllTargets()) {
- BasicBlock target = getTarget(targetOffset);
- addToWorklist(target, source.instructionIndex(targetOffset));
- targets.add(target);
+ // Construct unique move-exception header blocks for each unique target.
+ Map<BasicBlock, BasicBlock> moveExceptionHeaders =
+ new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
+ for (int targetOffset : catchHandlers.getAllTargets()) {
+ BasicBlock target = getTarget(targetOffset);
+ BasicBlock header = moveExceptionHeaders.get(target);
+ if (header == null) {
+ header = new BasicBlock();
+ header.incrementUnfilledPredecessorCount();
+ moveExceptionHeaders.put(target, header);
+ ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
}
- } else {
- // If there is a well-defined move-exception destination register (eg, compiling from
- // Java-bytecode) then we construct move-exception header blocks for each unique target.
- Map<BasicBlock, BasicBlock> moveExceptionHeaders =
- new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
- for (int targetOffset : catchHandlers.getAllTargets()) {
- BasicBlock target = getTarget(targetOffset);
- BasicBlock header = moveExceptionHeaders.get(target);
- if (header == null) {
- header = new BasicBlock();
- header.incrementUnfilledPredecessorCount();
- moveExceptionHeaders.put(target, header);
- ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
- }
- targets.add(header);
- }
+ targets.add(header);
}
currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
}
@@ -1907,6 +1929,7 @@
info = new BlockInfo();
}
targets.put(offset, info);
+ offsets.put(info.block, offset);
}
return info;
}
@@ -1980,11 +2003,23 @@
// Private block helpers.
+ private BlockInfo getBlockInfo(int offset) {
+ return targets.get(offset);
+ }
+
+ private BlockInfo getBlockInfo(BasicBlock block) {
+ return getBlockInfo(getOffset(block));
+ }
+
private BasicBlock getTarget(int offset) {
return targets.get(offset).block;
}
- private void closeCurrentBlock() {
+ private int getOffset(BasicBlock block) {
+ return offsets.getInt(block);
+ }
+
+ private void closeCurrentBlockGuaranteedNotToNeedEdgeSplitting() {
// TODO(zerny): To ensure liveness of locals throughout the entire block, we might want to
// insert reads before closing the block. It is unclear if we can rely on a local-end to ensure
// liveness in all blocks where the local should be live.
@@ -1994,48 +2029,41 @@
throwingInstructionInCurrentBlock = false;
}
+ private void closeCurrentBlock() {
+ assert currentBlock != null;
+ BlockInfo info = getBlockInfo(currentBlock);
+ if (!info.hasJustOneNormalExit()) {
+ // Exceptional edges are always split on construction, so no need to split edges to them.
+ for (int successorOffset : info.normalSuccessors) {
+ BlockInfo successorInfo = getBlockInfo(successorOffset);
+ if (successorInfo.predecessorCount() > 1) {
+ BasicBlock splitBlock = createSplitEdgeBlock(currentBlock, successorInfo.block);
+ ssaWorklist.add(new SplitBlockWorklistItem(splitBlock));
+ }
+ }
+ }
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+ }
+
+ private static BasicBlock createSplitEdgeBlock(BasicBlock source, BasicBlock target) {
+ BasicBlock splitBlock = new BasicBlock();
+ splitBlock.add(new Goto());
+ splitBlock.incrementUnfilledPredecessorCount();
+ splitBlock.getPredecessors().add(source);
+ splitBlock.getSuccessors().add(target);
+ source.replaceSuccessor(target, splitBlock);
+ target.replacePredecessor(source, splitBlock);
+ return splitBlock;
+ }
+
private void closeCurrentBlockWithFallThrough(BasicBlock nextBlock) {
assert currentBlock != null;
addInstruction(new Goto());
- if (currentBlock.hasCatchSuccessor(nextBlock)) {
- needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, nextBlock));
- } else {
- currentBlock.link(nextBlock);
- }
+ assert !currentBlock.hasCatchSuccessor(nextBlock);
+ currentBlock.link(nextBlock);
closeCurrentBlock();
}
- private void handleFallthroughToCatchBlock() {
- // When a catch handler for a block goes to the same block as the fallthrough for that
- // block the graph only has one edge there. In these cases we add an additional block so the
- // catch edge goes through that and then make the fallthrough go through a new direct edge.
- for (BasicBlock.Pair pair : needGotoToCatchBlocks) {
- BasicBlock source = pair.first;
- BasicBlock target = pair.second;
-
- // New block with one unfilled predecessor.
- BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, target);
- blocks.add(newBlock);
- newBlock.incrementUnfilledPredecessorCount();
-
- // Link blocks.
- source.replaceSuccessor(target, newBlock);
- newBlock.getPredecessors().add(source);
- source.getSuccessors().add(target);
- target.getPredecessors().add(newBlock);
-
- // Check that the successor indexes are correct.
- assert source.hasCatchSuccessor(newBlock);
- assert !source.hasCatchSuccessor(target);
-
- // Mark the filled predecessors to the blocks.
- if (source.isFilled()) {
- newBlock.filledPredecessor(this);
- }
- target.filledPredecessor(this);
- }
- }
-
/**
* Change to control-flow graph to avoid repeated phi operands when all the same values
* flow in from multiple predecessors.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index dce8aa6..3288aa9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -745,7 +745,7 @@
Suppliers.memoize(() -> inliner.createDefaultOracle(
method, code, effectivelyFinalTypeEnvironment,
isProcessedConcurrently, callSiteInformation,
- Integer.MAX_VALUE, Integer.MAX_VALUE)
+ Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2)
)
);
assert code.isConsistentSSA();
@@ -782,6 +782,10 @@
}
codeRewriter.identifyParameterUsages(method, code, feedback);
+ if (options.canHaveNumberConversionRegisterAllocationBug()) {
+ codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
+ }
+
printMethod(code, "Optimized IR (SSA)");
finalizeIR(method, code, feedback);
}
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 5c25fba..53bbb7e 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
@@ -594,7 +594,12 @@
}
@Override
- public int getMoveExceptionRegister() {
+ public int getMoveExceptionRegister(int instructionIndex) {
+ return getMoveExceptionRegister();
+ }
+
+ // In classfiles the register is always on top of stack.
+ private int getMoveExceptionRegister() {
return state.startOfStack;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index ad6d66d..c26aecf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.BitSet;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 326fa03..2b1a6db 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.BitSet;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 359c90d..a547b36 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.BitSet;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index e95790a..bf2b51f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -59,7 +59,8 @@
void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, IRBuilder builder);
CatchHandlers<Integer> getCurrentCatchHandlers();
- int getMoveExceptionRegister();
+
+ int getMoveExceptionRegister(int instructionIndex);
// For debugging/verification purpose.
boolean verifyRegister(int register);
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 22e3c32..ee64165 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
@@ -13,10 +13,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.NotUsed;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.SingleCallOfArgumentMethod;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
@@ -37,6 +33,9 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -63,6 +62,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -850,28 +850,27 @@
List<Value> values = code.collectArguments(true);
for (int i = 0; i < values.size(); i++) {
Value value = values.get(i);
-
- int numberOfAllUsages = value.numberOfAllUsers();
- if (numberOfAllUsages == 0) {
- usages.add(new NotUsed(i));
+ if (value.numberOfPhiUsers() > 0) {
continue;
}
-
- if (numberOfAllUsages == 1 && value.numberOfUsers() == 1) {
- Instruction instruction = value.singleUniqueUser();
- if (instruction.isInvokeMethodWithReceiver() &&
- instruction.inValues().lastIndexOf(value) == 0) {
- usages.add(new SingleCallOfArgumentMethod(i,
- instruction.asInvokeMethodWithReceiver().getType(),
- instruction.asInvokeMethodWithReceiver().getInvokedMethod()));
- }
- continue;
+ ParameterUsage usage = collectParameterUsages(i, value);
+ if (usage != null) {
+ usages.add(usage);
}
}
-
feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
}
+ private ParameterUsage collectParameterUsages(int i, Value value) {
+ ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+ for (Instruction user : value.uniqueUsers()) {
+ if (!builder.note(user)) {
+ return null;
+ }
+ }
+ return builder.build();
+ }
+
// This method defines trivial instance initializer as follows:
//
// ** The initializer may call the initializer of the base class, which
@@ -3008,4 +3007,61 @@
}
}
}
+
+ // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
+ public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
+ final Supplier<DexMethod> javaLangDoubleisNaN = Suppliers.memoize(() ->
+ dexItemFactory.createMethod(
+ dexItemFactory.createString("Ljava/lang/Double;"),
+ dexItemFactory.createString("isNaN"),
+ dexItemFactory.booleanDescriptor,
+ new DexString[]{dexItemFactory.doubleDescriptor}));
+
+ ListIterator<BasicBlock> blocks = code.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isArithmeticBinop() || instruction.isNeg()) {
+ for (Value value : instruction.inValues()) {
+ // Insert a call to Double.isNaN on each value which come from a number conversion
+ // to double and flows into an arithmetic instruction. This seems to break the traces
+ // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
+ // values in a single-precision registers with double values written to
+ // double-precision registers. See b/77496850 for examples.
+ if (!value.isPhi()
+ && value.definition.isNumberConversion()
+ && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
+ Value newValue = code.createValue(
+ instruction.outValue().outType(), instruction.getLocalInfo());
+ InvokeStatic invokeIsNaN =
+ new InvokeStatic(javaLangDoubleisNaN.get(), newValue, ImmutableList.of(value));
+ invokeIsNaN.setPosition(instruction.getPosition());
+
+ // Insert the invoke before the current instruction.
+ it.previous();
+ BasicBlock blockWithInvokeNaN =
+ block.hasCatchHandlers() ? it.split(code, blocks) : block;
+ if (blockWithInvokeNaN != block) {
+ // If we split, add the invoke at the end of the original block.
+ it = block.listIterator(block.getInstructions().size());
+ it.previous();
+ it.add(invokeIsNaN);
+ // Continue iteration in the split block.
+ block = blockWithInvokeNaN;
+ it = block.listIterator();
+ } else {
+ // Otherwise, add it to the current block.
+ it.add(invokeIsNaN);
+ }
+ // Skip over the instruction causing the invoke to be inserted.
+ Instruction temp = it.next();
+ assert temp == instruction;
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 6cb8f6e..e749479 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1099,7 +1099,7 @@
}
@Override
- public int getMoveExceptionRegister() {
+ public int getMoveExceptionRegister(int instructionIndex) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 4d6e3b1..b3bde90 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -9,9 +9,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexEncodedMethod.OptimizationInfo;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.NotUsed;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.SingleCallOfArgumentMethod;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
@@ -19,6 +16,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
@@ -38,7 +36,9 @@
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.LinkedList;
@@ -48,6 +48,9 @@
import java.util.function.Supplier;
final class InlineCandidateProcessor {
+ private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
+ ImmutableSet.of(If.Type.EQ, If.Type.NE);
+
private final DexItemFactory factory;
private final AppInfoWithLiveness appInfo;
private final Predicate<DexType> isClassEligible;
@@ -441,6 +444,12 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (initInvoke.getBlock().hasCatchHandlers() &&
+ definition.getOptimizationInfo().neverReturnsNormally()) {
+ return null;
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -473,16 +482,16 @@
if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
return null; // Instance passed as an argument.
}
- return isEligibleMethodCall(invoke.getInvokedMethod(),
+ return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
eligibility -> !eligibility.returnsReceiver ||
invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
}
private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
- return isEligibleMethodCall(callee, eligibility -> !eligibility.returnsReceiver);
+ return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
}
- private InliningInfo isEligibleMethodCall(
+ private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
DexEncodedMethod singleTarget = findSingleTarget(callee);
@@ -493,8 +502,9 @@
return null; // Don't inline itself.
}
- ClassInlinerEligibility eligibility =
- singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+ OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+ ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
if (eligibility == null) {
return null;
}
@@ -505,6 +515,11 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) {
+ return null;
+ }
+
if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
// We won't be able to inline it here.
@@ -532,13 +547,24 @@
// eligible according to the same rules defined for direct method call eligibility
// (except we require the method receiver to not be used in return instruction)
//
+ // -- eligible instance is used in zero-test 'if' instructions testing if the value
+ // is null/not-null (since we know the instance is not null, those checks can
+ // be rewritten)
+ //
// -- method itself can be inlined
//
- private synchronized boolean isExtraMethodCallEligible(
+ private boolean isExtraMethodCallEligible(
Supplier<InliningOracle> defaultOracle, InvokeMethod invokeMethod) {
List<Value> arguments = Lists.newArrayList(invokeMethod.inValues());
+ // Don't consider constructor invocations and super calls, since
+ // we don't want to forcibly inline them.
+ if (invokeMethod.isInvokeSuper() ||
+ (invokeMethod.isInvokeDirect() && factory.isConstructor(invokeMethod.getInvokedMethod()))) {
+ return false;
+ }
+
// Remove receiver from arguments.
if (invokeMethod.isInvokeMethodWithReceiver()) {
if (arguments.get(0) == eligibleInstance) {
@@ -556,6 +582,11 @@
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) {
+ return false;
+ }
+
// Go through all arguments, see if all usages of eligibleInstance are good.
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
Value argument = arguments.get(argIndex);
@@ -569,35 +600,44 @@
return false; // Don't know anything.
}
- if (parameterUsage instanceof NotUsed) {
+ if (parameterUsage.notUsed()) {
// Reference can be removed since it's not used.
unusedArguments.add(new Pair<>(invokeMethod, argIndex));
continue;
}
- if (parameterUsage instanceof SingleCallOfArgumentMethod) {
- // Method exactly one time calls a method on passed eligibleInstance.
- SingleCallOfArgumentMethod info = (SingleCallOfArgumentMethod) parameterUsage;
- if (info.type != Type.VIRTUAL && info.type != Type.INTERFACE) {
- return false; // Don't support direct and super calls yet.
- }
-
- // Is the method called indirectly still eligible?
- InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(info.method);
- if (potentialInliningInfo != null) {
- // Check if the method is inline-able by standard inliner.
- InlineAction inlineAction =
- invokeMethod.computeInlining(defaultOracle.get(), method.method.holder);
- if (inlineAction != null) {
- extraMethodCalls.put(invokeMethod, new InliningInfo(singleTarget, null));
- continue;
- }
- }
-
+ if (parameterUsage.returnValue &&
+ !(invokeMethod.outValue() == null || invokeMethod.outValue().numberOfAllUsers() == 0)) {
+ // Used as return value which is not ignored.
return false;
}
- return false; // All other argument usages are not eligible.
+ if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) {
+ // Used in unsupported zero-check-if kinds.
+ return false;
+ }
+
+ for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) {
+ if (call.getFirst() != Type.VIRTUAL && call.getFirst() != Type.INTERFACE) {
+ // Don't support direct and super calls yet.
+ return false;
+ }
+
+ // Is the method called indirectly still eligible?
+ InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(call.getSecond());
+ if (potentialInliningInfo == null) {
+ return false;
+ }
+
+ // Check if the method is inline-able by standard inliner.
+ InlineAction inlineAction =
+ invokeMethod.computeInlining(defaultOracle.get(), method.method.holder);
+ if (inlineAction == null) {
+ return false;
+ }
+ }
+
+ extraMethodCalls.put(invokeMethod, new InliningInfo(singleTarget, null));
}
// Looks good.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index cebcc8c..08d0cae 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -354,7 +354,8 @@
// avoid a bug where the index variable could end up being uninitialized.
if (code.options.canHaveBoundsCheckEliminationBug()
&& move.from.getValue().isConstNumber()
- && move.type == MoveType.SINGLE) {
+ && move.type == MoveType.SINGLE
+ && allocator.unadjustedRealRegisterFromAllocated(move.to.getRegister()) < 256) {
scheduler.addMove(
new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 80a439a..c64d8ed 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -209,7 +209,7 @@
}
@Override
- public int getMoveExceptionRegister() {
+ public int getMoveExceptionRegister(int instructionIndex) {
throw new Unreachable();
}
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 20e8d57..baacb1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -112,7 +112,10 @@
private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet();
private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
- /** Set of method signatures used in invoke-super instructions that cannot not be resolved. */
+ /**
+ * Set of method signatures used in invoke-super instructions that either cannot be resolved or
+ * resolve to a private method (leading to an IllegalAccessError).
+ */
private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
/**
* This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
@@ -1032,6 +1035,9 @@
reportMissingMethod(method);
return;
}
+ if (target.accessFlags.isPrivate()) {
+ brokenSuperInvokes.add(method);
+ }
assert !superInvokeDependencies.containsKey(from) || !superInvokeDependencies.get(from)
.contains(target);
if (Log.ENABLED) {
@@ -1520,7 +1526,10 @@
* Set of all methods referenced in static invokes;
*/
public final SortedSet<DexMethod> staticInvokes;
- /** Set of method signatures used in invoke-super instructions that cannot not be resolved. */
+ /**
+ * Set of method signatures used in invoke-super instructions that either cannot be resolved or
+ * resolve to a private method (leading to an IllegalAccessError).
+ */
public final SortedSet<DexMethod> brokenSuperInvokes;
/**
* Set of all items that have to be kept independent of whether they are used.
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 c4276dc..1de3f17 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -873,6 +873,7 @@
ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
ruleBuilder.setArguments(parseArgumentList());
} else {
+ TextPosition firstStart = getPosition();
IdentifierPatternWithWildcards first =
acceptIdentifierWithBackreference(IdentifierType.ANY);
if (first != null) {
@@ -885,6 +886,7 @@
ruleBuilder.setName(first);
ruleBuilder.setArguments(parseArgumentList());
} else {
+ TextPosition secondStart = getPosition();
IdentifierPatternWithWildcards second =
acceptIdentifierWithBackreference(IdentifierType.ANY);
if (second != null) {
@@ -897,6 +899,12 @@
ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
ruleBuilder.setArguments(parseArgumentList());
} else {
+ if (first.hasUnusualCharacters()) {
+ warnUnusualCharacters("type", first.pattern, "field", firstStart);
+ }
+ if (second.hasUnusualCharacters()) {
+ warnUnusualCharacters("field name", second.pattern, "field", secondStart);
+ }
ruleBuilder.setRuleType(ProguardMemberType.FIELD);
ruleBuilder.setName(second);
ruleBuilder
@@ -1490,6 +1498,16 @@
"Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
}
+ private void warnUnusualCharacters(
+ String kind, String pattern, String ruleType, TextPosition start) {
+ reporter.warning(new StringDiagnostic(
+ "The " + kind + " \"" + pattern + "\" is used in a " + ruleType + " rule. The "
+ + "characters in this " + kind + " are legal for the JVM, "
+ + "but unlikely to originate from a source language. "
+ + "Maybe this is not the rule you are looking for.",
+ origin, getPosition(start)));
+ }
+
private void failPartiallyImplementedOption(String optionName, TextPosition start) {
throw reporter.fatalError(new StringDiagnostic(
"Option " + optionName + " currently not supported", origin, getPosition(start)));
@@ -1528,5 +1546,25 @@
boolean isMatchAllNames() {
return pattern.equals("*");
}
+
+ boolean hasUnusualCharacters() {
+ if (pattern.contains("<") || pattern.contains(">")) {
+ int angleStartCount = 0;
+ int angleEndCount = 0;
+ for (int i = 0; i < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ if (c == '<') {
+ angleStartCount++;
+ }
+ if (c == '>') {
+ angleEndCount++;
+ }
+ }
+ // Check that start/end angles are matched, and *only* used for well-formed wildcard
+ // backreferences (e.g. '<1>', but not '<<1>>', '<<*>>' or '>1<').
+ return !(angleStartCount == angleEndCount && angleStartCount == wildcards.size());
+ }
+ return false;
+ }
}
}
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 ec54641..eb8ee0e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -323,7 +323,7 @@
boolean printOutdatedToolchain = false;
if (warningInvalidParameterAnnotations.size() > 0) {
// TODO(b/67626202): Add a regression test with a program that hits this issue.
- reporter.warning(
+ reporter.info(
new StringDiagnostic(
"Invalid parameter counts in MethodParameter attributes. "
+ "This is likely due to Proguard having removed a parameter."));
@@ -348,7 +348,7 @@
for (List<Pair<DexEncodedMethod, String>> methods : warningInvalidDebugInfo.values()) {
count += methods.size();
}
- reporter.warning(
+ reporter.info(
new StringDiagnostic(
"Stripped invalid locals information from "
+ count
@@ -359,13 +359,13 @@
builder.append("\n ").append(method.getFirst().toSourceString());
builder.append("\n ").append(method.getSecond());
}
- reporter.warning(new StringDiagnostic(builder.toString(), origin));
+ reporter.info(new StringDiagnostic(builder.toString(), origin));
}
printed = true;
printOutdatedToolchain = true;
}
if (missingEnclosingMembers.size() > 0) {
- reporter.warning(
+ reporter.info(
new StringDiagnostic(
"InnerClass annotations are missing corresponding EnclosingMember annotations."
+ " Such InnerClass annotations are ignored."));
@@ -631,4 +631,12 @@
public boolean canHaveArtStringNewInitBug() {
return minApiLevel <= AndroidApiLevel.P.getLevel();
}
+
+ // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
+ // double and used in arithmetic operations.
+ //
+ // See b/77496850.
+ public boolean canHaveNumberConversionRegisterAllocationBug() {
+ return minApiLevel <= AndroidApiLevel.K.getLevel();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 2f45ae2..d79aaf0 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -116,9 +116,14 @@
messageParts);
}
+ public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, int index, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ return checkDiagnostic(diagnostics.get(index), path, lineStart, columnStart, messageParts);
+ }
+
public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
int lineStart, int columnStart, String... messageParts) {
assertEquals(1, diagnostics.size());
- return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+ return checkDiagnostics(diagnostics, 0, path, lineStart, columnStart, messageParts);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index e7344a4..0cf2f53 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -196,7 +196,7 @@
public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
final int secondBlockInstructions = 4;
- final int initialBlockCount = 5;
+ final int initialBlockCount = 6;
// Try split between all instructions in second block.
for (int i = 1; i < secondBlockInstructions; i++) {
TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
@@ -235,7 +235,7 @@
public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
throws Exception {
final int secondBlockInstructions = 4;
- final int initialBlockCount = 5;
+ final int initialBlockCount = 6;
// Try split out all instructions in second block.
for (int i = 1; i < secondBlockInstructions - 1; i++) {
TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 51a5262..3239191 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.OutputMode;
@@ -27,6 +28,7 @@
import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -254,6 +256,51 @@
assertFalse(inspector.clazz(C.F.class).isPresent());
}
+ @Test
+ public void testInvalidatedRoot() throws Exception {
+ String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
+
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+
+ String javaOutput = runOnJava(InvalidRootsTestClass.class);
+ String artOutput = runOnArt(app, InvalidRootsTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$InitNeverReturnsNormally"),
+ collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+
+ assertTrue(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class).isPresent());
+ assertTrue(inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class).isPresent());
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder",
+ "java.lang.RuntimeException"),
+ collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+
+ assertFalse(inspector.clazz(InvalidRootsTestClass.A.class).isPresent());
+ assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
new file mode 100644
index 0000000..26dc8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.invalidroot;
+
+public class InvalidRootsTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ InvalidRootsTestClass test = new InvalidRootsTestClass();
+ test.testExtraNeverReturnsNormally();
+ test.testDirectNeverReturnsNormally();
+ test.testInitNeverReturnsNormally();
+ test.testRootInvalidatesAfterInlining();
+ }
+
+ private synchronized void testExtraNeverReturnsNormally() {
+ testExtraNeverReturnsNormallyA();
+ testExtraNeverReturnsNormallyB();
+
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ neverReturnsNormallyExtra(next(), a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyA() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyB() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testDirectNeverReturnsNormally() {
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ System.out.println(a.foo());
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testInitNeverReturnsNormally() {
+ try {
+ new InitNeverReturnsNormally();
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
+ throw new RuntimeException("neverReturnsNormallyExtra(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ }
+
+ public static class NeverReturnsNormally {
+ public String foo() {
+ throw new RuntimeException("NeverReturnsNormally::foo(): " + next());
+ }
+ }
+
+ public static class InitNeverReturnsNormally {
+ public InitNeverReturnsNormally() {
+ throw new RuntimeException("InitNeverReturnsNormally::init(): " + next());
+ }
+
+ public String foo() {
+ return "InitNeverReturnsNormally::foo(): " + next();
+ }
+ }
+
+ private synchronized void testRootInvalidatesAfterInlining() {
+ A a = new A();
+ try {
+ notInlinedExtraMethod(next(), a);
+ System.out.println(new B().foo() + " " + next());
+ testRootInvalidatesAfterInliningA(a);
+ testRootInvalidatesAfterInliningB(a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void notInlinedExtraMethod(String prefix, A a) {
+ System.out.println("notInlinedExtraMethod(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ if (a != null) {
+ throw new RuntimeException(
+ "notInlinedExtraMethod(" + prefix + ", " + a.foo() + "): " + next());
+ }
+ System.out.println("notInlinedExtraMethod(" + prefix + ", null): " + next());
+ }
+
+ private void testRootInvalidatesAfterInliningA(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ private void testRootInvalidatesAfterInliningB(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ public static class A {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+
+ public static class B {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index a690429..81e12a1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -114,6 +114,21 @@
"class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
assertTrue(inspector.clazz(
"class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
+ .isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
+ .isPresent());
+ assertTrue(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
+ .isPresent());
});
runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
@@ -136,6 +151,31 @@
assertFalse(inspector.clazz(
"class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+
+ assertEquals(
+ Sets.newHashSet(),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethod"));
+
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
+
+ assertEquals(
+ Sets.newHashSet(),
+ collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
+
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
+ .isPresent());
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
+ .isPresent());
+ assertFalse(inspector.clazz(
+ "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
+ .isPresent());
});
}
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 4214a67..101bf06 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -766,7 +766,7 @@
}
@Override
- public int getMoveExceptionRegister() {
+ public int getMoveExceptionRegister(int instructionIndex) {
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 3e46098..c929eaf 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,12 +22,12 @@
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.function.BiConsumer;
-import org.junit.Ignore;
import org.junit.Test;
public class NeverReturnsNormallyTest extends TestBase {
private void runTest(
- BiConsumer<DexInspector, CompilationMode> inspection, CompilationMode mode) throws Exception {
+ BiConsumer<DexInspector, CompilationMode> inspection,
+ boolean enableClassInliner, CompilationMode mode) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
@@ -49,7 +49,8 @@
"-allowaccessmodification"
),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(),
+ opts -> opts.enableClassInlining = enableClassInliner);
inspection.accept(new DexInspector(app), mode);
// Run on Art to check generated code against verifier.
@@ -128,10 +129,11 @@
return instructions.next();
}
- @Ignore("b/110736241")
@Test
public void test() throws Exception {
- runTest(this::validate, CompilationMode.DEBUG);
- runTest(this::validate, CompilationMode.RELEASE);
+ runTest(this::validate, true, CompilationMode.DEBUG);
+ runTest(this::validate, true, CompilationMode.RELEASE);
+ runTest(this::validate, false, CompilationMode.DEBUG);
+ runTest(this::validate, false, CompilationMode.RELEASE);
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
new file mode 100644
index 0000000..03614b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -0,0 +1,507 @@
+// Copyright (c) 2018, 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.regress.b77496850;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class B77496850 extends TestBase {
+
+ static final String LOGTAG = "";
+
+ // Mock class for the code in PathParser below.
+ public static class Path {
+ void rLineTo(int x, int y) {
+ }
+ void cubicTo(float a, float b, float c, float d, float e, float f) {
+ }
+ }
+
+ // Mock class for the code in PathParser below.
+ public static class Log {
+ static void w(String x, String y) {
+ }
+ }
+
+ // Code copied from Android support library:
+ // https://android.googlesource.com/platform/frameworks/support/+/9791ac540f94c318f6602123d7000bfc55909b81/compat/src/main/java/android/support/v4/graphics/PathParser.java
+ public static class PathParser {
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+ // Adding this no-op call to workaround a proguard related issue.
+ p.rLineTo(0, 0);
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+ }
+
+ // Same code as PathParser above, but with exception handlers in the two methods.
+ public static class PathParserWithExceptionHandler {
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+ try {
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ } catch (Throwable t) {
+ // Ignore.
+ }
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ try {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+ // Adding this no-op call to workaround a proguard related issue.
+ p.rLineTo(0, 0);
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ } catch (Throwable t) {
+ // Ignore.
+ }
+ }
+
+ }
+
+ // Reproduction from b/77496850.
+ public static class Reproduction {
+ public int test() {
+ int count = 0;
+ for (int i = 0; i < 1000; i++){
+ count += arcToBezier(1.0, 1.0, 2.0);
+ }
+ return count;
+ }
+
+ private static int arcToBezier(double a, double b, double sweep) {
+ int count = 0;
+
+ int numSegments = (int) sweep;
+
+ double cosTheta = 0.5;
+ double sinTheta = 0.5;
+ double cosEta1 = 0.5;
+ double sinEta1 = 0.5;
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+
+ for (int i = 0; i < numSegments; i++) {
+ count++;
+ }
+ if (numSegments != count) {
+ return 1;
+ }
+ return 0;
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 100; i++) {
+ System.out.println(new Reproduction().test());
+ }
+ }
+ }
+
+ // Reproduction from b/77496850 with exception handler.
+ public static class ReproductionWithExceptionHandler {
+ public int test() {
+ int count = 0;
+ for (int i = 0; i < 1000; i++){
+ count += arcToBezier(1.0, 1.0, 2.0);
+ }
+ return count;
+ }
+
+ private static int arcToBezier(double a, double b, double sweep) {
+ try {
+ int count = 0;
+
+ int numSegments = (int) sweep;
+
+ double cosTheta = 0.5;
+ double sinTheta = 0.5;
+ double cosEta1 = 0.5;
+ double sinEta1 = 0.5;
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+
+ for (int i = 0; i < numSegments; i++) {
+ count++;
+ }
+ if (numSegments != count) {
+ return 1;
+ }
+ return 0;
+ } catch (Throwable t) {
+ return 1;
+ }
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 100; i++) {
+ System.out.println(new Reproduction().test());
+ }
+ }
+ }
+
+ private int countInvokeDoubleIsNan(DexItemFactory factory, DexCode code) {
+ int count = 0;
+ DexMethod doubleIsNaN = factory.createMethod(
+ factory.createString("Ljava/lang/Double;"),
+ factory.createString("isNaN"),
+ factory.booleanDescriptor,
+ new DexString[]{factory.doubleDescriptor});
+ for (int i = 0; i < code.instructions.length; i++) {
+ if (code.instructions[i] instanceof InvokeStatic) {
+ InvokeStatic invoke = (InvokeStatic) code.instructions[i];
+ if (invoke.getMethod() == doubleIsNaN) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ private void checkPathParserMethods(AndroidApp app, Class testClass, int a, int b)
+ throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ DexItemFactory factory = inspector.getFactory();
+ ClassSubject clazz = inspector.clazz(testClass);
+ MethodSubject drawArc = clazz.method(
+ "void",
+ "drawArc",
+ ImmutableList.of(
+ getClass().getCanonicalName() + "$Path",
+ "float", "float", "float", "float", "float", "float", "float", "boolean", "boolean"));
+ MethodSubject arcToBezier = clazz.method(
+ "void",
+ "arcToBezier",
+ ImmutableList.of(
+ getClass().getCanonicalName() + "$Path",
+ "double", "double", "double", "double", "double", "double",
+ "double", "double", "double"));
+ assertEquals(a, countInvokeDoubleIsNan(factory, drawArc.getMethod().getCode().asDexCode()));
+ assertEquals(b, countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+ }
+
+ private void runTestPathParser(
+ Tool compiler, Class testClass, AndroidApiLevel apiLevel, int a, int b)
+ throws Exception {
+ AndroidApp app = readClasses(Path.class, Log.class, testClass);
+ if (compiler == Tool.D8) {
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ } else {
+ assert compiler == Tool.R8;
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ }
+ checkPathParserMethods(app, testClass, a, b);
+ }
+
+ @Test
+ public void testSupportLibraryPathParser() throws Exception{
+ for (Tool tool : Tool.values()) {
+ runTestPathParser(tool, PathParser.class, AndroidApiLevel.K, 14, 1);
+ runTestPathParser(tool, PathParser.class, AndroidApiLevel.L, 0, 0);
+ runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.K, 14, 1);
+ runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.L, 0, 0);
+ }
+ }
+
+ private void runTestReproduction(
+ Tool compiler, Class testClass, AndroidApiLevel apiLevel, int expectedInvokeDoubleIsNanCount)
+ throws Exception {
+ AndroidApp app = readClasses(testClass);
+ if (compiler == Tool.D8) {
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ } else {
+ assert compiler == Tool.R8;
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ }
+ DexInspector inspector = new DexInspector(app);
+ DexItemFactory factory = inspector.getFactory();
+ ClassSubject clazz = inspector.clazz(testClass);
+ MethodSubject arcToBezier = clazz.method(
+ "int", "arcToBezier", ImmutableList.of("double", "double", "double"));
+ assertEquals(
+ expectedInvokeDoubleIsNanCount,
+ countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+ }
+
+ @Test
+ public void testReproduction() throws Exception{
+ for (Tool tool : Tool.values()) {
+ runTestReproduction(tool, Reproduction.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+ runTestReproduction(tool, Reproduction.class, AndroidApiLevel.L, 0);
+ runTestReproduction(
+ tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+ runTestReproduction(tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.L, 0);
+ }
+ }
+}
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 c176ff3..f484bc8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -14,9 +16,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.hamcrest.core.StringContains.containsString;
-
-import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -25,10 +24,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -694,7 +689,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_5);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 6, 1,
+ checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -708,7 +703,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_6);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 6, 1,
+ checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -735,7 +730,7 @@
parser.parse(path);
fail("Expect to fail due to the lack of file name.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 14, "File name expected");
+ checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
}
}
@@ -755,7 +750,7 @@
.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 10,"does-not-exist.flags");
+ checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
}
}
@@ -767,7 +762,7 @@
.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6,2, "does-not-exist.flags");
+ checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
}
}
@@ -930,7 +925,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 7, 1,
+ checkDiagnostics(handler.warnings, path, 7, 1,
"Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isOptimizing());
@@ -942,7 +937,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(OPTIMIZATION_PASSES);
parser.parse(path);
- checkDiagnostic(handler.warnings, path, 5, 1,
+ checkDiagnostics(handler.warnings, path, 5, 1,
"Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isOptimizing());
@@ -957,7 +952,7 @@
parser.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 6, 1, "Missing n");
+ checkDiagnostics(handler.errors, path, 6, 1, "Missing n");
}
}
@@ -970,8 +965,8 @@
parser.parse(path);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, path, 5, 1, "Unsupported option",
- "-skipnonpubliclibraryclasses");
+ checkDiagnostics(handler.errors, path, 5, 1,
+ "Unsupported option", "-skipnonpubliclibraryclasses");
}
}
@@ -1052,7 +1047,8 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1, "Unknown option", "-keepclassx");
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+ "Unknown option", "-keepclassx");
}
}
@@ -1325,7 +1321,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 2, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
}
}
}
@@ -1340,7 +1336,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
}
}
}
@@ -1355,7 +1351,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+ checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
}
}
}
@@ -1438,7 +1434,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1456,7 +1452,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1489,7 +1485,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- verifyParserEndsCleanly();
+ checkDiagnostics(handler.warnings, proguardConfig, 3, 7, "The field name \"id<<*>>\" is");
verifyWithProguard6(proguardConfig);
}
@@ -1508,7 +1504,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 4, 2,
+ checkDiagnostics(handler.errors, proguardConfig, 4, 2,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1526,7 +1522,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+ checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Wildcard", "<0>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (0,");
@@ -1544,7 +1540,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1562,7 +1558,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<2>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (2,");
@@ -1584,7 +1580,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 6, 2,
+ checkDiagnostics(handler.errors, proguardConfig, 6, 2,
"Wildcard", "<3>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
@@ -1602,7 +1598,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1619,7 +1615,7 @@
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
- checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1649,7 +1645,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalsideeffects");
}
@@ -1663,7 +1659,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoescapingparameters");
}
@@ -1687,7 +1683,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalreturnvalues");
}
@@ -1721,7 +1717,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-addconfigurationdebugging");
}
@@ -1735,7 +1731,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- verifyParserEndsCleanly();
+ checkDiagnostics(handler.warnings, proguardConfig, 2, 5, "The field name \"<fields>\" is");
verifyWithProguard(proguardConfig);
}
@@ -1757,13 +1753,33 @@
verifyWithProguard(proguardConfig);
}
+ @Test
+ public void parse_regress110021323() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-keepclassmembernames class A {",
+ " <public methods>;",
+ " <public fields>;",
+ "}"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ assertEquals(4, handler.warnings.size());
+ checkDiagnostics(handler.warnings, 0, proguardConfig, 2, 3, "The type \"<public\" is");
+ checkDiagnostics(handler.warnings, 1, proguardConfig, 2, 11, "The field name \"methods>\" is");
+ checkDiagnostics(handler.warnings, 2, proguardConfig, 3, 3, "The type \"<public\" is");
+ checkDiagnostics(handler.warnings, 3, proguardConfig, 3, 11, "The field name \"fields>\" is");
+
+ verifyWithProguard(proguardConfig);
+ }
+
public void testNotSupported(String option) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
fail("Expect to fail due to unsupported option.");
} catch (AbortException e) {
- checkDiagnostic(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
+ checkDiagnostics(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
}
}
@@ -1787,31 +1803,6 @@
assertEquals(0, handler.errors.size());
}
- // TODO(sgjesse): Change to use DiagnosticsChecker.
- private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
- int columnStart, String... messageParts) {
- assertEquals(1, diagnostics.size());
- Diagnostic diagnostic = diagnostics.get(0);
- if (path != null) {
- assertEquals(path, ((PathOrigin) diagnostic.getOrigin()).getPath());
- } else {
- assertSame(Origin.unknown(), diagnostic.getOrigin());
- }
- TextPosition position;
- if (diagnostic.getPosition() instanceof TextRange) {
- position = ((TextRange) diagnostic.getPosition()).getStart();
- } else {
- position = ((TextPosition) diagnostic.getPosition());
- }
- assertEquals(lineStart, position.getLine());
- assertEquals(columnStart, position.getColumn());
- for (String part : messageParts) {
- assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
- diagnostic.getDiagnosticMessage().contains(part));
- }
- return diagnostic;
- }
-
private void verifyWithProguard(Path proguardConfig) throws Exception {
if (isRunProguard()) {
// Add a keep rule for the test class as Proguard will fail if the resulting output jar is
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 5845dd3..e543d46 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -86,18 +86,24 @@
" public int method();",
"}"
), this::defaultMethodKept);
- runTest(ImmutableList.of(
- "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
- " <methods>;",
- "}"
- ), this::defaultMethodNotKept);
- runTest(ImmutableList.of(
- "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
- " <methods>;",
- "}",
- "-keep class " + TestClass.class.getCanonicalName() + "{",
- " public void useInterfaceMethod();",
- "}"
- ), this::defaultMethodAbstract);
+ runTest(
+ ImmutableList.of(
+ "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+ " <methods>;",
+ "}",
+ // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+ "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+ this::defaultMethodNotKept);
+ runTest(
+ ImmutableList.of(
+ "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+ " <methods>;",
+ "}",
+ "-keep class " + TestClass.class.getCanonicalName() + "{",
+ " public void useInterfaceMethod();",
+ "}",
+ // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+ "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+ this::defaultMethodAbstract);
}
}
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
index 136f72d..06b937e 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
@@ -11,6 +11,8 @@
fun main(args: Array<String>) {
testKotlinSequencesStateless(produceSequence(10))
testKotlinSequencesStateful(5, 2, produceSequence(10))
+ testBigExtraMethod()
+ testBigExtraMethodReturningLambda()
}
data class Record(val foo: String, val good: Boolean)
@@ -38,12 +40,57 @@
}
}
-private fun produceSequence(size: Int): Sequence<String> {
+@Synchronized
+fun testBigExtraMethod() {
+ useRecord()
+ bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+ testBigExtraMethod2()
+ testBigExtraMethod3()
+}
+
+fun testBigExtraMethod2() {
+ bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+}
+
+fun testBigExtraMethod3() {
+ bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+}
+
+fun bigUserWithNotNullChecksAndTwoCalls(id: String, lambda: () -> String): String {
+ useRecord()
+ println("[A] logging call#$id returning ${lambda()}")
+ return "$id: ${lambda()}"
+}
+
+@Synchronized
+fun testBigExtraMethodReturningLambda() {
+ useRecord()
+ bigUserReturningLambda(next()) { next() } // Not used
+ testBigExtraMethodReturningLambda2()
+ testBigExtraMethodReturningLambda3()
+}
+
+fun testBigExtraMethodReturningLambda2() {
+ bigUserReturningLambda(next()) { next() } // Not used
+}
+
+fun testBigExtraMethodReturningLambda3() {
+ bigUserReturningLambda(next()) { next() } // Not used
+}
+
+fun bigUserReturningLambda(id: String, lambda: () -> String): () -> String {
+ useRecord()
+ println("[B] logging call#$id returning ${lambda()}")
+ println("[C] logging call#$id returning ${lambda()}")
+ return lambda
+}
+
+fun produceSequence(size: Int): Sequence<String> {
var count = size
return generateSequence { if (count-- > 0) next() else null }
}
// Need this to make sure testKotlinSequenceXXX is not processed
// concurrently with invoke() on lambdas.
-fun useRecord() = useRecord2()
-fun useRecord2() = Record("", true)
+@Synchronized fun useRecord() = useRecord2()
+@Synchronized fun useRecord2() = Record("", true)
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
index f57ba90..9d9985c 100644
--- a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -1 +1 @@
-161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
+a6d49ef4fb2094672a6f6be039c971727cc9fd34
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index 8f6813c..056ff59 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file
+57b5c53a80ba010d1faef7da1b643f8c72b3e4e8
\ No newline at end of file
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index b31ade4..90e34ed 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -13,7 +13,9 @@
import shutil
import subprocess
import sys
+import time
import utils
+import uuid
ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
DEFAULT_AAPT = 'aapt' # Assume in path.
@@ -22,6 +24,9 @@
DEFAULT_JAVAC = 'javac'
SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
+PACKAGE_PREFIX = 'com.android.tools.r8.sample'
+STANDARD_ACTIVITY = "R8Activity"
+BENCHMARK_ITERATIONS = 100
SAMPLE_APKS = [
'simple',
@@ -46,6 +51,12 @@
result.add_option('--install',
help='Install the app (including featuresplit)',
default=False, action='store_true')
+ result.add_option('--benchmark',
+ help='Benchmark the app on the phone with specialized markers',
+ default=False, action='store_true')
+ result.add_option('--benchmark-output-dir',
+ help='Store benchmark results here.',
+ default=None)
result.add_option('--app',
help='Which app to build',
default='simple',
@@ -86,6 +97,13 @@
def get_split_path(app, split):
return os.path.join(get_bin_path(app), split, 'classes.dex')
+def get_package_name(app):
+ return '%s.%s' % (PACKAGE_PREFIX, app)
+
+def get_qualified_activity(app):
+ # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
+ return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
+
def run_aapt_pack(aapt, api, app):
with utils.ChangedWorkingDirectory(get_sample_dir(app)):
args = ['package',
@@ -169,6 +187,71 @@
dex]
run_aapt(aapt, args)
+def kill(app):
+ args = ['shell', 'am', 'force-stop', get_package_name(app)]
+ run_adb(args)
+
+def start_logcat():
+ return subprocess.Popen(['adb', 'logcat'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+def start(app):
+ args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
+ run_adb(args)
+
+def clear_logcat():
+ args = ['logcat', '-c']
+ run_adb(args)
+
+def stop_logcat(popen):
+ popen.terminate()
+ lines = []
+ for l in popen.stdout:
+ if 'System.out' in l:
+ lines.append(l)
+ return lines
+
+def store_or_print_benchmarks(lines, output):
+ single_runs = []
+ total_time = None
+ # We assume that the individual runs are prefixed with 'Took: ' and the total time is
+ # prefixed with 'Total: '. The logcat lines looks like:
+ # 06-28 12:22:00.991 13698 13698 I System.out: Took: 61614
+ for l in lines:
+ if 'Took: ' in l:
+ timing = l.split('Took: ')[1]
+ single_runs.append(timing)
+ if 'Total: ' in l:
+ timing = l.split('Total: ')[1]
+ total_time = timing
+ assert len(single_runs) > 0
+ assert total_time
+ if not output:
+ print 'Individual timings: \n%s' % ''.join(single_runs)
+ print 'Total time: \n%s' % total_time
+ return
+
+ output_dir = os.path.join(output, str(uuid.uuid4()))
+ os.makedirs(output_dir)
+ single_run_file = os.path.join(output_dir, 'single_runs')
+ with open(single_run_file, 'w') as f:
+ f.writelines(single_runs)
+ total_file = os.path.join(output_dir, 'total')
+ with open(total_file, 'w') as f:
+ f.write(total_time)
+ print 'Result stored in %s and %s' % (single_run_file, total_file)
+
+def benchmark(app, output_dir):
+ # Ensure app is not running
+ kill(app)
+ clear_logcat()
+ logcat = start_logcat()
+ start(app)
+ # We could do better here by continiously parsing the logcat for a marker, but
+ # this works nicely with the current setup.
+ time.sleep(3)
+ kill(app)
+ store_or_print_benchmarks(stop_logcat(logcat), output_dir)
+
def Main():
(options, args) = parse_options()
apks = []
@@ -202,7 +285,9 @@
print('Generated apks available at: %s' % ' '.join(apks))
if options.install:
adb_install(apks)
-
+ if options.benchmark:
+ for _ in range(BENCHMARK_ITERATIONS):
+ benchmark(options.app, options.benchmark_output_dir)
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index 8230c71..bfc62e6 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -49,7 +49,7 @@
print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
dex(PINNED_PGR8_JAR, d8_pg_output)
- print "BootstrapPG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
- print "BootstrapPGDex(CodeSize):", os.path.getsize(d8_pg_output)
+ print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
+ print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
- sys.exit(0)
\ No newline at end of file
+ sys.exit(0)