Explicit transfer of exceptional control flow in analysis framework
Bug: b/236244240
Bug: b/214496607
Change-Id: I1cfa296b6e33d0fde231c115576756112adffe40
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 932a283..248cf1c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -343,6 +343,11 @@
return false;
}
+ @Override
+ public final boolean instructionTypeCanThrow() {
+ return canThrow();
+ }
+
public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code);
/** Return true if this instruction directly emits IR instructions. */
diff --git a/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
index 7bd1e7a..8d7ef16 100644
--- a/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/CfOrDexInstruction.java
@@ -5,8 +5,9 @@
package com.android.tools.r8.dex.code;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractInstruction;
-public interface CfOrDexInstruction {
+public interface CfOrDexInstruction extends AbstractInstruction {
CfInstruction asCfInstruction();
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index cc35c46..5efe526 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -415,4 +415,9 @@
public boolean canThrow() {
return false;
}
+
+ @Override
+ public final boolean instructionTypeCanThrow() {
+ return canThrow();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index bac72e9..99c43b7 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -281,6 +281,10 @@
return tryCatchRanges;
}
+ public CfInstruction getInstruction(int index) {
+ return instructions.get(index);
+ }
+
public List<CfInstruction> getInstructions() {
return Collections.unmodifiableList(instructions);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java
new file mode 100644
index 0000000..d68129f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractInstruction.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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.analysis.framework.intraprocedural;
+
+public interface AbstractInstruction {
+
+ boolean instructionTypeCanThrow();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index 697dbb9..57e5b6b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -11,18 +11,44 @@
public interface AbstractTransferFunction<
Block, Instruction, StateType extends AbstractState<StateType>> {
+ /** Applies the effect of the given instruction on the given abstract state. */
TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
default TransferFunctionResult<StateType> applyBlock(Block block, StateType state) {
return state;
}
+ /**
+ * Computes the analysis state for the method entry point, i.e., the state prior to the first
+ * instruction.
+ */
default StateType computeInitialState(Block entryBlock, StateType bottom) {
return bottom;
}
+ /** Transfers the state from predecessor block to its successor block. */
default StateType computeBlockEntryState(
Block block, Block predecessor, StateType predecessorExitState) {
return predecessorExitState;
}
+
+ /**
+ * Returns true if (a function of) the abstract state at the given (throwing) instruction should
+ * be transferred to the active catch handlers.
+ */
+ default boolean shouldTransferExceptionalControlFlowFromInstruction(
+ Block throwBlock, Instruction throwInstruction) {
+ return true;
+ }
+
+ /**
+ * Transfers the state from the given (throwing) instruction to its catch handler.
+ *
+ * <p>Only called if {@link #shouldTransferExceptionalControlFlowFromInstruction} has returned
+ * true.
+ */
+ default StateType computeExceptionalBlockEntryState(
+ Block block, Block throwBlock, Instruction throwInstruction, StateType throwState) {
+ return throwState;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
index db0d7ca..ecf6287 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
@@ -14,10 +14,32 @@
Block getEntryBlock();
+ default Block getUniquePredecessor(Block block) {
+ assert hasUniquePredecessor(block);
+ return TraversalUtils.getFirst(collector -> traversePredecessors(block, collector));
+ }
+
+ default Block getUniqueSuccessor(Block block) {
+ assert hasUniqueSuccessor(block);
+ return TraversalUtils.getFirst(collector -> traverseSuccessors(block, collector));
+ }
+
+ default boolean hasExceptionalPredecessors(Block block) {
+ return TraversalUtils.hasNext(counter -> traverseExceptionalPredecessors(block, counter));
+ }
+
+ default boolean hasExceptionalSuccessors(Block block) {
+ return TraversalUtils.hasNext(counter -> traverseExceptionalSuccessors(block, counter));
+ }
+
default boolean hasUniquePredecessor(Block block) {
return TraversalUtils.isSingleton(counter -> traversePredecessors(block, counter));
}
+ default boolean hasUniquePredecessorWithUniqueSuccessor(Block block) {
+ return hasUniquePredecessor(block) && hasUniqueSuccessor(getUniquePredecessor(block));
+ }
+
default boolean hasUniqueSuccessor(Block block) {
return TraversalUtils.isSingleton(counter -> traverseSuccessors(block, counter));
}
@@ -26,11 +48,6 @@
return hasUniqueSuccessor(block) && hasUniquePredecessor(getUniqueSuccessor(block));
}
- default Block getUniqueSuccessor(Block block) {
- assert hasUniqueSuccessor(block);
- return TraversalUtils.getFirst(collector -> traverseSuccessors(block, collector));
- }
-
// Block traversal.
default <BT, CT> TraversalContinuation<BT, CT> traversePredecessors(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
index b70c08f..dbd2a45 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
@@ -25,7 +25,7 @@
* FailedDataflowAnalysisResult}.
*/
public class IntraProceduralDataflowAnalysisBase<
- Block, Instruction, StateType extends AbstractState<StateType>> {
+ Block, Instruction extends AbstractInstruction, StateType extends AbstractState<StateType>> {
final StateType bottom;
@@ -34,13 +34,16 @@
// The transfer function that defines the abstract semantics for each instruction.
final AbstractTransferFunction<Block, Instruction, StateType> transfer;
- // The state of the analysis.
- final Map<Block, StateType> blockExitStates = new IdentityHashMap<>();
-
// The entry states for each block that satisfies the predicate
// shouldCacheBlockEntryStateFor(block). These entry states can be computed from the exit states
// of the predecessors, but doing so can be expensive when a block has many predecessors.
- final Map<Block, StateType> blockEntryStatesCache = new IdentityHashMap<>();
+ final Map<Block, StateType> blockEntryStates = new IdentityHashMap<>();
+
+ // The state of the analysis.
+ final Map<Block, StateType> blockExitStates = new IdentityHashMap<>();
+
+ // The entry states for exceptional blocks.
+ final Map<Block, StateType> exceptionalBlockEntryStates = new IdentityHashMap<>();
final IntraProceduralDataflowAnalysisOptions options;
@@ -81,10 +84,19 @@
timing.begin("Compute transfers");
do {
+ Block currentBlock = block;
+ boolean hasExceptionalSuccessors = cfg.hasExceptionalSuccessors(block);
TraversalContinuation<FailedDataflowAnalysisResult, StateType> traversalContinuation =
cfg.traverseInstructions(
block,
(instruction, previousState) -> {
+ if (instruction.instructionTypeCanThrow()
+ && hasExceptionalSuccessors
+ && transfer.shouldTransferExceptionalControlFlowFromInstruction(
+ currentBlock, instruction)) {
+ updateBlockEntryStateCacheForExceptionalSuccessors(
+ currentBlock, instruction, previousState);
+ }
TransferFunctionResult<StateType> transferResult =
transfer.apply(instruction, previousState);
if (transferResult.isFailedTransferResult()) {
@@ -99,10 +111,7 @@
return traversalContinuation.asBreak().getValue();
}
state = traversalContinuation.asContinue().getValue();
- if (cfg.hasUniqueSuccessorWithUniquePredecessor(block)) {
- if (!options.isCollapsingOfTrivialEdgesEnabled()) {
- setBlockExitState(block, state);
- }
+ if (isBlockWithIntermediateSuccessorBlock(block)) {
block = cfg.getUniqueSuccessor(block);
} else {
end = block;
@@ -117,27 +126,42 @@
cfg.forEachSuccessor(end, worklist::addIgnoringSeenSet);
}
- // Add the computed exit state to the entry state of each successor that satisfies the
+ // Add the computed exit state to the entry state of each normal successor that satisfies the
// predicate shouldCacheBlockEntryStateFor(successor).
- updateBlockEntryStateCacheForSuccessors(end, state);
+ updateBlockEntryStateCacheForNormalSuccessors(end, state);
}
return new SuccessfulDataflowAnalysisResult<>(blockExitStates);
}
public StateType computeBlockEntryState(Block block) {
if (block == cfg.getEntryBlock()) {
- return transfer.computeInitialState(block, bottom);
+ return transfer
+ .computeInitialState(block, bottom)
+ .join(computeBlockEntryStateForNormalBlock(block));
}
- if (shouldCacheBlockEntryStateFor(block)) {
- return blockEntryStatesCache.getOrDefault(block, bottom);
+ if (cfg.hasExceptionalPredecessors(block)) {
+ return exceptionalBlockEntryStates.getOrDefault(block, bottom).clone();
}
+ return computeBlockEntryStateForNormalBlock(block);
+ }
+
+ private StateType computeBlockEntryStateForNormalBlock(Block block) {
+ if (shouldCacheBlockEntryStateForNormalBlock(block)) {
+ return blockEntryStates.getOrDefault(block, bottom).clone();
+ }
+ return computeBlockEntryStateFromPredecessorExitStates(block);
+ }
+
+ private StateType computeBlockEntryStateFromPredecessorExitStates(Block block) {
TraversalContinuation<?, StateType> traversalContinuation =
- cfg.traversePredecessors(
+ cfg.traverseNormalPredecessors(
block,
(predecessor, entryState) -> {
StateType edgeState =
transfer.computeBlockEntryState(
- block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
+ block,
+ predecessor,
+ blockExitStates.getOrDefault(predecessor, bottom).clone());
return TraversalContinuation.doContinue(entryState.join(edgeState));
},
bottom);
@@ -145,26 +169,54 @@
}
boolean setBlockExitState(Block block, StateType state) {
- assert !cfg.hasUniqueSuccessorWithUniquePredecessor(block)
- || !options.isCollapsingOfTrivialEdgesEnabled();
+ assert !isBlockWithIntermediateSuccessorBlock(block);
StateType previous = blockExitStates.put(block, state);
assert previous == null || state.isGreaterThanOrEquals(previous);
return !state.equals(previous);
}
- void updateBlockEntryStateCacheForSuccessors(Block block, StateType state) {
- cfg.forEachSuccessor(
+ void updateBlockEntryStateCacheForNormalSuccessors(Block block, StateType state) {
+ cfg.forEachNormalSuccessor(
block,
successor -> {
- if (shouldCacheBlockEntryStateFor(successor)) {
+ if (shouldCacheBlockEntryStateForNormalBlock(successor)) {
StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
- StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
- blockEntryStatesCache.put(successor, previous.join(edgeState));
+ updateBlockEntryStateForBlock(successor, edgeState, blockEntryStates);
}
});
}
- boolean shouldCacheBlockEntryStateFor(Block block) {
+ void updateBlockEntryStateCacheForExceptionalSuccessors(
+ Block block, Instruction instruction, StateType state) {
+ cfg.forEachExceptionalSuccessor(
+ block,
+ exceptionalSuccessor -> {
+ StateType edgeState =
+ transfer.computeExceptionalBlockEntryState(
+ exceptionalSuccessor, block, instruction, state);
+ updateBlockEntryStateForBlock(
+ exceptionalSuccessor, edgeState, exceptionalBlockEntryStates);
+ });
+ }
+
+ private void updateBlockEntryStateForBlock(
+ Block block, StateType edgeState, Map<Block, StateType> states) {
+ StateType previous = states.getOrDefault(block, bottom);
+ states.put(block, previous.join(edgeState));
+ }
+
+ public boolean isIntermediateBlock(Block block) {
+ return options.isCollapsingOfTrivialEdgesEnabled()
+ && cfg.hasUniquePredecessorWithUniqueSuccessor(block)
+ && block != cfg.getEntryBlock()
+ && !cfg.hasExceptionalPredecessors(block);
+ }
+
+ public boolean isBlockWithIntermediateSuccessorBlock(Block block) {
+ return cfg.hasUniqueSuccessor(block) && isIntermediateBlock(cfg.getUniqueSuccessor(block));
+ }
+
+ boolean shouldCacheBlockEntryStateForNormalBlock(Block block) {
return TraversalUtils.isSizeGreaterThan(counter -> cfg.traversePredecessors(block, counter), 2);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index 4918dfe..9f3ea94 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -19,7 +19,7 @@
}
@Override
- boolean shouldCacheBlockEntryStateFor(BasicBlock block) {
+ boolean shouldCacheBlockEntryStateForNormalBlock(BasicBlock block) {
return block.getPredecessors().size() > 2;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
index 357ff33..942cb97 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfBlock.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -90,10 +91,14 @@
this.lastInstructionIndex = lastInstructionIndex;
}
- boolean validate() {
+ boolean validate(CfControlFlowGraph cfg, InternalOptions options) {
assert 0 <= firstInstructionIndex;
assert firstInstructionIndex <= lastInstructionIndex;
assert SetUtils.newIdentityHashSet(predecessors).size() == predecessors.size();
+ assert this == cfg.getEntryBlock()
+ || !predecessors.isEmpty()
+ || !exceptionalPredecessors.isEmpty()
+ || options.getCfCodeAnalysisOptions().isUnreachableCfBlocksAllowed();
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
index ff69065..4fe4724 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/cf/CfControlFlowGraph.java
@@ -12,12 +12,17 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.ControlFlowGraph;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.cf.CfBlock.MutableCfBlock;
-import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.TraversalUtils;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Deque;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -48,8 +53,8 @@
return new Builder(code);
}
- public static CfControlFlowGraph create(CfCode code) {
- return builder(code).build();
+ public static CfControlFlowGraph create(CfCode code, InternalOptions options) {
+ return builder(code).build(options);
}
private CfBlock getBlock(CfInstruction blockEntry) {
@@ -127,7 +132,7 @@
this.code = code;
}
- CfControlFlowGraph build() {
+ CfControlFlowGraph build(InternalOptions options) {
// Perform an initial pass over the CfCode to identify all instructions that start a new
// block.
createBlocks();
@@ -137,9 +142,11 @@
// identified all block entries up front.
processBlocks();
- assert blocks.values().stream().allMatch(MutableCfBlock::validate);
+ removeBlockForTrailingLabel();
- return new CfControlFlowGraph(blocks, code);
+ CfControlFlowGraph cfg = new CfControlFlowGraph(blocks, code);
+ assert blocks.values().stream().allMatch(block -> block.validate(cfg, options));
+ return cfg;
}
private void createBlocks() {
@@ -158,6 +165,11 @@
? instructions.get(fallthroughInstructionIndex)
: null;
instruction.forEachNormalTarget(this::createBlockIfAbsent, fallthroughInstruction);
+
+ // Also create a block for the fallthrough instruction, though it may be unreachable.
+ if (!instruction.asJump().hasFallthrough() && fallthroughInstruction != null) {
+ createBlockIfAbsent(fallthroughInstruction);
+ }
}
}
@@ -173,19 +185,25 @@
}
private void processBlocks() {
- // A collection of active catch handlers. The catch handlers are stored in a map where the key
- // is the label at which the catch handlers end.
- Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers = new IdentityHashMap<>();
+ // A collection of active catch handlers. The most recently added catch handlers take
+ // precedence over catch handlers added before them.
+ Deque<CfTryCatch> activeCatchHandlers = new ArrayDeque<>();
// A collection of inactive catch handlers. The catch handlers are stored in a map where the
// key is the label at which the catch handlers start.
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers = new IdentityHashMap<>();
// Initialize all catch handlers to be inactive.
+ Reference2IntMap<CfLabel> labelIndices = computeLabelToInstructionIndexMapForTesting();
for (CfTryCatch tryCatch : code.getTryCatchRanges()) {
- inactiveUntilCatchHandlers
- .computeIfAbsent(tryCatch.getStart(), ignoreKey(ArrayList::new))
- .add(tryCatch);
+ List<CfTryCatch> inactiveCatchHandlersUntilStart =
+ inactiveUntilCatchHandlers.computeIfAbsent(
+ tryCatch.getStart(), ignoreKey(ArrayList::new));
+ // Catch handlers that come before others are expected to end first.
+ assert inactiveCatchHandlersUntilStart.isEmpty()
+ || labelIndices.getInt(tryCatch.getEnd())
+ >= labelIndices.getInt(ListUtils.last(inactiveCatchHandlersUntilStart).getEnd());
+ inactiveCatchHandlersUntilStart.add(tryCatch);
}
// Process each instruction.
@@ -199,40 +217,55 @@
instruction,
instructionIndex,
block,
- activeUntilCatchHandlers,
+ activeCatchHandlers,
inactiveUntilCatchHandlers);
}
}
- assert activeUntilCatchHandlers.isEmpty();
+ assert activeCatchHandlers.isEmpty();
assert inactiveUntilCatchHandlers.isEmpty();
}
+ private Reference2IntMap<CfLabel> computeLabelToInstructionIndexMapForTesting() {
+ if (!InternalOptions.assertionsEnabled()) {
+ return null;
+ }
+ Reference2IntMap<CfLabel> result = new Reference2IntOpenHashMap<>();
+ List<CfInstruction> instructions = code.getInstructions();
+ for (int instructionIndex = 0; instructionIndex < instructions.size(); instructionIndex++) {
+ CfInstruction instruction = instructions.get(instructionIndex);
+ if (instruction.isLabel()) {
+ result.put(instruction.asLabel(), instructionIndex);
+ }
+ }
+ return result;
+ }
+
private int processBlock(
CfInstruction instruction,
int instructionIndex,
MutableCfBlock block,
- Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+ Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Record the index of the first instruction of the block.
block.setFirstInstructionIndex(instructionIndex);
if (instruction.isLabel()) {
- updateCatchHandlers(
- instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
+ updateCatchHandlers(instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
}
// Visit each instruction belonging to the current block.
Set<CfLabel> exceptionalSuccessors = new LinkedHashSet<>();
+ Iterator<CfTryCatch> activeCatchHandlerIterator = activeCatchHandlers.descendingIterator();
+ while (activeCatchHandlerIterator.hasNext()) {
+ CfTryCatch activeCatchHandler = activeCatchHandlerIterator.next();
+ activeCatchHandler.forEachTarget(exceptionalSuccessors::add);
+ }
+
do {
assert !instruction.isLabel()
|| verifyCatchHandlersUnchanged(
- instruction.asLabel(), activeUntilCatchHandlers, inactiveUntilCatchHandlers);
- if (instruction.canThrow()) {
- for (CfTryCatch tryCatch : IterableUtils.flatten(activeUntilCatchHandlers.values())) {
- exceptionalSuccessors.addAll(tryCatch.getTargets());
- }
- }
+ instruction.asLabel(), activeCatchHandlers, inactiveUntilCatchHandlers);
if (isBlockExit(instructionIndex)) {
break;
}
@@ -258,6 +291,13 @@
return instructionIndex;
}
+ private void removeBlockForTrailingLabel() {
+ CfInstruction lastInstruction = code.getInstruction(code.getInstructions().size() - 1);
+ if (lastInstruction.isLabel() && isBlockEntry(lastInstruction)) {
+ blocks.remove(lastInstruction);
+ }
+ }
+
private boolean isBlockEntry(CfInstruction instruction) {
return blocks.containsKey(instruction);
}
@@ -273,26 +313,31 @@
private void updateCatchHandlers(
CfLabel instruction,
- Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+ Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
// Remove active catch handlers that have expired at the current instruction.
- activeUntilCatchHandlers.remove(instruction);
+ while (!activeCatchHandlers.isEmpty()
+ && activeCatchHandlers.peekLast().getEnd() == instruction) {
+ activeCatchHandlers.removeLast();
+ }
// Promote inactive catch handlers that is activated at the current instruction to active.
- for (CfTryCatch tryCatch :
- inactiveUntilCatchHandlers.getOrDefault(instruction, Collections.emptyList())) {
- assert tryCatch.getEnd() != tryCatch.getStart();
- activeUntilCatchHandlers
- .computeIfAbsent(tryCatch.getEnd(), ignoreKey(ArrayList::new))
- .add(tryCatch);
+ List<CfTryCatch> newlyActiveCatchHandlers = inactiveUntilCatchHandlers.remove(instruction);
+ if (newlyActiveCatchHandlers != null) {
+ for (int i = newlyActiveCatchHandlers.size() - 1; i >= 0; i--) {
+ CfTryCatch newlyActiveCatchHandler = newlyActiveCatchHandlers.get(i);
+ assert newlyActiveCatchHandler.getEnd() != newlyActiveCatchHandler.getStart();
+ activeCatchHandlers.addLast(newlyActiveCatchHandler);
+ }
}
}
private boolean verifyCatchHandlersUnchanged(
CfLabel instruction,
- Map<CfLabel, List<CfTryCatch>> activeUntilCatchHandlers,
+ Deque<CfTryCatch> activeCatchHandlers,
Map<CfLabel, List<CfTryCatch>> inactiveUntilCatchHandlers) {
- assert !activeUntilCatchHandlers.containsKey(instruction);
+ assert activeCatchHandlers.isEmpty()
+ || activeCatchHandlers.peekLast().getEnd() != instruction;
assert !inactiveUntilCatchHandlers.containsKey(instruction);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index feaca12..7cb6f50 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractInstruction;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -46,7 +47,8 @@
import java.util.function.Function;
import java.util.function.Predicate;
-public abstract class Instruction implements InstructionOrPhi, TypeAndLocalInfoSupplier {
+public abstract class Instruction
+ implements AbstractInstruction, InstructionOrPhi, TypeAndLocalInfoSupplier {
protected Value outValue = null;
protected final List<Value> inValues = new ArrayList<>();
@@ -604,9 +606,8 @@
return false;
}
- /**
- * Returns true if this instruction may throw an exception.
- */
+ /** Returns true if this instruction may throw an exception. */
+ @Override
public boolean instructionTypeCanThrow() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index e4bbb6c..a955882 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -149,6 +149,15 @@
return predecessorExitState;
}
+ @Override
+ public ParameterUsages computeExceptionalBlockEntryState(
+ BasicBlock block,
+ BasicBlock throwBlock,
+ Instruction throwInstruction,
+ ParameterUsages throwState) {
+ return throwState;
+ }
+
private ParameterUsages analyzeArgument(Argument argument, ParameterUsages state) {
// Only consider arguments that could store an instance eligible for class inlining. Note that
// we can't ignore parameters with a library type, since instances of program classes could
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
index 33e4cf2..121b5ab 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.optimize.interfaces.analysis;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -44,7 +45,7 @@
}
CfCode cfCode = code.asCfCode();
- CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode);
+ CfControlFlowGraph cfg = CfControlFlowGraph.create(cfCode, appView.options());
CfIntraproceduralDataflowAnalysis<CfFrameState> analysis =
new CfIntraproceduralDataflowAnalysis<>(
CfFrameState.bottom(), cfg, new TransferFunction(method));
@@ -97,5 +98,15 @@
CfBlock block, CfBlock predecessor, CfFrameState predecessorExitState) {
return predecessorExitState;
}
+
+ @Override
+ public CfFrameState computeExceptionalBlockEntryState(
+ CfBlock block,
+ CfBlock throwBlock,
+ CfInstruction throwInstruction,
+ CfFrameState throwState) {
+ // TODO(b/214496607): Handle exceptional control flow.
+ throw new Unimplemented();
+ }
}
}
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 14dd120..71e92a3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -808,6 +808,7 @@
private final CallSiteOptimizationOptions callSiteOptimizationOptions =
new CallSiteOptimizationOptions();
+ private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions();
private final InlinerOptions inlinerOptions = new InlinerOptions();
private final HorizontalClassMergerOptions horizontalClassMergerOptions =
@@ -867,6 +868,10 @@
return desugarSpecificOptions;
}
+ public CfCodeAnalysisOptions getCfCodeAnalysisOptions() {
+ return cfCodeAnalysisOptions;
+ }
+
public OpenClosedInterfacesOptions getOpenClosedInterfacesOptions() {
return openClosedInterfacesOptions;
}
@@ -1399,6 +1404,19 @@
}
}
+ public static class CfCodeAnalysisOptions {
+
+ public boolean allowUnreachableCfBlocks = true;
+
+ public boolean isUnreachableCfBlocksAllowed() {
+ return allowUnreachableCfBlocks;
+ }
+
+ public void setAllowUnreachableCfBlocks(boolean allowUnreachableCfBlocks) {
+ this.allowUnreachableCfBlocks = allowUnreachableCfBlocks;
+ }
+ }
+
public class ClassInlinerOptions {
public int classInliningInstructionAllowance = -1;
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalUtils.java b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
index 25e1be0..b705d02 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalUtils.java
@@ -18,6 +18,16 @@
return traversal.apply(TraversalContinuation::doBreak).asBreak().getValue();
}
+ public static <BT, CT> boolean hasNext(
+ Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
+ return !isEmpty(traversal);
+ }
+
+ public static <BT, CT> boolean isEmpty(
+ Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
+ return isSizeExactly(traversal, 0);
+ }
+
public static <BT, CT> boolean isSingleton(
Consumer<Function<CT, TraversalContinuation<BT, CT>>> traversal) {
return isSizeExactly(traversal, 1);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index d7a6cc2..7956f69 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.benchmarks.BenchmarkResults;
import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.testing.AndroidBuildVersion;
@@ -59,6 +58,7 @@
options.testing.reportUnusedProguardConfigurationRules = true;
options.horizontalClassMergerOptions().enable();
options.horizontalClassMergerOptions().setEnableInterfaceMerging();
+ options.getCfCodeAnalysisOptions().setAllowUnreachableCfBlocks(false);
options.getOpenClosedInterfacesOptions().disallowOpenInterfaces();
};