Merge commit '0ff3415ebe3965d6ced3759dcf263f12fa2d0ec7' into dev-release
Change-Id: I5b6705a917c14971787e4b51dab0e227fe34b9f3
diff --git a/.gitignore b/.gitignore
index b62cc8f..8bd1412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,8 +113,8 @@
third_party/framework
third_party/framework.tar.gz
third_party/gmscore/*
-third_party/google/google-java-format/1.14.0
-third_party/google/google-java-format/1.14.0.tar.gz
+third_party/google/google-java-format/1.24.0
+third_party/google/google-java-format/1.24.0.tar.gz
third_party/google/yapf/20231013
third_party/google/yapf/20231013.tar.gz
third_party/google-java-format
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2312362..986d67c 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -19,16 +19,16 @@
'third_party',
'google',
'google-java-format',
- '1.14.0',
- 'google-java-format-1.14.0',
+ '1.24.0',
+ 'google-java-format-1.24.0',
'scripts',
'google-java-format-diff.py')
FMT_CMD_JDK17 = path.join('tools','google-java-format-diff.py')
FMT_SHA1 = path.join(
- 'third_party', 'google', 'google-java-format', '1.14.0.tar.gz.sha1')
+ 'third_party', 'google', 'google-java-format', '1.24.0.tar.gz.sha1')
FMT_TGZ = path.join(
- 'third_party', 'google', 'google-java-format', '1.14.0.tar.gz')
+ 'third_party', 'google', 'google-java-format', '1.24.0.tar.gz')
def CheckDoNotMerge(input_api, output_api):
for l in input_api.change.FullDescriptionText().splitlines():
@@ -73,7 +73,7 @@
FMT_CMD,
FMT_CMD_JDK17,
'--google-java-format-jar',
- 'third_party/google/google-java-format/1.14.0/google-java-format-1.14.0-all-deps.jar'
+ 'third_party/google/google-java-format/1.24.0/google-java-format-1.24.0-all-deps.jar'
)))
return results
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 16fa3e8..ecffa3a 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -623,10 +623,10 @@
"google-java-format",
Paths.get("third_party", "google-java-format").toFile(),
Paths.get("third_party", "google-java-format.tar.gz.sha1").toFile())
- val googleJavaFormat_1_14 = ThirdPartyDependency(
- "google-java-format-1.14",
- Paths.get("third_party", "google", "google-java-format", "1.14.0").toFile(),
- Paths.get("third_party", "google", "google-java-format", "1.14.0.tar.gz.sha1").toFile())
+ val googleJavaFormat_1_24 = ThirdPartyDependency(
+ "google-java-format-1.24",
+ Paths.get("third_party", "google", "google-java-format", "1.24.0").toFile(),
+ Paths.get("third_party", "google", "google-java-format", "1.24.0.tar.gz.sha1").toFile())
val googleYapf_20231013 = ThirdPartyDependency(
"google-yapf-20231013",
Paths.get("third_party", "google", "yapf", "20231013").toFile(),
@@ -817,7 +817,8 @@
"lib-v32",
"lib-v33",
"lib-v34",
- "lib-v35"
+ "lib-v35",
+ "lib-v36"
).map(::getThirdPartyAndroidJar)
}
@@ -893,6 +894,7 @@
"kotlin-compiler-1.8.0",
"kotlin-compiler-1.9.21",
"kotlin-compiler-2.0.20",
+ "kotlin-compiler-2.1.0-Beta1",
"kotlin-compiler-dev")
.map { ThirdPartyDependency(
it,
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
index 80bfd4e..c9a5d63 100644
--- a/d8_r8/test/build.gradle.kts
+++ b/d8_r8/test/build.gradle.kts
@@ -229,23 +229,6 @@
"r8lib.jar")
}
- val resourceshrinkercli by registering(Exec::class) {
- dependsOn(r8WithRelocatedDepsTask)
- val r8 = r8WithRelocatedDepsTask.getSingleOutputFile()
- val keepTxt = getRoot().resolveAll("src", "main", "resourceshrinker_cli.txt")
- val cliKeep = getRoot().resolveAll("src", "main", "keep_r8resourceshrinker.txt")
- inputs.files(keepTxt, cliKeep)
- val output = file(Paths.get("build", "libs", "resourceshrinkercli.jar"))
- outputs.file(output)
- commandLine = createR8LibCommandLine(
- r8,
- r8,
- output,
- listOf(keepTxt, cliKeep),
- false,
- false)
- }
-
fun Task.generateTestKeepRulesForR8Lib(
r8LibJarProvider: TaskProvider<Exec>, artifactName: String) {
dependsOn(r8LibJarProvider)
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 489f9e0..f2197c4 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1526,6 +1526,11 @@
.applyIf(
featureSplitConfiguration != null,
b -> b.setIsolatedSplits(featureSplitConfiguration.isIsolatedSplitsEnabled()))
+ .applyIf(
+ resourceShrinkerConfiguration != null,
+ b ->
+ b.setOptimizedResourceShrinking(
+ resourceShrinkerConfiguration.isOptimizedShrinking()))
.build();
}
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 0081d41..64dee4d 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -25,7 +26,6 @@
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
public class LoadStoreHelper {
@@ -34,8 +34,12 @@
private final IRCode code;
private final TypeVerificationHelper typesHelper;
- private Map<Value, ConstInstruction> clonableConstants = null;
- private ListIterator<BasicBlock> blockIterator = null;
+ private Map<Value, ConstInstruction> clonableConstants;
+
+ // The active block and instruction iterator used by the pass. These are stored in fields to avoid
+ // needing to pass them back and forth through Instruction#insertLoadAndStores.
+ private BasicBlockIterator blockIterator;
+ private InstructionListIterator instructionIterator;
public LoadStoreHelper(AppView<?> appView, IRCode code, TypeVerificationHelper typesHelper) {
this.appView = appView;
@@ -99,9 +103,11 @@
clonableConstants = new IdentityHashMap<>();
blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
- InstructionListIterator it = blockIterator.next().listIterator();
- while (it.hasNext()) {
- it.next().insertLoadAndStores(it, this);
+ BasicBlock block = blockIterator.next();
+ instructionIterator = block.listIterator();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ instruction.insertLoadAndStores(this);
}
clonableConstants.clear();
}
@@ -128,10 +134,10 @@
moves.add(new PhiMove(phi, value));
}
}
- InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
- Instruction exit = it.previous();
+ instructionIterator = pred.listIterator(pred.getInstructions().size());
+ Instruction exit = instructionIterator.previous();
assert pred.exit() == exit;
- movePhis(moves, it, exit.getPosition());
+ movePhis(moves, exit.getPosition());
}
allocator.addToLiveAtEntrySet(block, block.getPhis());
}
@@ -147,9 +153,9 @@
return StackValue.create(typesHelper.createInitializedType(type), height, appView);
}
- public void loadInValues(Instruction instruction, InstructionListIterator it) {
+ public void loadInValues(Instruction instruction) {
int topOfStack = 0;
- it.previous();
+ instructionIterator.previous();
for (int i = 0; i < instruction.inValues().size(); i++) {
Value value = instruction.inValues().get(i);
StackValue stackValue = createStackValue(value, topOfStack++);
@@ -158,26 +164,25 @@
if (constInstruction != null) {
ConstInstruction clonedConstInstruction =
ConstInstruction.copyOf(stackValue, constInstruction);
- add(clonedConstInstruction, instruction, it);
+ add(clonedConstInstruction, instruction);
} else {
- add(load(stackValue, value), instruction, it);
+ add(load(stackValue, value), instruction);
}
instruction.replaceValue(i, stackValue);
}
- it.next();
+ instructionIterator.next();
}
- public void storeOrPopOutValue(
- DexType type, Instruction instruction, InstructionListIterator it) {
+ public void storeOrPopOutValue(DexType type, Instruction instruction) {
if (instruction.hasOutValue()) {
assert instruction.outValue().isUsed();
- storeOutValue(instruction, it);
+ storeOutValue(instruction);
} else {
- popOutType(type, instruction, it);
+ popOutType(type, instruction);
}
}
- public void storeOutValue(Instruction instruction, InstructionListIterator it) {
+ public void storeOutValue(Instruction instruction) {
assert instruction.hasOutValue();
assert !(instruction.outValue() instanceof StackValue);
if (instruction.isConstInstruction()) {
@@ -187,7 +192,7 @@
|| constInstruction.outValue().numberOfUsers() == 1;
clonableConstants.put(instruction.outValue(), constInstruction);
instruction.outValue().clearUsers();
- it.removeOrReplaceByDebugLocalRead();
+ instructionIterator.removeOrReplaceByDebugLocalRead();
return;
}
assert instruction.outValue().isUsed()
@@ -195,7 +200,7 @@
: "Expected instruction to be removed: " + instruction;
}
if (!instruction.outValue().isUsed()) {
- popOutValue(instruction.outValue(), instruction, it);
+ popOutValue(instruction.outValue(), instruction);
return;
}
StackValue newOutValue = createStackValue(instruction.outValue(), 0);
@@ -208,41 +213,40 @@
// instruction is throwing, the action should be moved to a new block - otherwise, the store
// should be inserted and the remaining instructions should be moved along with the handlers to
// the new block.
- boolean hasCatchHandlers = instruction.getBlock().hasCatchHandlers();
+ boolean hasCatchHandlers = storeBlock.hasCatchHandlers();
if (hasCatchHandlers && instruction.instructionTypeCanThrow()) {
- storeBlock = it.split(this.code, this.blockIterator);
- it = storeBlock.listIterator();
+ storeBlock = instructionIterator.split(this.code, this.blockIterator);
+ instructionIterator = storeBlock.listIterator();
}
- add(store, instruction.getPosition(), it);
+ add(store, instruction.getPosition());
if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) {
- splitAfterStoredOutValue(it);
+ splitAfterStoredOutValue();
}
}
// DebugLocalWrite encodes a store and it needs to consistently split out the catch range after
// its store.
- public void splitAfterStoredOutValue(InstructionListIterator it) {
- it.split(this.code, this.blockIterator);
- this.blockIterator.previous();
+ public void splitAfterStoredOutValue() {
+ instructionIterator.split(this.code, this.blockIterator);
+ blockIterator.previous();
}
- public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
- popOutValue(createStackValue(type, 0), instruction, it);
+ public void popOutType(DexType type, Instruction instruction) {
+ popOutValue(createStackValue(type, 0), instruction);
}
- private void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
- popOutValue(createStackValue(value, 0), instruction, it);
+ private void popOutValue(Value value, Instruction instruction) {
+ popOutValue(createStackValue(value, 0), instruction);
}
- private void popOutValue(
- StackValue newOutValue, Instruction instruction, InstructionListIterator it) {
+ private void popOutValue(StackValue newOutValue, Instruction instruction) {
BasicBlock insertBlock = instruction.getBlock();
if (insertBlock.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
- insertBlock = it.split(this.code, this.blockIterator);
- it = insertBlock.listIterator();
+ insertBlock = instructionIterator.split(this.code, this.blockIterator);
+ instructionIterator = insertBlock.listIterator();
}
instruction.swapOutValue(newOutValue);
- add(new Pop(newOutValue), instruction.getPosition(), it);
+ add(new Pop(newOutValue), instruction.getPosition());
}
private static class PhiMove {
@@ -255,13 +259,13 @@
}
}
- private void movePhis(List<PhiMove> moves, InstructionListIterator it, Position position) {
+ private void movePhis(List<PhiMove> moves, Position position) {
// TODO(zerny): Accounting for non-interfering phis would lower the max stack size.
int topOfStack = 0;
List<StackValue> temps = new ArrayList<>(moves.size());
for (PhiMove move : moves) {
StackValue tmp = createStackValue(move.phi, topOfStack++);
- add(load(tmp, move.operand), position, it);
+ add(load(tmp, move.operand), position);
temps.add(tmp);
move.operand.removePhiUser(move.phi);
}
@@ -269,7 +273,7 @@
PhiMove move = moves.get(i);
StackValue tmp = temps.get(i);
FixedLocalValue out = new FixedLocalValue(move.phi);
- add(new Store(out, tmp), position, it);
+ add(new Store(out, tmp), position);
move.phi.replaceUsers(out);
}
}
@@ -294,14 +298,12 @@
return new Load(stackValue, value);
}
- private static void add(
- Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
- add(newInstruction, existingInstruction.getPosition(), it);
+ private void add(Instruction newInstruction, Instruction existingInstruction) {
+ add(newInstruction, existingInstruction.getPosition());
}
- private static void add(
- Instruction newInstruction, Position position, InstructionListIterator it) {
+ private void add(Instruction newInstruction, Position position) {
newInstruction.setPosition(position);
- it.add(newInstruction);
+ instructionIterator.add(newInstruction);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 985fbc9..9ee017e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -220,6 +220,10 @@
return false;
}
+ public CfStaticFieldRead asStaticFieldGet() {
+ return null;
+ }
+
public boolean isFieldPut() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index f7ae0ff..12467da 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -48,6 +48,11 @@
}
@Override
+ public CfStaticFieldRead asStaticFieldGet() {
+ return this;
+ }
+
+ @Override
void internalRegisterUse(
UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
registry.registerStaticFieldReadInstruction(this);
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index f2beb28..052a6de 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -47,6 +47,7 @@
private static final String INTERMEDIATE_KEY = "intermediate";
private static final String INCLUDE_DATA_RESOURCES_KEY = "include-data-resources";
private static final String ISOLATED_SPLITS_KEY = "isolated-splits";
+ private static final String OPTIMIZED_RESOURCE_SHRINKING = "optimized-resource-shrinking";
private static final String TREE_SHAKING_KEY = "tree-shaking";
private static final String MINIFICATION_KEY = "minification";
private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
@@ -66,6 +67,7 @@
private final Optional<Boolean> intermediate;
private final Optional<Boolean> includeDataResources;
private final Optional<Boolean> isolatedSplits;
+ private final Optional<Boolean> optimizedResourceShrinking;
private final Optional<Boolean> treeShaking;
private final Optional<Boolean> minification;
private final Optional<Boolean> forceProguardCompatibility;
@@ -113,7 +115,8 @@
Map<String, String> systemProperties,
boolean dumpInputToFile,
String traceReferencesConsumer,
- AndroidResourceProvider androidResourceProvider) {
+ AndroidResourceProvider androidResourceProvider,
+ Optional<Boolean> optimizedResourceShrinking) {
this.backend = backend;
this.tool = tool;
this.compilationMode = compilationMode;
@@ -125,6 +128,7 @@
this.intermediate = intermediate;
this.includeDataResources = includeDataResources;
this.isolatedSplits = isolatedSplits;
+ this.optimizedResourceShrinking = optimizedResourceShrinking;
this.treeShaking = treeShaking;
this.minification = minification;
this.forceProguardCompatibility = forceProguardCompatibility;
@@ -173,9 +177,12 @@
addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate);
addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
addOptionalDumpEntry(buildProperties, ISOLATED_SPLITS_KEY, isolatedSplits);
+ addOptionalDumpEntry(buildProperties, OPTIMIZED_RESOURCE_SHRINKING, isolatedSplits);
addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking);
addOptionalDumpEntry(
buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
+ addOptionalDumpEntry(
+ buildProperties, OPTIMIZED_RESOURCE_SHRINKING, optimizedResourceShrinking);
} else {
addDumpEntry(buildProperties, TRACE_REFERENCES_CONSUMER, traceReferencesConsumer);
}
@@ -251,6 +258,9 @@
case ISOLATED_SPLITS_KEY:
builder.setIsolatedSplits(Boolean.parseBoolean(value));
return;
+ case OPTIMIZED_RESOURCE_SHRINKING:
+ builder.setOptimizedResourceShrinking(Boolean.parseBoolean(value));
+ return;
case TREE_SHAKING_KEY:
builder.setTreeShaking(Boolean.parseBoolean(value));
return;
@@ -391,6 +401,7 @@
private Collection<ArtProfileProvider> artProfileProviders;
private Collection<StartupProfileProvider> startupProfileProviders;
private AndroidResourceProvider androidResourceProvider;
+ private Optional<Boolean> optimizedResourceShrinking = Optional.empty();
private boolean enableMissingLibraryApiModeling = false;
private boolean isAndroidPlatformBuild = false;
@@ -472,6 +483,10 @@
return this;
}
+ public void setOptimizedResourceShrinking(boolean optimizedResourceShrinking) {
+ this.optimizedResourceShrinking = Optional.of(optimizedResourceShrinking);
+ }
+
public Builder setForceProguardCompatibility(boolean forceProguardCompatibility) {
this.forceProguardCompatibility = Optional.of(forceProguardCompatibility);
return this;
@@ -581,7 +596,8 @@
systemProperties,
dumpInputToFile,
traceReferencesConsumer,
- androidResourceProvider);
+ androidResourceProvider,
+ optimizedResourceShrinking);
}
public AndroidResourceProvider getAndroidResourceProvider() {
diff --git a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
index f3b5171..93ac3df 100644
--- a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
@@ -3,22 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.errors;
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@KeepForApi
public class IgnoredBackportMethodDiagnostic implements DesugarDiagnostic {
- private final DexMethod backport;
+ private final DexMember<?, ?> backport;
private final Origin origin;
private final Position position;
private final int minApiLevel;
public IgnoredBackportMethodDiagnostic(
- DexMethod backport, Origin origin, Position position, int minApiLevel) {
+ DexMember<?, ?> backport, Origin origin, Position position, int minApiLevel) {
this.backport = backport;
this.origin = origin;
this.position = position;
@@ -26,7 +27,11 @@
}
public MethodReference getIgnoredBackportMethod() {
- return backport.asMethodReference();
+ return backport.isDexMethod() ? backport.asDexMethod().asMethodReference() : null;
+ }
+
+ public FieldReference getIgnoredBackportField() {
+ return backport.isDexField() ? backport.asDexField().asFieldReference() : null;
}
public int getConfiguredMinApiLevel() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index 1dc176f..1f1eff4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -61,7 +61,7 @@
return this;
}
- public final boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
+ public boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
return getAccessFlags().isFinal()
|| (appView.hasLiveness() && isEffectivelyFinal(appView.withLiveness()));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a79ae60..cea12d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -773,6 +773,7 @@
androidSystemOsConstantsMembers,
androidViewViewMembers,
// java.**
+ enumMembers,
javaIoFileMembers,
javaMathBigIntegerMembers,
javaNioByteOrderMembers,
@@ -1215,6 +1216,8 @@
public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+ public final DexField SDK_INT_FULL =
+ createField(androidOsBuildVersionType, intType, "SDK_INT_FULL");
public final DexField SECURITY_PATCH =
createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
@@ -1339,6 +1342,8 @@
createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
public final DexMethod toString =
createMethod(boxedBooleanType, createProto(stringType), "toString");
+ public final DexMethod staticHashCode =
+ createMethod(boxedBooleanType, createProto(intType, booleanType), "hashCode");
private BooleanMembers() {}
@@ -1403,6 +1408,8 @@
createMethod(boxedFloatType, createProto(stringType), "toString");
public final DexMethod valueOf =
createMethod(boxedFloatType, createProto(boxedFloatType, floatType), "valueOf");
+ public final DexMethod staticHashCode =
+ createMethod(boxedFloatType, createProto(intType, floatType), "hashCode");
private FloatMembers() {}
@@ -1606,6 +1613,8 @@
createMethod(boxedLongType, createProto(stringType), "toString");
public final DexMethod valueOf =
createMethod(boxedLongType, createProto(boxedLongType, longType), "valueOf");
+ public final DexMethod staticHashCode =
+ createMethod(boxedLongType, createProto(intType, longType), "hashCode");
private LongMembers() {}
@@ -1632,6 +1641,8 @@
createMethod(boxedDoubleType, createProto(stringType), "toString");
public final DexMethod valueOf =
createMethod(boxedDoubleType, createProto(boxedDoubleType, doubleType), "valueOf");
+ public final DexMethod staticHashCode =
+ createMethod(boxedDoubleType, createProto(intType, doubleType), "hashCode");
private DoubleMembers() {}
@@ -2114,7 +2125,7 @@
private JavaIoPrintStreamMembers() {}
}
- public class EnumMembers {
+ public class EnumMembers extends LibraryMembers {
public final DexField nameField = createField(enumType, stringType, "name");
public final DexField ordinalField = createField(enumType, intType, "ordinal");
@@ -2169,6 +2180,12 @@
fn.accept(ordinalField);
}
+ @Override
+ public void forEachFinalField(Consumer<DexField> fn) {
+ fn.accept(nameField);
+ fn.accept(ordinalField);
+ }
+
@SuppressWarnings("ReferenceEquality")
public boolean isNameOrOrdinalField(DexField field) {
return field == nameField || field == ordinalField;
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryField.java b/src/main/java/com/android/tools/r8/graph/LibraryField.java
index 49ed565..8221f8d 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryField.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryField.java
@@ -32,4 +32,9 @@
public boolean isLibraryMember() {
return true;
}
+
+ @Override
+ public boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
+ return appView.libraryMethodOptimizer().isFinalLibraryField(getDefinition());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 020db76..3a544aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -77,7 +77,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index cd4d6a4..8bfdfcf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -73,7 +73,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Nothing to do.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index a5cf0e4..3f99c84 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -74,7 +74,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index bebe521..d759235 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -133,7 +133,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Arguments are defined by locals so nothing to load or store.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 1f6ea43..865d83b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -176,9 +176,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index ae761d1..ba579de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -111,9 +111,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 9419bef..c96f6f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -238,8 +238,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 279d1b9..e29df9f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -214,7 +214,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable(ERROR_MESSAGE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 9bc7905..7afef24 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1175,7 +1175,7 @@
public boolean consistentCatchHandlers() {
// Check that catch handlers are always the first successors of a block.
if (hasCatchHandlers()) {
- assert exit().isGoto() || exit().isThrow();
+ assert exit().isGoto() || exit().isReturn() || exit().isThrow();
CatchHandlers<Integer> catchHandlers = getCatchHandlersWithSuccessorIndexes();
// Check that guards are unique.
assert catchHandlers.getGuards().size()
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 4bd1624..4bccf15 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
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -1010,6 +1011,34 @@
private InstructionListIterator ensureSingleReturnInstruction(
AppView<?> appView, IRCode code, List<BasicBlock> normalExits) {
+ // First ensure that there will be not critical edges after inlining. This is needed since
+ // return blocks are allowed to have catch handlers.
+ if (Iterables.any(normalExits, BasicBlock::hasCatchHandlers)) {
+ normalExits =
+ ListUtils.map(
+ normalExits,
+ exitBlock -> {
+ if (!exitBlock.hasCatchHandlers()) {
+ return exitBlock;
+ }
+ Return exit = exitBlock.exit().asReturn();
+
+ // Create new exit block.
+ BasicBlock newExitBlock = new BasicBlock(code.metadata());
+ newExitBlock.setNumber(code.getNextBlockNumber());
+ Return newReturn =
+ exit.isReturnVoid() ? new Return() : new Return(exit.returnValue());
+ newReturn.setPosition(exit.getPosition());
+ newExitBlock.add(newReturn, code.metadata());
+
+ // Fixup old exit.
+ exit.replace(new Goto());
+ exitBlock.link(newExitBlock);
+ newExitBlock.close(null);
+ code.blocks.add(newExitBlock);
+ return newExitBlock;
+ });
+ }
if (normalExits.size() == 1) {
InstructionListIterator it = normalExits.get(0).listIterator();
it.nextUntil(Instruction::isReturn);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 12cbc36..207024e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -137,9 +137,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 9845028..1b1d55c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -249,9 +249,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index fe1c8b3..5273534 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -188,8 +188,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 54cca22..514e31f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -136,8 +136,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index cd799fe..5f7f2c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -128,8 +128,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 95df669..124b3f7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -166,8 +166,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 0b2b5e1..590fa00 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -152,8 +152,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 3e8e999..8b3b71f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -90,7 +90,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Non-materializing so no stack values are needed.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 7304858..ea4ef1f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -64,12 +64,12 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
// A local-write does not have an outgoing stack value, but in writes directly to the local.
assert !instructionTypeCanThrow();
if (getBlock().hasCatchHandlers()) {
- helper.splitAfterStoredOutValue(it);
+ helper.splitAfterStoredOutValue();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 5852d5e..a8291d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -123,7 +123,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 090040a..174392a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -74,7 +74,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Nothing to do for positions which are not actual instructions.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 6b3072f..d1bea5b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -144,8 +144,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 41dea29..071839b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -103,7 +103,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Intentionally empty. Dup is a stack operation.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index eebb405..c05a8af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -117,7 +117,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Intentionally empty. Dup2 is a stack operation.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 3c5a357..6bb4e8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -84,7 +84,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Nothing to do.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 631034e..25c51c9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1156,7 +1156,7 @@
}
public Iterator<Argument> argumentIterator() {
- return new Iterator<Argument>() {
+ return new Iterator<>() {
private final InstructionIterator instructionIterator = entryBlock().iterator();
private Argument next = instructionIterator.next().asArgument();
@@ -1178,6 +1178,10 @@
};
}
+ public Iterable<Argument> arguments() {
+ return () -> argumentIterator();
+ }
+
public List<Value> collectArguments() {
return collectArguments(false);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index fac1512..e19b240 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -243,8 +243,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java
index 3b51d7d..37af806 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Inc.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java
@@ -71,7 +71,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Inc is inserted after load/store insertion, after register allocation.
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index c9039c1..91df093 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -164,8 +164,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 18401ac..90bf26a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -210,9 +210,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 19b2929..0ea0546 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -107,9 +107,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index d54a445..4e3f1ce 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -250,8 +250,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
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 f9072c0..9da772e 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
@@ -1567,7 +1567,7 @@
public abstract ConstraintWithTarget inliningConstraint(
InliningConstraints inliningConstraints, ProgramMethod context);
- public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
+ public abstract void insertLoadAndStores(LoadStoreHelper helper);
public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
assert outValue == null || !outValue.getType().isReferenceType();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index de5ce5a..8c2287b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -285,8 +285,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 6aa12e5..b284aa5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -241,13 +241,13 @@
if (arguments().isEmpty()) {
return false;
}
- Value current = getFirstArgument();
- if (!current.isArgument()) {
+ Argument current = getFirstArgument().getDefinitionOrNull(Instruction::isArgument);
+ if (current == null) {
return false;
}
for (int i = 1; i < arguments().size(); i++) {
- Value next = getArgument(i);
- if (current.getNextConsecutive() != next) {
+ Argument next = getArgument(i).getDefinitionOrNull(Instruction::isArgument);
+ if (current.getNext() != next) {
return false;
}
current = next;
@@ -317,5 +317,5 @@
* Subclasses must implement load and store handling and make sure to deal with a null out-value
*/
@Override
- public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
+ public abstract void insertLoadAndStores(LoadStoreHelper helper);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index a3f258c..7e03629 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -187,14 +187,14 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Essentially the same as InvokeMethod but with call site's method proto
// instead of a static called method.
- helper.loadInValues(this, it);
+ helper.loadInValues(this);
if (getCallSite().methodProto.returnType.isVoidType()) {
return;
}
- helper.storeOrPopOutValue(getCallSite().methodProto.returnType, this, it);
+ helper.storeOrPopOutValue(getCallSite().methodProto.returnType, this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c1a5688..e15b67c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -265,12 +265,12 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
if (getReturnType().isVoidType()) {
return;
}
- helper.storeOrPopOutValue(getReturnType(), this, it);
+ helper.storeOrPopOutValue(getReturnType(), this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index b4256d2..bb1400e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -101,9 +101,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOrPopOutValue(type, this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOrPopOutValue(type, this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index ac3865d..d9d2c52 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -91,7 +91,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Nothing to do. This is only hit because loads and stores are insert for phis.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 430f8a4..0982912 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -120,8 +120,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index a1c5475..24a777f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -121,7 +121,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable(ERROR_MESSAGE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 4d73577..9d12798 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -101,8 +101,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 1bd40d4..0eb198e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -155,9 +155,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
index 3b44907..bdaeb21 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
@@ -139,7 +139,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw cfUnsupported();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index cd21fbc..33a0b7d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -114,7 +114,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable(ERROR_MESSAGE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index f946940..a3f05e0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -125,8 +125,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
index cafceff..bc3d425 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -130,8 +130,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 483aa87..2a98b81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -96,7 +96,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// JVM has no Not instruction, they should be replaced by "Load -1, Xor" before building CF.
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 00554e8..92c7745 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -121,7 +121,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable("This IR must not be inserted before load and store insertion.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
index 7ab1c92..0876c64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
@@ -114,9 +114,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
index d18a410..053384e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
@@ -56,7 +56,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable("We never write cf code with resource numbers");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 4e89ab8..b263cb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -58,6 +58,10 @@
return !isReturnVoid();
}
+ public Value getReturnValueOrNull() {
+ return hasReturnValue() ? returnValue() : null;
+ }
+
public Value returnValue() {
assert !isReturnVoid();
return inValues.get(0);
@@ -129,13 +133,18 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
if (!isReturnVoid()) {
- helper.loadInValues(this, it);
+ helper.loadInValues(this);
}
}
@Override
+ public boolean isAllowedAfterThrowingInstruction() {
+ return true;
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
builder.add(
isReturnVoid() ? new CfReturnVoid() : new CfReturn(ValueType.fromType(getReturnType())),
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b7a26d9..aed2b5e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -213,8 +213,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index b201864..dc29a40 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -230,8 +230,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 0ace979..552466d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -92,7 +92,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable("This IR must not be inserted before load and store insertion.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
index 506cb80..9968963 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
@@ -130,7 +130,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 3e826c8..097dda3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -98,7 +98,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Intentionally empty. Swap is a stack operation.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index ab5db40..06bfeaa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -83,8 +83,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
index 3561840..b01312c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
@@ -82,7 +82,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
// Non-materializing so no stack values are needed.
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 9c65c4e..daf2e77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -52,9 +52,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- helper.loadInValues(this, it);
- helper.storeOutValue(this, it);
+ public void insertLoadAndStores(LoadStoreHelper helper) {
+ helper.loadInValues(this);
+ helper.storeOutValue(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
index e9ded3f..bd527d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
@@ -88,7 +88,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ public void insertLoadAndStores(LoadStoreHelper helper) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 2b027ab..d782e2b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -12,7 +12,6 @@
import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
@@ -33,7 +32,6 @@
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.ObjectUtils;
@@ -180,8 +178,6 @@
private LinkedList<Phi> phiUsers = new LinkedList<>();
private Set<Phi> uniquePhiUsers = null;
- private Value nextConsecutive = null;
- private Value previousConsecutive = null;
private LiveIntervals liveIntervals;
private int needsRegister = -1;
private boolean isThis = false;
@@ -213,6 +209,14 @@
return definition;
}
+ @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
+ public <T extends Instruction> T getDefinitionOrNull(Predicate<Instruction> predicate) {
+ if (definition != null && predicate.test(definition)) {
+ return (T) definition;
+ }
+ return null;
+ }
+
public boolean hasAliasedValue() {
return getAliasedValue() != this;
}
@@ -306,47 +310,6 @@
end.addDebugValue(this);
}
- public void linkTo(Value other) {
- assert nextConsecutive == null || nextConsecutive == other;
- assert other.previousConsecutive == null || other.previousConsecutive == this;
- other.previousConsecutive = this;
- nextConsecutive = other;
- }
-
- public void replaceLink(Value newArgument) {
- assert isLinked();
- if (previousConsecutive != null) {
- previousConsecutive.nextConsecutive = newArgument;
- newArgument.previousConsecutive = previousConsecutive;
- previousConsecutive = null;
- }
- if (nextConsecutive != null) {
- nextConsecutive.previousConsecutive = newArgument;
- newArgument.nextConsecutive = nextConsecutive;
- nextConsecutive = null;
- }
- }
-
- public boolean isLinked() {
- return nextConsecutive != null || previousConsecutive != null;
- }
-
- public Value getStartOfConsecutive() {
- Value current = this;
- while (current.getPreviousConsecutive() != null) {
- current = current.getPreviousConsecutive();
- }
- return current;
- }
-
- public Value getNextConsecutive() {
- return nextConsecutive;
- }
-
- public Value getPreviousConsecutive() {
- return previousConsecutive;
- }
-
public boolean onlyUsedInBlock(BasicBlock block) {
if (hasPhiUsers() || hasDebugUsers()) {
return false;
@@ -374,6 +337,10 @@
return uniqueUsers().size() == 1;
}
+ public boolean hasSingleUniqueUserAndNoOtherUsers() {
+ return hasSingleUniqueUser() && !hasPhiUsers() && !hasDebugUsers();
+ }
+
public Instruction singleUniqueUser() {
assert hasSingleUniqueUser();
return firstUser();
@@ -779,15 +746,6 @@
return false;
}
- public boolean hasRegisterConstraint() {
- for (Instruction instruction : uniqueUsers()) {
- if (instruction.maxInValueRegister() != Constants.U16BIT_MAX) {
- return true;
- }
- }
- return false;
- }
-
public boolean isValueOnStack() {
return false;
}
@@ -893,11 +851,6 @@
&& getConstInstruction().asConstNumber().getRawValue() == rawValue;
}
- public boolean isConstBoolean(boolean value) {
- return isConstNumber()
- && definition.asConstNumber().getRawValue() == BooleanUtils.longValue(value);
- }
-
public boolean isConstZero() {
return isConstNumber() && definition.asConstNumber().isZero();
}
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 5422c8a..a17b3f1 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
@@ -516,7 +516,7 @@
timing.begin("Lens rewrite");
lensCodeRewriter.rewrite(code, context, methodProcessor);
timing.end();
- previous = printMethod(code, "IR after disable assertions (SSA)", previous);
+ previous = printMethod(code, "IR after lens code rewriting (SSA)", previous);
}
boolean isDebugMode = options.debug || context.isReachabilitySensitive();
@@ -592,17 +592,17 @@
}
assertionsRewriter.run(method, code, deadCodeRemover, timing);
+ previous = printMethod(code, "IR after assertions rewriter (SSA)", previous);
+
CheckNotNullConverter.runIfNecessary(appView, code);
- previous = printMethod(code, "IR after disable assertions (SSA)", previous);
+ previous = printMethod(code, "IR after check not null converter (SSA)", previous);
timing.begin("Run proto shrinking tasks");
appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
-
previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(code));
timing.end();
-
previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
if (memberValuePropagation != null) {
@@ -637,18 +637,18 @@
if (assumeInserter != null) {
assumeInserter.insertAssumeInstructions(code, timing);
+ previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
}
- previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
if (inliner != null && !isDebugMode) {
timing.begin("Inlining");
inliner.performInlining(code.context(), code, feedback, methodProcessor, timing);
timing.end();
assert code.verifyTypes(appView);
+ previous = printMethod(code, "IR after inlining (SSA)", previous);
}
- previous = printMethod(code, "IR after inlining (SSA)", previous);
if (appView.appInfo().hasLiveness()) {
// Reflection optimization 1. getClass() / forName() -> const-class
@@ -665,9 +665,9 @@
.libraryMethodOptimizer()
.optimize(code, feedback, methodProcessor, methodProcessingContext);
timing.end();
- previous = printMethod(code, "IR after class library method optimizer (SSA)", previous);
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
+ previous = printMethod(code, "IR after class library method optimizer (SSA)", previous);
}
assert code.verifyTypes(appView);
@@ -704,17 +704,16 @@
// dead code which is removed right before register allocation in performRegisterAllocation.
deadCodeRemover.run(code, timing);
assert code.isConsistentSSA(appView);
+ previous = printMethod(code, "IR after dead code removal (SSA)", previous);
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
+ previous = printMethod(code, "IR after inverting conditionals for testing (SSA)", previous);
}
- previous = printMethod(code, "IR after dead code removal (SSA)", previous);
assert code.verifyTypes(appView);
- previous = printMethod(code, "IR before class inlining (SSA)", previous);
-
if (classInliner != null) {
timing.begin("Inline classes");
// Class inliner should work before lambda merger, so if it inlines the
@@ -725,25 +724,21 @@
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
assert code.verifyTypes(appView);
+ previous = printMethod(code, "IR after class inlining (SSA)", previous);
}
- previous = printMethod(code, "IR after class inlining (SSA)", previous);
-
assert code.verifyTypes(appView);
- previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
-
// TODO(b/140766440): an ideal solution would be putting CodeOptimization for this into
// the list for primary processing only.
outliner.collectOutlineSites(code, timing);
-
assert code.verifyTypes(appView);
-
previous = printMethod(code, "IR after outline handler (SSA)", previous);
if (!code.getConversionOptions().isGeneratingLir()) {
new FilledNewArrayRewriter(appView)
.run(code, methodProcessor, methodProcessingContext, timing);
+ previous = printMethod(code, "IR after filled-new-array rewriter (SSA)", previous);
}
// Remove string switches prior to canonicalization to ensure that the constants that are
@@ -761,7 +756,7 @@
previous = printMethod(code, "IR after constant canonicalization (SSA)", previous);
new DexConstantOptimizer(appView, constantCanonicalizer)
.run(code, methodProcessor, methodProcessingContext, timing);
- previous = printMethod(code, "IR after dex constant optimization (SSA)", previous);
+ previous = printMethod(code, "IR after DEX constant optimization (SSA)", previous);
}
if (removeVerificationErrorForUnknownReturnedValues != null) {
@@ -771,12 +766,9 @@
timing.begin("Canonicalize idempotent calls");
idempotentFunctionCallCanonicalizer.canonicalize(code);
timing.end();
-
previous =
printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
- previous = printMethod(code, "IR after argument type logging (SSA)", previous);
-
assert code.verifyTypes(appView);
deadCodeRemover.run(code, timing);
@@ -810,18 +802,17 @@
code.removeRedundantBlocks();
timing.end();
assert code.isConsistentSSA(appView);
+ previous = printMethod(code, "IR after removing assume instructions (SSA)", previous);
// TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules.
new MoveResultRewriter(appView).run(code, methodProcessor, methodProcessingContext, timing);
+ previous = printMethod(code, "IR after move result rewriter (SSA)", previous);
}
// Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
assert code.verifyNoNullabilityBottomTypes();
assert code.verifyTypes(appView);
- previous =
- printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
-
previous = printMethod(code, "Optimized IR (SSA)", previous);
timing.begin("Finalize IR");
finalizeIR(code, feedback, bytecodeMetadataProviderBuilder.build(), timing, previous);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
index c971967..c7c4533 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -71,7 +71,8 @@
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
assert deadCodeRemover.verifyNoDeadCode(code);
timing.begin("Allocate registers");
- LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
+ LinearScanRegisterAllocator registerAllocator =
+ new LinearScanRegisterAllocator(appView, code, timing);
registerAllocator.allocateRegisters();
timing.end();
TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
index a6bbd64..24ab29b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
@@ -53,11 +53,14 @@
passes.add(new BranchSimplifier(appView));
passes.add(new SplitBranch(appView));
passes.add(new RedundantConstNumberRemover(appView));
- if (!appView.options().debug) {
+ if (appView.options().isRelease()) {
passes.add(new RedundantFieldLoadAndStoreElimination(appView));
}
passes.add(new BinopRewriter(appView));
passes.add(new ServiceLoaderRewriter(appView));
+ if (appView.options().isRelease()) {
+ passes.add(new SplitReturnRewriter(appView));
+ }
return new CodeRewriterPassCollection(passes);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java
new file mode 100644
index 0000000..ee27ea1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2024, 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.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Phi;
+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.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Set;
+
+public class SplitReturnRewriter extends CodeRewriterPass<AppInfo> {
+
+ public SplitReturnRewriter(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getRewriterId() {
+ return "SplitReturnRewriter";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ // Disable in tests that need dead switches to be left behind.
+ assert options.isRelease();
+ return appView.options().getTestingOptions().enableDeadSwitchCaseElimination;
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean changed = false;
+ boolean hasUnreachableBlocks = false;
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+ Deque<BasicBlock> worklist = new ArrayDeque<>(code.computeNormalExitBlocks());
+ while (!worklist.isEmpty()) {
+ BasicBlock block = worklist.removeFirst();
+ Return returnInstruction = block.entry().asReturn();
+ if (returnInstruction == null) {
+ continue;
+ }
+ Value returnValue = returnInstruction.getReturnValueOrNull();
+ IntList predecessorsToRemove = new IntArrayList();
+ for (int predecessorIndex = 0;
+ predecessorIndex < block.getPredecessors().size();
+ predecessorIndex++) {
+ BasicBlock predecessor = block.getPredecessor(predecessorIndex);
+ if (!predecessor.exit().isGoto()) {
+ continue;
+ }
+ if (predecessor.exit().asGoto().getTarget() != block) {
+ assert predecessor.hasCatchSuccessor(block);
+ continue;
+ }
+ if (block.hasCatchHandlers()) {
+ for (BasicBlock catchHandlerBlock : block.getSuccessors()) {
+ catchHandlerBlock.getMutablePredecessors().clear();
+ }
+ block.getMutableSuccessors().clear();
+ block.clearCatchHandlers();
+ hasUnreachableBlocks = true;
+ } else {
+ assert block.getSuccessors().isEmpty();
+ }
+ Value newReturnValue;
+ if (returnValue != null && returnValue.isPhi() && returnValue.getBlock() == block) {
+ newReturnValue = returnValue.asPhi().getOperand(predecessorIndex);
+ } else {
+ newReturnValue = returnValue;
+ }
+ Return newReturnInstruction =
+ Return.builder().setReturnValue(newReturnValue).setPosition(returnInstruction).build();
+ predecessor.exit().replace(newReturnInstruction);
+ predecessor.removeAllNormalSuccessors();
+ predecessorsToRemove.add(predecessorIndex);
+ worklist.add(predecessor);
+ }
+ if (!predecessorsToRemove.isEmpty()) {
+ if (predecessorsToRemove.size() == block.getPredecessors().size()) {
+ blocksToRemove.add(block);
+ for (Phi phi : block.getPhis()) {
+ for (Value operand : phi.getOperands()) {
+ operand.removePhiUser(phi);
+ }
+ }
+ if (returnValue != null) {
+ returnValue.removeUser(returnInstruction);
+ }
+ } else {
+ block.removePredecessorsByIndex(predecessorsToRemove);
+ block.removePhisByIndex(predecessorsToRemove);
+ }
+ changed = true;
+ }
+ }
+ code.removeBlocks(blocksToRemove);
+ if (hasUnreachableBlocks) {
+ AffectedValues affectedValues = code.removeUnreachableBlocks();
+ affectedValues.narrowingWithAssumeRemoval(appView, code);
+ }
+ if (changed) {
+ code.removeRedundantBlocks();
+ }
+ return CodeRewriterResult.hasChanged(changed);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
index 10960e0..44cba60 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
@@ -775,6 +775,7 @@
if (idValue == null
|| !idValue.getType().isInt()
+ || !idValue.hasSingleUniqueUserAndNoOtherUsers()
|| (toBeExtended != null && idValue != toBeExtended.idValue)) {
// Not an extension of `toBeExtended`.
return setFallthroughBlock(toBeExtended, fallthroughBlock);
@@ -808,7 +809,9 @@
private IdToTargetMapping extendWithSwitch(
IdToTargetMapping toBeExtended, IntSwitch theSwitch, BasicBlock fallthroughBlock) {
Value switchValue = theSwitch.value();
- if (!switchValue.isPhi() || (toBeExtended != null && switchValue != toBeExtended.idValue)) {
+ if (!switchValue.isPhi()
+ || !switchValue.hasSingleUniqueUserAndNoOtherUsers()
+ || (toBeExtended != null && switchValue != toBeExtended.idValue)) {
// Not an extension of `toBeExtended`.
return setFallthroughBlock(toBeExtended, fallthroughBlock);
}
@@ -830,6 +833,7 @@
private final Int2ReferenceMap<BasicBlock> mapping = new Int2ReferenceOpenHashMap<>();
private IdToTargetMapping(Phi idValue) {
+ assert idValue.hasSingleUniqueUserAndNoOtherUsers();
this.idValue = idValue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index a9ec6d2..f9d4c18 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Constants;
@@ -32,7 +33,9 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
@@ -100,22 +103,40 @@
@Override
public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
- if (!instruction.isInvoke()) {
+ // Only invokes and static field gets are backported.
+ if (!instruction.isInvoke() && !instruction.isStaticFieldGet()) {
return DesugarDescription.nothing();
}
-
- CfInvoke invoke = instruction.asInvoke();
- MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod(), context);
- if (methodProvider == null
- || appView
- .getSyntheticItems()
- .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
- return DesugarDescription.nothing();
+ if (instruction.isInvoke()) {
+ CfInvoke invoke = instruction.asInvoke();
+ MethodProvider<DexMethod> methodProvider = getProviderOrNull(invoke.getMethod(), context);
+ if (methodProvider == null
+ || appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(
+ context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
+ return DesugarDescription.nothing();
+ }
+ return desugarInstruction(invoke, methodProvider);
+ } else {
+ assert instruction.isStaticFieldGet();
+ CfStaticFieldRead staticGet = instruction.asStaticFieldGet();
+ MethodProvider<DexField> methodProvider = getProviderOrNull(staticGet.getField());
+ if (methodProvider == null
+ || appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)
+ || appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getContextType(), kinds -> kinds.API_MODEL_OUTLINE)) {
+ return DesugarDescription.nothing();
+ }
+ return desugarInstruction(staticGet, methodProvider);
}
- return desugarInstruction(invoke, methodProvider);
}
- private DesugarDescription desugarInstruction(CfInvoke invoke, MethodProvider methodProvider) {
+ private DesugarDescription desugarInstruction(
+ CfInvoke invoke, MethodProvider<DexMethod> methodProvider) {
return DesugarDescription.builder()
.setDesugarRewrite(
(position,
@@ -127,7 +148,7 @@
methodProcessingContext,
desugaringCollection,
dexItemFactory) ->
- methodProvider.rewriteInvoke(
+ methodProvider.rewriteInstruction(
position,
invoke,
appView,
@@ -137,35 +158,63 @@
.build();
}
- public static List<DexMethod> generateListOfBackportedMethods(
- AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+ private DesugarDescription desugarInstruction(
+ CfStaticFieldRead staticGet, MethodProvider<DexField> methodProvider) {
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (position,
+ freshLocalProvider,
+ localStackAllocator,
+ desugaringInfo,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ desugaringCollection,
+ dexItemFactory) ->
+ methodProvider.rewriteInstruction(
+ position,
+ staticGet,
+ appView,
+ eventConsumer,
+ methodProcessingContext,
+ localStackAllocator))
+ .build();
+ }
+
+ public static void generateListOfBackportedMethodsAndFields(
+ AndroidApp androidApp,
+ InternalOptions options,
+ ExecutorService executor,
+ Consumer<DexMethod> methods,
+ Consumer<DexField> fields)
+ throws IOException {
DexApplication app = null;
if (androidApp != null) {
app = new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
}
- return generateListOfBackportedMethods(app, options);
+ generateListOfBackportedMethodsAndFields(app, options, methods, fields);
}
- public static List<DexMethod> generateListOfBackportedMethods(
- DexApplication app, InternalOptions options) throws IOException {
- List<DexMethod> methods = new ArrayList<>();
+ public static void generateListOfBackportedMethodsAndFields(
+ DexApplication app,
+ InternalOptions options,
+ Consumer<DexMethod> methods,
+ Consumer<DexField> fields)
+ throws IOException {
options.loadMachineDesugaredLibrarySpecification(Timing.empty(), app);
TypeRewriter typeRewriter = options.getTypeRewriter();
- AppInfo appInfo = null;
- if (app != null) {
- appInfo = AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
-
- }
+ AppInfo appInfo =
+ AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
AppView<?> appView = AppView.createForD8(appInfo, typeRewriter, Timing.empty());
BackportedMethodRewriter.RewritableMethods rewritableMethods =
new BackportedMethodRewriter.RewritableMethods(appView);
- rewritableMethods.visit(methods::add);
+ rewritableMethods.visit(methods);
if (appInfo != null) {
DesugaredLibraryRetargeter desugaredLibraryRetargeter =
new DesugaredLibraryRetargeter(appView);
- desugaredLibraryRetargeter.visit(methods::add);
+ desugaredLibraryRetargeter.visit(methods);
}
- return methods;
+ rewritableMethods.visitFields(fields);
}
public static void registerAssumedLibraryTypes(InternalOptions options) {
@@ -173,10 +222,11 @@
BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
}
- private MethodProvider getMethodProviderOrNull(DexMethod method, ProgramMethod context) {
+ private MethodProvider<DexMethod> getProviderOrNull(DexMethod member, ProgramMethod context) {
+ DexMethod method = member.asDexMethod();
DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
assert original != null;
- MethodProvider provider = rewritableMethods.getProvider(original);
+ MethodProvider<DexMethod> provider = rewritableMethods.getProvider(original);
// Old versions of desugared library have in the jar file pre-desugared code. This is used
// to undesugar pre-desugared code, then the code is re-desugared with D8/R8. This is
// maintained for legacy only, recent desugared library should not be shipped with
@@ -196,14 +246,14 @@
// unit, assume that the compilation is the defining instance and no backport is needed.
DexClass clazz =
appView
- .contextIndependentDefinitionForWithResolutionResult(provider.method.holder)
+ .contextIndependentDefinitionForWithResolutionResult(provider.member.getHolderType())
.toSingleClassWithProgramOverLibrary();
if (clazz == null || !clazz.isProgramDefinition()) {
appView
.reporter()
.warning(
new IgnoredBackportMethodDiagnostic(
- provider.method,
+ provider.member,
context.getOrigin(),
MethodPosition.create(context),
appView.options().getMinApiLevel().getLevel()));
@@ -213,14 +263,24 @@
return provider;
}
+ private MethodProvider<DexField> getProviderOrNull(DexField field) {
+ MethodProvider<DexField> provider = rewritableMethods.getProvider(field);
+ return provider;
+ }
+
private static final class RewritableMethods {
private final Map<DexType, AndroidApiLevel> typeMinApi;
private final AppView<?> appView;
- // Map backported method to a provider for creating the actual target method (with code).
- private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
+ // Map backported field or method to a provider for creating the actual target method
+ // (with code).
+ private final Map<DexMethod, MethodProvider<DexMethod>> rewritableMethods =
+ new IdentityHashMap<>();
+ private final Map<DexField, MethodProvider<DexField>> rewritableFields =
+ new IdentityHashMap<>();
+ MethodProvider<DexField> singleBackportedField = null; // As there is only one now skip map.
RewritableMethods(AppView<?> appView) {
InternalOptions options = appView.options();
@@ -298,6 +358,9 @@
if (options.getMinApiLevel().isLessThan(AndroidApiLevel.V)) {
initializeAndroidVMethodProviders(factory);
}
+ if (options.getMinApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+ initializeAndroidBaklavaMethodProviders(factory);
+ }
}
private Map<DexType, AndroidApiLevel> initializeTypeMinApi(DexItemFactory factory) {
@@ -384,11 +447,15 @@
}
boolean isEmpty() {
- return rewritable.isEmpty();
+ return rewritableMethods.isEmpty() && rewritableFields.isEmpty();
}
public void visit(Consumer<DexMethod> consumer) {
- rewritable.keySet().forEach(consumer);
+ rewritableMethods.keySet().forEach(consumer);
+ }
+
+ public void visitFields(Consumer<DexField> consumer) {
+ rewritableFields.keySet().forEach(consumer);
}
private void initializeAndroidKObjectsMethodProviders(DexItemFactory factory) {
@@ -1694,6 +1761,20 @@
}
}
+ private void initializeAndroidBaklavaMethodProviders(DexItemFactory factory) {
+ // android.os.Build$VERSION
+ DexType type = factory.androidOsBuildVersionType;
+
+ // int android.os.Build$VERSION.SDK_INT_FULL
+ DexString name = factory.createString("SDK_INT_FULL");
+ DexField field = factory.createField(type, factory.intType, name);
+ addProviderForField(
+ new StaticFieldGetMethodWithForwardingGenerator(
+ field,
+ // Template code calls the method again.
+ BackportedMethods::AndroidOsBuildVersionMethods_getSdkIntFull));
+ }
+
private void initializeAndroidUMethodProviders(DexItemFactory factory) {
DexType type;
DexString name;
@@ -1815,34 +1896,50 @@
addProvider(new ThreadLocalWithInitialWithSupplierGenerator(method));
}
- private void addProvider(MethodProvider generator) {
- MethodProvider replaced = rewritable.put(generator.method, generator);
+ private void addProvider(MethodProvider<DexMethod> generator) {
+ MethodProvider<DexMethod> replaced = rewritableMethods.put(generator.member, generator);
assert replaced == null;
}
- MethodProvider getProvider(DexMethod method) {
- return rewritable.get(method);
+ MethodProvider<DexMethod> getProvider(DexMethod method) {
+ return rewritableMethods.get(method);
+ }
+
+ private void addProviderForField(MethodProvider<DexField> generator) {
+ MethodProvider<DexField> replaced = rewritableFields.put(generator.member, generator);
+ assert replaced == null;
+ assert singleBackportedField == null;
+ singleBackportedField = generator;
+ }
+
+ MethodProvider<DexField> getProvider(DexField field) {
+ if (field.isIdenticalTo(singleBackportedField.member)) {
+ return singleBackportedField;
+ }
+ assert !rewritableFields.containsKey(field);
+ return null;
}
}
- public abstract static class MethodProvider {
+ public abstract static class MethodProvider<
+ T extends DexMember<? extends DexItem, ? extends DexMember<?, ?>>> {
- final DexMethod method;
+ final T member;
- public MethodProvider(DexMethod method) {
- this.method = method;
+ public MethodProvider(T member) {
+ this.member = member;
}
- public abstract Collection<CfInstruction> rewriteInvoke(
+ public abstract Collection<CfInstruction> rewriteInstruction(
Position position,
- CfInvoke invoke,
+ CfInstruction instruction,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext,
LocalStackAllocator localStackAllocator);
}
- private static final class InvokeRewriter extends MethodProvider {
+ private static final class InvokeRewriter extends MethodProvider<DexMethod> {
private final MethodInvokeRewriter rewriter;
@@ -1852,15 +1949,15 @@
}
@Override
- public Collection<CfInstruction> rewriteInvoke(
+ public Collection<CfInstruction> rewriteInstruction(
Position position,
- CfInvoke invoke,
+ CfInstruction instruction,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext,
LocalStackAllocator localStackAllocator) {
Collection<CfInstruction> instructions =
- rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator);
+ rewriter.rewrite(instruction.asInvoke(), appView.dexItemFactory(), localStackAllocator);
if (position == null) {
return instructions;
}
@@ -1868,7 +1965,7 @@
CfLabel start = new CfLabel();
CfLabel end = new CfLabel();
Position inlinePosition =
- SourcePosition.builder().setCallerPosition(position).setMethod(method).setLine(0).build();
+ SourcePosition.builder().setCallerPosition(position).setMethod(member).setLine(0).build();
instructionsWithPositions.add(start);
instructionsWithPositions.add(new CfPosition(start, inlinePosition));
instructionsWithPositions.addAll(instructions);
@@ -1878,7 +1975,7 @@
}
}
- private static class MethodGenerator extends MethodProvider {
+ private static class MethodGenerator extends MethodProvider<DexMethod> {
private final TemplateMethodFactory factory;
@@ -1897,9 +1994,9 @@
}
@Override
- public Collection<CfInstruction> rewriteInvoke(
+ public Collection<CfInstruction> rewriteInstruction(
Position position,
- CfInvoke invoke,
+ CfInstruction instruction,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext,
@@ -1927,14 +2024,14 @@
Code code = generateTemplateMethod(appView.dexItemFactory(), methodSig);
if (appView.options().hasMappingFileSupport()) {
return code.getCodeAsInlining(
- methodSig, true, method, false, appView.dexItemFactory());
+ methodSig, true, member, false, appView.dexItemFactory());
}
return code;
}));
}
public DexProto getProto(DexItemFactory itemFactory) {
- return method.proto;
+ return member.getProto();
}
public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
@@ -1957,7 +2054,7 @@
@Override
public DexProto getProto(DexItemFactory factory) {
- return method.getProto().prependParameter(receiverType, factory);
+ return member.getProto().prependParameter(receiverType, factory);
}
}
@@ -2008,9 +2105,9 @@
}
@Override
- public Collection<CfInstruction> rewriteInvoke(
+ public Collection<CfInstruction> rewriteInstruction(
Position position,
- CfInvoke invoke,
+ CfInstruction instruction,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext,
@@ -2172,4 +2269,65 @@
public abstract Collection<CfInstruction> rewrite(
CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator);
}
+
+ // Generator for backports of static field gets which will again read the field they backport in
+ // the backport code. So using BACKPORT_WITH_FORWARDING as such backports cannot not go through
+ // backporting again as that would cause an infinite backporting loop.
+ private static class StaticFieldGetMethodWithForwardingGenerator
+ extends MethodProvider<DexField> {
+
+ private final TemplateMethodFactory factory;
+
+ StaticFieldGetMethodWithForwardingGenerator(DexField field, TemplateMethodFactory factory) {
+ super(field);
+ this.factory = factory;
+ }
+
+ @Override
+ public Collection<CfInstruction> rewriteInstruction(
+ Position position,
+ CfInstruction instruction,
+ AppView<?> appView,
+ BackportedMethodDesugaringEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext,
+ LocalStackAllocator localStackAllocator) {
+ ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext);
+ eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext());
+ return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+ }
+
+ protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+ return naming.BACKPORT_WITH_FORWARDING;
+ }
+
+ private ProgramMethod getSyntheticMethod(
+ AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+ return appView
+ .getSyntheticItems()
+ .createMethod(
+ this::getSyntheticKind,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ // As this is forwarding to the field read set the API level accordingly to
+ // ensure API outlining when needed.
+ .setApiLevelForCode(
+ appView.apiLevelCompute().computeApiLevelForLibraryReference(member))
+ .setProto(getProto(appView.dexItemFactory()))
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ methodSig ->
+ generateTemplateMethod(appView.dexItemFactory(), methodSig)));
+ }
+
+ public DexProto getProto(DexItemFactory itemFactory) {
+ // Proto for the method replacing the field read.
+ return itemFactory.createProto(member.getType());
+ }
+
+ public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
+ return factory.create(dexItemFactory, method);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 20f1dda..ace4c1e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -221,7 +221,7 @@
@Override
public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
- // Intentionally empty. Added to the program using ProgramAdditions.
+ methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer);
}
@Override
@@ -232,7 +232,7 @@
@Override
public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
- methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index e3cbc07..4d0f768 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -153,10 +153,13 @@
if (target == null) {
target = appInfo.lookupDirectTarget(method, context, appView, appInfo);
}
- assert target == null
- || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
- || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
- || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+ assert target == null
+ // TODO(b/366932318): We should disallow staticizing of methods called from lambdas
+ // or update the implHandle accordingly.
+ || (implHandle.type.isInvokeInstance()
+ && (isInstanceMethod(target) || target.getAccessFlags().isStatic()))
+ || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+ || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
return target;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 29259c9..259234e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -95,7 +95,13 @@
private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
CfInstruction instruction, ProgramMethod context) {
- if (context.getDefinition().isD8R8Synthesized()) {
+ // Some backports will forward to the method/field they backport. For such synthetics run
+ // outlining. Other synthetics should not need it. And explicitly not API outlines, as that
+ // would cause infinite outlining.
+ if (context.getDefinition().isD8R8Synthesized()
+ && !appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getHolderType(), k -> k.BACKPORT_WITH_FORWARDING)) {
return appView.computedMinApiLevel();
}
DexReference reference = getReferenceFromInstruction(instruction);
@@ -194,10 +200,11 @@
ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
ProgramMethod context) {
assert instruction.isInvoke()
- || instruction.isFieldInstruction()
- || instruction.isCheckCast()
- || instruction.isInstanceOf()
- || instruction.isConstClass();
+ || instruction.isFieldInstruction()
+ || instruction.isCheckCast()
+ || instruction.isInstanceOf()
+ || instruction.isConstClass()
+ : instruction;
ProgramMethod outlinedMethod =
ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 6c73cde..005543b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -56,6 +56,7 @@
public final class BackportedMethods {
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Landroid/os/Build$VERSION;");
factory.createSynthesizedType("Ljava/lang/ArithmeticException;");
factory.createSynthesizedType("Ljava/lang/AssertionError;");
factory.createSynthesizedType("Ljava/lang/Double;");
@@ -122,6 +123,45 @@
factory.createSynthesizedType("[Ljava/util/Map$Entry;");
}
+ public static CfCode AndroidOsBuildVersionMethods_getSdkIntFull(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 0,
+ ImmutableList.of(
+ label0,
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Landroid/os/Build$VERSION;"),
+ factory.intType,
+ factory.createString("SDK_INT"))),
+ new CfConstNumber(36, ValueType.INT),
+ new CfIfCmp(IfType.GE, ValueType.INT, label2),
+ label1,
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Landroid/os/Build$VERSION;"),
+ factory.intType,
+ factory.createString("SDK_INT"))),
+ new CfConstNumber(100000, ValueType.INT),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+ new CfReturn(ValueType.INT),
+ label2,
+ new CfFrame(),
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Landroid/os/Build$VERSION;"),
+ factory.intType,
+ factory.createString("SDK_INT_FULL"))),
+ new CfReturn(ValueType.INT)),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode AssertionErrorMethods_createAssertionError(
DexItemFactory factory, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
index 8d1ede7..b257bc2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -107,6 +108,14 @@
desugaredApisSignatures.add(
classBinaryName + '#' + extraMethod.name + extraMethod.proto.toDescriptorString());
}
+ if (FORMAT_WITH_FIELD) {
+ for (DexField extraField : supportedClasses.getExtraFields()) {
+ String classBinaryName =
+ DescriptorUtils.getClassBinaryNameFromDescriptor(
+ extraField.getHolderType().descriptor.toString());
+ desugaredApisSignatures.add(classBinaryName + '#' + extraField.name);
+ }
+ }
// Write a plain text file with the desugared APIs.
desugaredApisSignatures.sort(Comparator.naturalOrder());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index db8847b..3e568da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -32,10 +32,15 @@
public class SupportedClasses {
private final Map<DexType, SupportedClass> supportedClasses;
private final List<DexMethod> extraMethods;
+ private final List<DexField> extraFields;
- SupportedClasses(Map<DexType, SupportedClass> supportedClasses, List<DexMethod> extraMethods) {
+ SupportedClasses(
+ Map<DexType, SupportedClass> supportedClasses,
+ List<DexMethod> extraMethods,
+ List<DexField> extraFields) {
this.supportedClasses = supportedClasses;
this.extraMethods = extraMethods;
+ this.extraFields = extraFields;
}
public void forEachClass(Consumer<SupportedClass> consumer) {
@@ -46,6 +51,10 @@
return extraMethods;
}
+ public List<DexField> getExtraFields() {
+ return extraFields;
+ }
+
public static class SupportedClass {
private final DexClass clazz;
@@ -199,6 +208,7 @@
Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
private List<DexMethod> extraMethods = ImmutableList.of();
+ private List<DexField> extraFields = ImmutableList.of();
ClassAnnotation getClassAnnotation(DexType type) {
SupportedClass.Builder builder = supportedClassBuilders.get(type);
@@ -282,6 +292,10 @@
this.extraMethods = extraMethods;
}
+ public void setExtraFields(List<DexField> extraFields) {
+ this.extraFields = extraFields;
+ }
+
public boolean hasOnlyExtraMethods() {
return supportedClassBuilders.isEmpty();
}
@@ -292,7 +306,7 @@
(type, classBuilder) -> {
map.put(type, classBuilder.build());
});
- return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods);
+ return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods, extraFields);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index a3ce881..d458dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -43,7 +43,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -53,6 +52,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
public class SupportedClassesGenerator {
@@ -181,7 +181,8 @@
AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
// This should depend only on machine specification and min api.
- List<DexMethod> backports = generateListOfBackportedMethods();
+ List<DexMethod> backports = new ArrayList<>();
+ generateListOfBackportedMethodsAndFields(backports::add, f -> {});
int finalApi = api;
builder.forEachClassAndMethod(
@@ -306,7 +307,9 @@
DirectMappedDexApplication implementationApplication =
new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
- List<DexMethod> backports = generateListOfBackportedMethods();
+ List<DexMethod> backports = new ArrayList<>();
+ List<DexField> backportFields = new ArrayList<>();
+ generateListOfBackportedMethodsAndFields(backports::add, backportFields::add);
for (DexProgramClass clazz : implementationApplication.classes()) {
// All emulated interfaces static and default methods are supported.
@@ -389,14 +392,24 @@
}
extraMethods.sort(Comparator.naturalOrder());
builder.setExtraMethods(extraMethods);
+
+ List<DexField> extraFields = new ArrayList<>();
+ for (DexField backport : backportFields) {
+ if (implementationApplication.definitionFor(backport.getHolderType()) == null) {
+ extraFields.add(backport);
+ }
+ }
+ extraFields.sort(Comparator.naturalOrder());
+ builder.setExtraFields(extraFields);
}
}
- private List<DexMethod> generateListOfBackportedMethods() throws IOException {
- if (androidPlatformBuild) {
- return ImmutableList.of();
+ private void generateListOfBackportedMethodsAndFields(
+ Consumer<DexMethod> methods, Consumer<DexField> fields) throws IOException {
+ if (!androidPlatformBuild) {
+ BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+ appForMax, options, methods, fields);
}
- return BackportedMethodRewriter.generateListOfBackportedMethods(appForMax, options);
}
private void registerMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
index 3743f0c..2084f67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -42,45 +42,9 @@
public final class RecordCfMethods {
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
- factory.createSynthesizedType("Ljava/util/Arrays;");
factory.createSynthesizedType("[Ljava/lang/Object;");
factory.createSynthesizedType("[Ljava/lang/String;");
}
-
- public static CfCode RecordMethods_hashCode(DexItemFactory factory, DexMethod method) {
- CfLabel label0 = new CfLabel();
- CfLabel label1 = new CfLabel();
- return new CfCode(
- method.holder,
- 2,
- 2,
- ImmutableList.of(
- label0,
- new CfConstNumber(31, ValueType.INT),
- new CfLoad(ValueType.OBJECT, 1),
- new CfInvoke(
- 184,
- factory.createMethod(
- factory.createType("Ljava/util/Arrays;"),
- factory.createProto(factory.intType, factory.createType("[Ljava/lang/Object;")),
- factory.createString("hashCode")),
- false),
- new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
- new CfLoad(ValueType.OBJECT, 0),
- new CfInvoke(
- 182,
- factory.createMethod(
- factory.objectType,
- factory.createProto(factory.intType),
- factory.createString("hashCode")),
- false),
- new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
- new CfReturn(ValueType.INT),
- label1),
- ImmutableList.of(),
- ImmutableList.of());
- }
-
public static CfCode RecordMethods_toString(DexItemFactory factory, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
index fb6b45b..773189b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
@@ -4,24 +4,29 @@
package com.android.tools.r8.ir.desugar.records;
-import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup;
-import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap;
import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -31,40 +36,56 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.ProgramAdditions;
import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
-import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqCfCodeProvider;
import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordHashCfCodeProvider;
import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.BiFunction;
import org.objectweb.asm.Opcodes;
public class RecordInstructionDesugaring implements CfInstructionDesugaring {
+ private static final int MAX_FIELDS_FOR_OUTLINE = 32;
+
final AppView<?> appView;
final DexItemFactory factory;
+ private final List<DexType> orderedSharedTypes;
private final DexProto recordToStringHelperProto;
- private final DexProto recordHashCodeHelperProto;
public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
+ public static final String HASH_CODE_METHOD_NAME = "$record$hashCode";
public static final String EQUALS_RECORD_METHOD_NAME = "$record$equals";
RecordInstructionDesugaring(AppView<?> appView) {
this.appView = appView;
factory = appView.dexItemFactory();
+ orderedSharedTypes =
+ ImmutableList.of(
+ factory.booleanType,
+ factory.intType,
+ factory.longType,
+ factory.floatType,
+ factory.doubleType,
+ factory.objectType);
recordToStringHelperProto =
factory.createProto(
factory.stringType, factory.objectArrayType, factory.classType, factory.stringType);
- recordHashCodeHelperProto =
- factory.createProto(factory.intType, factory.classType, factory.objectArrayType);
}
public static RecordInstructionDesugaring create(AppView<?> appView) {
@@ -82,7 +103,8 @@
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
RecordCfMethods.registerSynthesizedCodeReferences(factory);
RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
- RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+ RecordEqCfCodeProvider.registerSynthesizedCodeReferences(factory);
+ RecordHashCfCodeProvider.registerSynthesizedCodeReferences(factory);
}
@Override
@@ -107,11 +129,16 @@
RecordInstructionDesugaringEventConsumer eventConsumer) {
RecordInvokeDynamic recordInvokeDynamic =
parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView);
- if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
- || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
+ if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
return;
}
+ if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
+ if (!shouldOutlineMethods(recordInvokeDynamic.getRecordClass())) {
+ ensureHashCodeMethod(recordInvokeDynamic, programAdditions, context, eventConsumer);
+ }
+ return;
+ }
if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) {
ensureEqualsRecord(recordInvokeDynamic, programAdditions, context, eventConsumer);
return;
@@ -157,6 +184,7 @@
dexItemFactory) ->
desugarInvokeDynamicOnRecord(
instruction.asInvokeDynamic(),
+ freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
@@ -184,6 +212,7 @@
@SuppressWarnings("ReferenceEquality")
private List<CfInstruction> desugarInvokeDynamicOnRecord(
CfInvokeDynamic invokeDynamic,
+ FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
@@ -201,6 +230,7 @@
if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
return desugarInvokeRecordHashCode(
recordInvokeDynamic,
+ freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
@@ -212,12 +242,6 @@
throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
}
- private ProgramMethod synthesizeEqualsRecordMethod(
- DexProgramClass clazz, DexMethod getFieldsAsObjects, DexMethod method) {
- return synthesizeMethod(
- clazz, new RecordEqualsCfCodeProvider(appView, clazz.type, getFieldsAsObjects), method);
- }
-
private ProgramMethod synthesizeGetFieldsAsObjectsMethod(
DexProgramClass clazz, DexField[] fields, DexMethod method) {
return synthesizeMethod(
@@ -243,19 +267,43 @@
return result;
}
+ private void ensureHashCodeMethod(
+ RecordInvokeDynamic recordInvokeDynamic,
+ ProgramAdditions programAdditions,
+ ProgramMethod context,
+ RecordInstructionDesugaringEventConsumer eventConsumer) {
+ DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
+ DexMethod method = hashCodeMethod(clazz.type);
+ assert clazz.lookupProgramMethod(method) == null;
+ Pair<List<DexField>, List<DexType>> pair = sortedInstanceFields(clazz.instanceFields());
+ ProgramMethod hashCodeHelperMethod =
+ programAdditions.ensureMethod(
+ method,
+ () ->
+ synthesizeMethod(
+ clazz,
+ new RecordHashCfCodeProvider(appView, clazz.type, pair.getFirst(), false),
+ method));
+ eventConsumer.acceptRecordHashCodeHelperMethod(hashCodeHelperMethod, context);
+ }
+
private void ensureEqualsRecord(
RecordInvokeDynamic recordInvokeDynamic,
ProgramAdditions programAdditions,
ProgramMethod context,
RecordInstructionDesugaringEventConsumer eventConsumer) {
- DexMethod getFieldsAsObjects =
- ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
DexMethod method = equalsRecordMethod(clazz.type);
assert clazz.lookupProgramMethod(method) == null;
+ Pair<List<DexField>, List<DexType>> pair = sortedInstanceFields(clazz.instanceFields());
ProgramMethod equalsHelperMethod =
programAdditions.ensureMethod(
- method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method));
+ method,
+ () ->
+ synthesizeMethod(
+ clazz,
+ new RecordEqCfCodeProvider(appView, clazz.type, pair.getFirst()),
+ method));
eventConsumer.acceptRecordEqualsHelperMethod(equalsHelperMethod, context);
}
@@ -277,6 +325,11 @@
return method;
}
+ private DexMethod hashCodeMethod(DexType holder) {
+ return factory.createMethod(
+ holder, factory.createProto(factory.intType), HASH_CODE_METHOD_NAME);
+ }
+
private DexMethod getFieldsAsObjectsMethod(DexType holder) {
return factory.createMethod(
holder, factory.createProto(factory.objectArrayType), GET_FIELDS_AS_OBJECTS_METHOD_NAME);
@@ -307,34 +360,110 @@
.disableAndroidApiLevelCheck());
}
+ private boolean shouldOutlineMethods(DexClass recordClass) {
+ return recordClass.instanceFields().size() < MAX_FIELDS_FOR_OUTLINE;
+ }
+
+ public static int fixedHashCodeForEmptyRecord() {
+ return 0;
+ }
+
+ // Answers an ordered map of the fields with the type for the hashCode proto.
+ private Pair<List<DexField>, List<DexType>> sortedInstanceFields(
+ List<DexEncodedField> instanceFields) {
+ Map<DexType, List<DexField>> temp = new IdentityHashMap<>();
+ for (DexEncodedField instanceField : instanceFields) {
+ DexType protoType =
+ instanceField.getType().isBooleanType()
+ ? instanceField.getType()
+ : ValueType.fromDexType(instanceField.getType()).toDexType(factory);
+ temp.computeIfAbsent(protoType, ignored -> new ArrayList<>())
+ .add(instanceField.getReference());
+ }
+ Pair<List<DexField>, List<DexType>> pair = new Pair<>(new ArrayList<>(), new ArrayList<>());
+ for (DexType orderedSharedType : orderedSharedTypes) {
+ List<DexField> dexFields = temp.get(orderedSharedType);
+ if (dexFields != null) {
+ for (DexField dexField : dexFields) {
+ pair.getFirst().add(dexField);
+ pair.getSecond().add(orderedSharedType);
+ }
+ }
+ }
+ assert pair.getFirst().size() == instanceFields.size();
+ assert pair.getSecond().size() == instanceFields.size();
+ return pair;
+ }
+
private List<CfInstruction> desugarInvokeRecordHashCode(
RecordInvokeDynamic recordInvokeDynamic,
+ FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
RecordInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext) {
- localStackAllocator.allocateLocalStack(1);
- DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
- assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
ArrayList<CfInstruction> instructions = new ArrayList<>();
- instructions.add(new CfStackInstruction(Dup));
- instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
- instructions.add(new CfStackInstruction(Swap));
- instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
- ProgramMethod programMethod =
- synthesizeRecordHelper(
- recordHashCodeHelperProto,
- RecordCfMethods::RecordMethods_hashCode,
- methodProcessingContext);
- eventConsumer.acceptRecordHashCodeHelperMethod(programMethod, context);
- instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+ DexProgramClass recordClass = recordInvokeDynamic.getRecordClass();
+ DexMethod hashCodeMethod = hashCodeMethod(recordInvokeDynamic.getRecordType());
+ if (recordClass.instanceFields().isEmpty()) {
+ localStackAllocator.allocateLocalStack(1);
+ instructions.add(new CfConstNumber(fixedHashCodeForEmptyRecord(), ValueType.INT));
+ return instructions;
+ }
+ if (shouldOutlineMethods(recordClass)) {
+ Pair<List<DexField>, List<DexType>> sortedFields =
+ sortedInstanceFields(recordClass.instanceFields());
+ int freshLocal = freshLocalProvider.getFreshLocal(ValueType.OBJECT.requiredRegisters());
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ instructions.add(new CfStore(ValueType.OBJECT, freshLocal));
+ DexField field = sortedFields.getFirst().get(0);
+ int extraStack = field.getType().getRequiredRegisters();
+ instructions.add(new CfInstanceFieldRead(field));
+ for (int i = 1; i < sortedFields.getFirst().size(); i++) {
+ instructions.add(new CfLoad(ValueType.OBJECT, freshLocal));
+ field = sortedFields.getFirst().get(i);
+ instructions.add(new CfInstanceFieldRead(field));
+ extraStack += field.getType().getRequiredRegisters();
+ }
+ localStackAllocator.allocateLocalStack(extraStack);
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.RECORD_HELPER,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setProto(
+ factory.createProto(
+ factory.intType, new ArrayList<>(sortedFields.getSecond())))
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ methodSig ->
+ new RecordHashCfCodeProvider(
+ appView,
+ recordClass.getType(),
+ sortedFields.getFirst(),
+ true)
+ .generateCfCode())
+ .disableAndroidApiLevelCheck());
+ eventConsumer.acceptRecordHashCodeHelperMethod(method, context);
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+ } else {
+ assert recordClass.lookupProgramMethod(hashCodeMethod) != null;
+ instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, hashCodeMethod, false));
+ }
return instructions;
}
private List<CfInstruction> desugarInvokeRecordEquals(RecordInvokeDynamic recordInvokeDynamic) {
- DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordType());
- assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(equalsRecord) != null;
- return Collections.singletonList(new CfInvoke(Opcodes.INVOKESPECIAL, equalsRecord, false));
+ ArrayList<CfInstruction> instructions = new ArrayList<>();
+ DexProgramClass recordClass = recordInvokeDynamic.getRecordClass();
+ DexMethod equalsMethod = equalsRecordMethod(recordInvokeDynamic.getRecordType());
+ assert recordClass.lookupProgramMethod(equalsMethod) != null;
+ instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, equalsMethod, false));
+ return instructions;
}
private List<CfInstruction> desugarInvokeRecordToString(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 10988dc..471dbf6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -7,11 +7,13 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +40,11 @@
returnedTypes.add(returnValue.getDynamicType(appView));
}
}
- return DynamicType.join(appView, returnedTypes);
+ DynamicType dynamicReturnType = DynamicType.join(appView, returnedTypes);
+ AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+ if (assumeInfoCollection.get(method).getAssumeType().getNullability().isDefinitelyNotNull()) {
+ return dynamicReturnType.withNullability(Nullability.definitelyNotNull());
+ }
+ return dynamicReturnType;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 17eb9a8..b58048e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -746,17 +746,20 @@
monitorExitBlock.close(null);
}
- for (BasicBlock block : code.blocks) {
+ BasicBlockIterator blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
if (block.exit().isReturn()) {
- // Since return instructions are not allowed after a throwing instruction in a block
- // with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
- // care of ensuring that all return blocks have no throwing instructions.
- assert !block.canThrow();
-
InstructionListIterator instructionIterator =
block.listIterator(block.getInstructions().size() - 1);
- instructionIterator.setInsertionPosition(Position.syntheticNone());
- instructionIterator.add(new Monitor(MonitorType.EXIT, lockValue));
+ if (block.canThrow()) {
+ BasicBlock splitBlock =
+ instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+ instructionIterator = splitBlock.listIterator();
+ }
+ Monitor monitorExit = new Monitor(MonitorType.EXIT, lockValue);
+ monitorExit.setPosition(Position.syntheticNone());
+ instructionIterator.add(monitorExit);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 39b5c5c..7768041 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -157,12 +157,11 @@
}
@Override
- @SuppressWarnings("EqualsGetClass")
public boolean equals(Object other) {
if (this == other) {
return true;
}
- if (other == null || getClass() != other.getClass()) {
+ if (!(other instanceof ArraySlotWithConstantIndex)) {
return false;
}
ArraySlotWithConstantIndex arraySlot = (ArraySlotWithConstantIndex) other;
@@ -190,12 +189,11 @@
}
@Override
- @SuppressWarnings("EqualsGetClass")
public boolean equals(Object other) {
if (this == other) {
return true;
}
- if (other == null || getClass() != other.getClass()) {
+ if (!(other instanceof ArraySlotWithValueIndex)) {
return false;
}
ArraySlotWithValueIndex arraySlot = (ArraySlotWithValueIndex) other;
@@ -220,13 +218,15 @@
}
@Override
- @SuppressWarnings("ReferenceEquality")
public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
if (!(other instanceof FieldAndObject)) {
return false;
}
FieldAndObject o = (FieldAndObject) other;
- return o.object == object && o.field == field;
+ return o.object == object && o.field.isIdenticalTo(field);
}
}
@@ -345,14 +345,13 @@
return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition());
}
- @SuppressWarnings("ReferenceEquality")
private DexClassAndField resolveField(DexField field) {
if (appView.enableWholeProgramOptimizations()) {
SingleFieldResolutionResult resolutionResult =
appView.appInfo().withLiveness().resolveField(field).asSingleFieldResolutionResult();
return resolutionResult != null ? resolutionResult.getResolutionPair() : null;
}
- if (field.getHolderType() == method.getHolderType()) {
+ if (field.getHolderType().isIdenticalTo(method.getHolderType())) {
return method.getHolder().lookupProgramField(field);
}
return null;
@@ -614,10 +613,10 @@
return activeState.markClassAsInitialized(type);
}
- @SuppressWarnings("ReferenceEquality")
private void markMostRecentInitClassForRemoval(DexType initializedType) {
InitClass mostRecentInitClass = activeState.getMostRecentInitClass();
- if (mostRecentInitClass != null && mostRecentInitClass.getClassValue() == initializedType) {
+ if (mostRecentInitClass != null
+ && mostRecentInitClass.getClassValue().isIdenticalTo(initializedType)) {
instructionsToRemove
.computeIfAbsent(mostRecentInitClass.getBlock(), ignoreKey(Sets::newIdentityHashSet))
.add(mostRecentInitClass);
@@ -1113,10 +1112,9 @@
clearMostRecentStaticFieldWrites();
}
- @SuppressWarnings("ReferenceEquality")
public void clearMostRecentInstanceFieldWrite(DexField field) {
if (mostRecentInstanceFieldWrites != null) {
- mostRecentInstanceFieldWrites.keySet().removeIf(key -> key.field == field);
+ mostRecentInstanceFieldWrites.keySet().removeIf(key -> key.field.isIdenticalTo(field));
}
}
@@ -1329,10 +1327,9 @@
}
}
- @SuppressWarnings("ReferenceEquality")
public void removeNonFinalInstanceFields(DexField field) {
if (nonFinalInstanceFieldValues != null) {
- nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+ nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field.isIdenticalTo(field));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
index 6262a76..b0af2ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
@@ -14,8 +14,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.CheckCast;
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.Return;
import com.android.tools.r8.ir.code.Value;
@@ -191,12 +194,26 @@
if (returnsNeedingCast.isEmpty()) {
return;
}
- InstructionListIterator iterator = code.instructionListIterator();
- while (iterator.hasNext()) {
- Return returnInstruction = iterator.next().asReturn();
+ BasicBlockIterator blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ Return returnInstruction = block.exit().asReturn();
if (returnInstruction == null) {
continue;
}
+
+ BasicBlock insertionBlock;
+ if (block.hasCatchHandlers() && block.canThrow()) {
+ InstructionListIterator instructionIterator = block.listIterator();
+ instructionIterator.nextUntil(Instruction::instructionTypeCanThrow);
+ insertionBlock =
+ instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options());
+ } else {
+ insertionBlock = block;
+ }
+ InstructionListIterator instructionIterator =
+ insertionBlock.listIterator(insertionBlock.size() - 1);
+
DexType returnType = context.getReturnType();
Value returnValue = returnInstruction.returnValue();
CheckCast checkCast =
@@ -207,12 +224,10 @@
.setCastType(returnType)
.setPosition(returnInstruction.getPosition())
.build();
- iterator.replaceCurrentInstruction(checkCast);
- iterator.add(
- Return.builder()
- .setPosition(returnInstruction.getPosition())
- .setReturnValue(checkCast.outValue())
- .build());
+ instructionIterator.add(checkCast);
+ Instruction next = instructionIterator.next();
+ assert next.isReturn();
+ next.replaceValue(0, checkCast.outValue());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index dee6c67..1d4a1a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -202,7 +202,7 @@
invoke,
affectedValues,
(s, i, j) ->
- i <= 0 && i <= j && j <= s.length() ? s.substring(i, j, dexItemFactory) : null);
+ 0 <= i && i <= j && j <= s.length() ? s.substring(i, j, dexItemFactory) : null);
}
break;
case 't':
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
index 5cdcc6b..89469c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -385,13 +385,16 @@
boolean canRemoveIfLastAndNoLoop =
!isLoopingOnPath(root, currentNode, munchingState)
&& currentNode.getSuccessors().isEmpty();
+ Instruction instruction = currentNode.asAppendNode().getInstruction();
boolean hasKnownArgumentOrCannotBeObserved =
appendNode.hasConstantOrNonConstantArgument()
- || !munchingState.oracle.canObserveStringBuilderCall(
- currentNode.asAppendNode().getInstruction());
+ || !munchingState.oracle.canObserveStringBuilderCall(instruction);
+ // R8 would need to check for range overflow if removing append with sub arrays.
+ boolean canRemoveNonSub = !munchingState.oracle.isAppendWithSubArray(instruction);
if (canRemoveIfNoInspectionOrMaterializing
&& canRemoveIfLastAndNoLoop
- && hasKnownArgumentOrCannotBeObserved) {
+ && hasKnownArgumentOrCannotBeObserved
+ && canRemoveNonSub) {
removeNode = true;
}
} else if (currentNode.isInitNode()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
index 2c43b60..d9428ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import java.util.List;
@@ -39,6 +38,8 @@
boolean isAppend(Instruction instruction);
+ boolean isAppendWithSubArray(Instruction instruction);
+
boolean canObserveStringBuilderCall(Instruction instruction);
boolean isInit(Instruction instruction);
@@ -182,7 +183,11 @@
|| factory.stringBufferMethods.isAppendMethod(invokedMethod);
}
- public boolean isAppendWithSubArray(InvokeMethodWithReceiver instruction) {
+ @Override
+ public boolean isAppendWithSubArray(Instruction instruction) {
+ if (!instruction.isInvokeMethodWithReceiver()) {
+ return false;
+ }
DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
return factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
|| factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2e2dd39..185cce6 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -50,6 +50,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -112,6 +113,21 @@
ALLOW_ARGUMENT_REUSE_U8BIT_RETRY,
ALLOW_ARGUMENT_REUSE_U16BIT;
+ int getMaxRegisterNumber() {
+ switch (this) {
+ case ALLOW_ARGUMENT_REUSE_U4BIT:
+ return Constants.U4BIT_MAX;
+ case ALLOW_ARGUMENT_REUSE_U8BIT:
+ case ALLOW_ARGUMENT_REUSE_U8BIT_REFINEMENT:
+ case ALLOW_ARGUMENT_REUSE_U8BIT_RETRY:
+ return Constants.U8BIT_MAX;
+ case ALLOW_ARGUMENT_REUSE_U16BIT:
+ return Constants.U16BIT_MAX;
+ default:
+ throw new Unreachable();
+ }
+ }
+
boolean hasRegisterConstraint(LiveIntervals intervals) {
return hasRegisterConstraint(intervals.getRegisterLimit());
}
@@ -238,6 +254,13 @@
// because their values can be rematerialized.
private int[] unusedRegisters = null;
+ private final Timing timing;
+
+ Iterable<LiveIntervals> getArgumentLiveIntervals() {
+ return Iterables.transform(
+ code.arguments(), argument -> argument.outValue().getLiveIntervals());
+ }
+
// Whether or not the code has a move exception instruction. Used to pin the move exception
// register.
private boolean hasDedicatedMoveExceptionRegister() {
@@ -276,7 +299,7 @@
return !isDedicatedMoveExceptionRegisterInFirstLocalRegister();
}
- public LinearScanRegisterAllocator(AppView<?> appView, IRCode code) {
+ public LinearScanRegisterAllocator(AppView<?> appView, IRCode code, Timing timing) {
this.appView = appView;
this.code = code;
int argumentRegisters = 0;
@@ -288,13 +311,13 @@
}
}
numberOfArgumentRegisters = argumentRegisters;
+ this.timing = timing;
}
private boolean retry8BitAllocationWith4BitArgumentRegisters() {
assert mode.is8Bit();
assert numberOf4BitArgumentRegisters == 0;
- if (!options().getTestingOptions().enableRegisterAllocation8BitRefinement
- || code.context().getDefinition().getNumberOfArguments() == 0) {
+ if (code.context().getDefinition().getNumberOfArguments() == 0) {
return false;
}
numberOf4BitArgumentRegisters = computeNumberOf4BitArgumentRegisters();
@@ -334,16 +357,19 @@
@Override
public void allocateRegisters() {
// There are no linked values prior to register allocation.
- assert noLinkedValues();
assert code.isConsistentSSA(appView);
if (this.code.method().accessFlags.isBridge() && implementationIsBridge(this.code)) {
transformBridgeMethod();
}
+ timing.begin("Setup");
computeNeedsRegister();
constrainArgumentIntervals();
insertRangeInvokeMoves();
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
+ timing.end();
+ timing.begin("Allocate");
performAllocation();
+ timing.end();
assert code.isConsistentGraph(appView);
assert mode.is4Bit() || registersUsed() == 0 || unusedRegisters != null;
// Even if the method is reachability sensitive, we do not compute debug information after
@@ -857,15 +883,18 @@
assert numberOf4BitArgumentRegisters == 0 || mode.is8BitRefinement();
ArgumentReuseMode result = mode;
this.mode = mode;
-
+ timing.begin(mode.toString());
+ timing.begin("Prepare");
if (retry) {
clearRegisterAssignments();
removeSpillAndPhiMoves();
}
pinArgumentRegisters();
+ timing.end();
boolean succeeded = performLinearScan(mode);
+ timing.end();
if (succeeded) {
InsertMovesResult insertMovesResult = insertMoves();
@@ -917,7 +946,7 @@
}
}
} else {
- assert mode.is4Bit();
+ assert !mode.is16Bit();
}
switch (mode) {
@@ -986,10 +1015,7 @@
}
Reference2IntMap<LiveIntervals> originalRegisterAssignment = new Reference2IntOpenHashMap<>();
originalRegisterAssignment.defaultReturnValue(NO_REGISTER);
- for (Value current = firstArgumentValue;
- current != null;
- current = current.getNextConsecutive()) {
- LiveIntervals intervals = current.getLiveIntervals();
+ for (LiveIntervals intervals : getArgumentLiveIntervals()) {
int conservativeRealRegisterEnd = realRegisterNumberFromAllocated(intervals.getRegisterEnd());
assert !mode.hasRegisterConstraint(intervals)
|| (mode.is8BitRefinement()
@@ -1076,7 +1102,7 @@
int unadjustedRealRegisterFromAllocated(int allocated) {
assert allocated != NO_REGISTER;
assert allocated >= 0;
- if (allocated < numberOfArgumentRegisters) {
+ if (isArgumentRegister(allocated)) {
// For the |numberOfArguments| first registers map to the correct argument register.
return maxRegisterNumber - (numberOfArgumentRegisters - allocated - 1);
} else if (hasDedicatedMoveExceptionRegister()
@@ -1113,15 +1139,29 @@
private boolean performLinearScan(ArgumentReuseMode mode) {
unhandled.addAll(liveIntervals);
+ timing.begin("Prelude");
processArgumentLiveIntervals();
boolean hasInvokeRangeLiveIntervals = splitLiveIntervalsForInvokeRange();
allocateRegistersForMoveExceptionIntervals(hasInvokeRangeLiveIntervals);
+ timing.end();
+
+ timing.begin("Argument linked");
+ for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+ allocateRegistersForInvokeRangeSplits(argumentLiveIntervals);
+ }
+ timing.end();
// Go through each unhandled live interval and find a register for it.
+ timing.begin("Process all unhandled");
while (!unhandled.isEmpty()) {
assert invariantsHold(mode);
LiveIntervals unhandledInterval = unhandled.poll();
+ if (unhandledInterval.isHandled()) {
+ assert unhandledInterval.hasRegister();
+ continue;
+ }
+
setHintForDestRegOfCheckCast(unhandledInterval);
setHintToPromote2AddrInstruction(unhandledInterval);
@@ -1129,38 +1169,43 @@
// consecutive arguments now and add hints to the live intervals leading up to this
// invoke/range. This looks forward and propagate hints backwards to avoid many moves in
// connection with ranged invokes.
+ timing.begin("Linked");
allocateRegistersForInvokeRangeSplits(unhandledInterval);
- if (unhandledInterval.getRegister() != NO_REGISTER) {
+ timing.end();
+ if (unhandledInterval.hasRegister()) {
// The value itself is in the chain that has now gotten registers allocated.
continue;
}
+ timing.begin("Advance state");
advanceStateToLiveIntervals(unhandledInterval);
+ timing.end();
// Perform the actual allocation.
- if (!allocateSingleInterval(unhandledInterval)) {
+ timing.begin("Alloc single");
+ if (!allocateSingleInterval(unhandledInterval)
+ || maxRegisterNumber > mode.getMaxRegisterNumber()) {
+ timing.end();
+ timing.end();
return false;
}
-
+ timing.end();
expiredHere.clear();
}
+ timing.end();
assert invariantsHold(mode);
return true;
}
private void processArgumentLiveIntervals() {
- for (Value argumentValue = firstArgumentValue;
- argumentValue != null;
- argumentValue = argumentValue.getNextConsecutive()) {
- LiveIntervals argumentInterval = argumentValue.getLiveIntervals();
+ for (LiveIntervals argumentInterval : getArgumentLiveIntervals()) {
assert argumentInterval.hasRegister();
- unhandled.remove(argumentInterval);
+ argumentInterval.setHandled();
if (!mode.hasRegisterConstraint(argumentInterval)) {
// All the argument intervals are active in the beginning and have preallocated registers.
active.add(argumentInterval);
} else if (mode.is8BitRefinement()
- && argumentInterval.getRegister() + argumentValue.requiredRegisters()
- <= numberOf4BitArgumentRegisters) {
+ && argumentInterval.getRegisterEnd() < numberOf4BitArgumentRegisters) {
active.add(argumentInterval);
} else {
// Treat the argument interval as spilled which will require a load to a different
@@ -1173,15 +1218,16 @@
LiveIntervals split;
if (argumentInterval.numberOfUsesWithConstraint() == 1) {
// If there is only one register-constrained use, split before that one use.
- split = argumentInterval.splitBefore(use.getPosition());
+ split = argumentInterval.splitBefore(use.getPosition(), mode);
} else {
// If there are multiple register-constrained users, split right after the definition
// to make it more likely that arguments get in usable registers from the start.
// TODO(christofferqa): This is not great if there are many arguments with multiple
// constrained uses, since we fill up all the low registers immediately, making it
// likely that we will have to kick them back out before they are actually used.
- split = argumentInterval
- .splitBefore(argumentInterval.getValue().definition.getNumber() + 1);
+ split =
+ argumentInterval.splitBefore(
+ argumentInterval.getValue().definition.getNumber() + 1, mode);
}
unhandled.add(split);
}
@@ -1209,13 +1255,13 @@
MoveException moveException = block.entry().asMoveException();
LiveIntervals intervals = moveException.outValue().getLiveIntervals();
if (intervals.getValue().hasAnyUsers()) {
- LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition());
+ LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition(), mode);
unhandled.add(split);
}
if (intervals.getStart() < moveException.getNumber()) {
- intervals = intervals.splitBefore(moveException);
+ intervals = intervals.splitBefore(moveException, mode);
} else {
- unhandled.remove(intervals);
+ intervals.setHandled();
}
moveExceptionIntervals.add(intervals);
intervals.setRegister(getMoveExceptionRegister());
@@ -1237,12 +1283,12 @@
if (overlappingIntervals.getStart() == toGapPosition(invoke.getNumber())) {
invokeRangeIntervals = overlappingIntervals;
} else {
- invokeRangeIntervals = overlappingIntervals.splitBefore(invoke);
+ invokeRangeIntervals = overlappingIntervals.splitBefore(invoke, mode);
unhandled.add(invokeRangeIntervals);
}
- invokeRangeIntervals.setIsInvokeRangeIntervals();
+ invokeRangeIntervals.setIsInvokeRangeIntervals(invoke);
if (invoke.getNumber() + 1 < invokeRangeIntervals.getEnd()) {
- LiveIntervals successorIntervals = invokeRangeIntervals.splitAfter(invoke);
+ LiveIntervals successorIntervals = invokeRangeIntervals.splitAfter(invoke, mode);
unhandled.add(successorIntervals);
}
hasInvokeRangeLiveIntervals = true;
@@ -1282,7 +1328,7 @@
}
} else if (!activeIntervals.overlapsPosition(start)) {
activeIterator.remove();
- assert activeIntervals.getRegister() != NO_REGISTER;
+ assert activeIntervals.hasRegister();
inactive.add(activeIntervals);
freeOccupiedRegistersForIntervals(activeIntervals);
}
@@ -1302,7 +1348,7 @@
}
} else if (inactiveIntervals.overlapsPosition(start)) {
inactiveIterator.remove();
- assert inactiveIntervals.getRegister() != NO_REGISTER;
+ assert inactiveIntervals.hasRegister();
active.add(inactiveIntervals);
takeFreeRegistersForIntervals(inactiveIntervals);
}
@@ -1367,11 +1413,8 @@
}
private boolean verifyRegisterAssignmentNotConflictingWithArgument(LiveIntervals interval) {
- assert interval.getRegister() != NO_REGISTER;
- for (Value argumentValue = firstArgumentValue;
- argumentValue != null;
- argumentValue = argumentValue.getNextConsecutive()) {
- LiveIntervals argumentIntervals = argumentValue.getLiveIntervals();
+ assert interval.hasRegister();
+ for (LiveIntervals argumentIntervals : getArgumentLiveIntervals()) {
assert interval.getSplitParent() == argumentIntervals
|| !isPinnedArgumentRegister(argumentIntervals)
|| !interval.hasConflictingRegisters(argumentIntervals)
@@ -1431,21 +1474,19 @@
*/
@SuppressWarnings("JdkObsolete")
private void allocateRegistersForInvokeRangeSplits(LiveIntervals unhandledIntervals) {
- Value value = unhandledIntervals.getValue();
- for (Invoke invoke : value.<Invoke>uniqueUsers(this::needsInvokeRangeLiveIntervals)) {
- LiveIntervals overlappingIntervals =
- unhandledIntervals.getSplitParent().getSplitCovering(invoke);
- if (overlappingIntervals.hasRegister()) {
- assert invoke.arguments().stream()
- .allMatch(
- invokeArgument -> {
- LiveIntervals overlappingInvokeArgumentIntervals =
- invokeArgument.getLiveIntervals().getSplitCovering(invoke);
- assert overlappingInvokeArgumentIntervals.hasRegister();
- return true;
- });
- continue;
- }
+ if (!unhandledIntervals.isSplitParent()) {
+ return;
+ }
+ timing.begin("Extract splits");
+ List<LiveIntervals> invokeRangeIntervals =
+ ListUtils.filter(
+ unhandledIntervals.getSplitChildren(),
+ split -> split.isInvokeRangeIntervals() && !split.hasRegister());
+ timing.end();
+ timing.begin("Process splits");
+ for (LiveIntervals split : invokeRangeIntervals) {
+ timing.begin("Extract list");
+ Invoke invoke = split.getIsInvokeRangeIntervals();
List<LiveIntervals> intervalsList =
ListUtils.map(
invoke.arguments(),
@@ -1458,13 +1499,18 @@
|| overlappingInvokeArgumentIntervals.getEnd() == invoke.getNumber() + 1;
return overlappingInvokeArgumentIntervals;
});
+ timing.end();
+ timing.begin("Prelude");
// Save the current register allocation state so we can restore it at the end.
+ timing.begin("Copy free registers");
IntSortedSet savedFreeRegisters = new IntRBTreeSet(freeRegisters);
int savedMaxRegisterNumber = maxRegisterNumber;
+ timing.end();
// Simulate adding all the active intervals to the inactive set by blocking their register if
// they overlap with any of the invoke/range intervals.
+ timing.begin("Overlaps active");
for (LiveIntervals active : active) {
// We could allow the use of all the currently active registers for the ranged invoke (by
// adding the registers for all the active intervals to freeRegisters here). That could lead
@@ -1480,10 +1526,16 @@
freeOccupiedRegistersForIntervals(active);
}
}
+ timing.end();
- unhandled.removeAll(intervalsList);
+ timing.begin("Remove intervals from unhandled");
+ intervalsList.forEach(LiveIntervals::setHandled);
+ timing.end();
+ timing.end();
+ timing.begin("Allocate");
allocateLinkedIntervals(intervalsList, invoke);
-
+ timing.end();
+ timing.begin("Postlude");
// Restore the register allocation state.
freeRegisters = savedFreeRegisters;
// In case maxRegisterNumber has changed, update freeRegisters.
@@ -1492,12 +1544,15 @@
}
// Move all the argument intervals to the inactive set.
inactive.addAll(intervalsList);
+ timing.end();
}
+ timing.end();
}
private void allocateLinkedIntervals(List<LiveIntervals> intervalsList, Invoke invoke) {
LiveIntervals start = ListUtils.first(intervalsList);
+ timing.begin("Prelude");
boolean consecutiveArguments =
IterableUtils.allWithPrevious(
intervalsList,
@@ -1507,6 +1562,7 @@
== previous.getSplitParent());
boolean consecutivePinnedArguments =
consecutiveArguments && Iterables.all(intervalsList, this::isPinnedArgumentRegister);
+ timing.end();
int nextRegister;
if (consecutivePinnedArguments) {
@@ -1515,6 +1571,7 @@
} else {
// Ensure that there is a free register for the out value (or two consecutive registers if
// wide).
+ timing.begin("Not consecutive pinned args");
int numberOfRegisters = getNumberOfRequiredRegisters(intervalsList);
int numberOfOutRegisters = invoke.hasOutValue() ? invoke.outValue().requiredRegisters() : 0;
if (numberOfOutRegisters > 0
@@ -1532,32 +1589,43 @@
// Exclude the registers that overlap the start of one of the live ranges we are going to
// assign registers to now.
+ timing.begin("Overlaps inactive");
for (LiveIntervals inactiveIntervals : inactive) {
- if (Iterables.any(intervalsList, inactiveIntervals::overlaps)) {
+ if (inactiveIntervals.isInvokeRangeIntervals()) {
+ // This is the live intervals for another invoke-range, these can never overlap.
+ assert !Iterables.any(intervalsList, inactiveIntervals::overlaps);
+ continue;
+ }
+ // All of the invoke-range live intervals usually start at the same instruction number.
+ if (inactiveIntervals.overlapsAnyInvokeRangeIntervals(intervalsList)) {
excludeRegistersForInterval(inactiveIntervals);
}
}
+ timing.end();
+ timing.begin("Register range is free");
if (consecutiveArguments
&& registerRangeIsFree(start.getSplitParent().getRegister(), numberOfRegisters)) {
// For consecutive arguments we always to use the input argument registers, if they are
// free.
+ timing.end();
nextRegister = start.getSplitParent().getRegister();
} else {
+ timing.end();
// Exclude the pinned argument registers for which there exists a split that overlaps with
// one of the inputs to the invoke-range instruction.
- for (Value argument = firstArgumentValue;
- argument != null;
- argument = argument.getNextConsecutive()) {
- LiveIntervals argumentLiveIntervals = argument.getLiveIntervals();
+ timing.begin("Exclude pinned args");
+ for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
if (isPinnedArgumentRegister(argumentLiveIntervals)
&& liveIntervalsOverlappingAnyOf(argumentLiveIntervals, intervalsList)) {
excludeRegistersForInterval(argumentLiveIntervals);
}
}
+ timing.end();
// Exclude move exception register if the first interval overlaps a move exception interval.
// It is not necessary to check the remaining consecutive intervals, since we always use
// register 0 (after remapping) for the argument register.
+ timing.begin("Exclude move exc");
if (hasDedicatedMoveExceptionRegister()) {
boolean canUseMoveExceptionRegisterForLinkedIntervals =
isDedicatedMoveExceptionRegisterInFirstLocalRegister()
@@ -1566,33 +1634,22 @@
freeRegisters.remove(getMoveExceptionRegister());
}
}
+ timing.end();
+
// Select registers.
nextRegister = getFreeConsecutiveRegisters(numberOfRegisters);
}
+ timing.end();
}
// Assign registers.
+ timing.begin("Assign regs");
for (LiveIntervals current : intervalsList) {
current.setRegister(nextRegister);
assert verifyRegisterAssignmentNotConflictingWithArgument(current);
nextRegister += current.requiredRegisters();
}
-
- // Add hints.
- for (LiveIntervals intervals : intervalsList) {
- LiveIntervals parentIntervals = intervals.getSplitParent();
- parentIntervals.setHint(intervals, unhandled);
- for (LiveIntervals siblingIntervals : parentIntervals.getSplitChildren()) {
- if (siblingIntervals != intervals && !siblingIntervals.hasRegister()) {
- siblingIntervals.setHint(intervals, unhandled);
- }
- }
- Value value = intervals.getValue();
- if (value.isDefinedByInstructionSatisfying(Instruction::isMove)) {
- Move move = value.getDefinition().asMove();
- move.src().getLiveIntervals().setHint(intervals, unhandled);
- }
- }
+ timing.end();
}
private int getNumberOfRequiredRegisters(List<LiveIntervals> intervalsList) {
@@ -1606,10 +1663,15 @@
// Returns true if intervals has a split, which overlaps with any of the live intervals in the
// given list.
private boolean liveIntervalsOverlappingAnyOf(
- LiveIntervals intervals, List<LiveIntervals> intervalsList) {
- assert intervals == intervals.getSplitParent();
- for (LiveIntervals split : intervals.getSplitChildren()) {
- if (Iterables.any(intervalsList, split::overlaps)) {
+ LiveIntervals argumentLiveIntervals, List<LiveIntervals> intervalsList) {
+ assert argumentLiveIntervals == argumentLiveIntervals.getSplitParent();
+ for (LiveIntervals intervals : intervalsList) {
+ if (intervals.getValue() == argumentLiveIntervals.getValue()) {
+ return true;
+ }
+ }
+ for (LiveIntervals split : argumentLiveIntervals.getSplitChildren()) {
+ if (split.overlapsAnyInvokeRangeIntervals(intervalsList)) {
return true;
}
}
@@ -1629,7 +1691,7 @@
}
private int getSpillRegister(LiveIntervals intervals, IntList excludedRegisters) {
- if (intervals.isArgumentInterval()) {
+ if (isPinnedArgumentRegister(intervals)) {
// Arguments are always in the argument registers, so for arguments just use that register
// for the unconstrained prefix. For everything else, get a spill register.
return intervals.getSplitParent().getRegister();
@@ -1690,7 +1752,7 @@
//
// Note that this is *not* guaranteed when overlapsInactiveIntervals is null, because it is
// possible that some live ranges of the argument are still in the unhandled set.
- if (register < numberOfArgumentRegisters) {
+ if (isArgumentRegister(register)) {
// Find the first argument value that uses the given register.
LiveIntervals argumentLiveIntervals = firstArgumentValue.getLiveIntervals();
while (!argumentLiveIntervals.usesRegister(register, intervals.getType().isWide())) {
@@ -2001,9 +2063,12 @@
assert freePositionsAreConsistentWithFreeRegisters(freePositions, registerConstraint);
// Attempt to use register hints.
+ timing.begin("Try hint");
if (useRegisterHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair)) {
+ timing.end();
return true;
}
+ timing.end();
// Get the register (pair) that is free the longest. That is the register with the largest
// free position.
@@ -2039,11 +2104,18 @@
// of finding another candidate to spill via allocateBlockedRegister.
assert unhandledInterval.hasUses();
if (!unhandledInterval.getUses().first().hasConstraint()) {
- int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint(mode).getPosition();
- int register = getSpillRegister(unhandledInterval, null);
- LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
- assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
- unhandled.add(split);
+ if (mode.hasRegisterConstraint(unhandledInterval)) {
+ int nextConstrainedPosition =
+ unhandledInterval.firstUseWithConstraint(mode).getPosition();
+ int register = getSpillRegister(unhandledInterval, null);
+ LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition, mode);
+ assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
+ unhandled.add(split);
+ } else {
+ assert unhandledInterval.firstUseWithConstraint(mode) == null;
+ int register = getSpillRegister(unhandledInterval, null);
+ assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
+ }
} else {
allocateBlockedRegister(unhandledInterval, registerConstraint);
}
@@ -2063,7 +2135,7 @@
// The candidate is free for the beginning of an interval. We split the interval
// and use the register for as long as we can.
int registerConstraintBeforeSplit = unhandledInterval.getRegisterLimit();
- LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition);
+ LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition, mode);
assert split != unhandledInterval;
unhandled.add(split);
@@ -2103,10 +2175,9 @@
firstArgumentValue.getLiveIntervals().forEachRegister(freePositions::setBlocked);
}
// But not any of the other argument registers.
- for (Value argument = firstArgumentValue;
- argument != null;
- argument = argument.getNextConsecutive()) {
- assert !isPinnedArgumentRegister(argument.getLiveIntervals()) || argument.isThis();
+ for (LiveIntervals argumentIntervals : getArgumentLiveIntervals()) {
+ assert !isPinnedArgumentRegister(argumentIntervals)
+ || argumentIntervals.getValue().isThis();
}
} else {
// Generally argument reuse is not allowed and we block all the argument registers so that
@@ -2119,10 +2190,8 @@
if (mode.is8BitRefinement()) {
assert numberOf4BitArgumentRegisters > 0;
int remainingNumberOf4BitArgumentRegisters = numberOf4BitArgumentRegisters;
- for (Value argumentValue = firstArgumentValue;
- argumentValue != null;
- argumentValue = argumentValue.getNextConsecutive()) {
- int requiredRegisters = argumentValue.requiredRegisters();
+ for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+ int requiredRegisters = argumentLiveIntervals.requiredRegisters();
remainingNumberOf4BitArgumentRegisters -= requiredRegisters;
if (remainingNumberOf4BitArgumentRegisters < 0) {
// Block all subsequent argument registers.
@@ -2131,7 +2200,7 @@
// Block this argument register if there is any overlap between the two live intervals.
// TODO(b/374266460): Allow using the argument register even when there are overlapping
// live intervals.
- if (argumentValue.getLiveIntervals().anySplitOverlaps(unhandledInterval)) {
+ if (argumentLiveIntervals.anySplitOverlaps(unhandledInterval)) {
for (int j = 0; j < requiredRegisters; j++) {
freePositions.setBlocked(i + j);
}
@@ -2139,7 +2208,7 @@
i += requiredRegisters;
}
}
- for (; i < numberOfArgumentRegisters && i <= registerConstraint; i++) {
+ for (; isArgumentRegister(i) && i <= registerConstraint; i++) {
freePositions.setBlocked(i);
}
}
@@ -2330,10 +2399,8 @@
return false;
}
if (isArgumentRegister(candidate)) {
- for (Value argument = firstArgumentValue;
- argument != null;
- argument = argument.getNextConsecutive()) {
- if (isPinnedArgument(argument)) {
+ for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+ if (isPinnedArgumentRegister(argumentLiveIntervals)) {
return false;
}
}
@@ -2378,7 +2445,7 @@
if (!expiredHere.isEmpty()) {
return false;
}
- LiveIntervals split = blockingInterval.splitBefore(unhandledInterval.getStart());
+ LiveIntervals split = blockingInterval.splitBefore(unhandledInterval.getStart(), mode);
freeOccupiedRegistersForIntervals(blockingInterval);
assignFreeRegisterToUnhandledInterval(unhandledInterval, blockingInterval.getRegister());
active.remove(blockingInterval);
@@ -2621,7 +2688,9 @@
if (activeRegister + i <= registerConstraint) {
int unhandledStart = unhandledInterval.getStart();
usePositions.set(
- activeRegister + i, intervals.firstUseAfter(unhandledStart), intervals);
+ activeRegister + i,
+ intervals.firstUseWithConstraintAfter(unhandledStart, mode),
+ intervals);
}
}
}
@@ -2633,7 +2702,8 @@
if (inactiveRegister <= registerConstraint && intervals.overlaps(unhandledInterval)) {
for (int i = 0; i < intervals.requiredRegisters(); i++) {
if (inactiveRegister + i <= registerConstraint) {
- int firstUse = intervals.firstUseAfter(unhandledInterval.getStart());
+ int firstUse =
+ intervals.firstUseWithConstraintAfter(unhandledInterval.getStart(), mode);
if (firstUse < usePositions.get(inactiveRegister + i)) {
usePositions.set(inactiveRegister + i, firstUse, intervals);
}
@@ -2644,7 +2714,7 @@
// Disallow the reuse of argument registers by always treating them as being used
// at instruction number 0.
- for (int i = 0; i < numberOfArgumentRegisters; i++) {
+ for (int i = 0; isArgumentRegister(i); i++) {
usePositions.setBlocked(i);
}
@@ -2723,7 +2793,7 @@
// All active and inactive intervals are used before current. Therefore, it is best to spill
// current itself.
int splitPosition = unhandledInterval.getFirstUse();
- LiveIntervals split = unhandledInterval.splitBefore(splitPosition);
+ LiveIntervals split = unhandledInterval.splitBefore(splitPosition, mode);
assert split != unhandledInterval;
// Experiments show that it has a positive impact on code size to use a fresh register here.
int registerNumber = getNewSpillRegister(unhandledInterval);
@@ -2743,7 +2813,7 @@
assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
} else {
// Spilling only makes a register available for the first part of current.
- LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition);
+ LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition, mode);
unhandled.add(splitChild);
assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
}
@@ -2774,24 +2844,11 @@
protected void splitOverlappingInactiveIntervals(
LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
- List<LiveIntervals> newInactive = new ArrayList<>();
Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
while (inactiveIterator.hasNext()) {
LiveIntervals intervals = inactiveIterator.next();
if (intervals.usesRegister(candidate, candidateIsWide)
&& intervals.overlaps(unhandledInterval)) {
- if (intervals.isLinked() && !intervals.isArgumentInterval()) {
- // If the inactive register is linked but not an argument, it needs to get the
- // same register again at the next use after the start of the unhandled interval.
- // If there are no such uses, we can use a different register for the remainder
- // of the inactive interval and therefore do not have to split here.
- int nextUsePosition = intervals.firstUseAfter(unhandledInterval.getStart());
- if (nextUsePosition != Integer.MAX_VALUE) {
- LiveIntervals split = intervals.splitBefore(nextUsePosition);
- split.setRegister(intervals.getRegister());
- newInactive.add(split);
- }
- }
if (intervals.getStart() > unhandledInterval.getStart()) {
// The inactive live intervals hasn't started yet. Clear the temporary register
// assignment and move back to unhandled for register reassignment.
@@ -2801,17 +2858,16 @@
} else {
// The inactive live intervals is in a live range hole. Split the interval and
// put the ranges after the hole into the unhandled set for register reassignment.
- LiveIntervals split = intervals.splitBefore(unhandledInterval.getStart());
+ LiveIntervals split = intervals.splitBefore(unhandledInterval.getStart(), mode);
unhandled.add(split);
}
}
}
- inactive.addAll(newInactive);
}
private void spillOverlappingActiveIntervals(
LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
- assert unhandledInterval.getRegister() == NO_REGISTER;
+ assert !unhandledInterval.hasRegister();
assert atLeastOneOfRegistersAreTaken(candidate, candidateIsWide);
// Registers that we cannot choose for spilling.
IntList excludedRegisters = new IntArrayList(candidateIsWide ? 2 : 1);
@@ -2838,12 +2894,12 @@
// because we might otherwise end up spilling to the current registers of intervals,
// depending on getSpillRegister.
freeOccupiedRegistersForIntervals(intervals);
- LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart());
+ LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart(), mode);
assignRegister(splitChild, registerNumber);
splitChild.setSpilled(true);
takeFreeRegistersForIntervals(splitChild);
- assert splitChild.getRegister() != NO_REGISTER;
- assert intervals.getRegister() != NO_REGISTER;
+ assert splitChild.hasRegister();
+ assert intervals.hasRegister();
newActive.add(splitChild);
// If the constant is split before its first actual use, mark the constant as being
// spilled. That will allows us to remove it afterwards if it is rematerializable.
@@ -2852,20 +2908,15 @@
&& intervals.getUses().size() == 1) {
intervals.setSpilled(true);
}
- if (splitChild.getUses().size() > 0) {
- if (splitChild.isLinked() && !splitChild.isArgumentInterval()) {
- // Spilling a value with a pinned register. We need to move back at the next use.
- LiveIntervals splitOfSplit = splitChild.splitBefore(splitChild.getFirstUse());
- splitOfSplit.setRegister(intervals.getRegister());
- inactive.add(splitOfSplit);
- } else if (intervals.getValue().isConstNumber()) {
+ if (splitChild.hasUses()) {
+ if (intervals.getValue().isConstNumber()) {
// TODO(ager): Do this for all constants. Currently we only rematerialize const
- // number and therefore we only do it for numbers at this point.
+ // number and therefore we only do it for numbers at this point.
splitRangesForSpilledConstant(splitChild, registerNumber);
} else if (intervals.isArgumentInterval()) {
splitRangesForSpilledArgument(splitChild);
} else {
- splitRangesForSpilledInterval(splitChild, registerNumber);
+ splitRangesForSpilledInterval(splitChild);
}
}
}
@@ -2881,44 +2932,34 @@
// that is yet, and therefore we split before the next use to make sure we get a usable
// register at the next use.
if (!spilled.getUses().isEmpty()) {
- LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition());
- unhandled.add(split);
+ LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition(), mode);
+ if (split != spilled) {
+ unhandled.add(split);
+ } else {
+ spilled.setRegister(spilled.getSplitParent().getRegister());
+ }
}
}
- private void splitRangesForSpilledInterval(LiveIntervals spilled, int registerNumber) {
+ private void splitRangesForSpilledInterval(LiveIntervals spilled) {
// Spilling a non-pinned, non-rematerializable value. We use the value in the spill
// register for as long as possible to avoid further moves.
assert spilled.isSpilled();
assert !spilled.getValue().isConstNumber();
- assert !spilled.isLinked() || spilled.isArgumentInterval();
- boolean isSpillingToArgumentRegister =
- (spilled.isArgumentInterval() || registerNumber < numberOfArgumentRegisters);
- if (isSpillingToArgumentRegister) {
- if (mode.is8Bit()) {
- registerNumber = Constants.U8BIT_MAX;
+ LiveIntervalsUse firstUseWithConstraint = spilled.firstUseWithConstraint(mode);
+ if (firstUseWithConstraint != null) {
+ int register = spilled.getRegister();
+ LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithConstraint.getPosition(), mode);
+ if (splitOfSplit != spilled) {
+ unhandled.add(splitOfSplit);
} else {
- registerNumber = Constants.U16BIT_MAX;
+ assert !spilled.hasRegister();
+ spilled.setRegister(register);
+ if (spilled.hasUses()) {
+ spilled.setSpilled(false);
+ }
}
}
- LiveIntervalsUse firstUseWithLowerLimit = null;
- boolean hasUsesBeforeFirstUseWithLowerLimit = false;
- int highestRegisterNumber = registerNumber + spilled.requiredRegisters() - 1;
- for (LiveIntervalsUse use : spilled.getUses()) {
- if (highestRegisterNumber > use.getLimit()) {
- firstUseWithLowerLimit = use;
- break;
- } else {
- hasUsesBeforeFirstUseWithLowerLimit = true;
- }
- }
- if (hasUsesBeforeFirstUseWithLowerLimit) {
- spilled.setSpilled(false);
- }
- if (firstUseWithLowerLimit != null) {
- LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithLowerLimit.getPosition());
- unhandled.add(splitOfSplit);
- }
}
private void splitRangesForSpilledConstant(LiveIntervals spilled, int spillRegister) {
@@ -2929,13 +2970,16 @@
// as much as possible.
assert spilled.isSpilled();
assert spilled.getValue().isConstNumber();
- assert !spilled.isLinked() || spilled.isArgumentInterval();
// Do not split range if constant is reused by one of the eleven following instruction.
int maxGapSize = 11 * INSTRUCTION_NUMBER_DELTA;
- if (!spilled.getUses().isEmpty()) {
+ LiveIntervalsUse firstUseWithConstraint = spilled.firstUseWithConstraint(mode);
+ if (firstUseWithConstraint != null) {
// Split at first use after the spill position and add to unhandled to get a register
// assigned for rematerialization.
- LiveIntervals split = spilled.splitBefore(spilled.getFirstUse());
+ LiveIntervals split = spilled.splitBefore(firstUseWithConstraint.getPosition(), mode);
+ if (spilled.hasUses()) {
+ spilled.setSpilled(false);
+ }
unhandled.add(split);
// Now repeatedly split for each use that is more than maxGapSize away from the previous use.
boolean changed = true;
@@ -2946,14 +2990,14 @@
if (use.getPosition() - previousUse > maxGapSize) {
// Found a use that is more than gap size away from the previous use. Split after
// the previous use.
- split = split.splitBefore(previousUse + INSTRUCTION_NUMBER_DELTA);
+ split = split.splitBefore(previousUse + INSTRUCTION_NUMBER_DELTA, mode);
// If the next use is not at the start of the new split, we split again at the next use
// and spill the gap.
if (toGapPosition(use.getPosition()) > split.getStart()) {
assignRegister(split, spillRegister);
split.setSpilled(true);
inactive.add(split);
- split = split.splitBefore(use.getPosition());
+ split = split.splitBefore(use.getPosition(), mode);
}
// |split| now starts at the next use - add it to unhandled to get a register
// assigned for rematerialization.
@@ -2965,6 +3009,8 @@
previousUse = use.getPosition();
}
}
+ } else if (spilled.hasUses()) {
+ spilled.setSpilled(false);
}
}
@@ -3137,7 +3183,7 @@
return false;
}
assert intervals.hasRegister();
- if (intervals.getRegister() >= numberOfArgumentRegisters) {
+ if (!isArgumentRegister(intervals.getRegister())) {
return false;
}
// An argument register could be moved to another argument register.
@@ -3161,19 +3207,7 @@
instructionNumber = instruction.getNumber();
}
if (value.getLiveIntervals() == null) {
- Value current = value.getStartOfConsecutive();
- LiveIntervals intervals = new LiveIntervals(current);
- while (true) {
- liveIntervals.add(intervals);
- Value next = current.getNextConsecutive();
- if (next == null) {
- break;
- }
- LiveIntervals nextIntervals = new LiveIntervals(next);
- intervals.link(nextIntervals);
- current = next;
- intervals = nextIntervals;
- }
+ liveIntervals.add(new LiveIntervals(value));
}
LiveIntervals intervals = value.getLiveIntervals();
if (firstInstructionInBlock <= instructionNumber &&
@@ -3493,10 +3527,13 @@
private static boolean argumentsAreAlreadyLinked(Invoke invoke) {
Iterator<Value> it = invoke.arguments().iterator();
- Value current = it.next();
+ Argument current = it.next().getDefinitionOrNull(Instruction::isArgument);
+ if (current == null) {
+ return false;
+ }
while (it.hasNext()) {
- Value next = it.next();
- if (!current.isLinked() || current.getNextConsecutive() != next) {
+ Argument next = it.next().getDefinitionOrNull(Instruction::isArgument);
+ if (current.getNext() != next) {
return false;
}
current = next;
@@ -3522,7 +3559,6 @@
Value last = firstArgumentValue = arguments.get(0);
for (int i = 1; i < arguments.size(); ++i) {
Value next = arguments.get(i);
- last.linkTo(next);
last.getLiveIntervals().link(next.getLiveIntervals());
last = next;
}
@@ -3568,12 +3604,9 @@
if (firstArgumentValue != null) {
increaseCapacity(numberOfArgumentRegisters - 1, true);
int register = 0;
- for (Value current = firstArgumentValue;
- current != null;
- current = current.getNextConsecutive()) {
- LiveIntervals argumentLiveInterval = current.getLiveIntervals();
- assignRegister(argumentLiveInterval, register);
- register += current.requiredRegisters();
+ for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+ assignRegister(argumentLiveIntervals, register);
+ register += argumentLiveIntervals.requiredRegisters();
}
}
}
@@ -3608,8 +3641,8 @@
freeRegistersWithDesiredOrdering =
new IntRBTreeSet(
(Integer x, Integer y) -> {
- boolean xIsArgument = x < numberOfArgumentRegisters;
- boolean yIsArgument = y < numberOfArgumentRegisters;
+ boolean xIsArgument = isArgumentRegister(x);
+ boolean yIsArgument = isArgumentRegister(y);
// If x is an argument and y is not, then prioritize y.
if (xIsArgument && !yIsArgument) {
return 1;
@@ -3646,10 +3679,8 @@
}
// Either all the consecutive registers are from the argument registers, or all are from the
// non-argument registers.
- assert (first < numberOfArgumentRegisters
- && first + numberOfRegisters - 1 < numberOfArgumentRegisters)
- || (first >= numberOfArgumentRegisters
- && first + numberOfRegisters - 1 >= numberOfArgumentRegisters);
+ assert (isArgumentRegister(first) && isArgumentRegister(first + numberOfRegisters - 1))
+ || (!isArgumentRegister(first) && !isArgumentRegister(first + numberOfRegisters - 1));
return first;
}
@@ -3749,7 +3780,7 @@
}
private boolean registersForIntervalsAreTaken(LiveIntervals intervals) {
- assert intervals.getRegister() != NO_REGISTER;
+ assert intervals.hasRegister();
return registersAreTaken(intervals.getRegister(), intervals.getType().isWide());
}
@@ -3757,22 +3788,6 @@
return !freeRegisters.contains(register) || (isWide && !freeRegisters.contains(register + 1));
}
- private boolean noLinkedValues() {
- for (BasicBlock block : code.blocks) {
- for (Phi phi : block.getPhis()) {
- assert phi.getNextConsecutive() == null;
- }
- for (Instruction instruction : block.getInstructions()) {
- for (Value value : instruction.inValues()) {
- assert value.getNextConsecutive() == null;
- }
- assert instruction.outValue() == null ||
- instruction.outValue().getNextConsecutive() == null;
- }
- }
- return true;
- }
-
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Live ranges:\n");
@@ -3785,10 +3800,10 @@
builder.append("\nLive range ascii art: \n");
for (LiveIntervals intervals : liveIntervals) {
Value value = intervals.getValue();
- if (intervals.getRegister() == NO_REGISTER) {
- StringUtils.appendRightPadded(builder, value + " (no reg): ", 20);
- } else {
+ if (intervals.hasRegister()) {
StringUtils.appendRightPadded(builder, value + " r" + intervals.getRegister() + ": ", 20);
+ } else {
+ StringUtils.appendRightPadded(builder, value + " (no reg): ", 20);
}
builder.append("|");
builder.append(intervals.toAscciArtString());
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 64d42b0..75fd057 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
@@ -36,14 +37,15 @@
private final List<LiveIntervals> splitChildren = new ArrayList<>();
private final IntArrayList sortedSplitChildrenEnds = new IntArrayList();
private boolean sortedChildren = false;
- private List<LiveRange> ranges = new ArrayList<>();
+ private ArrayList<LiveRange> ranges = new ArrayList<>();
private final TreeSet<LiveIntervalsUse> uses = new TreeSet<>();
private int register = NO_REGISTER;
private int hint = NO_REGISTER;
private boolean spilled = false;
- private boolean isInvokeRangeIntervals = false;
+ private Invoke isInvokeRangeIntervals = null;
private boolean usedInMonitorOperations = false;
private boolean liveAtMoveExceptionEntry = false;
+ private boolean handled = false;
// Only registers up to and including the registerLimit are allowed for this interval.
private int registerLimit = U16BIT_MAX;
@@ -110,6 +112,15 @@
return hint;
}
+ public boolean isHandled() {
+ return handled;
+ }
+
+ // Equivalent to removing the live intervals from the unhandled set. This is O(1) instead of O(n).
+ public void setHandled() {
+ handled = true;
+ }
+
public void setSpilled(boolean value) {
// Check that we always spill arguments to their original register.
assert getRegister() != NO_REGISTER;
@@ -143,10 +154,6 @@
next.previousConsecutive = this;
}
- public boolean isLinked() {
- return splitParent.previousConsecutive != null || splitParent.nextConsecutive != null;
- }
-
public boolean isArgumentInterval() {
Instruction definition = this.splitParent.value.definition;
return definition != null && definition.isArgument();
@@ -297,17 +304,21 @@
}
public boolean isInvokeRangeIntervals() {
+ return isInvokeRangeIntervals != null;
+ }
+
+ public Invoke getIsInvokeRangeIntervals() {
return isInvokeRangeIntervals;
}
- public void setIsInvokeRangeIntervals() {
- assert !isInvokeRangeIntervals;
- isInvokeRangeIntervals = true;
+ public void setIsInvokeRangeIntervals(Invoke invoke) {
+ assert !isInvokeRangeIntervals();
+ isInvokeRangeIntervals = invoke;
}
public void unsetIsInvokeRangeIntervals() {
assert isSplitParent();
- isInvokeRangeIntervals = false;
+ isInvokeRangeIntervals = null;
}
public boolean isLiveAtMoveExceptionEntry() {
@@ -372,6 +383,9 @@
}
public boolean overlapsPosition(int position) {
+ if (position < getStart() || position >= getEnd()) {
+ return false;
+ }
for (LiveRange range : ranges) {
if (range.start > position) {
// Ranges are sorted. When a range starts after position there is no overlap.
@@ -388,6 +402,25 @@
return nextOverlap(other) != -1;
}
+ public boolean overlapsAnyInvokeRangeIntervals(List<LiveIntervals> intervalsList) {
+ boolean checked = false;
+ for (LiveIntervals intervals : intervalsList) {
+ boolean skip;
+ if (intervals.getValue().isDefinedByInstructionSatisfying(Instruction::isMove)) {
+ skip = false;
+ } else {
+ skip = checked;
+ if (!checked) {
+ checked = true;
+ }
+ }
+ if (!skip && overlaps(intervals)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public boolean anySplitOverlaps(LiveIntervals other) {
LiveIntervals parent = getSplitParent();
if (parent.overlaps(other)) {
@@ -427,6 +460,18 @@
return Integer.MAX_VALUE;
}
+ public int firstUseWithConstraintAfter(int unhandledStart, ArgumentReuseMode mode) {
+ if (isInvokeRangeIntervals()) {
+ return getFirstUse();
+ }
+ for (LiveIntervalsUse use : uses) {
+ if (use.hasConstraint(mode) && use.getPosition() >= unhandledStart) {
+ return use.getPosition();
+ }
+ }
+ return Integer.MAX_VALUE;
+ }
+
public boolean hasUses() {
return !uses.isEmpty();
}
@@ -451,17 +496,19 @@
}
}
- public LiveIntervals splitBefore(Instruction instruction) {
- return splitBefore(instruction.getNumber());
+ public LiveIntervals splitBefore(Instruction instruction, ArgumentReuseMode mode) {
+ return splitBefore(instruction.getNumber(), mode);
}
- public LiveIntervals splitAfter(Instruction instruction) {
- return splitBefore(instruction.getNumber() + INSTRUCTION_NUMBER_DELTA);
+ public LiveIntervals splitAfter(Instruction instruction, ArgumentReuseMode mode) {
+ return splitBefore(instruction.getNumber() + INSTRUCTION_NUMBER_DELTA, mode);
}
- public LiveIntervals splitBefore(int start) {
+ public LiveIntervals splitBefore(int start, ArgumentReuseMode mode) {
if (toInstructionPosition(start) == toInstructionPosition(getStart())) {
- assert uses.size() == 0 || getFirstUse() != start;
+ assert uses.isEmpty()
+ || getFirstUse() != start
+ || (!uses.first().hasConstraint(mode) && !isInvokeRangeIntervals());
register = NO_REGISTER;
return this;
}
@@ -472,8 +519,8 @@
LiveIntervals splitChild = new LiveIntervals(splitParent);
splitParent.splitChildren.add(splitChild);
splitParent.sortedChildren = splitParent.splitChildren.size() == 1;
- List<LiveRange> beforeSplit = new ArrayList<>();
- List<LiveRange> afterSplit = new ArrayList<>();
+ ArrayList<LiveRange> beforeSplit = new ArrayList<>();
+ ArrayList<LiveRange> afterSplit = new ArrayList<>();
if (start == getEnd()) {
beforeSplit = ranges;
afterSplit.add(new LiveRange(start, start));
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 2575d3b..736ed62 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -7,12 +7,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+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.Position;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.MapUtils;
import java.util.Collection;
import java.util.Collections;
@@ -135,12 +135,9 @@
insertAt.next();
}
// Generate moves for each argument.
- for (Value argumentValue = allocator.firstArgumentValue;
- argumentValue != null;
- argumentValue = argumentValue.getNextConsecutive()) {
- Instruction instruction = argumentValue.definition;
- if (needsMovesBeforeInstruction(instruction)) {
- scheduleMovesBeforeInstruction(tempRegister, instruction, insertAt);
+ for (Argument argument : code.arguments()) {
+ if (needsMovesBeforeInstruction(argument)) {
+ scheduleMovesBeforeInstruction(tempRegister, argument, insertAt);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
index 5b0810d..cf78fe1 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
-import com.android.tools.r8.ir.code.Value;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
public class UnsplitArgumentsResult {
@@ -28,10 +27,8 @@
// Returns true if any changes were made.
public boolean revertPartial() {
boolean changed = false;
- for (Value argument = allocator.firstArgumentValue;
- argument != null;
- argument = argument.getNextConsecutive()) {
- for (LiveIntervals child : argument.getLiveIntervals().getSplitChildren()) {
+ for (LiveIntervals argumentLiveIntervals : allocator.getArgumentLiveIntervals()) {
+ for (LiveIntervals child : argumentLiveIntervals.getSplitChildren()) {
changed |= revertPartial(child);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index 6621432..aaccebe 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -4,13 +4,17 @@
package com.android.tools.r8.ir.synthetic;
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
import com.android.tools.r8.cf.code.CfArrayStore;
import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.cf.code.CfIfCmp;
import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceOf;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLabel;
@@ -26,14 +30,177 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.records.RecordInstructionDesugaring;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.Opcodes;
-public abstract class RecordCfCodeProvider {
+public abstract class RecordCfCodeProvider extends SyntheticCfCodeProvider {
+
+ protected RecordCfCodeProvider(AppView<?> appView, DexType holder) {
+ super(appView, holder);
+ }
+
+ /**
+ * Generates a method which hashes all the fields. If outline, the method has all fields as
+ * parameters, else it's a local public method in the record class.
+ */
+ public static class RecordHashCfCodeProvider extends RecordCfCodeProvider {
+
+ private final List<DexField> fieldsToHash;
+ private final boolean outline;
+
+ public RecordHashCfCodeProvider(
+ AppView<?> appView, DexType holder, List<DexField> fieldsToHash, boolean outline) {
+ super(appView, holder);
+ this.fieldsToHash = fieldsToHash;
+ this.outline = outline;
+ }
+
+ public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Ljava/lang/Objects;");
+ factory.createSynthesizedType("Ljava/lang/Double;");
+ factory.createSynthesizedType("Ljava/lang/Float;");
+ factory.createSynthesizedType("Ljava/lang/Boolean;");
+ factory.createSynthesizedType("Ljava/lang/Long;");
+ }
+
+ private void addInvokeStatic(List<CfInstruction> instructions, DexMethod method) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+ }
+
+ private void pushHashCode(List<CfInstruction> instructions, DexField field, int index) {
+ DexItemFactory factory = appView.dexItemFactory();
+ ValueType valueType = ValueType.fromDexType(field.getType());
+ if (outline) {
+ instructions.add(new CfLoad(valueType, index));
+ } else {
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfInstanceFieldRead(field));
+ }
+ if (valueType == ValueType.DOUBLE) {
+ addInvokeStatic(instructions, factory.doubleMembers.staticHashCode);
+ } else if (valueType == ValueType.FLOAT) {
+ addInvokeStatic(instructions, factory.floatMembers.staticHashCode);
+ } else if (field.getType().isBooleanType()) {
+ addInvokeStatic(instructions, factory.booleanMembers.staticHashCode);
+ } else if (valueType == ValueType.LONG) {
+ addInvokeStatic(instructions, factory.longMembers.staticHashCode);
+ } else if (valueType.isObject()) {
+ addInvokeStatic(instructions, factory.objectsMethods.hashCode);
+ } else {
+ assert valueType == ValueType.INT;
+ }
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ int stackUsed = 0;
+ int localsUsed = 0;
+ List<CfInstruction> instructions = new ArrayList<>();
+ if (fieldsToHash.isEmpty()) {
+ stackUsed++;
+ instructions.add(
+ new CfConstNumber(
+ RecordInstructionDesugaring.fixedHashCodeForEmptyRecord(), ValueType.INT));
+ } else {
+ pushHashCode(instructions, fieldsToHash.get(0), 0);
+ boolean seenWide = fieldsToHash.get(0).getType().isWideType();
+ int argIndex = fieldsToHash.get(0).getType().getRequiredRegisters();
+ for (int i = 1; i < fieldsToHash.size(); i++) {
+ instructions.add(new CfConstNumber(31, ValueType.INT));
+ instructions.add(new CfArithmeticBinop(Opcode.Mul, NumericType.INT));
+ pushHashCode(instructions, fieldsToHash.get(i), argIndex);
+ seenWide |= fieldsToHash.get(i).getType().isWideType();
+ instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
+ argIndex += fieldsToHash.get(i).getType().getRequiredRegisters();
+ }
+ stackUsed += seenWide ? 3 : 2;
+ localsUsed += argIndex;
+ }
+ instructions.add(new CfReturn(ValueType.INT));
+ return new CfCode(getHolder(), stackUsed, localsUsed, instructions);
+ }
+ }
+
+ public static class RecordEqCfCodeProvider extends RecordCfCodeProvider {
+
+ private final List<DexField> fieldsToCompare;
+
+ public RecordEqCfCodeProvider(
+ AppView<?> appView, DexType holder, List<DexField> fieldsToCompare) {
+ super(appView, holder);
+ this.fieldsToCompare = fieldsToCompare;
+ }
+
+ public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Ljava/lang/Objects;");
+ }
+
+ private void addInvokeStatic(List<CfInstruction> instructions, DexMethod method) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+ }
+
+ private void pushComparison(
+ List<CfInstruction> instructions, DexField field, CfLabel falseLabel) {
+ ValueType valueType = ValueType.fromDexType(field.getType());
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfInstanceFieldRead(field));
+ instructions.add(new CfLoad(ValueType.OBJECT, 2));
+ instructions.add(new CfInstanceFieldRead(field));
+ if (valueType == ValueType.DOUBLE) {
+ instructions.add(new CfCmp(Bias.LT, NumericType.DOUBLE));
+ instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+ } else if (valueType == ValueType.FLOAT) {
+ instructions.add(new CfCmp(Bias.LT, NumericType.FLOAT));
+ instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+ } else if (valueType == ValueType.LONG) {
+ instructions.add(new CfCmp(Bias.NONE, NumericType.LONG));
+ instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+ } else if (valueType.isObject()) {
+ addInvokeStatic(instructions, appView.dexItemFactory().objectsMethods.equals);
+ instructions.add(new CfIf(IfType.EQ, ValueType.INT, falseLabel));
+ } else {
+ assert valueType == ValueType.INT;
+ instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, falseLabel));
+ }
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ CfFrame frame = buildFrame();
+ List<CfInstruction> instructions = new ArrayList<>();
+ CfLabel falseLabel = new CfLabel();
+ instructions.add(new CfLoad(ValueType.OBJECT, 1));
+ instructions.add(new CfInstanceOf(getHolder()));
+ instructions.add(new CfIf(IfType.EQ, ValueType.INT, falseLabel));
+ instructions.add(new CfLoad(ValueType.OBJECT, 1));
+ instructions.add(new CfCheckCast(getHolder()));
+ instructions.add(new CfStore(ValueType.OBJECT, 2));
+ for (int i = 0; i < fieldsToCompare.size(); i++) {
+ pushComparison(instructions, fieldsToCompare.get(i), falseLabel);
+ }
+ instructions.add(new CfConstNumber(1, ValueType.INT));
+ instructions.add(new CfReturn(ValueType.INT));
+ instructions.add(falseLabel);
+ instructions.add(frame);
+ instructions.add(new CfConstNumber(0, ValueType.INT));
+ instructions.add(new CfReturn(ValueType.INT));
+ return standardCfCodeFromInstructions(instructions);
+ }
+
+ private CfFrame buildFrame() {
+ return CfFrame.builder()
+ .appendLocal(FrameType.initialized(getHolder()))
+ .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
+ .build();
+ }
+ }
/**
* Generates a method which answers all field values as an array of objects. If the field value is
@@ -61,6 +228,11 @@
});
}
+ @Override
+ protected int defaultMaxStack() {
+ return fields.length + 3;
+ }
+
private final DexField[] fields;
public RecordGetFieldsAsObjectsCfCodeProvider(
@@ -135,75 +307,4 @@
}
}
}
-
- public static class RecordEqualsCfCodeProvider extends SyntheticCfCodeProvider {
-
- private final DexMethod getFieldsAsObjects;
-
- public RecordEqualsCfCodeProvider(
- AppView<?> appView, DexType holder, DexMethod getFieldsAsObjects) {
- super(appView, holder);
- this.getFieldsAsObjects = getFieldsAsObjects;
- }
-
- public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
- factory.createSynthesizedType("[Ljava/lang/Object;");
- factory.createSynthesizedType("[Ljava/util/Arrays;");
- }
-
- @Override
- public CfCode generateCfCode() {
- // This generates something along the lines of:
- // if (other == null) {
- // return false;
- // }
- // if (this.getClass() != other.getClass()) {
- // return false;
- // }
- // return Arrays.equals(
- // recordInstance.getFieldsAsObjects(),
- // ((RecordClass) other).getFieldsAsObjects());
- DexItemFactory factory = appView.dexItemFactory();
- int numberOfInstructions = 22;
- List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
- CfLabel notNullLabel = new CfLabel();
- CfLabel fieldCmp = new CfLabel();
- ValueType recordType = ValueType.fromDexType(getHolder());
- ValueType objectType = ValueType.fromDexType(factory.objectType);
- instructions.add(new CfLoad(objectType, 1));
- instructions.add(new CfIf(IfType.NE, ValueType.OBJECT, notNullLabel));
- instructions.add(new CfConstNumber(0, ValueType.INT));
- instructions.add(new CfReturn(ValueType.INT));
- instructions.add(notNullLabel);
- instructions.add(
- CfFrame.builder()
- .appendLocal(FrameType.initialized(getHolder()))
- .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
- .build());
- instructions.add(new CfLoad(recordType, 0));
- instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
- instructions.add(new CfLoad(objectType, 1));
- instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
- instructions.add(new CfIfCmp(IfType.EQ, ValueType.OBJECT, fieldCmp));
- instructions.add(new CfConstNumber(0, ValueType.INT));
- instructions.add(new CfReturn(ValueType.INT));
- instructions.add(fieldCmp);
- instructions.add(
- CfFrame.builder()
- .appendLocal(FrameType.initialized(getHolder()))
- .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
- .build());
- instructions.add(new CfLoad(recordType, 0));
- instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
- instructions.add(new CfLoad(objectType, 1));
- instructions.add(new CfCheckCast(getHolder(), true));
- instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
- instructions.add(
- new CfInvoke(
- Opcodes.INVOKESTATIC, factory.javaUtilArraysMethods.equalsObjectArray, false));
- instructions.add(new CfReturn(ValueType.INT));
- assert instructions.size() == numberOfInstructions;
- return standardCfCodeFromInstructions(instructions);
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index 287de23..2bdebd2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
@@ -25,6 +26,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.lightir.LirCode;
@@ -318,6 +320,30 @@
}
@Override
+ public void registerCallSite(DexCallSite callSite) {
+ LambdaDescriptor descriptor =
+ LambdaDescriptor.tryInfer(callSite, appView, appViewWithLiveness.appInfo(), getContext());
+ if (descriptor == null || descriptor.interfaces.isEmpty()) {
+ return;
+ }
+ ProgramMethod resolvedMainMethod =
+ appViewWithLiveness
+ .appInfo()
+ .resolveMethodOnInterface(descriptor.interfaces.get(0), descriptor.getMainMethod())
+ .getResolvedProgramMethod();
+ if (resolvedMainMethod == null) {
+ return;
+ }
+ DexMethod rewrittenMainMethod =
+ graphLens.getNextMethodSignature(resolvedMainMethod.getReference());
+ if (rewrittenMainMethod.isNotIdenticalTo(resolvedMainMethod.getReference())) {
+ markAffected();
+ } else {
+ assert !graphLens.hasPrototypeChanges(rewrittenMainMethod);
+ }
+ }
+
+ @Override
public void registerInstanceFieldRead(DexField field) {
registerFieldAccess(field);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
index 800b277..c403ea1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
@@ -84,6 +84,7 @@
converter,
fieldStates,
methodStates,
+ immediateSubtypingInfo,
inFlowComparator)
.run(executorService);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 5291e19..1c973c4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -314,7 +314,8 @@
method -> {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
if (!keepInfo.isOptimizationAllowed(options)
- || !keepInfo.isShrinkingAllowed(options)) {
+ || !keepInfo.isShrinkingAllowed(options)
+ || !keepInfo.isClosedWorldReasoningAllowed(options)) {
pinnedMethodSignatures.add(method.getMethodSignature());
}
});
@@ -781,7 +782,7 @@
// We need to find a new name for this method, since the signature is already occupied.
// TODO(b/190154391): Instead of generating a new name, we could also try permuting the order
- // of parameters.
+ // of parameters.
IntBox suffix =
newMethodSignatureSuffixes.computeIfAbsent(
methodSignatureWithParametersRemoved, ignoreKey(IntBox::new));
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 28d0ead..4c9489a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -36,6 +37,8 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -57,17 +60,20 @@
private final Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers;
private final FieldStateCollection fieldStates;
private final List<FlowGraph> flowGraphs;
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
public DefaultFieldValueJoiner(
AppView<AppInfoWithLiveness> appView,
Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers,
FieldStateCollection fieldStates,
- List<FlowGraph> flowGraphs) {
+ List<FlowGraph> flowGraphs,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
this.appView = appView;
this.classesWithSingleCallerInlinedInstanceInitializers =
classesWithSingleCallerInlinedInstanceInitializers;
this.fieldStates = fieldStates;
this.flowGraphs = flowGraphs;
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
}
public Map<FlowGraph, Deque<FlowGraphNode>> joinDefaultFieldValuesForFieldsWithReadBeforeWrite(
@@ -79,12 +85,35 @@
return Collections.emptyMap();
}
+ // Classes that are kept or have a kept subclass can be instantiated in a way that does not use
+ // any instance initializers. For all fields on such classes, we therefore include the default
+ // field value.
+ Map<DexProgramClass, Boolean> classesWithKeptSubclasses =
+ computeClassesWithKeptSubclasses(fieldsOfInterest);
+ ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
+ MapUtils.removeIf(
+ fieldsOfInterest,
+ (clazz, fields) -> {
+ Boolean isKeptOrHasKeptSubclass = classesWithKeptSubclasses.get(clazz);
+ assert isKeptOrHasKeptSubclass != null;
+ if (isKeptOrHasKeptSubclass) {
+ fields.removeIf(
+ field -> {
+ if (field.getDefinition().isInstance()) {
+ fieldsWithLiveDefaultValue.add(field);
+ return true;
+ }
+ return false;
+ });
+ }
+ return fields.isEmpty();
+ });
+
// If constructor inlining is disabled, then we focus on whether each instance initializer
// definitely assigns the given field before it is read. We do the same for final and static
// fields.
Map<DexType, ProgramFieldSet> fieldsNotSubjectToInitializerAnalysis =
removeFieldsNotSubjectToInitializerAnalysis(fieldsOfInterest);
- ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
analyzeInitializers(
fieldsOfInterest,
field -> {
@@ -108,7 +137,46 @@
return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService);
}
- protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+ /**
+ * For each of the classes in the key set of the given {@param fieldsOfInterest}, this computes
+ * whether the class is kept or has a kept subclass.
+ */
+ private Map<DexProgramClass, Boolean> computeClassesWithKeptSubclasses(
+ Map<DexProgramClass, List<ProgramField>> fieldsOfInterest) {
+ Reference2BooleanMap<DexProgramClass> classesWithKeptSubclasses =
+ new Reference2BooleanOpenHashMap<>();
+ for (DexProgramClass clazz : fieldsOfInterest.keySet()) {
+ computeClassHasKeptSubclass(clazz, classesWithKeptSubclasses);
+ }
+ return classesWithKeptSubclasses;
+ }
+
+ private boolean computeClassHasKeptSubclass(
+ DexProgramClass clazz, Reference2BooleanMap<DexProgramClass> classesWithKeptSubclasses) {
+ if (classesWithKeptSubclasses.containsKey(clazz)) {
+ return classesWithKeptSubclasses.getBoolean(clazz);
+ }
+ if (isKeptDirectly(clazz)) {
+ classesWithKeptSubclasses.put(clazz, true);
+ return true;
+ }
+ for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+ if (computeClassHasKeptSubclass(subclass, classesWithKeptSubclasses)) {
+ classesWithKeptSubclasses.put(clazz, true);
+ return true;
+ }
+ }
+ assert !classesWithKeptSubclasses.containsKey(clazz)
+ || !classesWithKeptSubclasses.getBoolean(clazz);
+ classesWithKeptSubclasses.put(clazz, false);
+ return false;
+ }
+
+ private boolean isKeptDirectly(DexProgramClass clazz) {
+ return appView.getKeepInfo(clazz).isPinned(appView.options());
+ }
+
+ private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.forEachProgramField(
@@ -262,12 +330,6 @@
(holderType, fields) -> {
assert !fields.isEmpty();
DexProgramClass holder = fields.iterator().next().getHolder();
- // If the class is kept it could be instantiated directly, in which case all default field
- // values could be live.
- if (appView.getKeepInfo(holder).isPinned(appView.options())) {
- fields.forEach(liveDefaultValueConsumer);
- return true;
- }
if (holder.isFinal() || !appView.appInfo().isInstantiatedIndirectly(holder)) {
// When the class is not explicitly marked final, the class could in principle have
// injected subclasses if it is pinned. However, none of the fields are pinned, so we
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 752e59d..ee8e703 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -50,6 +51,7 @@
final IRConverter converter;
protected final FieldStateCollection fieldStates;
final MethodStateCollectionByReference methodStates;
+ final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
final InFlowComparator inFlowComparator;
public InFlowPropagator(
@@ -58,6 +60,7 @@
IRConverter converter,
FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo,
InFlowComparator inFlowComparator) {
this.appView = appView;
this.classesWithSingleCallerInlinedInstanceInitializers =
@@ -65,6 +68,7 @@
this.converter = converter;
this.fieldStates = fieldStates;
this.methodStates = methodStates;
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
this.inFlowComparator = inFlowComparator;
}
@@ -124,7 +128,11 @@
protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(List<FlowGraph> flowGraphs) {
return new DefaultFieldValueJoiner(
- appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
+ appView,
+ classesWithSingleCallerInlinedInstanceInitializers,
+ fieldStates,
+ flowGraphs,
+ immediateSubtypingInfo);
}
private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index d512ac1..2f83503c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -45,14 +45,15 @@
T(33),
U(34),
V(35),
- MAIN(36), // API level for main is tentative.
+ BAKLAVA(36),
+ MAIN(37), // API level for main is tentative.
EXTENSION(Integer.MAX_VALUE); // Used for API modeling of Android extension APIs.
// When updating LATEST and a new version goes public, add a new api-versions.xml to third_party
// and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest. Together
// with that update third_party/android_jar/libcore_latest/core-oj.jar and run
// GenerateCovariantReturnTypeMethodsTest.
- public static final AndroidApiLevel LATEST = V;
+ public static final AndroidApiLevel LATEST = BAKLAVA;
public static final AndroidApiLevel API_DATABASE_LEVEL = LATEST;
@@ -116,7 +117,7 @@
public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
assert apiLevel > 0;
- assert V == LATEST; // This has to be updated when we add new api levels.
+ assert BAKLAVA == LATEST; // This has to be updated when we add new api levels.
assert UNKNOWN.isGreaterThan(LATEST);
switch (apiLevel) {
case 1:
@@ -189,6 +190,8 @@
return U;
case 35:
return V;
+ case 36:
+ return BAKLAVA;
default:
return MAIN;
}
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 3e484d4..ab542a4 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -49,6 +49,7 @@
"--release",
"--enable-missing-library-api-modeling",
"--android-platform-build",
+ "--optimized-resource-shrinking",
ISOLATED_SPLITS_FLAG);
private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
@@ -120,6 +121,7 @@
int threads = -1;
BooleanBox enableMissingLibraryApiModeling = new BooleanBox(false);
BooleanBox androidPlatformBuild = new BooleanBox(false);
+ BooleanBox optimizedResourceShrinking = new BooleanBox(false);
BooleanBox isolatedSplits = new BooleanBox(false);
for (int i = 0; i < args.length; i++) {
String option = args[i];
@@ -151,6 +153,9 @@
case "--android-platform-build":
androidPlatformBuild.set(true);
break;
+ case "--optimized-resource-shrinking":
+ optimizedResourceShrinking.set(true);
+ break;
case ISOLATED_SPLITS_FLAG:
isolatedSplits.set(true);
break;
@@ -302,6 +307,11 @@
ResourceShrinkerDumpUtils.setupBaseResourceShrinking(
finalAndroidResourcesInput, finalAndroidResourcesOutput, commandBuilder),
"Failed initializing resource shrinker.");
+ runIgnoreMissing(
+ () ->
+ ResourceShrinkerDumpUtils.setOptimziedResourceShrinking(
+ optimizedResourceShrinking.get(), commandBuilder),
+ "Failed setting optimized resource shrinking flag.");
}
if (desugaredLibKeepRuleConsumer != null) {
commandBuilder.setDesugaredLibraryKeepRuleConsumer(desugaredLibKeepRuleConsumer);
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index a4d1c8f..48ceda2 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -255,11 +255,12 @@
protected TraversalContinuation<TB, S> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
return joiner(
node,
- childStateMap.computeIfAbsent(
+ MapUtils.removeOrComputeDefault(
+ childStateMap,
node,
n -> {
assert false : "Unexpected joining of not visited node";
- return new ArrayList<>();
+ return Collections.emptyList();
}));
}
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index f3e1670..269b85c 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -68,6 +68,7 @@
switch (androidApiLevel) {
// MAIN is an unknown higher api version we therefore choose the highest known version.
case MAIN:
+ case BAKLAVA:
case V:
case U:
case T:
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 20d9edc..2b5aa51 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2488,11 +2488,6 @@
public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean enableRegisterHintsForBlockedRegisters = false;
- // TODO(b/374266460): Investigate why enabling this leads to more moves, for example, in
- // JetNews. Also investigate the impact on performance and how often the refinement pass is
- // successful (i.e., how often the assumed 4 bit argument registers actually end up being 4
- // bit). If the failure rate is too high maybe add a some buffer.
- public boolean enableRegisterAllocation8BitRefinement = false;
// TODO(b/374715251): Look into enabling this.
public boolean enableUseLastLocalRegisterAsMoveExceptionRegister = false;
public boolean enableKeepInfoCanonicalizer = true;
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ac5d7f6..99fae2e 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -96,6 +96,11 @@
return value != null ? value : defaultValue;
}
+ public static <K, V> V removeOrComputeDefault(Map<K, V> map, K key, Function<K, V> defaultValue) {
+ V value = map.remove(key);
+ return value != null ? value : defaultValue.apply(key);
+ }
+
public static String toString(Map<?, ?> map) {
return StringUtils.join(
",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
index 8ab1c93..c4964c1 100644
--- a/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
@@ -22,4 +22,8 @@
builder.setAndroidResourceProvider(new ArchiveProtoAndroidResourceProvider(input));
builder.setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output));
}
+
+ public static void setOptimziedResourceShrinking(boolean value, R8Command.Builder builder) {
+ builder.setResourceShrinkerConfiguration(b -> b.enableOptimizedShrinkingWithR8().build());
+ }
}
diff --git a/src/main/resourceshrinker_cli.txt b/src/main/resourceshrinker_cli.txt
deleted file mode 100644
index 6bc0f39..0000000
--- a/src/main/resourceshrinker_cli.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# 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.
-
-# This is dependent on the resource shrinker being relocated into r8 namespace
--keep class com.android.tools.r8.resourceshrinker.ResourceShrinkerCli {
- public static void main(java.lang.String[]);
-}
-
diff --git a/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java b/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java
new file mode 100644
index 0000000..6c2a31b
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java
@@ -0,0 +1,262 @@
+// Copyright (c) 2024, 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 records;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RecordHashCodeManyFieldsTest extends TestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("true", "true", "false", "false", "true", "true");
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private boolean isCfRuntimeWithNativeRecordSupport() {
+ return parameters.isCfRuntime()
+ && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK14)
+ && parameters.getApiLevel().equals(AndroidApiLevel.B);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(isCfRuntimeWithNativeRecordSupport());
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class, "desugar")
+ .applyIf(
+ isRecordsFullyDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .addInliningAnnotations()
+ .addKeepMainRule(TestClass.class);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+ .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder
+ .run(parameters.getRuntime(), TestClass.class, "desugar")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8DontShrinkDontObfuscate() throws Exception {
+ parameters.assumeR8TestParameters();
+ assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .addDontShrink()
+ .addDontObfuscate()
+ .addInliningAnnotations()
+ .addKeepMainRule(TestClass.class);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+ .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder
+ .run(parameters.getRuntime(), TestClass.class, "desugar")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ record Wide(int i1, int i2, long l1, long l2, double d1, double d2, String s1, String s2) {}
+
+ // It needs at least 33 fields with all primitive types.
+ record Many(
+ String s1,
+ String s2,
+ String s3,
+ String s4,
+ String s5,
+ boolean b1,
+ boolean b2,
+ boolean b3,
+ boolean b4,
+ boolean b5,
+ byte by1,
+ byte by2,
+ byte by3,
+ byte by4,
+ byte by5,
+ short sh1,
+ short sh2,
+ short sh3,
+ short sh4,
+ char c1,
+ char c2,
+ char c3,
+ char c4,
+ int i1,
+ int i2,
+ int i3,
+ int i4,
+ int i5,
+ long l1,
+ long l2,
+ long l3,
+ long l4,
+ long l5,
+ float f1,
+ float f2,
+ float f3,
+ float f4,
+ float f5,
+ double d1,
+ double d2,
+ double d3,
+ double d4,
+ double d5) {}
+
+ public static void main(String[] args) {
+
+ Many m1 =
+ new Many(
+ "s1", "s2", null, "s4", "s5", true, false, true, false, true, (byte) 1, (byte) 2,
+ (byte) 3, (byte) 4, (byte) 5, (short) 6, (short) 7, (short) 8, (short) 9, 'a', 'b',
+ 'c', 'd', 21, 22, 23, 24, 25, 31L, 32L, 33L, 34L, 35L, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
+ 11.0, 12.0, 13.0, 14.0, 15.0);
+ Many m2 =
+ new Many(
+ "ss1", "ss2", null, "s4", null, true, true, true, false, true, (byte) 1, (byte) 2,
+ (byte) 3, (byte) 4, (byte) 5, (short) 6, (short) 7, (short) 8, (short) 9, 'a', 'b',
+ 'f', 'd', 21, 22, 23, 24, 25, 31L, 32L, 33L, 34L, 35L, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
+ 11.0, 12.0, 13.0, 14.0, 15.0);
+ System.out.println(equals(m1.hashCode(), m1.hashCode()));
+ System.out.println(equalsHash(m1, m1));
+ System.out.println(equals(m1.hashCode(), m2.hashCode()));
+ System.out.println(equalsHash(m1, m2));
+ // The equals below are valid only if the record is desugared, i.e., not on r8 cf to cf.
+ if (args[0].equals("desugar")) {
+ System.out.println(equals(m1.hashCode(), hash(m1)));
+ Wide w = new Wide(1, 2, 3L, 4L, 1.0, 2.0, "s1", null);
+ System.out.println(equals(w.hashCode(), hash(w)));
+ } else {
+ System.out.println("true");
+ System.out.println("true");
+ }
+ }
+
+ @NeverInline
+ public static int hash(Many m) {
+ int hash = Boolean.hashCode(m.b1);
+ hash = 31 * hash + Boolean.hashCode(m.b2);
+ hash = 31 * hash + Boolean.hashCode(m.b3);
+ hash = 31 * hash + Boolean.hashCode(m.b4);
+ hash = 31 * hash + Boolean.hashCode(m.b5);
+ hash = 31 * hash + Integer.hashCode(m.by1);
+ hash = 31 * hash + Integer.hashCode(m.by2);
+ hash = 31 * hash + Integer.hashCode(m.by3);
+ hash = 31 * hash + Integer.hashCode(m.by4);
+ hash = 31 * hash + Integer.hashCode(m.by5);
+ hash = 31 * hash + Integer.hashCode(m.sh1);
+ hash = 31 * hash + Integer.hashCode(m.sh2);
+ hash = 31 * hash + Integer.hashCode(m.sh3);
+ hash = 31 * hash + Integer.hashCode(m.sh4);
+ hash = 31 * hash + Integer.hashCode(m.c1);
+ hash = 31 * hash + Integer.hashCode(m.c2);
+ hash = 31 * hash + Integer.hashCode(m.c3);
+ hash = 31 * hash + Integer.hashCode(m.c4);
+ hash = 31 * hash + Integer.hashCode(m.i1);
+ hash = 31 * hash + Integer.hashCode(m.i2);
+ hash = 31 * hash + Integer.hashCode(m.i3);
+ hash = 31 * hash + Integer.hashCode(m.i4);
+ hash = 31 * hash + Integer.hashCode(m.i5);
+ hash = 31 * hash + Long.hashCode(m.l1);
+ hash = 31 * hash + Long.hashCode(m.l2);
+ hash = 31 * hash + Long.hashCode(m.l3);
+ hash = 31 * hash + Long.hashCode(m.l4);
+ hash = 31 * hash + Long.hashCode(m.l5);
+ hash = 31 * hash + Float.hashCode(m.f1);
+ hash = 31 * hash + Float.hashCode(m.f2);
+ hash = 31 * hash + Float.hashCode(m.f3);
+ hash = 31 * hash + Float.hashCode(m.f4);
+ hash = 31 * hash + Float.hashCode(m.f5);
+ hash = 31 * hash + Double.hashCode(m.d1);
+ hash = 31 * hash + Double.hashCode(m.d2);
+ hash = 31 * hash + Double.hashCode(m.d3);
+ hash = 31 * hash + Double.hashCode(m.d4);
+ hash = 31 * hash + Double.hashCode(m.d5);
+ hash = 31 * hash + Objects.hashCode(m.s1);
+ hash = 31 * hash + Objects.hashCode(m.s2);
+ hash = 31 * hash + Objects.hashCode(m.s3);
+ hash = 31 * hash + Objects.hashCode(m.s4);
+ hash = 31 * hash + Objects.hashCode(m.s5);
+ return hash;
+ }
+
+ @NeverInline
+ public static int hash(Wide w) {
+ int hash = w.i1;
+ hash = 31 * hash + w.i2;
+ hash = 31 * hash + Long.hashCode(w.l1);
+ hash = 31 * hash + Long.hashCode(w.l2);
+ hash = 31 * hash + Double.hashCode(w.d1);
+ hash = 31 * hash + Double.hashCode(w.d2);
+ hash = 31 * hash + Objects.hashCode(w.s1);
+ hash = 31 * hash + Objects.hashCode(w.s2);
+ return hash;
+ }
+
+ @NeverInline
+ public static boolean equals(int i1, int i2) {
+ return System.currentTimeMillis() > 0 ? i1 == i2 : false;
+ }
+
+ @NeverInline
+ public static boolean equalsHash(Record r1, Record r2) {
+ return System.currentTimeMillis() > 0 ? equals(r1.hashCode(), r2.hashCode()) : false;
+ }
+ }
+}
diff --git a/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
new file mode 100644
index 0000000..cb3e379
--- /dev/null
+++ b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2024, 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 string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringBuilderWithAppendOutOfBoundsTest extends TestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("class java.lang.IndexOutOfBoundsException");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addKeepMainRule(Main.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(35))
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class Main {
+
+ public static void main(String[] strArr) {
+ String text = "sss";
+ int l = 7;
+
+ StringBuilder sb = new StringBuilder();
+ try {
+ sb.append(text, 0, l);
+ System.out.println("not out of bounds");
+ } catch (Exception e) {
+ System.out.println(e.getClass());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/B379241435Test.java
rename to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
index 3647f0a..9fb3f4f 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
@@ -12,7 +12,7 @@
// Regression test for b/379241435.
@RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedAftertInstantiatingImplementorTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@@ -22,7 +22,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
@Test
public void testJvm() throws Exception {
@@ -56,12 +56,14 @@
.getApiLevel()
.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
- r -> r.assertSuccessWithOutputLines("I.f()"));
+ r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
}
public static class TestClass {
public static void main(String[] args) {
+ // Instantiating B does not trigger class initialization of I.
I b = new B();
+ // Invoking the static method on I trigger class initialization of I.
I.f();
}
}
@@ -80,5 +82,9 @@
}
}
- static class B implements I {}
+ static class B implements I {
+ B() {
+ System.out.println("B.B()");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
similarity index 75%
copy from src/test/java/com/android/tools/r8/B379241435Test.java
copy to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
index 3647f0a..bb43551 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
@@ -10,9 +10,9 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
-// Regression test for b/379241435.
+// Regression test for a variant of b/379241435.
@RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedFromImplementorStaticMethodTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@@ -22,7 +22,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
@Test
public void testJvm() throws Exception {
@@ -49,6 +49,8 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.run(parameters.getRuntime(), TestClass.class)
.applyIf(
parameters.isDexRuntime()
@@ -56,13 +58,15 @@
.getApiLevel()
.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
- r -> r.assertSuccessWithOutputLines("I.f()"));
+ r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
}
public static class TestClass {
public static void main(String[] args) {
- I b = new B();
- I.f();
+ // Instantiating B does not trigger class initialization of I.
+ B b = new B();
+ // Invoking m indirectly trigger class initialization of I as it calls a static method on I.
+ B.m();
}
}
@@ -80,5 +84,15 @@
}
}
- static class B implements I {}
+ @NeverClassInline
+ static class B implements I {
+ B() {
+ System.out.println("B.B()");
+ }
+
+ @NeverInline
+ static void m() {
+ I.f();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
similarity index 74%
copy from src/test/java/com/android/tools/r8/B379241435Test.java
copy to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
index 3647f0a..bdb3b3b 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
@@ -10,9 +10,9 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
-// Regression test for b/379241435.
+// Regression test for a variant of b/379241435.
@RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedFromImplementorVirtualMethodTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@@ -22,7 +22,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
@Test
public void testJvm() throws Exception {
@@ -49,6 +49,9 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoMethodStaticizingAnnotations()
.run(parameters.getRuntime(), TestClass.class)
.applyIf(
parameters.isDexRuntime()
@@ -56,13 +59,15 @@
.getApiLevel()
.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
- r -> r.assertSuccessWithOutputLines("I.f()"));
+ r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
}
public static class TestClass {
public static void main(String[] args) {
- I b = new B();
- I.f();
+ // Instantiating B does not trigger class initialization of I.
+ B b = new B();
+ // Invoking m indirectly trigger class initialization of I as it calls a static method on I.
+ b.m();
}
}
@@ -80,5 +85,16 @@
}
}
- static class B implements I {}
+ @NeverClassInline
+ static class B implements I {
+ B() {
+ System.out.println("B.B()");
+ }
+
+ @NeverInline
+ @NoMethodStaticizing
+ void m() {
+ I.f();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index f003fdf..37f266a 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -118,6 +118,11 @@
assertEquals(
apiLevel < AndroidApiLevel.V.getLevel(),
backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+
+ // BAKLAVA added static field android/os/Build$VERSION.SDK_INT_FULL.
+ assertEquals(
+ apiLevel < AndroidApiLevel.BAKLAVA.getLevel(),
+ backports.contains("android/os/Build$VERSION#SDK_INT_FULL"));
}
private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index 76c0cde..469b5cd 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -75,7 +75,7 @@
Paths.get(ToolHelper.MAIN_SOURCE_DIR)
.resolve(PACKAGE_NAME.replace('.', '/'))
.resolve(CLASS_NAME + ".java");
- private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.V;
+ private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.BAKLAVA;
@Parameter public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 58a8ac6..9325826 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -200,19 +200,6 @@
expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcB;"));
expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/Ndef;"));
expectedMissingMembers.add(factory.createType("Landroid/webkit/CookieSyncManager;"));
- expectedMissingMembers.add(
- factory.createType("Landroid/adservices/customaudience/CustomAudienceManager;"));
- expectedMissingMembers.add(
- factory.createType("Landroid/adservices/customaudience/PartialCustomAudience$Builder;"));
- expectedMissingMembers.add(
- factory.createType("Landroid/adservices/customaudience/PartialCustomAudience;"));
- expectedMissingMembers.add(
- factory.createType(
- "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest$Builder;"));
- expectedMissingMembers.add(
- factory.createType(
- "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest;"));
- expectedMissingMembers.add(factory.createType("Landroid/app/appsearch/AppSearchResult;"));
assertEquals(
expectedMissingMembers.stream()
.map(DexType::toDescriptorString)
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 0dda84b2..5d93f88 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -120,9 +120,9 @@
methodReferences.forEach(field -> numberOfMethods.increment())));
});
// These numbers will change when updating api-versions.xml
- assertEquals(5972, parsedApiClasses.size());
- assertEquals(30341, numberOfFields.get());
- assertEquals(46576, numberOfMethods.get());
+ assertEquals(6031, parsedApiClasses.size());
+ assertEquals(30501, numberOfFields.get());
+ assertEquals(46885, numberOfMethods.get());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 2d09844..071d6cb 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -89,7 +89,8 @@
private Set<String> getDeletedTypesMissingRemovedAttribute() {
Set<String> removedTypeNames = new HashSet<>();
if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
- if (maxApiLevel.isLessThan(AndroidApiLevel.V)) {
+ if (maxApiLevel.isLessThan(AndroidApiLevel.V)
+ || maxApiLevel.equals(AndroidApiLevel.BAKLAVA)) {
removedTypeNames.add("com.android.internal.util.Predicate");
}
removedTypeNames.add("android.adservices.AdServicesVersion");
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
index 2617cb6..2925557 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
@@ -20,9 +20,9 @@
public abstract class CodeGenerationBase extends TestBase {
private static final Path GOOGLE_FORMAT_DIR =
- Paths.get(ToolHelper.THIRD_PARTY_DIR, "google", "google-java-format", "1.14.0");
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "google", "google-java-format", "1.24.0");
private static final Path GOOGLE_FORMAT_JAR =
- GOOGLE_FORMAT_DIR.resolve("google-java-format-1.14.0-all-deps.jar");
+ GOOGLE_FORMAT_DIR.resolve("google-java-format-1.24.0-all-deps.jar");
protected final DexItemFactory factory = new DexItemFactory();
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
new file mode 100644
index 0000000..ebf9f8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2024, 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.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidOsBuildVersionBackportTest extends AbstractBackportTest {
+
+ private static final String ANDROID_OS_BUILD_VERSION_TYPE_NAME = "android.os.Build$VERSION";
+ private static final String ANDROID_OS_BUILD_VERSION_DESCRIPTOR =
+ DescriptorUtils.javaTypeToDescriptor(ANDROID_OS_BUILD_VERSION_TYPE_NAME);
+
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public AndroidOsBuildVersionBackportTest(TestParameters parameters)
+ throws IOException, NoSuchFieldException {
+ super(
+ parameters,
+ ANDROID_OS_BUILD_VERSION_TYPE_NAME,
+ ImmutableList.of(getTestRunner(), getTransformedBuildVERSIONClass()));
+
+ // android.os.Build$VERSION.SDK_INT_FULL is on API 36.
+ registerFieldTarget(AndroidApiLevel.BAKLAVA, 1);
+ }
+
+ // Stub out android.os.Build$VERSION as it does not exist when building R8.
+ public static class /*android.os.Build$*/ VERSION {
+
+ public static /*final*/ int SDK_INT = -1;
+ public static /*final*/ int SDK_INT_FULL = -1;
+ }
+
+ private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+ return transformer(VERSION.class)
+ .setClassDescriptor(ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(VERSION.class), ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+ .transform();
+ }
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(VERSION.SDK_INT_FULL);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
new file mode 100644
index 0000000..260ea5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2024, 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.desugar.backports;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.BAKLAVA;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class SdkIntFullBackportOutlineInBackportTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void checkSdkIntFullApiLevel() {
+ assumeTrue(parameters.isNoneRuntime());
+ TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+ InternalOptions options = new InternalOptions();
+ AndroidApiLevelHashingDatabaseImpl androidApiLevelDatabase =
+ new AndroidApiLevelHashingDatabaseImpl(ImmutableList.of(), options, diagnosticsHandler);
+ assertEquals(
+ BAKLAVA.getLevel(),
+ androidApiLevelDatabase
+ .getFieldApiLevel(
+ options.dexItemFactory().androidOsBuildVersionMembers.SDK_INT_FULL.asDexField())
+ .getLevel());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+ if (apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+ continue;
+ }
+ testForD8()
+ .addProgramClassFileData(getTransformedMainClass())
+ .addLibraryClassFileData(getTransformedBuildVersionClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .setMinApi(apiLevel)
+ .compile()
+ .inspect(inspector -> inspectD8(inspector, apiLevel));
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+ // SDK_INT was introduced in D so don't bother testing before.
+ if (apiLevel.isLessThanOrEqualTo(AndroidApiLevel.D)
+ || apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+ continue;
+ }
+ System.out.println(apiLevel);
+ testForR8(Backend.DEX)
+ .addProgramClassFileData(getTransformedMainClass())
+ .addKeepMainRule(TestClass.class)
+ .addLibraryClassFileData(getTransformedBuildVersionClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .setMinApi(apiLevel)
+ .addDontObfuscate()
+ .compile()
+ .inspect(inspector -> inspectR8(inspector, apiLevel));
+ }
+ }
+
+ private void inspectD8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+ if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+ // From BAKLAVA the SDK_INT_FULL get stays.
+ assertEquals(
+ 1,
+ countStaticGets(
+ inspector.clazz(TestClass.class).mainMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ } else {
+ // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+ // is outlined from the backport as well.
+ ClassSubject backport =
+ inspector.clazz(
+ SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+ assertThat(backport, isPresent());
+ assertEquals(
+ 2,
+ countStaticGets(
+ backport.uniqueMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+ assertEquals(
+ 0,
+ countStaticGets(
+ backport.uniqueMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ ClassSubject apiOutline =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+ assertThat(apiOutline.uniqueMethod(), isPresent());
+ assertEquals(
+ 1,
+ countStaticGets(
+ apiOutline.uniqueMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ }
+ }
+
+ private void inspectR8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+ if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+ // From BAKLAVA the SDK_INT_FULL get stays.
+ assertEquals(
+ 1,
+ countStaticGets(
+ inspector.clazz(TestClass.class).mainMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ } else {
+ // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+ // is outlined from the backport as well. With just one use of the backport it is inlined
+ // into main.
+ ClassSubject backport =
+ inspector.clazz(
+ SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+ assertThat(backport, isAbsent());
+ assertEquals(
+ 1,
+ countStaticGets(
+ inspector.clazz(TestClass.class).mainMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+ assertEquals(
+ 0,
+ countStaticGets(
+ inspector.clazz(TestClass.class).mainMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ ClassSubject apiOutline =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+ assertThat(apiOutline.uniqueMethod(), isPresent());
+ assertEquals(
+ 1,
+ countStaticGets(
+ apiOutline.uniqueMethod(),
+ inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+ }
+ }
+
+ private long countStaticGets(MethodSubject subject, DexField field) {
+ return subject
+ .streamInstructions()
+ .filter(InstructionSubject::isStaticGet)
+ .map(InstructionSubject::getField)
+ .filter(dexField -> dexField.isIdenticalTo(field))
+ .count();
+ }
+
+ private static byte[] getTransformedMainClass() throws IOException {
+ return transformer(TestClass.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(VERSION.class), "Landroid/os/Build$VERSION;")
+ .transform();
+ }
+
+ private byte[] getTransformedBuildVersionClass() throws IOException, NoSuchFieldException {
+ return transformer(VERSION.class)
+ .setClassDescriptor("Landroid/os/Build$VERSION;")
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(VERSION.SDK_INT_FULL);
+ }
+ }
+
+ public static class /*android.os.Build$*/ VERSION {
+
+ public static /*final*/ int SDK_INT = -1;
+ public static /*final*/ int SDK_INT_FULL = -1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 26098b7..c3b9af4 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -6,12 +6,14 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -24,9 +26,11 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -100,14 +104,19 @@
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
InternalOptions options = new InternalOptions();
options.setMinApiLevel(apiLevel);
- List<DexMethod> backportedMethods =
- BackportedMethodRewriter.generateListOfBackportedMethods(
- AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options));
+ List<DexMethod> backportedMethods = new ArrayList<>();
+ List<DexField> backportedFields = new ArrayList<>();
+ BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+ AndroidApp.builder().build(),
+ options,
+ ThreadUtils.getExecutorService(options),
+ backportedMethods::add,
+ backportedFields::add);
Set<DexMethod> alwaysPresent = expectedToAlwaysBePresentInAndroidJar(options.itemFactory);
for (DexMethod method : backportedMethods) {
// Two different DexItemFactories are in play, but as toSourceString is used for lookup
// that is not an issue.
- ClassSubject clazz = inspector.clazz(method.holder.toSourceString());
+ ClassSubject clazz = inspector.clazz(method.getHolderType().toSourceString());
MethodSubject foundInAndroidJar =
clazz.method(
method.proto.returnType.toSourceString(),
@@ -120,6 +129,15 @@
foundInAndroidJar,
notIf(isPresent(), !alwaysPresent.contains(method)));
}
+ for (DexField field : backportedFields) {
+ // Two different DexItemFactories are in play, but as toSourceString is used for lookup
+ // that is not an issue.
+ ClassSubject clazz = inspector.clazz(field.getHolderType().toSourceString());
+ FieldSubject foundInAndroidJar =
+ clazz.field(field.getType().toSourceString(), field.getName().toSourceString());
+ assertThat(
+ foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent()));
+ }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
index cdbee2c..ac65d6a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
@@ -129,7 +129,8 @@
finalApp, GlobalSyntheticsStrategy.forNonSynthesizing()))
.appInfoForDesugaring();
Set<DexMethod> backports = Sets.newIdentityHashSet();
- backports.addAll(BackportedMethodRewriter.generateListOfBackportedMethods(libHolder, options));
+ BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+ libHolder, options, backports::add, f -> {});
Map<DexMethod, Object> failures = new IdentityHashMap<>();
for (FoundClassSubject clazz : inspector.allClasses()) {
if (clazz.toString().startsWith("j$.sun.nio.cs.")
diff --git a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
index b5eff1f..ecbb82f 100644
--- a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
@@ -2,14 +2,14 @@
// 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.desugar.nest;
+package com.android.tools.r8.desugar.nestaccesscontrol;
import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.desugar.nest.NestBasedAccessToNativeMethodTest.Main.Inner;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestBasedAccessToNativeMethodTest.Main.Inner;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.List;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
index f00657c..5ec2331 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.desugar.records;
-import java.util.Arrays;
-
// This class implements support methods for record desugaring. The RecordRewriter
// rewrites relevant calls to one of the following methods.
public class RecordMethods {
@@ -25,8 +23,4 @@
builder.append("]");
return builder.toString();
}
-
- public static int hashCode(Class<?> recordClass, Object[] recordFieldsAsObjects) {
- return 31 * Arrays.hashCode(recordFieldsAsObjects) + recordClass.hashCode();
- }
}
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index fcd2106..a19c8e9 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -58,7 +58,7 @@
.setMinApi(AndroidApiLevel.K)
.compile()
.inspect(
- inspector -> assertEquals(backend.isDex() ? 1091 : 4, inspector.allClasses().size()));
+ inspector -> assertEquals(backend.isDex() ? 1104 : 4, inspector.allClasses().size()));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
new file mode 100644
index 0000000..9b127ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2024, 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.desugar.backports;
+
+public final class AndroidOsBuildVersionMethods {
+ // Stub out android.os.Build$VERSION as it does not exist when building R8.
+ private static class AndroidOsBuildVersionStub {
+ public static int SDK_INT;
+ public static int SDK_INT_FULL;
+ }
+
+ // Android runtime value of field android.os.Build$VERSION.SDK_INT_FULL for all Android
+ // versions. Calculated from android.os.Build$VERSION.SDK_INT before Baklava (API level 36).
+ // See android.os.Build$VERSION_CODES_FULL for the constants for versions before Baklava.
+ public static int getSdkIntFull() {
+ if (AndroidOsBuildVersionStub.SDK_INT < 36) {
+ // Based on the constants in android.os.Build$VERSION_CODES_FULL.
+ return AndroidOsBuildVersionStub.SDK_INT * 100_000;
+ }
+ return AndroidOsBuildVersionStub.SDK_INT_FULL;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 9e964ea..7ec725f 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper.TestDataSourceSet;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexItemFactory;
@@ -35,6 +36,7 @@
factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
ImmutableList.of(
+ AndroidOsBuildVersionMethods.class,
AssertionErrorMethods.class,
AtomicReferenceArrayMethods.class,
AtomicReferenceFieldUpdaterMethods.class,
@@ -139,6 +141,28 @@
return instruction;
}
+ private static CfInstruction rewriteToAndroidOsBuildVersion(
+ DexItemFactory itemFactory, CfInstruction instruction) {
+ // Rewrite references to UnsafeStub to sun.misc.Unsafe.
+ if (instruction.isStaticFieldGet()) {
+ CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
+ return new CfStaticFieldRead(
+ itemFactory.createField(
+ itemFactory.createType("Landroid/os/Build$VERSION;"),
+ fieldGet.getField().getType(),
+ fieldGet.getField().getName()));
+ }
+ if (instruction.isFrame()) {
+ return instruction
+ .asFrame()
+ .mapReferenceTypes(
+ type -> {
+ throw new RuntimeException("Unexpected CfFrame instruction.");
+ });
+ }
+ return instruction;
+ }
+
@Override
protected CfCode getCode(String holderName, String methodName, CfCode code) {
if (methodName.endsWith("Stub")) {
@@ -157,6 +181,12 @@
.map(instruction -> rewriteToUnsafe(factory, instruction))
.collect(Collectors.toList()));
}
+ if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+ .collect(Collectors.toList()));
+ }
return code;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index adc7fb7..850cc4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
import java.util.LinkedList;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,7 +50,7 @@
private static class MockLinearScanRegisterAllocator extends LinearScanRegisterAllocator {
MockLinearScanRegisterAllocator(AppView<?> appView, IRCode code) {
- super(appView, code);
+ super(appView, code, Timing.empty());
}
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 12e89e5..818ddcf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -341,10 +341,7 @@
assertCounters(ALWAYS_INLINABLE, ALWAYS_INLINABLE, countInvokes(inspector, m));
m = clazz.method("int", "notInlinableDueToSideEffect", ImmutableList.of("inlining.A"));
- assertCounters(
- parameters.isCfRuntime() ? ALWAYS_INLINABLE : NEVER_INLINABLE,
- NEVER_INLINABLE,
- countInvokes(inspector, m));
+ assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
m =
clazz.method(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
index ade059d..89000f3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -5,11 +5,9 @@
package com.android.tools.r8.ir.optimize.inliner;
import static com.android.tools.r8.ir.optimize.inliner.testclasses.InliningIntoVisibilityBridgeTestClasses.getClassA;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.R8TestRunResult;
@@ -64,27 +62,23 @@
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
- // Verify that A.method() is only there if there is an explicit -neverinline rule.
+ // Verify that A.method() is removed unless there is an explicit -neverinline rule.
{
ClassSubject classSubject = result.inspector().clazz(getClassA());
- assertThat(classSubject, isPresent());
+ assertThat(classSubject, isPresentIf(neverInline || parameters.isDexRuntime()));
MethodSubject methodSubject = classSubject.uniqueMethodWithOriginalName("method");
assertEquals(neverInline, methodSubject.isPresent());
}
- // Verify that B.method() is still there, and that B.method() is neither a bridge nor a
- // synthetic method unless there is an explicit -neverinline rule.
+ // Verify that B.method() is removed.
{
ClassSubject classSubject =
result.inspector().clazz(InliningIntoVisibilityBridgeTestClassB.class);
- assertThat(classSubject, isPresent());
+ assertThat(classSubject, isPresentIf(neverInline || parameters.isDexRuntime()));
- MethodSubject methodSubject = classSubject.uniqueMethodWithOriginalName("method");
- if (!neverInline) {
- assertThat(methodSubject, isPresentAndRenamed());
- assertFalse(methodSubject.isBridge());
- assertFalse(methodSubject.isSynthetic());
+ if (neverInline) {
+ assertEquals(0, classSubject.allMethods().size());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java
new file mode 100644
index 0000000..01593db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2024, 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.redundantfieldloadelimination;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.PrintStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantLibraryFieldLoadEliminationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addInnerClasses(getClass())
+ .release()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ 2, mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ PrintStream out = System.out;
+ System.currentTimeMillis();
+ PrintStream out2 = System.out;
+ out.print("Hello");
+ out2.println(", world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java b/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
index 615ba60..bc159f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
@@ -49,8 +49,7 @@
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
.run(parameters.getRuntime(), TestClass.class)
- // TODO(b/369739224): Should throw IndexOutOfBoundsException.
- .assertSuccessWithOutputLines("46");
+ .assertFailureWithErrorThatThrows(IndexOutOfBoundsException.class);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
index 83d1acb..810baae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
@@ -34,7 +34,6 @@
.addOptionsModification(
options -> {
options.testing.enableSwitchToIfRewriting = false;
- options.testing.enableDeadSwitchCaseElimination = true;
})
.enableInliningAnnotations()
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java
new file mode 100644
index 0000000..dc5c1d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2024, 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.switches;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StringSwitchWithNonLocalPhiTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addProgramClasses(Main.class)
+ .release()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "1", "Bar", "2", "Baz", "0");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "1", "Bar", "2", "Baz", "0");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ test("Foo");
+ test("Bar");
+ test("Baz");
+ }
+
+ static void test(String str) {
+ int hashCode = str.hashCode();
+ int id = 0;
+ switch (hashCode) {
+ case 70822: // "Foo".hashCode()
+ if (str.equals("Foo")) {
+ id = 1;
+ }
+ break;
+ case 66547: // "Bar".hashCode()
+ if (str.equals("Bar")) {
+ id = 2;
+ }
+ break;
+ }
+ switch (id) {
+ case 1:
+ System.out.println("Foo");
+ break;
+ case 2:
+ System.out.println("Bar");
+ break;
+ default:
+ System.out.println("Baz");
+ break;
+ }
+ System.out.println(id);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
index d3f2fed..319f2f0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
@@ -55,7 +55,6 @@
.addOptionsModification(
options -> {
options.testing.enableSwitchToIfRewriting = false;
- options.testing.enableDeadSwitchCaseElimination = true;
})
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
index 197fa98..59e9fcf 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
@@ -33,8 +33,6 @@
public void test() throws Exception {
testForD8()
.addInnerClasses(getClass())
- .addOptionsModification(
- options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
index e5a34c5..ab68384 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
@@ -33,8 +33,6 @@
public void testD8() throws Exception {
testForD8()
.addInnerClasses(getClass())
- .addOptionsModification(
- options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
index 642eb7e..439e6ea 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
@@ -33,8 +33,6 @@
public void testD8() throws Exception {
testForD8()
.addInnerClasses(getClass())
- .addOptionsModification(
- options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
index 95ade31..a6452b5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
@@ -28,10 +28,7 @@
testForD8()
.addInnerClasses(getClass())
.addOptionsModification(
- options -> {
- options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
- options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
- })
+ options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
index 38c5012..a909ae4 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
@@ -39,10 +39,9 @@
testForD8()
.addInnerClasses(getClass())
.addOptionsModification(
- options -> {
- options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
- options.getTestingOptions().enableUseLastLocalRegisterAsMoveExceptionRegister = true;
- })
+ options ->
+ options.getTestingOptions().enableUseLastLocalRegisterAsMoveExceptionRegister =
+ true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
index d1977ab..c86017b 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
@@ -51,9 +51,10 @@
static class Main {
- static void test(long a, long b, long c, long d, long e, long f, long g, long h, int def) {
- long i = (def & 1) == 0 ? a : 42;
- accept(i);
+ static void test(
+ long a, long b, long c, long d, long e, long f, long g, long h, long i, int def) {
+ long j = (def & 1) == 0 ? i : 42;
+ accept(j);
}
static void accept(long a) {}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
index 1b947ab..f87a53d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
@@ -34,10 +34,7 @@
testForD8()
.addInnerClasses(getClass())
.addOptionsModification(
- options -> {
- options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
- options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
- })
+ options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index fd91973..3394057 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.PriorityQueue;
@@ -25,7 +26,7 @@
private static class MyRegisterAllocator extends LinearScanRegisterAllocator {
public MyRegisterAllocator(AppView<?> appView, IRCode code) {
- super(appView, code);
+ super(appView, code, Timing.empty());
}
public void addInactiveIntervals(LiveIntervals intervals) {
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
index 15ff3af..4e2a166 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
@@ -5,11 +5,12 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexMoveFrom16;
import com.android.tools.r8.dex.code.DexMoveResult;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -36,8 +37,6 @@
public void testD8() throws Exception {
testForD8()
.addInnerClasses(getClass())
- .addOptionsModification(
- options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
.release()
.setMinApi(parameters)
.compile()
@@ -56,13 +55,15 @@
.asDexInstruction()
.getInstruction();
- // TODO(b/375142715): The test no longer spills the `i` value. Look into if the test
- // can be tweeked so that `i` is spilled, and validate that it is spilled to the
- // unused argument register.
- assertTrue(
+ DexMoveFrom16 spillMove =
testMethodSubject
.streamInstructions()
- .noneMatch(i -> i.isMoveFrom(moveResult.AA)));
+ .filter(i -> i.isMoveFrom(moveResult.AA))
+ .collect(MoreCollectors.onlyElement())
+ .asDexInstruction()
+ .getInstruction();
+ int lastArgumentRegister = code.registerSize - 1;
+ assertEquals(lastArgumentRegister, spillMove.AA);
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
index 152a959..b8bd25f 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
@@ -28,10 +28,7 @@
testForD8()
.addInnerClasses(getClass())
.addOptionsModification(
- options -> {
- options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
- options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
- })
+ options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
.release()
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java
new file mode 100644
index 0000000..9605e14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepAnnoInstanceOfTargetTest extends KeepAnnoTestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello anno");
+
+ @Parameter public KeepAnnoParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<KeepAnnoParameters> data() {
+ return createParameters(
+ getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForKeepAnno(parameters)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setExcludedOuterClass(getClass())
+ .run(TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ codeInspector -> {
+ // TODO(b/349276075): The rule based version should not keep less than native.
+ if (!parameters.isShrinker() || parameters.isNativeR8()) {
+ assertTrue(codeInspector.clazz(C.class).isPresent());
+ } else {
+ assertFalse(codeInspector.clazz(C.class).isPresent());
+ }
+ });
+ }
+
+ static class TestClass {
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+
+ static class A {
+ @UsesReflection(@KeepTarget(instanceOfClassConstant = B.class, methodName = "inject"))
+ public void foo() throws Exception {
+ System.out.println("Hello anno");
+ }
+ }
+
+ static class B {
+ public static void inject() {
+ System.currentTimeMillis();
+ }
+ }
+
+ static class C extends B {
+ public static void inject() {
+ System.currentTimeMillis();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java
new file mode 100644
index 0000000..52b9cd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepAnnoWithWhyAreYouKeepingTest extends KeepAnnoTestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello anno");
+
+ @Parameter public KeepAnnoParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<KeepAnnoParameters> data() {
+ return createParameters(
+ getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ KeepAnnoTestBuilder keepAnnoTestBuilder =
+ testForKeepAnno(parameters)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setExcludedOuterClass(getClass())
+ .applyIfShrinker(
+ b -> {
+ b.addKeepRules("-whyareyoukeeping class **B {}");
+ b.allowStdoutMessages(); //
+ });
+
+ if (parameters.isNativeR8()) {
+ try {
+ keepAnnoTestBuilder.run(TestClass.class);
+ } catch (CompilationFailedException e) {
+ // TODO(b/381217105): Should not throw
+ }
+ } else {
+ keepAnnoTestBuilder
+ .run(TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ codeInspector -> {
+ assertTrue(codeInspector.clazz(B.class).init().isPresent());
+ });
+ }
+ }
+
+ static class TestClass {
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+
+ static class A {
+ @UsesReflection(@KeepTarget(classConstant = B.class, kind = KeepItemKind.CLASS_AND_MEMBERS))
+ public void foo() throws Exception {
+ System.out.println("Hello anno");
+ }
+ }
+
+ static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
index d2e241e..dd9a582 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -65,11 +64,10 @@
inspector -> {
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject.uniqueFieldWithOriginalName("f"), isAbsent());
+ assertThat(aClassSubject.uniqueFieldWithOriginalName("f"), isPresent());
})
- // TODO(b/379034741): Should succeed with expected output.
.run(parameters.getRuntime(), Main.class, B.class.getTypeName())
- .assertSuccessWithEmptyOutput();
+ .assertSuccessWithOutputLines("Hello, world!");
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 03275f0..3059040 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -150,9 +150,7 @@
);
Consumer<InternalOptions> optionsConsumer =
- options -> {
- options.testing.enableDeadSwitchCaseElimination = false;
- };
+ options -> options.testing.enableDeadSwitchCaseElimination = false;
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
DexEncodedMethod method = getMethod(processedApplication, signature);
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
index 9b61b25..f251a9c 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
@@ -70,19 +70,14 @@
private void inspectMethod(
MethodSubject methodSubject, boolean maybeNullReceiver, boolean maybeSubtype) {
assertThat(methodSubject, isPresent());
- assertThat(
- methodSubject, onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("equals")));
- assertThat(
- methodSubject,
- onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("hashCode")));
+ assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("equals")));
+ assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("hashCode")));
assertThat(
methodSubject,
onlyIf(
maybeNullReceiver,
anyOf(invokesMethodWithName("getClass"), invokesMethodWithName("requireNonNull"))));
- assertThat(
- methodSubject,
- onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("toString")));
+ assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("toString")));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 693a077..181a85a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -118,7 +118,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
- if (enableVerticalClassMerging && parameters.canInitNewInstanceUsingSuperclassConstructor()) {
+ if (enableVerticalClassMerging) {
// Verify that TestClass.field has been removed.
assertEquals(1, testClassSubject.allFields().size());
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 9642e4e..237753d 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -450,8 +450,8 @@
);
DexCode code = method.getCode().asDexCode();
// TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
- // expectation might need changing with other optimizations.
+ // expectation might need changing with other optimizations.
// TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
- assertEquals(26, code.instructions.length);
+ assertEquals(27, code.instructions.length);
}
}
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 25e8d89..edacb15 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.dex.code.DexConstWideHigh16;
import com.android.tools.r8.dex.code.DexDivInt;
import com.android.tools.r8.dex.code.DexDivInt2Addr;
-import com.android.tools.r8.dex.code.DexGoto;
import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.dex.code.DexInvokeStatic;
import com.android.tools.r8.dex.code.DexInvokeStaticRange;
@@ -1294,7 +1293,7 @@
assertTrue(code.instructions[1] instanceof DexInvokeStatic);
assertTrue(code.instructions[2] instanceof DexMoveResult);
assertTrue(code.instructions[3] instanceof DexDivInt2Addr);
- assertTrue(code.instructions[4] instanceof DexGoto);
+ assertTrue(code.instructions[4] instanceof DexReturn);
assertTrue(code.instructions[5] instanceof DexConst4);
assertTrue(code.instructions[6] instanceof DexReturn);
DexInvokeStatic invoke = (DexInvokeStatic) code.instructions[1];
diff --git a/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
index ba16ee1..692c387 100644
--- a/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
@@ -192,7 +192,9 @@
int index = 0;
List<KotlinCompilerVersion> compilerVersions;
if (withDevCompiler) {
- compilerVersions = ImmutableList.of(KotlinCompilerVersion.KOTLIN_DEV);
+ compilerVersions =
+ ImmutableList.of(
+ KotlinCompilerVersion.KOTLINC_2_1_0_BETA1, KotlinCompilerVersion.KOTLIN_DEV);
} else if (withOldCompilers) {
compilerVersions =
Arrays.stream(KotlinCompilerVersion.values())
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
rename to src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 4c2c44a..18c8f65 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -36,13 +36,14 @@
import org.junit.Assert;
import org.junit.Test;
-abstract class AbstractBackportTest extends TestBase {
+public abstract class AbstractBackportTest extends TestBase {
protected final TestParameters parameters;
private final ClassInfo targetClass;
private final ClassInfo testClass;
private final Path testJar;
private final String testClassName;
private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
+ private final Int2IntSortedMap staticGetCounts = new Int2IntAVLTreeMap();
private final Set<String> ignoredInvokes = new HashSet<>();
private static class ClassInfo {
@@ -85,22 +86,22 @@
}
}
- AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
- Class<?> testClass) {
+ protected AbstractBackportTest(
+ TestParameters parameters, Class<?> targetClass, Class<?> testClass) {
this(parameters, new ClassInfo(targetClass), new ClassInfo(testClass), null, null);
}
- AbstractBackportTest(
+ protected AbstractBackportTest(
TestParameters parameters, Class<?> targetClass, List<byte[]> testClassFileData) {
this(parameters, new ClassInfo(targetClass), new ClassInfo(testClassFileData), null, null);
}
- AbstractBackportTest(
+ protected AbstractBackportTest(
TestParameters parameters, String className, List<byte[]> testClassFileData) {
this(parameters, new ClassInfo(className), new ClassInfo(testClassFileData), null, null);
}
- AbstractBackportTest(
+ protected AbstractBackportTest(
TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
this(
parameters,
@@ -110,8 +111,8 @@
null);
}
- AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
- Path testJar, String testClassName) {
+ public AbstractBackportTest(
+ TestParameters parameters, Class<?> targetClass, Path testJar, String testClassName) {
this(parameters, new ClassInfo(targetClass), null, testJar, testClassName);
}
@@ -136,20 +137,30 @@
this.testClassName = testClassName;
}
- // Assume all method calls will be rewritten on the lowest API level.
+ // Assume all method calls and static gets will be rewritten on the lowest API level.
invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0);
+ staticGetCounts.put(AndroidApiLevel.B.getLevel(), 0);
}
- void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
+ protected void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount);
}
+ void registerFieldTarget(AndroidApiLevel apiLevel, int getStaticCount) {
+ staticGetCounts.put(apiLevel.getLevel(), getStaticCount);
+ }
+
private int getTargetInvokesCount(AndroidApiLevel apiLevel) {
int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
return invokeStaticCounts.get(key);
}
- void ignoreInvokes(String methodName) {
+ private int getTargetGetCount(AndroidApiLevel apiLevel) {
+ int key = staticGetCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
+ return staticGetCounts.get(key);
+ }
+
+ protected void ignoreInvokes(String methodName) {
ignoredInvokes.add(methodName);
}
@@ -243,6 +254,27 @@
+ actualTargetInvokes
+ ": "
+ javaInvokeStatics, expectedTargetInvokes, actualTargetInvokes);
+
+ List<InstructionSubject> javaStaticGets =
+ testSubject.allMethods().stream()
+ .flatMap(MethodSubject::streamInstructions)
+ .filter(InstructionSubject::isStaticGet)
+ .filter(is -> is.getField().holder.toSourceString().equals(targetClass.getName()))
+ .collect(toList());
+
+ long expectedTargetStaticGets = getTargetGetCount(apiLevel);
+ long actualTargetStaticGets = javaStaticGets.size();
+ assertEquals(
+ "Expected "
+ + expectedTargetStaticGets
+ + " static gets on "
+ + targetClass.getName()
+ + " but found "
+ + actualTargetStaticGets
+ + ": "
+ + javaStaticGets,
+ expectedTargetStaticGets,
+ actualTargetStaticGets);
}
public String getTestClassName() {
@@ -250,7 +282,7 @@
}
/** JUnit {@link Assert} isn't available in the VM runtime. This is a mini mirror of its API. */
- static abstract class MiniAssert {
+ public abstract static class MiniAssert {
static void assertTrue(boolean value) {
assertEquals(true, value);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
rename to src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
index 2e61664..76aa62c 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
@@ -11,13 +11,12 @@
import java.lang.annotation.Target;
/**
- * Denote a method that contains invoke instructions on the target class which should be ignored
- * in the counts. This is useful for using other functionality of the target class to verify the
+ * Denote a method that contains invoke instructions on the target class which should be ignored in
+ * the counts. This is useful for using other functionality of the target class to verify the
* behavior of the backport.
*
- * Methods with this annotation will never be inlined.
+ * <p>Methods with this annotation will never be inlined.
*/
@Target(METHOD)
@Retention(RUNTIME)
-@interface IgnoreInvokes {
-}
+public @interface IgnoreInvokes {}
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index de1aec9..da0c617 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -134,6 +134,10 @@
return syntheticClass(classReference, naming.BACKPORT, id);
}
+ public static ClassReference syntheticBackportWithForwardingClass(Class<?> clazz, int id) {
+ return syntheticClass(clazz, naming.BACKPORT_WITH_FORWARDING, id);
+ }
+
public static ClassReference syntheticBackportWithForwardingClass(
ClassReference classReference, int id) {
return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);
diff --git a/third_party/android_jar/lib-v36.tar.gz.sha1 b/third_party/android_jar/lib-v36.tar.gz.sha1
new file mode 100644
index 0000000..939f711
--- /dev/null
+++ b/third_party/android_jar/lib-v36.tar.gz.sha1
@@ -0,0 +1 @@
+f6fe8070ccad90b10d1bcc28a90f5befd3d9afb4
\ No newline at end of file
diff --git a/third_party/android_jar/libcore_latest.tar.gz.sha1 b/third_party/android_jar/libcore_latest.tar.gz.sha1
index 8200e76..4bcde1b 100644
--- a/third_party/android_jar/libcore_latest.tar.gz.sha1
+++ b/third_party/android_jar/libcore_latest.tar.gz.sha1
@@ -1 +1 @@
-96a23da90f1b9412821cef769f3524ef6b05436b
\ No newline at end of file
+6ea109a75610a6f2e173a361bd2345e622b8be06
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 966e156c..b81dbaf 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-0aab829fe216f39035a282024966bbd26bea3a26
\ No newline at end of file
+912f0e6d9fb5e1789c458801f3ed8e6c4e700767
\ No newline at end of file
diff --git a/third_party/google/google-java-format/1.14.0.tar.gz.sha1 b/third_party/google/google-java-format/1.14.0.tar.gz.sha1
deleted file mode 100644
index 4989c27..0000000
--- a/third_party/google/google-java-format/1.14.0.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b26f4b8c825401d198d54d5e230779fe7fc3c132
\ No newline at end of file
diff --git a/third_party/google/google-java-format/1.24.0.tar.gz.sha1 b/third_party/google/google-java-format/1.24.0.tar.gz.sha1
new file mode 100644
index 0000000..a5ed90d
--- /dev/null
+++ b/third_party/google/google-java-format/1.24.0.tar.gz.sha1
@@ -0,0 +1 @@
+f15e5757395aaeb77e9940884c46b29f1abfade7
\ No newline at end of file
diff --git a/tools/add-android-sdk.py b/tools/add-android-sdk.py
index e299172..5d00eab 100755
--- a/tools/add-android-sdk.py
+++ b/tools/add-android-sdk.py
@@ -21,6 +21,10 @@
required=True,
metavar=('<name>'),
help='Name of the SDK, either API level or code name')
+ parser.add_argument('--api-level',
+ '--api_level',
+ metavar=('<level>'),
+ help='API level to add this as in third_party')
return parser.parse_args()
@@ -60,7 +64,15 @@
print('Path %s does not exist' % source)
sys.exit(1)
- destination = utils.get_android_jar_dir(args.sdk_name)
+ api_level = -1
+ try:
+ api_level = int(args.api_level if args.api_level else args.sdk_name)
+ except:
+ print('API level "%s" must be an integer'
+ % (args.api_level if args.api_level else args.sdk_name))
+ sys.exit(1)
+
+ destination = utils.get_android_jar_dir(api_level)
# Remove existing if present.
shutil.rmtree(destination, ignore_errors=True)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 43267ef..c27fcf8 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -150,6 +150,10 @@
help='Run as a platform build',
default=False,
action='store_true')
+ parser.add_argument('--optimized-resource-shrinking',
+ help='Use optimized resource shrinking',
+ default=False,
+ action='store_true')
parser.add_argument('--compilation-mode',
'--compilation_mode',
help='Run compilation in specified mode',
@@ -427,6 +431,10 @@
return True
return build_properties.get('android-platform-build') == 'true'
+def determine_optimized_resource_shrinking(args, build_properties):
+ if args.optimized_resource_shrinking:
+ return True
+ return build_properties.get('optimized-resource-shrinking') == 'true'
def determine_enable_missing_library_api_modeling(args, build_properties):
if args.enable_missing_library_api_modeling:
@@ -600,6 +608,8 @@
classfile = determine_class_file(args, build_properties)
android_platform_build = determine_android_platform_build(
args, build_properties)
+ optimized_resource_shrinking = determine_optimized_resource_shrinking(
+ args, build_properties)
enable_missing_library_api_modeling = determine_enable_missing_library_api_modeling(
args, build_properties)
mode = determine_compilation_mode(args, build_properties)
@@ -715,6 +725,8 @@
cmd.extend(['--classfile'])
if android_platform_build:
cmd.extend(['--android-platform-build'])
+ if optimized_resource_shrinking:
+ cmd.extend(['--optimized-resource-shrinking'])
if enable_missing_library_api_modeling:
cmd.extend(['--enable-missing-library-api-modeling'])
if args.threads:
diff --git a/tools/d8.py b/tools/d8.py
index 636b7e7..b831849 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -17,6 +17,11 @@
'--commit_hash',
help='Commit hash of D8 to use.',
default=None)
+ parser.add_option('--disable-assertions',
+ '--disable_assertions',
+ help='Disable assertions when running',
+ default=False,
+ action='store_true')
parser.add_option('--print-runtimeraw',
'--print_runtimeraw',
metavar='BENCHMARKNAME',
@@ -40,6 +45,7 @@
return toolhelper.run('d8',
d8_args,
build=not options.no_build,
+ disable_assertions=options.disable_assertions,
jar=utils.find_r8_jar_from_options(options),
main='com.android.tools.r8.D8',
time_consumer=time_consumer)
diff --git a/tools/disasm.py b/tools/disasm.py
index e341557..e050d9c 100755
--- a/tools/disasm.py
+++ b/tools/disasm.py
@@ -7,4 +7,4 @@
import toolhelper
if __name__ == '__main__':
- sys.exit(toolhelper.run('disasm', sys.argv[1:], debug=False))
+ sys.exit(toolhelper.run('disasm', sys.argv[1:], disable_assertions=True))
diff --git a/tools/fmt-diff.py b/tools/fmt-diff.py
index 0e4ada7..3a63fcf 100755
--- a/tools/fmt-diff.py
+++ b/tools/fmt-diff.py
@@ -13,8 +13,8 @@
from subprocess import Popen, PIPE
GOOGLE_JAVA_FORMAT_DIFF = os.path.join(utils.THIRD_PARTY, 'google',
- 'google-java-format', '1.14.0',
- 'google-java-format-1.14.0', 'scripts',
+ 'google-java-format', '1.24.0',
+ 'google-java-format-1.24.0', 'scripts',
'google-java-format-diff.py')
GOOGLE_YAPF = os.path.join(utils.THIRD_PARTY, 'google/yapf/20231013')
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
index 2356ac1..c51f861 100755
--- a/tools/minify_tool.py
+++ b/tools/minify_tool.py
@@ -125,7 +125,7 @@
start_time = time.time()
return_code = toolhelper.run('r8',
args,
- debug=debug,
+ disable_assertions=not debug,
build=build,
track_memory_file=track_memory_file,
extra_args=java_args)
diff --git a/tools/r8.py b/tools/r8.py
index 5a2f5e3..a6bf42e 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -23,12 +23,11 @@
'Enable Java debug agent and suspend compilation (default disabled)',
default=False,
action='store_true')
- parser.add_option(
- '--ea',
- help=
- 'Enable Java assertions when running the compiler (default disabled)',
- default=False,
- action='store_true')
+ parser.add_option('--disable-assertions',
+ '--disable_assertions',
+ help='Disable assertions when running',
+ default=False,
+ action='store_true')
parser.add_option('--lib-android',
help='Add the android.jar for the given API level',
default=None,
@@ -64,7 +63,7 @@
return toolhelper.run('r8',
r8_args,
build=not options.no_build,
- debug=options.ea,
+ disable_assertions=options.disable_assertions,
debug_agent=options.debug_agent,
jar=utils.find_r8_jar_from_options(options),
main='com.android.tools.r8.R8',
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index ffe4298..f106022 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -523,7 +523,7 @@
exit_code = toolhelper.run(tool,
args,
build=should_build(options),
- debug=not options.disable_assertions,
+ disable_assertions=options.disable_assertions,
quiet=quiet,
jar=jar,
main=main)
@@ -743,7 +743,7 @@
tool,
args,
build=False,
- debug=not options.disable_assertions,
+ disable_assertions=options.disable_assertions,
profile=options.profile,
track_memory_file=options.track_memory_to_file,
extra_args=extra_args,
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index cfc386c..15fa557 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -15,7 +15,7 @@
def run(tool,
args,
build=None,
- debug=True,
+ disable_assertions=False,
profile=False,
track_memory_file=None,
extra_args=None,
@@ -51,7 +51,7 @@
cmd.append(
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'
)
- if debug:
+ if not disable_assertions:
cmd.append('-ea')
if profile:
cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')