Remove constant parameters from throw block outlines
Bug: b/434769547
Change-Id: Id4073c07f0d04b994bb03d5bb369ac0250993169
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 12b0e3b..d9e1cfd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.lightir.LirConstant;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.IntObjPredicate;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -168,4 +170,19 @@
public void internalLirConstantAcceptHashing(HashingVisitor visitor) {
acceptHashing(visitor);
}
+
+ public DexProto withoutParameters(IntObjPredicate<DexType> predicate, DexItemFactory factory) {
+ if (parameters.isEmpty()) {
+ return this;
+ }
+ DexType[] newParameters =
+ ArrayUtils.map(
+ parameters.getBacking(),
+ (index, parameter) -> predicate.test(index, parameter) ? null : parameter,
+ DexType.EMPTY_ARRAY);
+ if (newParameters == parameters.getBacking()) {
+ return this;
+ }
+ return factory.createProto(returnType, newParameters);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
index 6b4b88f..8556a9f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.outliner.exceptions.ThrowBlockOutline;
import com.android.tools.r8.lightir.LirBuilder;
+import com.android.tools.r8.utils.ListUtils;
import java.util.List;
public class ThrowBlockOutlineMarker extends Instruction {
@@ -29,6 +30,28 @@
return new Builder();
}
+ // Removes the in-values from this outline marker where the corresponding outline parameter has
+ // been removed due to constant propagation.
+ public boolean detachConstantOutlineArguments(ThrowBlockOutline outline) {
+ List<Value> newArguments =
+ ListUtils.mapOrElse(
+ inValues,
+ (i, argument) -> {
+ if (outline.isArgumentConstant(i)) {
+ argument.removeUser(this);
+ return null;
+ }
+ return argument;
+ },
+ null);
+ if (newArguments != null) {
+ inValues.clear();
+ inValues.addAll(newArguments);
+ return true;
+ }
+ return false;
+ }
+
public ThrowBlockOutline getOutline() {
return outline;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java
new file mode 100644
index 0000000..9dff1d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2025, 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.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.proto.ArgumentInfo;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.ir.code.UnusedArgument;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.ArrayUtils;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class LensCodeArgumentRewriter {
+
+ private final AppView<?> appView;
+
+ public LensCodeArgumentRewriter(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ // Applies the prototype changes of the current method to the argument instructions:
+ // - Replaces constant arguments by their constant value and then removes the (now unused)
+ // argument instruction
+ // - Removes unused arguments
+ // - Updates the type of arguments whose type has been strengthened
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ public void rewriteArguments(
+ IRCode code,
+ DexMethod originalMethodReference,
+ RewrittenPrototypeDescription prototypeChanges,
+ Set<Phi> affectedPhis,
+ Set<UnusedArgument> unusedArguments) {
+ AffectedValues affectedValues = new AffectedValues();
+ ArgumentInfoCollection argumentInfoCollection = prototypeChanges.getArgumentInfoCollection();
+ List<Instruction> argumentPostlude = new LinkedList<>();
+ int oldArgumentIndex = 0;
+ int nextArgumentIndex = 0;
+ int numberOfRemovedArguments = 0;
+ BasicBlock basicBlock = code.entryBlock();
+ InstructionListIterator instructionIterator = basicBlock.listIterator();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!instruction.isArgument()) {
+ break;
+ }
+
+ Argument argument = instruction.asArgument();
+ ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(oldArgumentIndex);
+ if (argumentInfo.isRemovedArgumentInfo()) {
+ rewriteRemovedArgument(
+ code,
+ instructionIterator,
+ originalMethodReference,
+ argument,
+ argumentInfo.asRemovedArgumentInfo(),
+ affectedPhis,
+ affectedValues,
+ argumentPostlude,
+ unusedArguments);
+ numberOfRemovedArguments++;
+ } else {
+ int newArgumentIndex =
+ argumentInfoCollection.getNewArgumentIndex(oldArgumentIndex, numberOfRemovedArguments);
+ Argument replacement;
+ if (argumentInfo.isRewrittenTypeInfo()) {
+ replacement =
+ rewriteArgumentType(
+ code,
+ argument,
+ argumentInfo.asRewrittenTypeInfo(),
+ affectedPhis,
+ newArgumentIndex);
+ argument.outValue().replaceUsers(replacement.outValue());
+ } else if (newArgumentIndex != oldArgumentIndex) {
+ replacement =
+ Argument.builder()
+ .setIndex(newArgumentIndex)
+ .setFreshOutValue(code, argument.getOutType(), argument.getLocalInfo())
+ .setPosition(argument.getPosition())
+ .build();
+ argument.outValue().replaceUsers(replacement.outValue());
+ } else {
+ replacement = argument;
+ }
+ if (newArgumentIndex == nextArgumentIndex) {
+ // This is the right position for the argument. Insert it into the code at this position.
+ if (replacement != argument) {
+ instructionIterator.replaceCurrentInstruction(replacement);
+ }
+ nextArgumentIndex++;
+ } else {
+ // Due the a permutation of the argument order, this argument needs to be inserted at a
+ // later point. Enqueue the argument into the argument postlude.
+ instructionIterator.removeInstructionIgnoreOutValue();
+ ListIterator<Instruction> argumentPostludeIterator = argumentPostlude.listIterator();
+ while (argumentPostludeIterator.hasNext()) {
+ Instruction current = argumentPostludeIterator.next();
+ if (!current.isArgument()
+ || replacement.getIndexRaw() < current.asArgument().getIndexRaw()) {
+ argumentPostludeIterator.previous();
+ break;
+ }
+ }
+ argumentPostludeIterator.add(replacement);
+ }
+ }
+ oldArgumentIndex++;
+ }
+
+ instructionIterator.previous();
+
+ if (!argumentPostlude.isEmpty()) {
+ for (Instruction instruction : argumentPostlude) {
+ instructionIterator.add(instruction);
+ }
+ }
+
+ affectedValues.narrowingWithAssumeRemoval(appView, code);
+ }
+
+ private Argument rewriteArgumentType(
+ IRCode code,
+ Argument argument,
+ RewrittenTypeInfo rewrittenTypeInfo,
+ Set<Phi> affectedPhis,
+ int newArgumentIndex) {
+ TypeElement rewrittenType = rewrittenTypeInfo.getNewType().toTypeElement(appView);
+ Argument replacement =
+ Argument.builder()
+ .setIndex(newArgumentIndex)
+ .setFreshOutValue(code, rewrittenType, argument.getLocalInfo())
+ .setPosition(argument.getPosition())
+ .build();
+ affectedPhis.addAll(argument.outValue().uniquePhiUsers());
+ return replacement;
+ }
+
+ private void rewriteRemovedArgument(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ DexMethod originalMethodReference,
+ Argument argument,
+ RemovedArgumentInfo removedArgumentInfo,
+ Set<Phi> affectedPhis,
+ AffectedValues affectedValues,
+ List<Instruction> argumentPostlude,
+ Set<UnusedArgument> unusedArguments) {
+ Instruction[] replacement;
+ if (removedArgumentInfo.hasSingleValue()) {
+ SingleValue singleValue = removedArgumentInfo.getSingleValue();
+ TypeElement type =
+ removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
+ ? TypeElement.getNull()
+ : removedArgumentInfo.getType().toTypeElement(appView);
+ Position position =
+ SourcePosition.builder().setLine(0).setMethod(originalMethodReference).build();
+ replacement =
+ singleValue.createMaterializingInstructions(
+ appView,
+ code,
+ MaterializingInstructionsInfo.create(type, argument.getLocalInfo(), position));
+ } else {
+ TypeElement unusedArgumentType = removedArgumentInfo.getType().toTypeElement(appView);
+ UnusedArgument unusedArgument =
+ UnusedArgument.builder()
+ .setFreshOutValue(code, unusedArgumentType)
+ .setPosition(Position.none())
+ .build();
+ unusedArguments.add(unusedArgument);
+ replacement = new Instruction[] {unusedArgument};
+ }
+ Value replacementValue = ArrayUtils.last(replacement).outValue();
+ argument.outValue().replaceUsers(replacementValue, affectedValues);
+ affectedPhis.addAll(replacementValue.uniquePhiUsers());
+ Collections.addAll(argumentPostlude, replacement);
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 07bef0b..aef1339 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -56,7 +56,6 @@
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.graph.proto.ArgumentInfo;
import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
-import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -64,8 +63,6 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.SingleConstValue;
-import com.android.tools.r8.ir.analysis.value.SingleValue;
-import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -99,7 +96,6 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.StaticGet;
@@ -121,13 +117,10 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.LinkedList;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
@@ -163,10 +156,13 @@
private final DexItemFactory factory;
private final InternalOptions options;
+ private final LensCodeArgumentRewriter argumentRewriter;
+
public LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.options = appView.options();
+ this.argumentRewriter = new LensCodeArgumentRewriter(appView);
}
private Value makeOutValue(
@@ -247,7 +243,7 @@
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
AffectedValues affectedValues = new AffectedValues();
Set<UnusedArgument> unusedArguments = Sets.newIdentityHashSet();
- rewriteArguments(
+ argumentRewriter.rewriteArguments(
code, originalMethodReference, prototypeChanges, affectedPhis, unusedArguments);
if (graphLens.hasCustomLensCodeRewriter()) {
assert graphLens.getPrevious() == codeLens
@@ -921,147 +917,6 @@
assert code.isConsistentSSABeforeTypesAreCorrect(appView);
}
- // Applies the prototype changes of the current method to the argument instructions:
- // - Replaces constant arguments by their constant value and then removes the (now unused)
- // argument instruction
- // - Removes unused arguments
- // - Updates the type of arguments whose type has been strengthened
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- private void rewriteArguments(
- IRCode code,
- DexMethod originalMethodReference,
- RewrittenPrototypeDescription prototypeChanges,
- Set<Phi> affectedPhis,
- Set<UnusedArgument> unusedArguments) {
- AffectedValues affectedValues = new AffectedValues();
- ArgumentInfoCollection argumentInfoCollection = prototypeChanges.getArgumentInfoCollection();
- List<Instruction> argumentPostlude = new LinkedList<>();
- int oldArgumentIndex = 0;
- int nextArgumentIndex = 0;
- int numberOfRemovedArguments = 0;
- BasicBlock basicBlock = code.entryBlock();
- InstructionListIterator instructionIterator = basicBlock.listIterator();
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- if (!instruction.isArgument()) {
- break;
- }
-
- Argument argument = instruction.asArgument();
- ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(oldArgumentIndex);
- if (argumentInfo.isRemovedArgumentInfo()) {
- rewriteRemovedArgument(
- code,
- instructionIterator,
- originalMethodReference,
- argument,
- argumentInfo.asRemovedArgumentInfo(),
- affectedPhis,
- affectedValues,
- argumentPostlude,
- unusedArguments);
- numberOfRemovedArguments++;
- } else {
- int newArgumentIndex =
- argumentInfoCollection.getNewArgumentIndex(oldArgumentIndex, numberOfRemovedArguments);
- Argument replacement;
- if (argumentInfo.isRewrittenTypeInfo()) {
- replacement =
- rewriteArgumentType(
- code,
- argument,
- argumentInfo.asRewrittenTypeInfo(),
- affectedPhis,
- newArgumentIndex);
- argument.outValue().replaceUsers(replacement.outValue());
- } else if (newArgumentIndex != oldArgumentIndex) {
- replacement =
- Argument.builder()
- .setIndex(newArgumentIndex)
- .setFreshOutValue(code, argument.getOutType(), argument.getLocalInfo())
- .setPosition(argument.getPosition())
- .build();
- argument.outValue().replaceUsers(replacement.outValue());
- } else {
- replacement = argument;
- }
- if (newArgumentIndex == nextArgumentIndex) {
- // This is the right position for the argument. Insert it into the code at this position.
- if (replacement != argument) {
- instructionIterator.replaceCurrentInstruction(replacement);
- }
- nextArgumentIndex++;
- } else {
- // Due the a permutation of the argument order, this argument needs to be inserted at a
- // later point. Enqueue the argument into the argument postlude.
- instructionIterator.removeInstructionIgnoreOutValue();
- ListIterator<Instruction> argumentPostludeIterator = argumentPostlude.listIterator();
- while (argumentPostludeIterator.hasNext()) {
- Instruction current = argumentPostludeIterator.next();
- if (!current.isArgument()
- || replacement.getIndexRaw() < current.asArgument().getIndexRaw()) {
- argumentPostludeIterator.previous();
- break;
- }
- }
- argumentPostludeIterator.add(replacement);
- }
- }
- oldArgumentIndex++;
- }
-
- instructionIterator.previous();
-
- if (!argumentPostlude.isEmpty()) {
- for (Instruction instruction : argumentPostlude) {
- instructionIterator.add(instruction);
- }
- }
-
- affectedValues.narrowingWithAssumeRemoval(appView, code);
- }
-
- private void rewriteRemovedArgument(
- IRCode code,
- InstructionListIterator instructionIterator,
- DexMethod originalMethodReference,
- Argument argument,
- RemovedArgumentInfo removedArgumentInfo,
- Set<Phi> affectedPhis,
- AffectedValues affectedValues,
- List<Instruction> argumentPostlude,
- Set<UnusedArgument> unusedArguments) {
- Instruction[] replacement;
- if (removedArgumentInfo.hasSingleValue()) {
- SingleValue singleValue = removedArgumentInfo.getSingleValue();
- TypeElement type =
- removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
- ? TypeElement.getNull()
- : removedArgumentInfo.getType().toTypeElement(appView);
- Position position =
- SourcePosition.builder().setLine(0).setMethod(originalMethodReference).build();
- replacement =
- singleValue.createMaterializingInstructions(
- appView,
- code,
- MaterializingInstructionsInfo.create(type, argument.getLocalInfo(), position));
- } else {
- TypeElement unusedArgumentType = removedArgumentInfo.getType().toTypeElement(appView);
- UnusedArgument unusedArgument =
- UnusedArgument.builder()
- .setFreshOutValue(code, unusedArgumentType)
- .setPosition(Position.none())
- .build();
- unusedArguments.add(unusedArgument);
- replacement = new Instruction[] {unusedArgument};
- }
- Value replacementValue = ArrayUtils.last(replacement).outValue();
- argument.outValue().replaceUsers(replacementValue, affectedValues);
- affectedPhis.addAll(replacementValue.uniquePhiUsers());
- Collections.addAll(argumentPostlude, replacement);
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
private void insertReadCast(
IRCode code,
@@ -1088,23 +943,6 @@
affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
}
- private Argument rewriteArgumentType(
- IRCode code,
- Argument argument,
- RewrittenTypeInfo rewrittenTypeInfo,
- Set<Phi> affectedPhis,
- int newArgumentIndex) {
- TypeElement rewrittenType = rewrittenTypeInfo.getNewType().toTypeElement(appView);
- Argument replacement =
- Argument.builder()
- .setIndex(newArgumentIndex)
- .setFreshOutValue(code, rewrittenType, argument.getLocalInfo())
- .setPosition(argument.getPosition())
- .build();
- affectedPhis.addAll(argument.outValue().uniquePhiUsers());
- return replacement;
- }
-
private void removeUnusedArguments(IRCode code, Set<UnusedArgument> unusedArguments) {
AffectedValues affectedValues = new AffectedValues();
for (UnusedArgument unusedArgument : unusedArguments) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
index 480f608..fc99554 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
@@ -6,10 +6,19 @@
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+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.AbstractValueFactory;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.lightir.LirConstant;
@@ -17,28 +26,69 @@
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
import com.google.common.collect.ConcurrentHashMultiset;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Multiset;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class ThrowBlockOutline implements LirConstant {
- @SuppressWarnings("UnusedVariable")
+ private final List<AbstractValue> arguments;
private final LirCode<?> lirCode;
-
private final DexProto proto;
private final Multiset<DexMethod> users = ConcurrentHashMultiset.create();
private ProgramMethod materializedOutlineMethod;
ThrowBlockOutline(LirCode<?> lirCode, DexProto proto) {
+ this.arguments = proto.getArity() == 0 ? Collections.emptyList() : new ArrayList<>();
this.lirCode = lirCode;
this.proto = proto;
}
- public void addUser(DexMethod user, List<Value> unusedArguments) {
+ public void addUser(
+ DexMethod user, List<Value> userArguments, AbstractValueFactory valueFactory) {
+ assert userArguments.size() == proto.getArity();
users.add(user);
- // TODO(TODO(b/434769547)): Compute abstraction of arguments to enable interprocedural constant
- // propagation.
+ if (!userArguments.isEmpty()) {
+ if (arguments.isEmpty()) {
+ for (Value userArgument : userArguments) {
+ arguments.add(encodeArgumentValue(userArgument, valueFactory));
+ }
+ } else {
+ for (int i = 0; i < userArguments.size(); i++) {
+ AbstractValue existingArgument = arguments.get(i);
+ if (existingArgument.isUnknown()) {
+ continue;
+ }
+ Value userArgument = userArguments.get(i);
+ if (!existingArgument.equals(encodeArgumentValue(userArgument, valueFactory))) {
+ arguments.set(i, AbstractValue.unknown());
+ }
+ }
+ }
+ }
+ }
+
+ private AbstractValue encodeArgumentValue(Value value, AbstractValueFactory valueFactory) {
+ if (value.isConstNumber()) {
+ ConstNumber constNumber = value.getDefinition().asConstNumber();
+ TypeElement type = value.getType();
+ if (type.isReferenceType()) {
+ return valueFactory.createNullValue(type);
+ } else {
+ return valueFactory.createSingleNumberValue(constNumber.getRawValue(), type);
+ }
+ } else if (value.isConstString()) {
+ ConstString constString = value.getDefinition().asConstString();
+ return valueFactory.createSingleStringValue(constString.getValue());
+ }
+ return AbstractValue.unknown();
+ }
+
+ public List<AbstractValue> getArguments() {
+ return arguments;
}
@Override
@@ -58,6 +108,27 @@
return proto;
}
+ public RewrittenPrototypeDescription getProtoChanges() {
+ assert hasConstantArgument();
+ ArgumentInfoCollection.Builder argumentsInfoBuilder =
+ ArgumentInfoCollection.builder().setArgumentInfosSize(proto.getArity());
+ for (int i = 0; i < arguments.size(); i++) {
+ if (isArgumentConstant(i)) {
+ RemovedArgumentInfo removedArgumentInfo =
+ RemovedArgumentInfo.builder()
+ .setSingleValue(arguments.get(i).asSingleValue())
+ .setType(proto.getParameter(i))
+ .build();
+ argumentsInfoBuilder.addArgumentInfo(i, removedArgumentInfo);
+ }
+ }
+ return RewrittenPrototypeDescription.createForArgumentsInfo(argumentsInfoBuilder.build());
+ }
+
+ public DexProto getOptimizedProto(DexItemFactory factory) {
+ return proto.withoutParameters((i, p) -> isArgumentConstant(i), factory);
+ }
+
public ProgramMethod getSynthesizingContext(AppView<?> appView) {
DexMethod shortestUser = null;
for (DexMethod user : users) {
@@ -81,6 +152,10 @@
return users;
}
+ public boolean hasConstantArgument() {
+ return Iterables.any(arguments, argument -> !argument.isUnknown());
+ }
+
@Override
public int internalLirConstantAcceptCompareTo(LirConstant other, CompareToVisitor visitor) {
throw new Unreachable();
@@ -91,6 +166,10 @@
throw new Unreachable();
}
+ public boolean isArgumentConstant(int index) {
+ return !arguments.get(index).isUnknown();
+ }
+
public boolean isMaterialized() {
return materializedOutlineMethod != null;
}
@@ -106,6 +185,6 @@
builder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(methodSig -> lirCode)
- .setProto(proto));
+ .setProto(getOptimizedProto(appView.dexItemFactory())));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
index 00ba07a..d375841 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
@@ -13,16 +13,27 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.ThrowBlockOutlineMarker;
+import com.android.tools.r8.ir.code.UnusedArgument;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRFinalizer;
+import com.android.tools.r8.ir.conversion.LensCodeArgumentRewriter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer;
+import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.lightir.Lir2IRConverter;
import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.timing.Timing;
+import java.util.Collections;
+import java.util.Set;
/** Rewriter that processes {@link ThrowBlockOutlineMarker} instructions. */
public class ThrowBlockOutlineMarkerRewriter {
@@ -35,16 +46,20 @@
this.deadCodeRemover = new DeadCodeRemover(appView);
}
- public void processMethod(ProgramMethod method) {
+ public void processMethod(ProgramMethod method, ThrowBlockOutline outline) {
assert method.getDefinition().hasCode();
assert method.getDefinition().getCode().isLirCode();
// Build IR.
- LirCode<?> lirCode = method.getDefinition().getCode().asLirCode();
- IRCode code = lirCode.buildIR(method, appView);
+ LirCode<Integer> lirCode = method.getDefinition().getCode().asLirCode();
+ IRCode code = buildIR(lirCode, method, outline);
assert code.getConversionOptions().isGeneratingDex();
// Process IR.
- processOutlineMarkers(code);
+ if (outline != null) {
+ removeConstantArgumentsFromOutline(method, code, outline);
+ } else {
+ processOutlineMarkers(code);
+ }
// Convert to DEX.
IRFinalizer<?> finalizer = code.getConversionOptions().getFinalizer(deadCodeRemover, appView);
@@ -52,7 +67,39 @@
method.setCode(dexCode, appView);
}
+ private IRCode buildIR(
+ LirCode<Integer> lirCode, ProgramMethod method, ThrowBlockOutline outline) {
+ if (outline == null || !outline.hasConstantArgument()) {
+ return lirCode.buildIR(method, appView);
+ }
+ // We need to inform IR construction to insert the arguments that have been removed from the
+ // proto.
+ Position callerPosition = null;
+ return Lir2IRConverter.translate(
+ method,
+ lirCode,
+ LirStrategy.getDefaultStrategy().getDecodingStrategy(lirCode, new NumberGenerator()),
+ appView,
+ callerPosition,
+ outline.getProtoChanges(),
+ MethodConversionOptions.forD8(appView, method));
+ }
+
+ private void removeConstantArgumentsFromOutline(
+ ProgramMethod method, IRCode code, ThrowBlockOutline outline) {
+ Set<Phi> affectedPhis = Collections.emptySet();
+ Set<UnusedArgument> unusedArguments = Collections.emptySet();
+ new LensCodeArgumentRewriter(appView)
+ .rewriteArguments(
+ code, method.getReference(), outline.getProtoChanges(), affectedPhis, unusedArguments);
+
+ // Run shorten live ranges to push materialized constants to their uses.
+ ConstantCanonicalizer constantCanonicalizer = new ConstantCanonicalizer(appView, method, code);
+ new DexConstantOptimizer(appView, constantCanonicalizer).run(code, Timing.empty());
+ }
+
private void processOutlineMarkers(IRCode code) {
+ boolean needsDeadCodeElimination = false;
for (BasicBlock block : code.getBlocks()) {
Throw throwInstruction = block.exit().asThrow();
if (throwInstruction != null) {
@@ -60,6 +107,11 @@
block.entry().nextUntilInclusive(Instruction::isThrowBlockOutlineMarker);
if (outlineMarker != null) {
ThrowBlockOutline outline = outlineMarker.getOutline();
+ if (outlineMarker.detachConstantOutlineArguments(outline)) {
+ // Make sure to run dead code elimination when arguments are detached, since detached
+ // values may become dead.
+ needsDeadCodeElimination = true;
+ }
if (outline.isMaterialized()) {
// Insert a call to the materialized outline method and load the return value.
BasicBlockInstructionListIterator instructionIterator =
@@ -96,6 +148,10 @@
}
assert block.streamInstructions().noneMatch(Instruction::isThrowBlockOutlineMarker);
}
+
+ if (needsDeadCodeElimination) {
+ new DeadCodeRemover(appView).run(code, Timing.empty());
+ }
}
private Value addReturnValue(IRCode code, BasicBlockInstructionListIterator instructionIterator) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
index a131719..d10d0e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -124,30 +123,31 @@
private void processMethods(
Collection<ThrowBlockOutline> outlines, ExecutorService executorService)
throws ExecutionException {
- ProgramMethodSet methodsToProcess = getMethodsToReprocess(outlines);
+ ProgramMethodMap<ThrowBlockOutline> methodsToReprocess = getMethodsToReprocess(outlines);
ThrowBlockOutlineMarkerRewriter rewriter = new ThrowBlockOutlineMarkerRewriter(appView);
- ThreadUtils.processItems(
- methodsToProcess,
+ ThreadUtils.processMap(
+ methodsToReprocess,
rewriter::processMethod,
appView.options().getThreadingModule(),
executorService);
}
- private ProgramMethodSet getMethodsToReprocess(Collection<ThrowBlockOutline> outlines) {
- ProgramMethodSet methodsToProcess = ProgramMethodSet.create();
+ private ProgramMethodMap<ThrowBlockOutline> getMethodsToReprocess(
+ Collection<ThrowBlockOutline> outlines) {
+ ProgramMethodMap<ThrowBlockOutline> methodsToReprocess = ProgramMethodMap.create();
Set<DexMethod> seenUsers = Sets.newIdentityHashSet();
for (ThrowBlockOutline outline : outlines) {
for (DexMethod user : outline.getUsers()) {
if (seenUsers.add(user)) {
- ProgramMethod methodToProcess = appView.definitionFor(user).asProgramMethod();
- methodsToProcess.add(methodToProcess);
+ ProgramMethod methodToReprocess = appView.definitionFor(user).asProgramMethod();
+ methodsToReprocess.put(methodToReprocess, null);
}
}
if (outline.getMaterializedOutlineMethod() != null) {
- methodsToProcess.add(outline.getMaterializedOutlineMethod());
+ methodsToReprocess.put(outline.getMaterializedOutlineMethod(), outline);
}
}
- return methodsToProcess;
+ return methodsToReprocess;
}
private boolean supplyOutlineConsumerForTesting(Collection<ThrowBlockOutline> outlines) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
index 980668a..1dab9c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexTypeUtils;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator;
@@ -57,6 +58,7 @@
private final AppView<?> appView;
private final DexItemFactory factory;
+ private final AbstractValueFactory valueFactory = new AbstractValueFactory();
private final Map<Wrapper<LirCode<?>>, ThrowBlockOutline> outlines = new ConcurrentHashMap<>();
@@ -69,6 +71,12 @@
new ThrowBlockOutlinerScannerForCode(code).run();
}
+ public AbstractValueFactory getAbstractValueFactory() {
+ // If/when extending this to R8, use the R8 AbstractValueFactory from AppView.
+ assert !appView.enableWholeProgramOptimizations();
+ return valueFactory;
+ }
+
public Collection<ThrowBlockOutline> getOutlines() {
return outlines.values();
}
@@ -126,7 +134,7 @@
// TODO(b/434769547): This may not hold. We should compute the join.
assert proto.isIdenticalTo(outline.getProto());
List<Value> arguments = outlineBuilder.buildArguments();
- outline.addUser(code.reference(), arguments);
+ outline.addUser(code.reference(), arguments, getAbstractValueFactory());
// Insert a synthetic marker instruction that references the outline so that we know
// where to materialize the outline call.
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index d0bd1a9..8dea431 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -125,12 +125,16 @@
* @param emptyArray an empty array
* @return an array with written elements
*/
- @SuppressWarnings("unchecked")
public static <S, T> T[] map(S[] original, Function<S, T> mapper, T[] emptyArray) {
+ return map(original, (i, s) -> mapper.apply(s), emptyArray);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <S, T> T[] map(S[] original, IntObjToObjFunction<S, T> mapper, T[] emptyArray) {
ArrayList<T> results = null;
for (int i = 0; i < original.length; i++) {
S oldOne = original[i];
- T newOne = mapper.apply(oldOne);
+ T newOne = mapper.apply(i, oldOne);
if (newOne == oldOne) {
if (results != null) {
results.add((T) oldOne);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
index 2e130f7..91908b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
@@ -15,8 +15,10 @@
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -74,7 +76,12 @@
assertEquals(1, outlines.size());
ThrowBlockOutline outline = outlines.iterator().next();
assertEquals(2, outline.getNumberOfUsers());
- assertEquals(4, outline.getProto().getArity());
+ assertEquals(5, outline.getProto().getArity());
+
+ // Verify that the last argument is known to be constant.
+ AbstractValue lastArgument = ListUtils.last(outline.getArguments());
+ assertTrue(lastArgument.isSingleStringValue());
+ assertEquals(", k=42", lastArgument.asSingleStringValue().getDexString().toString());
}
private void inspectOutput(CodeInspector inspector) {
@@ -85,12 +92,14 @@
assertThat(outlineClassSubject, isPresent());
assertEquals(1, outlineClassSubject.allMethods().size());
- // Validate that the outline uses StringBuilder.
+ // Validate that the outline uses StringBuilder and that the string ", k=42" has been moved into
+ // the outline.
MethodSubject outlineMethodSubject = outlineClassSubject.uniqueMethod();
assertTrue(
outlineMethodSubject
.streamInstructions()
.anyMatch(i -> i.isNewInstance("java.lang.StringBuilder")));
+ assertTrue(outlineMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", k=42")));
// Validate that main() no longer uses StringBuilder and that it calls the outline twice.
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
@@ -103,6 +112,7 @@
assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString("j=")));
assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", j=")));
assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", i=")));
+ assertTrue(mainMethodSubject.streamInstructions().noneMatch(i -> i.isConstString(", k=42")));
assertEquals(
2,
mainMethodSubject
@@ -117,10 +127,10 @@
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
if (i == 0) {
- throw new IllegalArgumentException("i=" + i + ", j=" + j);
+ throw new IllegalArgumentException("i=" + i + ", j=" + j + ", k=42");
}
if (j == 0) {
- throw new IllegalArgumentException("j=" + j + ", i=" + i);
+ throw new IllegalArgumentException("j=" + j + ", i=" + i + ", k=42");
}
}
}