Merge commit 'ecd7ced0dd6cdd020f111a7fac9c41e25866b96f' into dev-release
diff --git a/.gitignore b/.gitignore
index 26634e8..8534e4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,6 +80,8 @@
third_party/gradle/gradle
third_party/gradle/gradle.tar.gz
third_party/internal/*
+third_party/internal-apps/youtube_15_33
+third_party/internal-apps/youtube_15_33.tar.gz
third_party/iosched_2019
third_party/iosched_2019.tar.gz
third_party/jacoco/0.8.2/*
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 5960a22..a4e4210 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -145,7 +145,7 @@
mixins: "normal"
priority: 26
recipe {
- properties_j: "test_options:[\"--desugared-library\", \"HEAD\"]"
+ properties_j: "test_options:[\"--no_internal\", \"--desugared-library\", \"HEAD\"]"
}
}
builders {
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 2cf08a6..0e7614f 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -175,6 +175,14 @@
category: "win release"
short_name: "win"
}
+
+}
+consoles {
+ id: "kotlin"
+ name: "Kotlin"
+ repo_url: "https://github.com/google/kotlin"
+ refs: "regexp:refs/heads/.*"
+ manifest_name: "REVISION"
builders {
name: "buildbucket/luci.r8.ci/kotlin-builder"
category: "kotlin"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 998b838..cdbcdd8 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.RecordRewritingNamingLens;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.origin.Origin;
@@ -184,14 +185,6 @@
AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
SyntheticItems.collectSyntheticInputs(appView);
- if (!options.mainDexKeepRules.isEmpty()) {
- MainDexInfo mainDexInfo =
- new GenerateMainDexList(options)
- .traceMainDex(
- executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
- appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
- }
-
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
if (AssertionsRewriter.isEnabled(options)) {
@@ -267,26 +260,22 @@
Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
+ NamingLens namingLens = NamingLens.getIdentityLens();
+ if (appView.rewritePrefix.isRewriting()) {
+ namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+ }
+ if (appView.options().shouldDesugarRecords()) {
+ namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
+ }
if (options.isGeneratingClassFiles()) {
// TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
SyntheticFinalization.finalize(appView);
- new CfApplicationWriter(
- appView,
- marker,
- GraphLens.getIdentityLens(),
- appView.rewritePrefix.isRewriting()
- ? PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView)
- : NamingLens.getIdentityLens(),
- null)
+ new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens, null)
.write(options.getClassFileConsumer());
} else {
- NamingLens namingLens;
if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) {
// All inputs are either dex or cf, or there is nothing to rewrite.
- namingLens =
- hasDexResources
- ? NamingLens.getIdentityLens()
- : PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+ namingLens = hasDexResources ? NamingLens.getIdentityLens() : namingLens;
new GenericSignatureRewriter(appView, namingLens)
.run(appView.appInfo().classes(), executor);
new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
@@ -298,7 +287,13 @@
timing.begin("Rewrite non-dex inputs");
DexApplication app =
rewriteNonDexInputs(
- appView, inputApp, options, executor, timing, appView.appInfo().app());
+ appView,
+ inputApp,
+ options,
+ executor,
+ timing,
+ appView.appInfo().app(),
+ namingLens);
timing.end();
appView.setAppInfo(
new AppInfo(
@@ -307,6 +302,16 @@
namingLens = NamingLens.getIdentityLens();
}
+ // Since tracing is not lens aware, this needs to be done prior to synthetic finalization
+ // which will construct a graph lens.
+ if (!options.mainDexKeepRules.isEmpty()) {
+ MainDexInfo mainDexInfo =
+ new GenerateMainDexList(options)
+ .traceMainDex(
+ executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
+ appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
+ }
+
// TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
SyntheticFinalization.finalize(appView);
@@ -337,7 +342,8 @@
InternalOptions options,
ExecutorService executor,
Timing timing,
- DexApplication app)
+ DexApplication app,
+ NamingLens desugaringLens)
throws IOException, ExecutionException {
// TODO(b/154575955): Remove the naming lens in D8.
appView
@@ -363,17 +369,15 @@
appView.appInfo().getSyntheticItems().commit(cfApp),
appView.appInfo().getMainDexInfo()));
ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
- NamingLens prefixRewritingNamingLens =
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
- new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+ new GenericSignatureRewriter(appView, desugaringLens)
.run(appView.appInfo().classes(), executor);
- new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForD8(executor);
+ new KotlinMetadataRewriter(appView, desugaringLens).runForD8(executor);
new ApplicationWriter(
appView,
null,
GraphLens.getIdentityLens(),
InitClassLens.getDefault(),
- prefixRewritingNamingLens,
+ desugaringLens,
null,
convertedCfFiles)
.write(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c5eac58..05c2e8e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -515,6 +515,7 @@
return builder
.setIntermediate(intermediate)
.setDesugaredLibraryConfiguration(libraryConfiguration)
+ .setMainDexKeepRules(mainDexKeepRules)
.build();
}
}
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index cd2ac79..3feeee3 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -8,8 +8,10 @@
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.ThreadUtils;
+import java.util.List;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -49,6 +51,7 @@
private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
private final FeatureSplitConfiguration featureSplitConfiguration;
private final ProguardConfiguration proguardConfiguration;
+ private final List<ProguardConfigurationRule> mainDexKeepRules;
// Reporting only.
private final boolean dumpInputToFile;
@@ -68,6 +71,7 @@
Optional<Boolean> forceProguardCompatibility,
FeatureSplitConfiguration featureSplitConfiguration,
ProguardConfiguration proguardConfiguration,
+ List<ProguardConfigurationRule> mainDexKeepRules,
boolean dumpInputToFile) {
this.tool = tool;
this.compilationMode = compilationMode;
@@ -83,6 +87,7 @@
this.forceProguardCompatibility = forceProguardCompatibility;
this.featureSplitConfiguration = featureSplitConfiguration;
this.proguardConfiguration = proguardConfiguration;
+ this.mainDexKeepRules = mainDexKeepRules;
this.dumpInputToFile = dumpInputToFile;
}
@@ -137,6 +142,14 @@
return proguardConfiguration == null ? null : proguardConfiguration.getParsedConfiguration();
}
+ public boolean hasMainDexKeepRules() {
+ return mainDexKeepRules != null;
+ }
+
+ public List<ProguardConfigurationRule> getMainDexKeepRules() {
+ return mainDexKeepRules;
+ }
+
public boolean dumpInputToFile() {
return dumpInputToFile;
}
@@ -161,6 +174,7 @@
private DesugaredLibraryConfiguration desugaredLibraryConfiguration;
private FeatureSplitConfiguration featureSplitConfiguration;
private ProguardConfiguration proguardConfiguration;
+ private List<ProguardConfigurationRule> mainDexKeepRules;
// Reporting only.
private boolean dumpInputToFile;
@@ -241,6 +255,11 @@
return this;
}
+ public Builder setMainDexKeepRules(List<ProguardConfigurationRule> mainDexKeepRules) {
+ this.mainDexKeepRules = mainDexKeepRules;
+ return this;
+ }
+
public DumpOptions build() {
return new DumpOptions(
tool,
@@ -257,6 +276,7 @@
forceProguardCompatibility,
featureSplitConfiguration,
proguardConfiguration,
+ mainDexKeepRules,
dumpInputToFile);
}
}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 07783b3..2a794ab 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -21,14 +22,13 @@
import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SortingStringConsumer;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -42,21 +42,13 @@
this.options = options;
}
- private List<String> run(AndroidApp app, ExecutorService executor)
+ private void run(AndroidApp app, ExecutorService executor, SortingStringConsumer consumer)
throws IOException {
try {
- // TODO(b/178231294): Clean up this such that we do not both return the result and call the
- // consumer.
DexApplication application = new ApplicationReader(app, options, timing).read(executor);
- List<String> result = new ArrayList<>();
traceMainDex(executor, application, MainDexInfo.none())
- .forEach(type -> result.add(type.toBinaryName() + ".class"));
- Collections.sort(result);
- if (options.mainDexListConsumer != null) {
- options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
- options.mainDexListConsumer.finished(options.reporter);
- }
- return result;
+ .forEach(type -> consumer.accept(type.toBinaryName() + ".class", options.reporter));
+ consumer.finished(options.reporter);
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
}
@@ -137,11 +129,11 @@
/**
* Main API entry for computing the main-dex list.
*
- * The main-dex list is represented as a list of strings, each string specifies one class to
+ * <p>The main-dex list is represented as a list of strings, each string specifies one class to
* keep in the primary dex file (<code>classes.dex</code>).
*
- * A class is specified using the following format: "com/example/MyClass.class". That is
- * "/" as separator between package components, and a trailing ".class".
+ * <p>A class is specified using the following format: "com/example/MyClass.class". That is "/" as
+ * separator between package components, and a trailing ".class".
*
* @param command main dex-list generator command.
* @param executor executor service from which to get threads for multi-threaded processing.
@@ -151,17 +143,28 @@
throws CompilationFailedException {
AndroidApp app = command.getInputApp();
InternalOptions options = command.getInternalOptions();
- Box<List<String>> result = new Box<>();
+ List<String> result = new ArrayList<>();
ExceptionUtils.withMainDexListHandler(
command.getReporter(),
() -> {
try {
- result.set(new GenerateMainDexList(options).run(app, executor));
+ new GenerateMainDexList(options)
+ .run(
+ app,
+ executor,
+ new SortingStringConsumer(
+ new ForwardingConsumer(options.mainDexListConsumer) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ result.add(string);
+ super.accept(string, handler);
+ }
+ }));
} finally {
executor.shutdown();
}
});
- return result.get();
+ return result;
}
public static void main(String[] args) throws CompilationFailedException {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index b08a738..85bc305 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.JoiningStringConsumer;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
@@ -115,7 +116,7 @@
factory,
getAppBuilder().build(),
mainDexKeepRules,
- mainDexListConsumer,
+ new JoiningStringConsumer(mainDexListConsumer, "\n"),
mainDexKeptGraphConsumer,
getReporter());
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9158d3b..b77c8ed 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -495,16 +495,13 @@
&& options.isShrinking()) {
timing.begin("HorizontalClassMerger");
HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
- DirectMappedDexApplication.Builder appBuilder =
- appView.appInfo().app().asDirect().builder();
HorizontalClassMergerResult horizontalClassMergerResult =
- merger.run(appBuilder, runtimeTypeCheckInfo);
+ merger.run(runtimeTypeCheckInfo);
if (horizontalClassMergerResult != null) {
// Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
// allocations sites, fields accesses, etc. are correctly transferred to the target
// classes.
- appView.rewriteWithLensAndApplication(
- horizontalClassMergerResult.getGraphLens(), appBuilder.build());
+ appView.rewriteWithLens(horizontalClassMergerResult.getGraphLens());
horizontalClassMergerResult
.getFieldAccessInfoCollectionModifier()
.modify(appViewWithLiveness);
@@ -513,7 +510,7 @@
PrunedItems.builder()
.setPrunedApp(appView.appInfo().app())
.addRemovedClasses(appView.horizontallyMergedClasses().getSources())
- .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets())
+ .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getSources())
.build());
}
timing.end();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d07b18..9663d3f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1002,6 +1002,7 @@
.setForceProguardCompatibility(forceProguardCompatibility)
.setFeatureSplitConfiguration(featureSplitConfiguration)
.setProguardConfiguration(proguardConfiguration)
+ .setMainDexKeepRules(mainDexKeepRules)
.setDesugaredLibraryConfiguration(libraryConfiguration)
.build();
}
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index b01f9a3..77abde6 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -240,7 +240,7 @@
Stream<DexAnnotation> classAnnotations = classDef.annotations().stream();
Stream<DexAnnotation> fieldAnnotations =
Streams.stream(classDef.fields())
- .filter(DexEncodedField::hasAnnotation)
+ .filter(DexEncodedField::hasAnnotations)
.flatMap(f -> f.annotations().stream());
Stream<DexAnnotation> methodAnnotations =
Streams.stream(classDef.methods())
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 09f14ce..9862f27 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -59,6 +59,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Monitor;
@@ -396,10 +397,14 @@
builder.append(callSite.methodName);
builder.append(callSite.methodProto.toDescriptorString());
if (callSite.bootstrapArgs.size() > 1) {
- DexMethodHandle handle = callSite.bootstrapArgs.get(1).asDexValueMethodHandle().getValue();
- builder.append(", handle:");
- builder.append(handle.toSourceString());
- builder.append(", itf: ").append(handle.isInterface);
+ DexValue.DexValueMethodHandle dexValueMethodHandle =
+ callSite.bootstrapArgs.get(1).asDexValueMethodHandle();
+ if (dexValueMethodHandle != null) {
+ DexMethodHandle handle = dexValueMethodHandle.getValue();
+ builder.append(", handle:");
+ builder.append(handle.toSourceString());
+ builder.append(", itf: ").append(handle.isInterface);
+ }
}
builder.append(", bsm:");
appendMethod(bootstrapMethod.asMethod());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 22421de..4fa3241 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -82,6 +82,6 @@
InitClassLens initClassLens) {
// ..., arrayref →
// ..., length
- frameBuilder.popAndDiscard(factory.objectArrayType).push(factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectArrayType).push(factory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index c82e53e..4f851a0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -128,7 +128,7 @@
InitClassLens initClassLens) {
// ..., arrayref, index →
// ..., value
- frameBuilder.popAndDiscard(factory.objectArrayType, factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectArrayType, factory.intType);
frameBuilder.push(FrameType.fromMemberType(type, factory));
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 5d64188..cc0cd63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -120,6 +120,6 @@
// ...
frameBuilder
.popAndDiscard(FrameType.fromMemberType(type, factory))
- .popAndDiscard(factory.objectArrayType, factory.intType);
+ .popAndDiscardInitialized(factory.objectArrayType, factory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 721f3f7..1e25ca7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -26,7 +26,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfCheckCast extends CfInstruction {
+public class CfCheckCast extends CfInstruction implements CfTypeInstruction {
private final DexType type;
@@ -34,11 +34,27 @@
this.type = type;
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
@Override
+ public CfInstruction withType(DexType newType) {
+ return new CfCheckCast(newType);
+ }
+
+ @Override
public int getCompareToId() {
return Opcodes.CHECKCAST;
}
@@ -102,6 +118,6 @@
InitClassLens initClassLens) {
// ..., objectref →
// ..., objectref
- frameBuilder.popAndDiscard(factory.objectType).push(type);
+ frameBuilder.popAndDiscardInitialized(factory.objectType).push(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 863dce0..a6a0933 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -26,7 +26,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
-public class CfConstClass extends CfInstruction {
+public class CfConstClass extends CfInstruction implements CfTypeInstruction {
private final DexType type;
@@ -45,11 +45,27 @@
return type.acceptCompareTo(((CfConstClass) other).type, visitor);
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
@Override
+ public CfInstruction withType(DexType newType) {
+ return new CfConstClass(newType);
+ }
+
+ @Override
public void write(
AppView<?> appView,
ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index be6176b..2e8eedf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
@@ -189,7 +191,7 @@
case Opcodes.GETFIELD:
// ..., objectref →
// ..., value
- frameBuilder.popAndDiscard(field.holder).push(field.type);
+ frameBuilder.popAndDiscardInitialized(field.holder).push(field.type);
return;
case Opcodes.GETSTATIC:
// ..., →
@@ -199,12 +201,18 @@
case Opcodes.PUTFIELD:
// ..., objectref, value →
// ...,
- frameBuilder.popAndDiscard(field.holder, field.type);
+ frameBuilder
+ .popAndDiscardInitialized(field.type)
+ .pop(
+ field.holder,
+ or(
+ frameBuilder::isUninitializedThisAndTarget,
+ frameBuilder::isAssignableAndInitialized));
return;
case Opcodes.PUTSTATIC:
// ..., value →
// ...
- frameBuilder.pop(field.type);
+ frameBuilder.popAndDiscardInitialized(field.type);
return;
default:
throw new Unreachable("Unexpected opcode " + opcode);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 713d764..98bc101 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -371,6 +371,14 @@
return stack.size();
}
+ public int computeStackSize() {
+ int size = 0;
+ for (FrameType frameType : stack) {
+ size += frameType.isWide() ? 2 : 1;
+ }
+ return size;
+ }
+
private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) {
assert stackCount == stack.size();
if (stackCount == 0) {
@@ -452,7 +460,7 @@
DexType returnType,
DexItemFactory factory,
InitClassLens initClassLens) {
- frameBuilder.verifyFrameAndSet(this);
+ frameBuilder.checkFrameAndSet(this);
}
public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index 7ce2440..9272226 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
import com.android.tools.r8.graph.DexItemFactory;
@@ -39,6 +41,7 @@
private final DexItemFactory factory;
private final List<CfTryCatch> tryCatchRanges;
private final GraphLens graphLens;
+ private final int maxStackHeight;
private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
private final Set<CfLabel> tryCatchRangeLabels;
@@ -49,13 +52,15 @@
List<CfTryCatch> tryCatchRanges,
BiPredicate<DexType, DexType> isJavaAssignable,
DexItemFactory factory,
- GraphLens graphLens) {
+ GraphLens graphLens,
+ int maxStackHeight) {
this.context = context;
this.stateMap = stateMap;
this.tryCatchRanges = tryCatchRanges;
this.isJavaAssignable = isJavaAssignable;
this.factory = factory;
this.graphLens = graphLens;
+ this.maxStackHeight = maxStackHeight;
throwStack = ImmutableDeque.of(FrameType.initialized(factory.throwableType));
// Compute all labels that marks a start or end to catch ranges.
tryCatchRangeLabels = Sets.newIdentityHashSet();
@@ -70,50 +75,60 @@
}
public FrameType readLocal(int index, DexType expectedType) {
- verifyFrameIsSet();
+ checkFrameIsSet();
FrameType frameType = currentFrame.getLocals().get(index);
if (frameType == null) {
throw CfCodeStackMapValidatingException.error("No local at index " + index);
}
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(
+ frameType,
+ expectedType,
+ or(
+ this::isUninitializedThisAndTarget,
+ this::isUninitializedNewAndTarget,
+ this::isAssignableAndInitialized));
return frameType;
}
public void storeLocal(int index, FrameType frameType) {
- verifyFrameIsSet();
+ checkFrameIsSet();
currentFrame.getLocals().put(index, frameType);
}
public FrameType pop() {
- verifyFrameIsSet();
+ checkFrameIsSet();
if (currentFrame.getStack().isEmpty()) {
throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack");
}
return currentFrame.getStack().removeLast();
}
- public FrameType pop(DexType expectedType) {
+ public FrameType popInitialized(DexType expectedType) {
+ return pop(expectedType, this::isAssignableAndInitialized);
+ }
+
+ public FrameType pop(DexType expectedType, BiPredicate<FrameType, DexType> isAssignable) {
FrameType frameType = pop();
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(frameType, expectedType, isAssignable);
return frameType;
}
- public CfFrameVerificationHelper popAndDiscard(DexType... expectedTypes) {
- verifyFrameIsSet();
+ public CfFrameVerificationHelper popAndDiscardInitialized(DexType... expectedTypes) {
+ checkFrameIsSet();
for (int i = expectedTypes.length - 1; i >= 0; i--) {
- pop(expectedTypes[i]);
+ popInitialized(expectedTypes[i]);
}
return this;
}
public FrameType pop(FrameType expectedType) {
FrameType frameType = pop();
- verifyIsAssignable(frameType, expectedType);
+ checkIsAssignable(frameType, expectedType);
return frameType;
}
public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) {
- verifyFrameIsSet();
+ checkFrameIsSet();
for (int i = expectedTypes.length - 1; i >= 0; i--) {
pop(expectedTypes[i]);
}
@@ -121,18 +136,30 @@
}
public void popAndInitialize(DexType context, DexType methodHolder) {
- verifyFrameIsSet();
- FrameType objectRef = pop(factory.objectType);
+ checkFrameIsSet();
+ FrameType objectRef =
+ pop(
+ factory.objectType,
+ or(this::isUninitializedThisAndTarget, this::isUninitializedNewAndTarget));
CfFrame newFrame =
currentFrame.markInstantiated(
objectRef, objectRef.isUninitializedNew() ? methodHolder : context);
setNoFrame();
- verifyFrameAndSet(newFrame);
+ checkFrameAndSet(newFrame);
}
public CfFrameVerificationHelper push(FrameType type) {
- verifyFrameIsSet();
+ checkFrameIsSet();
currentFrame.getStack().addLast(type);
+ if (currentFrame.computeStackSize() > maxStackHeight) {
+ throw CfCodeStackMapValidatingException.error(
+ "The max stack height of "
+ + maxStackHeight
+ + " is violated when pushing type "
+ + type
+ + " to existing stack of size "
+ + currentFrame.getStack().size());
+ }
return this;
}
@@ -153,7 +180,7 @@
if (destinationFrame == null) {
throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
}
- verifyStackIsAssignable(
+ checkStackIsAssignable(
destinationFrame.getStack(), throwStack, factory, isJavaAssignable);
}
}
@@ -162,15 +189,15 @@
return this;
}
- private void verifyFrameIsSet() {
+ private void checkFrameIsSet() {
if (currentFrame == NO_FRAME) {
throw CfCodeStackMapValidatingException.error("Unexpected state change");
}
}
- public void verifyFrameAndSet(CfFrame newFrame) {
+ public void checkFrameAndSet(CfFrame newFrame) {
if (currentFrame != NO_FRAME) {
- verifyFrame(newFrame);
+ checkFrame(newFrame);
}
setFrame(newFrame);
}
@@ -182,7 +209,7 @@
new Int2ReferenceAVLTreeMap<>(frame.getLocals()), new ArrayDeque<>(frame.getStack()));
}
- public void verifyExceptionEdges() {
+ public void checkExceptionEdges() {
for (CfTryCatch currentCatchRange : currentCatchRanges) {
for (CfLabel target : currentCatchRange.targets) {
CfFrame destinationFrame = stateMap.get(target);
@@ -192,7 +219,7 @@
// We have to check all current handler targets have assignable locals and a 1-element
// stack assignable to throwable. It is not required that the the thrown error is
// handled.
- verifyLocalsIsAssignable(
+ checkLocalsIsAssignable(
currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
}
}
@@ -202,19 +229,19 @@
return currentFrame;
}
- public void verifyTarget(CfLabel label) {
- verifyFrame(stateMap.get(label));
+ public void checkTarget(CfLabel label) {
+ checkFrame(stateMap.get(label));
}
- public void verifyFrame(CfFrame destinationFrame) {
+ public void checkFrame(CfFrame destinationFrame) {
if (destinationFrame == null) {
throw CfCodeStackMapValidatingException.error("No destination frame");
}
- verifyFrame(destinationFrame.getLocals(), destinationFrame.getStack());
+ checkFrame(destinationFrame.getLocals(), destinationFrame.getStack());
}
- public void verifyFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
- verifyIsAssignable(
+ public void checkFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
+ checkIsAssignable(
currentFrame.getLocals(),
currentFrame.getStack(),
locals,
@@ -227,30 +254,37 @@
currentFrame = NO_FRAME;
}
- public void clearStack() {
- verifyFrameIsSet();
- currentFrame.getStack().clear();
+ public boolean isUninitializedThisAndTarget(FrameType source, DexType target) {
+ if (!source.isUninitializedThis()) {
+ return false;
+ }
+ return target == factory.objectType || graphLens.lookupClassType(target) == context;
}
- public void verifyIsAssignable(FrameType source, DexType target) {
+ public boolean isUninitializedNewAndTarget(FrameType source, DexType target) {
+ if (!source.isUninitializedNew()) {
+ return false;
+ }
+ return target == factory.objectType || graphLens.lookupClassType(target) == context;
+ }
+
+ public boolean isAssignableAndInitialized(FrameType source, DexType target) {
if (!source.isInitialized()) {
- DexType rewrittenTarget = graphLens.lookupClassType(target);
- if (source.isUninitializedThis() && rewrittenTarget == context) {
- return;
- }
- if (rewrittenTarget == factory.objectType) {
- return;
- }
- throw CfCodeStackMapValidatingException.error(
- "The expected type " + source + " is not assignable to " + target.toSourceString());
+ return false;
}
- if (!isJavaAssignable.test(source.getInitializedType(), target)) {
- throw CfCodeStackMapValidatingException.error(
- "The expected type " + source + " is not assignable to " + target.toSourceString());
- }
+ return isJavaAssignable.test(source.getInitializedType(), target);
}
- public void verifyIsAssignable(FrameType source, FrameType target) {
+ public void checkIsAssignable(
+ FrameType source, DexType target, BiPredicate<FrameType, DexType> predicate) {
+ if (predicate.test(source, target)) {
+ return;
+ }
+ throw CfCodeStackMapValidatingException.error(
+ "The expected type " + source + " is not assignable to " + target.toSourceString());
+ }
+
+ public void checkIsAssignable(FrameType source, FrameType target) {
if (!canBeAssigned(source, target, factory, isJavaAssignable)) {
throw CfCodeStackMapValidatingException.error(
"The expected type " + source + " is not assignable to " + target);
@@ -258,18 +292,18 @@
}
// Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
- public static void verifyIsAssignable(
+ public static void checkIsAssignable(
Int2ReferenceSortedMap<FrameType> sourceLocals,
Deque<FrameType> sourceStack,
Int2ReferenceSortedMap<FrameType> destLocals,
Deque<FrameType> destStack,
DexItemFactory factory,
BiPredicate<DexType, DexType> isJavaAssignable) {
- verifyLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
- verifyStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
+ checkLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
+ checkStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
}
- private static void verifyLocalsIsAssignable(
+ private static void checkLocalsIsAssignable(
Int2ReferenceSortedMap<FrameType> sourceLocals,
Int2ReferenceSortedMap<FrameType> destLocals,
DexItemFactory factory,
@@ -305,7 +339,7 @@
}
}
- private static void verifyStackIsAssignable(
+ private static void checkStackIsAssignable(
Deque<FrameType> sourceStack,
Deque<FrameType> destStack,
DexItemFactory factory,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 2f4a549..b3043c9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -97,7 +97,7 @@
DexType returnType,
DexItemFactory factory,
InitClassLens initClassLens) {
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
frameBuilder.setNoFrame();
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index b331673..7653d51 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -135,8 +135,8 @@
InitClassLens initClassLens) {
// ..., value →
// ...
- frameBuilder.pop(
+ frameBuilder.popAndDiscardInitialized(
type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory));
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 45b426b..3e5c38a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -138,7 +138,7 @@
// ...
DexType type =
this.type.isObject() ? factory.objectType : this.type.toPrimitiveType().toDexType(factory);
- frameBuilder.popAndDiscard(type, type);
- frameBuilder.verifyTarget(target);
+ frameBuilder.popAndDiscardInitialized(type, type);
+ frameBuilder.checkTarget(target);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 815500d..0b8240a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -55,6 +55,11 @@
}
@Override
+ public boolean isInitClass() {
+ return true;
+ }
+
+ @Override
public void write(
AppView<?> appView,
ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index c18eb57..b4d6d43 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -25,7 +25,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfInstanceOf extends CfInstruction {
+public class CfInstanceOf extends CfInstruction implements CfTypeInstruction {
private final DexType type;
@@ -33,11 +33,27 @@
this.type = type;
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
@Override
+ public CfInstruction withType(DexType newType) {
+ return new CfInstanceOf(newType);
+ }
+
+ @Override
public int getCompareToId() {
return Opcodes.INSTANCEOF;
}
@@ -110,6 +126,6 @@
InitClassLens initClassLens) {
// ..., objectref →
// ..., result
- frameBuilder.popAndDiscard(factory.objectType).push(factory.intType);
+ frameBuilder.popAndDiscardInitialized(factory.objectType).push(factory.intType);
}
}
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 c69ba09..1009080 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
@@ -221,6 +221,18 @@
return false;
}
+ public CfTypeInstruction asTypeInstruction() {
+ return null;
+ }
+
+ public boolean isTypeInstruction() {
+ return false;
+ }
+
+ public boolean isInitClass() {
+ return false;
+ }
+
public CfDexItemBasedConstString asDexItemBasedConstString() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index db81a5f..87cae25 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -354,11 +354,11 @@
// OR, for static method calls:
// ..., [arg1, [arg2 ...]] →
// ...
- frameBuilder.popAndDiscard(this.method.proto.parameters.values);
+ frameBuilder.popAndDiscardInitialized(this.method.proto.parameters.values);
if (opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(factory)) {
frameBuilder.popAndInitialize(context, method.holder);
} else if (opcode != Opcodes.INVOKESTATIC) {
- frameBuilder.pop(method.holder);
+ frameBuilder.popInitialized(method.holder);
}
if (this.method.proto.returnType != factory.voidType) {
frameBuilder.push(this.method.proto.returnType);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 88ffb82..3a98a8e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -167,7 +167,7 @@
InitClassLens initClassLens) {
// ..., [arg1, [arg2 ...]] →
// ...
- frameBuilder.popAndDiscard(callSite.methodProto.parameters.values);
+ frameBuilder.popAndDiscardInitialized(callSite.methodProto.parameters.values);
if (callSite.methodProto.returnType != factory.voidType) {
frameBuilder.push(callSite.methodProto.returnType);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 9180511..682c857 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -27,7 +27,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfMultiANewArray extends CfInstruction {
+public class CfMultiANewArray extends CfInstruction implements CfTypeInstruction {
private final DexType type;
private final int dimensions;
@@ -41,10 +41,26 @@
this.dimensions = dimensions;
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
+ @Override
+ public CfInstruction withType(DexType newType) {
+ return new CfMultiANewArray(newType, dimensions);
+ }
+
public int getDimensions() {
return dimensions;
}
@@ -114,7 +130,7 @@
// ..., count1, [count2, ...] →
// ..., arrayref
for (int i = 0; i < dimensions; i++) {
- frameBuilder.pop(factory.intType);
+ frameBuilder.popInitialized(factory.intType);
}
frameBuilder.push(type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 7140404..53fc40c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -26,7 +26,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfNew extends CfInstruction {
+public class CfNew extends CfInstruction implements CfTypeInstruction {
private final DexType type;
@@ -34,11 +34,27 @@
this.type = type;
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
@Override
+ public CfInstruction withType(DexType newType) {
+ return new CfNew(newType);
+ }
+
+ @Override
public int getCompareToId() {
return Opcodes.NEW;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index bd36756..ad50c17 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -28,7 +28,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfNewArray extends CfInstruction {
+public class CfNewArray extends CfInstruction implements CfTypeInstruction {
private final DexType type;
@@ -37,11 +37,27 @@
this.type = type;
}
+ @Override
+ public CfTypeInstruction asTypeInstruction() {
+ return this;
+ }
+
+ @Override
+ public boolean isTypeInstruction() {
+ return true;
+ }
+
+ @Override
public DexType getType() {
return type;
}
@Override
+ public CfInstruction withType(DexType newType) {
+ return new CfNewArray(newType);
+ }
+
+ @Override
public int getCompareToId() {
return type.isPrimitiveArrayType() ? Opcodes.NEWARRAY : Opcodes.ANEWARRAY;
}
@@ -150,6 +166,6 @@
// ..., count →
// ..., arrayref
assert type.isArrayType();
- frameBuilder.popAndDiscard(factory.intType).push(type);
+ frameBuilder.popAndDiscardInitialized(factory.intType).push(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 51d1283..2b4eed5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -113,7 +113,7 @@
DexItemFactory factory,
InitClassLens initClassLens) {
assert returnType != null;
- frameBuilder.popAndDiscard(returnType);
+ frameBuilder.popAndDiscardInitialized(returnType);
frameBuilder.setNoFrame();
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index bfdbe0a..4fd9829 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -368,7 +368,7 @@
// ...
final FrameType pop = frameBuilder.pop();
if (!pop.isWide()) {
- frameBuilder.verifyIsAssignable(pop, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(pop, FrameType.oneWord());
frameBuilder.pop(FrameType.oneWord());
}
return;
@@ -399,7 +399,7 @@
FrameType value1 = frameBuilder.pop(FrameType.oneWord());
FrameType value2 = frameBuilder.pop();
if (!value2.isWide()) {
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value1).push(value3);
} else {
@@ -417,7 +417,7 @@
// ..., value, value
FrameType value1 = frameBuilder.pop();
if (!value1.isWide()) {
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
FrameType value2 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value2);
} else {
@@ -436,7 +436,7 @@
FrameType value1 = frameBuilder.pop();
FrameType value2 = frameBuilder.pop(FrameType.oneWord());
if (!value1.isWide()) {
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value3);
} else {
@@ -466,9 +466,9 @@
FrameType value3 = frameBuilder.pop();
if (!value3.isWide()) {
// (1)
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value3, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value3, FrameType.oneWord());
FrameType value4 = frameBuilder.pop(FrameType.oneWord());
frameBuilder
.push(value2)
@@ -479,13 +479,13 @@
.push(value1);
} else {
// (3)
- frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1);
}
} else if (!value2.isWide()) {
// (2)
- frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+ frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
FrameType value3 = frameBuilder.pop(FrameType.oneWord());
frameBuilder.push(value1).push(value3).push(value2).push(value1);
} else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index d74eb39..b87c87c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
@@ -129,24 +131,34 @@
FrameType pop = frameBuilder.pop();
switch (type) {
case OBJECT:
- frameBuilder.verifyIsAssignable(pop, factory.objectType);
+ frameBuilder.checkIsAssignable(
+ pop,
+ factory.objectType,
+ or(
+ frameBuilder::isUninitializedThisAndTarget,
+ frameBuilder::isUninitializedNewAndTarget,
+ frameBuilder::isAssignableAndInitialized));
frameBuilder.storeLocal(var, pop);
return;
case INT:
- frameBuilder.verifyIsAssignable(pop, factory.intType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.intType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.intType));
return;
case FLOAT:
- frameBuilder.verifyIsAssignable(pop, factory.floatType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.floatType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.floatType));
return;
case LONG:
- frameBuilder.verifyIsAssignable(pop, factory.longType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.longType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.longType));
frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.longType));
return;
case DOUBLE:
- frameBuilder.verifyIsAssignable(pop, factory.doubleType);
+ frameBuilder.checkIsAssignable(
+ pop, factory.doubleType, frameBuilder::isAssignableAndInitialized);
frameBuilder.storeLocal(var, FrameType.initialized(factory.doubleType));
frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.doubleType));
return;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index f07011a..b84fc14 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -149,10 +149,10 @@
InitClassLens initClassLens) {
// ..., index/key →
// ...
- frameBuilder.pop(factory.intType);
- frameBuilder.verifyTarget(defaultTarget);
+ frameBuilder.popInitialized(factory.intType);
+ frameBuilder.checkTarget(defaultTarget);
for (CfLabel target : targets) {
- frameBuilder.verifyTarget(target);
+ frameBuilder.checkTarget(target);
}
frameBuilder.setNoFrame();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index aad20a8..90d25c5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -95,7 +95,7 @@
InitClassLens initClassLens) {
// ..., objectref →
// objectref
- frameBuilder.pop(factory.throwableType);
+ frameBuilder.popInitialized(factory.throwableType);
// The exception edges are verified in CfCode since this is a throwing instruction.
frameBuilder.setNoFrame();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java
new file mode 100644
index 0000000..1762c7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface CfTypeInstruction {
+
+ DexType getType();
+
+ CfInstruction withType(DexType newType);
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index da3b04c..fa7e21d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -643,7 +643,10 @@
// for all code objects and write the processed results into that map.
Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
for (DexProgramClass clazz : classes) {
- boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
+ // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never
+ // duplicated in outputs.
+ boolean isSharedSynthetic =
+ appView.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
clazz.forEachMethod(
method -> {
DexCode code =
@@ -672,7 +675,7 @@
provider,
objectMapping,
codeMapping,
- appView.appInfo().app(),
+ appView.appInfo(),
options,
namingLens,
desugaredLibraryCodeToKeep);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index bbe4a90..adfab44 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.errors.InvokeCustomDiagnostic;
import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic;
import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationElement;
@@ -99,6 +100,7 @@
private final ObjectToOffsetMapping mapping;
private final MethodToCodeObjectMapping codeMapping;
+ private final AppInfo appInfo;
private final DexApplication application;
private final InternalOptions options;
private final GraphLens graphLens;
@@ -112,13 +114,14 @@
ByteBufferProvider provider,
ObjectToOffsetMapping mapping,
MethodToCodeObjectMapping codeMapping,
- DexApplication application,
+ AppInfo appInfo,
InternalOptions options,
NamingLens namingLens,
CodeToKeep desugaredLibraryCodeToKeep) {
this.mapping = mapping;
this.codeMapping = codeMapping;
- this.application = application;
+ this.appInfo = appInfo;
+ this.application = appInfo.app();
this.options = options;
this.graphLens = mapping.getGraphLens();
this.namingLens = namingLens;
@@ -680,7 +683,8 @@
dest.putUleb128(clazz.getMethodCollection().numberOfVirtualMethods());
writeEncodedFields(clazz.staticFields());
writeEncodedFields(clazz.instanceFields());
- boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
+ boolean isSharedSynthetic =
+ appInfo.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
writeEncodedMethods(clazz.directMethods(), isSharedSynthetic);
writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic);
}
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index 84459b4..29987e7 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -304,7 +304,8 @@
if (getClassNameSeparator() != '/') {
javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
}
- String minifiedJavaPackage = namingLens.lookupPackageName(javaPackage);
+ String packageName = appView.graphLens().lookupPackageName(javaPackage);
+ String minifiedJavaPackage = namingLens.lookupPackageName(packageName);
if (!javaPackage.equals(minifiedJavaPackage)) {
outputRangeFromInput(outputFrom, from);
outputJavaType(
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 135fcb4..e44973a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -36,7 +36,6 @@
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -330,7 +329,11 @@
Collection<DexProgramClass> synthetics = new ArrayList<>();
// Assign dedicated virtual files for all program classes.
for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) {
+ Collection<DexType> contexts =
+ appView.getSyntheticItems().getSynthesizingContexts(clazz.getType());
+ // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
+ // duplicated.
+ if (!combineSyntheticClassesWithPrimaryClass || contexts.isEmpty()) {
VirtualFile file =
new VirtualFile(
virtualFiles.size(),
@@ -350,7 +353,9 @@
}
}
for (DexProgramClass synthetic : synthetics) {
- for (DexProgramClass inputType : synthetic.getSynthesizedFrom()) {
+ for (DexType context :
+ appView.getSyntheticItems().getSynthesizingContexts(synthetic.getType())) {
+ DexProgramClass inputType = appView.definitionForProgramType(context);
VirtualFile file = files.get(inputType);
file.addClass(synthetic);
file.commitTransaction();
@@ -471,23 +476,6 @@
classes.removeAll(featureClasses);
}
}
- List<DexProgramClass> toRemove = new ArrayList<>();
- for (DexProgramClass dexProgramClass : classes) {
- if (appView.getSyntheticItems().isLegacySyntheticClass(dexProgramClass)) {
- Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
- if (!synthesizedFrom.isEmpty()) {
- DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
- FeatureSplit featureSplit =
- classToFeatureSplitMap.getFeatureSplit(from, appView.getSyntheticItems());
- if (!featureSplit.isBase()) {
- Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
- dexProgramClasses.add(dexProgramClass);
- toRemove.add(dexProgramClass);
- }
- }
- }
- }
- classes.removeAll(toRemove);
return featureSplitClasses;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index f2064b3..5d62aae 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
import java.util.List;
import java.util.function.Consumer;
@@ -120,20 +121,22 @@
return syntheticItems;
}
- public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDex) {
+ public void addSynthesizedClassForLibraryDesugaring(DexProgramClass clazz) {
assert checkIfObsolete();
- syntheticItems.addLegacySyntheticClass(clazz);
- if (addToMainDex) {
- mainDexInfo.addSyntheticClass(clazz);
- }
+ assert options().desugaredLibraryConfiguration != null;
+ syntheticItems.addLegacySyntheticClassForLibraryDesugaring(clazz);
}
public void addSynthesizedClass(DexProgramClass clazz, ProgramDefinition context) {
assert checkIfObsolete();
- syntheticItems.addLegacySyntheticClass(clazz);
- if (context != null) {
- mainDexInfo.addLegacySyntheticClass(clazz, context);
- }
+ assert context != null;
+ syntheticItems.addLegacySyntheticClass(clazz, context);
+ }
+
+ public void addSynthesizedClass(DexProgramClass clazz, Iterable<DexProgramClass> contexts) {
+ assert checkIfObsolete();
+ assert !IterableUtils.isEmpty(contexts);
+ contexts.forEach(context -> addSynthesizedClass(clazz, context));
}
public List<DexProgramClass> classes() {
@@ -168,7 +171,7 @@
}
DexClass definition = definitionFor(type);
if (definition != null && !definition.isLibraryClass() && !dependent.isLibraryClass()) {
- InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, options());
+ InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, this);
}
return definition;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index cd90e16..c8d9834 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -100,14 +100,6 @@
commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses());
}
- public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(MissingClasses missingClasses) {
- return new AppInfoWithClassHierarchy(
- getSyntheticItems().commit(app()),
- getClassToFeatureSplitMap(),
- getMainDexInfo(),
- missingClasses);
- }
-
public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
Function<DexApplication, DexApplication> fn) {
assert checkIfObsolete();
@@ -123,7 +115,10 @@
assert getClass() == AppInfoWithClassHierarchy.class;
assert checkIfObsolete();
return new AppInfoWithClassHierarchy(
- getSyntheticItems().commit(app()), classToFeatureSplitMap, mainDexInfo, missingClasses);
+ getSyntheticItems().commit(app()),
+ getClassToFeatureSplitMap(),
+ mainDexInfo,
+ getMissingClasses());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index ed43ca9..71b938f 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -67,8 +67,17 @@
public enum StackMapStatus {
NOT_VERIFIED,
- INVALID_OR_NOT_PRESENT,
- VALID
+ NOT_PRESENT,
+ INVALID,
+ VALID;
+
+ public boolean isValid() {
+ return this == VALID || this == NOT_PRESENT;
+ }
+
+ public boolean isInvalidOrNotPresent() {
+ return this == INVALID || this == NOT_PRESENT;
+ }
}
public static class LocalVariableInfo {
@@ -305,7 +314,8 @@
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
GraphLens graphLens = appView.graphLens();
- assert verifyFrames(method.getDefinition(), appView, null, false);
+ assert verifyFrames(method.getDefinition(), appView, null, false).isValid()
+ : "Could not validate stack map frames";
DexItemFactory dexItemFactory = appView.dexItemFactory();
InitClassLens initClassLens = appView.initClassLens();
InternalOptions options = appView.options();
@@ -432,7 +442,8 @@
AppView<?> appView,
Origin origin,
boolean shouldApplyCodeRewritings) {
- if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
+ stackMapStatus = verifyFrames(method, appView, origin, shouldApplyCodeRewritings);
+ if (!stackMapStatus.isValid()) {
ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
copy.removeIf(CfInstruction::isFrame);
setInstructions(copy);
@@ -705,16 +716,14 @@
thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
}
- public boolean verifyFrames(
+ public StackMapStatus verifyFrames(
DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) {
if (!appView.options().canUseInputStackMaps()
|| appView.options().testing.disableStackMapVerification) {
- stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
- return true;
+ return StackMapStatus.NOT_PRESENT;
}
if (method.hasClassFileVersion() && method.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
- stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
- return true;
+ return StackMapStatus.NOT_PRESENT;
}
if (!method.isInstanceInitializer()
&& appView
@@ -722,8 +731,7 @@
.getOriginalMethodSignature(method.method)
.isInstanceInitializer(appView.dexItemFactory())) {
// We cannot verify instance initializers if they are moved.
- stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
- return true;
+ return StackMapStatus.NOT_PRESENT;
}
// Build a map from labels to frames.
Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>();
@@ -790,12 +798,13 @@
tryCatchRanges,
isAssignablePredicate(appView),
appView.dexItemFactory(),
- appView.graphLens());
+ appView.graphLens(),
+ maxStack);
if (stateMap.containsKey(null)) {
assert !shouldComputeInitialFrame();
- builder.verifyFrameAndSet(stateMap.get(null));
+ builder.checkFrameAndSet(stateMap.get(null));
} else if (shouldComputeInitialFrame()) {
- builder.verifyFrameAndSet(
+ builder.checkFrameAndSet(
new CfFrame(
computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>()));
}
@@ -807,7 +816,7 @@
// affect the exceptional transfer (the exception edge is always a singleton stack).
if (instruction.canThrow()) {
assert !instruction.isStore();
- builder.verifyExceptionEdges();
+ builder.checkExceptionEdges();
}
instruction.evaluate(
builder, context, returnType, appView.dexItemFactory(), appView.initClassLens());
@@ -823,18 +832,16 @@
appView);
}
}
- stackMapStatus = StackMapStatus.VALID;
- return true;
+ return StackMapStatus.VALID;
}
- private boolean reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
+ private StackMapStatus reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
// Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only
// started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
// libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
// report a warning.
- stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
appView.options().reporter.warning(diagnostics);
- return false;
+ return StackMapStatus.INVALID;
}
private boolean finalAndExitInstruction(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index abeb404..be553ac6 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -95,7 +95,7 @@
public int getAsDexAccessFlags() {
// We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing
// abstract to interfaces to work around a javac bug when generating package-info classes.
- int flags = materialize() & ~Constants.ACC_SUPER;
+ int flags = materialize() & ~Constants.ACC_SUPER & ~Constants.ACC_RECORD;
if (isInterface()) {
return flags | Constants.ACC_ABSTRACT;
}
@@ -186,6 +186,10 @@
set(Constants.ACC_RECORD);
}
+ public void unsetRecord() {
+ unset(Constants.ACC_RECORD);
+ }
+
public boolean isSuper() {
return isSet(Constants.ACC_SUPER);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
index f4f1c31..da0f128 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
@@ -21,6 +21,8 @@
DexType getType();
+ boolean isInterface();
+
@Override
default boolean isClass() {
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index d5ea16e..d82db4a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -199,6 +199,8 @@
return self();
}
+ public abstract void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz);
+
public synchronized T addProgramClasses(Collection<DexProgramClass> classes) {
programClasses.addAll(classes);
return self();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f0745c8..130dbcf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -598,6 +598,7 @@
return isFinal();
}
+ @Override
public boolean isInterface() {
return accessFlags.isInterface();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index c3e6998..8eeda6b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -21,6 +21,10 @@
this.annotations = annotations;
}
+ public boolean hasAnnotations() {
+ return !annotations().isEmpty();
+ }
+
public DexAnnotationSet annotations() {
return annotations;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index d15bfc7..1a2b411 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -51,23 +51,6 @@
// TODO(b/171867022): Should the optimization info and member info be part of the definition?
}
- public DexEncodedField(
- DexField field,
- FieldAccessFlags accessFlags,
- FieldTypeSignature genericSignature,
- DexAnnotationSet annotations,
- DexValue staticValue,
- boolean deprecated) {
- super(annotations);
- this.field = field;
- this.accessFlags = accessFlags;
- this.staticValue = staticValue;
- this.deprecated = deprecated;
- this.genericSignature = genericSignature;
- assert genericSignature != null;
- assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
- }
-
public DexEncodedField(DexField field, FieldAccessFlags accessFlags) {
this(field, accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
}
@@ -81,6 +64,34 @@
this(field, accessFlags, genericSignature, annotations, staticValue, false);
}
+ public DexEncodedField(
+ DexField field,
+ FieldAccessFlags accessFlags,
+ FieldTypeSignature genericSignature,
+ DexAnnotationSet annotations,
+ DexValue staticValue,
+ boolean deprecated) {
+ this(field, accessFlags, genericSignature, annotations, staticValue, deprecated, false);
+ }
+
+ public DexEncodedField(
+ DexField field,
+ FieldAccessFlags accessFlags,
+ FieldTypeSignature genericSignature,
+ DexAnnotationSet annotations,
+ DexValue staticValue,
+ boolean deprecated,
+ boolean d8R8Synthesized) {
+ super(annotations, d8R8Synthesized);
+ this.field = field;
+ this.accessFlags = accessFlags;
+ this.staticValue = staticValue;
+ this.deprecated = deprecated;
+ this.genericSignature = genericSignature;
+ assert genericSignature != null;
+ assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
+ }
+
@Override
public StructuralMapping<DexEncodedField> getStructuralMapping() {
return DexEncodedField::specify;
@@ -235,10 +246,6 @@
return accessFlags.isVolatile();
}
- public boolean hasAnnotation() {
- return !annotations().isEmpty();
- }
-
public boolean hasExplicitStaticValue() {
assert accessFlags.isStatic();
return staticValue != null;
@@ -356,6 +363,8 @@
private FieldTypeSignature genericSignature;
private DexValue staticValue;
private FieldOptimizationInfo optimizationInfo;
+ private boolean deprecated;
+ private boolean d8R8Synthesized;
private Consumer<DexEncodedField> buildConsumer = ConsumerUtils.emptyConsumer();
Builder(DexEncodedField from) {
@@ -370,6 +379,8 @@
from.optimizationInfo.isDefaultFieldOptimizationInfo()
? DefaultFieldOptimizationInfo.getInstance()
: from.optimizationInfo.mutableCopy();
+ deprecated = from.isDeprecated();
+ d8R8Synthesized = from.isD8R8Synthesized();
}
public Builder apply(Consumer<Builder> consumer) {
@@ -397,7 +408,14 @@
DexEncodedField build() {
DexEncodedField dexEncodedField =
- new DexEncodedField(field, accessFlags, genericSignature, annotations, staticValue);
+ new DexEncodedField(
+ field,
+ accessFlags,
+ genericSignature,
+ annotations,
+ staticValue,
+ deprecated,
+ d8R8Synthesized);
if (optimizationInfo.isMutableFieldOptimizationInfo()) {
dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index d5c5135..7d66bb6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -8,8 +8,17 @@
public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
extends DexDefinition {
- public DexEncodedMember(DexAnnotationSet annotations) {
+ // This flag indicates if this member has been synthesized by D8/R8. Such members do not require
+ // a proguard mapping file entry. This flag is different from the synthesized access flag. When a
+ // non synthesized member is inlined into a synthesized member, the member no longer has the
+ // synthesized access flag, but the d8R8Synthesized flag is still there. Members can also have
+ // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not
+ // set.
+ private final boolean d8R8Synthesized;
+
+ public DexEncodedMember(DexAnnotationSet annotations, boolean d8R8Synthesized) {
super(annotations);
+ this.d8R8Synthesized = d8R8Synthesized;
}
public abstract KotlinMemberLevelInfo getKotlinMemberInfo();
@@ -25,6 +34,10 @@
@Override
public abstract R getReference();
+ public boolean isD8R8Synthesized() {
+ return d8R8Synthesized;
+ }
+
@Override
public boolean isDexEncodedMember() {
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 5c9fc0f..470bee9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -160,7 +160,7 @@
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
- private CfVersion classFileVersion = null;
+ private CfVersion classFileVersion;
private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
/** Generic signature information if the attribute is present in the input */
private MethodTypeSignature genericSignature;
@@ -177,18 +177,6 @@
// Any newly added `public` method should check if `this` instance is obsolete.
private boolean obsolete = false;
- // This flag indicates if the method has been synthesized by D8/R8. Such method do not require
- // a proguard mapping file entry. This flag is different from the synthesized access flag. When a
- // non synthesized method is inlined into a synthesized method, the method no longer has the
- // synthesized access flag, but the d8R8Synthesized flag is still there. Methods can also have
- // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not
- // set.
- private final boolean d8R8Synthesized;
-
- public boolean isD8R8Synthesized() {
- return d8R8Synthesized;
- }
-
private void checkIfObsolete() {
assert !obsolete;
}
@@ -220,6 +208,10 @@
return getReference().getParameter(argumentIndex - 1);
}
+ public int getNumberOfArguments() {
+ return getReference().getArity() + BooleanUtils.intValue(isInstance());
+ }
+
public CompilationState getCompilationState() {
return compilationState;
}
@@ -319,7 +311,7 @@
boolean d8R8Synthesized,
CfVersion classFileVersion,
boolean deprecated) {
- super(annotations);
+ super(annotations, d8R8Synthesized);
this.method = method;
this.accessFlags = accessFlags;
this.deprecated = deprecated;
@@ -327,7 +319,6 @@
this.parameterAnnotationsList = parameterAnnotationsList;
this.code = code;
this.classFileVersion = classFileVersion;
- this.d8R8Synthesized = d8R8Synthesized;
assert accessFlags != null;
assert code == null || !shouldNotHaveCode();
assert parameterAnnotationsList != null;
@@ -354,15 +345,15 @@
// Visitor specifying the structure of the method with respect to its "synthetic" content.
// TODO(b/171867022): Generalize this so that it determines any method in full.
private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) {
- spec.withItem(m -> m.method)
- .withItem(m -> m.accessFlags)
+ spec.withItem(DexEncodedMethod::getReference)
+ .withItem(DexEncodedMethod::getAccessFlags)
.withItem(DexDefinition::annotations)
.withItem(m -> m.parameterAnnotationsList)
.withNullableItem(m -> m.classFileVersion)
- .withBool(m -> m.d8R8Synthesized)
+ .withBool(DexEncodedMember::isD8R8Synthesized)
// TODO(b/171867022): Make signatures structural and include it in the definition.
.withAssert(m -> m.genericSignature.hasNoSignature())
- .withAssert(m1 -> m1.code != null)
+ .withAssert(DexEncodedMethod::hasCode)
.withCustomItem(
DexEncodedMethod::getCode,
DexEncodedMethod::compareCodeObject,
@@ -1547,7 +1538,7 @@
private Consumer<DexEncodedMethod> buildConsumer = ConsumerUtils.emptyConsumer();
private Builder(DexEncodedMethod from) {
- this(from, from.d8R8Synthesized);
+ this(from, from.isD8R8Synthesized());
}
private Builder(DexEncodedMethod from, boolean d8R8Synthesized) {
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 e76409d..2ce742a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
import static com.android.tools.r8.ir.desugar.LambdaClass.LAMBDA_INSTANCE_FIELD_NAME;
@@ -215,6 +216,7 @@
public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
public final DexString recordDescriptor = createString("Ljava/lang/Record;");
+ public final DexString r8RecordDescriptor = createString("Lcom/android/tools/r8/RecordTag;");
public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
public final DexString classDescriptor = createString("Ljava/lang/Class;");
public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -346,6 +348,7 @@
public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor);
public final DexType objectType = createStaticallyKnownType(objectDescriptor);
public final DexType recordType = createStaticallyKnownType(recordDescriptor);
+ public final DexType r8RecordType = createStaticallyKnownType(r8RecordDescriptor);
public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -509,6 +512,7 @@
public final LongMembers longMembers = new LongMembers();
public final ObjectsMethods objectsMethods = new ObjectsMethods();
public final ObjectMembers objectMembers = new ObjectMembers();
+ public final RecordMembers recordMembers = new RecordMembers();
public final ShortMembers shortMembers = new ShortMembers();
public final StringMembers stringMembers = new StringMembers();
public final DoubleMembers doubleMembers = new DoubleMembers();
@@ -1203,9 +1207,23 @@
}
}
+ public class RecordMembers {
+ public final DexMethod init = createMethod(recordType, createProto(voidType), "<init>");
+ public final DexMethod equals =
+ createMethod(recordType, createProto(booleanType, objectType), "equals");
+ public final DexMethod hashCode = createMethod(recordType, createProto(intType), "hashCode");
+ public final DexMethod toString = createMethod(recordType, createProto(stringType), "toString");
+ }
+
public class ObjectMembers {
/**
+ * This field is not on {@link Object}, but will be synthesized on horizontally merged classes
+ * as an instance field.
+ */
+ public final DexField classIdField = createField(objectType, intType, CLASS_ID_FIELD_NAME);
+
+ /**
* This field is not on {@link Object}, but will be synthesized on program classes as a static
* field, for the compiler to have a principled way to trigger the initialization of a given
* class.
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 11e5957..a4ccb8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -28,9 +28,6 @@
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -50,7 +47,6 @@
public static final DexProgramClass[] EMPTY_ARRAY = {};
private final ProgramResource.Kind originKind;
- private final Collection<DexProgramClass> synthesizedFrom;
private CfVersion initialClassFileVersion = null;
private boolean deprecated = false;
private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
@@ -77,50 +73,6 @@
DexEncodedMethod[] virtualMethods,
boolean skipNameValidationForTesting,
ChecksumSupplier checksumSupplier) {
- this(
- type,
- originKind,
- origin,
- accessFlags,
- superType,
- interfaces,
- sourceFile,
- nestHost,
- nestMembers,
- enclosingMember,
- innerClasses,
- classSignature,
- classAnnotations,
- staticFields,
- instanceFields,
- directMethods,
- virtualMethods,
- skipNameValidationForTesting,
- checksumSupplier,
- Collections.emptyList());
- }
-
- public DexProgramClass(
- DexType type,
- Kind originKind,
- Origin origin,
- ClassAccessFlags accessFlags,
- DexType superType,
- DexTypeList interfaces,
- DexString sourceFile,
- NestHostClassAttribute nestHost,
- List<NestMemberClassAttribute> nestMembers,
- EnclosingMethodAttribute enclosingMember,
- List<InnerClassAttribute> innerClasses,
- ClassSignature classSignature,
- DexAnnotationSet classAnnotations,
- DexEncodedField[] staticFields,
- DexEncodedField[] instanceFields,
- DexEncodedMethod[] directMethods,
- DexEncodedMethod[] virtualMethods,
- boolean skipNameValidationForTesting,
- ChecksumSupplier checksumSupplier,
- Collection<DexProgramClass> synthesizedDirectlyFrom) {
super(
sourceFile,
interfaces,
@@ -143,8 +95,6 @@
assert classAnnotations != null;
this.originKind = originKind;
this.checksumSupplier = checksumSupplier;
- this.synthesizedFrom = new HashSet<>();
- synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
}
@Override
@@ -185,9 +135,7 @@
.withAssert(c -> c.classSignature == ClassSignature.noSignature())
.withItemArray(c -> c.staticFields)
.withItemArray(c -> c.instanceFields)
- .withItemCollection(DexClass::allMethodsSorted)
- // TODO(b/168584485): Synthesized-from is being removed (empty for new synthetics).
- .withAssert(c -> c.synthesizedFrom.isEmpty());
+ .withItemCollection(DexClass::allMethodsSorted);
}
public void forEachProgramField(Consumer<? super ProgramField> consumer) {
@@ -383,10 +331,6 @@
}
}
- public Collection<DexProgramClass> getSynthesizedFrom() {
- return synthesizedFrom;
- }
-
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
assert getEnclosingMethodAttribute() == null;
@@ -599,7 +543,7 @@
private boolean hasAnnotations(DexEncodedField[] fields) {
synchronized (fields) {
- return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation);
+ return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotations);
}
}
@@ -609,14 +553,6 @@
}
}
- public void addSynthesizedFrom(DexProgramClass clazz) {
- if (clazz.synthesizedFrom.isEmpty()) {
- synthesizedFrom.add(clazz);
- } else {
- clazz.synthesizedFrom.forEach(this::addSynthesizedFrom);
- }
- }
-
public DexEncodedArray computeStaticValuesArray(NamingLens namingLens) {
// Fast path to avoid sorting and collection allocation when no non-default values exist.
if (!hasNonDefaultStaticFieldValues()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 86377b3..0f7b951 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -213,6 +213,37 @@
}
@Override
+ public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
+ addProgramClass(clazz);
+ if (containsType(clazz.type, libraryClasses)) {
+ replaceLibraryClasses(withoutType(clazz.type, libraryClasses));
+ return;
+ }
+ if (containsType(clazz.type, classpathClasses)) {
+ replaceClasspathClasses(withoutType(clazz.type, classpathClasses));
+ }
+ }
+
+ private boolean containsType(DexType type, List<? extends DexClass> classes) {
+ for (DexClass clazz : classes) {
+ if (clazz.type == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private <T extends DexClass> ImmutableList<T> withoutType(DexType type, List<T> classes) {
+ ImmutableList.Builder<T> builder = ImmutableList.builder();
+ for (T clazz : classes) {
+ if (clazz.type != type) {
+ builder.add(clazz);
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
Builder self() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 4f07887..1697f43 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -63,6 +63,11 @@
Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
}
+ public static FieldAccessFlags createPublicFinalSynthetic() {
+ return fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
+ }
+
public static FieldAccessFlags fromSharedAccessFlags(int access) {
assert (access & FLAGS) == access;
return new FieldAccessFlags(access & FLAGS);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 43e733c..7135cfc 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.graph;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -364,6 +363,8 @@
return false;
}
+ public abstract String lookupPackageName(String pkg);
+
public abstract DexType lookupClassType(DexType type);
public abstract DexType lookupType(DexType type);
@@ -595,7 +596,6 @@
public boolean verifyMappingToOriginalProgram(
AppView<?> appView, DexApplication originalApplication) {
- DexItemFactory dexItemFactory = appView.dexItemFactory();
Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
// Collect all original fields and methods for efficient querying.
Set<DexField> originalFields = Sets.newIdentityHashSet();
@@ -616,11 +616,12 @@
continue;
}
for (DexEncodedField field : clazz.fields()) {
- // Fields synthesized by R8 are not present in the input, and therefore we do not require
- // that they can be mapped back to the original program.
+ if (field.isD8R8Synthesized()) {
+ // Fields synthesized by D8/R8 may not be mapped.
+ continue;
+ }
DexField originalField = getOriginalFieldSignature(field.getReference());
assert originalFields.contains(originalField)
- || isD8R8SynthesizedField(field.getReference(), appView)
: "Unable to map field `"
+ field.getReference().toSourceString()
+ "` back to original program";
@@ -638,23 +639,6 @@
return true;
}
- private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) {
- // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized
- // instead of relying on field names.
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- if (field.match(dexItemFactory.objectMembers.clinitField)) {
- return true;
- }
- if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
- return true;
- }
- if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType())
- && field.getName() == dexItemFactory.lambdaInstanceFieldName) {
- return true;
- }
- return false;
- }
-
public abstract static class NonIdentityGraphLens extends GraphLens {
private final DexItemFactory dexItemFactory;
@@ -715,6 +699,11 @@
}
@Override
+ public String lookupPackageName(String pkg) {
+ return pkg;
+ }
+
+ @Override
public final DexType lookupType(DexType type) {
if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
return type;
@@ -831,6 +820,11 @@
}
@Override
+ public String lookupPackageName(String pkg) {
+ return pkg;
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return type;
}
@@ -1051,7 +1045,7 @@
@Override
public DexField getRenamedFieldSignature(DexField originalField) {
DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
- return fieldMap.getOrDefault(renamedField, renamedField);
+ return internalGetNextFieldSignature(renamedField);
}
@Override
@@ -1151,6 +1145,10 @@
return prototypeChanges;
}
+ protected DexField internalGetNextFieldSignature(DexField field) {
+ return fieldMap.getOrDefault(field, field);
+ }
+
@Override
protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 35b4574..d8815c7 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -500,7 +500,7 @@
return;
}
// TODO(b/169645628): Support records in all compilation.
- if (!application.options.canUseRecords()) {
+ if (!application.options.enableExperimentalRecordDesugaring()) {
throw new CompilationError("Records are not supported", origin);
}
// TODO(b/169645628): Change this logic if we start stripping the record components.
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index ba22b16..08bc5f0 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -336,6 +336,7 @@
private final DebugParsingOptions debugParsingOptions;
private int maxStack;
private int maxLocals;
+ private boolean desugaredVisitMultiANewArrayInstruction;
private List<CfInstruction> instructions;
private List<CfTryCatch> tryCatchRanges;
private List<LocalVariableInfo> localVariables;
@@ -386,7 +387,12 @@
}
code.setCode(
new CfCode(
- method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
+ method.holder,
+ desugaredVisitMultiANewArrayInstruction ? Integer.MAX_VALUE : maxStack,
+ maxLocals,
+ instructions,
+ tryCatchRanges,
+ localVariables));
}
@Override
@@ -948,6 +954,7 @@
// ..., ref
visitTypeInsn(Opcodes.CHECKCAST, desc);
// ..., arrayref(of type : desc)
+ desugaredVisitMultiANewArrayInstruction = true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 7005e06..509a10d 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -226,6 +226,13 @@
}
@Override
+ public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
+ addProgramClass(clazz);
+ classpathClasses.clearType(clazz.type);
+ libraryClasses.clearType(clazz.type);
+ }
+
+ @Override
public LazyLoadedDexApplication build() {
return new LazyLoadedDexApplication(
proguardMap,
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index c0cc708..686d7d5 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -302,8 +302,7 @@
// We already gave up on tracking the allocation sites for `clazz` previously.
return false;
}
- // We currently only use allocation site information for instance field value propagation.
- return !clazz.instanceFields().isEmpty();
+ return true;
}
/**
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 89d0bd7..27f3251 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -113,8 +113,7 @@
DexEncodedMethod.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
dexItemFactory.getSkipNameValidationForTesting(),
- clazz.getChecksumSupplier(),
- fixupSynthesizedFrom(clazz.getSynthesizedFrom()));
+ clazz.getChecksumSupplier());
newClass.setInstanceFields(fixupFields(clazz.instanceFields()));
newClass.setStaticFields(fixupFields(clazz.staticFields()));
newClass.setDirectMethods(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java
deleted file mode 100644
index fa9873a..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging;
-
-import com.android.tools.r8.graph.DexProgramClass;
-
-public abstract class CanOnlyMergeIntoClassPolicy extends SingleClassPolicy {
- public abstract boolean canOnlyMergeInto(DexProgramClass clazz);
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- // TODO(b/165577835): Allow merging of classes that must be the target of their merge group.
- return !canOnlyMergeInto(program);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
index c919e63..d78d74d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
@@ -59,11 +59,14 @@
}
public CfCode synthesizeCode(DexType originalHolder) {
+ // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
+ // the CfCode constructor to ensure that the value passed in is the updated values.
+ List<CfInstruction> instructions = buildInstructions();
return new CfCode(
originalHolder,
maxStack,
maxLocals,
- buildInstructions(),
+ instructions,
Collections.emptyList(),
Collections.emptyList());
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 11001de..0e7d164 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -26,6 +27,9 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -52,6 +56,8 @@
public static final String CLASS_ID_FIELD_NAME = "$r8$classId";
+ private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+
private final AppView<AppInfoWithLiveness> appView;
private final MergeGroup group;
private final DexItemFactory dexItemFactory;
@@ -131,8 +137,6 @@
CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
if (cfVersion != null) {
clinit.upgradeClassFileVersion(cfVersion);
- } else {
- assert appView.options().isGeneratingDex();
}
classMethodsBuilder.addDirectMethod(clinit);
}
@@ -194,14 +198,29 @@
}
void appendClassIdField() {
- classInstanceFieldsMerger.setClassIdField(
+ boolean deprecated = false;
+ boolean d8R8Synthesized = true;
+ DexEncodedField classIdField =
new DexEncodedField(
group.getClassIdField(),
- FieldAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
+ FieldAccessFlags.createPublicFinalSynthetic(),
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
- null));
+ null,
+ deprecated,
+ d8R8Synthesized);
+
+ // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
+ // have. For example, for a merge group of size 4, we may compute the set {0, 2, 3}, if the
+ // instances with $r8$classId == 1 ends up dead as a result of optimizations). If no instances
+ // end up being dead, we would compute the set {0, 1, 2, 3}. The latter information does not
+ // provide any value, and therefore we should not save it in the optimization info. In order to
+ // be able to recognize that {0, 1, 2, 3} is useless, we record that the value of the field is
+ // known to be in [0; 3] here.
+ NumberFromIntervalValue abstractValue = new NumberFromIntervalValue(0, group.size() - 1);
+ feedback.recordFieldHasAbstractValue(classIdField, appView, abstractValue);
+
+ classInstanceFieldsMerger.setClassIdField(classIdField);
}
void mergeStaticFields() {
@@ -219,6 +238,16 @@
}
}
+ private void mergeAnnotations() {
+ assert group.getClasses().stream().filter(DexDefinition::hasAnnotations).count() <= 1;
+ for (DexProgramClass clazz : group.getSources()) {
+ if (clazz.hasAnnotations()) {
+ group.getTarget().setAnnotations(clazz.annotations());
+ break;
+ }
+ }
+ }
+
private void mergeInterfaces() {
DexTypeList previousInterfaces = group.getTarget().getInterfaces();
Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
@@ -241,6 +270,7 @@
fixAccessFlags();
appendClassIdField();
+ mergeAnnotations();
mergeInterfaces();
mergeVirtualMethods();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index d97f12f..05e91a4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,21 +6,20 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics;
import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
import com.android.tools.r8.horizontalclassmerging.policies.MinimizeFieldCasts;
-import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
-import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
@@ -30,13 +29,13 @@
import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDexList;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
+import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -57,9 +56,7 @@
assert appView.options().enableInlining;
}
- // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
public HorizontalClassMergerResult run(
- DirectMappedDexApplication.Builder appBuilder,
RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
@@ -88,7 +85,7 @@
// Merge the classes.
SyntheticArgumentClass syntheticArgumentClass =
- new SyntheticArgumentClass.Builder(appBuilder, appView).build(allMergeClasses);
+ new SyntheticArgumentClass.Builder(appView).build(allMergeClasses);
applyClassMergers(classMergers, syntheticArgumentClass);
// Generate the graph lens.
@@ -121,37 +118,44 @@
}
private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
- return ImmutableList.of(
- new NotMatchedByNoHorizontalClassMerging(appView),
- new SameInstanceFields(appView),
- new NoInterfaces(),
- new NoAnnotations(),
- new NoEnums(appView),
- new CheckAbstractClasses(appView),
- new IgnoreSynthetics(appView),
- new NoClassesOrMembersWithAnnotations(appView),
- new NoInnerClasses(),
- new NoClassInitializerWithObservableSideEffects(),
- new NoNativeMethods(),
- new NoKeepRules(appView),
- new NoKotlinMetadata(),
- new NoServiceLoaders(appView),
- new NotVerticallyMergedIntoSubtype(appView),
- new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
- new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
- new PreventMethodImplementation(appView),
- new DontInlinePolicy(appView),
- new PreventMergeIntoMainDexList(appView),
- new PreventMergeIntoDifferentMainDexGroups(appView),
- new AllInstantiatedOrUninstantiated(appView),
- new SameParentClass(),
- new SameNestHost(),
- new PreserveMethodCharacteristics(appView),
- new SameFeatureSplit(appView),
- new RespectPackageBoundaries(appView),
- new DontMergeSynchronizedClasses(appView),
- new MinimizeFieldCasts(),
- new LimitGroups(appView));
+ List<SingleClassPolicy> singleClassPolicies =
+ ImmutableList.of(
+ new NotMatchedByNoHorizontalClassMerging(appView),
+ new NoAnnotationClasses(),
+ new NoEnums(appView),
+ new NoInnerClasses(),
+ new NoInstanceFieldAnnotations(),
+ new NoInterfaces(),
+ new NoClassInitializerWithObservableSideEffects(),
+ new NoNativeMethods(),
+ new NoKeepRules(appView),
+ new NoKotlinMetadata(),
+ new NoServiceLoaders(appView),
+ new NotVerticallyMergedIntoSubtype(appView),
+ new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
+ new DontInlinePolicy(appView));
+ List<MultiClassPolicy> multiClassPolicies =
+ ImmutableList.of(
+ new SameInstanceFields(appView),
+ new NoClassAnnotationCollisions(),
+ new CheckAbstractClasses(appView),
+ new SyntheticItemsPolicy(appView),
+ new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
+ new PreventMethodImplementation(appView),
+ new PreventMergeIntoDifferentMainDexGroups(appView),
+ new AllInstantiatedOrUninstantiated(appView),
+ new SameParentClass(),
+ new SameNestHost(appView),
+ new PreserveMethodCharacteristics(appView),
+ new SameFeatureSplit(appView),
+ new RespectPackageBoundaries(appView),
+ new DontMergeSynchronizedClasses(appView),
+ new MinimizeFieldCasts(),
+ new LimitGroups(appView));
+ return ImmutableList.<Policy>builder()
+ .addAll(singleClassPolicies)
+ .addAll(multiClassPolicies)
+ .build();
}
/**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
new file mode 100644
index 0000000..bd83a89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+
+public class HorizontalClassMergerUtils {
+
+ public static boolean isClassIdField(AppView<?> appView, DexEncodedField field) {
+ DexField classIdField = appView.dexItemFactory().objectMembers.classIdField;
+ if (field.isD8R8Synthesized() && field.getType().isIntType()) {
+ DexField originalField = appView.graphLens().getOriginalFieldSignature(field.getReference());
+ return originalField.match(classIdField);
+ }
+ assert !appView.graphLens().getOriginalFieldSignature(field.getReference()).match(classIdField);
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 9e4ebf5..4b9ad6a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -30,6 +30,11 @@
this.classes = new LinkedList<>();
}
+ public MergeGroup(DexProgramClass clazz) {
+ this();
+ add(clazz);
+ }
+
public MergeGroup(Collection<DexProgramClass> classes) {
this();
addAll(classes);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
index 3cea5125..aa3ca62 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -15,11 +15,18 @@
public final Collection<MergeGroup> apply(MergeGroup group) {
Map<T, MergeGroup> groups = new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
- groups.computeIfAbsent(getMergeKey(clazz), ignore -> new MergeGroup()).add(clazz);
+ T mergeKey = getMergeKey(clazz);
+ if (mergeKey != null) {
+ groups.computeIfAbsent(mergeKey, ignore -> new MergeGroup()).add(clazz);
+ }
}
removeTrivialGroups(groups.values());
return groups.values();
}
public abstract T getMergeKey(DexProgramClass clazz);
+
+ protected final T ineligibleForClassMerging() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index 60c350a..ca0b752 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -4,23 +4,12 @@
package com.android.tools.r8.horizontalclassmerging;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.google.common.collect.Iterables;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -41,7 +30,6 @@
* <p>This class generates a synthetic class in the package of the first class to be merged.
*/
public class SyntheticArgumentClass {
- public static final String SYNTHETIC_CLASS_SUFFIX = "$r8$HorizontalClassMergingArgument";
private final List<DexType> syntheticClassTypes;
@@ -55,70 +43,28 @@
public static class Builder {
- private final DirectMappedDexApplication.Builder appBuilder;
private final AppView<AppInfoWithLiveness> appView;
- Builder(DirectMappedDexApplication.Builder appBuilder, AppView<AppInfoWithLiveness> appView) {
- this.appBuilder = appBuilder;
+ Builder(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
- private DexType synthesizeClass(
- DexProgramClass context,
- boolean requiresMainDex,
- int index) {
- DexType syntheticClassType =
- appView
- .dexItemFactory()
- .createFreshTypeName(
- context.type.addSuffix(SYNTHETIC_CLASS_SUFFIX, appView.dexItemFactory()),
- type -> appView.appInfo().definitionForWithoutExistenceAssert(type) == null,
- index);
-
- DexProgramClass clazz =
- new DexProgramClass(
- syntheticClassType,
- null,
- new SynthesizedOrigin("horizontal class merging", HorizontalClassMerger.class),
- ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
-
- appBuilder.addSynthesizedClass(clazz);
- appView.appInfo().addSynthesizedClass(clazz, requiresMainDex);
- return clazz.type;
+ private DexProgramClass synthesizeClass(DexProgramClass context, SyntheticKind syntheticKind) {
+ return appView
+ .getSyntheticItems()
+ .createFixedClass(syntheticKind, context, appView.dexItemFactory(), builder -> {});
}
public SyntheticArgumentClass build(Iterable<DexProgramClass> mergeClasses) {
// Find a fresh name in an existing package.
DexProgramClass context = mergeClasses.iterator().next();
-
- // Add as a root to the main dex tracing result if any of the merged classes is a root.
- // This is needed to satisfy an assertion in the inliner that verifies that we do not inline
- // methods with references to non-roots into classes that are roots.
- MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
- boolean requiresMainDex = Iterables.any(mergeClasses, mainDexInfo::isMainDex);
-
List<DexType> syntheticArgumentTypes = new ArrayList<>();
- for (int i = 0;
- i < appView.options().horizontalClassMergerOptions().getSyntheticArgumentCount();
- i++) {
- syntheticArgumentTypes.add(synthesizeClass(context, requiresMainDex, i));
- }
-
+ syntheticArgumentTypes.add(
+ synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
+ syntheticArgumentTypes.add(
+ synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType());
+ syntheticArgumentTypes.add(
+ synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
return new SyntheticArgumentClass(syntheticArgumentTypes);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
deleted file mode 100644
index e45e2d3..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class IgnoreSynthetics extends SingleClassPolicy {
-
- private final AppView<AppInfoWithLiveness> appView;
-
- public IgnoreSynthetics(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
- }
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- if (appView.getSyntheticItems().isSyntheticClass(program)) {
- return appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()
- && appView.getSyntheticItems().isLegacySyntheticClass(program);
- }
- return true;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
index 9190247..1effb8b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-public class NoAnnotations extends SingleClassPolicy {
+public class NoAnnotationClasses extends SingleClassPolicy {
@Override
public boolean canMerge(DexProgramClass program) {
return !program.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
new file mode 100644
index 0000000..9d3d730
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class NoClassAnnotationCollisions extends MultiClassPolicy {
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ // Create a new merge group for each class that has annotations.
+ List<MergeGroup> newGroups = new LinkedList<>();
+ for (DexProgramClass clazz : group) {
+ if (clazz.hasAnnotations()) {
+ newGroups.add(new MergeGroup(clazz));
+ }
+ }
+
+ // If there were at most one class with annotations, then just return the original merge group.
+ if (newGroups.size() <= 1) {
+ return ImmutableList.of(group);
+ }
+
+ // Otherwise, fill up the new merge groups with the classes that do not have annotations.
+ Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups);
+ for (DexProgramClass clazz : group) {
+ if (!clazz.hasAnnotations()) {
+ newGroupsIterator.next().add(clazz);
+ }
+ }
+ return removeTrivialGroups(newGroups);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
deleted file mode 100644
index f0b2a6e..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
-
-public class NoClassesOrMembersWithAnnotations extends SingleClassPolicy {
-
- private final HorizontalClassMergerOptions options;
-
- public NoClassesOrMembersWithAnnotations(AppView<AppInfoWithLiveness> appView) {
- this.options = appView.options().horizontalClassMergerOptions();
- }
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- return !program.hasClassOrMemberAnnotations();
- }
-
- @Override
- public boolean shouldSkipPolicy() {
- // TODO(b/179019716): Add support for merging in presence of annotations.
- return options.skipNoClassesOrMembersWithAnnotationsPolicyForTesting;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
similarity index 62%
copy from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
copy to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
index 9190247..610af7e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
@@ -4,12 +4,19 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-public class NoAnnotations extends SingleClassPolicy {
+public class NoInstanceFieldAnnotations extends SingleClassPolicy {
+
@Override
public boolean canMerge(DexProgramClass program) {
- return !program.isAnnotation();
+ for (DexEncodedField instanceField : program.instanceFields()) {
+ if (instanceField.hasAnnotations()) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
index 52a1d98..41c240f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -10,19 +10,23 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup;
+import com.android.tools.r8.synthesis.SyntheticItems;
public class PreventMergeIntoDifferentMainDexGroups
extends MultiClassSameReferencePolicy<MainDexGroup> {
private final MainDexInfo mainDexInfo;
+ private final SyntheticItems synthetics;
public PreventMergeIntoDifferentMainDexGroups(AppView<AppInfoWithLiveness> appView) {
- this.mainDexInfo = appView.appInfo().getMainDexInfo();
+ mainDexInfo = appView.appInfo().getMainDexInfo();
+ synthetics = appView.getSyntheticItems();
}
@Override
public MainDexGroup getMergeKey(DexProgramClass clazz) {
- assert !mainDexInfo.isFromList(clazz);
- return mainDexInfo.getMergeKey(clazz);
+ return mainDexInfo.canMerge(clazz, synthetics)
+ ? mainDexInfo.getMergeKey(clazz, synthetics)
+ : ineligibleForClassMerging();
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java
deleted file mode 100644
index 850d02e..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-
-public class PreventMergeIntoMainDexList extends SingleClassPolicy {
-
- private final MainDexInfo mainDexInfo;
-
- public PreventMergeIntoMainDexList(AppView<AppInfoWithLiveness> appView) {
- this.mainDexInfo = appView.appInfo().getMainDexInfo();
- }
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- return mainDexInfo.canMerge(program);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index 6619878..88d2984 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,13 +4,23 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class SameNestHost extends MultiClassSameReferencePolicy<DexType> {
+
+ private final DexItemFactory dexItemFactory;
+
+ public SameNestHost(AppView<AppInfoWithLiveness> appView) {
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
@Override
public DexType getMergeKey(DexProgramClass clazz) {
- return clazz.getNestHost();
+ return clazz.isInANest() ? clazz.getNestHost() : dexItemFactory.objectType;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
new file mode 100644
index 0000000..b012897
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+public class SyntheticItemsPolicy extends MultiClassSameReferencePolicy<ClassKind> {
+
+ enum ClassKind {
+ SYNTHETIC,
+ NOT_SYNTHETIC
+ }
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SyntheticItemsPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public ClassKind getMergeKey(DexProgramClass clazz) {
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+
+ // Allow merging non-synthetics with non-synthetics.
+ if (!syntheticItems.isSyntheticClass(clazz)) {
+ return ClassKind.NOT_SYNTHETIC;
+ }
+
+ // Do not allow merging synthetics that are not lambdas.
+ if (!syntheticItems.isNonLegacySynthetic(clazz)
+ || syntheticItems.getNonLegacySyntheticKind(clazz) != SyntheticKind.LAMBDA) {
+ return ineligibleForClassMerging();
+ }
+
+ // Allow merging Java lambdas with Java lambdas.
+ if (appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()) {
+ return ClassKind.SYNTHETIC;
+ }
+
+ // Java lambda merging is disabled.
+ return ineligibleForClassMerging();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index d0a86ef..2b63089 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -15,8 +15,10 @@
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.NonConstantNumberValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.FieldInstruction;
@@ -173,31 +175,65 @@
while (iterator.hasNext()) {
Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
DexEncodedField field = entry.getKey();
+ AbstractValue abstractValue = entry.getValue();
+
+ // The power set lattice is an expensive abstraction, so use it with caution.
+ boolean isClassIdField = HorizontalClassMergerUtils.isClassIdField(appView, field);
+
InstanceFieldInitializationInfo initializationInfo =
initializationInfoCollection.get(field);
if (initializationInfo.isArgumentInitializationInfo()) {
InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
initializationInfo.asArgumentInitializationInfo();
Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
- AbstractValue abstractValue =
- entry.getValue().join(argument.getAbstractValue(appView, context));
+ AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
+ abstractValue =
+ abstractValue.join(
+ argumentAbstractValue,
+ appView.abstractValueFactory(),
+ field.getType(),
+ isClassIdField);
assert !abstractValue.isBottom();
- if (!abstractValue.isUnknown()) {
- entry.setValue(abstractValue);
- continue;
- }
} else if (initializationInfo.isSingleValue()) {
SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
- AbstractValue abstractValue = entry.getValue().join(singleValueInitializationInfo);
- assert !abstractValue.isBottom();
- if (!abstractValue.isUnknown()) {
- entry.setValue(abstractValue);
- continue;
- }
+ abstractValue =
+ abstractValue.join(
+ singleValueInitializationInfo,
+ appView.abstractValueFactory(),
+ field.getType(),
+ isClassIdField);
} else if (initializationInfo.isTypeInitializationInfo()) {
// TODO(b/149732532): Not handled, for now.
+ abstractValue = UnknownValue.getInstance();
} else {
assert initializationInfo.isUnknown();
+ abstractValue = UnknownValue.getInstance();
+ }
+
+ assert !abstractValue.isBottom();
+
+ // When approximating the possible values for the $r8$classId fields from horizontal class
+ // merging, give up if the set of possible values equals the size of the merge group. In
+ // this case, the information is useless.
+ if (isClassIdField && abstractValue.isNonConstantNumberValue()) {
+ NonConstantNumberValue initialAbstractValue =
+ field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
+ if (initialAbstractValue != null) {
+ if (abstractValue.asNonConstantNumberValue().getAbstractionSize()
+ >= initialAbstractValue.getAbstractionSize()) {
+ abstractValue = UnknownValue.getInstance();
+ }
+ } else {
+ assert false
+ : "Expected abstract value of "
+ + field.toSourceString()
+ + " to be instance of NonConstantNumberValue";
+ }
+ }
+
+ if (!abstractValue.isUnknown()) {
+ entry.setValue(abstractValue);
+ continue;
}
// We just lost track for this field.
@@ -236,7 +272,11 @@
.fieldInitializationInfos()
.get(field);
if (fieldInitializationInfo.isSingleValue()) {
- abstractValue = abstractValue.join(fieldInitializationInfo.asSingleValue());
+ abstractValue =
+ abstractValue.join(
+ fieldInitializationInfo.asSingleValue(),
+ appView.abstractValueFactory(),
+ field.getType());
if (abstractValue.isUnknown()) {
break;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 899fa2b..05259ca 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -40,6 +40,12 @@
public class TrivialFieldAccessReprocessor {
+ enum FieldClassification {
+ CONSTANT,
+ NON_CONSTANT,
+ UNKNOWN
+ }
+
private final AppView<AppInfoWithLiveness> appView;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
@@ -54,6 +60,9 @@
private final Set<DexEncodedField> constantFields = Sets.newConcurrentHashSet();
/** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+ private final Set<DexEncodedField> nonConstantFields = Sets.newConcurrentHashSet();
+
+ /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
public TrivialFieldAccessReprocessor(
@@ -72,7 +81,7 @@
assert feedback.noUpdatesLeft();
timing.begin("Compute fields of interest");
- computeConstantFields();
+ computeFieldsWithNonTrivialValue();
timing.end(); // Compute fields of interest
timing.begin("Enqueue methods for reprocessing");
@@ -89,17 +98,32 @@
writtenFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
}
- private void computeConstantFields() {
+ private void computeFieldsWithNonTrivialValue() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedField field : clazz.instanceFields()) {
- if (canOptimizeField(field, appView)) {
- constantFields.add(field);
+ FieldClassification fieldClassification = classifyField(field, appView);
+ switch (fieldClassification) {
+ case CONSTANT:
+ // Reprocess reads and writes.
+ constantFields.add(field);
+ break;
+ case NON_CONSTANT:
+ // Only reprocess writes, to allow branch pruning.
+ nonConstantFields.add(field);
+ break;
+ default:
+ assert fieldClassification == FieldClassification.UNKNOWN;
+ break;
}
}
if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
for (DexEncodedField field : clazz.staticFields()) {
- if (canOptimizeField(field, appView)) {
+ FieldClassification fieldClassification = classifyField(field, appView);
+ if (fieldClassification == FieldClassification.CONSTANT) {
constantFields.add(field);
+ } else {
+ assert fieldClassification == FieldClassification.NON_CONSTANT
+ || fieldClassification == FieldClassification.UNKNOWN;
}
}
}
@@ -138,30 +162,39 @@
method -> method.registerCodeReferences(new TrivialFieldAccessUseRegistry(method)));
}
- private static boolean canOptimizeField(
+ private static FieldClassification classifyField(
DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
FieldAccessInfo fieldAccessInfo =
appView.appInfo().getFieldAccessInfoCollection().get(field.field);
- if (fieldAccessInfo == null || fieldAccessInfo.isAccessedFromMethodHandle()) {
- return false;
+ if (fieldAccessInfo == null
+ || fieldAccessInfo.hasReflectiveAccess()
+ || fieldAccessInfo.isAccessedFromMethodHandle()
+ || fieldAccessInfo.isReadFromAnnotation()) {
+ return FieldClassification.UNKNOWN;
}
AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
if (abstractValue.isSingleValue()) {
SingleValue singleValue = abstractValue.asSingleValue();
if (!singleValue.isMaterializableInAllContexts(appView)) {
- return false;
+ return FieldClassification.UNKNOWN;
}
if (singleValue.isSingleConstValue()) {
- return true;
+ return FieldClassification.CONSTANT;
}
if (singleValue.isSingleFieldValue()) {
SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
DexField singleField = singleFieldValue.getField();
- return singleField != field.field
- && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView);
+ if (singleField != field.field
+ && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView)) {
+ return FieldClassification.CONSTANT;
+ }
}
+ return FieldClassification.UNKNOWN;
}
- return false;
+ if (abstractValue.isNonConstantNumberValue()) {
+ return FieldClassification.NON_CONSTANT;
+ }
+ return FieldClassification.UNKNOWN;
}
private void processFieldsNeverRead(AppInfoWithLiveness appInfo) {
@@ -262,6 +295,13 @@
DexClassAndField field = resolutionResult.getResolutionPair();
DexEncodedField definition = field.getDefinition();
+ if (definition.isStatic() != isStatic
+ || appView.isCfByteCodePassThrough(method.getDefinition())
+ || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
+ recordAccessThatCannotBeOptimized(field, definition);
+ return;
+ }
+
// Record access.
if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
if (field.getAccessFlags().isStatic() == isStatic) {
@@ -275,19 +315,17 @@
}
}
- // We cannot remove references from pass through functions.
- if (appView.isCfByteCodePassThrough(method.getDefinition())) {
- constantFields.remove(definition);
- return;
+ if (constantFields.contains(definition)
+ || (!isWrite && nonConstantFields.contains(definition))) {
+ methodsToReprocess.add(method);
}
+ }
- if (definition.isStatic() == isStatic) {
- if (constantFields.contains(definition)) {
- methodsToReprocess.add(method);
- }
- } else {
- // Should generally not happen.
- constantFields.remove(definition);
+ private void recordAccessThatCannotBeOptimized(
+ DexClassAndField field, DexEncodedField definition) {
+ constantFields.remove(definition);
+ if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+ destroyFieldAccessContexts(definition);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
rename to src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index 73c9470..af7a836 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -11,7 +11,7 @@
* A transfer function that defines the abstract semantics of the instructions in the program
* according to some abstract state {@link StateType}.
*/
-public interface TransferFunction<StateType extends AbstractState<StateType>> {
+public interface AbstractTransferFunction<StateType extends AbstractState<StateType>> {
TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index 701df38..a8a1832 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -42,6 +42,14 @@
this.blockExitStates = blockExitStates;
}
+ public StateType join() {
+ StateType result = null;
+ for (StateType blockExitState : blockExitStates.values()) {
+ result = result != null ? result.join(blockExitState) : blockExitState;
+ }
+ return result;
+ }
+
@Override
public boolean isSuccessfulAnalysisResult() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
index 4341265..fd0a761 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
@@ -6,7 +6,10 @@
import com.android.tools.r8.errors.Unreachable;
-/** Used by the {@link TransferFunction} to signal that the dataflow analysis should be aborted. */
+/**
+ * Used by the {@link AbstractTransferFunction} to signal that the dataflow analysis should be
+ * aborted.
+ */
public class FailedTransferFunctionResult<StateType extends AbstractState<StateType>>
implements TransferFunctionResult<StateType> {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index c990bdb..60136ce 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -16,11 +16,11 @@
* This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
*
* <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link
- * TransferFunction} which defines the abstract semantics for each instruction.
+ * AbstractTransferFunction} which defines the abstract semantics for each instruction.
*
* <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
- * If the supplied {@link TransferFunction} returns a {@link FailedTransferFunctionResult} for a
- * given instruction and abstract state, then the analysis return a {@link
+ * If the supplied {@link AbstractTransferFunction} returns a {@link FailedTransferFunctionResult}
+ * for a given instruction and abstract state, then the analysis return a {@link
* FailedDataflowAnalysisResult}.
*/
public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> {
@@ -28,12 +28,13 @@
private final StateType bottom;
// The transfer function that defines the abstract semantics for each instruction.
- private final TransferFunction<StateType> transfer;
+ private final AbstractTransferFunction<StateType> transfer;
// The state of the analysis.
private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
- public IntraproceduralDataflowAnalysis(StateType bottom, TransferFunction<StateType> transfer) {
+ public IntraproceduralDataflowAnalysis(
+ StateType bottom, AbstractTransferFunction<StateType> transfer) {
this.bottom = bottom;
this.transfer = transfer;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
index 183d79a..96a17ab 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
@@ -7,8 +7,8 @@
import com.android.tools.r8.ir.code.Instruction;
/**
- * The result of applying the {@link TransferFunction} to an {@link Instruction} and an {@link
- * AbstractState}.
+ * The result of applying the {@link AbstractTransferFunction} to an {@link Instruction} and an
+ * {@link AbstractState}.
*
* <p>The result can either be a new {@link AbstractState} or a failure, in which case the dataflow
* analysis is aborted.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index d45bf07..a512bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.analysis.value;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -52,6 +53,10 @@
return false;
}
+ public SingleConstValue asSingleConstValue() {
+ return null;
+ }
+
public boolean isSingleConstClassValue() {
return false;
}
@@ -104,7 +109,47 @@
return null;
}
- public AbstractValue join(AbstractValue other) {
+ public boolean isConstantOrNonConstantNumberValue() {
+ return false;
+ }
+
+ public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+ return null;
+ }
+
+ public boolean isNonConstantNumberValue() {
+ return false;
+ }
+
+ public NonConstantNumberValue asNonConstantNumberValue() {
+ return null;
+ }
+
+ public boolean isNumberFromIntervalValue() {
+ return false;
+ }
+
+ public NumberFromIntervalValue asNumberFromIntervalValue() {
+ return null;
+ }
+
+ public boolean isNumberFromSetValue() {
+ return false;
+ }
+
+ public NumberFromSetValue asNumberFromSetValue() {
+ return null;
+ }
+
+ public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) {
+ return join(other, factory, type, false);
+ }
+
+ public AbstractValue join(
+ AbstractValue other,
+ AbstractValueFactory factory,
+ DexType type,
+ boolean allowNonConstantNumbers) {
if (isBottom() || other.isUnknown()) {
return other;
}
@@ -114,11 +159,31 @@
if (equals(other)) {
return this;
}
- if (isNull()) {
- return NullOrAbstractValue.create(other);
+ if (type.isReferenceType()) {
+ if (isNull()) {
+ return NullOrAbstractValue.create(other);
+ }
+ if (other.isNull()) {
+ return NullOrAbstractValue.create(this);
+ }
}
- if (other.isNull()) {
- return NullOrAbstractValue.create(this);
+ if (allowNonConstantNumbers
+ && isConstantOrNonConstantNumberValue()
+ && other.isConstantOrNonConstantNumberValue()) {
+ NumberFromSetValue.Builder numberFromSetValueBuilder;
+ if (isSingleNumberValue()) {
+ numberFromSetValueBuilder = NumberFromSetValue.builder(asSingleNumberValue());
+ } else {
+ assert isNumberFromSetValue();
+ numberFromSetValueBuilder = asNumberFromSetValue().instanceBuilder();
+ }
+ if (other.isSingleNumberValue()) {
+ numberFromSetValueBuilder.addInt(other.asSingleNumberValue().getIntValue());
+ } else {
+ assert other.isNumberFromSetValue();
+ numberFromSetValueBuilder.addInts(other.asNumberFromSetValue());
+ }
+ return numberFromSetValueBuilder.build(factory);
}
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java
new file mode 100644
index 0000000..4c6cf04
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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.analysis.value;
+
+import com.android.tools.r8.utils.OptionalBool;
+
+public interface ConstantOrNonConstantNumberValue {
+
+ boolean containsInt(int value);
+
+ OptionalBool isSubsetOf(int[] values);
+
+ ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue();
+
+ boolean isSingleNumberValue();
+
+ SingleNumberValue asSingleNumberValue();
+
+ boolean isNonConstantNumberValue();
+
+ NonConstantNumberValue asNonConstantNumberValue();
+
+ boolean isNumberFromIntervalValue();
+
+ NumberFromIntervalValue asNumberFromIntervalValue();
+
+ boolean isNumberFromSetValue();
+
+ NumberFromSetValue asNumberFromSetValue();
+
+ boolean mayOverlapWith(ConstantOrNonConstantNumberValue other);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java
new file mode 100644
index 0000000..e233568
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, 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.analysis.value;
+
+public abstract class NonConstantNumberValue extends AbstractValue
+ implements ConstantOrNonConstantNumberValue {
+
+ @Override
+ public boolean isNonConstantNumberValue() {
+ return true;
+ }
+
+ @Override
+ public NonConstantNumberValue asNonConstantNumberValue() {
+ return this;
+ }
+
+ @Override
+ public boolean isConstantOrNonConstantNumberValue() {
+ return true;
+ }
+
+ @Override
+ public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+ return this;
+ }
+
+ public abstract long getAbstractionSize();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
new file mode 100644
index 0000000..f100893
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2021, 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.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.Objects;
+
+public class NumberFromIntervalValue extends NonConstantNumberValue {
+
+ private final long minInclusive;
+ private final long maxInclusive;
+
+ public NumberFromIntervalValue(long minInclusive, long maxInclusive) {
+ assert maxInclusive > minInclusive;
+ this.minInclusive = minInclusive;
+ this.maxInclusive = maxInclusive;
+ }
+
+ @Override
+ public boolean containsInt(int value) {
+ return minInclusive <= value && value <= maxInclusive;
+ }
+
+ @Override
+ public long getAbstractionSize() {
+ return maxInclusive - minInclusive + 1;
+ }
+
+ @Override
+ public boolean isNumberFromIntervalValue() {
+ return true;
+ }
+
+ @Override
+ public NumberFromIntervalValue asNumberFromIntervalValue() {
+ return this;
+ }
+
+ @Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
+ public OptionalBool isSubsetOf(int[] values) {
+ // Not implemented.
+ return OptionalBool.unknown();
+ }
+
+ @Override
+ public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+ if (other.isSingleNumberValue()) {
+ return containsInt(other.asSingleNumberValue().getIntValue());
+ }
+ if (other.isNumberFromIntervalValue()) {
+ return mayOverlapWith(other.asNumberFromIntervalValue());
+ }
+ assert other.isNumberFromSetValue();
+ return mayOverlapWith(other.asNumberFromSetValue());
+ }
+
+ public boolean mayOverlapWith(NumberFromIntervalValue other) {
+ return minInclusive <= other.maxInclusive && maxInclusive >= other.minInclusive;
+ }
+
+ public boolean mayOverlapWith(NumberFromSetValue other) {
+ return other.mayOverlapWith(this);
+ }
+
+ @Override
+ public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || o.getClass() != getClass()) {
+ return false;
+ }
+ NumberFromIntervalValue numberFromIntervalValue = (NumberFromIntervalValue) o;
+ return minInclusive == numberFromIntervalValue.minInclusive
+ && maxInclusive == numberFromIntervalValue.maxInclusive;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 31 * (31 * (31 + Long.hashCode(minInclusive)) + Long.hashCode(maxInclusive));
+ assert hash == Objects.hash(minInclusive, maxInclusive);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return "NumberFromIntervalValue([" + minInclusive + "; " + maxInclusive + "])";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
new file mode 100644
index 0000000..e6062f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
@@ -0,0 +1,163 @@
+// Copyright (c) 2021, 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.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.util.Arrays;
+
+public class NumberFromSetValue extends NonConstantNumberValue {
+
+ private static final int MAX_SIZE = 30;
+
+ private final IntSet numbers;
+
+ private NumberFromSetValue(IntSet numbers) {
+ this.numbers = numbers;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ static Builder builder(SingleNumberValue singleNumberValue) {
+ return new Builder().addInt(singleNumberValue.getIntValue());
+ }
+
+ Builder instanceBuilder() {
+ return new Builder(this);
+ }
+
+ @Override
+ public boolean containsInt(int value) {
+ return numbers.contains(value);
+ }
+
+ @Override
+ public long getAbstractionSize() {
+ return numbers.size();
+ }
+
+ @Override
+ public boolean isNumberFromSetValue() {
+ return true;
+ }
+
+ @Override
+ public NumberFromSetValue asNumberFromSetValue() {
+ return this;
+ }
+
+ @Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
+ public OptionalBool isSubsetOf(int[] values) {
+ assert ArrayUtils.isSorted(values);
+ for (int number : numbers) {
+ if (Arrays.binarySearch(values, number) < 0) {
+ return OptionalBool.FALSE;
+ }
+ }
+ return OptionalBool.TRUE;
+ }
+
+ @Override
+ public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+ if (other.isSingleNumberValue()) {
+ return containsInt(other.asSingleNumberValue().getIntValue());
+ }
+ assert other.isNonConstantNumberValue();
+ for (int number : numbers) {
+ if (other.containsInt(number)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || o.getClass() != getClass()) {
+ return false;
+ }
+ NumberFromSetValue numberFromSetValue = (NumberFromSetValue) o;
+ return numbers.equals(numberFromSetValue.numbers);
+ }
+
+ @Override
+ public int hashCode() {
+ return numbers.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("NumberFromSetValue(");
+ IntIterator iterator = numbers.iterator();
+ builder.append(iterator.nextInt());
+ while (iterator.hasNext()) {
+ builder.append(", ").append(iterator.nextInt());
+ }
+ return builder.append(")").toString();
+ }
+
+ static class Builder {
+
+ private IntSet numbers;
+
+ Builder() {
+ numbers = new IntArraySet();
+ }
+
+ Builder(NumberFromSetValue numberFromSetValue) {
+ numbers = new IntArraySet(numberFromSetValue.numbers);
+ }
+
+ Builder addInt(int number) {
+ if (numbers != null) {
+ assert numbers.size() <= MAX_SIZE;
+ if (numbers.add(number) && numbers.size() > MAX_SIZE) {
+ numbers = null;
+ }
+ }
+ return this;
+ }
+
+ Builder addInts(NumberFromSetValue numberFromSetValue) {
+ if (numbers != null) {
+ assert numbers.size() <= MAX_SIZE;
+ if (numbers.addAll(numberFromSetValue.numbers) && numbers.size() > MAX_SIZE) {
+ numbers = null;
+ }
+ }
+ return this;
+ }
+
+ AbstractValue build(AbstractValueFactory abstractValueFactory) {
+ if (numbers != null) {
+ assert !numbers.isEmpty();
+ assert numbers.size() <= MAX_SIZE;
+ if (numbers.size() == 1) {
+ return abstractValueFactory.createSingleNumberValue(numbers.iterator().nextInt());
+ }
+ return new NumberFromSetValue(numbers);
+ }
+ return UnknownValue.getInstance();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index f0af9b4..4db63a6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -8,10 +8,12 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
public abstract class ObjectState {
@@ -25,6 +27,22 @@
public abstract void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer);
+ public final boolean hasMaterializableFieldValueThatMatches(
+ AppView<AppInfoWithLiveness> appView,
+ DexEncodedField field,
+ ProgramMethod context,
+ Predicate<SingleValue> predicate) {
+ AbstractValue abstractValue = getAbstractFieldValue(field);
+ if (!abstractValue.isSingleValue()) {
+ return false;
+ }
+ SingleValue singleValue = abstractValue.asSingleValue();
+ if (!singleValue.isMaterializableInContext(appView, context)) {
+ return false;
+ }
+ return predicate.test(singleValue);
+ }
+
public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
index ecf9838..11d238a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
@@ -10,4 +10,9 @@
public boolean isSingleConstValue() {
return true;
}
+
+ @Override
+ public SingleConstValue asSingleConstValue() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 76e6ac8..d51b94a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -16,8 +16,11 @@
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
-public class SingleNumberValue extends SingleConstValue {
+public class SingleNumberValue extends SingleConstValue
+ implements ConstantOrNonConstantNumberValue {
private final long value;
@@ -27,6 +30,16 @@
}
@Override
+ public boolean containsInt(int value) {
+ return value == getIntValue();
+ }
+
+ @Override
+ public OptionalBool isSubsetOf(int[] values) {
+ return OptionalBool.of(ArrayUtils.containsInt(values, getIntValue()));
+ }
+
+ @Override
public boolean isSingleBoolean() {
return isFalse() || isTrue();
}
@@ -51,6 +64,16 @@
return this;
}
+ @Override
+ public boolean isConstantOrNonConstantNumberValue() {
+ return true;
+ }
+
+ @Override
+ public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+ return this;
+ }
+
public boolean getBooleanValue() {
assert value == 0 || value == 1;
return value != 0;
@@ -77,6 +100,15 @@
}
@Override
+ public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+ if (other.isSingleNumberValue()) {
+ return equals(other.asSingleNumberValue());
+ }
+ assert other.isNonConstantNumberValue();
+ return other.asNonConstantNumberValue().containsInt(getIntValue());
+ }
+
+ @Override
public boolean equals(Object o) {
return this == o;
}
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 cbc8c6b..506e8af 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
@@ -1096,6 +1096,14 @@
return arguments;
}
+ public Argument getLastArgument() {
+ InstructionIterator instructionIterator = entryBlock().iterator(getNumberOfArguments() - 1);
+ Argument lastArgument = instructionIterator.next().asArgument();
+ assert lastArgument != null;
+ assert !instructionIterator.peekNext().isArgument();
+ return lastArgument;
+ }
+
public Value getThis() {
if (method().accessFlags.isStatic()) {
return null;
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 689514f..4c1d942 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
@@ -309,7 +309,11 @@
}
public void replace(Instruction newInstruction, IRCode code) {
- getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction);
+ replace(newInstruction, code, null);
+ }
+
+ public void replace(Instruction newInstruction, IRCode code, Set<Value> affectedValues) {
+ getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction, affectedValues);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index fdaf8f0..94ab740 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Iterables;
import java.util.List;
public abstract class InvokeMethodWithReceiver extends InvokeMethod {
@@ -32,6 +33,10 @@
super(target, result, arguments);
}
+ public Iterable<Value> getNonReceiverArguments() {
+ return Iterables.skip(arguments(), 1);
+ }
+
public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) {
assert isInvokeMethodWithDynamicDispatch();
return getReceiver().getDynamicLowerBoundType(appView) != null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e0841f7..8a63d63 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -164,7 +164,7 @@
DexBuilder.removeRedundantDebugPositions(code);
CfCode code = buildCfCode();
assert verifyInvokeInterface(code, appView);
- assert code.verifyFrames(method, appView, this.code.origin, false);
+ assert code.verifyFrames(method, appView, this.code.origin, false).isValid();
return code;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 50ba68b..1d7f619 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -671,7 +671,7 @@
public DexType getPhiTypeForBlock(
int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED;
- if (code.getStackMapStatus() == StackMapStatus.INVALID_OR_NOT_PRESENT) {
+ if (code.getStackMapStatus().isInvalidOrNotPresent()) {
return null;
}
// We should be able to find the a snapshot at the block-offset:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 6d79c9c..8f83dca 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.utils.ThreadUtils;
@@ -50,6 +52,11 @@
ClassConverterResult.Builder resultBuilder, ExecutorService executorService)
throws ExecutionException {
List<DexProgramClass> classes = appView.appInfo().classes();
+
+ D8CfClassDesugaringEventConsumer classDesugaringEventConsumer =
+ CfClassDesugaringEventConsumer.createForD8(methodProcessor);
+ converter.desugarClassesForD8(classes, classDesugaringEventConsumer, executorService);
+
while (!classes.isEmpty()) {
Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2);
@@ -65,18 +72,19 @@
}
}
- // Process the wave and wait for all IR processing to complete.
- D8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
+ D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
+
+ // Process the wave and wait for all IR processing to complete.
methodProcessor.newWave();
ThreadUtils.processItems(
- wave, clazz -> convertClass(clazz, desugaringEventConsumer), executorService);
+ wave, clazz -> convertClass(clazz, instructionDesugaringEventConsumer), executorService);
methodProcessor.awaitMethodProcessing();
// Finalize the desugaring of the processed classes. This may require processing (and
// reprocessing) of some methods.
List<ProgramMethod> needsProcessing =
- desugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
+ instructionDesugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
if (!needsProcessing.isEmpty()) {
// Create a new processor context to ensure unique method processing contexts.
methodProcessor.newWave();
@@ -90,13 +98,13 @@
if (definition.isProcessed()) {
definition.markNotProcessed();
}
- methodProcessor.processMethod(method, desugaringEventConsumer);
+ methodProcessor.processMethod(method, instructionDesugaringEventConsumer);
},
executorService);
// Verify there is nothing to finalize once method processing finishes.
methodProcessor.awaitMethodProcessing();
- assert desugaringEventConsumer.verifyNothingToFinalize();
+ assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
}
classes = deferred;
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 72f9ac7..54b0a66 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
@@ -42,6 +42,8 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
@@ -125,7 +127,8 @@
private final Timing timing;
private final Outliner outliner;
private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
- private final CfInstructionDesugaringCollection desugaring;
+ private final CfClassDesugaringCollection classDesugaring;
+ private final CfInstructionDesugaringCollection instructionDesugaring;
private final FieldAccessAnalysis fieldAccessAnalysis;
private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
private final StringOptimizer stringOptimizer;
@@ -217,7 +220,8 @@
// - nest based access desugaring,
// - invoke-special desugaring.
assert options.desugarState.isOn();
- this.desugaring = CfInstructionDesugaringCollection.create(appView);
+ this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
+ this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
this.desugaredLibraryRetargeter =
options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
? null
@@ -249,10 +253,11 @@
this.assumeInserter = null;
return;
}
- this.desugaring =
+ this.instructionDesugaring =
appView.enableWholeProgramOptimizations()
? CfInstructionDesugaringCollection.empty()
: CfInstructionDesugaringCollection.create(appView);
+ this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
this.interfaceMethodRewriter =
options.isInterfaceMethodDesugaringEnabled()
? new InterfaceMethodRewriter(appView, this)
@@ -349,7 +354,7 @@
private void synthesizeBridgesForNestBasedAccessesOnClasspath(
D8MethodProcessor methodProcessor, ExecutorService executorService)
throws ExecutionException {
- desugaring.withD8NestBasedAccessDesugaring(
+ instructionDesugaring.withD8NestBasedAccessDesugaring(
d8NestBasedAccessDesugaring ->
d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
methodProcessor, executorService));
@@ -357,14 +362,15 @@
}
private void reportNestDesugarDependencies() {
- desugaring.withD8NestBasedAccessDesugaring(
+ instructionDesugaring.withD8NestBasedAccessDesugaring(
D8NestBasedAccessDesugaring::reportDesugarDependencies);
}
- private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService)
+ private void staticizeClasses(
+ OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
throws ExecutionException {
if (classStaticizer != null) {
- classStaticizer.staticizeCandidates(feedback, executorService);
+ classStaticizer.staticizeCandidates(feedback, executorService, applied);
}
}
@@ -459,6 +465,26 @@
appView, classConverterResult.getForcefullyMovedLambdaMethods());
}
+ public void desugarClassesForD8(
+ List<DexProgramClass> classes,
+ D8CfClassDesugaringEventConsumer desugaringEventConsumer,
+ ExecutorService executorService)
+ throws ExecutionException {
+ if (classDesugaring.isEmpty()) {
+ return;
+ }
+ // Currently the classes can be processed in any order and do not require to be sorted.
+ ThreadUtils.processItems(
+ classes, clazz -> desugarClassForD8(clazz, desugaringEventConsumer), executorService);
+ }
+
+ public void desugarClassForD8(
+ DexProgramClass clazz, D8CfClassDesugaringEventConsumer desugaringEventConsumer) {
+ if (classDesugaring.needsDesugaring(clazz)) {
+ classDesugaring.desugar(clazz, desugaringEventConsumer);
+ }
+ }
+
void convertMethods(
DexProgramClass clazz,
D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
@@ -544,7 +570,7 @@
if (!options.cfToCfDesugar) {
return true;
}
- if (desugaring.needsDesugaring(method)) {
+ if (instructionDesugaring.needsDesugaring(method)) {
return true;
}
if (desugaredLibraryAPIConverter != null
@@ -620,7 +646,7 @@
AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
throws ExecutionException {
// Desugaring happens in the enqueuer.
- assert desugaring.isEmpty();
+ assert instructionDesugaring.isEmpty();
DexApplication application = appView.appInfo().app();
@@ -723,7 +749,7 @@
if (!options.isGeneratingClassFiles()) {
printPhase("Class staticizer post processing");
// TODO(b/127694949): Adapt to PostOptimization.
- staticizeClasses(feedback, executorService);
+ staticizeClasses(feedback, executorService, initialGraphLensForIR);
feedback.updateVisibleOptimizationInfo();
// The class staticizer lens shall not be applied through lens code rewriting or it breaks
// the lambda merger.
@@ -1109,13 +1135,15 @@
ProgramMethod method,
CfInstructionDesugaringEventConsumer desugaringEventConsumer,
MethodProcessingContext methodProcessingContext) {
- if (options.desugarState.isOff()
- || !method.getDefinition().getCode().isCfCode()
- || !desugaring.needsDesugaring(method)) {
+ if (options.desugarState.isOff() || !method.getDefinition().getCode().isCfCode()) {
return false;
}
- desugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
- return true;
+ instructionDesugaring.scan(method, desugaringEventConsumer);
+ if (instructionDesugaring.needsDesugaring(method)) {
+ instructionDesugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
+ return true;
+ }
+ return false;
}
// TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 0cdf967..9dfe2e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -13,9 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -64,15 +62,11 @@
void setClassInlinerMethodConstraint(
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
- void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
-
void setInstanceInitializerInfoCollection(
DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method);
- void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
-
void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts);
void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts);
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 bd5c0b3..5cd32f2 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
@@ -78,7 +78,8 @@
CfInvoke invoke = instruction.asInvoke();
MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod());
return methodProvider != null
- ? methodProvider.rewriteInvoke(invoke, appView, eventConsumer, methodProcessingContext)
+ ? methodProvider.rewriteInvoke(
+ invoke, appView, eventConsumer, methodProcessingContext, localStackAllocator)
: null;
}
@@ -194,6 +195,7 @@
}
// These are currently not implemented at any API level in Android.
+ initializeJava9MethodProviders(factory);
initializeJava10MethodProviders(factory);
initializeJava11MethodProviders(factory);
}
@@ -1043,6 +1045,60 @@
}
}
+ private void initializeJava9MethodProviders(DexItemFactory factory) {
+ // Integer
+ DexType type = factory.boxedIntType;
+ // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix)
+ DexString name = factory.createString("parseInt");
+ DexProto proto =
+ factory.createProto(
+ factory.intType,
+ factory.charSequenceType,
+ factory.intType,
+ factory.intType,
+ factory.intType);
+ DexMethod method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method,
+ BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
+ "parseIntSubsequenceWithRadix"));
+
+ // Long
+ type = factory.boxedLongType;
+ // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix)
+ name = factory.createString("parseLong");
+ proto =
+ factory.createProto(
+ factory.longType,
+ factory.charSequenceType,
+ factory.intType,
+ factory.intType,
+ factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method,
+ BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
+ "parseLongSubsequenceWithRadix"));
+
+ // long Long.parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix)
+ name = factory.createString("parseUnsignedLong");
+ proto =
+ factory.createProto(
+ factory.longType,
+ factory.charSequenceType,
+ factory.intType,
+ factory.intType,
+ factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method,
+ BackportedMethods::LongMethods_parseUnsignedLongSubsequenceWithRadix,
+ "parseUnsignedLongSubsequenceWithRadix"));
+ }
+
private void initializeJava10MethodProviders(DexItemFactory factory) {
// List
DexType type = factory.listType;
@@ -1309,7 +1365,8 @@
CfInvoke invoke,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
- MethodProcessingContext methodProcessingContext);
+ MethodProcessingContext methodProcessingContext,
+ LocalStackAllocator localStackAllocator);
}
private static final class InvokeRewriter extends MethodProvider {
@@ -1326,8 +1383,9 @@
CfInvoke invoke,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
- MethodProcessingContext methodProcessingContext) {
- return rewriter.rewrite(invoke, appView.dexItemFactory());
+ MethodProcessingContext methodProcessingContext,
+ LocalStackAllocator localStackAllocator) {
+ return rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator);
}
}
@@ -1351,7 +1409,8 @@
CfInvoke invoke,
AppView<?> appView,
BackportedMethodDesugaringEventConsumer eventConsumer,
- MethodProcessingContext methodProcessingContext) {
+ MethodProcessingContext methodProcessingContext,
+ LocalStackAllocator localStackAllocator) {
ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext);
eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext());
return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
@@ -1410,7 +1469,8 @@
CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory);
// Convenience wrapper since most rewrites are to a single instruction.
- default Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+ default Collection<CfInstruction> rewrite(
+ CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
return ImmutableList.of(rewriteSingle(invoke, factory));
}
}
@@ -1423,6 +1483,7 @@
}
@Override
- public abstract Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory);
+ public abstract Collection<CfInstruction> rewrite(
+ CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java
new file mode 100644
index 0000000..a885344
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+/** Interface for desugaring a class. */
+public interface CfClassDesugaring {
+
+ void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer);
+
+ /** Returns true if the given class needs desugaring. */
+ boolean needsDesugaring(DexProgramClass clazz);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
new file mode 100644
index 0000000..9b7c709
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+/** Interface for desugaring a class. */
+public abstract class CfClassDesugaringCollection {
+
+ public abstract void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer);
+
+ /** Returns true if the given class needs desugaring. */
+ public abstract boolean needsDesugaring(DexProgramClass clazz);
+
+ public abstract boolean isEmpty();
+
+ public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+ private final RecordRewriter recordRewriter;
+
+ NonEmptyCfClassDesugaringCollection(RecordRewriter recordRewriter) {
+ this.recordRewriter = recordRewriter;
+ }
+
+ @Override
+ public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+ recordRewriter.desugar(clazz, eventConsumer);
+ }
+
+ @Override
+ public boolean needsDesugaring(DexProgramClass clazz) {
+ return recordRewriter.needsDesugaring(clazz);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+ }
+
+ public static class EmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+ @Override
+ public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public boolean needsDesugaring(DexProgramClass clazz) {
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
new file mode 100644
index 0000000..d5acaa7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+
+public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer {
+
+ public static D8CfClassDesugaringEventConsumer createForD8(D8MethodProcessor methodProcessor) {
+ return new D8CfClassDesugaringEventConsumer(methodProcessor);
+ }
+
+ public static class D8CfClassDesugaringEventConsumer extends CfClassDesugaringEventConsumer {
+
+ private final D8MethodProcessor methodProcessor;
+
+ public D8CfClassDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
+ this.methodProcessor = methodProcessor;
+ }
+
+ @Override
+ public void acceptRecordClass(DexProgramClass recordClass) {
+ methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
+ }
+ }
+
+ // TODO(b/): Implement R8CfClassDesugaringEventConsumer
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 21ac7ae..3fb9329 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
/**
* Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -34,6 +35,9 @@
return EmptyCfInstructionDesugaringCollection.getInstance();
}
+ public abstract void scan(
+ ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer);
+
/** Desugars the instructions in the given method. */
public abstract void desugar(
ProgramMethod method,
@@ -44,9 +48,13 @@
return false;
}
+ public abstract CfClassDesugaringCollection createClassDesugaringCollection();
+
/** Returns true if the given method needs desugaring. */
public abstract boolean needsDesugaring(ProgramMethod method);
public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
+
+ public abstract void withRecordRewriter(Consumer<RecordRewriter> consumer);
}
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 d765537..a95569e 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
@@ -39,6 +39,7 @@
InvokeSpecialToSelfDesugaringEventConsumer,
LambdaDesugaringEventConsumer,
NestBasedAccessDesugaringEventConsumer,
+ RecordDesugaringEventConsumer,
TwrCloseResourceDesugaringEventConsumer {
public static D8CfInstructionDesugaringEventConsumer createForD8(
@@ -58,6 +59,11 @@
return new CfInstructionDesugaringEventConsumer() {
@Override
+ public void acceptRecordClass(DexProgramClass recordClass) {
+ assert false;
+ }
+
+ @Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
assert false;
}
@@ -121,6 +127,11 @@
}
@Override
+ public void acceptRecordClass(DexProgramClass recordClass) {
+ methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
+ }
+
+ @Override
public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
synchronized (synthesizedLambdaClasses) {
synthesizedLambdaClasses.add(lambdaClass);
@@ -225,6 +236,12 @@
}
@Override
+ public void acceptRecordClass(DexProgramClass recordClass) {
+ // This is called each time an instruction or a class is found to require the record class.
+ assert false : "TODO(b/179146128): To be implemented";
+ }
+
+ @Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
// Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
// present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 6dbd525..8a6624e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -256,7 +256,7 @@
: new DexEncodedMethod[] {uniqueMethod},
factory.getSkipNameValidationForTesting(),
getChecksumSupplier(uniqueMethod, appView));
- appView.appInfo().addSynthesizedClass(newClass, false);
+ appView.appInfo().addSynthesizedClassForLibraryDesugaring(newClass);
builder.addSynthesizedClass(newClass);
}
@@ -409,6 +409,21 @@
itemFactory.createMethod(
itemFactory.createType("Ljava/util/DesugarArrays;"), proto, name);
retargetLibraryMember.put(source, target);
+
+ // TODO(b/181629049): This is only a workaround rewriting invokes of
+ // j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+ // to j.u.DesugarArrays.deepEquals0.
+ name = itemFactory.createString("getTimeZone");
+ proto =
+ itemFactory.createProto(
+ itemFactory.createType("Ljava/util/TimeZone;"),
+ itemFactory.createType("Ljava/time/ZoneId;"));
+ source =
+ itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+ target =
+ itemFactory.createMethod(
+ itemFactory.createType("Ljava/util/DesugarTimeZone;"), proto, name);
+ retargetLibraryMember.put(source, target);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index eddbf6d..ce4f61a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -537,7 +537,7 @@
throws ExecutionException {
for (DexProgramClass wrapper : wrappers) {
builder.addSynthesizedClass(wrapper);
- appView.appInfo().addSynthesizedClass(wrapper, false);
+ appView.appInfo().addSynthesizedClassForLibraryDesugaring(wrapper);
}
irConverter.optimizeSynthesizedClasses(wrappers, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 60b3537..c8b6147 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -6,8 +6,10 @@
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
@@ -22,6 +24,11 @@
}
@Override
+ public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ // Intentionally empty.
+ }
+
+ @Override
public void desugar(
ProgramMethod method,
MethodProcessingContext methodProcessingContext,
@@ -35,6 +42,11 @@
}
@Override
+ public CfClassDesugaringCollection createClassDesugaringCollection() {
+ return new EmptyCfClassDesugaringCollection();
+ }
+
+ @Override
public boolean needsDesugaring(ProgramMethod method) {
return false;
}
@@ -44,4 +56,9 @@
ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) {
// Intentionally empty.
}
+
+ @Override
+ public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
+ // Intentionally empty.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 7d041f6..a1c0d58 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -979,8 +979,7 @@
emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
DexEncodedMethod.EMPTY_ARRAY,
factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType,
- Collections.singletonList(theInterface));
+ DexProgramClass::checksumFromType);
clazz.forEachProgramMethod(synthesizedMethods::add);
return clazz;
}
@@ -1467,7 +1466,7 @@
// At this point we likely have a non-library type that may depend on default method information
// from its interfaces and the dependency should be reported.
if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
- reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options());
+ reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.appInfo());
}
// Merge information from all superinterfaces.
@@ -1491,19 +1490,19 @@
}
public static void reportDependencyEdge(
- DexClass dependent, DexClass dependency, InternalOptions options) {
+ DexClass dependent, DexClass dependency, AppInfo appInfo) {
assert !dependent.isLibraryClass();
assert !dependency.isLibraryClass();
- DesugarGraphConsumer consumer = options.desugarGraphConsumer;
+ DesugarGraphConsumer consumer = appInfo.app().options.desugarGraphConsumer;
if (consumer != null) {
Origin dependencyOrigin = dependency.getOrigin();
- java.util.Collection<DexProgramClass> dependents =
- dependent.isProgramClass() ? dependent.asProgramClass().getSynthesizedFrom() : null;
- if (dependents == null || dependents.isEmpty()) {
+ java.util.Collection<DexType> dependents =
+ appInfo.getSyntheticItems().getSynthesizingContexts(dependent.getType());
+ if (dependents.isEmpty()) {
reportDependencyEdge(consumer, dependencyOrigin, dependent);
} else {
- for (DexClass clazz : dependents) {
- reportDependencyEdge(consumer, dependencyOrigin, clazz);
+ for (DexType type : dependents) {
+ reportDependencyEdge(consumer, dependencyOrigin, appInfo.definitionFor(type));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index e40251c..d20eee7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -135,8 +135,7 @@
companionMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
DexEncodedMethod.EMPTY_ARRAY,
rewriter.factory.getSkipNameValidationForTesting(),
- getChecksumSupplier(iface),
- Collections.singletonList(iface));
+ getChecksumSupplier(iface));
syntheticClasses.put(iface, companionClass);
if (companionClass.hasClassInitializer()) {
newSynthesizedMethodConsumer.accept(companionClass.getProgramClassInitializer());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 076c0c4..d54264c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -230,16 +230,17 @@
int fieldCount = fieldTypes.length;
List<DexEncodedField> fields = new ArrayList<>(fieldCount);
for (int i = 0; i < fieldCount; i++) {
- FieldAccessFlags accessFlags =
- FieldAccessFlags.fromSharedAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
+ boolean deprecated = false;
+ boolean d8R8Synthesized = true;
fields.add(
new DexEncodedField(
getCaptureField(i),
- accessFlags,
+ FieldAccessFlags.createPublicFinalSynthetic(),
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
- null));
+ null,
+ deprecated,
+ d8R8Synthesized));
}
builder.setInstanceFields(fields);
}
@@ -249,6 +250,8 @@
if (isStateless()) {
// Create instance field for stateless lambda.
assert this.lambdaField != null;
+ boolean deprecated = false;
+ boolean d8R8Synthesized = true;
builder.setStaticFields(
Collections.singletonList(
new DexEncodedField(
@@ -260,7 +263,9 @@
| Constants.ACC_STATIC),
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
- DexValueNull.NULL)));
+ DexValueNull.NULL,
+ deprecated,
+ d8R8Synthesized)));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index cbab081..6975252 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -361,9 +361,18 @@
Builder<CfInstruction> instructions,
DexItemFactory factory) {
internalAdjustType(fromType, toType, returnType, instructions, factory);
+ if (fromType == toType) {
+ return ValueType.fromDexType(fromType).requiredRegisters();
+ }
+ // Account for the potential unboxing of a wide type.
+ DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(fromType);
return Math.max(
ValueType.fromDexType(fromType).requiredRegisters(),
- ValueType.fromDexType(toType).requiredRegisters());
+ Math.max(
+ fromTypeAsPrimitive == null
+ ? 0
+ : ValueType.fromDexType(fromTypeAsPrimitive).requiredRegisters(),
+ ValueType.fromDexType(toType).requiredRegisters()));
}
private static void internalAdjustType(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 5815053..3a7a195 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
@@ -28,6 +30,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Consumer;
public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
@@ -35,6 +38,7 @@
private final List<CfInstructionDesugaring> desugarings = new ArrayList<>();
private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
+ private final RecordRewriter recordRewriter;
NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
this.appView = appView;
@@ -54,6 +58,11 @@
if (nestBasedAccessDesugaring != null) {
desugarings.add(nestBasedAccessDesugaring);
}
+ this.recordRewriter = RecordRewriter.create(appView);
+ if (recordRewriter != null) {
+ assert !appView.enableWholeProgramOptimizations() : "To be implemented";
+ desugarings.add(recordRewriter);
+ }
}
// TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled.
@@ -62,6 +71,7 @@
AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) {
this.appView = appView;
this.nestBasedAccessDesugaring = null;
+ this.recordRewriter = null;
desugarings.add(invokeSpecialToSelfDesugaring);
}
@@ -72,13 +82,8 @@
appView, new InvokeSpecialToSelfDesugaring(appView));
}
- @Override
- public void desugar(
- ProgramMethod method,
- MethodProcessingContext methodProcessingContext,
- CfInstructionDesugaringEventConsumer eventConsumer) {
- Code code = method.getDefinition().getCode();
- if (!code.isCfCode()) {
+ private void ensureCfCode(ProgramMethod method) {
+ if (!method.getDefinition().getCode().isCfCode()) {
appView
.options()
.reporter
@@ -87,10 +92,24 @@
"Unsupported attempt to desugar non-CF code",
method.getOrigin(),
method.getPosition()));
- return;
}
+ }
- CfCode cfCode = code.asCfCode();
+ @Override
+ public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ ensureCfCode(method);
+ if (recordRewriter != null) {
+ recordRewriter.scan(method, eventConsumer);
+ }
+ }
+
+ @Override
+ public void desugar(
+ ProgramMethod method,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer) {
+ ensureCfCode(method);
+ CfCode cfCode = method.getDefinition().getCode().asCfCode();
// Tracking of temporary locals used for instruction desugaring. The desugaring of each
// instruction is assumed to use locals only for the duration of the instruction, such that any
@@ -114,12 +133,14 @@
method,
methodProcessingContext);
if (replacement != null) {
- // Record if we increased the max number of locals for the method, and reset the
- // next temporary locals register.
+ // Record if we increased the max number of locals and stack height for the method,
+ // and reset the next temporary locals register.
maxLocalsForCode.setMax(maxLocalsForInstruction.getAndSet(cfCode.getMaxLocals()));
+ maxStackForCode.setMax(maxStackForInstruction.getAndSet(cfCode.getMaxStack()));
} else {
// The next temporary locals register should be unchanged.
assert maxLocalsForInstruction.get() == cfCode.getMaxLocals();
+ assert maxStackForInstruction.get() == cfCode.getMaxStack();
}
return replacement;
},
@@ -135,6 +156,14 @@
}
}
+ @Override
+ public CfClassDesugaringCollection createClassDesugaringCollection() {
+ if (recordRewriter == null) {
+ return new EmptyCfClassDesugaringCollection();
+ }
+ return new NonEmptyCfClassDesugaringCollection(recordRewriter);
+ }
+
private Collection<CfInstruction> desugarInstruction(
CfInstruction instruction,
FreshLocalProvider freshLocalProvider,
@@ -220,4 +249,11 @@
consumer.accept((D8NestBasedAccessDesugaring) nestBasedAccessDesugaring);
}
}
+
+ @Override
+ public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
+ if (recordRewriter != null) {
+ consumer.accept(recordRewriter);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
new file mode 100644
index 0000000..19d9ae3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public interface RecordDesugaringEventConsumer {
+
+ void acceptRecordClass(DexProgramClass recordClass);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
new file mode 100644
index 0000000..67e2c90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -0,0 +1,272 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+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.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Collections;
+
+public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring {
+
+ private final AppView<?> appView;
+ private final DexItemFactory factory;
+
+ public static RecordRewriter create(AppView<?> appView) {
+ return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null;
+ }
+
+ private RecordRewriter(AppView<?> appView) {
+ this.appView = appView;
+ factory = appView.dexItemFactory();
+ }
+
+ public void scan(
+ ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+ CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
+ for (CfInstruction instruction : cfCode.getInstructions()) {
+ scanInstruction(instruction, eventConsumer);
+ }
+ }
+
+ // The record rewriter scans the cf instructions to figure out if the record class needs to
+ // be added in the output. the analysis cannot be done in desugarInstruction because the analysis
+ // does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one
+ // instruction for assertions to be valid.
+ private void scanInstruction(
+ CfInstruction instruction, CfInstructionDesugaringEventConsumer eventConsumer) {
+ assert !instruction.isInitClass();
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ if (refersToRecord(cfInvoke.getMethod())) {
+ requiresRecordClass(eventConsumer);
+ }
+ return;
+ }
+ if (instruction.isFieldInstruction()) {
+ CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ if (refersToRecord(fieldInstruction.getField())) {
+ requiresRecordClass(eventConsumer);
+ }
+ return;
+ }
+ if (instruction.isTypeInstruction()) {
+ CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
+ if (refersToRecord(typeInstruction.getType())) {
+ requiresRecordClass(eventConsumer);
+ }
+ return;
+ }
+ // TODO(b/179146128): Analyse MethodHandle and MethodType.
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext) {
+
+ // TODO(b/179146128): This is a temporary work-around to test desugaring of records
+ // without rewriting the record invoke-custom. This should be removed when the record support
+ // is complete.
+ if (instruction.isInvokeDynamic()
+ && context.getHolder().superType == factory.recordType
+ && (context.getReference().match(factory.recordMembers.toString)
+ || context.getReference().match(factory.recordMembers.hashCode)
+ || context.getReference().match(factory.recordMembers.equals))) {
+ requiresRecordClass(eventConsumer);
+ CfInstruction constant =
+ context.getReference().match(factory.recordMembers.toString)
+ ? new CfConstNull()
+ : new CfConstNumber(0, ValueType.INT);
+ return ImmutableList.of(new CfStackInstruction(CfStackInstruction.Opcode.Pop), constant);
+ }
+
+ CfInstruction desugaredInstruction = desugarInstruction(instruction, context);
+ return desugaredInstruction == null ? null : Collections.singletonList(desugaredInstruction);
+ }
+
+ private CfInstruction desugarInstruction(CfInstruction instruction, ProgramMethod context) {
+ assert !instruction.isInitClass();
+ // TODO(b/179146128): Rewrite record invoke-dynamic here.
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ DexMethod newMethod =
+ rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
+ if (newMethod != cfInvoke.getMethod()) {
+ return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ assert !instruction.isInitClass();
+ // TODO(b/179146128): This is a temporary work-around to test desugaring of records
+ // without rewriting the record invoke-custom. This should be removed when the record support
+ // is complete.
+ if (instruction.isInvokeDynamic()
+ && context.getHolder().superType == factory.recordType
+ && (context.getName() == factory.toStringMethodName
+ || context.getName() == factory.hashCodeMethodName
+ || context.getName() == factory.equalsMethodName)) {
+ return true;
+ }
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ return needsDesugaring(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
+ }
+ return false;
+ }
+
+ private void requiresRecordClass(RecordDesugaringEventConsumer eventConsumer) {
+ DexProgramClass recordClass = synthesizeR8Record();
+ if (recordClass != null) {
+ eventConsumer.acceptRecordClass(recordClass);
+ }
+ }
+
+ @Override
+ public boolean needsDesugaring(DexProgramClass clazz) {
+ assert clazz.isRecord() || clazz.superType != factory.recordType;
+ return clazz.isRecord();
+ }
+
+ @Override
+ public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+ if (clazz.isRecord()) {
+ assert clazz.superType == factory.recordType;
+ requiresRecordClass(eventConsumer);
+ clazz.accessFlags.unsetRecord();
+ }
+ }
+
+ private boolean refersToRecord(DexField field) {
+ assert !refersToRecord(field.holder) : "The java.lang.Record class has no fields.";
+ return refersToRecord(field.type);
+ }
+
+ private boolean refersToRecord(DexMethod method) {
+ if (refersToRecord(method.holder)) {
+ return true;
+ }
+ return refersToRecord(method.proto);
+ }
+
+ private boolean refersToRecord(DexProto proto) {
+ if (refersToRecord(proto.returnType)) {
+ return true;
+ }
+ return refersToRecord(proto.parameters.values);
+ }
+
+ private boolean refersToRecord(DexType[] types) {
+ for (DexType type : types) {
+ if (refersToRecord(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean refersToRecord(DexType type) {
+ return type == factory.recordType;
+ }
+
+ private boolean needsDesugaring(DexMethod method, boolean isSuper) {
+ return rewriteMethod(method, isSuper) != method;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ private DexMethod rewriteMethod(DexMethod method, boolean isSuper) {
+ if (method.holder != factory.recordType || method.isInstanceInitializer(factory)) {
+ return method;
+ }
+ assert method == factory.recordMembers.equals
+ || method == factory.recordMembers.hashCode
+ || method == factory.recordMembers.toString;
+ if (isSuper) {
+ // TODO(b/179146128): Support rewriting invoke-super to a Record method.
+ throw new CompilationError("Rewrite invoke-super to abstract method error.");
+ }
+ if (method == factory.recordMembers.equals) {
+ return factory.objectMembers.equals;
+ }
+ if (method == factory.recordMembers.toString) {
+ return factory.objectMembers.toString;
+ }
+ assert method == factory.recordMembers.hashCode;
+ return factory.objectMembers.toString;
+ }
+
+ private DexProgramClass synthesizeR8Record() {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexClass recordClass =
+ appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType);
+ if (recordClass != null && recordClass.isProgramClass()) {
+ return null;
+ }
+ assert recordClass == null || recordClass.isLibraryClass();
+ DexEncodedMethod init = synthesizeRecordInitMethod();
+ // TODO(b/179146128): We may want to remove here the class from the library classes if present
+ // in cf to cf.
+ return appView
+ .getSyntheticItems()
+ .createFixedClassFromType(
+ SyntheticNaming.SyntheticKind.RECORD_TAG,
+ factory.recordType,
+ factory,
+ builder -> builder.setAbstract().setDirectMethods(Collections.singletonList(init)));
+ }
+
+ private DexEncodedMethod synthesizeRecordInitMethod() {
+ MethodAccessFlags methodAccessFlags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
+ DexEncodedMethod init =
+ new DexEncodedMethod(
+ factory.recordMembers.init,
+ methodAccessFlags,
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ null,
+ true);
+ init.setCode(
+ new CallObjectInitCfCodeProvider(appView, factory.r8RecordType).generateCfCode(), appView);
+ return init;
+ }
+}
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 da56b69..b7b0db0 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
@@ -2115,6 +2115,53 @@
ImmutableList.of());
}
+ public static CfCode IntegerMethods_parseIntSubsequenceWithRadix(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 3,
+ 4,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 2),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.intType,
+ options.itemFactory.intType),
+ options.itemFactory.createString("subSequence")),
+ true),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(options.itemFactory.stringType),
+ options.itemFactory.createString("toString")),
+ true),
+ new CfLoad(ValueType.INT, 3),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/Integer;"),
+ options.itemFactory.createProto(
+ options.itemFactory.intType,
+ options.itemFactory.stringType,
+ options.itemFactory.intType),
+ options.itemFactory.createString("parseInt")),
+ false),
+ new CfReturn(ValueType.INT),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
@@ -2643,6 +2690,53 @@
ImmutableList.of());
}
+ public static CfCode LongMethods_parseLongSubsequenceWithRadix(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 3,
+ 4,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 2),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.intType,
+ options.itemFactory.intType),
+ options.itemFactory.createString("subSequence")),
+ true),
+ new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(options.itemFactory.stringType),
+ options.itemFactory.createString("toString")),
+ true),
+ new CfLoad(ValueType.INT, 3),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/Long;"),
+ options.itemFactory.createProto(
+ options.itemFactory.longType,
+ options.itemFactory.stringType,
+ options.itemFactory.intType),
+ options.itemFactory.createString("parseLong")),
+ false),
+ new CfReturn(ValueType.LONG),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
@@ -2670,7 +2764,7 @@
ImmutableList.of());
}
- public static CfCode LongMethods_parseUnsignedLongWithRadix(
+ public static CfCode LongMethods_parseUnsignedLongSubsequenceWithRadix(
InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
@@ -2697,20 +2791,15 @@
return new CfCode(
method.holder,
5,
- 10,
+ 12,
ImmutableList.of(
label0,
- new CfLoad(ValueType.OBJECT, 0),
- new CfInvoke(
- 182,
- options.itemFactory.createMethod(
- options.itemFactory.stringType,
- options.itemFactory.createProto(options.itemFactory.intType),
- options.itemFactory.createString("length")),
- false),
- new CfStore(ValueType.INT, 2),
- label1,
new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 1),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfStore(ValueType.INT, 4),
+ label1,
+ new CfLoad(ValueType.INT, 4),
new CfIf(If.Type.NE, ValueType.INT, label3),
label2,
new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
@@ -2728,25 +2817,29 @@
label3,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2},
+ new int[] {0, 1, 2, 3, 4},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 3),
new CfConstNumber(2, ValueType.INT),
new CfIfCmp(If.Type.LT, ValueType.INT, label4),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 3),
new CfConstNumber(36, ValueType.INT),
new CfIfCmp(If.Type.LE, ValueType.INT, label5),
label4,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2},
+ new int[] {0, 1, 2, 3, 4},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType)
}),
@@ -2754,7 +2847,7 @@
new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
new CfStackInstruction(CfStackInstruction.Opcode.Dup),
new CfConstString(options.itemFactory.createString("illegal radix: ")),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 3),
new CfInvoke(
184,
options.itemFactory.createMethod(
@@ -2783,15 +2876,17 @@
label5,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2},
+ new int[] {0, 1, 2, 3, 4},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType)
}),
new ArrayDeque<>(Arrays.asList())),
new CfConstNumber(-1, ValueType.LONG),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 3),
new CfNumberConversion(NumericType.INT, NumericType.LONG),
new CfInvoke(
184,
@@ -2803,62 +2898,70 @@
options.itemFactory.longType),
options.itemFactory.createString("divideUnsigned")),
false),
- new CfStore(ValueType.LONG, 3),
+ new CfStore(ValueType.LONG, 5),
label6,
new CfLoad(ValueType.OBJECT, 0),
- new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.INT, 1),
new CfInvoke(
- 182,
+ 185,
options.itemFactory.createMethod(
- options.itemFactory.stringType,
+ options.itemFactory.charSequenceType,
options.itemFactory.createProto(
options.itemFactory.charType, options.itemFactory.intType),
options.itemFactory.createString("charAt")),
- false),
+ true),
new CfConstNumber(43, ValueType.INT),
new CfIfCmp(If.Type.NE, ValueType.INT, label7),
- new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 4),
new CfConstNumber(1, ValueType.INT),
new CfIfCmp(If.Type.LE, ValueType.INT, label7),
+ new CfLoad(ValueType.INT, 1),
new CfConstNumber(1, ValueType.INT),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
new CfGoto(label8),
label7,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3},
+ new int[] {0, 1, 2, 3, 4, 5},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.INT, 1),
label8,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3},
+ new int[] {0, 1, 2, 3, 4, 5},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType)
}),
new ArrayDeque<>(
Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
- new CfStore(ValueType.INT, 5),
+ new CfStore(ValueType.INT, 7),
label9,
new CfConstNumber(0, ValueType.LONG),
- new CfStore(ValueType.LONG, 6),
+ new CfStore(ValueType.LONG, 8),
label10,
- new CfLoad(ValueType.INT, 5),
- new CfStore(ValueType.INT, 8),
+ new CfLoad(ValueType.INT, 7),
+ new CfStore(ValueType.INT, 10),
label11,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 5, 6, 8},
+ new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType),
@@ -2867,21 +2970,21 @@
FrameType.initialized(options.itemFactory.intType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfLoad(ValueType.INT, 8),
+ new CfLoad(ValueType.INT, 10),
new CfLoad(ValueType.INT, 2),
new CfIfCmp(If.Type.GE, ValueType.INT, label20),
label12,
new CfLoad(ValueType.OBJECT, 0),
- new CfLoad(ValueType.INT, 8),
+ new CfLoad(ValueType.INT, 10),
new CfInvoke(
- 182,
+ 185,
options.itemFactory.createMethod(
- options.itemFactory.stringType,
+ options.itemFactory.charSequenceType,
options.itemFactory.createProto(
options.itemFactory.charType, options.itemFactory.intType),
options.itemFactory.createString("charAt")),
- false),
- new CfLoad(ValueType.INT, 1),
+ true),
+ new CfLoad(ValueType.INT, 3),
new CfInvoke(
184,
options.itemFactory.createMethod(
@@ -2892,9 +2995,9 @@
options.itemFactory.intType),
options.itemFactory.createString("digit")),
false),
- new CfStore(ValueType.INT, 9),
+ new CfStore(ValueType.INT, 11),
label13,
- new CfLoad(ValueType.INT, 9),
+ new CfLoad(ValueType.INT, 11),
new CfConstNumber(-1, ValueType.INT),
new CfIfCmp(If.Type.NE, ValueType.INT, label15),
label14,
@@ -2902,6 +3005,13 @@
new CfStackInstruction(CfStackInstruction.Opcode.Dup),
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(options.itemFactory.stringType),
+ options.itemFactory.createString("toString")),
+ true),
+ new CfInvoke(
183,
options.itemFactory.createMethod(
options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
@@ -2913,9 +3023,11 @@
label15,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+ new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType),
@@ -2925,21 +3037,21 @@
FrameType.initialized(options.itemFactory.intType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfLoad(ValueType.LONG, 6),
+ new CfLoad(ValueType.LONG, 8),
new CfConstNumber(0, ValueType.LONG),
new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
new CfIf(If.Type.LT, ValueType.INT, label17),
- new CfLoad(ValueType.LONG, 6),
- new CfLoad(ValueType.LONG, 3),
+ new CfLoad(ValueType.LONG, 8),
+ new CfLoad(ValueType.LONG, 5),
new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
new CfIf(If.Type.GT, ValueType.INT, label17),
- new CfLoad(ValueType.LONG, 6),
- new CfLoad(ValueType.LONG, 3),
+ new CfLoad(ValueType.LONG, 8),
+ new CfLoad(ValueType.LONG, 5),
new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
new CfIf(If.Type.NE, ValueType.INT, label18),
- new CfLoad(ValueType.INT, 9),
+ new CfLoad(ValueType.INT, 11),
new CfConstNumber(-1, ValueType.LONG),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.INT, 3),
new CfNumberConversion(NumericType.INT, NumericType.LONG),
label16,
new CfInvoke(
@@ -2957,9 +3069,11 @@
label17,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+ new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType),
@@ -2974,6 +3088,13 @@
new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")),
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
+ 185,
+ options.itemFactory.createMethod(
+ options.itemFactory.charSequenceType,
+ options.itemFactory.createProto(options.itemFactory.stringType),
+ options.itemFactory.createString("toString")),
+ true),
+ new CfInvoke(
182,
options.itemFactory.createMethod(
options.itemFactory.stringType,
@@ -2993,9 +3114,11 @@
label18,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+ new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType),
@@ -3005,23 +3128,25 @@
FrameType.initialized(options.itemFactory.intType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfLoad(ValueType.LONG, 6),
- new CfLoad(ValueType.INT, 1),
+ new CfLoad(ValueType.LONG, 8),
+ new CfLoad(ValueType.INT, 3),
new CfNumberConversion(NumericType.INT, NumericType.LONG),
new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.LONG),
- new CfLoad(ValueType.INT, 9),
+ new CfLoad(ValueType.INT, 11),
new CfNumberConversion(NumericType.INT, NumericType.LONG),
new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
- new CfStore(ValueType.LONG, 6),
+ new CfStore(ValueType.LONG, 8),
label19,
- new CfIinc(8, 1),
+ new CfIinc(10, 1),
new CfGoto(label11),
label20,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 5, 6},
+ new int[] {0, 1, 2, 3, 4, 5, 7, 8},
new FrameType[] {
- FrameType.initialized(options.itemFactory.stringType),
+ FrameType.initialized(options.itemFactory.charSequenceType),
+ FrameType.initialized(options.itemFactory.intType),
+ FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.intType),
FrameType.initialized(options.itemFactory.longType),
@@ -3029,13 +3154,52 @@
FrameType.initialized(options.itemFactory.longType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfLoad(ValueType.LONG, 6),
+ new CfLoad(ValueType.LONG, 8),
new CfReturn(ValueType.LONG),
label21),
ImmutableList.of(),
ImmutableList.of());
}
+ public static CfCode LongMethods_parseUnsignedLongWithRadix(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 4,
+ 2,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.stringType,
+ options.itemFactory.createProto(options.itemFactory.intType),
+ options.itemFactory.createString("length")),
+ false),
+ new CfLoad(ValueType.INT, 1),
+ new CfInvoke(
+ 184,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/Long;"),
+ options.itemFactory.createProto(
+ options.itemFactory.longType,
+ options.itemFactory.charSequenceType,
+ options.itemFactory.intType,
+ options.itemFactory.intType,
+ options.itemFactory.intType),
+ options.itemFactory.createString("parseUnsignedLong")),
+ false),
+ new CfReturn(ValueType.LONG),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode LongMethods_remainderUnsigned(InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 51949c9..8b928aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import java.util.Collection;
import java.util.Collections;
import org.objectweb.asm.Opcodes;
@@ -34,7 +35,8 @@
public static MethodInvokeRewriter rewriteAsIdentity() {
return new FullMethodInvokeRewriter() {
@Override
- public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+ public Collection<CfInstruction> rewrite(
+ CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
// The invoke consumes the stack value and pushes another assumed to be the same.
return Collections.emptyList();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index f8ab4a7..20998db 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import org.objectweb.asm.Opcodes;
@@ -32,8 +33,10 @@
return new FullMethodInvokeRewriter() {
@Override
- public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+ public Collection<CfInstruction> rewrite(
+ CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
// requireNonNull returns the operand, so dup top-of-stack, do getClass and pop the class.
+ localStackAllocator.allocateLocalStack(1);
return ImmutableList.of(
new CfStackInstruction(Opcode.Dup),
new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 2518219..44a37b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -45,8 +45,8 @@
DexClass hostClass = nest.getHostClass();
for (DexClass memberClass : nest.getMembers()) {
if (hostClass.isProgramClass() || memberClass.isProgramClass()) {
- reportDependencyEdge(hostClass, memberClass, appView.options());
- reportDependencyEdge(memberClass, hostClass, appView.options());
+ reportDependencyEdge(hostClass, memberClass, appView.appInfo());
+ reportDependencyEdge(memberClass, hostClass, appView.appInfo());
}
}
},
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d01470e..d3dc297 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -27,11 +27,14 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue;
import com.android.tools.r8.ir.analysis.value.SingleConstClassValue;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -54,6 +57,7 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.InstanceFieldInstruction;
+import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstanceOf;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
@@ -1092,6 +1096,10 @@
BasicBlock block,
InstructionListIterator iterator,
IntSwitch theSwitch) {
+ if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) {
+ return;
+ }
+
if (theSwitch.numberOfKeys() == 1) {
rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
return;
@@ -1186,6 +1194,27 @@
}
}
+ // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId
+ // field values (from horizontal class merging. See bug for more details.
+ private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) {
+ Value switchValue = theSwitch.value().getAliasedValue();
+ if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
+ return false;
+ }
+ AppInfoWithLiveness appInfo = appView.appInfoWithLiveness();
+ if (appInfo == null) {
+ return false;
+ }
+ InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet();
+ SuccessfulFieldResolutionResult resolutionResult =
+ appInfo.resolveField(instanceGet.getField()).asSuccessfulResolution();
+ if (resolutionResult == null) {
+ return false;
+ }
+ DexEncodedField resolvedField = resolutionResult.getResolvedField();
+ return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField);
+ }
+
private SwitchCaseEliminator removeUnnecessarySwitchCases(
IRCode code,
Switch theSwitch,
@@ -1197,6 +1226,7 @@
new BasicBlockBehavioralSubsumption(appView, code);
// Compute the set of switch cases that can be removed.
+ AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context());
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
BasicBlock targetBlock = theSwitch.targetBlock(i);
@@ -1210,7 +1240,7 @@
// This switch case can be removed if the behavior of the target block is equivalent to the
// behavior of the default block, or if the switch case is unreachable.
- if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i)
+ if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)
|| behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
if (eliminator == null) {
eliminator = new SwitchCaseEliminator(theSwitch, iterator);
@@ -1218,6 +1248,16 @@
eliminator.markSwitchCaseForRemoval(i);
}
}
+
+ if (eliminator == null || eliminator.isFallthroughLive()) {
+ if (switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
+ if (eliminator == null) {
+ eliminator = new SwitchCaseEliminator(theSwitch, iterator);
+ }
+ eliminator.markSwitchFallthroughAsNeverHit();
+ }
+ }
+
if (eliminator != null) {
eliminator.optimize();
}
@@ -2619,6 +2659,16 @@
}
}
+ if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+ AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+ if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+ && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) {
+ // Value doesn't contain zero at all.
+ simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+ return true;
+ }
+ }
+
if (lhs.hasValueRange()) {
LongInterval interval = lhs.getValueRange();
if (!interval.containsValue(0)) {
@@ -2691,6 +2741,23 @@
return true;
}
+ if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+ AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+ AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context());
+ if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+ && rhsAbstractValue.isConstantOrNonConstantNumberValue()) {
+ ConstantOrNonConstantNumberValue lhsNumberValue =
+ lhsAbstractValue.asConstantOrNonConstantNumberValue();
+ ConstantOrNonConstantNumberValue rhsNumberValue =
+ rhsAbstractValue.asConstantOrNonConstantNumberValue();
+ if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) {
+ // No overlap.
+ simplifyIfWithKnownCondition(code, block, theIf, 1);
+ return true;
+ }
+ }
+ }
+
if (lhs.hasValueRange() && rhs.hasValueRange()) {
// Zero test with a value range, or comparison between between two values,
// each with a value ranges.
@@ -3372,6 +3439,7 @@
Instruction arrayDefinition = array.getDefinition();
assert arrayDefinition != null;
+ Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
if (arrayDefinition.isNewArrayEmpty()) {
Value size = arrayDefinition.asNewArrayEmpty().size();
arrayLength.outValue().replaceUsers(size);
@@ -3380,7 +3448,11 @@
int size = (int) arrayDefinition.asNewArrayFilledData().size;
ConstNumber constSize = code.createIntConstant(size);
iterator.replaceCurrentInstruction(constSize);
+ } else {
+ continue;
}
+
+ phiUsers.forEach(Phi::removeTrivialPhi);
// TODO(139489070): static-get of constant array
}
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 33e414f..8e66601 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -201,13 +201,13 @@
// If we do this it can increase the size of the main dex dependent classes.
if (reason != Reason.FORCE
&& inliner.mainDexInfo.disallowInliningIntoContext(
- appView.appInfo(), method, singleTarget)) {
+ appView.appInfo(), method, singleTarget, appView.getSyntheticItems())) {
whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
return false;
}
assert reason != Reason.FORCE
|| !inliner.mainDexInfo.disallowInliningIntoContext(
- appView.appInfo(), method, singleTarget);
+ appView.appInfo(), method, singleTarget, appView.getSyntheticItems());
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index f5f4d70..fa3886e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -391,6 +391,9 @@
}
private boolean isRebindingNewClassIntoMainDex(ProgramMethod context, DexMethod reboundMethod) {
- return !appView.appInfo().getMainDexInfo().canRebindReference(context, reboundMethod);
+ return !appView
+ .appInfo()
+ .getMainDexInfo()
+ .canRebindReference(context, reboundMethod, appView.getSyntheticItems());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 289b2ba..802fdb3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -92,7 +92,8 @@
&& !appView
.appInfo()
.getMainDexInfo()
- .canRebindReference(code.context(), baseClass.getType())) {
+ .canRebindReference(
+ code.context(), baseClass.getType(), appView.getSyntheticItems())) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 6c9bfbb..11e0b51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.StringSwitch;
import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.utils.BooleanUtils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -28,6 +29,7 @@
private int alwaysHitCase = -1;
private BasicBlock alwaysHitTarget;
+ private boolean liveFallthrough = true;
private boolean mayHaveIntroducedUnreachableBlocks = false;
private IntSet switchCasesToBeRemoved;
@@ -39,13 +41,13 @@
}
private boolean allSwitchCasesMarkedForRemoval() {
- assert switchCasesToBeRemoved != null;
- return switchCasesToBeRemoved.size() == theSwitch.numberOfKeys();
+ return switchCasesToBeRemoved != null
+ && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys();
}
private boolean canBeOptimized() {
assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty();
- return switchCasesToBeRemoved != null || hasAlwaysHitCase();
+ return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive();
}
boolean mayHaveIntroducedUnreachableBlocks() {
@@ -56,11 +58,19 @@
if (hasAlwaysHitCase()) {
return index == alwaysHitCase;
}
- return !switchCasesToBeRemoved.contains(index);
+ if (switchCasesToBeRemoved != null) {
+ return !switchCasesToBeRemoved.contains(index);
+ }
+ assert !isFallthroughLive();
+ return true;
+ }
+
+ public boolean isFallthroughDead() {
+ return !isFallthroughLive();
}
public boolean isFallthroughLive() {
- return !hasAlwaysHitCase();
+ return liveFallthrough;
}
public boolean hasAlwaysHitCase() {
@@ -71,6 +81,7 @@
assert alwaysHitCase < 0;
alwaysHitCase = i;
alwaysHitTarget = theSwitch.targetBlock(i);
+ liveFallthrough = false;
}
void markSwitchCaseForRemoval(int i) {
@@ -80,6 +91,11 @@
switchCasesToBeRemoved.add(i);
}
+ void markSwitchFallthroughAsNeverHit() {
+ assert !hasAlwaysHitCase();
+ liveFallthrough = false;
+ }
+
boolean optimize() {
if (canBeOptimized()) {
int originalNumberOfSuccessors = block.getSuccessors().size();
@@ -147,53 +163,70 @@
targetBlockIndexOffset[i] += targetBlockIndexOffset[i - 1];
}
- int newNumberOfKeys = theSwitch.numberOfKeys() - switchCasesToBeRemoved.size();
+ int newFallthrough = theSwitch.numberOfKeys();
+ if (isFallthroughDead()) {
+ for (int i = theSwitch.numberOfKeys() - 1; i >= 0; i--) {
+ if (isSwitchCaseLive(i)) {
+ newFallthrough = i;
+ break;
+ }
+ }
+ }
+
+ // Compute the number of removed switch cases. If the fallthrough is dead, we promote one of the
+ // live switch cases to being the fallthrough instead.
+ int numberOfRemovedSwitchCases =
+ switchCasesToBeRemoved != null ? switchCasesToBeRemoved.size() : 0;
+ numberOfRemovedSwitchCases += BooleanUtils.intValue(isFallthroughDead());
+
+ int newNumberOfKeys = theSwitch.numberOfKeys() - numberOfRemovedSwitchCases;
int[] newTargetBlockIndices = new int[newNumberOfKeys];
- for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
- if (!switchCasesToBeRemoved.contains(i)) {
+ for (int i = 0, j = 0; i < newFallthrough; i++) {
+ if (isSwitchCaseLive(i)) {
newTargetBlockIndices[j] =
theSwitch.getTargetBlockIndex(i)
- targetBlockIndexOffset[theSwitch.getTargetBlockIndex(i)];
assert newTargetBlockIndices[j] < block.getSuccessors().size();
- assert newTargetBlockIndices[j] != theSwitch.getFallthroughBlockIndex();
j++;
}
}
+ int fallthroughBlockIndex;
+ if (isFallthroughLive()) {
+ fallthroughBlockIndex =
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()];
+ } else {
+ fallthroughBlockIndex =
+ theSwitch.getTargetBlockIndex(newFallthrough)
+ - targetBlockIndexOffset[theSwitch.getTargetBlockIndex(newFallthrough)];
+ }
+
Switch replacement;
if (theSwitch.isIntSwitch()) {
IntSwitch intSwitch = theSwitch.asIntSwitch();
int[] newKeys = new int[newNumberOfKeys];
- for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
- if (!switchCasesToBeRemoved.contains(i)) {
+ for (int i = 0, j = 0; i < newFallthrough; i++) {
+ if (isSwitchCaseLive(i)) {
newKeys[j] = intSwitch.getKey(i);
j++;
}
}
replacement =
- new IntSwitch(
- theSwitch.value(),
- newKeys,
- newTargetBlockIndices,
- theSwitch.getFallthroughBlockIndex()
- - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ new IntSwitch(theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex);
} else {
assert theSwitch.isStringSwitch();
StringSwitch stringSwitch = theSwitch.asStringSwitch();
DexString[] newKeys = new DexString[newNumberOfKeys];
- for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
- if (!switchCasesToBeRemoved.contains(i)) {
+ for (int i = 0, j = 0; i < newFallthrough; i++) {
+ if (isSwitchCaseLive(i)) {
newKeys[j] = stringSwitch.getKey(i);
j++;
}
}
replacement =
new StringSwitch(
- theSwitch.value(),
- newKeys,
- newTargetBlockIndices,
- theSwitch.getFallthroughBlockIndex()
- - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex);
}
iterator.replaceCurrentInstruction(replacement);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 78dc973..4857999 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -183,14 +183,11 @@
continue;
}
- assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
-
// Is inlining allowed.
InliningIRProvider inliningIRProvider =
new InliningIRProvider(appView, method, code, methodProcessor);
ClassInlinerCostAnalysis costAnalysis =
- new ClassInlinerCostAnalysis(
- appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
+ new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers());
if (costAnalysis.willExceedInstructionBudget(
code,
processor.getEligibleClass(),
@@ -213,8 +210,7 @@
// Inline the class instance.
Set<Value> affectedValues = Sets.newIdentityHashSet();
try {
- anyInlinedMethods |=
- processor.processInlining(code, affectedValues, defaultOracle, inliningIRProvider);
+ anyInlinedMethods |= processor.processInlining(code, affectedValues, inliningIRProvider);
} catch (IllegalClassInlinerStateException e) {
// We introduced a user that we cannot handle in the class inliner as a result of force
// inlining. Abort gracefully from class inlining without removing the instance.
@@ -227,8 +223,6 @@
anyInlinedMethods = true;
}
- assert inliningIRProvider.verifyIRCacheIsEmpty();
-
// Restore normality.
code.removeAllDeadAndTrivialPhis(affectedValues);
if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 3feac87..33fa6fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -28,17 +28,17 @@
private final AppView<AppInfoWithLiveness> appView;
private final InliningIRProvider inliningIRProvider;
- private final Set<Value> definiteReceiverAliases;
+ private final ClassInlinerReceiverSet receivers;
private int estimatedCost = 0;
ClassInlinerCostAnalysis(
AppView<AppInfoWithLiveness> appView,
InliningIRProvider inliningIRProvider,
- Set<Value> definiteReceiverAliases) {
+ ClassInlinerReceiverSet receivers) {
this.appView = appView;
this.inliningIRProvider = inliningIRProvider;
- this.definiteReceiverAliases = definiteReceiverAliases;
+ this.receivers = receivers;
}
boolean willExceedInstructionBudget(
@@ -145,10 +145,10 @@
Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet();
for (int i = 0; i < invoke.inValues().size(); i++) {
Value inValue = invoke.inValues().get(i);
- if (definiteReceiverAliases.contains(inValue)) {
+ if (receivers.isReceiverAlias(inValue)) {
receiverAliasesInInlinee.add(arguments.get(i));
} else {
- assert !definiteReceiverAliases.contains(inValue.getAliasedValue());
+ assert !receivers.isReceiverAlias(inValue.getAliasedValue());
}
}
return receiverAliasesInInlinee;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
deleted file mode 100644
index 78e756d..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2019, 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.classinliner;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Pair;
-import java.util.List;
-
-public class ClassInlinerEligibilityInfo {
-
- final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
-
- /**
- * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link
- * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link
- * OptionalBool#UNKNOWN} if the method may return the receiver.
- */
- final OptionalBool returnsReceiver;
-
- public final boolean hasMonitorOnReceiver;
- public final boolean modifiesInstanceFields;
-
- public ClassInlinerEligibilityInfo(
- List<Pair<Invoke.Type, DexMethod>> callsReceiver,
- OptionalBool returnsReceiver,
- boolean hasMonitorOnReceiver,
- boolean modifiesInstanceFields) {
- this.callsReceiver = callsReceiver;
- this.returnsReceiver = returnsReceiver;
- this.hasMonitorOnReceiver = hasMonitorOnReceiver;
- this.modifiesInstanceFields = modifiesInstanceFields;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
deleted file mode 100644
index e0f991d..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2019, 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.classinliner;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-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.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.BooleanLatticeElement;
-import com.android.tools.r8.utils.OptionalBool;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This analysis determines whether a method returns the receiver. The analysis is specific to the
- * class inliner, and the result is therefore not sound in general.
- *
- * <p>The analysis makes the following assumptions.
- *
- * <ul>
- * <li>None of the given method's arguments is an alias of the receiver (except for the receiver
- * itself).
- * <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
- * instructions do not return an alias of the receiver, and (ii) invoke instructions can only
- * return an alias of the receiver if the receiver is given as an argument.
- * </ul>
- */
-public class ClassInlinerReceiverAnalysis {
-
- private final AppView<?> appView;
- private final DexEncodedMethod method;
- private final IRCode code;
- private final Value receiver;
-
- private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
-
- public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
- this.appView = appView;
- this.method = method;
- this.code = code;
- this.receiver = code.getThis();
- assert !receiver.hasAliasedValue();
- assert receiver.getType().isClassType();
- }
-
- public OptionalBool computeReturnsReceiver() {
- if (method.method.proto.returnType.isVoidType()) {
- return OptionalBool.FALSE;
- }
-
- List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
- if (normalExitBlocks.isEmpty()) {
- return OptionalBool.FALSE;
- }
-
- BooleanLatticeElement result = OptionalBool.BOTTOM;
- for (BasicBlock block : normalExitBlocks) {
- Value returnValue = block.exit().asReturn().returnValue();
- result = result.join(getOrComputeIsReceiverAlias(returnValue));
-
- // Stop as soon as we reach unknown.
- if (result.isUnknown()) {
- return OptionalBool.UNKNOWN;
- }
- }
- assert !result.isBottom();
- return result.asOptionalBool();
- }
-
- private OptionalBool getOrComputeIsReceiverAlias(Value value) {
- Value root = value.getAliasedValue();
- return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
- }
-
- private OptionalBool computeIsReceiverAlias(Value value) {
- assert !value.hasAliasedValue();
-
- if (value == receiver) {
- // Guaranteed to return the receiver.
- return OptionalBool.TRUE;
- }
-
- ClassTypeElement valueType = value.getType().asClassType();
- if (valueType == null) {
- return OptionalBool.FALSE;
- }
-
- ClassTypeElement receiverType = receiver.getType().asClassType();
- if (!valueType.isRelatedTo(receiverType, appView)) {
- // Guaranteed not to return the receiver.
- return OptionalBool.FALSE;
- }
-
- if (value.isPhi()) {
- // Not sure what is returned.
- return OptionalBool.UNKNOWN;
- }
-
- Instruction definition = value.definition;
- if (definition.isArrayGet() || definition.isFieldGet()) {
- // Guaranteed not to return the receiver, since the class inliner does not allow the
- // receiver to flow into any arrays or fields.
- return OptionalBool.FALSE;
- }
-
- if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
- // Guaranteed not to return the receiver.
- return OptionalBool.FALSE;
- }
-
- if (definition.isInvokeMethod()) {
- // Since the class inliner does not allow the receiver to flow into the heap, the only way for
- // the invoked method to return the receiver is if one of the given arguments is an alias of
- // the receiver.
- InvokeMethod invoke = definition.asInvokeMethod();
- for (Value argument : invoke.arguments()) {
- if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
- return OptionalBool.UNKNOWN;
- }
- }
-
- return OptionalBool.FALSE;
- }
-
- // Not sure what is returned.
- return OptionalBool.UNKNOWN;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
index 5a8c59d..1d73fdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.optimize.classinliner;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.AliasKind;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -19,8 +18,7 @@
private final Value root;
- private final Set<Value> definiteReceiverAliases;
- private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet();
+ private final Set<Value> receiverAliases;
// Set of values that are not allowed to become an alias of the receiver.
private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet();
@@ -30,32 +28,18 @@
new IdentityHashMap<>();
ClassInlinerReceiverSet(Value root) {
- this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root);
+ this.receiverAliases = SetUtils.newIdentityHashSet(root);
this.root = root;
}
- Set<Value> getDefiniteReceiverAliases() {
- return definiteReceiverAliases;
- }
-
- Set<Value> getMaybeReceiverAliases() {
- return maybeReceiverAliases;
- }
-
- boolean addReceiverAlias(Value alias, AliasKind kind) {
+ boolean addReceiverAlias(Value alias) {
if (isIllegalReceiverAlias(alias)) {
return false; // Not allowed.
}
// All checks passed.
deferredAliasValidityChecks.remove(alias);
boolean changed;
- if (kind == AliasKind.DEFINITE) {
- assert !maybeReceiverAliases.contains(alias);
- changed = definiteReceiverAliases.add(alias);
- } else {
- assert !definiteReceiverAliases.contains(alias);
- changed = maybeReceiverAliases.add(alias);
- }
+ changed = receiverAliases.add(alias);
// Verify that the state changed. Otherwise, we are analyzing the same instruction more than
// once.
assert changed : alias.toString() + " already added as an alias";
@@ -87,15 +71,11 @@
}
boolean isReceiverAlias(Value value) {
- return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value);
+ return isDefiniteReceiverAlias(value);
}
boolean isDefiniteReceiverAlias(Value value) {
- return definiteReceiverAliases.contains(value);
- }
-
- private boolean isMaybeReceiverAlias(Value value) {
- return maybeReceiverAliases.contains(value);
+ return receiverAliases.contains(value);
}
private boolean isIllegalReceiverAlias(Value value) {
@@ -115,13 +95,7 @@
void reset() {
deferredAliasValidityChecks.clear();
- definiteReceiverAliases.clear();
- definiteReceiverAliases.add(root);
- maybeReceiverAliases.clear();
- }
-
- boolean verifyReceiverSetsAreDisjoint() {
- assert Sets.intersection(getMaybeReceiverAliases(), getDefiniteReceiverAliases()).isEmpty();
- return true;
+ receiverAliases.clear();
+ receiverAliases.add(root);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 32e25f1..854f6e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.ir.optimize.classinliner;
-import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,23 +23,23 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InstructionOrPhi;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
@@ -48,29 +48,25 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Comparator;
@@ -80,18 +76,10 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.function.Supplier;
final class InlineCandidateProcessor {
- enum AliasKind {
- DEFINITE,
- MAYBE
- }
-
- private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
- ImmutableSet.of(If.Type.EQ, If.Type.NE);
private static final AliasedValueConfiguration aliasesThroughAssumeAndCheckCasts =
AssumeAndCheckCastAliasedValueConfiguration.getInstance();
@@ -105,15 +93,11 @@
private Value eligibleInstance;
private DexProgramClass eligibleClass;
+ private ObjectState objectState;
- private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
- new IdentityHashMap<>();
+ private final Map<InvokeMethod, InliningInfo> directMethodCalls = new IdentityHashMap<>();
private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create();
- private final Map<InvokeMethod, InliningInfo> extraMethodCalls
- = new IdentityHashMap<>();
- private final List<Pair<InvokeMethod, Integer>> unusedArguments
- = new ArrayList<>();
private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>();
private final List<ProgramMethod> indirectInlinees = new ArrayList<>();
@@ -203,6 +187,11 @@
if (eligibleClass == null) {
return EligibilityStatus.NOT_ELIGIBLE;
}
+ AbstractValue abstractValue = optimizationInfo.getAbstractValue();
+ objectState =
+ abstractValue.isSingleFieldValue()
+ ? abstractValue.asSingleFieldValue().getState()
+ : ObjectState.empty();
return EligibilityStatus.ELIGIBLE;
}
@@ -255,7 +244,7 @@
if (alias.hasPhiUsers()) {
return alias.firstPhiUser(); // Not eligible.
}
- if (!receivers.addReceiverAlias(alias, AliasKind.DEFINITE)) {
+ if (!receivers.addReceiverAlias(alias)) {
return user; // Not eligible.
}
indirectUsers.addAll(alias.uniqueUsers());
@@ -263,10 +252,6 @@
}
if (user.isInstanceGet()) {
- if (root.isStaticGet()) {
- // We don't have a replacement for this field read.
- return user; // Not eligible.
- }
DexEncodedField field =
appView
.appInfo()
@@ -275,6 +260,11 @@
if (field == null || field.isStatic()) {
return user; // Not eligible.
}
+ if (root.isStaticGet()
+ && !objectState.hasMaterializableFieldValueThatMatches(
+ appView, field, method, AbstractValue::isSingleConstValue)) {
+ return user; // Not eligible.
+ }
continue;
}
@@ -298,11 +288,11 @@
}
if (user.isInvokeMethod()) {
- InvokeMethod invokeMethod = user.asInvokeMethod();
+ InvokeMethod invoke = user.asInvokeMethod();
SingleResolutionResult resolutionResult =
appView
.appInfo()
- .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
+ .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
.asSingleResolution();
if (resolutionResult == null
|| resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
@@ -310,13 +300,13 @@
}
// TODO(b/156853206): Avoid duplicating resolution.
- DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method);
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method);
if (singleTarget == null) {
return user; // Not eligible.
}
if (singleTarget.isLibraryMethod()
- && isEligibleLibraryMethodCall(invokeMethod, singleTarget.asLibraryMethod())) {
+ && isEligibleLibraryMethodCall(invoke, singleTarget.asLibraryMethod())) {
continue;
}
@@ -331,44 +321,25 @@
}
// Eligible constructor call (for new instance roots only).
- if (user.isInvokeDirect()) {
- InvokeDirect invoke = user.asInvokeDirect();
- if (dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
- boolean isCorrespondingConstructorCall =
- root.isNewInstance()
- && !invoke.inValues().isEmpty()
- && root.outValue() == invoke.getReceiver();
- if (isCorrespondingConstructorCall) {
- InliningInfo inliningInfo = isEligibleConstructorCall(invoke, singleProgramTarget);
- if (inliningInfo != null) {
- methodCallsOnInstance.put(invoke, inliningInfo);
- continue;
- }
+ if (user.isInvokeConstructor(dexItemFactory)) {
+ InvokeDirect invokeDirect = user.asInvokeDirect();
+ boolean isCorrespondingConstructorCall =
+ root.isNewInstance() && root.outValue() == invokeDirect.getReceiver();
+ if (isCorrespondingConstructorCall) {
+ InliningInfo inliningInfo =
+ isEligibleConstructorCall(invokeDirect, singleProgramTarget);
+ if (inliningInfo != null) {
+ directMethodCalls.put(invoke, inliningInfo);
+ continue;
}
- assert !isExtraMethodCall(invoke);
- return user; // Not eligible.
}
+ return user; // Not eligible.
}
- // Eligible virtual method call on the instance as a receiver.
- if (user.isInvokeVirtual() || user.isInvokeInterface()) {
- InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
- InliningInfo inliningInfo =
- isEligibleDirectVirtualMethodCall(
- invoke, resolutionResult, singleProgramTarget, indirectUsers, defaultOracle);
- if (inliningInfo != null) {
- methodCallsOnInstance.put(invoke, inliningInfo);
- continue;
- }
- }
-
- // Eligible usage as an invocation argument.
- if (isExtraMethodCall(invokeMethod)) {
- assert !invokeMethod.isInvokeSuper();
- assert !invokeMethod.isInvokePolymorphic();
- if (isExtraMethodCallEligible(invokeMethod, singleProgramTarget, defaultOracle)) {
- continue;
- }
+ // Eligible non-constructor method call.
+ if (isEligibleDirectMethodCall(
+ invoke, resolutionResult, singleProgramTarget, defaultOracle, indirectUsers)) {
+ continue;
}
return user; // Not eligible.
@@ -395,7 +366,6 @@
// Process inlining, includes the following steps:
//
// * remove linked assume instructions if any so that users of the eligible field are up-to-date.
- // * replace unused instance usages as arguments which are never used
// * inline extra methods if any, collect new direct method calls
// * inline direct methods if any
// * remove superclass initializer call and field reads
@@ -406,30 +376,12 @@
boolean processInlining(
IRCode code,
Set<Value> affectedValues,
- Supplier<InliningOracle> defaultOracle,
InliningIRProvider inliningIRProvider)
throws IllegalClassInlinerStateException {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
- replaceUsagesAsUnusedArgument(code);
- boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
- if (anyInlinedMethods) {
- // Reset the collections.
- clear();
-
- // Repeat user analysis
- InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
- if (ineligibleUser != null) {
- throw new IllegalClassInlinerStateException();
- }
- assert extraMethodCalls.isEmpty()
- : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
- assert unusedArguments.isEmpty()
- : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
- }
-
- anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
+ boolean anyInlinedMethods = forceInlineDirectMethodInvocations(code, inliningIRProvider);
anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
rebindIndirectEligibleInstanceUsersFromPhis();
@@ -440,60 +392,21 @@
return anyInlinedMethods;
}
- private void clear() {
- methodCallsOnInstance.clear();
- indirectMethodCallsOnInstance.clear();
- extraMethodCalls.clear();
- unusedArguments.clear();
- receivers.reset();
- }
-
- private void replaceUsagesAsUnusedArgument(IRCode code) {
- for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
- InvokeMethod invoke = unusedArgument.getFirst();
- BasicBlock block = invoke.getBlock();
-
- ConstNumber nullValue = code.createConstNull();
- nullValue.setPosition(invoke.getPosition());
- block.listIterator(code, invoke).add(nullValue);
- assert nullValue.getBlock() == block;
-
- int argIndex = unusedArgument.getSecond();
- invoke.replaceValue(argIndex, nullValue.outValue());
- }
- unusedArguments.clear();
- }
-
- private boolean forceInlineExtraMethodInvocations(
- IRCode code, InliningIRProvider inliningIRProvider) {
- if (extraMethodCalls.isEmpty()) {
- return false;
- }
- // Inline extra methods.
- inliner.performForcedInlining(
- method, code, extraMethodCalls, inliningIRProvider, Timing.empty());
- return true;
- }
-
private boolean forceInlineDirectMethodInvocations(
IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
- if (methodCallsOnInstance.isEmpty()) {
+ if (directMethodCalls.isEmpty()) {
return false;
}
- assert methodCallsOnInstance.keySet().stream()
- .map(InvokeMethodWithReceiver::getReceiver)
- .allMatch(receivers::isReceiverAlias);
-
inliner.performForcedInlining(
- method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+ method, code, directMethodCalls, inliningIRProvider, Timing.empty());
// In case we are class inlining an object allocation that does not inherit directly from
// java.lang.Object, we need keep force inlining the constructor until we reach
// java.lang.Object.<init>().
if (root.isNewInstance()) {
do {
- methodCallsOnInstance.clear();
+ directMethodCalls.clear();
for (Instruction instruction : eligibleInstance.uniqueUsers()) {
if (instruction.isInvokeDirect()) {
InvokeDirect invoke = instruction.asInvokeDirect();
@@ -512,7 +425,7 @@
}
DexProgramClass holder =
- asProgramClassOrNull(appView.definitionForHolder(invokedMethod));
+ asProgramClassOrNull(appView.definitionForHolder(invokedMethod, method));
if (holder == null) {
throw new IllegalClassInlinerStateException();
}
@@ -523,21 +436,21 @@
.getDefinition()
.isInliningCandidate(
method,
- Reason.SIMPLE,
+ Reason.ALWAYS,
appView.appInfo(),
NopWhyAreYouNotInliningReporter.getInstance())) {
throw new IllegalClassInlinerStateException();
}
- methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
+ directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
break;
}
}
- if (!methodCallsOnInstance.isEmpty()) {
+ if (!directMethodCalls.isEmpty()) {
inliner.performForcedInlining(
- method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+ method, code, directMethodCalls, inliningIRProvider, Timing.empty());
}
- } while (!methodCallsOnInstance.isEmpty());
+ } while (!directMethodCalls.isEmpty());
}
return true;
@@ -746,11 +659,24 @@
// Replace field reads with appropriate values, insert phis when needed.
private void removeFieldReads(IRCode code) {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ if (root.isNewInstance()) {
+ removeFieldReadsFromNewInstance(code, affectedValues);
+ } else {
+ assert root.isStaticGet();
+ removeFieldReadsFromStaticGet(code, affectedValues);
+ }
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+ }
+
+ private void removeFieldReadsFromNewInstance(IRCode code, Set<Value> affectedValues) {
TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (user.isInstanceGet()) {
- if (user.outValue().hasAnyUsers()) {
+ if (user.hasUsedOutValue()) {
uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
} else {
removeInstruction(user);
@@ -761,6 +687,7 @@
if (user.isInstancePut()) {
// Skip in this iteration since these instructions are needed to properly calculate what
// value should field reads be replaced with.
+ assert root.isNewInstance();
continue;
}
@@ -774,12 +701,15 @@
Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
// Replace a field read with appropriate value.
- replaceFieldRead(code, user, fieldHelpers);
+ removeFieldReadFromNewInstance(code, user, affectedValues, fieldHelpers);
}
}
- private void replaceFieldRead(
- IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+ private void removeFieldReadFromNewInstance(
+ IRCode code,
+ InstanceGet fieldRead,
+ Set<Value> affectedValues,
+ Map<DexField, FieldValueHelper> fieldHelpers) {
Value value = fieldRead.outValue();
if (value != null) {
FieldValueHelper helper =
@@ -794,12 +724,89 @@
// `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
// type of read field, but it could be more precise than that due to (multiple) inlining.
// In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
- new TypeAnalysis(appView).narrowing(
- Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues()));
+ affectedValues.add(newValue);
+ affectedValues.addAll(newValue.affectedValues());
}
removeInstruction(fieldRead);
}
+ private void removeFieldReadsFromStaticGet(IRCode code, Set<Value> affectedValues) {
+ Set<BasicBlock> seen = Sets.newIdentityHashSet();
+ Set<Instruction> users = eligibleInstance.uniqueUsers();
+ for (Instruction user : users) {
+ BasicBlock block = user.getBlock();
+ if (!seen.add(block)) {
+ continue;
+ }
+
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!users.contains(instruction)) {
+ continue;
+ }
+
+ if (user.isInstanceGet()) {
+ if (user.hasUsedOutValue()) {
+ replaceFieldReadFromStaticGet(
+ code, instructionIterator, user.asInstanceGet(), affectedValues);
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ continue;
+ }
+
+ if (user.isInstancePut()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+
+ throw new Unreachable(
+ "Unexpected usage left in method `"
+ + method.toSourceString()
+ + "` after inlining: "
+ + user);
+ }
+ }
+ }
+
+ private void replaceFieldReadFromStaticGet(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InstanceGet fieldRead,
+ Set<Value> affectedValues) {
+ DexField fieldReference = fieldRead.getField();
+ DexClass holder = appView.definitionFor(fieldReference.getHolderType(), method);
+ DexEncodedField field = fieldReference.lookupOnClass(holder);
+ if (field == null) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ AbstractValue abstractValue = objectState.getAbstractFieldValue(field);
+ if (!abstractValue.isSingleConstValue()) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+ if (!singleConstValue.isMaterializableInContext(appView, method)) {
+ throw reportUnknownFieldReadFromSingleton(fieldRead);
+ }
+
+ Instruction replacement =
+ singleConstValue.createMaterializingInstruction(appView, code, fieldRead);
+ instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
+ }
+
+ private RuntimeException reportUnknownFieldReadFromSingleton(InstanceGet fieldRead) {
+ throw appView
+ .reporter()
+ .fatalError(
+ "Unexpected usage left in method `"
+ + method.toSourceString()
+ + "` after inlining: "
+ + fieldRead.toString());
+ }
+
private void removeFieldWrites() {
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (!user.isInstancePut()) {
@@ -809,6 +816,9 @@
+ "` after field reads removed: "
+ user);
}
+
+ assert root.isNewInstance();
+
InstancePut instancePut = user.asInstancePut();
DexEncodedField field =
appView
@@ -864,7 +874,8 @@
if (parent == null) {
return null;
}
- DexProgramClass parentClass = asProgramClassOrNull(appView.definitionForHolder(parent));
+ DexProgramClass parentClass =
+ asProgramClassOrNull(appView.definitionForHolder(parent, method));
if (parentClass == null) {
return null;
}
@@ -878,7 +889,7 @@
DexEncodedMethod encodedParentMethod = encodedParent.getDefinition();
if (!encodedParentMethod.isInliningCandidate(
method,
- Reason.SIMPLE,
+ Reason.ALWAYS,
appView.appInfo(),
NopWhyAreYouNotInliningReporter.getInstance())) {
return null;
@@ -900,11 +911,29 @@
// - if it is a regular chaining pattern where the only users of the out value are receivers to
// other invocations. In that case, we should add all indirect users of the out value to ensure
// they can also be inlined.
- private boolean isEligibleInvokeWithAllUsersAsReceivers(
- ClassInlinerEligibilityInfo eligibility,
- InvokeMethodWithReceiver invoke,
+ private boolean scheduleNewUsersForAnalysis(
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ int parameter,
Set<Instruction> indirectUsers) {
- if (eligibility.returnsReceiver.isFalse()) {
+ ClassInlinerMethodConstraint classInlinerMethodConstraint =
+ singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint();
+ ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter);
+
+ OptionalBool returnsParameter;
+ if (usage.isParameterReturned()) {
+ if (singleTarget.getDefinition().getOptimizationInfo().returnsArgument()) {
+ assert singleTarget.getDefinition().getOptimizationInfo().getReturnedArgument()
+ == parameter;
+ returnsParameter = OptionalBool.TRUE;
+ } else {
+ returnsParameter = OptionalBool.UNKNOWN;
+ }
+ } else {
+ returnsParameter = OptionalBool.FALSE;
+ }
+
+ if (returnsParameter.isFalse()) {
return true;
}
@@ -922,168 +951,58 @@
// We cannot guarantee the invoke returns the receiver or another instance and since the
// return value is used we have to bail out.
- if (eligibility.returnsReceiver.isUnknown()) {
+ if (returnsParameter.isUnknown()) {
return false;
}
// Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
// receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
// may-alias set.
- AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE;
- if (!receivers.addReceiverAlias(outValue, kind)) {
+ assert returnsParameter.isTrue();
+ if (!receivers.addReceiverAlias(outValue)) {
return false;
}
- Set<Instruction> currentUsers = outValue.uniqueUsers();
- while (!currentUsers.isEmpty()) {
- Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
- for (Instruction instruction : currentUsers) {
- if (instruction.isAssume() || instruction.isCheckCast()) {
- if (instruction.isCheckCast()) {
- CheckCast checkCast = instruction.asCheckCast();
- if (!appView.appInfo().isSubtype(eligibleClass.type, checkCast.getType())) {
- return false; // Unsafe cast.
- }
- }
- Value outValueAlias = instruction.outValue();
- if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
- return false;
- }
- if (!receivers.addReceiverAlias(outValueAlias, kind)) {
- return false;
- }
- indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
- continue;
- }
-
- if (instruction.isInvokeMethodWithReceiver()) {
- InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
- if (user.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts) != outValue) {
- return false;
- }
- for (int i = 1; i < user.inValues().size(); i++) {
- Value inValue = user.inValues().get(i);
- if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == outValue) {
- return false;
- }
- }
- indirectUsers.add(user);
- continue;
- }
-
- return false;
- }
- currentUsers = indirectOutValueUsers;
- }
-
+ indirectUsers.addAll(outValue.uniqueUsers());
return true;
}
- private InliningInfo isEligibleDirectVirtualMethodCall(
- InvokeMethodWithReceiver invoke,
- SingleResolutionResult resolutionResult,
- ProgramMethod singleTarget,
- Set<Instruction> indirectUsers,
- Supplier<InliningOracle> defaultOracle) {
- assert isEligibleSingleTarget(singleTarget);
-
- // None of the none-receiver arguments may be an alias of the receiver.
- List<Value> inValues = invoke.inValues();
- for (int i = 1; i < inValues.size(); i++) {
- if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
- return null;
- }
- }
-
- // TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
- if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
- InliningOracle inliningOracle = defaultOracle.get();
- if (!inliningOracle.passesInliningConstraints(
- invoke,
- resolutionResult,
- singleTarget,
- Reason.SIMPLE,
- NopWhyAreYouNotInliningReporter.getInstance())) {
- return null;
- }
- }
-
- if (!isEligibleVirtualMethodCall(
- invoke,
- invoke.getInvokedMethod(),
- singleTarget,
- eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers),
- invoke.getType())) {
- return null;
- }
-
- ClassInlinerEligibilityInfo eligibility =
- singleTarget.getDefinition().getOptimizationInfo().getClassInlinerEligibility();
- if (eligibility.callsReceiver.size() > 1) {
- return null;
- }
- if (!eligibility.callsReceiver.isEmpty()) {
- assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL;
- Pair<Type, DexMethod> invokeInfo = eligibility.callsReceiver.get(0);
- Type invokeType = invokeInfo.getFirst();
- DexMethod indirectlyInvokedMethod = invokeInfo.getSecond();
- SingleResolutionResult indirectResolutionResult =
- appView
- .appInfo()
- .resolveMethodOn(eligibleClass, indirectlyInvokedMethod)
- .asSingleResolution();
- if (indirectResolutionResult == null) {
- return null;
- }
- ProgramMethod indirectSingleTarget =
- indirectResolutionResult.getResolutionPair().asProgramMethod();
- if (!isEligibleIndirectVirtualMethodCall(
- indirectlyInvokedMethod, invokeType, indirectSingleTarget)) {
- return null;
- }
- indirectMethodCallsOnInstance.add(indirectSingleTarget);
- }
-
- return new InliningInfo(singleTarget, eligibleClass.type);
- }
-
- private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod, Type type) {
- ProgramMethod singleTarget =
- asProgramMethodOrNull(
- appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).getSingleTarget(),
- appView);
- return isEligibleIndirectVirtualMethodCall(invokedMethod, type, singleTarget);
- }
private boolean isEligibleIndirectVirtualMethodCall(
- DexMethod invokedMethod, Type type, ProgramMethod singleTarget) {
+ DexMethod invokedMethod, ProgramMethod singleTarget) {
if (!isEligibleSingleTarget(singleTarget)) {
return false;
}
if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
return false;
}
- return isEligibleVirtualMethodCall(
- null,
- invokedMethod,
- singleTarget,
- eligibility -> eligibility.callsReceiver.isEmpty() && eligibility.returnsReceiver.isFalse(),
- type);
+ if (!isEligibleVirtualMethodCall(invokedMethod, singleTarget)) {
+ return false;
+ }
+
+ ParameterUsage usage =
+ singleTarget
+ .getDefinition()
+ .getOptimizationInfo()
+ .getClassInlinerMethodConstraint()
+ .getParameterUsage(0);
+ assert !usage.isTop();
+ if (usage.isBottom()) {
+ return true;
+ }
+ NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
+ return nonEmptyUsage.getMethodCallsWithParameterAsReceiver().isEmpty()
+ && !nonEmptyUsage.isParameterReturned();
}
- private boolean isEligibleVirtualMethodCall(
- InvokeMethodWithReceiver invoke,
- DexMethod callee,
- ProgramMethod singleTarget,
- Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck,
- Type type) {
+ private boolean isEligibleVirtualMethodCall(DexMethod callee, ProgramMethod singleTarget) {
assert isEligibleSingleTarget(singleTarget);
// We should not inline a method if the invocation has type interface or virtual and the
// signature of the invocation resolves to a private or static method.
// TODO(b/147212189): Why not inline private methods? If access is permitted it is valid.
ResolutionResult resolutionResult =
- appView.appInfo().resolveMethod(callee, type == Type.INTERFACE);
+ appView.appInfo().resolveMethodOnClass(callee, eligibleClass);
if (resolutionResult.isSingleResolution()
&& !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
return false;
@@ -1099,53 +1018,20 @@
MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
ClassInlinerMethodConstraint classInlinerMethodConstraint =
optimizationInfo.getClassInlinerMethodConstraint();
+ int parameter = 0;
if (root.isNewInstance()) {
- if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(singleTarget)) {
- return false;
- }
- } else {
- assert root.isStaticGet();
- if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(singleTarget)) {
- return false;
- }
+ return classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
+ singleTarget, parameter);
}
- // If the method returns receiver and the return value is actually
- // used in the code we need to make some additional checks.
- if (!eligibilityAcceptanceCheck.test(optimizationInfo.getClassInlinerEligibility())) {
- return false;
- }
-
- markSizeForInlining(invoke, singleTarget);
- return true;
- }
-
- private boolean isExtraMethodCall(InvokeMethod invoke) {
- if (invoke.isInvokeDirect() && dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
- return false;
- }
- if (invoke.isInvokeMethodWithReceiver()) {
- Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
- if (!receivers.addIllegalReceiverAlias(receiver)) {
- return false;
- }
- }
- if (invoke.isInvokeSuper()) {
- return false;
- }
- if (invoke.isInvokePolymorphic()) {
- return false;
- }
- return true;
+ assert root.isStaticGet();
+ return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
+ appView, parameter, objectState, method);
}
// Analyzes if a method invoke the eligible instance is passed to is eligible. In short,
// it can be eligible if:
//
- // -- eligible instance is passed as argument #N which is not used in the method,
- // such cases are collected in 'unusedArguments' parameter and later replaced
- // with 'null' value
- //
// -- eligible instance is passed as argument #N which is only used in the method to
// call a method on this object (we call it indirect method call), and method is
// eligible according to the same rules defined for direct method call eligibility
@@ -1157,52 +1043,48 @@
//
// -- method itself can be inlined
//
- private boolean isExtraMethodCallEligible(
- InvokeMethod invoke, ProgramMethod singleTarget, Supplier<InliningOracle> defaultOracle) {
- // Don't consider constructor invocations and super calls, since we don't want to forcibly
- // inline them.
- assert isExtraMethodCall(invoke);
- assert isEligibleSingleTarget(singleTarget);
-
- List<Value> arguments = Lists.newArrayList(invoke.inValues());
-
- // If we got here with invocation on receiver the user is ineligible.
- if (invoke.isInvokeMethodWithReceiver()) {
- InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
- Value receiver = invokeMethodWithReceiver.getReceiver();
- if (!receivers.addIllegalReceiverAlias(receiver)) {
- return false;
- }
-
- // A definitely null receiver will throw an error on call site.
- if (receiver.getType().nullability().isDefinitelyNull()) {
- return false;
- }
- }
-
- MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
-
- // Go through all arguments, see if all usages of eligibleInstance are good.
- if (!isEligibleParameterUsages(
- invoke, arguments, singleTarget.getDefinition(), defaultOracle)) {
+ private boolean isEligibleDirectMethodCall(
+ InvokeMethod invoke,
+ SingleResolutionResult resolutionResult,
+ ProgramMethod singleTarget,
+ Supplier<InliningOracle> defaultOracle,
+ Set<Instruction> indirectUsers) {
+ if (!((invoke.isInvokeDirect() && !invoke.isInvokeConstructor(dexItemFactory))
+ || invoke.isInvokeInterface()
+ || invoke.isInvokeStatic()
+ || invoke.isInvokeVirtual())) {
return false;
}
- for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex).getAliasedValue();
- ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
- if (receivers.isDefiniteReceiverAlias(argument)
- && parameterUsage != null
- && parameterUsage.notUsed()) {
- // Reference can be removed since it's not used.
- unusedArguments.add(new Pair<>(invoke, argIndex));
+ // If we got here with invocation on receiver the user is ineligible.
+ if (invoke.isInvokeMethodWithReceiver()) {
+ // A definitely null receiver will throw an error on call site.
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+ if (receiver.getType().isDefinitelyNull()) {
+ return false;
}
}
- extraMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
+ // Check if the method is inline-able by standard inliner.
+ InliningOracle oracle = defaultOracle.get();
+ if (!oracle.passesInliningConstraints(
+ invoke,
+ resolutionResult,
+ singleTarget,
+ Reason.ALWAYS,
+ NopWhyAreYouNotInliningReporter.getInstance())) {
+ return false;
+ }
+
+ // Go through all arguments, see if all usages of eligibleInstance are good.
+ if (!isEligibleParameterUsages(invoke, singleTarget, indirectUsers)) {
+ return false;
+ }
+
+ directMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
// Looks good.
- markSizeForInlining(invoke, singleTarget);
+ markSizeOfDirectTargetForInlining(invoke, singleTarget);
return true;
}
@@ -1219,147 +1101,90 @@
}
private boolean isEligibleParameterUsages(
- InvokeMethod invoke,
- List<Value> arguments,
- DexEncodedMethod singleTarget,
- Supplier<InliningOracle> defaultOracle) {
+ InvokeMethod invoke, ProgramMethod singleTarget, Set<Instruction> indirectUsers) {
// Go through all arguments, see if all usages of eligibleInstance are good.
- for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
-
- Value argument = arguments.get(argIndex);
+ for (int parameter = 0; parameter < invoke.arguments().size(); parameter++) {
+ Value argument = invoke.getArgument(parameter);
if (receivers.isReceiverAlias(argument)) {
// Have parameter usage info?
- if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
+ if (!isEligibleParameterUsage(invoke, singleTarget, parameter, indirectUsers)) {
return false;
}
} else {
// Nothing to worry about, unless `argument` becomes an alias of the receiver later.
+ int finalParameter = parameter;
receivers.addDeferredAliasValidityCheck(
- argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle));
+ argument,
+ () -> isEligibleParameterUsage(invoke, singleTarget, finalParameter, indirectUsers));
}
}
return true;
}
private boolean isEligibleParameterUsage(
- ParameterUsage parameterUsage, InvokeMethod invoke, Supplier<InliningOracle> defaultOracle) {
- if (parameterUsage == null) {
- return false; // Don't know anything.
- }
-
- if (parameterUsage.notUsed()) {
- return true;
- }
-
- if (parameterUsage.isAssignedToField) {
- return false;
- }
-
- if (root.isStaticGet()) {
- // If we are class inlining a singleton instance from a static-get, then we don't know
- // the value of the fields, and we also can't optimize away instance-field assignments, as
- // they have global side effects.
- if (parameterUsage.hasFieldAssignment || parameterUsage.hasFieldRead) {
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ int parameter,
+ Set<Instruction> indirectUsers) {
+ ClassInlinerMethodConstraint classInlinerMethodConstraint =
+ singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint();
+ if (root.isNewInstance()) {
+ if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
+ singleTarget, parameter)) {
+ return false;
+ }
+ } else {
+ assert root.isStaticGet();
+ if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
+ appView, parameter, objectState, method)) {
return false;
}
}
- if (parameterUsage.isReturned) {
- if (invoke.outValue() != null && invoke.outValue().hasAnyUsers()) {
- // Used as return value which is not ignored.
- return false;
- }
- }
-
- if (parameterUsage.isUsedInMonitor) {
- return !root.isStaticGet();
- }
-
- if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) {
- // Used in unsupported zero-check-if kinds.
+ ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter);
+ if (!scheduleNewUsersForAnalysis(invoke, singleTarget, parameter, indirectUsers)) {
return false;
}
- for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) {
- Type type = call.getFirst();
- DexMethod target = call.getSecond();
+ if (!usage.isBottom()) {
+ NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
+ for (DexMethod invokedMethod : nonEmptyUsage.getMethodCallsWithParameterAsReceiver()) {
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).asSingleResolution();
+ if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
+ return false;
+ }
- if (type == Type.VIRTUAL || type == Type.INTERFACE) {
// Is the method called indirectly still eligible?
- if (!isEligibleIndirectVirtualMethodCall(target, type)) {
+ ProgramMethod indirectSingleTarget = resolutionResult.getResolutionPair().asProgramMethod();
+ if (!isEligibleIndirectVirtualMethodCall(invokedMethod, indirectSingleTarget)) {
return false;
}
- } else if (type == Type.DIRECT) {
- if (!isInstanceInitializerEligibleForClassInlining(target)) {
- // Only calls to trivial instance initializers are supported at this point.
- return false;
- }
- } else {
- // Static and super calls are not yet supported.
- return false;
- }
-
- SingleResolutionResult resolutionResult =
- appView
- .appInfo()
- .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
- .asSingleResolution();
- if (resolutionResult == null) {
- return false;
- }
-
- // Check if the method is inline-able by standard inliner.
- // TODO(b/156853206): Should not duplicate resolution.
- ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method);
- if (singleTarget == null) {
- return false;
- }
-
- InliningOracle oracle = defaultOracle.get();
- InlineAction inlineAction =
- oracle.computeInlining(
- invoke,
- resolutionResult,
- singleTarget,
- method,
- ClassInitializationAnalysis.trivial(),
- NopWhyAreYouNotInliningReporter.getInstance());
- if (inlineAction == null) {
- return false;
+ markSizeOfIndirectTargetForInlining(indirectSingleTarget);
}
}
+
return true;
}
- private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) {
- if (method == dexItemFactory.objectMembers.constructor) {
- return true;
- }
- DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
- DexEncodedMethod definition = method.lookupOnClass(holder);
- if (definition == null) {
- return false;
- }
- InstanceInitializerInfo initializerInfo =
- definition.getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
- return initializerInfo.receiverNeverEscapesOutsideConstructorChain();
- }
-
private boolean exemptFromInstructionLimit(ProgramMethod inlinee) {
KotlinClassLevelInfo kotlinInfo = inlinee.getHolder().getKotlinInfo();
return kotlinInfo.isSyntheticClass() && kotlinInfo.asSyntheticClass().isLambda();
}
- private void markSizeForInlining(InvokeMethod invoke, ProgramMethod inlinee) {
+ private void markSizeOfIndirectTargetForInlining(ProgramMethod inlinee) {
assert !methodProcessor.isProcessedConcurrently(inlinee);
if (!exemptFromInstructionLimit(inlinee)) {
- if (invoke != null) {
- directInlinees.put(invoke, inlinee);
- } else {
- indirectInlinees.add(inlinee);
- }
+ indirectInlinees.add(inlinee);
+ }
+ indirectMethodCallsOnInstance.add(inlinee);
+ }
+
+ private void markSizeOfDirectTargetForInlining(InvokeMethod invoke, ProgramMethod inlinee) {
+ assert invoke != null;
+ assert !methodProcessor.isProcessedConcurrently(inlinee);
+ if (!exemptFromInstructionLimit(inlinee)) {
+ directInlinees.put(invoke, inlinee);
}
}
@@ -1374,7 +1199,7 @@
.getDefinition()
.isInliningCandidate(
method,
- Reason.SIMPLE,
+ Reason.ALWAYS,
appView.appInfo(),
NopWhyAreYouNotInliningReporter.getInstance())) {
// If `singleTarget` is not an inlining candidate, we won't be able to inline it here.
@@ -1394,5 +1219,10 @@
instruction.getBlock().removeInstruction(instruction);
}
- static class IllegalClassInlinerStateException extends Exception {}
+ static class IllegalClassInlinerStateException extends Exception {
+
+ IllegalClassInlinerStateException() {
+ assert false;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java
new file mode 100644
index 0000000..5f63f4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+/**
+ * The context used for the {@link ParameterUsages} lattice.
+ *
+ * <p>By only using {@link DefaultAnalysisContext} a context-insensitive result is achieved.
+ * Context-sensitive results can be achieved by forking different analysis contexts during the class
+ * inliner constraint analysis.
+ */
+public abstract class AnalysisContext {
+
+ public static DefaultAnalysisContext getDefaultContext() {
+ return DefaultAnalysisContext.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
new file mode 100644
index 0000000..930edc5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+class BottomParameterUsage extends ParameterUsage {
+
+ private static final BottomParameterUsage BOTTOM = new BottomParameterUsage();
+
+ private BottomParameterUsage() {}
+
+ static BottomParameterUsage getInstance() {
+ return BOTTOM;
+ }
+
+ @Override
+ ParameterUsage addFieldReadFromParameter(DexField field) {
+ return InternalNonEmptyParameterUsage.builder().addFieldReadFromParameter(field).build();
+ }
+
+ @Override
+ ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+ return InternalNonEmptyParameterUsage.builder()
+ .addMethodCallWithParameterAsReceiver(invoke)
+ .build();
+ }
+
+ @Override
+ ParameterUsage externalize() {
+ return this;
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ public boolean isParameterMutated() {
+ return false;
+ }
+
+ @Override
+ public boolean isParameterReturned() {
+ return false;
+ }
+
+ @Override
+ public boolean isParameterUsedAsLock() {
+ return false;
+ }
+
+ @Override
+ ParameterUsage setParameterMutated() {
+ return InternalNonEmptyParameterUsage.builder().setParameterMutated().build();
+ }
+
+ @Override
+ ParameterUsage setParameterReturned() {
+ return InternalNonEmptyParameterUsage.builder().setParameterReturned().build();
+ }
+
+ @Override
+ ParameterUsage setParameterUsedAsLock() {
+ return InternalNonEmptyParameterUsage.builder().setParameterUsedAsLock().build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java
new file mode 100644
index 0000000..b535038
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+class BottomParameterUsagePerContext extends ParameterUsagePerContext {
+
+ private static final BottomParameterUsagePerContext INSTANCE =
+ new BottomParameterUsagePerContext();
+
+ private BottomParameterUsagePerContext() {}
+
+ static BottomParameterUsagePerContext getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ ParameterUsagePerContext externalize() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsage get(AnalysisContext context) {
+ return ParameterUsage.bottom();
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ ParameterUsagePerContext rebuild(
+ BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
new file mode 100644
index 0000000..84bae0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+public class BottomParameterUsages extends ParameterUsages {
+
+ private static final BottomParameterUsages INSTANCE = new BottomParameterUsages();
+
+ private BottomParameterUsages() {}
+
+ static BottomParameterUsages getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ ParameterUsages externalize() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsagePerContext get(int parameter) {
+ return ParameterUsagePerContext.bottom();
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) {
+ Int2ObjectOpenHashMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>();
+ backing.put(parameter, usagePerContext);
+ return NonEmptyParameterUsages.create(backing);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == INSTANCE;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
new file mode 100644
index 0000000..b8f406a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.classinliner.constraint.ConditionalClassInlinerMethodConstraint;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ClassInlinerMethodConstraintAnalysis {
+
+ public static ClassInlinerMethodConstraint analyze(
+ AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+ if (method.getDefinition().isClassInitializer()
+ || method.getDefinition().getNumberOfArguments() == 0) {
+ return ClassInlinerMethodConstraint.alwaysFalse();
+ }
+
+ // Analyze code.
+ IntraproceduralDataflowAnalysis<ParameterUsages> analysis =
+ new IntraproceduralDataflowAnalysis<>(
+ ParameterUsages.bottom(), new TransferFunction(appView, method, code));
+ SuccessfulDataflowAnalysisResult<ParameterUsages> result =
+ analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
+ if (result == null) {
+ return ClassInlinerMethodConstraint.alwaysFalse();
+ }
+ ParameterUsages usages = result.join().externalize();
+ if (usages.isBottom()) {
+ return ClassInlinerMethodConstraint.alwaysTrue();
+ }
+ if (usages.isTop()) {
+ return ClassInlinerMethodConstraint.alwaysFalse();
+ }
+ return new ConditionalClassInlinerMethodConstraint(usages);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java
new file mode 100644
index 0000000..5378648
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+class DefaultAnalysisContext extends AnalysisContext {
+
+ private static final DefaultAnalysisContext INSTANCE = new DefaultAnalysisContext();
+
+ private DefaultAnalysisContext() {}
+
+ static DefaultAnalysisContext getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
new file mode 100644
index 0000000..4bba181
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
@@ -0,0 +1,282 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Non-trivial information (neither BOTTOM nor TOP) about a method's usage of a given parameter.
+ *
+ * <p>This is internal to the analysis, since {@link #methodCallsWithParameterAsReceiver} references
+ * instructions from {@link com.android.tools.r8.ir.code.IRCode}, which makes it unsuited for being
+ * stored in {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}.
+ */
+public class InternalNonEmptyParameterUsage extends ParameterUsage {
+
+ private Set<DexField> fieldsReadFromParameter;
+ private Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver;
+
+ private boolean isParameterMutated;
+ private boolean isParameterReturned;
+ private boolean isParameterUsedAsLock;
+
+ InternalNonEmptyParameterUsage(
+ Set<DexField> fieldsReadFromParameter,
+ Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver,
+ boolean isParameterMutated,
+ boolean isParameterReturned,
+ boolean isParameterUsedAsLock) {
+ assert !fieldsReadFromParameter.isEmpty()
+ || !methodCallsWithParameterAsReceiver.isEmpty()
+ || isParameterMutated
+ || isParameterReturned
+ || isParameterUsedAsLock;
+ this.fieldsReadFromParameter = fieldsReadFromParameter;
+ this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
+ this.isParameterMutated = isParameterMutated;
+ this.isParameterReturned = isParameterReturned;
+ this.isParameterUsedAsLock = isParameterUsedAsLock;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ Builder builderFromInstance() {
+ return new Builder(this);
+ }
+
+ @Override
+ InternalNonEmptyParameterUsage addFieldReadFromParameter(DexField field) {
+ ImmutableSet.Builder<DexField> newFieldsReadFromParameterBuilder = ImmutableSet.builder();
+ newFieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter);
+ newFieldsReadFromParameterBuilder.add(field);
+ return new InternalNonEmptyParameterUsage(
+ newFieldsReadFromParameterBuilder.build(),
+ methodCallsWithParameterAsReceiver,
+ isParameterMutated,
+ isParameterReturned,
+ isParameterUsedAsLock);
+ }
+
+ @Override
+ InternalNonEmptyParameterUsage addMethodCallWithParameterAsReceiver(
+ InvokeMethodWithReceiver invoke) {
+ ImmutableSet.Builder<InvokeMethodWithReceiver> newMethodCallsWithParameterAsReceiverBuilder =
+ ImmutableSet.builder();
+ newMethodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver);
+ newMethodCallsWithParameterAsReceiverBuilder.add(invoke);
+ return new InternalNonEmptyParameterUsage(
+ fieldsReadFromParameter,
+ newMethodCallsWithParameterAsReceiverBuilder.build(),
+ isParameterMutated,
+ isParameterReturned,
+ isParameterUsedAsLock);
+ }
+
+ @Override
+ public InternalNonEmptyParameterUsage asInternalNonEmpty() {
+ return this;
+ }
+
+ @Override
+ ParameterUsage externalize() {
+ ImmutableMultiset.Builder<DexMethod> methodCallsWithParameterAsReceiverBuilder =
+ ImmutableMultiset.builder();
+ methodCallsWithParameterAsReceiver.forEach(
+ invoke -> methodCallsWithParameterAsReceiverBuilder.add(invoke.getInvokedMethod()));
+ return new NonEmptyParameterUsage(
+ fieldsReadFromParameter,
+ methodCallsWithParameterAsReceiverBuilder.build(),
+ isParameterMutated,
+ isParameterReturned,
+ isParameterUsedAsLock);
+ }
+
+ @Override
+ public boolean isParameterMutated() {
+ return isParameterMutated;
+ }
+
+ @Override
+ public boolean isParameterReturned() {
+ return isParameterReturned;
+ }
+
+ @Override
+ public boolean isParameterUsedAsLock() {
+ return isParameterUsedAsLock;
+ }
+
+ InternalNonEmptyParameterUsage join(InternalNonEmptyParameterUsage other) {
+ return builderFromInstance()
+ .addFieldsReadFromParameter(other.fieldsReadFromParameter)
+ .addMethodCallsWithParameterAsReceiver(other.methodCallsWithParameterAsReceiver)
+ .joinIsReceiverMutated(other.isParameterMutated)
+ .joinIsReceiverReturned(other.isParameterReturned)
+ .joinIsReceiverUsedAsLock(other.isParameterUsedAsLock)
+ .build();
+ }
+
+ @Override
+ InternalNonEmptyParameterUsage setParameterMutated() {
+ if (isParameterMutated) {
+ return this;
+ }
+ return new InternalNonEmptyParameterUsage(
+ fieldsReadFromParameter,
+ methodCallsWithParameterAsReceiver,
+ true,
+ isParameterReturned,
+ isParameterUsedAsLock);
+ }
+
+ @Override
+ InternalNonEmptyParameterUsage setParameterReturned() {
+ if (isParameterReturned) {
+ return this;
+ }
+ return new InternalNonEmptyParameterUsage(
+ fieldsReadFromParameter,
+ methodCallsWithParameterAsReceiver,
+ isParameterMutated,
+ true,
+ isParameterUsedAsLock);
+ }
+
+ @Override
+ InternalNonEmptyParameterUsage setParameterUsedAsLock() {
+ if (isParameterUsedAsLock) {
+ return this;
+ }
+ return new InternalNonEmptyParameterUsage(
+ fieldsReadFromParameter,
+ methodCallsWithParameterAsReceiver,
+ isParameterMutated,
+ isParameterReturned,
+ true);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ InternalNonEmptyParameterUsage knownParameterUsage = (InternalNonEmptyParameterUsage) obj;
+ return isParameterMutated == knownParameterUsage.isParameterMutated
+ && isParameterReturned == knownParameterUsage.isParameterReturned
+ && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+ && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
+ && methodCallsWithParameterAsReceiver.equals(
+ knownParameterUsage.methodCallsWithParameterAsReceiver);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash =
+ 31 * (31 + fieldsReadFromParameter.hashCode())
+ + methodCallsWithParameterAsReceiver.hashCode();
+ assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
+ return hash;
+ }
+
+ static class Builder {
+
+ private ImmutableSet.Builder<DexField> fieldsReadFromParameterBuilder;
+ private ImmutableSet.Builder<InvokeMethodWithReceiver>
+ methodCallsWithParameterAsReceiverBuilder;
+ private boolean isParameterMutated;
+ private boolean isParameterReturned;
+ private boolean isParameterUsedAsLock;
+
+ Builder() {
+ fieldsReadFromParameterBuilder = ImmutableSet.builder();
+ methodCallsWithParameterAsReceiverBuilder = ImmutableSet.builder();
+ }
+
+ Builder(InternalNonEmptyParameterUsage methodBehavior) {
+ fieldsReadFromParameterBuilder =
+ ImmutableSet.<DexField>builder().addAll(methodBehavior.fieldsReadFromParameter);
+ methodCallsWithParameterAsReceiverBuilder =
+ ImmutableSet.<InvokeMethodWithReceiver>builder()
+ .addAll(methodBehavior.methodCallsWithParameterAsReceiver);
+ isParameterMutated = methodBehavior.isParameterMutated;
+ isParameterReturned = methodBehavior.isParameterReturned;
+ isParameterUsedAsLock = methodBehavior.isParameterUsedAsLock;
+ }
+
+ Builder addFieldReadFromParameter(DexField fieldReadFromParameter) {
+ fieldsReadFromParameterBuilder.add(fieldReadFromParameter);
+ return this;
+ }
+
+ Builder addFieldsReadFromParameter(Collection<DexField> fieldsReadFromParameter) {
+ fieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter);
+ return this;
+ }
+
+ Builder addMethodCallWithParameterAsReceiver(
+ InvokeMethodWithReceiver methodCallWithParameterAsReceiver) {
+ methodCallsWithParameterAsReceiverBuilder.add(methodCallWithParameterAsReceiver);
+ return this;
+ }
+
+ Builder addMethodCallsWithParameterAsReceiver(
+ Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver) {
+ methodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver);
+ return this;
+ }
+
+ Builder joinIsReceiverMutated(boolean isParameterMutated) {
+ this.isParameterMutated |= isParameterMutated;
+ return this;
+ }
+
+ Builder joinIsReceiverReturned(boolean isParameterReturned) {
+ this.isParameterReturned |= isParameterReturned;
+ return this;
+ }
+
+ Builder joinIsReceiverUsedAsLock(boolean isParameterUsedAsLock) {
+ this.isParameterUsedAsLock |= isParameterUsedAsLock;
+ return this;
+ }
+
+ Builder setParameterMutated() {
+ this.isParameterMutated = true;
+ return this;
+ }
+
+ Builder setParameterReturned() {
+ this.isParameterReturned = true;
+ return this;
+ }
+
+ Builder setParameterUsedAsLock() {
+ this.isParameterUsedAsLock = true;
+ return this;
+ }
+
+ InternalNonEmptyParameterUsage build() {
+ return new InternalNonEmptyParameterUsage(
+ fieldsReadFromParameterBuilder.build(),
+ methodCallsWithParameterAsReceiverBuilder.build(),
+ isParameterMutated,
+ isParameterReturned,
+ isParameterUsedAsLock);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
new file mode 100644
index 0000000..d377305
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.Multiset;
+import java.util.Objects;
+import java.util.Set;
+
+public class NonEmptyParameterUsage extends ParameterUsage {
+
+ private Set<DexField> fieldsReadFromParameter;
+ private Multiset<DexMethod> methodCallsWithParameterAsReceiver;
+
+ private boolean isParameterMutated;
+ private boolean isParameterReturned;
+ private boolean isParameterUsedAsLock;
+
+ NonEmptyParameterUsage(
+ Set<DexField> fieldsReadFromParameter,
+ Multiset<DexMethod> methodCallsWithParameterAsReceiver,
+ boolean isParameterMutated,
+ boolean isParameterReturned,
+ boolean isParameterUsedAsLock) {
+ assert !fieldsReadFromParameter.isEmpty()
+ || !methodCallsWithParameterAsReceiver.isEmpty()
+ || isParameterMutated
+ || isParameterReturned
+ || isParameterUsedAsLock;
+ this.fieldsReadFromParameter = fieldsReadFromParameter;
+ this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
+ this.isParameterMutated = isParameterMutated;
+ this.isParameterReturned = isParameterReturned;
+ this.isParameterUsedAsLock = isParameterUsedAsLock;
+ }
+
+ @Override
+ ParameterUsage addFieldReadFromParameter(DexField field) {
+ throw new Unreachable();
+ }
+
+ @Override
+ ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public NonEmptyParameterUsage asNonEmpty() {
+ return this;
+ }
+
+ @Override
+ ParameterUsage externalize() {
+ throw new Unreachable();
+ }
+
+ public boolean hasFieldsReadFromParameter() {
+ return !getFieldsReadFromParameter().isEmpty();
+ }
+
+ public Set<DexField> getFieldsReadFromParameter() {
+ return fieldsReadFromParameter;
+ }
+
+ public Multiset<DexMethod> getMethodCallsWithParameterAsReceiver() {
+ return methodCallsWithParameterAsReceiver;
+ }
+
+ @Override
+ public boolean isParameterMutated() {
+ return isParameterMutated;
+ }
+
+ @Override
+ public boolean isParameterReturned() {
+ return isParameterReturned;
+ }
+
+ @Override
+ public boolean isParameterUsedAsLock() {
+ return isParameterUsedAsLock;
+ }
+
+ @Override
+ ParameterUsage setParameterMutated() {
+ throw new Unreachable();
+ }
+
+ @Override
+ ParameterUsage setParameterReturned() {
+ throw new Unreachable();
+ }
+
+ @Override
+ ParameterUsage setParameterUsedAsLock() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ NonEmptyParameterUsage knownParameterUsage = (NonEmptyParameterUsage) obj;
+ return isParameterMutated == knownParameterUsage.isParameterMutated
+ && isParameterReturned == knownParameterUsage.isParameterReturned
+ && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+ && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
+ && methodCallsWithParameterAsReceiver.equals(
+ knownParameterUsage.methodCallsWithParameterAsReceiver);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash =
+ 31 * (31 + fieldsReadFromParameter.hashCode())
+ + methodCallsWithParameterAsReceiver.hashCode();
+ assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
+ hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
+ return hash;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
new file mode 100644
index 0000000..9460e7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+class NonEmptyParameterUsagePerContext extends ParameterUsagePerContext {
+
+ private final Map<AnalysisContext, ParameterUsage> backing;
+
+ private NonEmptyParameterUsagePerContext(Map<AnalysisContext, ParameterUsage> backing) {
+ assert !backing.isEmpty();
+ this.backing = backing;
+ }
+
+ static ParameterUsagePerContext create(Map<AnalysisContext, ParameterUsage> backing) {
+ return backing.isEmpty() ? bottom() : new NonEmptyParameterUsagePerContext(backing);
+ }
+
+ static NonEmptyParameterUsagePerContext createInitial() {
+ return new NonEmptyParameterUsagePerContext(
+ ImmutableMap.of(DefaultAnalysisContext.getInstance(), ParameterUsage.bottom()));
+ }
+
+ void forEach(BiConsumer<AnalysisContext, ParameterUsage> consumer) {
+ backing.forEach(consumer);
+ }
+
+ ParameterUsagePerContext join(NonEmptyParameterUsagePerContext parameterUsagePerContext) {
+ if (isBottom()) {
+ return parameterUsagePerContext;
+ }
+ if (parameterUsagePerContext.isBottom()) {
+ return this;
+ }
+ Map<AnalysisContext, ParameterUsage> newBacking = new HashMap<>(backing);
+ parameterUsagePerContext.forEach(
+ (context, parameterUsage) ->
+ newBacking.put(
+ context,
+ parameterUsage.join(newBacking.getOrDefault(context, ParameterUsage.bottom()))));
+ return create(newBacking);
+ }
+
+ @Override
+ NonEmptyParameterUsagePerContext asKnown() {
+ return this;
+ }
+
+ @Override
+ ParameterUsagePerContext externalize() {
+ boolean allBottom = true;
+ boolean allTop = true;
+ for (ParameterUsage usage : backing.values()) {
+ if (!usage.isBottom()) {
+ allBottom = false;
+ }
+ if (!usage.isTop()) {
+ allTop = false;
+ }
+ }
+ if (allBottom) {
+ return bottom();
+ }
+ if (allTop) {
+ return top();
+ }
+ // Remove mappings to top. These mappings represent unknown information, which there is no point
+ // in storing. After the removal of these mappings, the result should still be non-empty.
+ ParameterUsagePerContext rebuilt =
+ rebuild((context, usage) -> usage.isTop() ? null : usage.externalize());
+ assert !rebuilt.isBottom();
+ assert !rebuilt.isTop();
+ return rebuilt;
+ }
+
+ @Override
+ public ParameterUsage get(AnalysisContext context) {
+ return backing.getOrDefault(context, ParameterUsage.top());
+ }
+
+ @Override
+ ParameterUsagePerContext rebuild(
+ BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+ ImmutableMap.Builder<AnalysisContext, ParameterUsage> builder = null;
+ for (Map.Entry<AnalysisContext, ParameterUsage> entry : backing.entrySet()) {
+ AnalysisContext context = entry.getKey();
+ ParameterUsage usage = entry.getValue();
+ ParameterUsage newUsage = transformation.apply(context, usage);
+ if (newUsage != null) {
+ if (newUsage != usage) {
+ if (builder == null) {
+ builder = ImmutableMap.builder();
+ for (Map.Entry<AnalysisContext, ParameterUsage> previousEntry : backing.entrySet()) {
+ AnalysisContext previousContext = previousEntry.getKey();
+ if (previousContext == context) {
+ break;
+ }
+ builder.put(previousContext, previousEntry.getValue());
+ }
+ }
+ builder.put(context, newUsage);
+ } else if (builder != null) {
+ builder.put(context, newUsage);
+ }
+ }
+ }
+ return builder != null ? create(builder.build()) : this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ NonEmptyParameterUsagePerContext knownParameterUsagePerContext =
+ (NonEmptyParameterUsagePerContext) obj;
+ return backing.equals(knownParameterUsagePerContext.backing);
+ }
+
+ @Override
+ public int hashCode() {
+ return backing.hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
new file mode 100644
index 0000000..f7b2cae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Int2ObjectMapUtils;
+import com.android.tools.r8.utils.IntObjConsumer;
+import com.android.tools.r8.utils.IntObjPredicate;
+import com.android.tools.r8.utils.IntObjToObjFunction;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+/**
+ * This implements the lattice for the dataflow analysis used to determine if a given method is
+ * eligible for class inlining.
+ *
+ * <p>Given a value that is subject to class inlining (e.g., `A a = new A()`), we need to determine
+ * if the value `a` will be eligible for class inlining if it flows into a method call. The value
+ * `a` may flow into any argument position, therefore we need to be able to determine if calls where
+ * `a` is used as a receiver are eligible for class inlining {@code a.foo(...)}, as well as calls
+ * where `a` is not used as a receiver: {@code other.foo(a)} or {@code Clazz.foo(a)}.
+ *
+ * <p>To answer such questions, this lattice contains information about the way a method uses its
+ * parameters. For a given parameter, this information is encoded in {@link ParameterUsage}.
+ *
+ * <p>To facilitate context sensitive information, {@link ParameterUsagePerContext} gives the
+ * parameter usage information for a given parameter in a given context. As a simple example,
+ * consider the following method:
+ *
+ * <pre>
+ * int x;
+ * void foo() {
+ * if (this.x == 0) {
+ * // Do nothing.
+ * } else {
+ * System.out.println(x);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>In the above example, the parameter `this` is not eligible for class inlining if `this.x !=
+ * 0`. However, when `this.x == 0`, the parameter usage information is bottom. This piece of
+ * information is encoded as a map lattice from contexts to parameter usage information:
+ *
+ * <pre>
+ * ParameterUsagePerContext[
+ * Context[this.x == 0] -> BottomParameterUsage (BOTTOM),
+ * DefaultContext -> UnknownParameterUsage (TOP)
+ * ]
+ * </pre>
+ *
+ * <p>Finally, to provide the information for each method parameter, this class provides a mapping
+ * from parameters to {@link ParameterUsagePerContext}.
+ */
+public class NonEmptyParameterUsages extends ParameterUsages {
+
+ private static final AssumeAndCheckCastAliasedValueConfiguration aliasedValueConfiguration =
+ AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+
+ private final Int2ObjectMap<ParameterUsagePerContext> backing;
+
+ private NonEmptyParameterUsages(Int2ObjectMap<ParameterUsagePerContext> backing) {
+ assert !backing.isEmpty() : "Should use bottom() instead";
+ this.backing = backing;
+ }
+
+ public static ParameterUsages create(Int2ObjectMap<ParameterUsagePerContext> backing) {
+ return backing.isEmpty() ? bottom() : new NonEmptyParameterUsages(backing);
+ }
+
+ public boolean allMatch(IntObjPredicate<ParameterUsagePerContext> predicate) {
+ for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) {
+ if (!predicate.test(entry.getIntKey(), entry.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public NonEmptyParameterUsages asNonEmpty() {
+ return this;
+ }
+
+ @Override
+ ParameterUsages externalize() {
+ NonEmptyParameterUsages rebuilt =
+ rebuildParameters((parameter, usagePerContext) -> usagePerContext.externalize());
+ boolean allBottom = true;
+ boolean allTop = true;
+ for (ParameterUsagePerContext usagePerContext : rebuilt.backing.values()) {
+ if (!usagePerContext.isBottom()) {
+ allBottom = false;
+ }
+ if (!usagePerContext.isTop()) {
+ allTop = false;
+ }
+ }
+ if (allBottom) {
+ return bottom();
+ }
+ if (allTop) {
+ return top();
+ }
+ return rebuilt;
+ }
+
+ @Override
+ ParameterUsages put(int parameter, ParameterUsagePerContext parameterUsagePerContext) {
+ Int2ObjectOpenHashMap<ParameterUsagePerContext> newBacking =
+ new Int2ObjectOpenHashMap<>(backing);
+ newBacking.put(parameter, parameterUsagePerContext);
+ return create(newBacking);
+ }
+
+ public void forEach(IntObjConsumer<ParameterUsagePerContext> consumer) {
+ Int2ObjectMapUtils.forEach(backing, consumer);
+ }
+
+ @Override
+ public ParameterUsagePerContext get(int parameter) {
+ return backing.getOrDefault(parameter, ParameterUsagePerContext.top());
+ }
+
+ NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Value value) {
+ return rebuildParameter(value, (context, usage) -> ParameterUsage.top());
+ }
+
+ NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Collection<Value> values) {
+ if (values.isEmpty()) {
+ return this;
+ }
+ int[] parametersToRebuild = new int[values.size()];
+ Iterator<Value> iterator = values.iterator();
+ for (int i = 0; i < values.size(); i++) {
+ parametersToRebuild[i] = iterator.next().getDefinition().asArgument().getIndex();
+ }
+ return rebuildParameters(
+ (currentParameter, usagePerContext) ->
+ ArrayUtils.containsInt(parametersToRebuild, currentParameter)
+ ? usagePerContext.rebuild((context, usage) -> ParameterUsage.top())
+ : usagePerContext);
+ }
+
+ NonEmptyParameterUsages abandonClassInliningInCurrentContexts(
+ Iterable<Value> values, Predicate<Value> predicate) {
+ List<Value> filtered = new ArrayList<>();
+ for (Value value : values) {
+ Value root = value.getAliasedValue(aliasedValueConfiguration);
+ if (predicate.test(root)) {
+ filtered.add(root);
+ }
+ }
+ return abandonClassInliningInCurrentContexts(filtered);
+ }
+
+ NonEmptyParameterUsages rebuildParameter(
+ Value value, BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+ Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
+ assert valueRoot.isArgument();
+ int parameter = valueRoot.getDefinition().asArgument().getIndex();
+ return rebuildParameters(
+ (currentParameter, usagePerContext) ->
+ currentParameter == parameter
+ ? usagePerContext.rebuild(transformation)
+ : usagePerContext);
+ }
+
+ NonEmptyParameterUsages rebuildParameters(
+ IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) {
+ Int2ObjectMap<ParameterUsagePerContext> rebuiltBacking = null;
+ for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) {
+ int parameter = entry.getIntKey();
+ ParameterUsagePerContext usagePerContext = entry.getValue();
+ ParameterUsagePerContext newUsagePerContext =
+ transformation.apply(parameter, entry.getValue());
+ if (newUsagePerContext != usagePerContext) {
+ if (rebuiltBacking == null) {
+ rebuiltBacking = new Int2ObjectOpenHashMap<>();
+ for (Int2ObjectMap.Entry<ParameterUsagePerContext> previousEntry :
+ backing.int2ObjectEntrySet()) {
+ int previousParameter = previousEntry.getIntKey();
+ if (previousParameter == parameter) {
+ break;
+ }
+ rebuiltBacking.put(previousParameter, previousEntry.getValue());
+ }
+ }
+ rebuiltBacking.put(parameter, newUsagePerContext);
+ } else if (rebuiltBacking != null) {
+ rebuiltBacking.put(parameter, newUsagePerContext);
+ }
+ }
+ return rebuiltBacking != null ? new NonEmptyParameterUsages(rebuiltBacking) : this;
+ }
+
+ public NonEmptyParameterUsages join(NonEmptyParameterUsages otherAnalysisState) {
+ if (isBottom()) {
+ return otherAnalysisState;
+ }
+ if (otherAnalysisState.isBottom()) {
+ return this;
+ }
+ Int2ObjectMap<ParameterUsagePerContext> newBacking = new Int2ObjectOpenHashMap<>(backing);
+ otherAnalysisState.forEach(
+ (parameter, parameterUsagePerContext) ->
+ newBacking.put(
+ parameter,
+ parameterUsagePerContext.join(
+ Int2ObjectMapUtils.getOrDefault(
+ newBacking, parameter, ParameterUsagePerContext.bottom()))));
+ return new NonEmptyParameterUsages(newBacking);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ NonEmptyParameterUsages analysisState = (NonEmptyParameterUsages) other;
+ return backing.equals(analysisState.backing);
+ }
+
+ @Override
+ public int hashCode() {
+ return backing.hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
new file mode 100644
index 0000000..e2ad8da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+public abstract class ParameterUsage {
+
+ abstract ParameterUsage addFieldReadFromParameter(DexField field);
+
+ abstract ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke);
+
+ public NonEmptyParameterUsage asNonEmpty() {
+ return null;
+ }
+
+ InternalNonEmptyParameterUsage asInternalNonEmpty() {
+ return null;
+ }
+
+ abstract ParameterUsage externalize();
+
+ /**
+ * Returns true if this is an instanceof {@link BottomParameterUsage}.
+ *
+ * <p>Note that this does NOT imply that the parameter is <i>unused</i>, but only that it is
+ * always eligible for class inlining.
+ */
+ public boolean isBottom() {
+ return false;
+ }
+
+ /**
+ * Returns true if the method may mutate the state of this parameter (i.e., mutate the value of
+ * one of its instance fields).
+ */
+ public abstract boolean isParameterMutated();
+
+ /**
+ * Returns true if the method <i>may</i> return the parameter.
+ *
+ * <p>Note that this does NOT imply that the method <i>always</i> returns the method.
+ */
+ public abstract boolean isParameterReturned();
+
+ /**
+ * Returns true if the parameter may be used as a lock (i.e., may flow into a monitor
+ * instruction).
+ */
+ public abstract boolean isParameterUsedAsLock();
+
+ /**
+ * Returns true if this is an instance of {@link UnknownParameterUsage}.
+ *
+ * <p>In this case, the parameter is never eligible for class inlining.
+ */
+ public boolean isTop() {
+ return false;
+ }
+
+ ParameterUsage join(ParameterUsage parameterUsage) {
+ if (isBottom()) {
+ return parameterUsage;
+ }
+ if (parameterUsage.isBottom()) {
+ return this;
+ }
+ if (isTop() || parameterUsage.isTop()) {
+ return top();
+ }
+ return asInternalNonEmpty().join(parameterUsage.asInternalNonEmpty());
+ }
+
+ abstract ParameterUsage setParameterMutated();
+
+ abstract ParameterUsage setParameterReturned();
+
+ abstract ParameterUsage setParameterUsedAsLock();
+
+ public static BottomParameterUsage bottom() {
+ return BottomParameterUsage.getInstance();
+ }
+
+ public static UnknownParameterUsage top() {
+ return UnknownParameterUsage.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java
new file mode 100644
index 0000000..f294ea2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+public abstract class ParameterUsagePerContext {
+
+ NonEmptyParameterUsagePerContext asKnown() {
+ return null;
+ }
+
+ abstract ParameterUsagePerContext externalize();
+
+ /** Returns the usage information for this parameter in the given context. */
+ public abstract ParameterUsage get(AnalysisContext context);
+
+ /**
+ * Returns true if this is an instance of {@link BottomParameterUsagePerContext}.
+ *
+ * <p>In this case, the given parameter is always eligible for class inlining.
+ */
+ public boolean isBottom() {
+ return false;
+ }
+
+ /**
+ * Returns true if this is an instance of {@link UnknownParameterUsagePerContext}.
+ *
+ * <p>In this case, the given parameter is never eligible for class inlining.
+ */
+ public boolean isTop() {
+ return false;
+ }
+
+ ParameterUsagePerContext join(ParameterUsagePerContext parameterUsagePerContext) {
+ if (isTop() || parameterUsagePerContext.isTop()) {
+ return top();
+ }
+ return asKnown().join(parameterUsagePerContext.asKnown());
+ }
+
+ abstract ParameterUsagePerContext rebuild(
+ BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation);
+
+ static BottomParameterUsagePerContext bottom() {
+ return BottomParameterUsagePerContext.getInstance();
+ }
+
+ static UnknownParameterUsagePerContext top() {
+ return UnknownParameterUsagePerContext.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
new file mode 100644
index 0000000..0cb3a5e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+
+public abstract class ParameterUsages extends AbstractState<ParameterUsages> {
+
+ @Override
+ public ParameterUsages asAbstractState() {
+ return this;
+ }
+
+ public NonEmptyParameterUsages asNonEmpty() {
+ return null;
+ }
+
+ /**
+ * Prepares this instance for being stored in the optimization info. This converts instances
+ * inside this {@link ParameterUsages} instance that are not suitable for being stored in
+ * optimization info into instances that can be stored in the optimization info.
+ *
+ * <p>For example, converts instances of {@link InternalNonEmptyParameterUsage} to {@link
+ * NonEmptyParameterUsage}. This is needed because {@link InternalNonEmptyParameterUsage} is not
+ * suitable for being stored in {@link
+ * com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}, since it contains references to
+ * IR instructions.
+ */
+ abstract ParameterUsages externalize();
+
+ public abstract ParameterUsagePerContext get(int parameter);
+
+ public boolean isBottom() {
+ return false;
+ }
+
+ public boolean isTop() {
+ return false;
+ }
+
+ @Override
+ public ParameterUsages join(ParameterUsages state) {
+ if (isBottom()) {
+ return state;
+ }
+ if (state.isBottom()) {
+ return this;
+ }
+ if (isTop() || state.isTop()) {
+ return top();
+ }
+ return asNonEmpty().join(state.asNonEmpty());
+ }
+
+ abstract ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext);
+
+ public static BottomParameterUsages bottom() {
+ return BottomParameterUsages.getInstance();
+ }
+
+ public static UnknownParameterUsages top() {
+ return UnknownParameterUsages.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
new file mode 100644
index 0000000..8cbade6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -0,0 +1,386 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.MONITOR;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class TransferFunction implements AbstractTransferFunction<ParameterUsages> {
+
+ private static final AliasedValueConfiguration aliasedValueConfiguration =
+ AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final ProgramMethod method;
+
+ // The last argument instruction.
+ private final Argument lastArgument;
+
+ // Caches the parent or forwarding constructor call (only used in constructors).
+ private InvokeDirect constructorInvoke;
+
+ // The arguments that are considered by the analysis. We don't consider primitive arguments since
+ // they cannot be class inlined.
+ private Set<Value> argumentsOfInterest = Sets.newIdentityHashSet();
+
+ // Instructions that use one of the arguments. Instructions that don't use any of the arguments
+ // do not have any impact on the ability to class inline the arguments, therefore they are
+ // skipped.
+ private Set<Instruction> instructionsOfInterest = Sets.newIdentityHashSet();
+
+ TransferFunction(AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.method = method;
+ this.lastArgument = code.getLastArgument();
+ }
+
+ @Override
+ public TransferFunctionResult<ParameterUsages> apply(
+ Instruction instruction, ParameterUsages state) {
+ if (instruction.isArgument()) {
+ Argument argument = instruction.asArgument();
+ ParameterUsages result = analyzeArgument(argument, state);
+ // After analyzing the last argument instruction, only proceed if there is at least one
+ // argument that may be eligible for class inlining.
+ if (argument == lastArgument
+ && result.asNonEmpty().allMatch((context, usagePerContext) -> usagePerContext.isTop())) {
+ return new FailedTransferFunctionResult<>();
+ }
+ return result;
+ }
+ if (!instructionsOfInterest.contains(instruction)) {
+ // The instruction does not use any of the argument values that we are analyzing, so for the
+ // purpose of class inlining we can ignore this instruction.
+ return state;
+ }
+ assert !state.isBottom();
+ assert !state.isTop();
+ return apply(instruction, state.asNonEmpty());
+ }
+
+ private ParameterUsages apply(Instruction instruction, NonEmptyParameterUsages state) {
+ switch (instruction.opcode()) {
+ case ASSUME:
+ return analyzeAssume(instruction.asAssume(), state);
+ case CHECK_CAST:
+ return analyzeCheckCast(instruction.asCheckCast(), state);
+ case IF:
+ return analyzeIf(instruction.asIf(), state);
+ case INSTANCE_GET:
+ return analyzeInstanceGet(instruction.asInstanceGet(), state);
+ case INSTANCE_PUT:
+ return analyzeInstancePut(instruction.asInstancePut(), state);
+ case INVOKE_DIRECT:
+ return analyzeInvokeDirect(instruction.asInvokeDirect(), state);
+ case INVOKE_INTERFACE:
+ return analyzeInvokeInterface(instruction.asInvokeInterface(), state);
+ case INVOKE_STATIC:
+ return analyzeInvokeStatic(instruction.asInvokeStatic(), state);
+ case INVOKE_VIRTUAL:
+ return analyzeInvokeVirtual(instruction.asInvokeVirtual(), state);
+ case MONITOR:
+ return analyzeMonitor(instruction.asMonitor(), state);
+ case RETURN:
+ return analyzeReturn(instruction.asReturn(), state);
+ default:
+ return fail(instruction, state);
+ }
+ }
+
+ @Override
+ public ParameterUsages computeBlockEntryState(
+ BasicBlock block, BasicBlock predecessor, ParameterUsages predecessorExitState) {
+ // TODO(b/173337498): Fork a new `FIELD=x` analysis context for the successor block if the
+ // predecessor ends with an if or switch instruction, and the successor block is the
+ // `FIELD=x` target of the predecessor. To avoid an excessive number of contexts being
+ // created, only allow forking new contexts for $r8$classId fields synthesized by the
+ // horizontal class merger.
+ return predecessorExitState;
+ }
+
+ private ParameterUsages analyzeArgument(Argument argument, ParameterUsages state) {
+ // Only consider arguments that could store an instance eligible for class inlining. Note that
+ // we can't ignore parameters with a library type, since instances of program classes could
+ // still flow into such parameters.
+ Value value = argument.outValue();
+ if (!isMaybeEligibleForClassInlining(value.getType()) || value.hasPhiUsers()) {
+ return state.put(argument.getIndex(), ParameterUsagePerContext.top());
+ }
+
+ // Mark the users of this argument for analysis, and fork the analysis of this argument in the
+ // default analysis context.
+ argumentsOfInterest.add(value);
+ instructionsOfInterest.addAll(value.aliasedUsers(aliasedValueConfiguration));
+ return state.put(argument.getIndex(), NonEmptyParameterUsagePerContext.createInitial());
+ }
+
+ private ParameterUsages analyzeAssume(Assume assume, NonEmptyParameterUsages state) {
+ // Mark the value as ineligible for class inlining if it has phi users.
+ return assume.outValue().hasPhiUsers() ? fail(assume, state) : state;
+ }
+
+ private ParameterUsages analyzeCheckCast(CheckCast checkCast, NonEmptyParameterUsages state) {
+ // Mark the value as ineligible for class inlining if it has phi users.
+ return checkCast.outValue().hasPhiUsers() ? fail(checkCast, state) : state;
+ }
+
+ private ParameterUsages analyzeIf(If theIf, NonEmptyParameterUsages state) {
+ // Null/not-null tests are ok.
+ if (theIf.isZeroTest()) {
+ assert argumentsOfInterest.contains(theIf.lhs().getAliasedValue(aliasedValueConfiguration));
+ return state;
+ }
+
+ // For non-null tests, mark the inputs as ineligible for class inlining.
+ return fail(theIf, state);
+ }
+
+ private ParameterUsages analyzeInstanceGet(
+ InstanceGet instanceGet, NonEmptyParameterUsages state) {
+ // Instance field reads are OK, as long as the field resolves, since the class inliner will
+ // just replace the field read by the value of the field.
+ FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instanceGet.getField());
+ if (resolutionResult.isSuccessfulResolution()) {
+ // Record that the field is read from the parameter. For class inlining of singletons, this
+ // parameter is only eligible for class inlining if the singleton's field value is known.
+ return state.rebuildParameter(
+ instanceGet.object(),
+ (context, usage) -> usage.addFieldReadFromParameter(instanceGet.getField()));
+ }
+
+ return fail(instanceGet, state);
+ }
+
+ private ParameterUsages analyzeInstancePut(
+ InstancePut instancePut, NonEmptyParameterUsages state) {
+ // Instance field writes are OK, as long as the field resolves and the receiver is not being
+ // assigned (in that case the receiver escapes, and thus it is not eligible for class
+ // inlining).
+ Value valueRoot = instancePut.value().getAliasedValue(aliasedValueConfiguration);
+ if (isArgumentOfInterest(valueRoot)) {
+ state = state.abandonClassInliningInCurrentContexts(valueRoot);
+ }
+
+ Value objectRoot = instancePut.object().getAliasedValue(aliasedValueConfiguration);
+ if (!isArgumentOfInterest(objectRoot)) {
+ return state;
+ }
+
+ FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instancePut.getField());
+ if (resolutionResult.isSuccessfulResolution()) {
+ return state.rebuildParameter(objectRoot, (context, usage) -> usage.setParameterMutated());
+ } else {
+ return state.abandonClassInliningInCurrentContexts(objectRoot);
+ }
+ }
+
+ private ParameterUsages analyzeInvokeDirect(InvokeDirect invoke, NonEmptyParameterUsages state) {
+ // We generally don't class inline instances that escape through invoke-direct calls, but we
+ // make an exception for forwarding/parent constructor calls that does not leak the receiver.
+ state =
+ state.abandonClassInliningInCurrentContexts(
+ invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+ Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+ if (!isArgumentOfInterest(receiverRoot)) {
+ return state;
+ }
+
+ if (!receiverRoot.isThis()
+ || !method.getDefinition().isInstanceInitializer()
+ || !invoke.isInvokeConstructor(dexItemFactory)) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+ if (resolutionResult == null) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ InstanceInitializerInfo instanceInitializerInfo =
+ resolutionResult
+ .getResolvedMethod()
+ .getOptimizationInfo()
+ .getInstanceInitializerInfo(invoke);
+ if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ // We require that there is exactly one forwarding/parent constructor call.
+ if (constructorInvoke != null && constructorInvoke != invoke) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ constructorInvoke = invoke;
+ return state;
+ }
+
+ private ParameterUsages analyzeInvokeInterface(
+ InvokeInterface invoke, NonEmptyParameterUsages state) {
+ // We only allow invoke-interface instructions where the parameter is in the receiver position.
+ state =
+ state.abandonClassInliningInCurrentContexts(
+ invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+ Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+ if (!isArgumentOfInterest(receiverRoot)) {
+ return state;
+ }
+
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnInterface(invoke.getInvokedMethod()).asSingleResolution();
+ if (resolutionResult == null) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ return state.rebuildParameter(
+ receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke));
+ }
+
+ private ParameterUsages analyzeInvokeStatic(InvokeStatic invoke, NonEmptyParameterUsages state) {
+ // We generally don't class inline instances that escape through invoke-static calls, but we
+ // make an exception for calls to Objects.requireNonNull().
+ SingleResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+ .asSingleResolution();
+ if (resolutionResult != null
+ && resolutionResult.getResolvedMethod().getReference()
+ == dexItemFactory.objectsMethods.requireNonNull) {
+ return state;
+ }
+
+ return fail(invoke, state);
+ }
+
+ private ParameterUsages analyzeInvokeVirtual(
+ InvokeVirtual invoke, NonEmptyParameterUsages state) {
+ // We only allow invoke-virtual instructions where the parameter is in the receiver position.
+ state =
+ state.abandonClassInliningInCurrentContexts(
+ invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+ Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+ if (!isArgumentOfInterest(receiverRoot)) {
+ return state;
+ }
+
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+ if (resolutionResult == null) {
+ return state.abandonClassInliningInCurrentContexts(receiverRoot);
+ }
+
+ return state.rebuildParameter(
+ receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke));
+ }
+
+ private ParameterUsages analyzeMonitor(Monitor monitor, NonEmptyParameterUsages state) {
+ // Record that the receiver is used as a lock in each context that may reach this monitor
+ // instruction.
+ return state.rebuildParameter(
+ monitor.object(), (context, usage) -> usage.setParameterUsedAsLock());
+ }
+
+ private ParameterUsages analyzeReturn(Return theReturn, NonEmptyParameterUsages state) {
+ return state.rebuildParameter(
+ theReturn.returnValue(), (context, usage) -> usage.setParameterReturned());
+ }
+
+ private ParameterUsages fail(Instruction instruction, NonEmptyParameterUsages state) {
+ return state.abandonClassInliningInCurrentContexts(
+ instruction.inValues(), this::isArgumentOfInterest);
+ }
+
+ private boolean isArgumentOfInterest(Value value) {
+ assert value.getAliasedValue(aliasedValueConfiguration) == value;
+ return value.isArgument() && argumentsOfInterest.contains(value);
+ }
+
+ private boolean isMaybeEligibleForClassInlining(TypeElement type) {
+ if (!type.isClassType()) {
+ // Primitives and arrays will never be class inlined.
+ return false;
+ }
+ DexClass clazz = appView.definitionFor(type.asClassType().getClassType());
+ if (clazz == null) {
+ // We cannot class inline in presence of missing classes.
+ return false;
+ }
+ return clazz.isProgramClass()
+ ? isMaybeEligibleForClassInlining(clazz.asProgramClass())
+ : isMaybeEligibleForClassInlining(clazz.asClasspathOrLibraryClass());
+ }
+
+ private boolean isMaybeEligibleForClassInlining(DexProgramClass clazz) {
+ // We can only class inline parameters that does not inherit from other classpath or library
+ // classes than java.lang.Object.
+ DexType superType = clazz.getSuperType();
+ do {
+ DexClass superClass = appView.definitionFor(superType);
+ if (superClass == null) {
+ return false;
+ }
+ if (!superClass.isProgramClass()) {
+ return superClass.getType() == dexItemFactory.objectType;
+ }
+ superType = superClass.getSuperType();
+ } while (true);
+ }
+
+ private boolean isMaybeEligibleForClassInlining(ClasspathOrLibraryClass clazz) {
+ // We can only class inline a parameter that is either java.lang.Object or an interface type.
+ return clazz.getType() == dexItemFactory.objectType || clazz.isInterface();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
new file mode 100644
index 0000000..93577ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+class UnknownParameterUsage extends ParameterUsage {
+
+ private static final UnknownParameterUsage TOP = new UnknownParameterUsage();
+
+ private UnknownParameterUsage() {}
+
+ public static UnknownParameterUsage getInstance() {
+ return TOP;
+ }
+
+ @Override
+ UnknownParameterUsage addFieldReadFromParameter(DexField field) {
+ return this;
+ }
+
+ @Override
+ UnknownParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+ return this;
+ }
+
+ @Override
+ ParameterUsage externalize() {
+ return this;
+ }
+
+ @Override
+ public boolean isParameterMutated() {
+ return true;
+ }
+
+ @Override
+ public boolean isParameterReturned() {
+ return true;
+ }
+
+ @Override
+ public boolean isParameterUsedAsLock() {
+ return true;
+ }
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+
+ @Override
+ UnknownParameterUsage setParameterMutated() {
+ return this;
+ }
+
+ @Override
+ UnknownParameterUsage setParameterReturned() {
+ return this;
+ }
+
+ @Override
+ UnknownParameterUsage setParameterUsedAsLock() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java
new file mode 100644
index 0000000..4ead51d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+class UnknownParameterUsagePerContext extends ParameterUsagePerContext {
+
+ private static final UnknownParameterUsagePerContext INSTANCE =
+ new UnknownParameterUsagePerContext();
+
+ private UnknownParameterUsagePerContext() {}
+
+ static UnknownParameterUsagePerContext getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ ParameterUsagePerContext externalize() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsage get(AnalysisContext context) {
+ return ParameterUsage.top();
+ }
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+
+ @Override
+ ParameterUsagePerContext rebuild(
+ BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
new file mode 100644
index 0000000..13e32a7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, 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.classinliner.analysis;
+
+public class UnknownParameterUsages extends ParameterUsages {
+
+ private static final UnknownParameterUsages INSTANCE = new UnknownParameterUsages();
+
+ private UnknownParameterUsages() {}
+
+ static UnknownParameterUsages getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ ParameterUsages externalize() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsagePerContext get(int parameter) {
+ return ParameterUsagePerContext.top();
+ }
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+
+ @Override
+ ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == INSTANCE;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index c441134..6ddae8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class AlwaysFalseClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
@@ -13,17 +17,31 @@
private AlwaysFalseClassInlinerMethodConstraint() {}
- public static AlwaysFalseClassInlinerMethodConstraint getInstance() {
+ static AlwaysFalseClassInlinerMethodConstraint getInstance() {
return INSTANCE;
}
@Override
- public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
+ public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsage getParameterUsage(int parameter) {
+ return ParameterUsage.top();
+ }
+
+ @Override
+ public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
return false;
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
index 3488111..dc89cf4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class AlwaysTrueClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
@@ -13,17 +17,31 @@
private AlwaysTrueClassInlinerMethodConstraint() {}
- public static AlwaysTrueClassInlinerMethodConstraint getInstance() {
+ static AlwaysTrueClassInlinerMethodConstraint getInstance() {
return INSTANCE;
}
@Override
- public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
+ public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+ return this;
+ }
+
+ @Override
+ public ParameterUsage getParameterUsage(int parameter) {
+ return ParameterUsage.bottom();
+ }
+
+ @Override
+ public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
return true;
}
@Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context) {
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index e8ab3ab..22079fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,11 +4,31 @@
package com.android.tools.r8.ir.optimize.classinliner.constraint;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public interface ClassInlinerMethodConstraint {
- boolean isEligibleForNewInstanceClassInlining(ProgramMethod method);
+ ClassInlinerMethodConstraint fixupAfterRemovingThisParameter();
- boolean isEligibleForStaticGetClassInlining(ProgramMethod method);
+ ParameterUsage getParameterUsage(int parameter);
+
+ boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
+
+ boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context);
+
+ static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
+ return AlwaysFalseClassInlinerMethodConstraint.getInstance();
+ }
+
+ static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() {
+ return AlwaysTrueClassInlinerMethodConstraint.getInstance();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java
deleted file mode 100644
index fea67a4..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2021, 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.classinliner.constraint;
-
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-
-public class ClassInlinerMethodConstraintAnalysis {
-
- public static ClassInlinerMethodConstraint analyze(
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
- ParameterUsagesInfo parameterUsagesInfo) {
- boolean isEligibleForNewInstanceClassInlining =
- isEligibleForNewInstanceClassInlining(classInlinerEligibilityInfo);
- boolean isEligibleForStaticGetClassInlining =
- isEligibleForStaticGetClassInlining(classInlinerEligibilityInfo, parameterUsagesInfo);
- if (isEligibleForNewInstanceClassInlining) {
- if (isEligibleForStaticGetClassInlining) {
- return alwaysTrue();
- }
- return onlyNewInstanceClassInlining();
- }
- assert !isEligibleForStaticGetClassInlining;
- return alwaysFalse();
- }
-
- private static boolean isEligibleForNewInstanceClassInlining(
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo) {
- return classInlinerEligibilityInfo != null;
- }
-
- private static boolean isEligibleForStaticGetClassInlining(
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
- ParameterUsagesInfo parameterUsagesInfo) {
- if (classInlinerEligibilityInfo == null || parameterUsagesInfo == null) {
- return false;
- }
- if (classInlinerEligibilityInfo.hasMonitorOnReceiver) {
- // We will not be able to remove the monitor instruction afterwards.
- return false;
- }
- if (classInlinerEligibilityInfo.modifiesInstanceFields) {
- // The static instance could be accessed from elsewhere. Therefore, we cannot allow
- // side-effects to be removed and therefore cannot class inline method calls that modifies the
- // instance.
- return false;
- }
- ParameterUsage receiverUsage = parameterUsagesInfo.getParameterUsage(0);
- if (receiverUsage == null) {
- return false;
- }
- if (receiverUsage.hasFieldRead) {
- // We don't know the value of the field.
- return false;
- }
- return true;
- }
-
- private static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
- return AlwaysFalseClassInlinerMethodConstraint.getInstance();
- }
-
- private static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() {
- return AlwaysTrueClassInlinerMethodConstraint.getInstance();
- }
-
- private static OnlyNewInstanceClassInlinerMethodConstraint onlyNewInstanceClassInlining() {
- return OnlyNewInstanceClassInlinerMethodConstraint.getInstance();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
new file mode 100644
index 0000000..eb743cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2021, 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.classinliner.constraint;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsages;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+public class ConditionalClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
+
+ private final ParameterUsages usages;
+
+ public ConditionalClassInlinerMethodConstraint(ParameterUsages usages) {
+ assert !usages.isTop();
+ this.usages = usages;
+ }
+
+ @Override
+ public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+ if (usages.isBottom()) {
+ return this;
+ }
+ Int2ObjectMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>();
+ usages
+ .asNonEmpty()
+ .forEach(
+ (parameter, usagePerContext) -> {
+ if (parameter > 0) {
+ backing.put(parameter - 1, usagePerContext);
+ }
+ });
+ return new ConditionalClassInlinerMethodConstraint(NonEmptyParameterUsages.create(backing));
+ }
+
+ @Override
+ public ParameterUsage getParameterUsage(int parameter) {
+ AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+ return usages.get(parameter).get(defaultContext);
+ }
+
+ @Override
+ public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
+ AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+ ParameterUsage usage = usages.get(parameter).get(defaultContext);
+ return !usage.isTop();
+ }
+
+ @Override
+ public boolean isEligibleForStaticGetClassInlining(
+ AppView<AppInfoWithLiveness> appView,
+ int parameter,
+ ObjectState objectState,
+ ProgramMethod context) {
+ AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+ ParameterUsage usage = usages.get(parameter).get(defaultContext);
+ if (usage.isBottom()) {
+ return true;
+ }
+ if (usage.isTop()) {
+ return false;
+ }
+
+ NonEmptyParameterUsage knownUsage = usage.asNonEmpty();
+ if (knownUsage.isParameterMutated()) {
+ // The static instance could be accessed from elsewhere. Therefore, we cannot allow
+ // side-effects to be removed and therefore cannot class inline method calls that modifies the
+ // instance.
+ return false;
+ }
+ if (knownUsage.isParameterUsedAsLock()) {
+ // We will not be able to remove the monitor instruction afterwards.
+ return false;
+ }
+ for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) {
+ DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType());
+ DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder);
+ if (definition == null) {
+ return false;
+ }
+ AbstractValue abstractValue = objectState.getAbstractFieldValue(definition);
+ if (!abstractValue.isSingleConstValue()) {
+ return false;
+ }
+ SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+ if (!singleConstValue.isMaterializableInContext(appView, context)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java
deleted file mode 100644
index d3f35d4..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2021, 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.classinliner.constraint;
-
-import com.android.tools.r8.graph.ProgramMethod;
-
-public class OnlyNewInstanceClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
-
- private static final OnlyNewInstanceClassInlinerMethodConstraint INSTANCE =
- new OnlyNewInstanceClassInlinerMethodConstraint();
-
- private OnlyNewInstanceClassInlinerMethodConstraint() {}
-
- public static OnlyNewInstanceClassInlinerMethodConstraint getInstance() {
- return INSTANCE;
- }
-
- @Override
- public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
- return true;
- }
-
- @Override
- public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
- return false;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
index 76f63ac..4e01bc5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.ir.optimize.controlflow;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.LongInterval;
@@ -37,11 +39,17 @@
&& key == rootSwitchValue.definition.asConstString().getValue();
}
- public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+ public boolean switchCaseIsUnreachable(
+ Switch theSwitch, AbstractValue switchAbstractValue, int index) {
Value switchValue = theSwitch.value();
if (theSwitch.isIntSwitch()) {
- return switchValue.hasValueRange()
- && !switchValue.getValueRange().containsValue(theSwitch.asIntSwitch().getKey(index));
+ int key = theSwitch.asIntSwitch().getKey(index);
+ if (switchAbstractValue.isConstantOrNonConstantNumberValue()
+ && !switchAbstractValue.asConstantOrNonConstantNumberValue().containsInt(key)) {
+ return true;
+ }
+ // TODO(b/150836439): Reimplement using AbstractValue.
+ return switchValue.hasValueRange() && !switchValue.getValueRange().containsValue(key);
}
assert theSwitch.isStringSwitch();
@@ -51,4 +59,21 @@
return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
&& key != rootSwitchValue.definition.asConstString().getValue();
}
+
+ public boolean switchFallthroughIsNeverHit(Switch theSwitch, AbstractValue switchAbstractValue) {
+ if (theSwitch.isIntSwitch()) {
+ IntSwitch intSwitch = theSwitch.asIntSwitch();
+ if (switchAbstractValue.isConstantOrNonConstantNumberValue()) {
+ return switchAbstractValue
+ .asConstantOrNonConstantNumberValue()
+ .isSubsetOf(intSwitch.getKeys())
+ .isTrue();
+ }
+ return false;
+ }
+
+ assert theSwitch.isStringSwitch();
+
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
index 48e545c..c29dd86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.optimize.enums;
import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
@@ -24,7 +23,6 @@
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
@@ -34,7 +32,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Predicate;
public class UnboxedEnumMemberRelocator {
@@ -132,7 +129,7 @@
Set<DexProgramClass> relocatedEnums,
DirectMappedDexApplication.Builder appBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- DexProgramClass deterministicContext = findDeterministicContextType(contexts, alwaysTrue());
+ DexProgramClass deterministicContext = findDeterministicContextType(contexts);
String descriptorString = deterministicContext.getType().toDescriptorString();
String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
@@ -182,21 +179,13 @@
appView.dexItemFactory().getSkipNameValidationForTesting(),
DexProgramClass::checksumFromType);
appBuilder.addSynthesizedClass(syntheticClass);
- MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
- appView
- .appInfo()
- .addSynthesizedClass(
- syntheticClass, findDeterministicContextType(contexts, mainDexInfo::isMainDex));
+ appView.appInfo().addSynthesizedClass(syntheticClass, contexts);
return syntheticClass;
}
- private DexProgramClass findDeterministicContextType(
- Set<DexProgramClass> contexts, Predicate<DexProgramClass> predicate) {
+ private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
DexProgramClass deterministicContext = null;
for (DexProgramClass context : contexts) {
- if (!predicate.test(context)) {
- continue;
- }
if (deterministicContext == null) {
deterministicContext = context;
} else if (context.type.compareTo(deterministicContext.type) < 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index ded60a3..ab2b0ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -40,16 +39,21 @@
CallSiteOptimizationInfo join(
ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
- assert this.size == other.size;
+ assert size == other.size;
+ assert size == method.getNumberOfArguments();
boolean allowConstantPropagation =
appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
ConcreteCallSiteOptimizationInfo result =
- new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
+ new ConcreteCallSiteOptimizationInfo(size, allowConstantPropagation);
for (int i = 0; i < result.size; i++) {
if (allowConstantPropagation) {
assert result.constants != null;
AbstractValue abstractValue =
- getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i));
+ getAbstractArgumentValue(i)
+ .join(
+ other.getAbstractArgumentValue(i),
+ appView.abstractValueFactory(),
+ method.getArgumentType(i));
if (abstractValue.isNonTrivial()) {
result.constants.put(i, abstractValue);
}
@@ -94,11 +98,6 @@
public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
TypeElement[] staticTypes = getStaticTypes(appView, method);
for (int i = 0; i < size; i++) {
- ParameterUsage parameterUsage = method.getOptimizationInfo().getParameterUsages(i);
- // If the parameter is not used, passing accurate argument info doesn't matter.
- if (parameterUsage != null && parameterUsage.notUsed()) {
- continue;
- }
AbstractValue abstractValue = getAbstractArgumentValue(i);
if (abstractValue.isNonTrivial()) {
assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 45764df..d49189f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -12,10 +12,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -36,9 +33,7 @@
static ClassTypeElement UNKNOWN_CLASS_TYPE = null;
static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
- static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
- static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
@@ -77,7 +72,7 @@
@Override
public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
- return AlwaysFalseClassInlinerMethodConstraint.getInstance();
+ return ClassInlinerMethodConstraint.alwaysFalse();
}
@Override
@@ -106,12 +101,6 @@
}
@Override
- public ParameterUsage getParameterUsages(int parameter) {
- assert UNKNOWN_PARAMETER_USAGE_INFO == null;
- return null;
- }
-
- @Override
public BitSet getNonNullParamOrThrow() {
return NO_NULL_PARAMETER_OR_THROW_FACTS;
}
@@ -153,11 +142,6 @@
}
@Override
- public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
- return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
- }
-
- @Override
public AbstractValue getAbstractReturnValue() {
return UNKNOWN_ABSTRACT_RETURN_VALUE;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 7cced9d..bdc35f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,9 +10,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import java.util.BitSet;
@@ -47,8 +45,6 @@
public abstract ClassTypeElement getDynamicLowerBoundType();
- public abstract ParameterUsage getParameterUsages(int parameter);
-
public final boolean hasNonNullParamOrThrow() {
return getNonNullParamOrThrow() != null;
}
@@ -73,8 +69,6 @@
public abstract BridgeInfo getBridgeInfo();
- public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility();
-
public abstract Set<DexType> getInitializedClassesOnNormalExit();
public abstract InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 804b6b3..aa28923 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -29,7 +29,6 @@
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.MONITOR;
import static com.android.tools.r8.ir.code.Opcodes.MUL;
import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
@@ -47,10 +46,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
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;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -77,23 +74,17 @@
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraintAnalysis;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -103,19 +94,14 @@
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
-import java.util.ArrayList;
import java.util.BitSet;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
-import java.util.function.Predicate;
public class MethodOptimizationInfoCollector {
@@ -145,16 +131,11 @@
Timing timing) {
DexEncodedMethod definition = method.getDefinition();
identifyBridgeInfo(definition, code, feedback, timing);
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
- identifyClassInlinerEligibility(code, feedback, timing);
- ParameterUsagesInfo parameterUsagesInfo =
- identifyParameterUsages(definition, code, feedback, timing);
analyzeReturns(code, feedback, timing);
if (options.enableInlining) {
identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
}
- computeClassInlinerMethodConstraint(
- method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo, timing);
+ computeClassInlinerMethodConstraint(method, code, feedback, timing);
computeSimpleInliningConstraint(method, code, feedback, timing);
computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
@@ -173,201 +154,6 @@
timing.end();
}
- private ClassInlinerEligibilityInfo identifyClassInlinerEligibility(
- IRCode code, OptimizationFeedback feedback, Timing timing) {
- timing.begin("Identify class inliner eligibility");
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
- identifyClassInlinerEligibility(code, feedback);
- timing.end();
- return classInlinerEligibilityInfo;
- }
-
- private ClassInlinerEligibilityInfo identifyClassInlinerEligibility(
- IRCode code, OptimizationFeedback feedback) {
- // Method eligibility is calculated in similar way for regular method
- // and for the constructor. To be eligible method should only be using its
- // receiver in the following ways:
- //
- // (1) as a receiver of reads/writes of instance fields of the holder class,
- // (2) as a return value,
- // (3) as a receiver of a call to the superclass initializer. Note that we don't
- // check what is passed to superclass initializer as arguments, only check
- // that it is not the instance being initialized,
- // (4) as argument to a monitor instruction.
- //
- // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class
- // inline it, and hence any monitor instructions are no-ops.
- ProgramMethod context = code.context();
- DexEncodedMethod definition = context.getDefinition();
- boolean instanceInitializer = definition.isInstanceInitializer();
- if (definition.isNative()
- || (!definition.isNonAbstractVirtualMethod() && !instanceInitializer)) {
- return null;
- }
-
- feedback.setClassInlinerEligibility(definition, null); // To allow returns below.
-
- Value receiver = code.getThis();
- if (receiver.numberOfPhiUsers() > 0) {
- return null;
- }
-
- List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
- boolean seenSuperInitCall = false;
- boolean seenMonitor = false;
- boolean modifiesInstanceFields = false;
-
- AliasedValueConfiguration configuration =
- AssumeAndCheckCastAliasedValueConfiguration.getInstance();
- Predicate<Value> isReceiverAlias = value -> value.getAliasedValue(configuration) == receiver;
- for (Instruction insn : receiver.aliasedUsers(configuration)) {
- switch (insn.opcode()) {
- case ASSUME:
- case CHECK_CAST:
- case RETURN:
- break;
-
- case MONITOR:
- seenMonitor = true;
- break;
-
- case INSTANCE_GET:
- case INSTANCE_PUT:
- {
- if (insn.isInstancePut()) {
- InstancePut instancePutInstruction = insn.asInstancePut();
- // Only allow field writes to the receiver.
- if (!isReceiverAlias.test(instancePutInstruction.object())) {
- return null;
- }
- // Do not allow the receiver to escape via a field write.
- if (isReceiverAlias.test(instancePutInstruction.value())) {
- return null;
- }
- modifiesInstanceFields = true;
- }
- DexField field = insn.asFieldInstruction().getField();
- if (appView.appInfo().resolveField(field).isFailedOrUnknownResolution()) {
- return null;
- }
- break;
- }
-
- case INVOKE_DIRECT:
- {
- InvokeDirect invoke = insn.asInvokeDirect();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (dexItemFactory.isConstructor(invokedMethod)
- && invokedMethod.holder == context.getHolder().superType
- && ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) == 0
- && !seenSuperInitCall
- && instanceInitializer) {
- seenSuperInitCall = true;
- break;
- }
- // We don't support other direct calls yet.
- return null;
- }
-
- case INVOKE_STATIC:
- {
- InvokeStatic invoke = insn.asInvokeStatic();
- DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
- if (singleTarget == null) {
- return null; // Not allowed.
- }
- if (singleTarget.getReference() == dexItemFactory.objectsMethods.requireNonNull) {
- if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
- continue;
- }
- }
- return null;
- }
-
- case INVOKE_VIRTUAL:
- {
- InvokeVirtual invoke = insn.asInvokeVirtual();
- if (ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) != 0) {
- return null; // Not allowed.
- }
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType returnType = invokedMethod.proto.returnType;
- if (returnType.isClassType()
- && appView.appInfo().inSameHierarchy(returnType, context.getHolderType())) {
- return null; // Not allowed, could introduce an alias of the receiver.
- }
- callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
- }
- break;
-
- default:
- // Other receiver usages make the method not eligible.
- return null;
- }
- }
-
- if (instanceInitializer && !seenSuperInitCall) {
- // Call to super constructor not found?
- return null;
- }
-
- boolean synchronizedVirtualMethod = definition.isSynchronized() && definition.isVirtualMethod();
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
- new ClassInlinerEligibilityInfo(
- callsReceiver,
- new ClassInlinerReceiverAnalysis(appView, definition, code).computeReturnsReceiver(),
- seenMonitor || synchronizedVirtualMethod,
- modifiesInstanceFields);
- feedback.setClassInlinerEligibility(definition, classInlinerEligibilityInfo);
- return classInlinerEligibilityInfo;
- }
-
- private ParameterUsagesInfo identifyParameterUsages(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
- timing.begin("Identify parameter usages");
- ParameterUsagesInfo parameterUsagesInfo = identifyParameterUsages(method, code, feedback);
- timing.end();
- return parameterUsagesInfo;
- }
-
- private ParameterUsagesInfo identifyParameterUsages(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- List<ParameterUsage> usages = new ArrayList<>();
- List<Value> values = code.collectArguments();
- for (int i = 0; i < values.size(); i++) {
- Value value = values.get(i);
- ParameterUsage usage = collectParameterUsages(i, value);
- if (usage != null) {
- usages.add(usage);
- }
- }
- ParameterUsagesInfo parameterUsagesInfo =
- !usages.isEmpty() ? new ParameterUsagesInfo(usages) : null;
- feedback.setParameterUsages(method, parameterUsagesInfo);
- return parameterUsagesInfo;
- }
-
- private ParameterUsage collectParameterUsages(int i, Value root) {
- ParameterUsageBuilder builder = new ParameterUsageBuilder(root, i, dexItemFactory);
- WorkList<Value> worklist = WorkList.newIdentityWorkList();
- worklist.addIfNotSeen(root);
- while (worklist.hasNext()) {
- Value value = worklist.next();
- if (value.hasPhiUsers()) {
- return null;
- }
- for (Instruction user : value.uniqueUsers()) {
- if (!builder.note(user)) {
- return null;
- }
- if (user.isAssume()) {
- worklist.addIfNotSeen(user.outValue());
- }
- }
- }
- return builder.build();
- }
-
private void analyzeReturns(IRCode code, OptimizationFeedback feedback, Timing timing) {
timing.begin("Identify returns argument");
analyzeReturns(code, feedback);
@@ -982,24 +768,16 @@
ProgramMethod method,
IRCode code,
OptimizationFeedback feedback,
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
- ParameterUsagesInfo parameterUsagesInfo,
Timing timing) {
timing.begin("Compute class inlining constraint");
- computeClassInlinerMethodConstraint(
- method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo);
+ computeClassInlinerMethodConstraint(method, code, feedback);
timing.end();
}
private void computeClassInlinerMethodConstraint(
- ProgramMethod method,
- IRCode code,
- OptimizationFeedback feedback,
- ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
- ParameterUsagesInfo parameterUsagesInfo) {
+ ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
ClassInlinerMethodConstraint classInlinerMethodConstraint =
- ClassInlinerMethodConstraintAnalysis.analyze(
- classInlinerEligibilityInfo, parameterUsagesInfo);
+ ClassInlinerMethodConstraintAnalysis.analyze(appView, method, code);
feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 39f725d..df06045 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -263,12 +262,6 @@
}
@Override
- public synchronized void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
- getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility);
- }
-
- @Override
public synchronized void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -282,12 +275,6 @@
}
@Override
- public synchronized void setParameterUsages(
- DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
- getMethodOptimizationInfoForUpdating(method).setParameterUsages(parameterUsagesInfo);
- }
-
- @Override
public synchronized void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
getMethodOptimizationInfoForUpdating(method).setNonNullParamOrThrow(facts);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 3b46f4e..e862824 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -118,10 +117,6 @@
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {}
@Override
- public void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
-
- @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
@@ -130,10 +125,6 @@
public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {}
@Override
- public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
- }
-
- @Override
public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 2624486..3fb77d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -170,12 +169,6 @@
}
@Override
- public void setClassInlinerEligibility(
- DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
- // Ignored.
- }
-
- @Override
public void setInstanceInitializerInfoCollection(
DexEncodedMethod method,
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -190,11 +183,6 @@
}
@Override
- public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
- // Ignored.
- }
-
- @Override
public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
deleted file mode 100644
index 4389193..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2019, 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.info;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.If.Type;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Monitor;
-import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public final class ParameterUsagesInfo {
-
- private ImmutableList<ParameterUsage> parametersUsages;
-
- public ParameterUsagesInfo(List<ParameterUsage> usages) {
- assert !usages.isEmpty();
- parametersUsages = ImmutableList.copyOf(usages);
- assert parametersUsages.size() ==
- parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
- }
-
- public ParameterUsage getParameterUsage(int index) {
- for (ParameterUsage usage : parametersUsages) {
- if (usage.index == index) {
- return usage;
- }
- }
- return null;
- }
-
- ParameterUsagesInfo remove(int index) {
- assert parametersUsages.size() > 0;
- assert 0 <= index && index <= ListUtils.last(parametersUsages).index;
- ImmutableList.Builder<ParameterUsage> builder = ImmutableList.builder();
- for (ParameterUsage usage : parametersUsages) {
- // Once we remove or pass the designated index, copy-n-shift the remaining usages.
- if (index < usage.index) {
- builder.add(ParameterUsage.copyAndShift(usage, 1));
- } else if (index == usage.index) {
- // Do not add the parameter usage with the matched index.
- } else {
- // Until we meet the `parameter` of interest, keep copying.
- assert usage.index < index;
- builder.add(usage);
- }
- }
- ImmutableList<ParameterUsage> adjustedUsages = builder.build();
- if (adjustedUsages.isEmpty()) {
- return DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
- }
- return new ParameterUsagesInfo(adjustedUsages);
- }
-
- public final static class ParameterUsage {
-
- public final int index;
- public final Set<Type> ifZeroTest;
- public final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
-
- // If a field of this argument is assigned: arg.f = x.
- public final boolean hasFieldAssignment;
-
- // If a field of this argument is read: x = arg.f.
- public final boolean hasFieldRead;
-
- // If this argument is assigned to a field: x.f = arg.
- public final boolean isAssignedToField;
-
- // If this argument is returned: return arg.
- public final boolean isReturned;
-
- // If this argument is used in a monitor instruction.
- public final boolean isUsedInMonitor;
-
- ParameterUsage(
- int index,
- Set<Type> ifZeroTest,
- List<Pair<Invoke.Type, DexMethod>> callsReceiver,
- boolean hasFieldAssignment,
- boolean hasFieldRead,
- boolean isAssignedToField,
- boolean isReturned,
- boolean isUsedInMonitor) {
- this.index = index;
- this.ifZeroTest =
- ifZeroTest.isEmpty() ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest);
- this.callsReceiver =
- callsReceiver.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(callsReceiver);
- this.hasFieldAssignment = hasFieldAssignment;
- this.hasFieldRead = hasFieldRead;
- this.isAssignedToField = isAssignedToField;
- this.isReturned = isReturned;
- this.isUsedInMonitor = isUsedInMonitor;
- }
-
- static ParameterUsage copyAndShift(ParameterUsage original, int shift) {
- assert original.index >= shift;
- return new ParameterUsage(
- original.index - shift,
- original.ifZeroTest,
- original.callsReceiver,
- original.hasFieldAssignment,
- original.hasFieldRead,
- original.isAssignedToField,
- original.isReturned,
- original.isUsedInMonitor);
- }
-
- public boolean notUsed() {
- return (ifZeroTest == null || ifZeroTest.isEmpty())
- && (callsReceiver == null || callsReceiver.isEmpty())
- && !hasFieldAssignment
- && !hasFieldRead
- && !isAssignedToField
- && !isReturned
- && !isUsedInMonitor;
- }
- }
-
- public static class ParameterUsageBuilder {
-
- private final int index;
- private final Value arg;
- private final DexItemFactory dexItemFactory;
-
- private final Set<Type> ifZeroTestTypes = new HashSet<>();
- private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
-
- private boolean hasFieldAssignment = false;
- private boolean hasFieldRead = false;
- private boolean isAssignedToField = false;
- private boolean isReturned = false;
- private boolean isUsedInMonitor = false;
-
- ParameterUsageBuilder(Value arg, int index, DexItemFactory dexItemFactory) {
- this.arg = arg;
- this.index = index;
- this.dexItemFactory = dexItemFactory;
- }
-
- // Returns false if the instruction is not supported.
- public boolean note(Instruction instruction) {
- if (instruction.isAssume()) {
- // Keep examining other users if there are no phi users, but the param usage builder should
- // consider aliased users.
- return !instruction.outValue().hasPhiUsers();
- }
- if (instruction.isIf()) {
- return note(instruction.asIf());
- }
- if (instruction.isInstanceGet()) {
- return note(instruction.asInstanceGet());
- }
- if (instruction.isInstancePut()) {
- return note(instruction.asInstancePut());
- }
- if (instruction.isInvokeMethodWithReceiver()) {
- return note(instruction.asInvokeMethodWithReceiver());
- }
- if (instruction.isInvokeStatic()) {
- return note(instruction.asInvokeStatic());
- }
- if (instruction.isReturn()) {
- return note(instruction.asReturn());
- }
- if (instruction.isMonitor()) {
- return note(instruction.asMonitor());
- }
- return false;
- }
-
- public ParameterUsage build() {
- return new ParameterUsage(
- index,
- ifZeroTestTypes,
- callsOnReceiver,
- hasFieldAssignment,
- hasFieldRead,
- isAssignedToField,
- isReturned,
- isUsedInMonitor);
- }
-
- private boolean note(If ifInstruction) {
- if (ifInstruction.asIf().isZeroTest()) {
- assert ifInstruction.inValues().size() == 1
- && ifInstruction.inValues().get(0).getAliasedValue() == arg;
- ifZeroTestTypes.add(ifInstruction.asIf().getType());
- return true;
- }
- return false;
- }
-
- private boolean note(InstanceGet instanceGetInstruction) {
- assert arg != instanceGetInstruction.outValue();
- if (instanceGetInstruction.object().getAliasedValue() == arg) {
- hasFieldRead = true;
- return true;
- }
- return false;
- }
-
- private boolean note(InstancePut instancePutInstruction) {
- assert arg != instancePutInstruction.outValue();
- if (instancePutInstruction.object().getAliasedValue() == arg) {
- hasFieldAssignment = true;
- isAssignedToField |= instancePutInstruction.value().getAliasedValue() == arg;
- return true;
- }
- if (instancePutInstruction.value().getAliasedValue() == arg) {
- isAssignedToField = true;
- return true;
- }
- return false;
- }
-
- private boolean note(InvokeMethodWithReceiver invokeInstruction) {
- if (ListUtils.lastIndexMatching(
- invokeInstruction.inValues(), v -> v.getAliasedValue() == arg) == 0) {
- callsOnReceiver.add(
- new Pair<>(
- invokeInstruction.asInvokeMethodWithReceiver().getType(),
- invokeInstruction.asInvokeMethodWithReceiver().getInvokedMethod()));
- return true;
- }
- return false;
- }
-
- private boolean note(InvokeStatic invokeInstruction) {
- if (invokeInstruction.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
- if (!invokeInstruction.hasOutValue() || !invokeInstruction.outValue().hasAnyUsers()) {
- ifZeroTestTypes.add(Type.EQ);
- return true;
- }
- }
- return false;
- }
-
- private boolean note(Return returnInstruction) {
- assert returnInstruction.inValues().size() == 1
- && returnInstruction.inValues().get(0).getAliasedValue() == arg;
- isReturned = true;
- return true;
- }
-
- private boolean note(Monitor monitorInstruction) {
- assert monitorInstruction.inValues().size() == 1;
- assert monitorInstruction.inValues().get(0).getAliasedValue() == arg;
- isUsedInMonitor = true;
- return true;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 43667c8..d6bdd6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -15,10 +15,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -36,7 +33,7 @@
private AbstractValue abstractReturnValue =
DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
private ClassInlinerMethodConstraint classInlinerConstraint =
- AlwaysFalseClassInlinerMethodConstraint.getInstance();
+ ClassInlinerMethodConstraint.alwaysFalse();
private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
private ClassTypeElement returnsObjectWithLowerBoundType =
DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
@@ -44,12 +41,8 @@
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private BridgeInfo bridgeInfo = null;
- private ClassInlinerEligibilityInfo classInlinerEligibility =
- DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
private InstanceInitializerInfoCollection instanceInitializerInfoCollection =
InstanceInitializerInfoCollection.empty();
- private ParameterUsagesInfo parametersUsages =
- DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
// Stores information about nullability hint per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or using checkParameterIsNotNull) ensures
// the corresponding parameter is not null, or throws NPE before any other side effects.
@@ -148,9 +141,7 @@
inlining = template.inlining;
simpleInliningConstraint = template.simpleInliningConstraint;
bridgeInfo = template.bridgeInfo;
- classInlinerEligibility = template.classInlinerEligibility;
instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
- parametersUsages = template.parametersUsages;
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
}
@@ -277,11 +268,6 @@
}
@Override
- public ParameterUsage getParameterUsages(int parameter) {
- return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
- }
-
- @Override
public BitSet getNonNullParamOrThrow() {
return nonNullParamOrThrow;
}
@@ -331,11 +317,6 @@
}
@Override
- public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
- return classInlinerEligibility;
- }
-
- @Override
public AbstractValue getAbstractReturnValue() {
return abstractReturnValue;
}
@@ -380,10 +361,6 @@
return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
}
- void setParameterUsages(ParameterUsagesInfo parametersUsages) {
- this.parametersUsages = parametersUsages;
- }
-
void setNonNullParamOrThrow(BitSet facts) {
this.nonNullParamOrThrow = facts;
}
@@ -400,10 +377,6 @@
this.simpleInliningConstraint = constraint;
}
- void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) {
- this.classInlinerEligibility = eligibility;
- }
-
void setInstanceInitializerInfoCollection(
InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
this.instanceInitializerInfoCollection = instanceInitializerInfoCollection;
@@ -518,6 +491,7 @@
}
public void adjustOptimizationInfoAfterRemovingThisParameter() {
+ classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
// cannotBeKept: doesn't depend on `this`
// classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
@@ -550,18 +524,12 @@
// triggersClassInitBeforeAnySideEffect: code is not changed.
markTriggerClassInitBeforeAnySideEffect(
DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT);
- // classInlinerEligibility: chances are the method is not an instance method anymore.
- classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
// initializerInfo: the computed initializer info may become invalid.
instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty();
// initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
setFlag(
INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG,
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS);
- parametersUsages =
- parametersUsages == DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
- ? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
- : parametersUsages.remove(0);
nonNullParamOrThrow =
nonNullParamOrThrow == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 1141303..2de1eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
+import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -57,6 +58,16 @@
}
private void modelInstanceInitializers() {
+ DexEncodedMethod objectConstructor = lookupMethod(dexItemFactory.objectMembers.constructor);
+ if (objectConstructor != null) {
+ InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+ EmptyInstanceFieldInitializationInfoCollection.getInstance();
+ feedback.setInstanceInitializerInfoCollection(
+ objectConstructor,
+ InstanceInitializerInfoCollection.of(
+ NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos).build()));
+ }
+
EnumMembers enumMembers = dexItemFactory.enumMembers;
DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor);
if (enumConstructor != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index d5ef586..5a2f63a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
@@ -686,8 +687,9 @@
// 3. Rewrite methods referencing staticized members, also remove instance creation
//
public final void staticizeCandidates(
- OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
- new StaticizingProcessor(appView, this, converter).run(feedback, executorService);
+ OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
+ throws ExecutionException {
+ new StaticizingProcessor(appView, this, converter, applied).run(feedback, executorService);
}
private class CallSiteReferencesInvalidator extends UseRegistry {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 5f54ab6..27806d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
@@ -85,14 +86,17 @@
private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
+ private final GraphLens applied;
StaticizingProcessor(
AppView<AppInfoWithLiveness> appView,
ClassStaticizer classStaticizer,
- IRConverter converter) {
+ IRConverter converter,
+ GraphLens applied) {
this.appView = appView;
this.classStaticizer = classStaticizer;
this.converter = converter;
+ this.applied = applied;
}
final void run(OptimizationFeedback feedback, ExecutorService executorService)
@@ -233,7 +237,7 @@
LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
classStaticizer.referencedFrom.remove(info);
assert referencedFromBuilder != null;
- referencedFrom = referencedFromBuilder.build(appView);
+ referencedFrom = referencedFromBuilder.build(appView, applied);
materializedReferencedFromCollections.put(info, referencedFrom);
} else {
referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
index 0cafbcd..80066f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -6,10 +6,10 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunction;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Instruction;
@@ -42,7 +42,7 @@
Value builder, StringBuilderOptimizationConfiguration configuration) {
IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
new IntraproceduralDataflowAnalysis<>(
- AbstractStateImpl.bottom(), new TransferFunctionImpl(builder, configuration));
+ AbstractStateImpl.bottom(), new TransferFunction(builder, configuration));
DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
return result.isFailedAnalysisResult();
}
@@ -121,13 +121,12 @@
* <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
* bottom.
*/
- private static class TransferFunctionImpl implements TransferFunction<AbstractStateImpl> {
+ private static class TransferFunction implements AbstractTransferFunction<AbstractStateImpl> {
private final Value builder;
private final StringBuilderOptimizationConfiguration configuration;
- private TransferFunctionImpl(
- Value builder, StringBuilderOptimizationConfiguration configuration) {
+ private TransferFunction(Value builder, StringBuilderOptimizationConfiguration configuration) {
this.builder = builder;
this.configuration = configuration;
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java
new file mode 100644
index 0000000..77462a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class CallObjectInitCfCodeProvider extends SyntheticCfCodeProvider {
+
+ public CallObjectInitCfCodeProvider(AppView<?> appView, DexType holder) {
+ super(appView, holder);
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ DexItemFactory factory = appView.dexItemFactory();
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, factory.objectMembers.constructor, false));
+ instructions.add(new CfReturnVoid());
+ return standardCfCodeFromInstructions(instructions);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
index b02544d..1f3c6d0 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -89,6 +89,7 @@
// Load the argument.
ValueType fieldType = ValueType.fromDexType(field.getType());
instructions.add(new CfLoad(fieldType, maxLocals));
+ maxStack += fieldType.requiredRegisters();
maxLocals += fieldType.requiredRegisters();
}
@@ -102,6 +103,7 @@
instructions.add(new CfReturnVoid());
} else {
ValueType fieldType = ValueType.fromDexType(field.getType());
+ maxStack = Math.max(fieldType.requiredRegisters(), maxStack);
instructions.add(new CfReturn(fieldType));
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 84acf70..0c6b359 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -182,6 +182,13 @@
maybeInsertArgumentCast(i, parameter, instructions);
}
instructions.add(new CfInvoke(getInvokeOpcode(), targetMethod, isInterface));
+ if (!targetMethod.getReturnType().isVoidType()) {
+ // If the return type is not void, it will push a value on the stack. We subtract the
+ // arguments pushed by the invoke to see if bumping the stack height is necessary.
+ maxStack =
+ Math.max(
+ maxStack, ValueType.fromDexType(targetMethod.getReturnType()).requiredRegisters());
+ }
if (isSourceReturnVoid()) {
assert !isConstructorDelegate;
instructions.add(new CfReturnVoid());
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
new file mode 100644
index 0000000..83fa84e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, 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.naming;
+
+import com.android.tools.r8.graph.AppView;
+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.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.IdentityHashMap;
+
+// Naming lens for rewriting java.lang.Record to the internal RecordTag type.
+public class RecordRewritingNamingLens extends NonIdentityNamingLens {
+
+ final NamingLens namingLens;
+ private final DexItemFactory factory;
+
+ public static NamingLens createRecordRewritingNamingLens(AppView<?> appView) {
+ return createRecordRewritingNamingLens(appView, NamingLens.getIdentityLens());
+ }
+
+ public static NamingLens createRecordRewritingNamingLens(
+ AppView<?> appView, NamingLens namingLens) {
+ if (!appView.options().shouldDesugarRecords()) {
+ return namingLens;
+ }
+ return new RecordRewritingNamingLens(namingLens, appView);
+ }
+
+ public RecordRewritingNamingLens(NamingLens namingLens, AppView<?> appView) {
+ super(appView.dexItemFactory(), new IdentityHashMap<>());
+ this.namingLens = namingLens;
+ factory = appView.dexItemFactory();
+ }
+
+ private boolean isRenamed(DexType type) {
+ return getRenaming(type) != null;
+ }
+
+ private DexString getRenaming(DexType type) {
+ if (type == factory.recordType) {
+ return factory.r8RecordType.descriptor;
+ }
+ return null;
+ }
+
+ @Override
+ protected DexString internalLookupClassDescriptor(DexType type) {
+ DexString renaming = getRenaming(type);
+ return renaming != null ? renaming : namingLens.lookupDescriptor(type);
+ }
+
+ @Override
+ public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
+ assert !isRenamed(attribute.getInner());
+ return namingLens.lookupInnerName(attribute, options);
+ }
+
+ @Override
+ public DexString lookupName(DexMethod method) {
+ // Record rewriting does not influence method name.
+ return namingLens.lookupName(method);
+ }
+
+ @Override
+ public DexString lookupName(DexField field) {
+ // Record rewriting does not influence field name.
+ return namingLens.lookupName(field);
+ }
+
+ @Override
+ public DexString lookupDescriptorForJavaTypeName(String typeName) {
+ if (typeName.equals(factory.recordType.toSourceString())) {
+ return factory.r8RecordType.descriptor;
+ }
+ return namingLens.lookupDescriptorForJavaTypeName(typeName);
+ }
+
+ @Override
+ public String lookupPackageName(String packageName) {
+ return namingLens.lookupPackageName(packageName);
+ }
+
+ @Override
+ public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
+ return namingLens.verifyRenamingConsistentWithResolution(item);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index cdfa649..056abce 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -33,9 +33,11 @@
import com.google.common.collect.HashBiMap;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -130,11 +132,13 @@
}
BiMap<DexType, DexType> mappings = HashBiMap.create();
+ Map<String, String> packageMappings = new HashMap<>();
Set<String> seenPackageDescriptors = new HashSet<>();
ProgramPackageCollection packages =
SortedProgramPackageCollection.createWithAllProgramClasses(appView);
- processPackagesInDesiredLocation(packages, mappings, seenPackageDescriptors);
- processRemainingPackages(packages, mappings, seenPackageDescriptors, executorService);
+ processPackagesInDesiredLocation(packages, mappings, packageMappings, seenPackageDescriptors);
+ processRemainingPackages(
+ packages, mappings, packageMappings, seenPackageDescriptors, executorService);
mappings.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
if (mappings.isEmpty()) {
return null;
@@ -146,7 +150,7 @@
new ArrayList<>(
repackagingTreeFixer.fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
appBuilder.replaceProgramClasses(newProgramClasses);
- RepackagingLens lens = lensBuilder.build(appView);
+ RepackagingLens lens = lensBuilder.build(appView, packageMappings);
new AnnotationFixer(lens).run(appBuilder.getProgramClasses());
return lens;
}
@@ -191,6 +195,7 @@
private void processPackagesInDesiredLocation(
ProgramPackageCollection packages,
BiMap<DexType, DexType> mappings,
+ Map<String, String> packageMappings,
Set<String> seenPackageDescriptors) {
// For each package that is already in the desired location, record all the classes from the
// package in the mapping for collision detection.
@@ -208,6 +213,7 @@
for (DexProgramClass alreadyRepackagedClass : pkg) {
processClass(alreadyRepackagedClass, pkg, newPackageDescriptor, mappings);
}
+ packageMappings.put(pkg.getPackageDescriptor(), newPackageDescriptor);
seenPackageDescriptors.add(newPackageDescriptor);
iterator.remove();
}
@@ -217,6 +223,7 @@
private void processRemainingPackages(
ProgramPackageCollection packages,
BiMap<DexType, DexType> mappings,
+ Map<String, String> packageMappings,
Set<String> seenPackageDescriptors,
ExecutorService executorService)
throws ExecutionException {
@@ -228,13 +235,20 @@
repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
assert !pkg.getPackageDescriptor().equals(newPackageDescriptor);
- Iterable<DexProgramClass> classesToRepackage =
+ Collection<DexProgramClass> classesToRepackage =
computeClassesToRepackage(pkg, executorService);
for (DexProgramClass classToRepackage : classesToRepackage) {
processClass(classToRepackage, pkg, newPackageDescriptor, mappings);
}
-
seenPackageDescriptors.add(newPackageDescriptor);
+ // Package remapping is used for adapting resources. If we cannot repackage all classes in
+ // a package then we put in the original descriptor to ensure that resources are not
+ // rewritten.
+ packageMappings.put(
+ pkg.getPackageDescriptor(),
+ classesToRepackage.size() == pkg.classesInPackage().size()
+ ? newPackageDescriptor
+ : pkg.getPackageDescriptor());
// TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
// for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
}
@@ -269,12 +283,12 @@
classToRepackage, outerClass, newPackageDescriptor, mappings));
}
- private Iterable<DexProgramClass> computeClassesToRepackage(
+ private Collection<DexProgramClass> computeClassesToRepackage(
ProgramPackage pkg, ExecutorService executorService) throws ExecutionException {
RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg);
boolean canRepackageAllClasses = constraintGraph.initializeGraph();
if (canRepackageAllClasses) {
- return pkg;
+ return pkg.classesInPackage();
}
constraintGraph.populateConstraints(executorService);
return constraintGraph.computeClassesToRepackage();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index ab5b01b..dd6af4a 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -172,7 +173,7 @@
}
}
- public Iterable<DexProgramClass> computeClassesToRepackage() {
+ public Collection<DexProgramClass> computeClassesToRepackage() {
WorkList<Node> worklist = WorkList.newIdentityWorkList(pinnedNodes);
while (worklist.hasNext()) {
Node pinnedNode = worklist.next();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index 63b8d06..e6d16eb 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -18,16 +18,19 @@
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import java.util.Map;
public class RepackagingLens extends NestedGraphLens {
private final BiMap<DexType, DexType> originalTypes;
+ private final Map<String, String> packageRenamings;
private RepackagingLens(
AppView<AppInfoWithLiveness> appView,
BidirectionalOneToOneMap<DexField, DexField> newFieldSignatures,
BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
- BiMap<DexType, DexType> originalTypes) {
+ BiMap<DexType, DexType> originalTypes,
+ Map<String, String> packageRenamings) {
super(
originalTypes.inverse(),
originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
@@ -36,6 +39,12 @@
appView.graphLens(),
appView.dexItemFactory());
this.originalTypes = originalTypes;
+ this.packageRenamings = packageRenamings;
+ }
+
+ @Override
+ public String lookupPackageName(String pkg) {
+ return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg);
}
@Override
@@ -97,10 +106,11 @@
originalTypes.put(to, from);
}
- public RepackagingLens build(AppView<AppInfoWithLiveness> appView) {
+ public RepackagingLens build(
+ AppView<AppInfoWithLiveness> appView, Map<String, String> packageRenamings) {
assert !originalTypes.isEmpty();
return new RepackagingLens(
- appView, newFieldSignatures, originalMethodSignatures, originalTypes);
+ appView, newFieldSignatures, originalMethodSignatures, originalTypes, packageRenamings);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 3c83acd..f032832 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -77,13 +77,17 @@
| Constants.ACC_FINAL
| Constants.ACC_PUBLIC
| Constants.ACC_STATIC);
+ boolean deprecated = false;
+ boolean d8R8Synthesized = true;
encodedClinitField =
new DexEncodedField(
appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name),
accessFlags,
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
- null);
+ null,
+ deprecated,
+ d8R8Synthesized);
clazz.appendStaticField(encodedClinitField);
}
lensBuilder.map(type, encodedClinitField.field);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index fd3c069..e818526 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1695,7 +1695,7 @@
assert !mode.isFinalMainDexTracing()
|| !options.testing.checkForNotExpandingMainDexTracingResult
- || appView.appInfo().getMainDexInfo().isTracedRoot(clazz)
+ || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
|| clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
: "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index a60967f..96e263c 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.shaking.MainDexInfo.MainDexGroup.MAIN_DEX_ROOT;
import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
-import static com.android.tools.r8.utils.PredicateUtils.not;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexMethod;
@@ -17,13 +16,11 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.ConsumerUtils;
-import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.Collections;
-import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class MainDexInfo {
@@ -34,7 +31,6 @@
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
- Collections.emptySet(),
false);
public enum MainDexGroup {
@@ -46,7 +42,6 @@
// Specific set of classes specified to be in main-dex
private final Set<DexType> classList;
- private final Map<DexType, DexType> synthesizedClassesMap;
// Traced roots are traced main dex references.
private final Set<DexType> tracedRoots;
// Traced method roots are the methods visited from an initial main dex root set. The set is
@@ -64,7 +59,6 @@
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
- /* synthesized classes - cannot be emptyset */ Sets.newIdentityHashSet(),
false);
}
@@ -73,17 +67,18 @@
Set<DexType> tracedRoots,
Set<DexMethod> tracedMethodRoots,
Set<DexType> tracedDependencies,
- Set<DexType> synthesizedClasses,
boolean tracedMethodRootsCleared) {
this.classList = classList;
this.tracedRoots = tracedRoots;
this.tracedMethodRoots = tracedMethodRoots;
this.tracedDependencies = tracedDependencies;
- this.synthesizedClassesMap = new ConcurrentHashMap<>();
this.tracedMethodRootsCleared = tracedMethodRootsCleared;
- synthesizedClasses.forEach(type -> synthesizedClassesMap.put(type, type));
assert tracedDependencies.stream().noneMatch(tracedRoots::contains);
- assert tracedRoots.containsAll(synthesizedClasses);
+ }
+
+ // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+ public boolean isSyntheticContextOnMainDexList(DexType syntheticContextType) {
+ return classList.contains(syntheticContextType);
}
public boolean isNone() {
@@ -91,25 +86,16 @@
return this == NONE;
}
- public boolean isMainDexTypeThatShouldIncludeDependencies(DexType type) {
- // Dependencies of 'type' are only needed if 'type' is a direct/executed main-dex type.
- return classList.contains(type) || tracedRoots.contains(type);
+ public boolean isFromList(ProgramDefinition definition, SyntheticItems synthetics) {
+ return isFromList(definition.getContextType(), synthetics);
}
- public boolean isMainDex(ProgramDefinition definition) {
- return isFromList(definition) || isTracedRoot(definition) || isDependency(definition);
+ private boolean isFromList(DexReference reference, SyntheticItems synthetics) {
+ return isContainedOrHasContainedContext(reference, classList, synthetics);
}
- public boolean isFromList(ProgramDefinition definition) {
- return isFromList(definition.getContextType());
- }
-
- private boolean isFromList(DexReference reference) {
- return classList.contains(reference.getContextType());
- }
-
- public boolean isTracedRoot(ProgramDefinition definition) {
- return isTracedRoot(definition.getContextType());
+ public boolean isTracedRoot(ProgramDefinition definition, SyntheticItems synthetics) {
+ return isTracedRoot(definition.getContextType(), synthetics);
}
public boolean isTracedMethodRoot(DexMethod method) {
@@ -117,8 +103,22 @@
return tracedMethodRoots.contains(method);
}
- private boolean isTracedRoot(DexReference reference) {
- return tracedRoots.contains(reference.getContextType());
+ private boolean isTracedRoot(DexReference reference, SyntheticItems synthetics) {
+ return isContainedOrHasContainedContext(reference, tracedRoots, synthetics);
+ }
+
+ private boolean isContainedOrHasContainedContext(
+ DexReference reference, Set<DexType> items, SyntheticItems synthetics) {
+ if (items.isEmpty()) {
+ return false;
+ }
+ DexType type = reference.getContextType();
+ for (DexType context : synthetics.getSynthesizingContexts(type)) {
+ if (items.contains(context)) {
+ return true;
+ }
+ }
+ return items.contains(type);
}
private boolean isDependency(ProgramDefinition definition) {
@@ -138,8 +138,9 @@
this.tracedMethodRoots = Sets.newIdentityHashSet();
}
- public boolean canRebindReference(ProgramMethod context, DexReference referenceToTarget) {
- MainDexGroup holderGroup = getMainDexGroupInternal(context);
+ public boolean canRebindReference(
+ ProgramMethod context, DexReference referenceToTarget, SyntheticItems synthetics) {
+ MainDexGroup holderGroup = getMainDexGroupInternal(context, synthetics);
if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX
|| holderGroup == MainDexGroup.MAIN_DEX_DEPENDENCY) {
// We are always free to rebind/inline into something not in main-dex or traced dependencies.
@@ -152,20 +153,21 @@
}
assert holderGroup == MAIN_DEX_ROOT;
// Otherwise we allow if either is both root.
- return getMainDexGroupInternal(referenceToTarget) == MAIN_DEX_ROOT;
+ return getMainDexGroupInternal(referenceToTarget, synthetics) == MAIN_DEX_ROOT;
}
- public boolean canMerge(ProgramDefinition candidate) {
- return !isFromList(candidate);
+ public boolean canMerge(ProgramDefinition candidate, SyntheticItems synthetics) {
+ return !isFromList(candidate, synthetics);
}
- public boolean canMerge(ProgramDefinition source, ProgramDefinition target) {
- return canMerge(source.getContextType(), target.getContextType());
+ public boolean canMerge(
+ ProgramDefinition source, ProgramDefinition target, SyntheticItems synthetics) {
+ return canMerge(source.getContextType(), target.getContextType(), synthetics);
}
- private boolean canMerge(DexReference source, DexReference target) {
- MainDexGroup sourceGroup = getMainDexGroupInternal(source);
- MainDexGroup targetGroup = getMainDexGroupInternal(target);
+ private boolean canMerge(DexReference source, DexReference target, SyntheticItems synthetics) {
+ MainDexGroup sourceGroup = getMainDexGroupInternal(source, synthetics);
+ MainDexGroup targetGroup = getMainDexGroupInternal(target, synthetics);
if (sourceGroup != targetGroup) {
return false;
}
@@ -174,20 +176,22 @@
return sourceGroup != MainDexGroup.MAIN_DEX_LIST;
}
- public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate) {
- MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate);
+ public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate, SyntheticItems synthetics) {
+ assert canMerge(mergeCandidate, synthetics);
+ MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate, synthetics);
return mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST ? null : mainDexGroupInternal;
}
- private MainDexGroup getMainDexGroupInternal(ProgramDefinition definition) {
- return getMainDexGroupInternal(definition.getReference());
+ private MainDexGroup getMainDexGroupInternal(
+ ProgramDefinition definition, SyntheticItems synthetics) {
+ return getMainDexGroupInternal(definition.getReference(), synthetics);
}
- private MainDexGroup getMainDexGroupInternal(DexReference reference) {
- if (isFromList(reference)) {
+ private MainDexGroup getMainDexGroupInternal(DexReference reference, SyntheticItems synthetics) {
+ if (isFromList(reference, synthetics)) {
return MainDexGroup.MAIN_DEX_LIST;
}
- if (isTracedRoot(reference)) {
+ if (isTracedRoot(reference, synthetics)) {
return MAIN_DEX_ROOT;
}
if (isDependency(reference)) {
@@ -197,22 +201,25 @@
}
public boolean disallowInliningIntoContext(
- AppInfoWithClassHierarchy appInfo, ProgramDefinition context, ProgramMethod method) {
+ AppInfoWithClassHierarchy appInfo,
+ ProgramDefinition context,
+ ProgramMethod method,
+ SyntheticItems synthetics) {
if (context.getContextType() == method.getContextType()) {
return false;
}
- MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context);
+ MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context, synthetics);
if (mainDexGroupInternal == MainDexGroup.NOT_IN_MAIN_DEX
|| mainDexGroupInternal == MainDexGroup.MAIN_DEX_DEPENDENCY) {
return false;
}
if (mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST) {
return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
- appInfo, method, not(this::isFromList));
+ appInfo, method, t -> !isFromList(t, synthetics));
}
assert mainDexGroupInternal == MAIN_DEX_ROOT;
return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
- appInfo, method, not(this::isTracedRoot));
+ appInfo, method, t -> !isTracedRoot(t, synthetics));
}
public boolean isEmpty() {
@@ -224,35 +231,6 @@
return NONE;
}
- // TODO(b/178127572): This mutates the MainDexClasses which otherwise should be immutable.
- public void addSyntheticClass(DexProgramClass clazz) {
- // TODO(b/178127572): This will add a synthesized type as long as the initial set is not empty.
- // A better approach would be to use the context for the synthetic with a containment check.
- assert !isNone();
- if (!classList.isEmpty()) {
- synthesizedClassesMap.computeIfAbsent(
- clazz.type,
- type -> {
- classList.add(type);
- return type;
- });
- }
- if (!tracedRoots.isEmpty()) {
- synthesizedClassesMap.computeIfAbsent(
- clazz.type,
- type -> {
- tracedRoots.add(type);
- return type;
- });
- }
- }
-
- public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) {
- if (isTracedRoot(context) || isFromList(context) || isDependency(context)) {
- addSyntheticClass(clazz);
- }
- }
-
public int size() {
return classList.size() + tracedRoots.size() + tracedDependencies.size();
}
@@ -278,11 +256,7 @@
}
Set<DexType> removedClasses = prunedItems.getRemovedClasses();
Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
- Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
classList.forEach(type -> ifNotRemoved(type, removedClasses, modifiedClassList::add));
- synthesizedClassesMap
- .keySet()
- .forEach(type -> ifNotRemoved(type, removedClasses, modifiedSynthesized::add));
MainDexInfo.Builder builder = builder();
tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot));
// TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has
@@ -292,7 +266,7 @@
ifNotRemoved(
method.getHolderType(), removedClasses, ignored -> builder.addRoot(method)));
tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency));
- return builder.build(modifiedClassList, modifiedSynthesized);
+ return builder.build(modifiedClassList);
}
private void ifNotRemoved(
@@ -304,10 +278,8 @@
public MainDexInfo rewrittenWithLens(GraphLens lens) {
Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
- Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
classList.forEach(
type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add));
- synthesizedClassesMap.keySet().forEach(type -> modifiedSynthesized.add(lens.lookupType(type)));
MainDexInfo.Builder builder = builder();
tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot));
tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method)));
@@ -321,7 +293,7 @@
rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency);
}
});
- return builder.build(modifiedClassList, modifiedSynthesized);
+ return builder.build(modifiedClassList);
}
public Builder builder() {
@@ -413,20 +385,27 @@
return new MainDexInfo(list);
}
- public MainDexInfo build(Set<DexType> classList, Set<DexType> synthesizedClasses) {
+ public MainDexInfo build(Set<DexType> classList) {
// Class can contain dependencies which we should not regard as roots.
assert list.isEmpty();
- return new MainDexInfo(
- classList,
- SetUtils.unionIdentityHashSet(roots, synthesizedClasses),
- methodRoots,
- Sets.difference(dependencies, synthesizedClasses),
- synthesizedClasses,
- tracedMethodRootsCleared);
+ return new MainDexInfo(classList, roots, methodRoots, dependencies, tracedMethodRootsCleared);
}
public MainDexInfo build(MainDexInfo previous) {
- return build(previous.classList, previous.synthesizedClassesMap.keySet());
+ return build(previous.classList);
}
+
+ public MainDexInfo build() {
+ return new MainDexInfo(list, roots, methodRoots, dependencies, tracedMethodRootsCleared);
+ }
+ }
+
+ public Builder builderFromCopy() {
+ Builder builder = new Builder(tracedMethodRootsCleared);
+ builder.list.addAll(classList);
+ builder.roots.addAll(tracedRoots);
+ builder.methodRoots.addAll(tracedMethodRoots);
+ builder.dependencies.addAll(tracedDependencies);
+ return builder;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index afc9e50..761b314 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -826,7 +826,7 @@
}
// Check with main dex classes to see if we are allowed to merge.
- if (!mainDexInfo.canMerge(clazz, targetClass)) {
+ if (!mainDexInfo.canMerge(clazz, targetClass, appView.getSyntheticItems())) {
return;
}
@@ -1657,7 +1657,8 @@
}
// Constructors can have references beyond the root main dex classes. This can increase the
// size of the main dex dependent classes and we should bail out.
- if (mainDexInfo.disallowInliningIntoContext(appView.appInfo(), context, method)) {
+ if (mainDexInfo.disallowInliningIntoContext(
+ appView.appInfo(), context, method, appView.getSyntheticItems())) {
return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
}
return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index 2681a90..adc1298 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -56,7 +56,7 @@
@Deprecated
public Collection<DexType> getLegacySyntheticTypes() {
- return committed.getLegacyTypes();
+ return committed.getLegacyTypes().keySet();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 053b20b..7dbdec5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -4,12 +4,11 @@
package com.android.tools.r8.synthesis;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.utils.BooleanBox;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Map;
@@ -29,7 +28,7 @@
private ImmutableMap.Builder<DexType, SyntheticProgramClassReference> newNonLegacyClasses =
null;
private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
- private ImmutableSet.Builder<DexType> newLegacyClasses = null;
+ private ImmutableMap.Builder<DexType, LegacySyntheticReference> newLegacyClasses = null;
public Builder(CommittedSyntheticsCollection parent) {
this.parent = parent;
@@ -66,19 +65,19 @@
return this;
}
- public Builder addLegacyClasses(Collection<DexProgramClass> classes) {
+ public Builder addLegacyClasses(Map<DexType, LegacySyntheticDefinition> classes) {
if (newLegacyClasses == null) {
- newLegacyClasses = ImmutableSet.builder();
+ newLegacyClasses = ImmutableMap.builder();
}
- classes.forEach(c -> newLegacyClasses.add(c.getType()));
+ classes.forEach((type, item) -> newLegacyClasses.put(type, item.toReference()));
return this;
}
- public Builder addLegacyClass(DexType type) {
+ public Builder addLegacyClass(LegacySyntheticReference item) {
if (newLegacyClasses == null) {
- newLegacyClasses = ImmutableSet.builder();
+ newLegacyClasses = ImmutableMap.builder();
}
- newLegacyClasses.add(type);
+ newLegacyClasses.put(item.getHolder(), item);
return this;
}
@@ -94,24 +93,24 @@
newNonLegacyMethods == null
? parent.nonLegacyMethods
: newNonLegacyMethods.putAll(parent.nonLegacyMethods).build();
- ImmutableSet<DexType> allLegacyClasses =
+ ImmutableMap<DexType, LegacySyntheticReference> allLegacyClasses =
newLegacyClasses == null
? parent.legacyTypes
- : newLegacyClasses.addAll(parent.legacyTypes).build();
+ : newLegacyClasses.putAll(parent.legacyTypes).build();
return new CommittedSyntheticsCollection(
allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses);
}
}
private static final CommittedSyntheticsCollection EMPTY =
- new CommittedSyntheticsCollection(ImmutableSet.of(), ImmutableMap.of(), ImmutableMap.of());
+ new CommittedSyntheticsCollection(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of());
/**
* Immutable set of synthetic types in the application (eg, committed).
*
* <p>TODO(b/158159959): Remove legacy support.
*/
- private final ImmutableSet<DexType> legacyTypes;
+ private final ImmutableMap<DexType, LegacySyntheticReference> legacyTypes;
/** Mapping from synthetic type to its synthetic method item description. */
private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods;
@@ -120,14 +119,16 @@
private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses;
public CommittedSyntheticsCollection(
- ImmutableSet<DexType> legacyTypes,
+ ImmutableMap<DexType, LegacySyntheticReference> legacyTypes,
ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses) {
this.legacyTypes = legacyTypes;
this.nonLegacyMethods = nonLegacyMethods;
this.nonLegacyClasses = nonLegacyClasses;
assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
- == Sets.union(Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), legacyTypes)
+ == Sets.union(
+ Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()),
+ legacyTypes.keySet())
.size();
}
@@ -148,14 +149,14 @@
}
public boolean containsLegacyType(DexType type) {
- return legacyTypes.contains(type);
+ return legacyTypes.containsKey(type);
}
public boolean containsNonLegacyType(DexType type) {
return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type);
}
- public ImmutableSet<DexType> getLegacyTypes() {
+ public ImmutableMap<DexType, LegacySyntheticReference> getLegacyTypes() {
return legacyTypes;
}
@@ -186,39 +187,40 @@
return this;
}
Builder builder = CommittedSyntheticsCollection.empty().builder();
- boolean changed = false;
- for (DexType type : legacyTypes) {
- if (removed.contains(type)) {
- changed = true;
- } else {
- builder.addLegacyClass(type);
- }
- }
+ BooleanBox changed = new BooleanBox(false);
+ legacyTypes.forEach(
+ (type, item) -> {
+ if (removed.contains(type)) {
+ changed.set();
+ } else {
+ builder.addLegacyClass(item);
+ }
+ });
for (SyntheticMethodReference reference : nonLegacyMethods.values()) {
if (removed.contains(reference.getHolder())) {
- changed = true;
+ changed.set();
} else {
builder.addNonLegacyMethod(reference);
}
}
for (SyntheticProgramClassReference reference : nonLegacyClasses.values()) {
if (removed.contains(reference.getHolder())) {
- changed = true;
+ changed.set();
} else {
builder.addNonLegacyClass(reference);
}
}
- return changed ? builder.build() : this;
+ return changed.isTrue() ? builder.build() : this;
}
CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
return new CommittedSyntheticsCollection(
- lens.rewriteTypes(legacyTypes),
+ rewriteItems(legacyTypes, lens),
rewriteItems(nonLegacyMethods, lens),
rewriteItems(nonLegacyClasses, lens));
}
- private static <R extends SyntheticReference<R, ?, ?>> ImmutableMap<DexType, R> rewriteItems(
+ private static <R extends Rewritable<R>> ImmutableMap<DexType, R> rewriteItems(
Map<DexType, R> items, NonIdentityGraphLens lens) {
ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder();
for (R reference : items.values()) {
@@ -231,7 +233,7 @@
}
boolean verifyTypesAreInApp(DexApplication application) {
- assert verifyTypesAreInApp(application, legacyTypes);
+ assert verifyTypesAreInApp(application, legacyTypes.keySet());
assert verifyTypesAreInApp(application, nonLegacyMethods.keySet());
assert verifyTypesAreInApp(application, nonLegacyClasses.keySet());
return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java
new file mode 100644
index 0000000..47a9556
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, 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.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class LegacySyntheticDefinition {
+ private final DexProgramClass clazz;
+ private final Map<DexType, DexType> contexts = new ConcurrentHashMap<>();
+
+ public LegacySyntheticDefinition(DexProgramClass clazz) {
+ this.clazz = clazz;
+ }
+
+ public void addContext(ProgramDefinition clazz) {
+ DexType type = clazz.getContextType();
+ contexts.put(type, type);
+ }
+
+ public Set<DexType> getContexts() {
+ return contexts.keySet();
+ }
+
+ public LegacySyntheticReference toReference() {
+ return new LegacySyntheticReference(clazz.getType(), ImmutableSet.copyOf(contexts.keySet()));
+ }
+
+ public DexProgramClass getDefinition() {
+ return clazz;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java
new file mode 100644
index 0000000..06d2e2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, 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.synthesis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import java.util.Set;
+
+public class LegacySyntheticReference implements Rewritable<LegacySyntheticReference> {
+ private final DexType type;
+ private final Set<DexType> contexts;
+
+ public LegacySyntheticReference(DexType type, Set<DexType> contexts) {
+ this.type = type;
+ this.contexts = contexts;
+ }
+
+ @Override
+ public DexType getHolder() {
+ return type;
+ }
+
+ public Set<DexType> getContexts() {
+ return contexts;
+ }
+
+ @Override
+ public LegacySyntheticReference rewrite(NonIdentityGraphLens lens) {
+ DexType rewrittenType = lens.lookupType(type);
+ Set<DexType> rewrittenContexts = lens.rewriteTypes(getContexts());
+ if (type == rewrittenType && contexts.equals(rewrittenContexts)) {
+ return this;
+ }
+ return new LegacySyntheticReference(rewrittenType, rewrittenContexts);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/Rewritable.java b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java
new file mode 100644
index 0000000..408b3a3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.synthesis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+
+public interface Rewritable<R extends Rewritable<R>> {
+
+ DexType getHolder();
+
+ R rewrite(NonIdentityGraphLens lens);
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index bff9202..04ec40a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -44,6 +44,10 @@
context.getContextType(), context.getContextType(), context.getOrigin());
}
+ static SynthesizingContext fromType(DexType type) {
+ return new SynthesizingContext(type, type, Origin.unknown());
+ }
+
static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
// A context that is itself non-synthetic is the single context, thus both the input context
// and synthesizing context coincide.
@@ -122,21 +126,13 @@
appView.rewritePrefix.rewriteType(hygienicType, rewrittenType);
}
- // TODO(b/180074885): Remove this once main-dex is traced at the end of of compilation.
- void addIfDerivedFromMainDexClass(
- DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo) {
- if (mainDexInfo.isMainDex(externalSyntheticClass)) {
- return;
- }
- // The input context type (not the annotated context) determines if the derived class is to be
- // in main dex, as it is the input context type that is traced as part of main-dex tracing.
- if (mainDexInfo.isMainDexTypeThatShouldIncludeDependencies(inputContextType)) {
- mainDexInfo.addSyntheticClass(externalSyntheticClass);
- }
- }
-
@Override
public String toString() {
return "SynthesizingContext{" + getSynthesizingContextType() + "}";
}
+
+ // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+ boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) {
+ return mainDexInfo.isSyntheticContextOnMainDexList(inputContextType);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 01e6c75..126355f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -34,6 +34,7 @@
private final DexType type;
private final Origin origin;
+ private boolean isAbstract = false;
private Kind originKind;
private DexType superType;
private DexTypeList interfaces = DexTypeList.empty();
@@ -70,6 +71,11 @@
return self();
}
+ public B setAbstract() {
+ isAbstract = true;
+ return self();
+ }
+
public B setOriginKind(Kind originKind) {
this.originKind = originKind;
return self();
@@ -107,9 +113,10 @@
}
public C build() {
+ int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL;
ClassAccessFlags accessFlags =
ClassAccessFlags.fromSharedAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+ flag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
DexString sourceFile = null;
NestHostClassAttribute nestHost = null;
List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
index 69f87fe..92ac183 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -27,7 +27,7 @@
}
@Override
- DexType getHolder() {
+ public DexType getHolder() {
return type;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 2e70db6..d610175 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import static com.google.common.base.Predicates.alwaysTrue;
+
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -29,7 +31,8 @@
import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap;
import com.android.tools.r8.utils.structural.RepresentativeMap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
@@ -43,10 +46,8 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
-import java.util.function.Function;
public class SyntheticFinalization {
@@ -54,129 +55,76 @@
public final CommittedItems commit;
public final NonIdentityGraphLens lens;
public final PrunedItems prunedItems;
+ public final MainDexInfo mainDexInfo;
public Result(
- CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) {
+ CommittedItems commit,
+ SyntheticFinalizationGraphLens lens,
+ PrunedItems prunedItems,
+ MainDexInfo mainDexInfo) {
this.commit = commit;
this.lens = lens;
this.prunedItems = prunedItems;
+ this.mainDexInfo = mainDexInfo;
}
}
public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
- private final Map<DexType, DexType> syntheticTypeMap;
- private final Map<DexMethod, DexMethod> syntheticMethodsMap;
-
private SyntheticFinalizationGraphLens(
GraphLens previous,
- Map<DexType, DexType> syntheticClassesMap,
- Map<DexMethod, DexMethod> syntheticMethodsMap,
Map<DexType, DexType> typeMap,
BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
Map<DexMethod, DexMethod> methodMap,
BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
DexItemFactory factory) {
super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory);
- this.syntheticTypeMap = syntheticClassesMap;
- this.syntheticMethodsMap = syntheticMethodsMap;
}
@Override
public boolean isSyntheticFinalizationGraphLens() {
return true;
}
-
- // The mapping is many to one, so the inverse is only defined up to equivalence groups.
- // Override the access to renamed signatures to first check for synthetic mappings before
- // using the original item mappings of the
-
- @Override
- public DexField getRenamedFieldSignature(DexField originalField) {
- if (syntheticTypeMap.containsKey(originalField.holder)) {
- DexField renamed = fieldMap.get(originalField);
- if (renamed != null) {
- return renamed;
- }
- }
- return super.getRenamedFieldSignature(originalField);
- }
-
- @Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
- if (syntheticTypeMap.containsKey(originalMethod.holder)) {
- DexMethod renamed = methodMap.get(originalMethod);
- if (renamed != null) {
- return renamed;
- }
- }
- DexMethod renamed = syntheticMethodsMap.get(originalMethod);
- return renamed != null ? renamed : super.getRenamedMethodSignature(originalMethod, applied);
- }
}
private static class Builder {
- // Forward mapping of internal to external synthetics.
- Map<DexType, DexType> syntheticClassesMap = new IdentityHashMap<>();
- Map<DexMethod, DexMethod> syntheticMethodsMap = new IdentityHashMap<>();
-
Map<DexType, DexType> typeMap = new IdentityHashMap<>();
BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
new BidirectionalManyToOneRepresentativeHashMap<>();
Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
- protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
- new BidirectionalOneToOneHashMap<>();
-
- void moveSyntheticClass(DexType from, DexType to) {
- assert !syntheticClassesMap.containsKey(from);
- syntheticClassesMap.put(from, to);
- typeMap.put(from, to);
- }
-
- void moveSyntheticMethod(DexMethod from, DexMethod to) {
- assert !syntheticMethodsMap.containsKey(from);
- syntheticMethodsMap.put(from, to);
- methodMap.put(from, to);
- typeMap.put(from.getHolderType(), to.getHolderType());
- }
+ protected final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
+ originalMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
void move(DexType from, DexType to) {
- typeMap.put(from, to);
+ DexType old = typeMap.put(from, to);
+ assert old == null || old == to;
}
void move(DexField from, DexField to) {
- fieldMap.put(from, to);
+ DexField old = fieldMap.put(from, to);
+ assert old == null || old == to;
}
void move(DexMethod from, DexMethod to) {
- methodMap.put(from, to);
+ DexMethod old = methodMap.put(from, to);
+ assert old == null || old == to;
originalMethodSignatures.put(to, from);
}
SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) {
- assert verifySubMap(syntheticClassesMap, typeMap);
if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
return null;
}
return new SyntheticFinalizationGraphLens(
previous,
- syntheticClassesMap,
- syntheticMethodsMap,
typeMap,
fieldMap,
methodMap,
originalMethodSignatures,
factory);
}
-
- private static <K, V> boolean verifySubMap(Map<K, V> sub, Map<K, V> sup) {
- for (Entry<K, V> entry : sub.entrySet()) {
- assert sup.get(entry.getKey()) == entry.getValue();
- }
- return true;
- }
}
public static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
@@ -231,26 +179,38 @@
assert !appView.appInfo().hasClassHierarchy();
assert !appView.appInfo().hasLiveness();
Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
- appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexInfo()));
- appView.pruneItems(result.prunedItems);
+ appView.setAppInfo(new AppInfo(result.commit, result.mainDexInfo));
if (result.lens != null) {
+ appView.setAppInfo(
+ appView
+ .appInfo()
+ .rebuildWithMainDexInfo(
+ appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
appView.setGraphLens(result.lens);
}
+ appView.pruneItems(result.prunedItems);
}
public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) {
assert !appView.appInfo().hasLiveness();
Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
- appView.pruneItems(result.prunedItems);
+ appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
if (result.lens != null) {
appView.setGraphLens(result.lens);
+ appView.setAppInfo(
+ appView
+ .appInfo()
+ .rebuildWithMainDexInfo(
+ appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
}
+ appView.pruneItems(result.prunedItems);
}
public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
+ appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
appView.rewriteWithLens(result.lens);
appView.pruneItems(result.prunedItems);
}
@@ -263,23 +223,20 @@
ImmutableMap.builder();
ImmutableMap.Builder<DexType, SyntheticProgramClassReference> finalClassesBuilder =
ImmutableMap.builder();
- List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>();
+ Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
{
Map<String, NumberGenerator> generators = new HashMap<>();
application =
buildLensAndProgram(
appView,
- computeEquivalences(appView, committed.getNonLegacyMethods().values(), generators),
- computeEquivalences(appView, committed.getNonLegacyClasses().values(), generators),
+ computeEquivalences(
+ appView, committed.getNonLegacyMethods().values(), generators, lensBuilder),
+ computeEquivalences(
+ appView, committed.getNonLegacyClasses().values(), generators, lensBuilder),
lensBuilder,
- (clazz, reference) -> {
- finalSyntheticProgramDefinitions.add(clazz);
- finalClassesBuilder.put(clazz.getType(), reference);
- },
- (clazz, reference) -> {
- finalSyntheticProgramDefinitions.add(clazz);
- finalMethodsBuilder.put(clazz.getType(), reference);
- });
+ (clazz, reference) -> finalClassesBuilder.put(clazz.getType(), reference),
+ (clazz, reference) -> finalMethodsBuilder.put(clazz.getType(), reference),
+ derivedMainDexTypes);
}
ImmutableMap<DexType, SyntheticMethodReference> finalMethods = finalMethodsBuilder.build();
ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses =
@@ -294,6 +251,10 @@
}
});
+ // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+ MainDexInfo.Builder mainDexInfoBuilder = appView.appInfo().getMainDexInfo().builderFromCopy();
+ derivedMainDexTypes.forEach(mainDexInfoBuilder::addList);
+
return new Result(
new CommittedItems(
SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
@@ -302,17 +263,16 @@
committed.getLegacyTypes(), finalMethods, finalClasses),
ImmutableList.of()),
lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
- PrunedItems.builder()
- .setPrunedApp(application)
- .addRemovedClasses(prunedSynthetics)
- .build());
+ PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(),
+ mainDexInfoBuilder.build());
}
private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
Map<DexType, EquivalenceGroup<D>> computeEquivalences(
AppView<?> appView,
ImmutableCollection<R> references,
- Map<String, NumberGenerator> generators) {
+ Map<String, NumberGenerator> generators,
+ Builder lensBuilder) {
boolean intermediate = appView.options().intermediate;
Map<DexType, D> definitions = lookupDefinitions(appView, references);
ClassToFeatureSplitMap classToFeatureSplitMap =
@@ -328,7 +288,12 @@
classToFeatureSplitMap,
synthetics);
return computeActualEquivalences(
- potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
+ potentialEquivalences,
+ generators,
+ appView,
+ intermediate,
+ classToFeatureSplitMap,
+ lensBuilder);
}
private boolean isNotSyntheticType(DexType type) {
@@ -339,7 +304,8 @@
// Check that a context is never itself synthetic class.
committed.forEachNonLegacyItem(
item -> {
- assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
+ assert isNotSyntheticType(item.getContext().getSynthesizingContextType())
+ || item.getKind().allowSyntheticContext();
});
return true;
}
@@ -350,75 +316,18 @@
Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups,
Builder lensBuilder,
BiConsumer<DexProgramClass, SyntheticProgramClassReference> addFinalSyntheticClass,
- BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod) {
+ BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod,
+ Set<DexType> derivedMainDexSynthetics) {
DexApplication application = appView.appInfo().app();
- DexItemFactory factory = appView.dexItemFactory();
+ MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
List<DexProgramClass> newProgramClasses = new ArrayList<>();
Set<DexType> pruned = Sets.newIdentityHashSet();
- syntheticMethodGroups.forEach(
- (syntheticType, syntheticGroup) -> {
- SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
- SynthesizingContext context = representative.getContext();
- context.registerPrefixRewriting(syntheticType, appView);
- DexProgramClass externalSyntheticClass =
- createExternalMethodClass(syntheticType, representative, factory);
- newProgramClasses.add(externalSyntheticClass);
- addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
- assert externalSyntheticClass.getMethodCollection().size() == 1;
- DexEncodedMethod externalSyntheticMethod =
- externalSyntheticClass.methods().iterator().next();
- for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
- DexMethod memberReference = member.getMethod().getReference();
- pruned.add(member.getHolder().getType());
- if (memberReference != externalSyntheticMethod.method) {
- lensBuilder.moveSyntheticMethod(memberReference, externalSyntheticMethod.method);
- }
- }
- });
-
- List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
- syntheticClassGroups.forEach(
- (syntheticType, syntheticGroup) -> {
- SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
- SynthesizingContext context = representative.getContext();
- context.registerPrefixRewriting(syntheticType, appView);
- DexProgramClass externalSyntheticClass = representative.getHolder();
- newProgramClasses.add(externalSyntheticClass);
- addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
- for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
- DexProgramClass memberClass = member.getHolder();
- DexType memberType = memberClass.getType();
- pruned.add(memberType);
- if (memberType != syntheticType) {
- lensBuilder.moveSyntheticClass(memberType, syntheticType);
- }
- // The aliasing of the non-representative members needs to be recorded manually.
- if (member != representative) {
- deduplicatedClasses.add(memberClass);
- }
- }
- });
-
- for (DexProgramClass clazz : application.classes()) {
- if (!pruned.contains(clazz.type)) {
- newProgramClasses.add(clazz);
- }
- }
- application = application.builder().replaceProgramClasses(newProgramClasses).build();
-
- // We can only assert that the method container classes are in here as the classes need
- // to be rewritten by the tree-fixer.
- for (DexType key : syntheticMethodGroups.keySet()) {
- assert application.definitionFor(key) != null;
- }
-
- DexApplication.Builder<?> builder = application.builder();
TreeFixerBase treeFixer =
new TreeFixerBase(appView) {
@Override
public DexType mapClassType(DexType type) {
- return lensBuilder.syntheticClassesMap.getOrDefault(type, type);
+ return lensBuilder.typeMap.getOrDefault(type, type);
}
@Override
@@ -436,6 +345,59 @@
lensBuilder.move(from, to);
}
};
+
+ List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
+ syntheticMethodGroups.forEach(
+ (syntheticType, syntheticGroup) -> {
+ SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
+ SynthesizingContext context = representative.getContext();
+ context.registerPrefixRewriting(syntheticType, appView);
+ DexProgramClass representativeClass = representative.getHolder();
+ addSyntheticMarker(representative.getKind(), representativeClass, context, appView);
+ assert representativeClass.getMethodCollection().size() == 1;
+ for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
+ if (member != representative) {
+ pruned.add(member.getHolder().getType());
+ deduplicatedClasses.add(member.getHolder());
+ }
+ if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
+ derivedMainDexSynthetics.add(syntheticType);
+ }
+ }
+ });
+
+ syntheticClassGroups.forEach(
+ (syntheticType, syntheticGroup) -> {
+ SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
+ SynthesizingContext context = representative.getContext();
+ context.registerPrefixRewriting(syntheticType, appView);
+ addSyntheticMarker(
+ representative.getKind(), representative.getHolder(), context, appView);
+ for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
+ DexProgramClass memberClass = member.getHolder();
+ DexType memberType = memberClass.getType();
+ if (member != representative) {
+ pruned.add(memberType);
+ deduplicatedClasses.add(memberClass);
+ }
+ if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
+ derivedMainDexSynthetics.add(syntheticType);
+ }
+ }
+ });
+
+ for (DexProgramClass clazz : application.classes()) {
+ if (!pruned.contains(clazz.type)) {
+ newProgramClasses.add(clazz);
+ }
+ }
+ application = application.builder().replaceProgramClasses(newProgramClasses).build();
+
+ // Assert that the non-representatives have been removed from the app.
+ assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups);
+ assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
+
+ DexApplication.Builder<?> builder = application.builder();
treeFixer.fixupClasses(deduplicatedClasses);
builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
application = builder.build();
@@ -452,34 +414,21 @@
representative.getKind(),
representative.getContext(),
externalSyntheticClass.type));
- for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
- addMainDexAndSynthesizedFromForMember(
- member,
- externalSyntheticClass,
- appView.appInfo().getMainDexInfo(),
- appForLookup::programDefinitionFor);
- }
});
syntheticMethodGroups.forEach(
(syntheticType, syntheticGroup) -> {
DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
+ assert externalSyntheticClass.getMethodCollection().size() == 1;
+ assert externalSyntheticClass.getMethodCollection().hasDirectMethods();
+ DexEncodedMethod syntheticMethodDefinition =
+ externalSyntheticClass.getMethodCollection().getDirectMethod(alwaysTrue());
addFinalSyntheticMethod.accept(
externalSyntheticClass,
new SyntheticMethodReference(
representative.getKind(),
representative.getContext(),
- representative
- .getMethod()
- .getReference()
- .withHolder(externalSyntheticClass.type, factory)));
- for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
- addMainDexAndSynthesizedFromForMember(
- member,
- externalSyntheticClass,
- appView.appInfo().getMainDexInfo(),
- appForLookup::programDefinitionFor);
- }
+ syntheticMethodDefinition.getReference()));
});
for (DexType key : syntheticMethodGroups.keySet()) {
@@ -493,6 +442,18 @@
return application;
}
+ private static <T extends SyntheticDefinition<?, T, ?>>
+ boolean verifyNonRepresentativesRemovedFromApplication(
+ DexApplication application, Map<DexType, EquivalenceGroup<T>> syntheticGroups) {
+ for (EquivalenceGroup<?> syntheticGroup : syntheticGroups.values()) {
+ for (SyntheticDefinition<?, ?, ?> member : syntheticGroup.getMembers()) {
+ assert member == syntheticGroup.getRepresentative()
+ || application.definitionFor(member.getHolder().getType()) == null;
+ }
+ }
+ return true;
+ }
+
private static void addSyntheticMarker(
SyntheticKind kind,
DexProgramClass externalSyntheticClass,
@@ -504,38 +465,6 @@
}
}
- private static DexProgramClass createExternalMethodClass(
- DexType syntheticType, SyntheticMethodDefinition representative, DexItemFactory factory) {
- SyntheticProgramClassBuilder builder =
- new SyntheticProgramClassBuilder(syntheticType, representative.getContext(), factory);
- // TODO(b/158159959): Support grouping multiple methods per synthetic class.
- builder.addMethod(
- methodBuilder -> {
- DexEncodedMethod definition = representative.getMethod().getDefinition();
- methodBuilder
- .setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)
- .setAccessFlags(definition.accessFlags)
- .setProto(definition.getProto())
- .setClassFileVersion(
- definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
- .setCode(m -> definition.getCode());
- });
- return builder.build();
- }
-
- private static void addMainDexAndSynthesizedFromForMember(
- SyntheticDefinition<?, ?, ?> member,
- DexProgramClass externalSyntheticClass,
- MainDexInfo mainDexInfo,
- Function<DexType, DexProgramClass> definitions) {
- member.getContext().addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo);
- // TODO(b/168584485): Remove this once class-mapping support is removed.
- DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
- if (from != null) {
- externalSyntheticClass.addSynthesizedFrom(from);
- }
- }
-
private static boolean shouldAnnotateSynthetics(InternalOptions options) {
// Only intermediate builds have annotated synthetics to allow later sharing.
// This is currently also disabled on CF to CF desugaring to avoid missing class references to
@@ -550,7 +479,8 @@
Map<String, NumberGenerator> generators,
AppView<?> appView,
boolean intermediate,
- ClassToFeatureSplitMap classToFeatureSplitMap) {
+ ClassToFeatureSplitMap classToFeatureSplitMap,
+ Builder lensBuilder) {
Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
potentialEquivalences.forEach(
members -> {
@@ -599,6 +529,9 @@
DexType representativeType =
createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
equivalences.put(representativeType, group);
+ for (T member : group.getMembers()) {
+ lensBuilder.move(member.getHolder().getType(), representativeType);
+ }
}
});
return equivalences;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 9922011..65c53d5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -47,7 +48,7 @@
*
* <p>TODO(b/158159959): Remove legacy support.
*/
- private final Map<DexType, DexProgramClass> legacyClasses = new ConcurrentHashMap<>();
+ private final Map<DexType, LegacySyntheticDefinition> legacyClasses = new ConcurrentHashMap<>();
/** Thread safe collection of synthetic items not yet committed to the application. */
private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?, ?>> nonLegacyDefinitions =
@@ -75,7 +76,9 @@
allPending.add(item.asProgramDefinition().getHolder());
}
}
- allPending.addAll(legacyClasses.values());
+ for (LegacySyntheticDefinition legacy : legacyClasses.values()) {
+ allPending.add(legacy.getDefinition());
+ }
return Collections.unmodifiableList(allPending);
}
}
@@ -146,17 +149,24 @@
@Override
public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
- DexClass clazz = pending.legacyClasses.get(type);
- if (clazz == null) {
+ DexClass clazz = null;
+ SyntheticKind kind = null;
+ LegacySyntheticDefinition legacyItem = pending.legacyClasses.get(type);
+ if (legacyItem != null) {
+ clazz = legacyItem.getDefinition();
+ } else {
SyntheticDefinition<?, ?, ?> item = pending.nonLegacyDefinitions.get(type);
if (item != null) {
clazz = item.getHolder();
+ kind = item.getKind();
assert clazz.isProgramClass() == item.isProgramDefinition();
assert clazz.isClasspathClass() == item.isClasspathDefinition();
}
}
if (clazz != null) {
+ assert legacyItem != null || kind != null;
assert baseDefinitionFor.apply(type) == null
+ || (kind != null && kind.mayOverridesNonProgramType)
: "Pending synthetic definition also present in the active program: " + type;
return clazz;
}
@@ -212,6 +222,22 @@
return isCommittedSynthetic(type) || isPendingSynthetic(type);
}
+ public SyntheticKind getNonLegacySyntheticKind(DexProgramClass clazz) {
+ assert isNonLegacySynthetic(clazz);
+ SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType());
+ if (reference == null) {
+ SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+ if (definition != null) {
+ reference = definition.toReference();
+ }
+ }
+ if (reference != null) {
+ return reference.getKind();
+ }
+ assert false;
+ return null;
+ }
+
public boolean isSyntheticClass(DexType type) {
return isLegacySyntheticClass(type) || isNonLegacySynthetic(type);
}
@@ -229,6 +255,14 @@
if (definition != null) {
return Collections.singletonList(definition.getContext().getSynthesizingContextType());
}
+ LegacySyntheticReference legacyReference = committed.getLegacyTypes().get(type);
+ if (legacyReference != null) {
+ return legacyReference.getContexts();
+ }
+ LegacySyntheticDefinition legacyDefinition = pending.legacyClasses.get(type);
+ if (legacyDefinition != null) {
+ return legacyDefinition.getContexts();
+ }
return Collections.emptyList();
}
@@ -266,7 +300,7 @@
}
public Collection<DexProgramClass> getLegacyPendingClasses() {
- return Collections.unmodifiableCollection(pending.legacyClasses.values());
+ return ListUtils.map(pending.legacyClasses.values(), LegacySyntheticDefinition::getDefinition);
}
private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
@@ -285,12 +319,26 @@
// Addition and creation of synthetic items.
+ public void addLegacySyntheticClassForLibraryDesugaring(DexProgramClass clazz) {
+ internalAddLegacySyntheticClass(clazz);
+ // No context information is added for library context.
+ // This is intended only to support desugared-library compilation.
+ }
+
// TODO(b/158159959): Remove the usage of this direct class addition.
- public void addLegacySyntheticClass(DexProgramClass clazz) {
+ public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) {
+ LegacySyntheticDefinition legacyItem = internalAddLegacySyntheticClass(clazz);
+ legacyItem.addContext(context);
+ }
+
+ private LegacySyntheticDefinition internalAddLegacySyntheticClass(DexProgramClass clazz) {
assert !isCommittedSynthetic(clazz.type);
assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
- DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
- assert previous == null || previous == clazz;
+ LegacySyntheticDefinition legacyItem =
+ pending.legacyClasses.computeIfAbsent(
+ clazz.getType(), type -> new LegacySyntheticDefinition(clazz));
+ assert legacyItem.getDefinition() == clazz;
+ return legacyItem;
}
public DexProgramClass createClass(
@@ -342,6 +390,23 @@
return clazz;
}
+ public DexProgramClass createFixedClassFromType(
+ SyntheticKind kind,
+ DexType contextType,
+ DexItemFactory factory,
+ Consumer<SyntheticProgramClassBuilder> fn) {
+ // Obtain the outer synthesizing context in the case the context itself is synthetic.
+ // This is to ensure a flat input-type -> synthetic-item mapping.
+ SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+ DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+ SyntheticProgramClassBuilder classBuilder =
+ new SyntheticProgramClassBuilder(type, outerContext, factory);
+ fn.accept(classBuilder);
+ DexProgramClass clazz = classBuilder.build();
+ addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+ return clazz;
+ }
+
public DexClasspathClass createFixedClasspathClass(
SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
// Obtain the outer synthesizing context in the case the context itself is synthetic.
@@ -418,7 +483,7 @@
CommittedSyntheticsCollection.Builder builder = committed.builder();
// Legacy synthetics must already have been committed to the app.
assert verifyClassesAreInApp(application, pending.legacyClasses.values());
- builder.addLegacyClasses(pending.legacyClasses.values());
+ builder.addLegacyClasses(pending.legacyClasses);
// Compute the synthetic additions and add them to the application.
ImmutableList<DexType> committedProgramTypes;
DexApplication amendedApplication;
@@ -432,7 +497,12 @@
if (!removedClasses.contains(definition.getHolder().getType())) {
if (definition.isProgramDefinition()) {
committedProgramTypesBuilder.add(definition.getHolder().getType());
- appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
+ if (definition.getKind().mayOverridesNonProgramType) {
+ appBuilder.addProgramClassPotentiallyOverridingNonProgramClass(
+ definition.asProgramDefinition().getHolder());
+ } else {
+ appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
+ }
} else if (appBuilder.isDirect()) {
assert definition.isClasspathDefinition();
appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
@@ -451,8 +521,9 @@
}
private static boolean verifyClassesAreInApp(
- DexApplication app, Collection<DexProgramClass> classes) {
- for (DexProgramClass clazz : classes) {
+ DexApplication app, Collection<LegacySyntheticDefinition> classes) {
+ for (LegacySyntheticDefinition item : classes) {
+ DexProgramClass clazz = item.getDefinition();
assert app.programDefinitionFor(clazz.type) != null : "Missing synthetic: " + clazz.type;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index dfc240b..9c19e81 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -20,7 +20,7 @@
*/
class SyntheticMethodReference
extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
- implements SyntheticProgramReference {
+ implements SyntheticProgramReference, Rewritable<SyntheticMethodReference> {
final DexMethod method;
SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) {
@@ -29,7 +29,7 @@
}
@Override
- DexType getHolder() {
+ public DexType getHolder() {
return method.holder;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 1b80f41..7148738 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -23,9 +23,13 @@
*/
public enum SyntheticKind {
// Class synthetics.
+ RECORD_TAG("", false, true, true),
COMPANION_CLASS("CompanionClass", false),
LAMBDA("Lambda", false),
INIT_TYPE_ARGUMENT("-IA", false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true),
// Method synthetics.
BACKPORT("Backport", true),
STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
@@ -41,6 +45,7 @@
public final String descriptor;
public final boolean isSingleSyntheticMethod;
public final boolean isFixedSuffixSynthetic;
+ public final boolean mayOverridesNonProgramType;
SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
this(descriptor, isSingleSyntheticMethod, false);
@@ -48,9 +53,22 @@
SyntheticKind(
String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) {
+ this(descriptor, isSingleSyntheticMethod, isFixedSuffixSynthetic, false);
+ }
+
+ SyntheticKind(
+ String descriptor,
+ boolean isSingleSyntheticMethod,
+ boolean isFixedSuffixSynthetic,
+ boolean mayOverridesNonProgramType) {
this.descriptor = descriptor;
this.isSingleSyntheticMethod = isSingleSyntheticMethod;
this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
+ this.mayOverridesNonProgramType = mayOverridesNonProgramType;
+ }
+
+ public boolean allowSyntheticContext() {
+ return this == RECORD_TAG;
}
public static SyntheticKind fromDescriptor(String descriptor) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
index 5a17b04..d5973f6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -19,7 +19,7 @@
class SyntheticProgramClassReference
extends SyntheticClassReference<
SyntheticProgramClassReference, SyntheticProgramClassDefinition, DexProgramClass>
- implements SyntheticProgramReference {
+ implements SyntheticProgramReference, Rewritable<SyntheticProgramClassReference> {
SyntheticProgramClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
super(kind, context, type);
@@ -42,9 +42,6 @@
// If the reference has been non-trivially rewritten the compiler has changed it and it can no
// longer be considered a synthetic. The context may or may not have changed.
if (type != rewritten && !lens.isSimpleRenaming(type, rewritten)) {
- // If the referenced item is rewritten, it should be moved to another holder as the
- // synthetic holder is no longer part of the synthetic collection.
- assert SyntheticNaming.verifyNotInternalSynthetic(rewritten);
return null;
}
if (rewrittenContext == getContext() && rewritten == type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index ddb28f7..9824ebc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -44,7 +44,7 @@
abstract DexReference getReference();
- final R rewrite(NonIdentityGraphLens lens) {
+ public final R rewrite(NonIdentityGraphLens lens) {
SynthesizingContext rewrittenContext = getContext().rewrite(lens);
return internalRewrite(rewrittenContext, lens);
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1534b4a..d1f6e4c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -89,6 +89,7 @@
private static final String dumpBuildPropertiesFileName = "build.properties";
private static final String dumpDesugaredLibraryFileName = "desugared-library.json";
private static final String dumpMainDexListResourceFileName = "main-dex-list.txt";
+ private static final String dumpMainDexRulesResourceFileName = "main-dex-rules.txt";
private static final String dumpProgramFileName = "program.jar";
private static final String dumpClasspathFileName = "classpath.jar";
private static final String dumpLibraryFileName = "library.jar";
@@ -512,6 +513,13 @@
String join = StringUtils.join(mainDexList, "\n");
writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED);
}
+ if (options.hasMainDexKeepRules()) {
+ writeToZipStream(
+ out,
+ dumpMainDexRulesResourceFileName,
+ StringUtils.joinLines(options.getMainDexKeepRules()).getBytes(),
+ ZipEntry.DEFLATED);
+ }
nextDexIndex =
dumpProgramResources(
dumpProgramFileName,
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index b7839ea..5a4ee11 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -13,6 +13,15 @@
public class ArrayUtils {
+ public static boolean containsInt(int[] array, int value) {
+ for (int element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Copies the input array and then applies specified sparse changes.
*
@@ -71,6 +80,13 @@
clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
}
+ public static boolean isSorted(int[] array) {
+ for (int i = 0; i < array.length - 1; i++) {
+ assert array[i] < array[i + 1];
+ }
+ return true;
+ }
+
/**
* Rewrites the input array based on the given function.
*
diff --git a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
index f470d35..5245876 100644
--- a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -15,4 +15,16 @@
public static <S, T> BiPredicate<S, T> alwaysTrue() {
return (s, t) -> true;
}
+
+ @SafeVarargs
+ public static <S, T> BiPredicate<S, T> or(BiPredicate<S, T>... predicates) {
+ return (s, t) -> {
+ for (BiPredicate<S, T> predicate : predicates) {
+ if (predicate.test(s, t)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 3c7b99b..c885d9e 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Iterator;
@@ -115,6 +116,21 @@
}
/**
+ * Clears the type so if a class with the given type was present, it cannot be found anymore. This
+ * has to be run at a join point, concurrent accesses may be confused.
+ */
+ public void clearType(DexType type) {
+ if (classes.containsKey(type)) {
+ classes.remove(type);
+ }
+ ClassProvider<T> provider = classProvider.get();
+ if (provider == null) {
+ return;
+ }
+ classProvider.set(provider.without(ImmutableSet.of(type)));
+ }
+
+ /**
* Returns all classes from the collection. The collection must be force-loaded.
*/
public List<T> getAllClasses() {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index c048529..af272b8 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -70,6 +71,10 @@
return new PreloadedClassProvider<>(classKind, builder.build());
}
+ public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
+ return new FilteringClassProvider(classKind, this, filteredTypes);
+ }
+
/** Create class provider for preloaded classes. */
public static <T extends DexClass> ClassProvider<T> combine(
ClassKind<T> classKind, List<ClassProvider<T>> providers) {
@@ -145,6 +150,47 @@
}
}
+ /** Class provider which ignores a list of filtered classes */
+ private static class FilteringClassProvider<T extends DexClass> extends ClassProvider<T> {
+ private final ClassProvider<T> provider;
+ private final Set<DexType> filteredOut;
+
+ FilteringClassProvider(
+ ClassKind<T> classKind, ClassProvider<T> provider, Set<DexType> filteredOut) {
+ super(classKind);
+ assert !(provider instanceof FilteringClassProvider) : "Nested Filtering class providers";
+ this.provider = provider;
+ this.filteredOut = filteredOut;
+ }
+
+ @Override
+ public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
+ ImmutableSet<DexType> newSet =
+ ImmutableSet.<DexType>builder().addAll(filteredOut).addAll(filteredTypes).build();
+ return new FilteringClassProvider(getClassKind(), provider, newSet);
+ }
+
+ @Override
+ public void collectClass(DexType type, Consumer<T> classConsumer) {
+ if (filteredOut.contains(type)) {
+ return;
+ }
+ provider.collectClass(type, classConsumer);
+ }
+
+ @Override
+ public Collection<DexType> collectTypes() {
+ Collection<DexType> dexTypes = provider.collectTypes();
+ dexTypes.removeAll(filteredOut);
+ return dexTypes;
+ }
+
+ @Override
+ public String toString() {
+ return provider + " without " + filteredOut;
+ }
+ }
+
private static class CombinedClassProvider<T extends DexClass> extends ClassProvider<T> {
private final List<ClassProvider<T>> providers;
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 a32e296..292f103 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -75,6 +75,7 @@
List<Path> library = new ArrayList<>();
List<Path> classpath = new ArrayList<>();
List<Path> config = new ArrayList<>();
+ List<Path> mainDexRulesFiles = new ArrayList<>();
int minApi = 1;
int threads = -1;
for (int i = 0; i < args.length; i++) {
@@ -147,6 +148,11 @@
threads = Integer.parseInt(operand);
break;
}
+ case "--main-dex-rules":
+ {
+ mainDexRulesFiles.add(Paths.get(operand));
+ break;
+ }
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
@@ -178,6 +184,7 @@
.addLibraryFiles(library)
.addClasspathFiles(classpath)
.addProguardConfigurationFiles(config)
+ .addMainDexRulesFiles(mainDexRulesFiles)
.setOutput(outputPath, outputMode)
.setMode(compilationMode);
if (desugaredLibJson != null) {
diff --git a/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java
new file mode 100644
index 0000000..7da945b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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.utils;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class Int2ObjectMapUtils {
+
+ public static <V> void forEach(Int2ObjectMap<V> map, IntObjConsumer<V> consumer) {
+ for (Int2ObjectMap.Entry<V> entry : map.int2ObjectEntrySet()) {
+ consumer.accept(entry.getIntKey(), entry.getValue());
+ }
+ }
+
+ public static <V> V getOrDefault(Int2ObjectMap<V> map, int key, V defaultValue) {
+ V value = map.get(key);
+ return value != null ? value : defaultValue;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java
new file mode 100644
index 0000000..a73a7be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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.utils;
+
+public interface IntObjPredicate<T> {
+
+ boolean test(int i, T obj);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
new file mode 100644
index 0000000..9c07bed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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.utils;
+
+public interface IntObjToObjFunction<S, T> {
+
+ S apply(int i, T obj);
+}
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 ea310aa..54bbbcc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -490,9 +490,16 @@
return !canUseNestBasedAccess();
}
- public boolean canUseRecords() {
- // TODO(b/169645628): Replace by true when records are supported.
- return testing.canUseRecords;
+ public boolean enableExperimentalRecordDesugaring() {
+ // TODO(b/169645628): Remove when records are supported.
+ return testing.enableExperimentalRecordDesugaring;
+ }
+
+ public boolean shouldDesugarRecords() {
+ if (!enableExperimentalRecordDesugaring()) {
+ return false;
+ }
+ return desugarState.isOn() && !canUseRecords();
}
public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
@@ -1032,7 +1039,7 @@
}
// Currently the filter is simple string equality on the qualified name.
String qualifiedName = method.qualifiedName();
- return methodsFilter.indexOf(qualifiedName) >= 0;
+ return methodsFilter.contains(qualifiedName);
}
public boolean methodMatchesLogArgumentsFilter(DexEncodedMethod method) {
@@ -1042,7 +1049,7 @@
}
// Currently the filter is simple string equality on the qualified name.
String qualifiedName = method.qualifiedName();
- return logArgumentsFilter.indexOf(qualifiedName) >= 0;
+ return logArgumentsFilter.contains(qualifiedName);
}
public enum PackageObfuscationMode {
@@ -1151,15 +1158,10 @@
public boolean enable = true;
public boolean enableConstructorMerging = true;
- // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics.
- public boolean enableJavaLambdaMerging = false;
+ public boolean enableJavaLambdaMerging = true;
- public int syntheticArgumentCount = 3;
public int maxGroupSize = 30;
- // TODO(b/179019716): Add support for merging in presence of annotations.
- public boolean skipNoClassesOrMembersWithAnnotationsPolicyForTesting = false;
-
public void disable() {
enable = false;
}
@@ -1180,10 +1182,6 @@
return maxGroupSize;
}
- public int getSyntheticArgumentCount() {
- return syntheticArgumentCount;
- }
-
public boolean isConstructorMergingEnabled() {
return enableConstructorMerging;
}
@@ -1295,7 +1293,7 @@
public boolean enableSwitchToIfRewriting = true;
public boolean enableEnumUnboxingDebugLogs = false;
public boolean forceRedundantConstNumberRemoval = false;
- public boolean canUseRecords = false;
+ public boolean enableExperimentalRecordDesugaring = false;
public boolean invertConditionals = false;
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
@@ -1456,6 +1454,10 @@
return !isDesugaring();
}
+ public boolean canUseRecords() {
+ return !isDesugaring();
+ }
+
public boolean canLeaveStaticInterfaceMethodInvokes() {
return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
}
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 93b7e4a..3ecdc4f 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.google.common.collect.Iterables;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
@@ -16,6 +17,27 @@
public class IteratorUtils {
+ public static <T> Iterator<T> createCircularIterator(Iterable<T> iterable) {
+ assert !Iterables.isEmpty(iterable);
+ return new Iterator<T>() {
+
+ private Iterator<T> iterator = iterable.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public T next() {
+ if (!iterator.hasNext()) {
+ iterator = iterable.iterator();
+ }
+ return iterator.next();
+ }
+ };
+ }
+
public static <T> int countRemaining(Iterator<T> iterator) {
IntBox counter = new IntBox();
iterator.forEachRemaining(ignore -> counter.increment());
diff --git a/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java
new file mode 100644
index 0000000..48a1e90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2021, 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.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
+
+/* Joining String Consumer to join strings that it accepts. */
+public class JoiningStringConsumer extends ForwardingConsumer {
+
+ private final String separator;
+ private final StringConsumer consumer;
+ private final StringBuilder builder = new StringBuilder();
+
+ /**
+ * @param consumer Consumer to forward to the joined input to. If null, nothing will be forwarded.
+ */
+ public JoiningStringConsumer(StringConsumer consumer, String separator) {
+ super(consumer);
+ this.consumer = consumer;
+ this.separator = separator;
+ }
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ if (builder.length() > 0) {
+ builder.append(separator);
+ }
+ builder.append(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.accept(builder.toString(), handler);
+ super.finished(handler);
+ }
+
+ public StringConsumer getConsumer() {
+ return consumer;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java
new file mode 100644
index 0000000..8c65b00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, 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.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Sorting consumer that accepts all input and then sorts it when calling finished */
+public class SortingStringConsumer extends ForwardingConsumer {
+
+ private final List<String> accepted = new ArrayList<>();
+
+ /**
+ * @param consumer Consumer to forward to the sorted consumed input to. If null, nothing will be
+ * forwarded.
+ */
+ public SortingStringConsumer(StringConsumer consumer) {
+ super(consumer);
+ }
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ this.accepted.add(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ Collections.sort(accepted);
+ accepted.forEach(string -> super.accept(string, handler));
+ super.finished(handler);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
index 8f29df9..b75d224 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -130,10 +130,11 @@
}
@Override
- public void put(K key, V value) {
- remove(key);
+ public V put(K key, V value) {
+ V old = remove(key);
backing.put(key, value);
inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key);
+ return old;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
index 830138a..e23a565 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -77,6 +77,11 @@
}
@Override
+ public K getKey(V value) {
+ return backing.inverse().get(value);
+ }
+
+ @Override
public BiMap<K, V> getForwardMap() {
return backing;
}
@@ -88,12 +93,12 @@
@Override
public K getRepresentativeKey(V value) {
- return backing.inverse().get(value);
+ return getKey(value);
}
@Override
public V getRepresentativeValue(K key) {
- return backing.get(key);
+ return get(key);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
index e5085c2..569c52e 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
@@ -15,6 +15,8 @@
public interface BidirectionalOneToOneMap<K, V>
extends BidirectionalManyToOneRepresentativeMap<K, V> {
+ K getKey(V value);
+
@Override
BiMap<K, V> getForwardMap();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
index f91f7df..62fba9a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
@@ -57,6 +57,11 @@
}
@Override
+ public K getKey(V value) {
+ return null;
+ }
+
+ @Override
public BiMap<K, V> getForwardMap() {
return HashBiMap.create();
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
index 7953e91..05bd5ea 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
@@ -11,7 +11,7 @@
void clear();
- void put(K key, V value);
+ V put(K key, V value);
void put(Iterable<K> key, V value);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
index 5e5f469..87a29fa 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
@@ -10,4 +10,6 @@
V put(K key, V value);
void putAll(BidirectionalManyToManyMap<K, V> map);
+
+ V remove(Object key);
}
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java
index e512173..98ca7b2 100644
--- a/src/test/examples/classmerging/ProguardFieldMapTest.java
+++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -13,7 +13,7 @@
public static class A {
- public String f = "A.f";
+ public String f = System.currentTimeMillis() > 0 ? "A.f" : null;
}
@NeverClassInline
diff --git a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
index 627d255..25c0af0 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
@@ -471,15 +471,6 @@
static final InstanceAndClassChecks INSTANCE = new InstanceAndClassChecks();
static void test() {
- assertSameInstance(
- InstanceAndClassChecks::staticProvider,
- InstanceAndClassChecks::staticProvider,
- "Instances must be same");
- assertSameInstance(
- InstanceAndClassChecks::staticProvider,
- statelessLambda(),
- "Instances must be same");
-
assertDifferentInstance(
INSTANCE::instanceProvider,
INSTANCE::instanceProvider,
diff --git a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
new file mode 100644
index 0000000..30e9086
--- /dev/null
+++ b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, 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 backport;
+
+import java.math.BigInteger;
+
+public final class IntegerBackportJava9Main {
+ private static final int[] interestingValues = {
+ Integer.MIN_VALUE,
+ Integer.MAX_VALUE,
+ Short.MIN_VALUE,
+ Short.MAX_VALUE,
+ Byte.MIN_VALUE,
+ Byte.MAX_VALUE,
+ 0,
+ -1,
+ 1,
+ -42,
+ 42
+ };
+
+ public static void main(String[] args) {
+ testParseIntegerSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+ }
+
+ private static void testParseIntegerSubsequenceWithRadix(boolean supportsPlusPrefix) {
+ for (int value : interestingValues) {
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String valueString = prefix + Long.toString(value, radix) + postfix;
+ int start = prefix.length();
+ int end = valueString.length() - postfix.length();
+ assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
+ if (value > 0 && supportsPlusPrefix) {
+ valueString = prefix + '+' + Long.toString(value, radix) + postfix;
+ end++;
+ assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+ } catch (NumberFormatException expected) {
+ }
+
+ BigInteger overflow = new BigInteger("18446744073709551616");
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String overflowString = prefix + overflow.toString(radix) + postfix;
+ int start = prefix.length();
+ int end = overflowString.length() - postfix.length();
+ try {
+ throw new AssertionError(Long.parseLong(overflowString, start, end, radix));
+ } catch (NumberFormatException expected) {
+ }
+ String underflowString = prefix + '-' + overflow.toString(radix) + postfix;
+ start = prefix.length();
+ end = underflowString.length() - postfix.length();
+ try {
+ throw new AssertionError(Long.parseLong(underflowString, start, end, radix));
+ } catch (NumberFormatException expected) {
+ }
+ }
+ }
+ }
+ }
+
+ private static void assertEquals(String m, int expected, int actual) {
+ if (expected != actual) {
+ throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>');
+ }
+ }
+}
diff --git a/src/test/examplesJava9/backport/LongBackportJava9Main.java b/src/test/examplesJava9/backport/LongBackportJava9Main.java
new file mode 100644
index 0000000..1634a43
--- /dev/null
+++ b/src/test/examplesJava9/backport/LongBackportJava9Main.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2021, 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 backport;
+
+import java.math.BigInteger;
+
+public final class LongBackportJava9Main {
+ private static final long[] interestingValues = {
+ Long.MIN_VALUE,
+ Long.MAX_VALUE,
+ Integer.MIN_VALUE,
+ Integer.MAX_VALUE,
+ Short.MIN_VALUE,
+ Short.MAX_VALUE,
+ Byte.MIN_VALUE,
+ Byte.MAX_VALUE,
+ 0L,
+ -1L,
+ 1L,
+ -42L,
+ 42L
+ };
+
+ public static void main(String[] args) {
+ testParseLongSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+ testParseUnsignedLongSubsequenceWithRadix();
+ }
+
+ private static void testParseLongSubsequenceWithRadix(boolean supportsPlusPrefix) {
+ for (long value : interestingValues) {
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String valueString = prefix + Long.toString(value, radix) + postfix;
+ int start = prefix.length();
+ int end = valueString.length() - postfix.length();
+ assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
+ if (value > 0 && supportsPlusPrefix) {
+ valueString = prefix + "+" + Long.toString(value, radix) + postfix;
+ end++;
+ assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+ } catch (NumberFormatException expected) {
+ }
+
+ BigInteger overflow = new BigInteger("18446744073709551616");
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String overflowString = prefix + overflow.toString(radix) + postfix;
+ int start = prefix.length();
+ int end = overflowString.length() - postfix.length();
+ try {
+ throw new AssertionError(Long.parseLong(overflowString, start, end, radix));
+ } catch (NumberFormatException expected) {
+ }
+ }
+ }
+ }
+ }
+
+ private static void testParseUnsignedLongSubsequenceWithRadix() {
+ for (long value : interestingValues) {
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String valueString = prefix + unsignedLongToBigInteger(value).toString(radix) + postfix;
+ int start = prefix.length();
+ int end = valueString.length() - postfix.length();
+ assertEquals(
+ valueString, value, Long.parseUnsignedLong(valueString, start, end, radix));
+ valueString = prefix + "+" + unsignedLongToBigInteger(value).toString(radix) + postfix;
+ end++;
+ assertEquals(
+ valueString, value, Long.parseUnsignedLong(valueString, start, end, radix));
+ }
+ }
+ }
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+ } catch (NumberFormatException expected) {
+ }
+
+ BigInteger overflow = new BigInteger("18446744073709551616");
+ for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+ for (String prefix : new String[] {"", "x", "xxx", "+", "x+", "xxx+"}) {
+ for (String postfix : new String[] {"", "x", "xxx"}) {
+ String overflowString = prefix + overflow.toString(radix) + postfix;
+ int start = prefix.length();
+ int end = overflowString.length() - postfix.length();
+ try {
+ throw new AssertionError(Long.parseUnsignedLong(overflowString, start, end, radix));
+ } catch (NumberFormatException expected) {
+ }
+ }
+ }
+ }
+ }
+
+ private static BigInteger unsignedLongToBigInteger(long value) {
+ BigInteger bigInt = BigInteger.valueOf(value & 0x7fffffffffffffffL);
+ if (value < 0) {
+ bigInt = bigInt.setBit(Long.SIZE - 1);
+ }
+ return bigInt;
+ }
+
+ private static void assertEquals(String m, long expected, long actual) {
+ if (expected != actual) {
+ throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>');
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 43bbd99..c5ce290 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -362,11 +362,7 @@
@Override
protected void testIntermediateWithMainDexList(
- String packageName,
- Path input,
- int expectedMainDexListSize,
- List<String> mainDexClasses,
- List<String> mainDexOverApproximation)
+ String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses)
throws Throwable {
// Skip those tests.
Assume.assumeTrue(false);
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 4913bc5..67eeeb0 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -99,4 +99,11 @@
}
return self();
}
+
+ public D8TestBuilder addMainDexKeepClassAndMemberRules(Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ addMainDexRules("-keep class " + clazz.getTypeName() + " { *; }");
+ }
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index e169df0..488480c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -107,14 +108,14 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
.run();
}
@@ -146,14 +147,14 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(AndroidApiLevel.N)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
.run();
}
@@ -167,7 +168,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -176,7 +177,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
.run();
}
@@ -190,7 +191,7 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 36, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -199,19 +200,19 @@
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
.run();
}
private void checkLambdaCount(CodeInspector inspector, int maxExpectedCount, String prefix) {
- int count = 0;
+ List<String> found = new ArrayList<>();
for (FoundClassSubject clazz : inspector.allClasses()) {
if (clazz.isSynthesizedJavaLambdaClass() &&
clazz.getOriginalName().startsWith(prefix)) {
- count++;
+ found.add(clazz.getOriginalName());
}
}
- assertEquals(maxExpectedCount, count);
+ assertEquals(StringUtils.lines(found), maxExpectedCount, found.size());
}
private void checkTestMultipleInterfacesCheckCastCount(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 52480be..bd9bd27 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -10,13 +10,13 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OffOrAuto;
@@ -115,21 +115,19 @@
return self();
}
- C withMainDexClass(String... classes) {
- return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
- }
-
C withMainDexKeepClassRules(List<String> classes) {
return withBuilderTransformation(
builder -> {
if (builder instanceof D8Command.Builder) {
((D8Command.Builder) builder)
.addMainDexRules(
- ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+ ListUtils.map(classes, c -> "-keep class " + c + " { *; }"),
+ Origin.unknown());
} else if (builder instanceof R8Command.Builder) {
((R8Command.Builder) builder)
.addMainDexRules(
- ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+ ListUtils.map(classes, c -> "-keep class " + c + " { *; }"),
+ Origin.unknown());
} else {
fail("Unexpected builder type: " + builder.getClass());
}
@@ -486,22 +484,14 @@
public void testLambdaDesugaringWithMainDexList1() throws Throwable {
// Minimal case: there are synthesized classes but not form the main dex class.
testIntermediateWithMainDexList(
- "lambdadesugaring",
- 1,
- ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"),
- ImmutableList.of());
+ "lambdadesugaring", 1, ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"));
}
@Test
public void testLambdaDesugaringWithMainDexList2() throws Throwable {
// Main dex class has many lambdas.
testIntermediateWithMainDexList(
- "lambdadesugaring",
- // TODO(b/180074885): Over approximation not present in R8.
- this instanceof R8RunExamplesAndroidOTest ? 51 : 52,
- ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"),
- // TODO(b/180074885): Over approximation due to invoke-dynamic reference adds as dependency.
- ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$D"));
+ "lambdadesugaring", 98, ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"));
}
@Test
@@ -511,11 +501,7 @@
"interfacemethods",
Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
2,
- ImmutableList.of("interfacemethods.I1"),
- // TODO(b/180074885): Over approximation due to including I1-CC by being derived from I1,
- // but after desugaring I1 does not reference I1$-CC (the static method is moved), so it
- // is incorrect to include I1-CC in the main dex.
- ImmutableList.of("interfacemethods.I1$-CC"));
+ ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC"));
}
@@ -526,11 +512,7 @@
"interfacemethods",
Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
2,
- ImmutableList.of("interfacemethods.I2"),
- // TODO(b/180074885): Over approximation due to including I2$-CC by being derived from I2,
- // but after desugaring I2 does not reference I2$-CC (the default method is moved), so it
- // is incorrect to include I2$-CC in the main dex.
- ImmutableList.of("interfacemethods.I2$-CC"));
+ ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC"));
}
@Test
@@ -544,26 +526,21 @@
}
private void testIntermediateWithMainDexList(
- String packageName,
- int expectedMainDexListSize,
- List<String> mainDexClasses,
- List<String> mainDexOverApproximation)
+ String packageName, int expectedMainDexListSize, List<String> mainDexClasses)
throws Throwable {
testIntermediateWithMainDexList(
packageName,
Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION),
expectedMainDexListSize,
- mainDexClasses,
- mainDexOverApproximation);
+ mainDexClasses);
}
protected void testIntermediateWithMainDexList(
- String packageName,
- Path input,
- int expectedMainDexListSize,
- List<String> mainDexClasses,
- List<String> mainDexOverApproximation)
+ String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses)
throws Throwable {
+ // R8 does not support merging intermediate builds via DEX.
+ assumeFalse(this instanceof R8RunExamplesAndroidOTest);
+
AndroidApiLevel minApi = AndroidApiLevel.K;
// Full build, will be used as reference.
@@ -604,13 +581,8 @@
// Check.
Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size());
- SetView<String> adjustedFull =
- Sets.difference(
- fullMainClasses,
- new HashSet<>(
- ListUtils.map(mainDexOverApproximation, DescriptorUtils::javaTypeToDescriptor)));
- assertEqualSets(adjustedFull, indexedIntermediateMainClasses);
- assertEqualSets(adjustedFull, filePerInputClassIntermediateMainClasses);
+ assertEqualSets(fullMainClasses, indexedIntermediateMainClasses);
+ assertEqualSets(fullMainClasses, filePerInputClassIntermediateMainClasses);
}
<T> void assertEqualSets(Set<T> expected, Set<T> actual) {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 26477d3..e9cd5ff 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -285,13 +285,6 @@
return setMinApi(minApi);
}
- public T setMinApiThreshold(TestRuntime runtime) {
- if (runtime.isDex()) {
- setMinApiThreshold(runtime.asDex().getMinApiLevel());
- }
- return self();
- }
-
public T setMinApi(AndroidApiLevel minApiLevel) {
return setMinApi(minApiLevel.getLevel());
}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java
new file mode 100644
index 0000000..ae574e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidLongStackValueMaxHeightTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"52"};
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public InvalidLongStackValueMaxHeightTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class, Tester.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Tester.class)
+ .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningThatMatches(
+ diagnosticMessage(containsString("The max stack height of 2 is violated")));
+ });
+ }
+
+ @Test()
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Tester.class)
+ .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningThatMatches(
+ diagnosticMessage(containsString("The max stack height of 2 is violated")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public byte[] getMainWithChangedMaxStackHeight() throws Exception {
+ return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 2).transform();
+ }
+
+ public static class Tester {
+
+ public static void test(long x, int y) {
+ System.out.println(x + y);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Tester.test(10L, 42);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
new file mode 100644
index 0000000..71b6637
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidStackHeightTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"42"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public InvalidStackHeightTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The max stack height of 1 is violated"));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The max stack height of 1 is violated"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test()
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+ .enableInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningsMatch(
+ diagnosticMessage(containsString("The max stack height of 1 is violated")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public byte[] getMainWithChangedMaxStackHeight() throws Exception {
+ return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 1).transform();
+ }
+
+ public static class Main {
+
+ @NeverInline
+ private void test(int x, int y) {
+ System.out.println(x + y);
+ }
+
+ public static void main(String[] args) {
+ new Main().test(args.length, 42);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java
new file mode 100644
index 0000000..742472b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.LongStackValuesInFramesTest.LongStackValuesInFramesTest$MainDump.dump;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class LongStackValuesInFramesTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"52"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public LongStackValuesInFramesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Tester.class)
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Tester.class)
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Tester {
+
+ @NeverInline
+ public static void test(long x, int y) {
+ System.out.println(x + y);
+ }
+ }
+
+ public static class Main {
+
+ // This code will be rewritten to:
+ // ldc_w 10
+ // bipush 10
+ // if (args.length == 0) {
+ // invoke Tester.test(JI)V
+ // }
+ // pop
+ // pop2
+ public static void main(String[] args) {
+ long x = 10L;
+ int y = 42;
+ if (args.length == 0) {
+ Tester.test(x, y);
+ }
+ }
+ }
+
+ public static class LongStackValuesInFramesTest$MainDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+ classWriter.visitSource("LongStackValuesInFramesTest.java", null);
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main",
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester",
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest",
+ "Tester",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitLdcInsn(10L);
+ methodVisitor.visitIntInsn(BIPUSH, 42);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitInsn(ARRAYLENGTH);
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFNE, label1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester",
+ "test",
+ "(JI)V",
+ false);
+ Label label2 = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, label2);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 2,
+ new Object[] {Opcodes.LONG, Opcodes.INTEGER});
+ methodVisitor.visitInsn(Opcodes.POP);
+ methodVisitor.visitInsn(Opcodes.POP2);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 3);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
new file mode 100644
index 0000000..113a40f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedGetFieldTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedGetFieldTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Main::foo"};
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public UninitializedGetFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningThatMatches(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningThatMatches(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ public static class Main {
+
+ private Object object;
+
+ private Main() {
+ this.object = new Object();
+ foo(this.object);
+ }
+
+ private void foo(Object object) {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change made is to Main::<init> where we
+ // now putfield and getfield before initializing.
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "foo",
+ "(Ljava/lang/Object;)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
new file mode 100644
index 0000000..30b2a78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedInstanceOfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedInstanceOfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test()
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ boolean expectFailure = parameters.getDexRuntimeVersion().isAtLeast(Version.V7_0_0);
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ expectFailure,
+ result -> result.assertFailureWithErrorThatThrows(VerifyError.class),
+ TestRunResult::assertSuccessWithOutputLines);
+ }
+
+ public UninitializedInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ // The dump is generated from the following code, where we just swap the instanceof with the
+ // initializer call.
+ public static void main(String[] args) {
+ boolean m = (new Object() instanceof Main);
+ }
+ }
+
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ // INSTANCEOF and INVOKESPECIAL is swapped.
+ methodVisitor.visitTypeInsn(
+ INSTANCEOF, "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main");
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
new file mode 100644
index 0000000..86429af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedNewCheckCastTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedNewCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized new is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ public UninitializedNewCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ // The dump is generated from the following code, where we just swap the CheckCast with the
+ // initializer call.
+ public static void main(String[] args) {
+ Main m = (Main) new Object();
+ }
+ }
+
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ // CHECK-CAST has swapped position with INVOKESPECIAL.
+ methodVisitor.visitTypeInsn(
+ CHECKCAST, "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main");
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
new file mode 100644
index 0000000..9a52445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldSelfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldSelfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized this is not assignable"));
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Could not validate stack map frames"));
+ });
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ boolean willFailVerification =
+ parameters.getDexRuntimeVersion().isOlderThan(Version.V5_1_1)
+ || parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1);
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningMessageThatMatches(
+ containsString("The expected type uninitialized this is not assignable"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrowsIf(willFailVerification, VerifyError.class)
+ .assertSuccessWithOutputLinesIf(!willFailVerification, "Main::foo");
+ }
+
+ public UninitializedPutFieldSelfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ private Main main;
+
+ private Main() {
+ this.main = this;
+ this.main.foo();
+ }
+
+ private void foo() {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change that is made is to Main::<init> where we
+ // now putfield before initializing. That will try and assign an uninstantiated type to a field
+ // which is not allowed.
+ public static class MainDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE,
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "main",
+ "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "foo",
+ "()V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
new file mode 100644
index 0000000..e943ebc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2021, 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+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.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Main::foo"};
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test()
+ public void testD8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(dump())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoWarningsMatch(
+ diagnosticMessage(containsString("The expected type uninitialized")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public UninitializedPutFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public static class Main {
+
+ private Object object;
+
+ private Main() {
+ this.object = new Object();
+ foo(this.object);
+ }
+
+ private void foo(Object object) {
+ System.out.println("Main::foo");
+ }
+
+ public static void main(String[] args) {
+ new Main();
+ }
+ }
+
+ // The dump is generated from the above code. The change made is to Main::<init> where we
+ // now putfield before initializing this.
+ static class MainDump implements Opcodes {
+
+ static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "object",
+ "Ljava/lang/Object;");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "foo",
+ "(Ljava/lang/Object;)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Main::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(
+ NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 4161d87..fcc79dc 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.cf.CfCodePrinter;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.io.File;
@@ -66,6 +68,10 @@
return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java");
}
+ protected CfCode getCode(String holderName, String methodName, CfCode code) {
+ return code;
+ }
+
private String getGeneratedClassName() {
return getGeneratedType().getName();
}
@@ -94,7 +100,7 @@
}
private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
- InternalOptions options = new InternalOptions();
+ InternalOptions options = new InternalOptions(factory, new Reporter());
options.testing.readInputStackMaps = true;
JarClassFileReader<DexProgramClass> reader =
new JarClassFileReader<>(
@@ -104,9 +110,13 @@
if (method.isInitializer()) {
continue;
}
- String methodName =
- method.getHolderType().getName() + "_" + method.method.name.toString();
- codePrinter.visitMethod(methodName, method.getCode().asCfCode());
+ String holderName = method.getHolderType().getName();
+ String methodName = method.method.name.toString();
+ String generatedMethodName = holderName + "_" + methodName;
+ CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
+ if (code != null) {
+ codePrinter.visitMethod(generatedMethodName, code);
+ }
}
},
ClassKind.PROGRAM);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
index d84c164..5b2dbe7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -35,10 +35,8 @@
return codeInspector.allClasses().stream()
.filter(
clazz ->
- clazz.isSynthetic()
- && clazz
- .getOriginalName()
- .endsWith(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX))
+ SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
+ clazz.getOriginalReference()))
.findFirst()
.get();
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index 63c1f18..f7aacb5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.classmerging.horizontal;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
@@ -15,7 +14,6 @@
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import java.util.Set;
import java.util.stream.Collectors;
-import org.junit.Ignore;
import org.junit.Test;
public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase {
@@ -25,17 +23,13 @@
}
@Test
- @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove")
public void test() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addOptionsModification(
- options -> {
- options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
- assertFalse(options.horizontalClassMergerOptions().isJavaLambdaMergingEnabled());
- options.horizontalClassMergerOptions().enableJavaLambdaMerging();
- })
+ options ->
+ options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
.addHorizontallyMergedClassesInspectorIf(
enableHorizontalClassMerging && parameters.isDexRuntime(),
inspector -> {
@@ -59,7 +53,7 @@
}
private static boolean isLambda(DexType type) {
- return SyntheticItemsTestUtils.isExternalLambda(
+ return SyntheticItemsTestUtils.isInternalLambda(
Reference.classFromDescriptor(type.toDescriptorString()));
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
new file mode 100644
index 0000000..e7eda20
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class MergedVirtualMethodStaticizerTest extends HorizontalClassMergingTestBase {
+ public MergedVirtualMethodStaticizerTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(Program.class)
+ .addKeepClassAndMembersRules(Program.Main.class)
+ .addOptionsModification(
+ options ->
+ options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging,
+ inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
+ .run(parameters.getRuntime(), Program.Main.class)
+ .assertSuccessWithOutputLines("A::foo", "Staticized::foo", "B::foo");
+ }
+
+ public static class Program {
+
+ @NeverClassInline
+ public static class Staticized {
+ public static final Staticized staticized = new Staticized();
+
+ @NeverInline
+ public void foo() {
+ System.out.println("Staticized::foo");
+ }
+ }
+
+ @NeverClassInline
+ public static class A {
+
+ public void foo() {
+ System.out.println("A::foo");
+ Staticized.staticized.foo();
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+
+ public void foo() {
+ System.out.println("B::foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ new B().foo();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 74d0ad6..441934e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -33,6 +34,9 @@
.addOptionsModification(
options ->
options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging,
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -45,7 +49,8 @@
assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
- assertThat(codeInspector.clazz(C.class), isPresent());
+ assertThat(
+ codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
});
}
@@ -57,6 +62,7 @@
@Target({ElementType.METHOD})
public @interface MethodAnnotation {}
+ @TypeAnnotation
@NeverClassInline
public static class A {
public A() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
index 6f3c8f8..dbc83c8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -99,6 +99,7 @@
}
}
+ @NeverClassInline
public static class C extends Parent implements I {
@Override
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
index 627838f..169a59b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -99,6 +99,7 @@
}
}
+ @NeverClassInline
public static class C extends Parent implements I {
@Override
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 635ec43..11164b1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
@@ -15,6 +16,7 @@
import org.junit.Test;
public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
+
public OverrideAbstractMethodWithDefaultTest(
TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
@@ -29,6 +31,7 @@
options ->
options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspectorIf(
@@ -73,8 +76,10 @@
}
}
+ @NeverClassInline
static class C1 extends B1 implements J {}
+ @NeverClassInline
static class C2 extends B2 {}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index 0e3a098..15ca564 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
@@ -29,6 +30,7 @@
options ->
options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspectorIf(
@@ -52,8 +54,10 @@
}
}
+ @NeverClassInline
public static class A implements I {}
+ @NeverClassInline
public static class B implements I {
@NeverInline
@Override
@@ -69,6 +73,7 @@
}
}
+ @NeverClassInline
public static class C extends A implements J {}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index fa2dcb2..2aea3d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -30,6 +31,7 @@
options ->
options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -58,8 +60,10 @@
public static class Parent implements I {}
+ @NeverClassInline
public static class A extends Parent {}
+ @NeverClassInline
public static class B extends Parent {
@NeverInline
@Override
@@ -68,6 +72,7 @@
}
}
+ @NeverClassInline
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
interface J extends I {
@@ -76,6 +81,7 @@
}
}
+ @NeverClassInline
public static class C extends A implements J {}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 646dbf9..784e875 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -102,7 +102,10 @@
.apply(this::configureProgram)
.setIncludeClassesChecksum(true)
.compile()
- .run(parameters.getRuntime(), testClassName)
+ .run(
+ parameters.getRuntime(),
+ testClassName,
+ parameters.getRuntime().asDex().getVm().getVersion().toString())
.assertSuccess()
.inspect(this::assertDesugaring);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java
new file mode 100644
index 0000000..a2e1120
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, 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.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class IntegerBackportJava9Test extends AbstractBackportTest {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+ public IntegerBackportJava9Test(TestParameters parameters) {
+ super(parameters, Short.class, TEST_JAR, "backport.IntegerBackportJava9Main");
+ // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+ // an actual API level, migrate these tests to IntegerBackportTest.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
new file mode 100644
index 0000000..4bd90c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, 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.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class LongBackportJava9Test extends AbstractBackportTest {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+ public LongBackportJava9Test(TestParameters parameters) {
+ super(parameters, Short.class, TEST_JAR, "backport.LongBackportJava9Main");
+ // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+ // an actual API level, migrate these tests to LongBackportTest.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
index 90c8009..bf4f43f 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
@@ -67,8 +67,8 @@
"false",
"Empty lambda",
"true", // R8 will eliminate the call to the impl method thus making lambdas equal.
- "true",
- "true");
+ "false",
+ "false");
private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index c68ec40..2e97d1e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -57,7 +57,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 811d24a..5fce5e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -57,7 +57,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index fc7a247..071af85 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -70,7 +70,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index eca975d..15684fe 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -67,7 +67,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 3a0a306..7589764 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -99,7 +99,8 @@
}
public static void assertRecordsAreRecords(Path output) throws IOException {
- CodeInspector inspector = new CodeInspector(output, opt -> opt.testing.canUseRecords = true);
+ CodeInspector inspector =
+ new CodeInspector(output, opt -> opt.testing.enableExperimentalRecordDesugaring = true);
for (FoundClassSubject clazz : inspector.allClasses()) {
if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
assertTrue(clazz.getDexProgramClass().isRecord());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index e6d1444..610b061 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -61,7 +61,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index ff540d1..d5d2b1a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
import java.util.List;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -36,20 +37,37 @@
public static List<Object[]> data() {
// TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
return buildParameters(
- getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build());
}
@Test
- public void testJvm() throws Exception {
- testForJvm()
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .enablePreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ // TODO(b/179146128): Add a test for D8 cf to cf.
+ return;
+ }
+ testForD8()
.addProgramClassFileData(PROGRAM_DATA)
- .enablePreview()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+ .compile()
.run(parameters.getRuntime(), MAIN_TYPE)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testR8Cf() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
Path output =
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
@@ -58,7 +76,7 @@
.addKeepMainRule(MAIN_TYPE)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+ .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
.compile()
.writeToZip();
RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 81ae312..1584f41 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -122,11 +122,11 @@
DexEncodedMethod.EMPTY_ARRAY,
new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
false,
- DexProgramClass::invalidChecksumRequest,
- synthesizedFrom);
+ DexProgramClass::invalidChecksumRequest);
return programClass;
}
+ // TODO(b/181636450): Reconsider this test as it no longer reflects the compiler synthetics.
@Test
public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
@@ -154,12 +154,15 @@
builder.addSynthesizedClass(sharedSynthesizedClass);
classes.forEach(builder::addProgramClass);
DexApplication application = builder.build();
+ AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(application));
+ classes.forEach(
+ c -> appView.getSyntheticItems().addLegacySyntheticClass(sharedSynthesizedClass, c));
CollectInfoConsumer consumer = new CollectInfoConsumer();
options.programConsumer = consumer;
ApplicationWriter writer =
new ApplicationWriter(
- AppView.createForD8(AppInfo.createInitialAppInfo(application)),
+ appView,
null,
GraphLens.getIdentityLens(),
InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 278d99c..96220f8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -75,7 +75,7 @@
@Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
- Consumer<R8FullTestBuilder> configurator =
+ ThrowableConsumer<R8FullTestBuilder> configurator =
r8FullTestBuilder ->
r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
ProcessResult processResult =
@@ -84,7 +84,7 @@
ImmutableSet.of(BaseSuperClass.class),
ImmutableSet.of(FeatureClass.class),
FeatureClass.class,
- ConsumerUtils.emptyThrowingConsumer(),
+ ThrowableConsumer.empty(),
configurator);
assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index e6d0ee9..938a8f6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -77,7 +77,7 @@
ImmutableSet.of(BaseSuperClass.class),
ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
FeatureClass.class,
- ConsumerUtils.emptyThrowingConsumer(),
+ ThrowableConsumer.empty(),
R8TestBuilder::enableInliningAnnotations);
assertEquals(processResult.exitCode, 0);
assertEquals(processResult.stdout, EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 780696e..aa298a7 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -18,8 +18,8 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -80,7 +80,7 @@
@Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
- Consumer<R8FullTestBuilder> configurator =
+ ThrowableConsumer<R8FullTestBuilder> configurator =
r8FullTestBuilder ->
r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
ProcessResult processResult =
@@ -89,7 +89,7 @@
ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
FeatureClass.class,
- ConsumerUtils.emptyThrowingConsumer(),
+ ThrowableConsumer.empty(),
configurator);
assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index 46ac75d..496c3d3 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -15,12 +15,11 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.google.common.collect.ImmutableSet;
-import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -49,10 +48,10 @@
@Test
public void testInlining() throws Exception {
assumeTrue(parameters.isDexRuntime());
- Consumer<R8FullTestBuilder> configurator =
+ ThrowableConsumer<R8FullTestBuilder> configurator =
r8FullTestBuilder ->
r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
- ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined =
+ ThrowableConsumer<R8TestCompileResult> ensureInlined =
r8TestCompileResult -> {
// Ensure that isEarly from BaseUtilClass is inlined into the feature
ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index dbb1e76..05d6d3d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -119,14 +120,14 @@
return builder.build();
}
- <E extends Throwable> ProcessResult testR8Splitter(
+ public ProcessResult testR8Splitter(
TestParameters parameters,
Set<Class<?>> baseClasses,
Set<Class<?>> featureClasses,
Class<?> toRun,
- ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
- Consumer<R8FullTestBuilder> r8TestConfigurator)
- throws IOException, CompilationFailedException, E {
+ ThrowableConsumer<R8TestCompileResult> compileResultConsumer,
+ ThrowableConsumer<R8FullTestBuilder> r8TestConfigurator)
+ throws IOException, CompilationFailedException {
R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
if (parameters.isCfRuntime()) {
// Compiling to jar we need to support the same way of loading code at runtime as
@@ -136,19 +137,19 @@
.addKeepClassAndMembersRules(PathClassLoader.class);
}
- r8FullTestBuilder
- .addProgramClasses(SplitRunner.class, RunInterface.class)
- .addProgramClasses(baseClasses)
- .addFeatureSplit(featureClasses.toArray(new Class[0]))
- .addInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(SplitRunner.class)
- .addKeepClassRules(toRun);
+ R8TestCompileResult r8TestCompileResult =
+ r8FullTestBuilder
+ .addProgramClasses(SplitRunner.class, RunInterface.class)
+ .addProgramClasses(baseClasses)
+ .addFeatureSplit(featureClasses.toArray(new Class[0]))
+ .addInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(SplitRunner.class)
+ .addKeepClassRules(toRun)
+ .apply(r8TestConfigurator)
+ .compile()
+ .apply(compileResultConsumer);
- r8TestConfigurator.accept(r8FullTestBuilder);
-
- R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
- compileResultConsumer.accept(r8TestCompileResult);
Path baseOutput = r8TestCompileResult.writeToZip();
return runFeatureOnArt(
toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
index 20c2b51..61b0fd7 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -14,15 +14,12 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.ImmutableSet;
-import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -51,31 +48,25 @@
@Test
public void testDistribution() throws Exception {
assumeTrue(parameters.isDexRuntime());
- Consumer<R8FullTestBuilder> configurator =
- r8FullTestBuilder ->
- r8FullTestBuilder
- .noMinification()
- .enableInliningAnnotations()
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
- ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
- r8TestCompileResult -> {
- r8TestCompileResult.inspect(
- base ->
- assertFalse(base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
- feature ->
- assertTrue(
- feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
- };
+ ThrowableConsumer<R8TestCompileResult> ensureLambdaNotInBase =
+ r8TestCompileResult ->
+ r8TestCompileResult.inspect(
+ base ->
+ assertFalse(
+ base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
+ feature ->
+ assertTrue(
+ feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
ProcessResult processResult =
testR8Splitter(
parameters,
- ImmutableSet.of(BaseSuperClass.class),
- ImmutableSet.of(FeatureClass.class, MyFunction.class),
+ ImmutableSet.of(BaseSuperClass.class, MyFunction.class),
+ ImmutableSet.of(FeatureClass.class),
FeatureClass.class,
ensureLambdaNotInBase,
- configurator);
- assertEquals(processResult.exitCode, 0);
- assertEquals(processResult.stdout, StringUtils.lines("42foobar"));
+ this::configure);
+ assertEquals(0, processResult.exitCode);
+ assertEquals(StringUtils.lines("42foobar"), processResult.stdout);
}
@Test
@@ -87,13 +78,7 @@
.addFeatureSplit(FeatureClass.class)
.addFeatureSplit(Feature2Class.class)
.addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
- .addKeepMethodRules(
- Reference.methodFromMethod(
- BaseSuperClass.class.getDeclaredMethod(
- "keptApplyLambda", MyFunction.class, String.class)))
- .noMinification()
- .enableInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
+ .apply(this::configure)
.compile();
compileResult
@@ -105,6 +90,17 @@
.assertSuccessWithOutputLines("43barfoo");
}
+ private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
+ testBuilder
+ .addKeepMethodRules(
+ Reference.methodFromMethod(
+ BaseSuperClass.class.getDeclaredMethod(
+ "keptApplyLambda", MyFunction.class, String.class)))
+ .enableInliningAnnotations()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel());
+ }
+
public abstract static class BaseSuperClass implements RunInterface {
@Override
public void run() {
diff --git a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
index ede7822..7c8eafe 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
@@ -5,6 +5,7 @@
import static java.util.stream.Collectors.toList;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -20,7 +21,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Assume;
@@ -32,6 +32,16 @@
public class DumpMainDexInputsTest extends TestBase {
private final TestParameters parameters;
+ private final String mainDexRulesForMainDexFile1AndMainDexFile2 =
+ StringUtils.lines(
+ "-keep class " + MainDexFile1.class.getTypeName() + " {",
+ " *;",
+ "}",
+ "-keep class " + MainDexFile2.class.getTypeName() + " {",
+ " *;",
+ "}");
+ private final String mainDexRulesForMainDexFile3 =
+ "-keep class " + MainDexFile3.class.getTypeName();
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
@@ -42,14 +52,33 @@
this.parameters = parameters;
}
- private Path newMainDexListPath() throws IOException {
- Path mainDexPath = temp.newFile("main-dex-list.txt").toPath();
+ private Path mainDexListForMainDexFile1AndMainDexFile2() throws IOException {
+ Path mainDexPath = temp.newFile("mylist1.txt").toPath();
String mainDexList =
StringUtils.lines(toMainDexFormat(MainDexFile1.class), toMainDexFormat(MainDexFile2.class));
Files.write(mainDexPath, mainDexList.getBytes());
return mainDexPath;
}
+ private Path mainDexListForMainDexFile3() throws IOException {
+ Path mainDexPath = temp.newFile("mylist2.txt").toPath();
+ String mainDexList = StringUtils.lines(toMainDexFormat(MainDexFile3.class));
+ Files.write(mainDexPath, mainDexList.getBytes());
+ return mainDexPath;
+ }
+
+ private Path newMainDexRulesPath1() throws IOException {
+ Path mainDexPath = temp.newFile("myrules1.txt").toPath();
+ Files.write(mainDexPath, mainDexRulesForMainDexFile1AndMainDexFile2.getBytes());
+ return mainDexPath;
+ }
+
+ private Path newMainDexRulesPath2() throws IOException {
+ Path mainDexPath = temp.newFile("myrules2.txt").toPath();
+ Files.write(mainDexPath, mainDexRulesForMainDexFile3.getBytes());
+ return mainDexPath;
+ }
+
private static String toMainDexFormat(Class<?> clazz) {
return clazz.getName().replace(".", "/") + FileUtils.CLASS_EXTENSION;
}
@@ -63,7 +92,8 @@
testForD8()
.setMinApi(parameters.getApiLevel())
.addInnerClasses(DumpMainDexInputsTest.class)
- .addMainDexListFiles(Collections.singleton(newMainDexListPath()))
+ .addMainDexListFiles(
+ mainDexListForMainDexFile1AndMainDexFile2(), mainDexListForMainDexFile3())
.addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
@@ -74,24 +104,64 @@
"Dumping main dex list resources may have side effects due to I/O on Paths."))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello, world");
- verifyDumpDir(dumpDir);
+ verifyDumpDir(dumpDir, true);
}
- private void verifyDumpDir(Path dumpDir) throws IOException {
+ @Test
+ public void testD8MainDexRules() throws Exception {
+ Assume.assumeTrue(
+ "pre-native-multidex only",
+ parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K));
+ Path dumpDir = temp.newFolder().toPath();
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(DumpMainDexInputsTest.class)
+ .addMainDexRulesFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
+ .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+ .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .compile()
+ .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ verifyDumpDir(dumpDir, false);
+ }
+
+ @Test
+ public void testR8MainDexRules() throws Exception {
+ Assume.assumeTrue(
+ "pre-native-multidex only",
+ parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K));
+ Path dumpDir = temp.newFolder().toPath();
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(DumpMainDexInputsTest.class)
+ .addMainDexRuleFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
+ .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+ .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addKeepAllClassesRule()
+ .allowDiagnosticMessages()
+ .compile()
+ .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ verifyDumpDir(dumpDir, false);
+ }
+
+ private void verifyDumpDir(Path dumpDir, boolean checkMainDexList) throws IOException {
assertTrue(Files.isDirectory(dumpDir));
List<Path> paths = Files.walk(dumpDir, 1).collect(toList());
boolean hasVerified = false;
for (Path path : paths) {
if (!path.equals(dumpDir)) {
// The non-external run here results in assert code calling application read.
- verifyDump(path);
+ verifyDump(path, checkMainDexList);
hasVerified = true;
}
}
assertTrue(hasVerified);
}
- private void verifyDump(Path dumpFile) throws IOException {
+ private void verifyDump(Path dumpFile, boolean checkMainDexList) throws IOException {
assertTrue(Files.exists(dumpFile));
Path unzipped = temp.newFolder().toPath();
ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
@@ -99,20 +169,27 @@
assertTrue(Files.exists(unzipped.resolve("program.jar")));
assertTrue(Files.exists(unzipped.resolve("library.jar")));
assertTrue(Files.exists(unzipped.resolve("main-dex-list.txt")));
- String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt")));
- List<String> mainDexLines =
- Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList());
- assertEquals(5, mainDexLines.size());
- List<String> expected =
- Stream.of(
- MainDexFile1.class,
- MainDexFile2.class,
- MainDexClass1.class,
- MainDexClass2.class,
- TestClass.class)
- .map(DumpMainDexInputsTest::toMainDexFormat)
- .collect(toList());
- assertEquals(expected, mainDexLines);
+ if (checkMainDexList) {
+ String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt")));
+ List<String> mainDexLines =
+ Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList());
+ assertEquals(6, mainDexLines.size());
+ List<String> expected =
+ Stream.of(
+ MainDexFile1.class,
+ MainDexFile2.class,
+ MainDexFile3.class,
+ MainDexClass1.class,
+ MainDexClass2.class,
+ TestClass.class)
+ .map(DumpMainDexInputsTest::toMainDexFormat)
+ .collect(toList());
+ assertEquals(expected, mainDexLines);
+ } else {
+ String mainDexRules = new String(Files.readAllBytes(unzipped.resolve("main-dex-rules.txt")));
+ assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile1AndMainDexFile2));
+ assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile3));
+ }
}
static class TestClass {
@@ -129,6 +206,8 @@
static class MainDexFile2 {}
+ static class MainDexFile3 {}
+
static class NonMainDex1 {}
static class NonMainDex2 {}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
index 3a09469..a9e28c9 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.StringConsumer.FileConsumer;
-import com.android.tools.r8.utils.AndroidApp;
import java.io.File;
import java.nio.file.Path;
import org.junit.Test;
@@ -15,12 +14,11 @@
@Test
public void buildAndTreeShakeFromDeployJar() throws Exception {
Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
- AndroidApp app =
- buildAndTreeShakeFromDeployJar(
- CompilationMode.RELEASE,
- GMSCORE_V9_DIR,
- true,
- GMSCORE_V9_MAX_SIZE,
- options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath));
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V9_DIR,
+ true,
+ GMSCORE_V9_MAX_SIZE,
+ options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
index 9461c6b..1950e53 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis.sideeffect;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -48,7 +49,7 @@
private void inspect(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(A.class);
- assertThat(classSubject, isPresent());
+ assertThat(classSubject, isAbsent());
// A.inlineable() should be inlined because we should be able to determine that A.<clinit>() can
// safely be postponed.
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 d78a1c7..455c5c2 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
@@ -9,7 +9,11 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
@@ -17,6 +21,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -73,6 +78,44 @@
return 2020;
}
+ private static CfInstruction rewriteToJava9API(
+ DexItemFactory itemFactory, CfInstruction instruction) {
+ // Rewrite static invoke of javaUtilLongParseUnsignedLongStub to j.l.Long.parseUnsignedLong.
+ if (instruction.isInvoke()
+ && instruction
+ .asInvoke()
+ .getMethod()
+ .getName()
+ .toString()
+ .equals("javaLangLongParseUnsignedLongStub")) {
+ CfInvoke invoke = instruction.asInvoke();
+ return new CfInvoke(
+ invoke.getOpcode(),
+ itemFactory.createMethod(
+ itemFactory.createType("Ljava/lang/Long;"),
+ invoke.getMethod().getProto(),
+ itemFactory.createString("parseUnsignedLong")),
+ invoke.isInterface());
+ } else {
+ return instruction;
+ }
+ }
+
+ @Override
+ protected CfCode getCode(String holderName, String methodName, CfCode code) {
+ if (methodName.endsWith("Stub")) {
+ // Don't include stubs targeted only for rewriting in the generated code.
+ return null;
+ }
+ if (holderName.equals("LongMethods") && methodName.equals("parseUnsignedLongWithRadix")) {
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToJava9API(factory, instruction))
+ .collect(Collectors.toList()));
+ }
+ return code;
+ }
+
@Test
public void testBackportsGenerated() throws Exception {
ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
index 0560b97..a3bd157 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
@@ -57,4 +57,9 @@
long asLong = i & 0xffffffffL;
return Long.toString(asLong, radix);
}
+
+ public static int parseIntSubsequenceWithRadix(
+ CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
+ return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
index 643d295..65a60ef 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
@@ -10,6 +10,11 @@
return (int) (l ^ (l >>> 32));
}
+ public static long parseLongSubsequenceWithRadix(
+ CharSequence s, int beginIndex, int endIndex, int radix) {
+ return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
+ }
+
public static long divideUnsigned(long dividend, long divisor) {
// This implementation is adapted from Guava's UnsignedLongs.java and Longs.java.
@@ -92,10 +97,20 @@
return Long.parseUnsignedLong(s, 10);
}
+ private static long javaLangLongParseUnsignedLongStub(
+ CharSequence s, int beginIndex, int endIndex, int radix) {
+ throw new RuntimeException("Stub invoked");
+ }
+
public static long parseUnsignedLongWithRadix(String s, int radix) {
+ return javaLangLongParseUnsignedLongStub(s, 0, s.length(), radix);
+ }
+
+ public static long parseUnsignedLongSubsequenceWithRadix(
+ CharSequence s, int beginIndex, int endIndex, int radix) {
// This implementation is adapted from Guava's UnsignedLongs.java
- int length = s.length();
+ int length = endIndex - beginIndex;
if (length == 0) {
throw new NumberFormatException("empty string");
}
@@ -106,23 +121,23 @@
long maxValueBeforeRadixMultiply = Long.divideUnsigned(-1L, radix);
// If the string starts with '+' and contains at least two characters, skip the plus.
- int start = s.charAt(0) == '+' && length > 1 ? 1 : 0;
+ int start = s.charAt(beginIndex) == '+' && length > 1 ? beginIndex + 1 : beginIndex;
long value = 0;
- for (int pos = start; pos < length; pos++) {
+ for (int pos = start; pos < endIndex; pos++) {
int digit = Character.digit(s.charAt(pos), radix);
if (digit == -1) {
- throw new NumberFormatException(s);
+ throw new NumberFormatException(s.toString());
}
- if (// high bit is already set
- value < 0
+ if ( // high bit is already set
+ value < 0
// or radix multiply will overflow
|| value > maxValueBeforeRadixMultiply
// or digit add will overflow after radix multiply
|| (value == maxValueBeforeRadixMultiply
&& digit > (int) Long.remainderUnsigned(-1L, radix))) {
// Explicit String.concat to work around https://issuetracker.google.com/issues/136596951.
- throw new NumberFormatException("Too large for unsigned long: ".concat(s));
+ throw new NumberFormatException("Too large for unsigned long: ".concat(s.toString()));
}
value = (value * radix) + digit;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index 1740c8a..6001549 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -73,7 +73,7 @@
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS);
options.testing.inlineeIrModifier = this::modifyIr;
})
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
index 1d1f00e..b1e3cfc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -77,24 +77,12 @@
private void inspect(CodeInspector inspector) {
ClassSubject clazz = inspector.clazz(TestClass.class);
- if (parameters.isCfRuntime()) {
- assertThat(inspector.clazz(PairBuilder.class), isPresent());
+ assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
- // const-string canonicalization is disabled in CF, which helps ClassInliner identify
- // PairBuilder as candidate.
- Set<String> expected =
- ImmutableSet.of(StringBuilder.class.getTypeName(), PairBuilder.class.getTypeName());
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
- } else {
- assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
-
- Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
- assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
- }
+ Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
index 4f020fd..24c1c8b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.references.Reference.methodFromMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
@@ -113,8 +114,9 @@
.holder
.toString()
.equals(SimpleLibraryOverride.class.getTypeName())));
- // Check the non-simple run is not inlined.
- assertTrue(
+ // TODO(b/181942160): The non-simple run should ideally not inlined by the class inliner, since
+ // there is an instance of NonSimpleLibraryOverride that is not eligible for class inlining.
+ assertFalse(
"Expected NonSimple.run invoke in:\n" + main.getMethod().codeToString(),
main.streamInstructions()
.anyMatch(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java
new file mode 100644
index 0000000..9ddf887
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, 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.fields;
+
+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.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SwitchOnConstantClassIdAfterBranchPruningTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SwitchOnConstantClassIdAfterBranchPruningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class))
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(0, aClassSubject.allInstanceFields().size());
+
+ MethodSubject mMethodSubject =
+ aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+ assertThat(mMethodSubject, isPresent());
+ assertTrue(
+ mMethodSubject.streamInstructions().noneMatch(x -> x.isIf() || x.isSwitch()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ if (alwaysFalse()) {
+ new B().m();
+ new C().m();
+ }
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ void m() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NeverClassInline
+ static class C {
+
+ void m() {
+ System.out.println("C");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java
new file mode 100644
index 0000000..a2bfb2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2021, 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.fields;
+
+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.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SwitchOnNonConstantClassIdAfterBranchPruningTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SwitchOnNonConstantClassIdAfterBranchPruningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class))
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(1, aClassSubject.allInstanceFields().size());
+
+ MethodSubject mMethodSubject =
+ aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+ assertThat(mMethodSubject, isPresent());
+ assertTrue(
+ mMethodSubject
+ .streamInstructions()
+ .noneMatch(instruction -> instruction.isConstString("C")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ new B().m();
+ if (alwaysFalse()) {
+ new C().m();
+ }
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ void m() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NeverClassInline
+ static class C {
+
+ void m() {
+ System.out.println("C");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
index f489112..209f9f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -48,6 +49,7 @@
.addInnerClasses(InterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.run(TestClass.class)
@@ -84,6 +86,7 @@
Uninstantiated m();
}
+ @NeverClassInline
static class A implements I {
@NeverInline
@@ -97,6 +100,7 @@
// The purpose of this class is merely to avoid that the invoke-interface instruction in
// TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
// I.m() would not be present in the output.
+ @NeverClassInline
@NoHorizontalClassMerging
static class B implements I {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index 7890640..3cbaed0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -49,6 +50,7 @@
.addInnerClasses(NestedInterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.addOptionsModification(
options -> {
@@ -91,6 +93,7 @@
@NoVerticalClassMerging
interface J extends I {}
+ @NeverClassInline
static class A implements J {
@Override
@@ -100,11 +103,13 @@
}
}
+ @NeverClassInline
static class B extends A {}
// The purpose of this class is merely to avoid that the invoke-interface instruction in
// TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
// I.m() would not be present in the output.
+ @NeverClassInline
static class C extends A {}
static class Uninstantiated {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
index 88459a5..a04df37 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
@@ -4,11 +4,17 @@
package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
+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.Assert.assertSame;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -18,23 +24,41 @@
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class SynchronizedMethodTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("In A.m()", "Got NullPointerException");
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
.addInnerClasses(SynchronizedMethodTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
- .run(TestClass.class)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
ClassSubject clazz = inspector.clazz(A.class);
MethodSubject method = clazz.uniqueMethodWithName("m");
+ assertThat(method, isPresent());
// The invoke on the uninstantiated turns into a "throw null", and the synchronized method
// already have a throw instruction in the catch all handler ensuring monitor exit is called.
@@ -43,24 +67,30 @@
.streamInstructions()
.filter(InstructionSubject::isThrow)
.collect(Collectors.toList());
- assertEquals(2, throwInstructions.size());
+ assertEquals(1 + BooleanUtils.intValue(parameters.isDexRuntime()), throwInstructions.size());
- // The inserted "throw null" should still be covered by the catch all to ensure monitor exit
- // is called.
- List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>();
- method.iterateTryCatches().forEachRemaining(tryCatchSubject -> {
- if (tryCatchSubject.hasCatchAll()) {
- catchAllCoveredInstructions.addAll(
- throwInstructions
- .stream()
- .filter(
- throwInstruction ->
- tryCatchSubject.getRange().includes(throwInstruction.getOffset(method)))
- .collect(Collectors.toList()));
- }
- });
- assertEquals(1, catchAllCoveredInstructions.size());
- assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0));
+ if (parameters.isDexRuntime()) {
+ // The inserted "throw null" should still be covered by the catch all to ensure monitor exit
+ // is called.
+ List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>();
+ method
+ .iterateTryCatches()
+ .forEachRemaining(
+ tryCatchSubject -> {
+ if (tryCatchSubject.hasCatchAll()) {
+ catchAllCoveredInstructions.addAll(
+ throwInstructions.stream()
+ .filter(
+ throwInstruction ->
+ tryCatchSubject
+ .getRange()
+ .includes(throwInstruction.getOffset(method)))
+ .collect(Collectors.toList()));
+ }
+ });
+ assertEquals(1, catchAllCoveredInstructions.size());
+ assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0));
+ }
}
static class TestClass {
@@ -79,6 +109,7 @@
}
}
+ @NeverClassInline
static class A {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index ef269dc..49b90e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -32,7 +32,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) {
@@ -72,17 +72,20 @@
dumpAnnotation2(),
dumpAnnotation3())
.addKeepMainRule("Test")
- .addKeepRules("-keep @interface Annotation?")
- .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+ .addKeepRules(
+ "-keep @interface Annotation?",
+ "-neverclassinline class *",
+ "-nohorizontalclassmerging class Test$Inner?")
+ .addKeepRuntimeVisibleParameterAnnotations()
.enableProguardTestOptions()
- .addKeepRules("-neverclassinline class *")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
- .inspect(inspector -> {
- checkClass(inspector.clazz("Test$Inner1"), "Annotation1");
- checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
- checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
- })
+ .inspect(
+ inspector -> {
+ checkClass(inspector.clazz("Test$Inner1"), "Annotation1");
+ checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
+ checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
+ })
.run(parameters.getRuntime(), "Test")
.assertSuccessWithOutput(expectedOutput);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 52131b3..768fbd6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -40,6 +41,7 @@
.addInnerClasses(UnusedInterfaceWithDefaultMethodTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -94,5 +96,6 @@
}
}
+ @NeverClassInline
static class A implements J {}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 16413e0..f6b24da 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -20,12 +21,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
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.Lists;
-import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@@ -63,15 +63,6 @@
&& clazz.getInterfaces().size() == 1;
}
- private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
- Set<DexType> lambdaClasses =
- inspector.allClasses().stream()
- .filter(clazz -> isLambda(clazz.getDexProgramClass()))
- .map(clazz -> clazz.getDexProgramClass().type)
- .collect(Collectors.toSet());
- return lambdaClasses::contains;
- }
-
@Test
public void testJStyleLambdas() throws Exception {
String mainClassName = "class_inliner_lambda_j_style.MainKt";
@@ -116,10 +107,10 @@
.addKeepRules("-neverinline class * { void test*State*(...); }"))
.inspect(
inspector -> {
- // TODO(b/173337498): MainKt$testStateless$1 should be class inlined.
+ // TODO(b/173337498): MainKt$testStateless$1 should always be class inlined.
assertThat(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
- isPresent());
+ notIf(isPresent(), testParameters.isDexRuntime()));
// TODO(b/173337498): MainKt$testStateful$1 should be class inlined.
assertThat(
@@ -209,10 +200,8 @@
.inspect(
inspector -> {
ClassSubject clazz = inspector.clazz(mainClassName);
-
- // TODO(b/141719453): Data class should maybe be class inlined.
assertEquals(
- Sets.newHashSet("class_inliner_data_class.Alpha"),
+ Collections.emptySet(),
collectAccessedTypes(
type -> !type.toSourceString().startsWith("java."),
clazz,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index d3d99ee..2dd90c1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -120,7 +120,7 @@
testForD8()
.setMinApi(AndroidApiLevel.K)
.addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
- .addMainDexListClasses(TestClass.class)
+ .addMainDexKeepClassAndMemberRules(TestClass.class)
.setMainDexListConsumer(consumer)
.compile();
assertTrue(consumer.called);
@@ -141,7 +141,7 @@
testForD8()
.setMinApi(AndroidApiLevel.K)
.addProgramFiles(dexOutput)
- .addMainDexKeepClassRules(TestClass.class)
+ .addMainDexKeepClassAndMemberRules(TestClass.class)
.setMainDexListConsumer(consumer)
.compile();
assertTrue(consumer.called);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 5e88d7d..8252f4d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -8,11 +8,14 @@
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Files;
@@ -26,7 +29,7 @@
static final AndroidApiLevel nativeMultiDexLevel = AndroidApiLevel.L;
- static final String EXPECTED = StringUtils.lines("AB");
+ static final String EXPECTED = StringUtils.lines("A");
private final TestParameters parameters;
@@ -53,8 +56,8 @@
D8TestCompileResult compileResult =
testForD8()
.addInnerClasses(MainDexWithSynthesizedClassesTest.class)
- .addMainDexListClasses(TestClass.class, A.class)
- .setMinApiThreshold(parameters.getApiLevel())
+ .addMainDexKeepClassAndMemberRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
.compile();
checkCompilationResult(compileResult);
}
@@ -66,19 +69,67 @@
D8TestCompileResult intermediateResult =
testForD8()
.addInnerClasses(MainDexWithSynthesizedClassesTest.class)
- .setMinApiThreshold(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
.setIntermediate(true)
.compile();
D8TestCompileResult compileResult =
testForD8()
.addProgramFiles(intermediateResult.writeToZip())
- .addMainDexKeepClassRules(TestClass.class, A.class)
- .setMinApiThreshold(parameters.getApiLevel())
+ .addMainDexKeepClassAndMemberRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
.compile();
checkCompilationResult(compileResult);
}
+ /**
+ * This test checks for maintained support of including synthetics from main-dex-list entries in
+ * the main-dex file. This test simulates that the tracing done at the class-file level has
+ * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
+ * included in the main-dex file.
+ *
+ * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+ */
+ @Test
+ public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+ .addMainDexListClasses(TestClass.class, A.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ checkCompilationResult(compileResult);
+ }
+
+ /**
+ * This test checks for maintained support of including synthetics from main-dex-list entries in
+ * the main-dex file. This test simulates that the tracing done at the class-file level has
+ * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
+ * included in the main-dex file.
+ *
+ * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+ */
+ @Test
+ public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(o -> o.minimalMainDex = true)
+ .addMainDexListClasses(TestClass.class, A.class)
+ .noMinification()
+ .noTreeShaking()
+ .compile();
+ checkCompilationResult(compileResult, compileResult.app);
+ }
+
private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
+ checkCompilationResult(compileResult, compileResult.app);
+ }
+
+ private void checkCompilationResult(TestCompileResult compileResult, AndroidApp app)
+ throws Exception {
if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
< nativeMultiDexLevel.getLevel()) {
compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
@@ -86,7 +137,7 @@
compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED);
}
Path out = temp.newFolder().toPath();
- compileResult.apply(b -> b.app.writeToDirectory(out, OutputMode.DexIndexed));
+ app.writeToDirectory(out, OutputMode.DexIndexed);
Path classes = out.resolve("classes.dex");
Path classes2 = out.resolve("classes2.dex");
assertTrue(Files.exists(classes));
@@ -119,7 +170,7 @@
static class A {
Getter foo() {
- return () -> "A" + new B().foo().get();
+ return () -> "A";
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index fb89853..3727423 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -338,6 +338,7 @@
static class B extends A {
+ @Override
public void method() {
System.out.println("In B.method()");
super.method();
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 6a01e83..85c8459 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -11,50 +12,51 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DataResourceProvider.Visitor;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.DataResourceConsumerForTesting;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.KeepingDiagnosticHandler;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.File;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase {
- private Backend backend;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public AdaptResourceFileNamesTest(Backend backend) {
- this.backend = backend;
+ public AdaptResourceFileNamesTest(TestParameters parameters) {
+ this.parameters = parameters;
}
private static final Path CF_DIR =
@@ -63,15 +65,6 @@
Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
.resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
- private KeepingDiagnosticHandler diagnosticsHandler;
- private ClassNameMapper mapper = null;
-
- @Before
- public void reset() {
- diagnosticsHandler = new KeepingDiagnosticHandler();
- mapper = null;
- }
-
private static String getProguardConfig(
boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
String adaptResourceFilenamesRule;
@@ -86,7 +79,6 @@
return String.join(
System.lineSeparator(),
adaptResourceFilenamesRule,
- "-keeppackagenames adaptresourcefilenames**",
"-keep class adaptresourcefilenames.TestClass {",
" public static void main(...);",
"}");
@@ -114,30 +106,38 @@
}
@Test
- public void testEnabled() throws Exception {
+ public void testEnabled() throws Throwable {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(
getProguardConfigWithNeverInline(true, null),
dataResourceConsumer,
- ToolHelper.consumeString(this::checkR8Renamings));
- // Check that the generated resources have the expected names.
- for (DataEntryResource dataResource : getOriginalDataResources()) {
- assertNotNull(
- "Resource not renamed as expected: " + dataResource.getName(),
- dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
- }
+ R8TestBuilder::enableProguardTestOptions,
+ mapper -> {
+ checkR8Renamings(mapper);
+ // Check that the generated resources have the expected names.
+ for (DataEntryResource dataResource : getOriginalDataResources()) {
+ ImmutableList<String> object =
+ dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
+ if (object == null) {
+ object =
+ dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
+ }
+ assertNotNull("Resource not renamed as expected: " + dataResource.getName(), object);
+ }
+ });
}
@Test
- public void testEnabledWithFilter() throws Exception {
+ public void testEnabledWithFilter() throws Throwable {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
compileWithR8(
getProguardConfigWithNeverInline(true, "**.md"),
dataResourceConsumer,
- ToolHelper.consumeString(this::checkR8Renamings));
+ R8TestBuilder::enableProguardTestOptions,
+ this::checkR8Renamings);
// Check that the generated resources have the expected names.
Map<String, String> expectedRenamings =
- ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
+ ImmutableMap.of("adaptresourcefilenames/B.md", "a/b.md");
for (DataEntryResource dataResource : getOriginalDataResources()) {
assertNotNull(
"Resource not renamed as expected: " + dataResource.getName(),
@@ -147,9 +147,12 @@
}
@Test
- public void testDisabled() throws Exception {
+ public void testDisabled() throws Throwable {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
- compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+ compileWithR8(
+ getProguardConfigWithNeverInline(false, null),
+ dataResourceConsumer,
+ R8TestBuilder::enableProguardTestOptions);
// Check that none of the resources were renamed.
for (DataEntryResource dataResource : getOriginalDataResources()) {
assertNotNull(
@@ -159,22 +162,25 @@
}
@Test
- public void testCollisionBehavior() throws Exception {
+ public void testCollisionBehavior() throws Throwable {
DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
- compileWithR8(
- getProguardConfigWithNeverInline(true, null),
- dataResourceConsumer,
- ToolHelper.consumeString(this::checkR8Renamings),
- ImmutableList.<DataEntryResource>builder()
- .addAll(getOriginalDataResources())
- .add(
- DataEntryResource.fromBytes(
- new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
- .build());
- assertEquals(1, diagnosticsHandler.warnings.size());
- assertThat(
- diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
- containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
+ R8TestCompileResult compileResult =
+ compileWithR8(
+ getProguardConfigWithNeverInline(true, null),
+ dataResourceConsumer,
+ builder -> {
+ builder.enableProguardTestOptions();
+ builder.allowDiagnosticWarningMessages();
+ },
+ this::checkR8Renamings,
+ ImmutableList.<DataEntryResource>builder()
+ .addAll(getOriginalDataResources())
+ .add(DataEntryResource.fromBytes(new byte[0], "a/b.txt", Origin.unknown()))
+ .build());
+ compileResult.inspectDiagnosticMessages(
+ diagnosticMessages ->
+ diagnosticMessages.assertWarningsMatch(
+ diagnosticMessage(containsString("Resource 'a/b.txt' already exists."))));
assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
}
@@ -243,64 +249,59 @@
.count());
}
- private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
- throws CompilationFailedException, IOException {
- return compileWithR8(proguardConfig, dataResourceConsumer, null);
- }
-
- private AndroidApp compileWithR8(
+ private void compileWithR8(
String proguardConfig,
DataResourceConsumer dataResourceConsumer,
- StringConsumer proguardMapConsumer)
- throws CompilationFailedException, IOException {
- return compileWithR8(
- proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
+ ThrowableConsumer<R8FullTestBuilder> builderConsumer)
+ throws Throwable {
+ compileWithR8(proguardConfig, dataResourceConsumer, builderConsumer, null);
}
- private AndroidApp compileWithR8(
+ private void compileWithR8(
String proguardConfig,
DataResourceConsumer dataResourceConsumer,
- StringConsumer proguardMapConsumer,
+ ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+ Consumer<ClassNameMapper> proguardMapConsumer)
+ throws Throwable {
+ compileWithR8(
+ proguardConfig,
+ dataResourceConsumer,
+ builderConsumer,
+ proguardMapConsumer,
+ getOriginalDataResources());
+ }
+
+ private R8TestCompileResult compileWithR8(
+ String proguardConfig,
+ DataResourceConsumer dataResourceConsumer,
+ ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+ Consumer<ClassNameMapper> proguardMapConsumer,
List<DataEntryResource> dataResources)
- throws CompilationFailedException, IOException {
- R8Command command =
- ToolHelper.allowTestProguardOptions(
- ToolHelper.prepareR8CommandBuilder(
- getAndroidApp(dataResources), emptyConsumer(backend), diagnosticsHandler)
- .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
- .addLibraryFiles(runtimeJar(backend))
- .build();
- return ToolHelper.runR8(
- command,
- options -> {
- options.dataResourceConsumer = dataResourceConsumer;
- options.proguardMapConsumer = proguardMapConsumer;
- });
- }
-
- private void checkR8Renamings(String proguardMap) {
- try {
- // Check that the renamings are as expected. These exact renamings are not important as
- // such, but the test expectations rely on them.
- mapper = ClassNameMapper.mapperFromString(proguardMap);
- assertEquals(
- "adaptresourcefilenames.TestClass",
- mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
- assertEquals(
- "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
- assertEquals(
- "adaptresourcefilenames.B$Inner",
- mapper.deobfuscateClassName("adaptresourcefilenames.a"));
- } catch (IOException e) {
- throw new RuntimeException(e);
+ throws Throwable {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR))
+ .addDataResources(dataResources)
+ .addKeepRules(proguardConfig)
+ .apply(builderConsumer)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer)
+ .compile();
+ if (proguardMapConsumer != null) {
+ compile.inspectProguardMap(
+ map -> proguardMapConsumer.accept(ClassNameMapper.mapperFromString(map)));
}
+ return compile;
}
- private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
- AndroidApp.Builder builder = AndroidApp.builder();
- builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
- dataResources.forEach(builder::addDataResource);
- return builder.build();
+ private void checkR8Renamings(ClassNameMapper mapper) {
+ // Check that the renamings are as expected. These exact renamings are not important as
+ // such, but the test expectations rely on them.
+ assertEquals(
+ "adaptresourcefilenames.TestClass",
+ mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
+ assertEquals("adaptresourcefilenames.B", mapper.deobfuscateClassName("a.b"));
+ assertEquals("adaptresourcefilenames.B$Inner", mapper.deobfuscateClassName("a.a"));
}
private static List<DataEntryResource> getOriginalDataResources() {
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index d9b9a3c..218e037 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -69,11 +69,6 @@
options -> {
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
options.enableClassInlining = false;
-
- // TODO(b/179019716): Add support for merging in presence of annotations.
- options.horizontalClassMergerOptions()
- .skipNoClassesOrMembersWithAnnotationsPolicyForTesting =
- true;
})
.addHorizontallyMergedClassesInspector(
inspector ->
diff --git a/src/test/java/com/android/tools/r8/regress/Regress181837660.java b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
new file mode 100644
index 0000000..7fd9e77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2021, 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.regress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dexsplitter.SplitterTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * We need to ensure that we distribute the synthetic items in the features where they where
+ * generated.
+ */
+@RunWith(Parameterized.class)
+public class Regress181837660 extends SplitterTestBase {
+
+ public static final String EXPECTED = StringUtils.lines("42");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public Regress181837660(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDistribution() throws Exception {
+ ProcessResult processResult =
+ testR8Splitter(
+ parameters,
+ ImmutableSet.of(BaseClass.class),
+ ImmutableSet.of(FeatureClass.class),
+ FeatureClass.class,
+ b -> {},
+ this::configure);
+
+ assertEquals(1, processResult.exitCode);
+ // We can't actually read the field since it is in the feature.
+ assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+ }
+
+ @Test
+ public void testRegress181571571() throws Exception {
+ ProcessResult processResult =
+ testR8Splitter(
+ parameters,
+ ImmutableSet.of(BaseClass.class),
+ ImmutableSet.of(FeatureClass.class),
+ FeatureClass.class,
+ b -> {},
+ this::configureNoInlineAnnotations);
+ // TODO(b/181571571): This should not succeed as illustrated by non inlining case
+ assertEquals(0, processResult.exitCode);
+ // We can't actually read the field since it is in the feature.
+ assertFalse(processResult.stderr.contains("NoClassDefFoundError"));
+ }
+
+ private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
+ testBuilder.enableInliningAnnotations().noMinification().setMinApi(parameters.getApiLevel());
+ }
+
+ private void configureNoInlineAnnotations(R8FullTestBuilder testBuilder)
+ throws NoSuchMethodException {
+ testBuilder.noMinification().setMinApi(parameters.getApiLevel());
+ }
+
+ public static class BaseClass {
+ @NeverInline
+ public static String getFromFeature() {
+ return FeatureClass.featureString;
+ }
+ }
+
+ public static class FeatureClass implements RunInterface {
+
+ public static String featureString = "22";
+
+ public static String getAString() {
+ return BaseClass.getFromFeature();
+ }
+
+ @Override
+ public void run() {
+ System.out.println(getAString());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
index 16d9503..0a1f760 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
@@ -7,7 +7,8 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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;
@@ -16,33 +17,34 @@
@RunWith(Parameterized.class)
public class InstantiatedLambdaReceiverTest extends TestBase {
- private Backend backend;
-
private static final String expectedOutput = "In C.m()";
+ private final TestParameters parameters;
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
- public InstantiatedLambdaReceiverTest(Backend backend) {
- this.backend = backend;
+ public InstantiatedLambdaReceiverTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void jvmTest() throws Exception {
- assumeTrue(
- "JVM test independent of Art version - only run when testing on latest",
- ToolHelper.getDexVm().getVersion().isLatest());
- testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
}
@Test
- public void dexTest() throws Exception {
- testForR8(backend)
+ public void r8Test() throws Exception {
+ testForR8(parameters.getBackend())
.addInnerClasses(InstantiatedLambdaReceiverTest.class)
.addKeepMainRule(TestClass.class)
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 6f6ad5a..f98e5ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -44,25 +44,21 @@
public class NonVirtualOverrideTest extends TestBase {
private final TestParameters parameters;
- private final boolean enableClassInlining;
private final boolean enableVerticalClassMerging;
static class Dimensions {
private final Backend backend;
- private final boolean enableClassInlining;
private final boolean enableVerticalClassMerging;
- public Dimensions(
- Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) {
+ public Dimensions(Backend backend, boolean enableVerticalClassMerging) {
this.backend = backend;
- this.enableClassInlining = enableClassInlining;
this.enableVerticalClassMerging = enableVerticalClassMerging;
}
@Override
public int hashCode() {
- return Objects.hash(backend, enableClassInlining, enableVerticalClassMerging);
+ return Objects.hash(backend, enableVerticalClassMerging);
}
@Override
@@ -72,23 +68,19 @@
}
Dimensions other = (Dimensions) o;
return this.backend == other.backend
- && this.enableClassInlining == other.enableClassInlining
&& this.enableVerticalClassMerging == other.enableVerticalClassMerging;
}
}
- @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}")
+ @Parameterized.Parameters(name = "{0}, vertical class merging: {1}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimes().build(),
- BooleanUtils.values(),
BooleanUtils.values());
}
- public NonVirtualOverrideTest(
- TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) {
+ public NonVirtualOverrideTest(TestParameters parameters, boolean enableVerticalClassMerging) {
this.parameters = parameters;
- this.enableClassInlining = enableClassInlining;
this.enableVerticalClassMerging = enableVerticalClassMerging;
}
@@ -162,7 +154,6 @@
.addKeepMainRule(NonVirtualOverrideTestClass.class)
.addOptionsModification(
options -> {
- options.enableClassInlining = dimensions.enableClassInlining;
options.enableVerticalClassMerging = dimensions.enableVerticalClassMerging;
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
})
@@ -173,18 +164,16 @@
@Test
public void test() throws Exception {
// Run the program on Art after is has been compiled with R8.
- String referenceResult =
- expectedResults.apply(!enableClassInlining && isDexVmBetween5_1_1and7_0_0(parameters));
+ String referenceResult = expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters));
R8TestCompileResult compiled =
compilationResults.apply(
- new Dimensions(
- parameters.getBackend(), enableClassInlining, enableVerticalClassMerging));
+ new Dimensions(parameters.getBackend(), enableVerticalClassMerging));
compiled
.run(parameters.getRuntime(), NonVirtualOverrideTestClass.class)
.assertSuccessWithOutput(referenceResult);
// Check that B is present and that it doesn't contain the unused private method m2.
- if (!enableClassInlining && !enableVerticalClassMerging) {
+ if (!enableVerticalClassMerging) {
CodeInspector inspector = compiled.inspector();
ClassSubject classSubject = inspector.clazz(B.class.getName());
assertThat(classSubject, isPresentAndRenamed());
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index ace926f..9246164 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.annotations;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -55,6 +56,7 @@
.addKeepMainRule(TestClass.class)
.addKeepRuntimeVisibleAnnotations()
.enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noMinification()
@@ -94,6 +96,7 @@
void targetedMethod();
}
+ @NeverClassInline
@NoHorizontalClassMerging
static class InterfaceImpl implements Interface {
@@ -104,6 +107,7 @@
}
}
+ @NeverClassInline
@NoHorizontalClassMerging
static class OtherInterfaceImpl implements Interface {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
index 5bb8e48..035ddf7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.references.Reference.methodFromMethod;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -31,6 +32,7 @@
}
}
+ @NeverClassInline
@NoVerticalClassMerging
public static class B extends A {
// Intermediate to A.
@@ -72,6 +74,7 @@
GraphInspector inspector =
testForR8(parameters.getBackend())
.enableGraphInspector()
+ .enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableInliningAnnotations()
.addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 6f3a6cc..46924fd 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -90,6 +90,15 @@
return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
}
+ public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(
+ reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
+ || SyntheticNaming.isSynthetic(
+ reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
+ || SyntheticNaming.isSynthetic(
+ reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3);
+ }
+
public static Matcher<String> containsInternalSyntheticReference() {
return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index f828cfc..0169604 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -517,6 +517,16 @@
static MethodPredicate onName(String name) {
return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
}
+
+ static boolean testContext(MethodPredicate predicate, MethodContext context) {
+ MethodReference reference = context.getReference();
+ return predicate.test(
+ context.accessFlags,
+ reference.getMethodName(),
+ reference.getMethodDescriptor(),
+ null,
+ null);
+ }
}
@FunctionalInterface
@@ -970,4 +980,16 @@
}
});
}
+
+ public ClassFileTransformer setMaxStackHeight(MethodPredicate predicate, int newMaxStack) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(
+ MethodPredicate.testContext(predicate, getContext()) ? newMaxStack : maxStack,
+ maxLocals);
+ }
+ });
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index c35bbf2..f681673 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -42,6 +42,9 @@
private Path getOutputPath(GenerateMainDexListCommand command) {
StringConsumer consumer = command.getMainDexListConsumer();
+ if (consumer instanceof JoiningStringConsumer) {
+ consumer = ((JoiningStringConsumer) consumer).getConsumer();
+ }
if (consumer instanceof StringConsumer.FileConsumer) {
return ((StringConsumer.FileConsumer) consumer).getOutputPath();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 959a2c9..e8818b2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -63,6 +63,10 @@
boolean isConstString(String value, JumboStringMode jumboStringMode);
+ default boolean isConstString(String value) {
+ return isConstString(value, JumboStringMode.ALLOW);
+ }
+
boolean isJumboString();
long getConstNumber();
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
new file mode 100644
index 0000000..ad38c2b
--- /dev/null
+++ b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
@@ -0,0 +1 @@
+a992d1cdf6b957a6e4b06361ea1b8edbaecc3088
\ No newline at end of file
diff --git a/tools/adb.py b/tools/adb.py
index c6cece7..07ca034 100644
--- a/tools/adb.py
+++ b/tools/adb.py
@@ -20,6 +20,8 @@
['adb', '-s', emulator_id, 'uninstall', app_id],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
+ stdout = stdout.decode('UTF-8')
+ stderr = stderr.decode('UTF-8')
if stdout.strip() == 'Success':
# Successfully uninstalled
@@ -40,7 +42,7 @@
def wait_for_emulator(emulator_id):
- stdout = subprocess.check_output(['adb', 'devices'])
+ stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8')
if '{}\tdevice'.format(emulator_id) in stdout:
return True
@@ -51,7 +53,7 @@
while True:
time.sleep(10)
time_waited += 10
- stdout = subprocess.check_output(['adb', 'devices'])
+ stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8')
if '{}\tdevice'.format(emulator_id) not in stdout:
print('... still waiting for connection')
if time_waited >= 5 * 60:
diff --git a/tools/compiledump.py b/tools/compiledump.py
index b5f49d1..3828739 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -147,10 +147,13 @@
if self.if_exists('proguard_input.config'):
print("Unimplemented: proguard_input configuration.")
- def main_dex_resource(self):
+ def main_dex_list_resource(self):
if self.if_exists('main-dex-list.txt'):
print("Unimplemented: main-dex-list.")
+ def main_dex_rules_resource(self):
+ return self.if_exists('main-dex-rules.txt')
+
def build_properties_file(self):
return self.if_exists('build.properties')
@@ -318,6 +321,8 @@
if hasattr(args, 'config_file_consumer') and args.config_file_consumer:
args.config_file_consumer(dump.config_file())
cmd.extend(['--pg-conf', dump.config_file()])
+ if dump.main_dex_rules_resource():
+ cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
if compiler != 'd8':
cmd.extend(['--pg-map-output', '%s.map' % out])
if min_api:
@@ -332,10 +337,9 @@
print(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
return 0
except subprocess.CalledProcessError as e:
- print(e.output)
if not args.nolib and version != 'source':
stacktrace = os.path.join(temp, 'stacktrace')
- open(stacktrace, 'w+').write(e.output)
+ open(stacktrace, 'w+').write(e.output.decode('UTF-8'))
local_map = utils.R8LIB_MAP if version == 'master' else None
hash_or_version = None if version == 'master' else version
print("=" * 80)
@@ -343,6 +347,8 @@
print("=" * 80)
retrace.run(
local_map, hash_or_version, stacktrace, is_hash(version), no_r8lib=False)
+ else:
+ print(e.output.decode('UTF-8'))
return 1
def run(args, otherargs):
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index c4de6e3..1644895 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -8,6 +8,7 @@
import as_utils
import compiledump
import gradle
+import hashlib
import optparse
import os
import shutil
@@ -50,6 +51,7 @@
'folder': None,
'skip_recompilation': False,
'compiler_properties': [],
+ 'internal': False,
}
# This below does not work in python3
defaults.update(fields.items())
@@ -340,8 +342,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/crane',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -353,8 +353,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/jetcaster',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -366,8 +364,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/jetchat',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -379,8 +375,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/jetnews',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -392,8 +386,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/jetsnack',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -405,8 +397,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/jetsurvey',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -418,8 +408,6 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/owl',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
# TODO(b/173167253): Check if monkey testing works.
App({
@@ -431,9 +419,17 @@
'url': 'https://github.com/android/compose-samples',
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/rally',
- # TODO(b/173176042): Fix recompilation
- 'skip_recompilation': True,
}),
+ App({
+ 'id': 'youtube_15_33',
+ 'name': 'youtube_15_33',
+ 'dump_app': 'dump.zip',
+ 'apk_app': 'YouTubeRelease_unsigned.apk',
+ 'folder': 'youtube_15_33',
+ 'internal': True,
+ # TODO(b/181629268): Fix recompilation
+ 'skip_recompilation': True,
+ })
]
@@ -453,8 +449,11 @@
f.write(line)
-def download_app(app_sha):
- utils.DownloadFromGoogleCloudStorage(app_sha)
+def download_app(app_sha, internal, quiet=False):
+ if internal:
+ utils.DownloadFromX20(app_sha)
+ else:
+ utils.DownloadFromGoogleCloudStorage(app_sha, quiet=quiet)
def is_logging_enabled_for(app, options):
@@ -503,11 +502,12 @@
def get_results_for_app(app, options, temp_dir):
app_folder = app.folder if app.folder else app.name + "_" + app.revision
- app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
+ app_dir = (os.path.join(utils.INTERNAL_DUMPS_DIR, app_folder) if app.internal
+ else os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder))
if not os.path.exists(app_dir) and not options.golem:
# Download the app from google storage.
- download_app(app_dir + ".tar.gz.sha1")
+ download_app(app_dir + ".tar.gz.sha1", app.internal)
# Ensure that the dumps are in place
assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \
@@ -789,6 +789,11 @@
warn(' {}-#{}: {}'.format(shrinker, compilation_index, build_status))
continue
+ if options.golem:
+ print('%s(RunTimeRaw): %s ms' % (app.name, result.get('duration')))
+ print('%s(CodeSize): %s' % (app.name, result.get('dex_size')))
+ continue
+
print(' {}-#{}:'.format(shrinker, compilation_index))
dex_size = result.get('dex_size')
msg = ' dex size: {}'.format(dex_size)
@@ -839,10 +844,17 @@
help='What app collection to run',
choices=[collection.name for collection in APP_COLLECTIONS],
action='append')
+ result.add_option('--app-logging-filter', '--app_logging_filter',
+ help='The apps for which to turn on logging',
+ action='append')
result.add_option('--bot',
help='Running on bot, use third_party dependency.',
default=False,
action='store_true')
+ result.add_option('--generate-golem-config', '--generate_golem_config',
+ help='Generate a new config for golem.',
+ default=False,
+ action='store_true')
result.add_option('--debug-agent',
help='Enable Java debug agent and suspend compilation '
'(default disabled)',
@@ -861,15 +873,16 @@
action='store_true')
result.add_option('--hash',
help='The commit of R8 to use')
+ result.add_option('--internal',
+ help='Run internal apps if set, otherwise run opensource',
+ default=False,
+ action='store_true')
result.add_option('--keystore',
help='Path to app.keystore',
default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
result.add_option('--keystore-password', '--keystore_password',
help='Password for app.keystore',
default='android')
- result.add_option('--app-logging-filter', '--app_logging_filter',
- help='The apps for which to turn on logging',
- action='append')
result.add_option('--monkey',
help='Whether to install and run app(s) with monkey',
default=False,
@@ -934,7 +947,7 @@
del options.app
del options.app_collection
else:
- options.apps = APPS
+ options.apps = [app for app in APPS if app.internal == options.internal]
if options.app_logging_filter:
for app_name in options.app_logging_filter:
@@ -960,6 +973,65 @@
return (options, args)
+def print_indented(s, indent):
+ print(' ' * indent + s)
+
+
+def get_sha256(gz_file):
+ with open(gz_file, 'rb') as f:
+ bytes = f.read() # read entire file as bytes
+ return hashlib.sha256(bytes).hexdigest();
+
+
+def get_sha_from_file(sha_file):
+ with open(sha_file, 'r') as f:
+ return f.readlines()[0]
+
+
+def print_golem_config(options):
+ print('// AUTOGENERATED FILE from tools/run_on_app_dump.py in R8 repo')
+ print('part of r8_config;')
+ print('')
+ print('final Suite dumpsSuite = new Suite("OpenSourceAppDumps");')
+ print('')
+ print('createOpenSourceAppBenchmarks() {')
+ print_indented('final cpus = ["Lenovo M90"];', 2)
+ for app in options.apps:
+ if app.folder and not app.internal:
+ indentation = 2;
+ print_indented('{', indentation)
+ indentation = 4
+ print_indented('final name = "%s";' % app.name, indentation)
+ print_indented('final benchmark =', indentation)
+ print_indented(
+ 'new StandardBenchmark(name, [Metric.RunTimeRaw, Metric.CodeSize]);',
+ indentation + 4)
+ print_indented(
+ 'final options = benchmark.addTargets(noImplementation, ["R8"]);',
+ indentation)
+ print_indented('options.cpus = cpus;', indentation)
+ print_indented('options.isScript = true;', indentation)
+ print_indented('options.fromRevision = 9700;', indentation);
+ print_indented('options.mainFile = "tools/run_on_app_dump.py "', indentation)
+ print_indented('"--golem --shrinker r8 --app %s";' % app.name, indentation + 4)
+ app_gz = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app.folder + '.tar.gz')
+ app_sha = app_gz + '.sha1'
+ # Golem uses a sha256 of the file in the cache, and you need to specify that.
+ download_app(app_sha, app.internal, quiet=True)
+ sha256 = get_sha256(app_gz)
+ sha = get_sha_from_file(app_sha)
+ print_indented('final resource = BenchmarkResource("",', indentation)
+ print_indented('type: BenchmarkResourceType.Storage,', indentation + 4)
+ print_indented('uri: "gs://r8-deps/%s",' % sha, indentation + 4)
+ print_indented('hash:', indentation + 4)
+ print_indented('"%s",' % sha256, indentation + 8)
+ print_indented('extract: "gz");', indentation + 4);
+ print_indented('options.resources.add(resource);', indentation)
+ print_indented('dumpsSuite.addBenchmark(name);', indentation)
+ indentation = 2
+ print_indented('}', indentation)
+ print('}')
+
def main(argv):
(options, args) = parse_options(argv)
@@ -969,13 +1041,16 @@
print(options.shrinker)
if options.golem:
- golem.link_third_party()
options.disable_assertions = True
options.no_build = True
options.r8_compilation_steps = 1
options.quiet = True
options.no_logging = True
+ if options.generate_golem_config:
+ print_golem_config(options)
+ return 0
+
with utils.TempDir() as temp_dir:
if options.hash:
# Download r8-<hash>.jar from
diff --git a/tools/test.py b/tools/test.py
index 546a25f..9e2a3e2 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -192,6 +192,9 @@
print('Building desugared library.')
with utils.TempDir() as checkout_dir:
archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir)
+ # Make sure bazel is extracted in third_party.
+ utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
+ utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
(library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir)
desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar))
shutil.copyfile(library_jar, desugar_jdk_libs)
diff --git a/tools/utils.py b/tools/utils.py
index 8b7acb7..1820991 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -79,6 +79,7 @@
# TODO(b/152155164): Remove this when all apps has been migrated.
OPENSOURCE_APPS_FOLDER = os.path.join(THIRD_PARTY, 'opensource_apps')
OPENSOURCE_DUMPS_DIR = os.path.join(THIRD_PARTY, 'opensource-apps')
+INTERNAL_DUMPS_DIR = os.path.join(THIRD_PARTY, 'internal-apps')
BAZEL_SHA_FILE = os.path.join(THIRD_PARTY, 'bazel.tar.gz.sha1')
BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel')
JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1')
@@ -237,15 +238,19 @@
PrintCmd(cmd)
subprocess.check_call(cmd)
-def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False):
+def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False,
+ quiet=False):
suffix = '.bat' if IsWindows() else ''
download_script = 'download_from_google_storage%s' % suffix
cmd = [download_script]
if not auth:
cmd.append('-n')
cmd.extend(['-b', bucket, '-u', '-s', sha1_file])
- PrintCmd(cmd)
- subprocess.check_call(cmd)
+ if not quiet:
+ PrintCmd(cmd)
+ subprocess.check_call(cmd)
+ else:
+ subprocess.check_output(cmd)
def get_sha1(filename):
sha1 = hashlib.sha1()