Implement the number unboxer optimization
- NumberUnboxerTreeFixer and NumberUnboxerRewriter
Bug: b/307872552
Change-Id: Id6c3a3b1035cd071ff7da80d0305330af19f4e97
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index 64f8399..eff9aa9 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -414,6 +414,10 @@
return null;
}
+ public boolean isNumberUnboxerLens() {
+ return false;
+ }
+
public boolean isHorizontalClassMergerGraphLens() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
index 84599f0..e64f101 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
@@ -21,8 +21,10 @@
AppView<?> appView,
BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
- BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap) {
+ BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap,
+ CustomLensCodeRewriter customLensCodeRewriter) {
super(appView, fieldMap, methodMap, typeMap);
+ this.customLensCodeRewriter = customLensCodeRewriter;
}
public NestedGraphLensWithCustomLensCodeRewriter(
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
index 23df11c..3f628b9 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
@@ -33,7 +33,7 @@
private static final RewrittenPrototypeDescription NONE = new RewrittenPrototypeDescription();
- private final List<ExtraParameter> extraParameters;
+ private final List<? extends ExtraParameter> extraParameters;
private final ArgumentInfoCollection argumentInfoCollection;
private final RewrittenTypeInfo rewrittenReturnInfo;
@@ -44,7 +44,7 @@
}
private RewrittenPrototypeDescription(
- List<ExtraParameter> extraParameters,
+ List<? extends ExtraParameter> extraParameters,
RewrittenTypeInfo rewrittenReturnInfo,
ArgumentInfoCollection argumentsInfo) {
assert argumentsInfo != null;
@@ -55,7 +55,7 @@
}
public static RewrittenPrototypeDescription create(
- List<ExtraParameter> extraParameters,
+ List<? extends ExtraParameter> extraParameters,
RewrittenTypeInfo rewrittenReturnInfo,
ArgumentInfoCollection argumentsInfo) {
return extraParameters.isEmpty() && rewrittenReturnInfo == null && argumentsInfo.isEmpty()
@@ -116,7 +116,7 @@
return !extraParameters.isEmpty();
}
- public List<ExtraParameter> getExtraParameters() {
+ public List<? extends ExtraParameter> getExtraParameters() {
return extraParameters;
}
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 3588346..0f6fb1f 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
@@ -87,6 +87,13 @@
Instruction previous = previous();
assert previous == instruction;
}
+
+ default void addBeforeAndPositionAfterNewInstruction(Instruction instruction) {
+ previous();
+ add(instruction);
+ next();
+ }
+
/** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
default void replaceCurrentInstruction(Instruction newInstruction) {
replaceCurrentInstruction(newInstruction, null);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
index dbfb37a..b360857 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
@@ -34,7 +34,8 @@
public abstract void unboxNumbers(
PostMethodProcessor.Builder postMethodProcessorBuilder,
Timing timing,
- ExecutorService executorService);
+ ExecutorService executorService)
+ throws ExecutionException;
public abstract void onMethodPruned(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
index 2f0a128..4a9cbfd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
@@ -7,12 +7,12 @@
import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.NO_UNBOX;
import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.TO_PROCESS;
import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.UNBOX;
-import static com.android.tools.r8.utils.ListUtils.*;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult;
import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodArg;
import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodRet;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.WorkList;
import java.util.Arrays;
@@ -72,6 +72,20 @@
public BoxingStatusResult[] getArgs() {
return args;
}
+
+ public boolean isNoneUnboxable() {
+ return ret == NO_UNBOX && ArrayUtils.all(args, NO_UNBOX);
+ }
+
+ public boolean shouldUnboxArg(int i) {
+ assert args[i] != TO_PROCESS;
+ return args[i] == UNBOX;
+ }
+
+ public boolean shouldUnboxRet() {
+ assert ret != TO_PROCESS;
+ return ret == UNBOX;
+ }
}
void markNoneUnboxable(DexMethod method) {
@@ -135,9 +149,14 @@
}
}
assert allProcessed();
+ clearNoneUnboxable();
return boxingStatusResultMap;
}
+ private void clearNoneUnboxable() {
+ boxingStatusResultMap.values().removeIf(MethodBoxingStatusResult::isNoneUnboxable);
+ }
+
private boolean allProcessed() {
boxingStatusResultMap.forEach(
(k, v) -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
index cf8f2ec..c6b0d70 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
@@ -40,6 +40,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
public class NumberUnboxerImpl extends NumberUnboxer {
@@ -301,17 +303,26 @@
public void unboxNumbers(
PostMethodProcessor.Builder postMethodProcessorBuilder,
Timing timing,
- ExecutorService executorService) {
-
- Map<DexMethod, MethodBoxingStatusResult> result =
+ ExecutorService executorService)
+ throws ExecutionException {
+ Map<DexMethod, MethodBoxingStatusResult> unboxingResult =
new NumberUnboxerBoxingStatusResolution().resolve(methodBoxingStatus);
+ if (unboxingResult.isEmpty()) {
+ return;
+ }
+
+ NumberUnboxerLens numberUnboxerLens =
+ new NumberUnboxerTreeFixer(appView, unboxingResult).fixupTree(executorService, timing);
+ appView.rewriteWithLens(numberUnboxerLens, executorService, timing);
+
+ enqueueMethodsForReprocessing(postMethodProcessorBuilder);
// TODO(b/307872552): The result encodes for each method which return value and parameter of
// each method should be unboxed. We need here to implement the treefixer using it, and set up
// correctly the reprocessing with a code rewriter similar to the enum unboxing code rewriter.
// We should implement the optimization, so far, we just print out the result.
StringBuilder stringBuilder = new StringBuilder();
- result.forEach(
+ unboxingResult.forEach(
(k, v) -> {
if (v.getRet() == UNBOX) {
stringBuilder
@@ -333,6 +344,19 @@
appView.reporter().warning(stringBuilder.toString());
}
+ private void enqueueMethodsForReprocessing(
+ PostMethodProcessor.Builder postMethodProcessorBuilder) {
+ postMethodProcessorBuilder.rewrittenWithLens(appView);
+
+ // TODO(b/307872552): Implement the reprocessing enqueuer so that only relevant methods are
+ // reprocessed. For testing we temporarily reprocess all.
+ postMethodProcessorBuilder.addAll(
+ appView.appInfo().classes().stream()
+ .flatMap(c -> StreamSupport.stream(c.programMethods().spliterator(), false))
+ .collect(Collectors.toList()),
+ appView.graphLens());
+ }
+
@Override
public void onMethodPruned(ProgramMethod method) {
// TODO(b/307872552): Should we do something about this? We might need to change the
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
new file mode 100644
index 0000000..eeb1768
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.NestedGraphLensWithCustomLensCodeRewriter;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NumberUnboxerLens extends NestedGraphLensWithCustomLensCodeRewriter {
+ private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
+
+ NumberUnboxerLens(
+ AppView<AppInfoWithLiveness> appView,
+ BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> renamedSignatures,
+ Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
+ NumberUnboxerRewriter numberUnboxerRewriter) {
+ super(appView, EMPTY_FIELD_MAP, renamedSignatures, EMPTY_TYPE_MAP, numberUnboxerRewriter);
+ this.prototypeChangesPerMethod = prototypeChangesPerMethod;
+ }
+
+ @Override
+ protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+ RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+ RewrittenPrototypeDescription enumUnboxingPrototypeChanges =
+ prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
+ return prototypeChanges.combine(enumUnboxingPrototypeChanges);
+ }
+
+ @Override
+ public boolean isNumberUnboxerLens() {
+ return true;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
+ new IdentityHashMap<>();
+ private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
+ new BidirectionalOneToOneHashMap<>();
+
+ public RewrittenPrototypeDescription move(DexEncodedMethod fromEncoded, DexMethod to) {
+ DexMethod from = fromEncoded.getReference();
+ assert !from.isIdenticalTo(to);
+ List<ExtraUnusedNullParameter> extraUnusedNullParameters =
+ ExtraUnusedNullParameter.computeExtraUnusedNullParameters(from, to);
+ RewrittenPrototypeDescription prototypeChanges =
+ computePrototypeChanges(from, to, fromEncoded.isStatic(), extraUnusedNullParameters);
+ synchronized (this) {
+ newMethodSignatures.put(from, to);
+ prototypeChangesPerMethod.put(to, prototypeChanges);
+ }
+ return prototypeChanges;
+ }
+
+ private RewrittenPrototypeDescription computePrototypeChanges(
+ DexMethod from,
+ DexMethod to,
+ boolean staticMethod,
+ List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
+ assert from.getArity() + extraUnusedNullParameters.size() == to.getArity();
+ ArgumentInfoCollection.Builder builder =
+ ArgumentInfoCollection.builder()
+ .setArgumentInfosSize(from.getNumberOfArguments(staticMethod));
+ for (int i = 0; i < from.getParameters().size(); i++) {
+ DexType fromType = from.getParameter(i);
+ DexType toType = to.getParameter(i);
+ if (!fromType.isIdenticalTo(toType)) {
+ builder.addArgumentInfo(
+ i, RewrittenTypeInfo.builder().setOldType(fromType).setNewType(toType).build());
+ }
+ }
+ RewrittenTypeInfo returnInfo =
+ from.getReturnType().isIdenticalTo(to.getReturnType())
+ ? null
+ : RewrittenTypeInfo.builder()
+ .setOldType(from.getReturnType())
+ .setNewType(to.getReturnType())
+ .build();
+ return RewrittenPrototypeDescription.create(
+ extraUnusedNullParameters, returnInfo, builder.build());
+ }
+
+ public NumberUnboxerLens build(
+ AppView<AppInfoWithLiveness> appView, NumberUnboxerRewriter numberUnboxerRewriter) {
+ return new NumberUnboxerLens(
+ appView, newMethodSignatures, prototypeChangesPerMethod, numberUnboxerRewriter);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java
new file mode 100644
index 0000000..83b1c96
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerRewriter.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.proto.ArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+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.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+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.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class NumberUnboxerRewriter implements CustomLensCodeRewriter {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NumberUnboxerRewriter(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public Set<Phi> rewriteCode(
+ IRCode code,
+ MethodProcessor methodProcessor,
+ RewrittenPrototypeDescription prototypeChanges,
+ NonIdentityGraphLens graphLens) {
+ assert graphLens.isNumberUnboxerLens();
+ Set<Phi> affectedPhis = Sets.newIdentityHashSet();
+ rewriteArgs(code, prototypeChanges, affectedPhis);
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction next = iterator.next();
+ if (next.isInvokeMethod()) {
+ InvokeMethod invokeMethod = next.asInvokeMethod();
+ // TODO(b/314117865): This is assuming that there are no non-rebound method references.
+ DexMethod rewrittenMethod =
+ graphLens
+ .lookupMethod(
+ invokeMethod.getInvokedMethod(),
+ code.context().getReference(),
+ invokeMethod.getType(),
+ graphLens.getPrevious())
+ .getReference();
+ assert rewrittenMethod != null;
+ RewrittenPrototypeDescription rewrittenPrototypeDescription =
+ graphLens.lookupPrototypeChangesForMethodDefinition(
+ rewrittenMethod, graphLens.getPrevious());
+ if (!rewrittenPrototypeDescription.isEmpty()) {
+ unboxInvokeValues(
+ code, iterator, invokeMethod, rewrittenPrototypeDescription, affectedPhis);
+ }
+ } else if (next.isReturn() && next.asReturn().hasReturnValue()) {
+ unboxReturnIfNeeded(code, iterator, next.asReturn(), prototypeChanges);
+ }
+ }
+ return affectedPhis;
+ }
+
+ private void unboxInvokeValues(
+ IRCode code,
+ InstructionListIterator iterator,
+ InvokeMethod invokeMethod,
+ RewrittenPrototypeDescription prototypeChanges,
+ Set<Phi> affectedPhis) {
+ assert prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments() == 0;
+ for (int inValueIndex = 0; inValueIndex < invokeMethod.inValues().size(); inValueIndex++) {
+ ArgumentInfo argumentInfo =
+ prototypeChanges.getArgumentInfoCollection().getArgumentInfo(inValueIndex);
+ if (argumentInfo.isRewrittenTypeInfo()) {
+ Value invokeArg = invokeMethod.getArgument(inValueIndex);
+ InvokeVirtual unboxOperation =
+ computeUnboxInvokeIfNeeded(
+ code, invokeArg, argumentInfo.asRewrittenTypeInfo(), invokeMethod.getPosition());
+ if (unboxOperation != null) {
+ iterator.addBeforeAndPositionAfterNewInstruction(unboxOperation);
+ invokeMethod.replaceValue(inValueIndex, unboxOperation.outValue());
+ }
+ }
+ }
+ if (invokeMethod.hasOutValue()) {
+ InvokeStatic boxOperation =
+ computeBoxInvokeIfNeeded(
+ code,
+ invokeMethod.outValue(),
+ prototypeChanges.getRewrittenReturnInfo(),
+ invokeMethod.getPosition());
+ if (boxOperation != null) {
+ iterator.add(boxOperation);
+ affectedPhis.addAll(boxOperation.outValue().uniquePhiUsers());
+ }
+ }
+ }
+
+ private void unboxReturnIfNeeded(
+ IRCode code,
+ InstructionListIterator iterator,
+ Return ret,
+ RewrittenPrototypeDescription prototypeChanges) {
+ InvokeVirtual unbox =
+ computeUnboxInvokeIfNeeded(
+ code, ret.returnValue(), prototypeChanges.getRewrittenReturnInfo(), ret.getPosition());
+ if (unbox != null) {
+ iterator.addBeforeAndPositionAfterNewInstruction(unbox);
+ ret.replaceValue(ret.returnValue(), unbox.outValue());
+ }
+ }
+
+ private InvokeVirtual computeUnboxInvokeIfNeeded(
+ IRCode code, Value input, RewrittenTypeInfo rewrittenTypeInfo, Position pos) {
+ if (rewrittenTypeInfo == null) {
+ return null;
+ }
+ assert rewrittenTypeInfo.getOldType().isReferenceType();
+ assert rewrittenTypeInfo.getNewType().isPrimitiveType();
+ assert appView.dexItemFactory().primitiveToBoxed.containsValue(rewrittenTypeInfo.getOldType());
+ return InvokeVirtual.builder()
+ .setMethod(appView.dexItemFactory().getUnboxPrimitiveMethod(rewrittenTypeInfo.getNewType()))
+ .setFreshOutValue(
+ code.valueNumberGenerator, rewrittenTypeInfo.getNewType().toTypeElement(appView))
+ .setArguments(ImmutableList.of(input))
+ .setPosition(pos)
+ .build();
+ }
+
+ private InvokeStatic computeBoxInvokeIfNeeded(
+ IRCode code, Value output, RewrittenTypeInfo rewrittenTypeInfo, Position pos) {
+ if (rewrittenTypeInfo == null) {
+ return null;
+ }
+ assert rewrittenTypeInfo.getOldType().isReferenceType();
+ assert rewrittenTypeInfo.getNewType().isPrimitiveType();
+ assert appView.dexItemFactory().primitiveToBoxed.containsValue(rewrittenTypeInfo.getOldType());
+ Value outValue = code.createValue(rewrittenTypeInfo.getOldType().toTypeElement(appView));
+ output.replaceUsers(outValue);
+ return InvokeStatic.builder()
+ .setOutValue(outValue)
+ .setMethod(appView.dexItemFactory().getBoxPrimitiveMethod(rewrittenTypeInfo.getNewType()))
+ .setSingleArgument(output)
+ .setPosition(pos)
+ .build();
+ }
+
+ private void rewriteArgs(
+ IRCode code, RewrittenPrototypeDescription prototypeChanges, Set<Phi> affectedPhis) {
+ List<InvokeStatic> boxingOperations = new ArrayList<>();
+ InstructionListIterator iterator = code.entryBlock().listIterator(code);
+ Position pos = iterator.peekNext().getPosition();
+ assert prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments() == 0;
+ int originalNumberOfArguments = code.getNumberOfArguments();
+ for (int argumentIndex = 0; argumentIndex < originalNumberOfArguments; argumentIndex++) {
+ ArgumentInfo argumentInfo =
+ prototypeChanges.getArgumentInfoCollection().getArgumentInfo(argumentIndex);
+ Instruction next = iterator.next();
+ assert next.isArgument();
+ if (argumentInfo.isRewrittenTypeInfo()) {
+ InvokeStatic boxOperation =
+ computeBoxInvokeIfNeeded(
+ code, next.outValue(), argumentInfo.asRewrittenTypeInfo(), pos);
+ if (boxOperation != null) {
+ boxingOperations.add(boxOperation);
+ }
+ }
+ }
+ assert !iterator.peekNext().isArgument();
+ for (InvokeStatic boxingOp : boxingOperations) {
+ iterator.add(boxingOp);
+ affectedPhis.addAll(boxingOp.outValue().uniquePhiUsers());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
new file mode 100644
index 0000000..166cccd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup.ProgramClassFixer;
+import com.android.tools.r8.graph.fixup.MethodNamingUtility;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Timing;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class NumberUnboxerTreeFixer implements ProgramClassFixer {
+
+ private final Map<DexMethod, MethodBoxingStatusResult> unboxingResult;
+ private final AppView<AppInfoWithLiveness> appView;
+
+ private final NumberUnboxerLens.Builder lensBuilder = NumberUnboxerLens.builder();
+
+ public NumberUnboxerTreeFixer(
+ AppView<AppInfoWithLiveness> appView,
+ Map<DexMethod, MethodBoxingStatusResult> unboxingResult) {
+ this.unboxingResult = unboxingResult;
+ this.appView = appView;
+ }
+
+ public NumberUnboxerLens fixupTree(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ // Take strongly connected components, for each merge data from call-sites and methods and
+ // unbox.
+ new ConcurrentMethodFixup(appView, this)
+ .fixupClassesConcurrentlyByConnectedProgramComponents(timing, executorService);
+ return lensBuilder.build(appView, new NumberUnboxerRewriter(appView));
+ }
+
+ @Override
+ public void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility utility) {
+ clazz.getMethodCollection().replaceMethods(m -> fixupEncodedMethod(m, utility));
+ }
+
+ @Override
+ public boolean shouldReserveAsIfPinned(ProgramMethod method) {
+ // We don't reprocess dependencies of unchanged methods so we have to maintain them
+ // with the same signature.
+ return !unboxingResult.containsKey(method.getReference());
+ }
+
+ private DexEncodedMethod fixupEncodedMethod(
+ DexEncodedMethod method, MethodNamingUtility utility) {
+ if (!unboxingResult.containsKey(method.getReference())) {
+ assert method
+ .getReference()
+ .isIdenticalTo(
+ utility.nextUniqueMethod(
+ method, method.getProto(), appView.dexItemFactory().shortType));
+ return method;
+ }
+ MethodBoxingStatusResult methodBoxingStatus = unboxingResult.get(method.getReference());
+ assert !methodBoxingStatus.isNoneUnboxable();
+ DexProto newProto = fixupProto(method.getProto(), methodBoxingStatus);
+ DexMethod newMethod =
+ utility.nextUniqueMethod(method, newProto, appView.dexItemFactory().shortType);
+
+ RewrittenPrototypeDescription prototypeChanges = lensBuilder.move(method, newMethod);
+ return method.toTypeSubstitutedMethodAsInlining(
+ newMethod,
+ appView.dexItemFactory(),
+ builder ->
+ builder
+ .fixupOptimizationInfo(
+ appView, prototypeChanges.createMethodOptimizationInfoFixer())
+ .setCompilationState(method.getCompilationState())
+ .setIsLibraryMethodOverrideIf(
+ method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
+ }
+
+ private DexType fixupType(DexType type, boolean unbox) {
+ if (!unbox) {
+ return type;
+ }
+ DexType newType = appView.dexItemFactory().primitiveToBoxed.inverse().get(type);
+ assert newType != null;
+ return newType;
+ }
+
+ private DexProto fixupProto(DexProto proto, MethodBoxingStatusResult methodBoxingStatus) {
+ DexType[] argTypes = proto.getParameters().values;
+ DexType[] newArgTypes =
+ ArrayUtils.initialize(
+ new DexType[argTypes.length],
+ i -> fixupType(argTypes[i], methodBoxingStatus.shouldUnboxArg(i)));
+ DexType newReturnType = fixupType(proto.getReturnType(), methodBoxingStatus.shouldUnboxRet());
+ DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newArgTypes);
+ assert newProto.isNotIdenticalTo(proto);
+ return newProto;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
index 755096b..c14645d 100644
--- a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
@@ -4,10 +4,17 @@
package com.android.tools.r8.numberunboxing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Objects;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
@@ -39,6 +46,7 @@
.setMinApi(parameters)
.allowDiagnosticWarningMessages()
.compile()
+ .inspect(this::assertUnboxing)
.assertWarningMessageThatMatches(
CoreMatchers.containsString(
"Unboxing of arg 0 of void"
@@ -67,6 +75,33 @@
.assertSuccessWithOutputLines("32", "33", "42", "43", "51", "52", "2");
}
+ private void assertFirstParameterUnboxed(ClassSubject mainClass, String methodName) {
+ MethodSubject methodSubject = mainClass.uniqueMethodWithOriginalName(methodName);
+ assertThat(methodSubject, isPresent());
+ assertEquals("java.lang.Integer", methodSubject.getOriginalSignature().parameters[0]);
+ assertEquals("int", methodSubject.getFinalSignature().asMethodSignature().parameters[0]);
+ }
+
+ private void assertReturnUnboxed(ClassSubject mainClass, String methodName) {
+ MethodSubject methodSubject = mainClass.uniqueMethodWithOriginalName(methodName);
+ assertThat(methodSubject, isPresent());
+ assertEquals("java.lang.Integer", methodSubject.getOriginalSignature().type);
+ assertEquals("int", methodSubject.getFinalSignature().asMethodSignature().type);
+ }
+
+ private void assertUnboxing(CodeInspector codeInspector) {
+ ClassSubject mainClass = codeInspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+
+ assertFirstParameterUnboxed(mainClass, "print");
+ assertFirstParameterUnboxed(mainClass, "forwardToPrint2");
+ assertFirstParameterUnboxed(mainClass, "directPrintUnbox");
+ assertFirstParameterUnboxed(mainClass, "forwardToPrint");
+
+ assertReturnUnboxed(mainClass, "get");
+ assertReturnUnboxed(mainClass, "forwardGet");
+ }
+
static class Main {
public static void main(String[] args) {