Leverage processInvokesNeverReturningNormally() for optimizing failing null checks
Bug: 150269949
Change-Id: I27473b258413d5a56e7454e5e622217453cf1155
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 363c6a1..50cec6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -87,10 +87,20 @@
}
@Override
+ public boolean hasInsertionPosition() {
+ return position != null;
+ }
+
+ @Override
public void setInsertionPosition(Position position) {
this.position = position;
}
+ @Override
+ public void unsetInsertionPosition() {
+ this.position = null;
+ }
+
/**
* Adds an instruction to the block. The instruction will be added just before the current cursor
* position.
@@ -212,13 +222,15 @@
IRCode code, InternalOptions options, long value, TypeElement type) {
ConstNumber constNumberInstruction = code.createNumberConstant(value, type);
// Note that we only keep position info for throwing instructions in release mode.
- Position position;
- if (options.debug) {
- position = current != null ? current.getPosition() : block.getPosition();
- } else {
- position = Position.none();
+ if (!hasInsertionPosition()) {
+ Position position;
+ if (options.debug) {
+ position = current != null ? current.getPosition() : block.getPosition();
+ } else {
+ position = Position.none();
+ }
+ constNumberInstruction.setPosition(position);
}
- constNumberInstruction.setPosition(position);
add(constNumberInstruction);
return constNumberInstruction.outValue();
}
@@ -308,8 +320,13 @@
new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS),
affectedValues));
- InstructionListIterator throwBlockInstructionIterator =
- throwBlock == block ? this : throwBlock.listIterator(code);
+ InstructionListIterator throwBlockInstructionIterator;
+ if (throwBlock == block) {
+ throwBlockInstructionIterator = this;
+ } else {
+ throwBlockInstructionIterator = throwBlock.listIterator(code);
+ throwBlockInstructionIterator.setInsertionPosition(position);
+ }
// Insert constant null before the goto instruction.
Value nullValue =
@@ -321,7 +338,9 @@
// Replace the instruction by throw.
Throw throwInstruction = new Throw(nullValue);
- if (toBeReplaced.getPosition().isSome()) {
+ if (hasInsertionPosition()) {
+ throwInstruction.setPosition(position);
+ } else if (toBeReplaced.getPosition().isSome()) {
throwInstruction.setPosition(toBeReplaced.getPosition());
} else {
// The instruction that is being removed cannot throw, and thus it must be unreachable as we
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 47ce330..bec911e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -152,11 +152,6 @@
}
@Override
- public void setInsertionPosition(Position position) {
- instructionIterator.setInsertionPosition(position);
- }
-
- @Override
public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues);
}
@@ -165,4 +160,19 @@
public void removeOrReplaceByDebugLocalRead() {
instructionIterator.removeOrReplaceByDebugLocalRead();
}
+
+ @Override
+ public boolean hasInsertionPosition() {
+ return instructionIterator.hasInsertionPosition();
+ }
+
+ @Override
+ public void setInsertionPosition(Position position) {
+ instructionIterator.setInsertionPosition(position);
+ }
+
+ @Override
+ public void unsetInsertionPosition() {
+ instructionIterator.unsetInsertionPosition();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
index 9077c97..faebe44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -16,6 +16,9 @@
Value object();
+ boolean instructionInstanceCanThrow(
+ AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
+
boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
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 e4e96af..ee7d57e 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
@@ -1461,17 +1461,70 @@
return false;
}
- public enum SideEffectAssumption {
- NONE,
- CLASS_ALREADY_INITIALIZED,
- RECEIVER_NOT_NULL;
+ public static class SideEffectAssumption {
- boolean canAssumeClassIsAlreadyInitialized() {
- return this == CLASS_ALREADY_INITIALIZED;
+ public static final SideEffectAssumption NONE = new SideEffectAssumption();
+
+ public static final SideEffectAssumption CLASS_ALREADY_INITIALIZED =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return true;
+ }
+ };
+
+ public static final SideEffectAssumption INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return true;
+ }
+ };
+
+ public static final SideEffectAssumption RECEIVER_NOT_NULL =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeReceiverIsNotNull() {
+ return true;
+ }
+ };
+
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return false;
}
- boolean canAssumeReceiverIsNotNull() {
- return this == RECEIVER_NOT_NULL;
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return false;
+ }
+
+ public boolean canAssumeReceiverIsNotNull() {
+ return false;
+ }
+
+ public SideEffectAssumption join(SideEffectAssumption other) {
+ return new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return SideEffectAssumption.this.canAssumeClassIsAlreadyInitialized()
+ || other.canAssumeClassIsAlreadyInitialized();
+ }
+
+ @Override
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+ || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+ }
+
+ @Override
+ public boolean canAssumeReceiverIsNotNull() {
+ return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+ || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+ }
+ };
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index d83aa64..2790e66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -58,10 +58,18 @@
*/
void removeOrReplaceByDebugLocalRead();
+ default boolean hasInsertionPosition() {
+ return false;
+ }
+
default void setInsertionPosition(Position position) {
// Intentionally empty.
}
+ default void unsetInsertionPosition() {
+ // Intentionally empty.
+ }
+
default Value insertConstNullInstruction(IRCode code, InternalOptions options) {
return insertConstNumberInstruction(code, options, 0, TypeElement.getNull());
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 877d359..79f7152 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -225,6 +225,10 @@
return true;
}
+ if (assumption.canAssumeInvokedMethodDoesNotHaveSideEffects()) {
+ return false;
+ }
+
DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod();
if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod())
|| appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) {
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 6634df6..4c127fa 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
@@ -69,7 +69,6 @@
import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
-import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
@@ -159,7 +158,6 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
private final StringSwitchRemover stringSwitchRemover;
- private final UninstantiatedTypeOptimization uninstantiatedTypeOptimization;
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
@@ -254,7 +252,6 @@
this.lensCodeRewriter = null;
this.identifierNameStringMarker = null;
this.devirtualizer = null;
- this.uninstantiatedTypeOptimization = null;
this.typeChecker = null;
this.d8NestBasedAccessDesugaring = null;
this.stringSwitchRemover = null;
@@ -320,10 +317,6 @@
}
this.devirtualizer =
options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
- this.uninstantiatedTypeOptimization =
- options.enableUninstantiatedTypeOptimization
- ? new UninstantiatedTypeOptimization(appViewWithLiveness)
- : null;
this.typeChecker = new TypeChecker(appViewWithLiveness);
this.d8NestBasedAccessDesugaring = null;
this.serviceLoaderRewriter =
@@ -351,7 +344,6 @@
this.lensCodeRewriter = null;
this.identifierNameStringMarker = null;
this.devirtualizer = null;
- this.uninstantiatedTypeOptimization = null;
this.typeChecker = null;
this.d8NestBasedAccessDesugaring =
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
@@ -1318,14 +1310,6 @@
assert code.verifyTypes(appView);
- if (uninstantiatedTypeOptimization != null) {
- timing.begin("Rewrite uninstantiated types");
- uninstantiatedTypeOptimization.rewrite(code);
- timing.end();
- }
-
- assert code.verifyTypes(appView);
-
timing.begin("Remove trivial type checks/casts");
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
timing.end();
@@ -1367,8 +1351,8 @@
timing.begin("Propogate sparse conditionals");
new SparseConditionalConstantPropagation(appView, code).run();
timing.end();
- timing.begin("Rewrite always throwing invokes");
- codeRewriter.processMethodsNeverReturningNormally(code);
+ timing.begin("Rewrite always throwing instructions");
+ codeRewriter.optimizeAlwaysThrowingInstructions(code);
timing.end();
timing.begin("Simplify control flow");
if (codeRewriter.simplifyControlFlow(code)) {
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 def219f..c6447c1 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
@@ -51,8 +51,10 @@
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceFieldInstruction;
import com.android.tools.r8.ir.code.InstanceOf;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InstructionOrPhi;
@@ -60,6 +62,7 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -78,7 +81,9 @@
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
@@ -115,6 +120,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
@@ -2809,69 +2815,94 @@
return changed;
}
- // Find all method invocations that never returns normally, split the block
- // after each such invoke instruction and follow it with a block throwing a
- // null value (which should result in NPE). Note that this throw is not
+ // Find all instructions that always throw, split the block after each such instruction and follow
+ // it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
- public void processMethodsNeverReturningNormally(IRCode code) {
+ public void optimizeAlwaysThrowingInstructions(IRCode code) {
if (!appView.appInfo().hasLiveness()) {
return;
}
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
+ ProgramMethod context = code.context();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
continue;
}
- InstructionListIterator insnIterator = block.listIterator(code);
- while (insnIterator.hasNext()) {
- Instruction insn = insnIterator.next();
- if (!insn.isInvokeMethod()) {
+ if (blocksToRemove.contains(block)) {
+ continue;
+ }
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (instruction.throwsOnNullInput()) {
+ Value inValue = instruction.getNonNullInput();
+ if (inValue.isAlwaysNull(appView)) {
+ // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE.
+ if (instruction.isInstanceFieldInstruction()) {
+ InstanceFieldInstruction instanceFieldInstruction =
+ instruction.asInstanceFieldInstruction();
+ if (instanceFieldInstruction.instructionInstanceCanThrow(
+ appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
+ instructionIterator.next();
+ }
+ } else if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+ SideEffectAssumption assumption =
+ SideEffectAssumption.RECEIVER_NOT_NULL.join(
+ SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS);
+ if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) {
+ instructionIterator.next();
+ }
+ }
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+ continue;
+ }
+ }
+
+ if (!instruction.isInvokeMethod()) {
continue;
}
- InvokeMethod invoke = insn.asInvokeMethod();
+ InvokeMethod invoke = instruction.asInvokeMethod();
DexEncodedMethod singleTarget =
invoke.lookupSingleTarget(appView.withLiveness(), code.context());
- if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
+ if (singleTarget == null) {
continue;
}
- // Split the block.
- {
- BasicBlock newBlock = insnIterator.split(code, blockIterator);
- assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo.
- // Move block iterator back so current block is 'newBlock'.
- blockIterator.previous();
+ MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- newBlock.unlinkSinglePredecessorSiblingsAllowed();
+ // If the invoke instruction is a null check, we can remove it.
+ boolean isNullCheck = false;
+ if (optimizationInfo.hasNonNullParamOrThrow()) {
+ BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow();
+ for (int i = 0; i < invoke.arguments().size(); i++) {
+ Value argument = invoke.arguments().get(i);
+ if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) {
+ isNullCheck = true;
+ break;
+ }
+ }
}
-
- // We want to follow the invoke instruction with 'throw null', which should
- // be unreachable but is needed to satisfy the verifier. Note that we have
- // to put 'throw null' into a separate block to make sure we don't get two
- // throwing instructions in the block having catch handler. This new block
- // does not need catch handlers.
- Instruction gotoInsn = insnIterator.previous();
- assert gotoInsn.isGoto();
- assert insnIterator.hasNext();
- BasicBlock throwNullBlock = insnIterator.split(code, blockIterator);
- InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator(code);
-
- // Insert 'null' constant.
- ConstNumber nullConstant = code.createConstNull(gotoInsn.getLocalInfo());
- nullConstant.setPosition(invoke.getPosition());
- throwNullInsnIterator.add(nullConstant);
-
- // Replace Goto with Throw.
- Throw notReachableThrow = new Throw(nullConstant.outValue());
- Instruction insnGoto = throwNullInsnIterator.next();
- assert insnGoto.isGoto();
- throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow);
+ // If the invoke instruction never returns normally, we can insert a throw null instruction
+ // after the invoke.
+ if (isNullCheck || optimizationInfo.neverReturnsNormally()) {
+ instructionIterator.setInsertionPosition(invoke.getPosition());
+ instructionIterator.next();
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+ instructionIterator.unsetInsertionPosition();
+ }
}
}
- Set<Value> affectedValues = code.removeUnreachableBlocks();
+ code.removeBlocks(blocksToRemove);
+ assert code.getUnreachableBlocks().isEmpty();
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index cb1eec7..6a8b6a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL;
import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -18,21 +17,11 @@
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -45,11 +34,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -356,95 +343,4 @@
return dexItemFactory.createMethod(method.holder, newProto, method.name);
}
-
- public void rewrite(IRCode code) {
- Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
- ListIterator<BasicBlock> blockIterator = code.listIterator();
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- if (blocksToBeRemoved.contains(block)) {
- continue;
- }
- InstructionListIterator instructionIterator = block.listIterator(code);
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- if (instruction.throwsOnNullInput()) {
- Value couldBeNullValue = instruction.getNonNullInput();
- if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.context())) {
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved, affectedValues);
- continue;
- }
- }
- if (instruction.isInvokeMethod()) {
- rewriteInvoke(
- instruction.asInvokeMethod(),
- blockIterator,
- instructionIterator,
- code,
- affectedValues,
- blocksToBeRemoved);
- }
- }
- }
- code.removeBlocks(blocksToBeRemoved);
- code.removeAllDeadAndTrivialPhis(affectedValues);
- code.removeUnreachableBlocks();
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- assert code.isConsistentSSA();
- }
-
- private static boolean isThrowNullCandidate(
- Value couldBeNullValue,
- Instruction current,
- AppView<? extends AppInfoWithClassHierarchy> appView,
- ProgramMethod context) {
- if (!couldBeNullValue.isAlwaysNull(appView)) {
- return false;
- }
- if (current.isFieldInstruction()) {
- // Other resolution-related errors come first.
- FieldInstruction fieldInstruction = current.asFieldInstruction();
- // We can't replace the current instruction with `throw null` if it may throw another
- // exception than NullPointerException.
- if (fieldInstruction.instructionInstanceCanThrow(
- appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
- return false;
- }
- }
- return true;
- }
-
- // invoke instructions with a null receiver has already been rewritten to `throw null`.
- // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
- // the invocation with `throw null` if an argument is known to be null and the method is going to
- // throw for that null argument.
- private void rewriteInvoke(
- InvokeMethod invoke,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructionIterator,
- IRCode code,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToBeRemoved) {
- DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context());
- if (target == null) {
- return;
- }
-
- BitSet facts = target.getOptimizationInfo().getNonNullParamOrThrow();
- if (facts != null) {
- for (int i = 0; i < invoke.arguments().size(); i++) {
- Value argument = invoke.arguments().get(i);
- if (argument.isAlwaysNull(appView) && facts.get(i)) {
- instructionIterator.next();
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved, affectedValues);
- return;
- }
- }
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 1b79eb8..f919a1e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -44,6 +44,10 @@
public abstract ParameterUsage getParameterUsages(int parameter);
+ public final boolean hasNonNullParamOrThrow() {
+ return getNonNullParamOrThrow() != null;
+ }
+
public abstract BitSet getNonNullParamOrThrow();
public abstract BitSet getNonNullParamOnNormalExits();
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 6326718..34fc2fc 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -513,9 +513,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non-R8/D8 running.
"156-register-dex-file-multi-loader",
"412-new-array",
@@ -525,9 +522,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non-R8/D8 running.
"004-checker-UnsafeTest18",
"005-annotations",
@@ -544,9 +538,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -567,9 +558,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -590,9 +578,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -821,6 +806,11 @@
.put("138-duplicate-classes-check", TestCondition.any())
// Array index out of bounds exception.
.put("150-loadlibrary", TestCondition.any())
+ // Fails due to non-matching Exception messages.
+ .put(
+ "201-built-in-except-detail-messages",
+ TestCondition.match(
+ TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8)))
// Uses dex file version 37 and therefore only runs on Android N and above.
.put(
"370-dex-v37",
@@ -1170,13 +1160,6 @@
"435-new-instance"
);
- private static List<String> requireUninstantiatedTypeOptimizationToBeDisabled = ImmutableList.of(
- // This test inspects the message of the exception that is thrown when calling a virtual
- // method with a null-receiver. This message changes when the invocation is rewritten to
- // "throw null".
- "201-built-in-except-detail-messages"
- );
-
private static List<String> hasMissingClasses = ImmutableList.of(
"091-override-package-private-method",
"003-omnibus-opcodes",
@@ -1279,8 +1262,6 @@
private final boolean disableInlining;
// Whether to disable class inlining
private final boolean disableClassInlining;
- // Whether to disable the uninitialized type optimization.
- private final boolean disableUninstantiatedTypeOptimization;
// Has missing classes.
private final boolean hasMissingClasses;
// Explicitly disable desugaring.
@@ -1304,7 +1285,6 @@
boolean outputMayDiffer,
boolean disableInlining,
boolean disableClassInlining,
- boolean disableUninstantiatedTypeOptimization,
boolean hasMissingClasses,
boolean disableDesugaring,
List<String> keepRules,
@@ -1323,7 +1303,6 @@
this.outputMayDiffer = outputMayDiffer;
this.disableInlining = disableInlining;
this.disableClassInlining = disableClassInlining;
- this.disableUninstantiatedTypeOptimization = disableUninstantiatedTypeOptimization;
this.hasMissingClasses = hasMissingClasses;
this.disableDesugaring = disableDesugaring;
this.keepRules = keepRules;
@@ -1354,7 +1333,6 @@
disableInlining,
true, // Disable class inlining for JCTF tests.
false,
- false,
true, // Disable desugaring for JCTF tests.
ImmutableList.of(),
null);
@@ -1383,7 +1361,6 @@
disableInlining,
true, // Disable class inlining for JCTF tests.
false,
- false,
true, // Disable desugaring for JCTF tests.
ImmutableList.of(),
null);
@@ -1548,7 +1525,6 @@
outputMayDiffer.contains(name),
requireInliningToBeDisabled.contains(name),
requireClassInliningToBeDisabled.contains(name),
- requireUninstantiatedTypeOptimizationToBeDisabled.contains(name),
hasMissingClasses.contains(name),
false,
keepRules.getOrDefault(name, ImmutableList.of()),
@@ -1619,7 +1595,6 @@
private final boolean disableInlining;
private final boolean disableClassInlining;
- private final boolean disableUninstantiatedTypeOptimization;
private final boolean hasMissingClasses;
private final boolean disableDesugaring;
private final List<String> keepRules;
@@ -1628,7 +1603,6 @@
private CompilationOptions(TestSpecification spec) {
this.disableInlining = spec.disableInlining;
this.disableClassInlining = spec.disableClassInlining;
- this.disableUninstantiatedTypeOptimization = spec.disableUninstantiatedTypeOptimization;
this.hasMissingClasses = spec.hasMissingClasses;
this.disableDesugaring = spec.disableDesugaring;
this.keepRules = spec.keepRules;
@@ -1643,9 +1617,6 @@
if (disableClassInlining) {
options.enableClassInlining = false;
}
- if (disableUninstantiatedTypeOptimization) {
- options.enableUninstantiatedTypeOptimization = false;
- }
// Some tests actually rely on missing classes for what they test.
options.ignoreMissingClasses = hasMissingClasses;
if (configuration != null) {
@@ -1664,7 +1635,6 @@
CompilationOptions options = (CompilationOptions) o;
return disableInlining == options.disableInlining
&& disableClassInlining == options.disableClassInlining
- && disableUninstantiatedTypeOptimization == options.disableUninstantiatedTypeOptimization
&& hasMissingClasses == options.hasMissingClasses;
}
@@ -1673,7 +1643,6 @@
return Objects.hash(
disableInlining,
disableClassInlining,
- disableUninstantiatedTypeOptimization,
hasMissingClasses);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
index a46524a..3ce1ee1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -166,7 +166,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -228,7 +228,7 @@
testForD8()
.release()
.addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, false);
@@ -244,10 +244,9 @@
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(MAIN)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, true);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index c5ca6c9..bfcda02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -50,6 +50,10 @@
boolean isConstNull();
+ default boolean isConstString() {
+ return isConstString(JumboStringMode.ALLOW);
+ }
+
boolean isConstString(JumboStringMode jumboStringMode);
boolean isConstString(String value, JumboStringMode jumboStringMode);