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')