blob: 4a9cbfdc84cf4c60a92b34838275d3312e1de930 [file] [log] [blame]
// 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 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 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;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
public class NumberUnboxerBoxingStatusResolution {
// TODO(b/307872552): Add threshold to NumberUnboxing options.
private static final int UNBOX_DELTA_THRESHOLD = 0;
private final Map<DexMethod, MethodBoxingStatusResult> boxingStatusResultMap =
new IdentityHashMap<>();
static class MethodBoxingStatusResult {
public static MethodBoxingStatusResult createNonUnboxable(DexMethod method) {
// Replace by singleton.
return new MethodBoxingStatusResult(method, NO_UNBOX);
}
public static MethodBoxingStatusResult create(DexMethod method) {
return new MethodBoxingStatusResult(method, TO_PROCESS);
}
MethodBoxingStatusResult(DexMethod method, BoxingStatusResult init) {
this.ret = init;
this.args = new BoxingStatusResult[method.getArity()];
Arrays.fill(args, init);
}
enum BoxingStatusResult {
TO_PROCESS,
UNBOX,
NO_UNBOX
}
BoxingStatusResult ret;
BoxingStatusResult[] args;
public void setRet(BoxingStatusResult ret) {
this.ret = ret;
}
public BoxingStatusResult getRet() {
return ret;
}
public void setArg(BoxingStatusResult arg, int i) {
this.args[i] = arg;
}
public BoxingStatusResult getArg(int i) {
return args[i];
}
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) {
boxingStatusResultMap.put(method, MethodBoxingStatusResult.createNonUnboxable(method));
}
private MethodBoxingStatusResult getMethodBoxingStatusResult(DexMethod method) {
return boxingStatusResultMap.computeIfAbsent(method, MethodBoxingStatusResult::create);
}
BoxingStatusResult get(TransitiveDependency transitiveDependency) {
assert transitiveDependency.isMethodDependency();
MethodBoxingStatusResult methodBoxingStatusResult =
getMethodBoxingStatusResult(transitiveDependency.asMethodDependency().getMethod());
if (transitiveDependency.isMethodRet()) {
return methodBoxingStatusResult.getRet();
}
assert transitiveDependency.isMethodArg();
return methodBoxingStatusResult.getArg(transitiveDependency.asMethodArg().getParameterIndex());
}
void register(TransitiveDependency transitiveDependency, BoxingStatusResult boxingStatusResult) {
assert transitiveDependency.isMethodDependency();
MethodBoxingStatusResult methodBoxingStatusResult =
getMethodBoxingStatusResult(transitiveDependency.asMethodDependency().getMethod());
if (transitiveDependency.isMethodRet()) {
methodBoxingStatusResult.setRet(boxingStatusResult);
return;
}
assert transitiveDependency.isMethodArg();
methodBoxingStatusResult.setArg(
boxingStatusResult, transitiveDependency.asMethodArg().getParameterIndex());
}
public Map<DexMethod, MethodBoxingStatusResult> resolve(
Map<DexMethod, MethodBoxingStatus> methodBoxingStatus) {
List<DexMethod> methods = ListUtils.sort(methodBoxingStatus.keySet(), DexMethod::compareTo);
for (DexMethod method : methods) {
MethodBoxingStatus status = methodBoxingStatus.get(method);
if (status.isNoneUnboxable()) {
markNoneUnboxable(method);
continue;
}
MethodBoxingStatusResult methodBoxingStatusResult = getMethodBoxingStatusResult(method);
if (status.getReturnStatus().isNotUnboxable()) {
methodBoxingStatusResult.setRet(NO_UNBOX);
} else {
if (methodBoxingStatusResult.getRet() == TO_PROCESS) {
resolve(methodBoxingStatus, new MethodRet(method));
}
}
for (int i = 0; i < status.getArgStatuses().length; i++) {
ValueBoxingStatus argStatus = status.getArgStatus(i);
if (argStatus.isNotUnboxable()) {
methodBoxingStatusResult.setArg(NO_UNBOX, i);
} else {
if (methodBoxingStatusResult.getArg(i) == TO_PROCESS) {
resolve(methodBoxingStatus, new MethodArg(i, method));
}
}
}
}
assert allProcessed();
clearNoneUnboxable();
return boxingStatusResultMap;
}
private void clearNoneUnboxable() {
boxingStatusResultMap.values().removeIf(MethodBoxingStatusResult::isNoneUnboxable);
}
private boolean allProcessed() {
boxingStatusResultMap.forEach(
(k, v) -> {
assert v.getRet() != TO_PROCESS;
for (BoxingStatusResult arg : v.getArgs()) {
assert arg != TO_PROCESS;
}
});
return true;
}
private ValueBoxingStatus getValueBoxingStatus(
TransitiveDependency dep, Map<DexMethod, MethodBoxingStatus> methodBoxingStatus) {
// Later we will implement field dependencies.
assert dep.isMethodDependency();
MethodBoxingStatus status = methodBoxingStatus.get(dep.asMethodDependency().getMethod());
if (dep.isMethodRet()) {
return status.getReturnStatus();
}
assert dep.isMethodArg();
return status.getArgStatus(dep.asMethodArg().getParameterIndex());
}
private void resolve(
Map<DexMethod, MethodBoxingStatus> methodBoxingStatus, TransitiveDependency dep) {
WorkList<TransitiveDependency> workList = WorkList.newIdentityWorkList(dep);
int delta = 0;
while (workList.hasNext()) {
TransitiveDependency next = workList.next();
BoxingStatusResult boxingStatusResult = get(next);
if (boxingStatusResult == UNBOX) {
delta++;
continue;
}
ValueBoxingStatus valueBoxingStatus = getValueBoxingStatus(next, methodBoxingStatus);
if (boxingStatusResult == NO_UNBOX || valueBoxingStatus.isNotUnboxable()) {
// TODO(b/307872552): Unbox when a non unboxable non null dependency is present.
// If a dependency is not unboxable, we need to prove it's non-null, else we cannot unbox.
// In this first version we bail out by setting a negative delta.
delta = -1;
break;
}
assert boxingStatusResult == TO_PROCESS;
workList.addIfNotSeen(valueBoxingStatus.getTransitiveDependencies());
// Each dependency has been pessimistically marked as requiring extra boxing operation.
// TODO(b/307872552): Test and re-evaluate the delta computation.
delta += valueBoxingStatus.getTransitiveDependencies().size();
delta += valueBoxingStatus.getBoxingDelta();
}
BoxingStatusResult boxingStatusResult = delta > UNBOX_DELTA_THRESHOLD ? UNBOX : NO_UNBOX;
for (TransitiveDependency transitiveDependency : workList.getSeenSet()) {
assert transitiveDependency.isMethodDependency();
register(transitiveDependency.asMethodDependency(), boxingStatusResult);
}
}
}