Rewrite LIR in class merger when possible
Fixes: b/320432664
Change-Id: Iab102fca300cc674c8ccf6819db3604312faf0ab
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 24f0c28..ed2b2cd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -728,39 +728,44 @@
// already have been leveraged.
OptimizationInfoRemover.run(appView, executorService);
- // Perform repackaging.
- if (appView.hasLiveness()) {
- if (options.isRepackagingEnabled()) {
- new Repackaging(appView.withLiveness()).run(executorService, timing);
+ GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging = null;
+ if (appView.hasCfByteCodePassThroughMethods()) {
+ LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+ } else {
+ // Perform repackaging.
+ if (appView.hasLiveness()) {
+ if (options.isRepackagingEnabled()) {
+ new Repackaging(appView.withLiveness()).run(executorService, timing);
+ }
+ assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
}
- assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
+
+ // Rewrite LIR with lens to allow building IR from LIR in class mergers.
+ LirConverter.rewriteLirWithLens(appView, timing, executorService);
+ appView.clearCodeRewritings(executorService, timing);
+
+ if (appView.hasLiveness()) {
+ VerticalClassMerger.createForFinalClassMerging(appView.withLiveness())
+ .runIfNecessary(executorService, timing);
+ }
+
+ // TODO(b/225838009): Move further down.
+ LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+ assert appView.dexItemFactory().verifyNoCachedTypeElements();
+
+ genericContextBuilderBeforeFinalMerging = GenericSignatureContextBuilder.create(appView);
+
+ // Run horizontal class merging. This runs even if shrinking is disabled to ensure
+ // synthetics are always merged.
+ HorizontalClassMerger.createForFinalClassMerging(appView)
+ .runIfNecessary(
+ executorService,
+ timing,
+ finalRuntimeTypeCheckInfoBuilder != null
+ ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
+ : null);
}
- // Rewrite LIR with lens to allow building IR from LIR in class mergers.
- LirConverter.rewriteLirWithLens(appView, timing, executorService);
-
- if (appView.hasLiveness()) {
- VerticalClassMerger.createForFinalClassMerging(appView.withLiveness())
- .runIfNecessary(executorService, timing);
- }
-
- // TODO(b/225838009): Move further down.
- LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
- assert appView.dexItemFactory().verifyNoCachedTypeElements();
-
- GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
- GenericSignatureContextBuilder.create(appView);
-
- // Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
- // are always merged.
- HorizontalClassMerger.createForFinalClassMerging(appView)
- .runIfNecessary(
- executorService,
- timing,
- finalRuntimeTypeCheckInfoBuilder != null
- ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
- : null);
-
// Perform minification.
if (options.getProguardConfiguration().hasApplyMappingFile()) {
timing.begin("apply-mapping");
@@ -823,8 +828,10 @@
new KotlinMetadataRewriter(appView).runForR8(executorService);
timing.end();
- new GenericSignatureRewriter(appView, genericContextBuilderBeforeFinalMerging)
- .run(appView.appInfo().classes(), executorService);
+ if (genericContextBuilderBeforeFinalMerging != null) {
+ new GenericSignatureRewriter(appView, genericContextBuilderBeforeFinalMerging)
+ .run(appView.appInfo().classes(), executorService);
+ }
assert appView.checkForTesting(
() ->
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index c09b0fd..84d8da6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -438,12 +438,15 @@
allCodeProcessed = true;
}
- public void clearCodeRewritings(ExecutorService executorService) throws ExecutionException {
+ public void clearCodeRewritings(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ timing.begin("Clear code rewritings");
setGraphLens(new ClearCodeRewritingGraphLens(withClassHierarchy()));
MemberRebindingIdentityLens memberRebindingIdentityLens =
MemberRebindingIdentityLensFactory.rebuild(withClassHierarchy(), executorService);
setGraphLens(memberRebindingIdentityLens);
+ timing.end();
}
public void flattenGraphLenses() {
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index 96948b9..aad4cc2 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -447,10 +447,6 @@
return null;
}
- public boolean isPublicizerLens() {
- return false;
- }
-
public boolean isVerticalClassMergerLens() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 7da900e..357828b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -10,6 +10,9 @@
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -113,9 +116,29 @@
}
public CatchHandlers<T> rewriteWithLens(GraphLens graphLens, GraphLens codeLens) {
+ IntList targetIndicesToRemove = new IntArrayList();
+ Set<DexType> seenGuards = Sets.newIdentityHashSet();
List<DexType> newGuards =
- ListUtils.mapOrElse(guards, guard -> graphLens.lookupType(guard, codeLens), null);
- return newGuards != null ? new CatchHandlers<>(newGuards, targets) : this;
+ ListUtils.mapOrElse(
+ guards,
+ (index, guard) -> {
+ DexType newGuard = graphLens.lookupType(guard, codeLens);
+ if (seenGuards.add(newGuard)) {
+ return newGuard;
+ }
+ targetIndicesToRemove.add(index);
+ return null;
+ },
+ null);
+ if (newGuards == null) {
+ assert targetIndicesToRemove.isEmpty();
+ return this;
+ }
+ List<T> newTargets =
+ targetIndicesToRemove.isEmpty()
+ ? targets
+ : ListUtils.newArrayListWithoutIndices(targets, targetIndicesToRemove);
+ return new CatchHandlers<>(newGuards, newTargets);
}
public void forEach(BiConsumer<DexType, T> consumer) {
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 ae74a82..453d999 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
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -622,6 +623,21 @@
return true;
}
+ public boolean verifyInvokeInterface(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ if (appView.testing().allowInvokeErrors) {
+ return true;
+ }
+ for (InvokeMethod invoke : this.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
+ DexType holderType = invoke.getInvokedMethod().getHolderType();
+ if (holderType.isArrayType()) {
+ continue;
+ }
+ DexClass holder = appView.definitionFor(holderType, context());
+ assert holder == null || invoke.getInterfaceBit() == holder.isInterface();
+ }
+ return true;
+ }
+
public boolean hasNoMergedClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
MergedClassesCollection mergedClasses = appView.allMergedClasses();
if (mergedClasses == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java
new file mode 100644
index 0000000..b798181
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/IROpcodeUtils.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEINTERFACE;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class IROpcodeUtils {
+
+ public static int fromLirInvokeOpcode(int opcode) {
+ switch (opcode) {
+ case INVOKEDIRECT:
+ case INVOKEDIRECT_ITF:
+ return Opcodes.INVOKE_DIRECT;
+ case INVOKEINTERFACE:
+ return Opcodes.INVOKE_INTERFACE;
+ case INVOKESTATIC:
+ case INVOKESTATIC_ITF:
+ return Opcodes.INVOKE_STATIC;
+ case INVOKESUPER:
+ case INVOKESUPER_ITF:
+ return Opcodes.INVOKE_SUPER;
+ case INVOKEVIRTUAL:
+ return Opcodes.INVOKE_VIRTUAL;
+ default:
+ throw new Unreachable();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
index 8bedc64..28e3846 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.lightir.LirOpcodes;
import org.objectweb.asm.Opcodes;
public enum InvokeType {
@@ -186,6 +187,23 @@
return dexOpcodeRange;
}
+ public int getLirOpcode(boolean isInterface) {
+ switch (this) {
+ case DIRECT:
+ return isInterface ? LirOpcodes.INVOKEDIRECT_ITF : LirOpcodes.INVOKEDIRECT;
+ case INTERFACE:
+ return LirOpcodes.INVOKEINTERFACE;
+ case STATIC:
+ return isInterface ? LirOpcodes.INVOKESTATIC_ITF : LirOpcodes.INVOKESTATIC;
+ case SUPER:
+ return isInterface ? LirOpcodes.INVOKESUPER_ITF : LirOpcodes.INVOKESUPER;
+ case VIRTUAL:
+ return LirOpcodes.INVOKEVIRTUAL;
+ default:
+ throw new Unreachable();
+ }
+ }
+
public boolean isDirect() {
return this == DIRECT;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 676fcc5..72e5dac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -161,7 +161,7 @@
private final DexItemFactory factory;
private final InternalOptions options;
- LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.options = appView.options();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 19bb6dc..f1c8a4336 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
@@ -23,6 +22,7 @@
import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.IncompleteVerticalClassMergerBridgeCode;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -90,7 +90,7 @@
GraphLens graphLens = appView.graphLens();
assert graphLens.isNonIdentityLens();
- assert appView.codeLens().isAppliedLens();
+ assert appView.codeLens().isAppliedLens() || appView.codeLens().isClearCodeRewritingLens();
MemberRebindingIdentityLens memberRebindingIdentityLens =
graphLens.asNonIdentityLens().find(GraphLens::isMemberRebindingIdentityLens);
@@ -104,9 +104,6 @@
timing.begin("LIR->LIR@" + graphLens.getClass().getTypeName());
rewriteLirWithUnappliedLens(appView, executorService);
timing.end();
-
- // At this point all code has been mapped according to the graph lens.
- updateCodeLens(appView);
}
private static void rewriteLirWithUnappliedLens(
@@ -117,10 +114,7 @@
appView.appInfo().classes(),
clazz ->
clazz.forEachProgramMethodMatching(
- m ->
- m.hasCode()
- && !m.getCode().isSharedCodeObject()
- && !appView.isCfByteCodePassThrough(m),
+ m -> m.hasCode() && m.getCode().isLirCode(),
m -> rewriteLirMethodWithLens(m, appView, rewriterUtils)),
appView.options().getThreadingModule(),
executorService);
@@ -133,14 +127,8 @@
ProgramMethod method,
AppView<? extends AppInfoWithClassHierarchy> appView,
LensCodeRewriterUtils rewriterUtils) {
- Code code = method.getDefinition().getCode();
- if (!code.isLirCode()) {
- assert false;
- return;
- }
- LirCode<Integer> lirCode = code.asLirCode();
- LirCode<Integer> rewrittenLirCode =
- lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+ LirCode<Integer> lirCode = method.getDefinition().getCode().asLirCode();
+ LirCode<Integer> rewrittenLirCode = lirCode.rewriteWithLens(method, appView, rewriterUtils);
if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
method.setCode(rewrittenLirCode, appView);
}
@@ -171,67 +159,7 @@
// Clear the reference type cache after conversion to reduce memory pressure.
appView.dexItemFactory().clearTypeElementsCache();
// At this point all code has been mapped according to the graph lens.
- updateCodeLens(appView);
- }
-
- private static void updateCodeLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
- final NonIdentityGraphLens lens = appView.graphLens().asNonIdentityLens();
- if (lens == null) {
- assert false;
- return;
- }
-
- // If the current graph lens is the member rebinding identity lens then code lens is simply
- // the previous lens. This is the same structure as the more complicated case below but where
- // there is no need to rewrite any previous pointers.
- if (lens.isMemberRebindingIdentityLens()) {
- appView.setCodeLens(lens.getPrevious());
- return;
- }
-
- // Otherwise search out where the lens pointing to the member rebinding identity lens.
- NonIdentityGraphLens lensAfterMemberRebindingIdentityLens =
- lens.find(p -> p.getPrevious().isMemberRebindingIdentityLens());
- if (lensAfterMemberRebindingIdentityLens == null) {
- // With the current compiler structure we expect to always find the lens.
- assert false;
- appView.setCodeLens(lens);
- return;
- }
-
- GraphLens codeLens = appView.codeLens();
- MemberRebindingIdentityLens memberRebindingIdentityLens =
- lensAfterMemberRebindingIdentityLens.getPrevious().asMemberRebindingIdentityLens();
-
- // We are assuming that the member rebinding identity lens is always installed after the current
- // applied lens/code lens and also that there should not be a rebinding lens from the compilers
- // first phase (this subroutine is only used after IR conversion for now).
- assert memberRebindingIdentityLens
- == lens.findPrevious(
- p -> p == memberRebindingIdentityLens || p == codeLens || p.isMemberRebindingLens());
-
- // Rewrite the graph lens effects from 'lens' and up to the member rebinding identity lens.
- MemberRebindingIdentityLens rewrittenMemberRebindingLens =
- memberRebindingIdentityLens.toRewrittenMemberRebindingIdentityLens(
- appView, lens, memberRebindingIdentityLens, lens);
-
- // The current previous pointers for the graph lenses are:
- // lens -> ... -> lensAfterMemberRebindingIdentityLens -> memberRebindingIdentityLens -> g
- // we rewrite them now to:
- // rewrittenMemberRebindingLens -> lens -> ... -> lensAfterMemberRebindingIdentityLens -> g
-
- // The above will construct the new member rebinding lens such that it points to the new
- // code-lens point already.
- assert rewrittenMemberRebindingLens.getPrevious() == lens;
-
- // Update the previous pointer on the new code lens to jump over the old member rebinding
- // identity lens.
- lensAfterMemberRebindingIdentityLens.setPrevious(memberRebindingIdentityLens.getPrevious());
-
- // The applied lens can now be updated and the rewritten member rebinding lens installed as
- // the current "unapplied lens".
- appView.setCodeLens(lens);
- appView.setGraphLens(rewrittenMemberRebindingLens);
+ appView.clearCodeRewritings(executorService, timing);
}
private static void finalizeLirMethodToOutputFormat(
@@ -245,12 +173,17 @@
}
Timing onThreadTiming = Timing.empty();
LirCode<Integer> lirCode = code.asLirCode();
- LirCode<Integer> rewrittenLirCode =
- lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+ LirCode<Integer> rewrittenLirCode = lirCode.rewriteWithLens(method, appView, rewriterUtils);
if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
method.setCode(rewrittenLirCode, appView);
}
IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
+ assert irCode.verifyInvokeInterface(appView);
+ if (lirCode.hasTryCatchTable()) {
+ // Vertical class merging may lead to dead catch handlers.
+ // TODO(b/322762660): Ensure IR is valid immediately after IR building.
+ irCode.removeUnreachableBlocks();
+ }
FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
if (appView.options().isGeneratingDex() && changed) {
@@ -275,6 +208,7 @@
for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
assert method.getCode().isLirCode()
|| method.getCode().isSharedCodeObject()
+ || method.getCode() instanceof IncompleteVerticalClassMergerBridgeCode
|| appView.isCfByteCodePassThrough(method)
|| appView.options().skipIR;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index f782127..ae766bd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -118,7 +118,7 @@
// All the code has been processed so the rewriting required by the lenses is done everywhere,
// we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
// lenses with code rewriting are added.
- appView.clearCodeRewritings(executorService);
+ appView.clearCodeRewritings(executorService, Timing.empty());
// Commit synthetics from the primary optimization pass.
commitPendingSyntheticItems(appView);
@@ -203,7 +203,7 @@
// All the code that should be impacted by the lenses inserted between phase 1 and phase 2
// have now been processed and rewritten, we clear code lens rewriting so that the class
// staticizer and phase 3 does not perform again the rewriting.
- appView.clearCodeRewritings(executorService);
+ appView.clearCodeRewritings(executorService, Timing.empty());
// Commit synthetics before creating a builder (otherwise the builder will not include the
// synthetics.)
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 29c7ca6..74c6cfa 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ArgumentUse;
import com.android.tools.r8.graph.ClasspathMethod;
@@ -490,6 +491,10 @@
return positionTable;
}
+ public boolean hasTryCatchTable() {
+ return tryCatchTable != null;
+ }
+
public TryCatchTable getTryCatchTable() {
return tryCatchTable;
}
@@ -802,17 +807,18 @@
metadataMap);
}
- public LirCode<EV> rewriteWithSimpleLens(
- ProgramMethod context, AppView<?> appView, LensCodeRewriterUtils rewriterUtils) {
+ public LirCode<EV> rewriteWithLens(
+ ProgramMethod context,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ LensCodeRewriterUtils rewriterUtils) {
GraphLens graphLens = appView.graphLens();
assert graphLens.isNonIdentityLens();
if (graphLens.isMemberRebindingIdentityLens()) {
return this;
}
- GraphLens codeLens = context.getDefinition().getCode().getCodeLens(appView);
- SimpleLensLirRewriter<EV> rewriter =
- new SimpleLensLirRewriter<>(this, context, graphLens, codeLens, rewriterUtils);
+ LirLensCodeRewriter<EV> rewriter =
+ new LirLensCodeRewriter<>(appView, this, context, rewriterUtils);
return rewriter.rewrite();
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
new file mode 100644
index 0000000..12bb785
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -0,0 +1,438 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.lightir;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.graph.lens.FieldLookupResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.IROpcodeUtils;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
+import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.VerticalClassMergerGraphLens;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LirLensCodeRewriter<EV> extends LirParsedInstructionCallback<EV> {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final ProgramMethod context;
+ private final DexMethod contextReference;
+ private final GraphLens graphLens;
+ private final GraphLens codeLens;
+ private final LensCodeRewriterUtils helper;
+
+ private int numberOfInvokeOpcodeChanges = 0;
+ private Map<LirConstant, LirConstant> constantPoolMapping = null;
+
+ private boolean hasNonTrivialRewritings = false;
+
+ public LirLensCodeRewriter(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ LirCode<EV> code,
+ ProgramMethod context,
+ LensCodeRewriterUtils helper) {
+ super(code);
+ this.appView = appView;
+ this.context = context;
+ this.contextReference = context.getReference();
+ this.graphLens = appView.graphLens();
+ this.codeLens = context.getDefinition().getCode().getCodeLens(appView);
+ this.helper = helper;
+ }
+
+ @Override
+ public int getCurrentValueIndex() {
+ // We do not need to interpret values.
+ return -1;
+ }
+
+ public void onTypeReference(DexType type) {
+ addRewrittenMapping(type, graphLens.lookupType(type, codeLens));
+ }
+
+ public void onFieldReference(DexField field) {
+ FieldLookupResult result = graphLens.lookupFieldResult(field, codeLens);
+ assert !result.hasReadCastType();
+ assert !result.hasWriteCastType();
+ addRewrittenMapping(field, result.getReference());
+ }
+
+ public void onCallSiteReference(DexCallSite callSite) {
+ addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
+ }
+
+ public void onMethodHandleReference(DexMethodHandle methodHandle) {
+ addRewrittenMapping(
+ methodHandle,
+ helper.rewriteDexMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context));
+ }
+
+ public void onProtoReference(DexProto proto) {
+ addRewrittenMapping(proto, helper.rewriteProto(proto));
+ }
+
+ private void onInvoke(DexMethod method, InvokeType type, boolean isInterface) {
+ MethodLookupResult result = graphLens.lookupMethod(method, contextReference, type, codeLens);
+ if (hasPotentialNonTrivialInvokeRewriting(method, type, result)) {
+ hasNonTrivialRewritings = true;
+ return;
+ }
+ int opcode = type.getLirOpcode(isInterface);
+ DexMethod newMethod = result.getReference();
+ InvokeType newType = result.getType();
+ boolean newIsInterface = lookupIsInterface(method, opcode, result);
+ int newOpcode = newType.getLirOpcode(newIsInterface);
+ assert newMethod.getArity() == method.getArity();
+ if (newOpcode != opcode) {
+ assert type == newType
+ || (type.isVirtual() && newType.isInterface())
+ || (type.isInterface() && newType.isVirtual())
+ || (type.isSuper() && newType.isVirtual())
+ : type + " -> " + newType;
+ numberOfInvokeOpcodeChanges++;
+ } else {
+ // All non-type dependent mappings are just rewritten in the content pool.
+ addRewrittenMapping(method, newMethod);
+ }
+ }
+
+ private boolean hasPotentialNonTrivialInvokeRewriting(
+ DexMethod method, InvokeType type, MethodLookupResult result) {
+ VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+ if (verticalClassMergerLens != null) {
+ if (!result.getPrototypeChanges().isEmpty()) {
+ return true;
+ }
+ for (int argumentIndex = 0;
+ argumentIndex < method.getNumberOfArguments(type.isStatic());
+ argumentIndex++) {
+ DexType argumentType = method.getArgumentType(argumentIndex, type.isStatic());
+ if (verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(argumentType)) {
+ return true;
+ }
+ }
+ }
+ assert result.getPrototypeChanges().isEmpty();
+ return false;
+ }
+
+ private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
+ if (item == rewrittenItem) {
+ return;
+ }
+ if (constantPoolMapping == null) {
+ constantPoolMapping =
+ new IdentityHashMap<>(
+ // Avoid using initial capacity larger than the number of actual constants.
+ Math.min(getCode().getConstantPool().length, 32));
+ }
+ LirConstant old = constantPoolMapping.put(item, rewrittenItem);
+ if (old != null && old != rewrittenItem) {
+ throw new Unreachable(
+ "Unexpected rewriting of item: "
+ + item
+ + " to two distinct items: "
+ + rewrittenItem
+ + " and "
+ + old);
+ }
+ }
+
+ @Override
+ public void onInstancePut(DexField field, EV object, EV value) {
+ onFieldPut(field);
+ }
+
+ @Override
+ public void onStaticPut(DexField field, EV value) {
+ onFieldPut(field);
+ }
+
+ private void onFieldPut(DexField field) {
+ if (hasPotentialNonTrivialFieldPutRewriting(field)) {
+ hasNonTrivialRewritings = true;
+ }
+ }
+
+ private boolean hasPotentialNonTrivialFieldPutRewriting(DexField field) {
+ VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+ if (verticalClassMergerLens != null
+ && verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(field.getType())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
+ onInvoke(method, InvokeType.DIRECT, isInterface);
+ }
+
+ @Override
+ public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
+ onInvoke(method, InvokeType.SUPER, isInterface);
+ }
+
+ @Override
+ public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
+ onInvoke(method, InvokeType.VIRTUAL, false);
+ }
+
+ @Override
+ public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
+ onInvoke(method, InvokeType.STATIC, isInterface);
+ }
+
+ @Override
+ public void onInvokeInterface(DexMethod method, List<EV> arguments) {
+ onInvoke(method, InvokeType.INTERFACE, true);
+ }
+
+ private InvokeType getInvokeTypeThatMayChange(int opcode) {
+ if (opcode == LirOpcodes.INVOKEVIRTUAL) {
+ return InvokeType.VIRTUAL;
+ }
+ if (opcode == LirOpcodes.INVOKEINTERFACE) {
+ return InvokeType.INTERFACE;
+ }
+ if (graphLens.isVerticalClassMergerLens()) {
+ if (opcode == LirOpcodes.INVOKESTATIC_ITF) {
+ return InvokeType.STATIC;
+ }
+ if (opcode == LirOpcodes.INVOKESUPER) {
+ return InvokeType.SUPER;
+ }
+ }
+ return null;
+ }
+
+ public LirCode<EV> rewrite() {
+ if (hasNonTrivialMethodChanges()) {
+ return rewriteWithLensCodeRewriter();
+ }
+ assert !hasNonTrivialRewritings;
+ LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
+ if (hasNonTrivialRewritings) {
+ return rewriteWithLensCodeRewriter();
+ }
+ rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
+ return rewriteTryCatchTable(rewritten);
+ }
+
+ private boolean hasNonTrivialMethodChanges() {
+ VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+ if (verticalClassMergerLens != null) {
+ DexMethod previousReference =
+ verticalClassMergerLens.getPreviousMethodSignature(contextReference);
+ if (verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(
+ previousReference.getReturnType())) {
+ return true;
+ }
+ RewrittenPrototypeDescription prototypeChanges =
+ graphLens.lookupPrototypeChangesForMethodDefinition(context.getReference(), codeLens);
+ if (!prototypeChanges.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private LirCode<EV> rewriteWithLensCodeRewriter() {
+ IRCode code =
+ context.buildIR(
+ appView,
+ MethodConversionOptions.forLirPhase(appView)
+ .disableStringSwitchConversion()
+ .setFinalizeAfterLensCodeRewriter());
+ // MethodProcessor argument is only used by unboxing lenses.
+ MethodProcessor methodProcessor = null;
+ new LensCodeRewriter(appView).rewrite(code, context, methodProcessor);
+ DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+ deadCodeRemover.run(code, Timing.empty());
+ IRToLirFinalizer finalizer = new IRToLirFinalizer(appView, deadCodeRemover);
+ LirCode<?> rewritten =
+ finalizer.finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
+ return (LirCode<EV>) rewritten;
+ }
+
+ private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
+ // The code may need to be rewritten by the lens.
+ // First pass scans just the constant pool to see if any types change or if there are any
+ // fields/methods that need to be examined.
+ boolean hasFieldReference = false;
+ boolean hasPotentialRewrittenMethod = false;
+ for (LirConstant constant : code.getConstantPool()) {
+ // RecordFieldValuesPayload is lowered to NewArrayEmpty before lens code rewriting any LIR.
+ assert !(constant instanceof RecordFieldValuesPayload);
+ if (constant instanceof DexType) {
+ onTypeReference((DexType) constant);
+ } else if (constant instanceof DexField) {
+ onFieldReference((DexField) constant);
+ hasFieldReference = true;
+ } else if (constant instanceof DexCallSite) {
+ onCallSiteReference((DexCallSite) constant);
+ } else if (constant instanceof DexMethodHandle) {
+ onMethodHandleReference((DexMethodHandle) constant);
+ } else if (constant instanceof DexProto) {
+ onProtoReference((DexProto) constant);
+ } else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
+ // We might be able to still fast-case this if we can guarantee the method is never
+ // rewritten. Say it is an java.lang.Object reference or if the lens can fast-check it.
+ hasPotentialRewrittenMethod = true;
+ }
+ }
+
+ // If there are potential method rewritings then we need to iterate the instructions as the
+ // rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
+ boolean hasPotentialNonTrivialFieldPutRewriting =
+ hasFieldReference && graphLens.isVerticalClassMergerLens();
+ if (hasPotentialNonTrivialFieldPutRewriting || hasPotentialRewrittenMethod) {
+ for (LirInstructionView view : code) {
+ view.accept(this);
+ if (hasNonTrivialRewritings) {
+ return null;
+ }
+ }
+ }
+
+ if (constantPoolMapping == null) {
+ return code;
+ }
+
+ return code.newCodeWithRewrittenConstantPool(
+ item -> constantPoolMapping.getOrDefault(item, item));
+ }
+
+ private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
+ if (numberOfInvokeOpcodeChanges == 0) {
+ return code;
+ }
+ // Build a small map from method refs to index in case the type-dependent methods are already
+ // in the constant pool.
+ Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>();
+ LirConstant[] rewrittenConstants = code.getConstantPool();
+ for (int i = 0, length = rewrittenConstants.length; i < length; i++) {
+ LirConstant constant = rewrittenConstants[i];
+ if (constant instanceof DexMethod) {
+ methodIndices.put((DexMethod) constant, i);
+ }
+ }
+
+ IRMetadata irMetadata = code.getMetadataForIR();
+ ByteArrayWriter byteWriter = new ByteArrayWriter();
+ LirWriter lirWriter = new LirWriter(byteWriter);
+ List<LirConstant> methodsToAppend = new ArrayList<>(numberOfInvokeOpcodeChanges);
+ for (LirInstructionView view : code) {
+ int opcode = view.getOpcode();
+ // Instructions that do not have an invoke-type change are just mapped via identity.
+ if (LirOpcodes.isOneByteInstruction(opcode)) {
+ lirWriter.writeOneByteInstruction(opcode);
+ continue;
+ }
+ InvokeType type = getInvokeTypeThatMayChange(opcode);
+ if (type == null) {
+ int size = view.getRemainingOperandSizeInBytes();
+ lirWriter.writeInstruction(opcode, size);
+ while (size-- > 0) {
+ lirWriter.writeOperand(view.getNextU1());
+ }
+ continue;
+ }
+ // This is potentially an invoke with a type change, in such cases the method is mapped with
+ // the instruction updated to the new type. The constant pool is amended with the mapped
+ // method if needed.
+ int constantIndex = view.getNextConstantOperand();
+ DexMethod method = (DexMethod) code.getConstantItem(constantIndex);
+ MethodLookupResult result =
+ graphLens.lookupMethod(method, context.getReference(), type, codeLens);
+ boolean newIsInterface = lookupIsInterface(method, opcode, result);
+ InvokeType newType = result.getType();
+ int newOpcode = newType.getLirOpcode(newIsInterface);
+ if (newOpcode != opcode) {
+ --numberOfInvokeOpcodeChanges;
+ if (newType != type) {
+ irMetadata.record(IROpcodeUtils.fromLirInvokeOpcode(newOpcode));
+ }
+ constantIndex =
+ methodIndices.computeIfAbsent(
+ result.getReference(),
+ ref -> {
+ methodsToAppend.add(ref);
+ return rewrittenConstants.length + methodsToAppend.size() - 1;
+ });
+ }
+ int constantIndexSize = ByteUtils.intEncodingSize(constantIndex);
+ int remainingSize = view.getRemainingOperandSizeInBytes();
+ lirWriter.writeInstruction(newOpcode, constantIndexSize + remainingSize);
+ ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand);
+ while (remainingSize-- > 0) {
+ lirWriter.writeOperand(view.getNextU1());
+ }
+ }
+ assert numberOfInvokeOpcodeChanges == 0;
+ // Note that since we assume 'null' in the mapping is identity this may end up with a stale
+ // reference to a no longer used method. That is not an issue as it will be pruned when
+ // building IR again, it is just a small and size overhead.
+ LirCode<EV> newCode =
+ code.copyWithNewConstantsAndInstructions(
+ irMetadata,
+ ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend),
+ byteWriter.toByteArray());
+ return newCode;
+ }
+
+ // TODO(b/157111832): This should be part of the graph lens lookup result.
+ private boolean lookupIsInterface(DexMethod method, int opcode, MethodLookupResult result) {
+ VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
+ if (verticalClassMergerLens != null
+ && verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(method.getHolderType())) {
+ DexClass clazz = appView.definitionFor(result.getReference().getHolderType());
+ if (clazz != null) {
+ return clazz.isInterface();
+ }
+ }
+ return LirOpcodeUtils.getInterfaceBitFromInvokeOpcode(opcode);
+ }
+
+ private LirCode<EV> rewriteTryCatchTable(LirCode<EV> code) {
+ TryCatchTable tryCatchTable = code.getTryCatchTable();
+ if (tryCatchTable == null) {
+ return code;
+ }
+ TryCatchTable newTryCatchTable = tryCatchTable.rewriteWithLens(graphLens, codeLens);
+ return code.newCodeWithRewrittenTryCatchTable(newTryCatchTable);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
new file mode 100644
index 0000000..944bfce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.lightir;
+
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEDIRECT_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEINTERFACE;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESTATIC_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKESUPER_ITF;
+import static com.android.tools.r8.lightir.LirOpcodes.INVOKEVIRTUAL;
+
+public class LirOpcodeUtils {
+
+ public static boolean getInterfaceBitFromInvokeOpcode(int opcode) {
+ switch (opcode) {
+ case INVOKEDIRECT_ITF:
+ case INVOKEINTERFACE:
+ case INVOKESTATIC_ITF:
+ case INVOKESUPER_ITF:
+ return true;
+ default:
+ assert opcode == INVOKEDIRECT
+ || opcode == INVOKESTATIC
+ || opcode == INVOKESUPER
+ || opcode == INVOKEVIRTUAL;
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java b/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
deleted file mode 100644
index 1a4d065..0000000
--- a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.lightir;
-
-import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InvokeType;
-import com.android.tools.r8.ir.code.Opcodes;
-import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
-import com.android.tools.r8.lightir.LirCode.TryCatchTable;
-import com.android.tools.r8.utils.ArrayUtils;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-public class SimpleLensLirRewriter<EV> extends LirParsedInstructionCallback<EV> {
-
- private final ProgramMethod context;
- private final DexMethod contextReference;
- private final GraphLens graphLens;
- private final GraphLens codeLens;
- private final LensCodeRewriterUtils helper;
-
- private int numberOfInvokeTypeChanges = 0;
- private Map<LirConstant, LirConstant> constantPoolMapping = null;
-
- public SimpleLensLirRewriter(
- LirCode<EV> code,
- ProgramMethod context,
- GraphLens graphLens,
- GraphLens codeLens,
- LensCodeRewriterUtils helper) {
- super(code);
- this.context = context;
- this.contextReference = context.getReference();
- this.graphLens = graphLens;
- this.codeLens = codeLens;
- this.helper = helper;
- }
-
- @Override
- public int getCurrentValueIndex() {
- // We do not need to interpret values.
- return -1;
- }
-
- public void onTypeReference(DexType type) {
- addRewrittenMapping(type, graphLens.lookupType(type, codeLens));
- }
-
- public void onFieldReference(DexField field) {
- addRewrittenMapping(field, graphLens.lookupField(field, codeLens));
- }
-
- public void onCallSiteReference(DexCallSite callSite) {
- addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
- }
-
- public void onMethodHandleReference(DexMethodHandle methodHandle) {
- addRewrittenMapping(
- methodHandle,
- helper.rewriteDexMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context));
- }
-
- public void onProtoReference(DexProto proto) {
- addRewrittenMapping(proto, helper.rewriteProto(proto));
- }
-
- private void onInvoke(DexMethod method, InvokeType type) {
- MethodLookupResult result = graphLens.lookupMethod(method, contextReference, type, codeLens);
- if (result.getType() != type) {
- assert (type == InvokeType.VIRTUAL && result.getType() == InvokeType.INTERFACE)
- || (type == InvokeType.INTERFACE && result.getType() == InvokeType.VIRTUAL);
- numberOfInvokeTypeChanges++;
- } else {
- // All non-type dependent mappings are just rewritten in the content pool.
- addRewrittenMapping(method, result.getReference());
- }
- }
-
- private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
- if (item == rewrittenItem) {
- return;
- }
- if (constantPoolMapping == null) {
- constantPoolMapping =
- new IdentityHashMap<>(
- // Avoid using initial capacity larger than the number of actual constants.
- Math.min(getCode().getConstantPool().length, 32));
- }
- LirConstant old = constantPoolMapping.put(item, rewrittenItem);
- if (old != null && old != rewrittenItem) {
- throw new Unreachable(
- "Unexpected rewriting of item: "
- + item
- + " to two distinct items: "
- + rewrittenItem
- + " and "
- + old);
- }
- }
-
- @Override
- public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
- onInvoke(method, InvokeType.DIRECT);
- }
-
- @Override
- public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
- onInvoke(method, InvokeType.SUPER);
- }
-
- @Override
- public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
- onInvoke(method, InvokeType.VIRTUAL);
- }
-
- @Override
- public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
- onInvoke(method, InvokeType.STATIC);
- }
-
- @Override
- public void onInvokeInterface(DexMethod method, List<EV> arguments) {
- onInvoke(method, InvokeType.INTERFACE);
- }
-
- private InvokeType getInvokeTypeThatMayChange(int opcode) {
- if (opcode == LirOpcodes.INVOKEVIRTUAL) {
- return InvokeType.VIRTUAL;
- }
- if (opcode == LirOpcodes.INVOKEINTERFACE) {
- return InvokeType.INTERFACE;
- }
- return null;
- }
-
- public LirCode<EV> rewrite() {
- LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
- rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
- return rewriteTryCatchTable(rewritten);
- }
-
- private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
- // The code may need to be rewritten by the lens.
- // First pass scans just the constant pool to see if any types change or if there are any
- // fields/methods that need to be examined.
- boolean hasPotentialRewrittenMethod = false;
- for (LirConstant constant : code.getConstantPool()) {
- // RecordFieldValuesPayload is lowered to NewArrayEmpty before lens code rewriting any LIR.
- assert !(constant instanceof RecordFieldValuesPayload);
- if (constant instanceof DexType) {
- onTypeReference((DexType) constant);
- } else if (constant instanceof DexField) {
- onFieldReference((DexField) constant);
- } else if (constant instanceof DexCallSite) {
- onCallSiteReference((DexCallSite) constant);
- } else if (constant instanceof DexMethodHandle) {
- onMethodHandleReference((DexMethodHandle) constant);
- } else if (constant instanceof DexProto) {
- onProtoReference((DexProto) constant);
- } else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
- // We might be able to still fast-case this if we can guarantee the method is never
- // rewritten. Say it is an java.lang.Object reference or if the lens can fast-check it.
- hasPotentialRewrittenMethod = true;
- }
- }
-
- // If there are potential method rewritings then we need to iterate the instructions as the
- // rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
- if (hasPotentialRewrittenMethod) {
- for (LirInstructionView view : code) {
- view.accept(this);
- }
- }
-
- if (constantPoolMapping == null) {
- return code;
- }
-
- return code.newCodeWithRewrittenConstantPool(
- item -> constantPoolMapping.getOrDefault(item, item));
- }
-
- private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
- if (numberOfInvokeTypeChanges == 0) {
- return code;
- }
- // Build a small map from method refs to index in case the type-dependent methods are already
- // in the constant pool.
- Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>();
- LirConstant[] rewrittenConstants = code.getConstantPool();
- for (int i = 0, length = rewrittenConstants.length; i < length; i++) {
- LirConstant constant = rewrittenConstants[i];
- if (constant instanceof DexMethod) {
- methodIndices.put((DexMethod) constant, i);
- }
- }
-
- IRMetadata irMetadata = code.getMetadataForIR();
- ByteArrayWriter byteWriter = new ByteArrayWriter();
- LirWriter lirWriter = new LirWriter(byteWriter);
- List<LirConstant> methodsToAppend = new ArrayList<>(numberOfInvokeTypeChanges);
- for (LirInstructionView view : code) {
- int opcode = view.getOpcode();
- // Instructions that do not have an invoke-type change are just mapped via identity.
- if (LirOpcodes.isOneByteInstruction(opcode)) {
- lirWriter.writeOneByteInstruction(opcode);
- continue;
- }
- InvokeType type = getInvokeTypeThatMayChange(opcode);
- if (type == null) {
- int size = view.getRemainingOperandSizeInBytes();
- lirWriter.writeInstruction(opcode, size);
- while (size-- > 0) {
- lirWriter.writeOperand(view.getNextU1());
- }
- continue;
- }
- // This is potentially an invoke with a type change, in such cases the method is mapped with
- // the instruction updated to the new type. The constant pool is amended with the mapped
- // method if needed.
- int constantIndex = view.getNextConstantOperand();
- DexMethod method = (DexMethod) code.getConstantItem(constantIndex);
- MethodLookupResult result =
- graphLens.lookupMethod(method, context.getReference(), type, codeLens);
- if (result.getType() != type) {
- --numberOfInvokeTypeChanges;
- if (result.getType().isVirtual()) {
- opcode = LirOpcodes.INVOKEVIRTUAL;
- irMetadata.record(Opcodes.INVOKE_VIRTUAL);
- } else if (result.getType().isInterface()) {
- opcode = LirOpcodes.INVOKEINTERFACE;
- irMetadata.record(Opcodes.INVOKE_INTERFACE);
- } else {
- throw new Unreachable(
- "Unexpected change of invoke that may need an interface bit set: "
- + result.getType());
- }
- constantIndex =
- methodIndices.computeIfAbsent(
- result.getReference(),
- ref -> {
- methodsToAppend.add(ref);
- return rewrittenConstants.length + methodsToAppend.size() - 1;
- });
- }
- int constantIndexSize = ByteUtils.intEncodingSize(constantIndex);
- int remainingSize = view.getRemainingOperandSizeInBytes();
- lirWriter.writeInstruction(opcode, constantIndexSize + remainingSize);
- ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand);
- while (remainingSize-- > 0) {
- lirWriter.writeOperand(view.getNextU1());
- }
- }
- assert numberOfInvokeTypeChanges == 0;
- // Note that since we assume 'null' in the mapping is identity this may end up with a stale
- // reference to a no longer used method. That is not an issue as it will be pruned when
- // building IR again, it is just a small and size overhead.
- LirCode<EV> newCode =
- code.copyWithNewConstantsAndInstructions(
- irMetadata,
- ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend),
- byteWriter.toByteArray());
- return newCode;
- }
-
- private LirCode<EV> rewriteTryCatchTable(LirCode<EV> code) {
- TryCatchTable tryCatchTable = code.getTryCatchTable();
- if (tryCatchTable == null) {
- return code;
- }
- TryCatchTable newTryCatchTable = tryCatchTable.rewriteWithLens(graphLens, codeLens);
- return code.newCodeWithRewrittenTryCatchTable(newTryCatchTable);
- }
-}
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 5272500..d6bc517 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -14,7 +14,6 @@
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.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.InnerClassAttribute;
@@ -22,7 +21,6 @@
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.SortedProgramPackageCollection;
import com.android.tools.r8.graph.fixup.TreeFixerBase;
-import com.android.tools.r8.graph.lens.NestedGraphLens;
import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.repackaging.RepackagingLens.Builder;
@@ -113,18 +111,7 @@
assert false;
}
}.fixupClasses(appView.appInfo().classesWithDeterministicOrder());
- NestedGraphLens emptyRepackagingLens =
- new NestedGraphLens(appView) {
- @Override
- protected boolean isLegitimateToHaveEmptyMappings() {
- return true;
- }
-
- @Override
- public <T extends DexReference> boolean isSimpleRenaming(T from, T to) {
- return getPrevious().isSimpleRenaming(from, to);
- }
- };
+ RepackagingLens emptyRepackagingLens = new RepackagingLens.Builder().buildEmpty(appView);
DirectMappedDexApplication newApplication =
appView
.appInfo()
@@ -479,58 +466,4 @@
|| appView.appInfo().getMissingClasses().contains(type);
}
}
-
- /** Testing only. */
- public static class SuffixRenamingRepackagingConfiguration implements RepackagingConfiguration {
-
- private final String classNameSuffix;
- private final DexItemFactory dexItemFactory;
-
- public SuffixRenamingRepackagingConfiguration(
- String classNameSuffix, DexItemFactory dexItemFactory) {
- this.classNameSuffix = classNameSuffix;
- this.dexItemFactory = dexItemFactory;
- }
-
- @Override
- public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
- // Don't change the package of classes.
- return pkg.getPackageDescriptor();
- }
-
- @Override
- public boolean isPackageInTargetLocation(ProgramPackage pkg) {
- return true;
- }
-
- @Override
- public DexType getRepackagedType(
- DexProgramClass clazz,
- DexProgramClass outerClass,
- String newPackageDescriptor,
- BiMap<DexType, DexType> mappings) {
- DexType repackagedDexType = clazz.getType();
- // Rename the class consistently with its outer class.
- if (outerClass != null) {
- String simpleName = clazz.getType().getSimpleName();
- String outerClassSimpleName = outerClass.getType().getSimpleName();
- if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
- String newSimpleName =
- mappings.get(outerClass.getType()).getSimpleName()
- + simpleName.substring(outerClassSimpleName.length());
- repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
- }
- }
- // Append the class name suffix to all classes.
- repackagedDexType = repackagedDexType.addSuffix(classNameSuffix, dexItemFactory);
- // Ensure that the generated name is unique.
- DexType finalRepackagedDexType = repackagedDexType;
- for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
- finalRepackagedDexType =
- repackagedDexType.addSuffix(
- Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
- }
- return finalRepackagedDexType;
- }
- }
}
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 0b40c72..acc7ed6 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
+import java.util.Collections;
import java.util.Map;
public class RepackagingLens extends NestedGraphLens {
@@ -36,7 +37,8 @@
@Override
public String lookupPackageName(String pkg) {
- return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg);
+ String previousPkg = getPrevious().lookupPackageName(pkg);
+ return packageRenamings.getOrDefault(previousPkg, previousPkg);
}
@Override
@@ -101,5 +103,16 @@
return new RepackagingLens(
appView, newFieldSignatures, newMethodSignatures, newTypes, packageRenamings);
}
+
+ public RepackagingLens buildEmpty(AppView<AppInfoWithLiveness> appView) {
+ return new RepackagingLens(
+ appView, EMPTY_FIELD_MAP, EMPTY_METHOD_MAP, EMPTY_TYPE_MAP, Collections.emptyMap()) {
+
+ @Override
+ protected boolean isLegitimateToHaveEmptyMappings() {
+ return true;
+ }
+ };
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 1210325..22d19f7 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntListIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -208,6 +210,35 @@
return list;
}
+ public static <T> List<T> newArrayListWithoutIndices(List<T> list, IntList indicesToRemove) {
+ // Verify each index to remove is unique and in bounds.
+ assert indicesToRemove.stream().distinct().count() == indicesToRemove.size();
+ assert indicesToRemove.stream().allMatch(indexToRemove -> indexToRemove < list.size());
+ if (indicesToRemove.size() == list.size()) {
+ return new ArrayList<>();
+ }
+ List<T> result = new ArrayList<>(list.size() - indicesToRemove.size());
+ IntListIterator indicesToRemoveIterator = indicesToRemove.iterator();
+ int nextIndexToRemove = indicesToRemoveIterator.nextInt();
+ for (int i = 0; i < list.size(); i++) {
+ assert i <= nextIndexToRemove;
+ if (i == nextIndexToRemove) {
+ if (indicesToRemoveIterator.hasNext()) {
+ nextIndexToRemove = indicesToRemoveIterator.nextInt();
+ assert nextIndexToRemove > i;
+ } else {
+ for (int j = i + 1; j < list.size(); j++) {
+ result.add(list.get(j));
+ }
+ break;
+ }
+ } else {
+ result.add(list.get(i));
+ }
+ }
+ return result;
+ }
+
public static <T> ArrayList<T> newInitializedArrayList(int size, T element) {
ArrayList<T> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 18e8417..d5e839e 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -18,11 +18,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
-import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.conversion.LirConverter;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
@@ -248,46 +244,12 @@
return lens;
}
- // TODO(b/320432664): For code objects where the rewriting is an alpha renaming we can rewrite the
- // LIR directly without building IR.
private void rewriteCodeWithLens(ExecutorService executorService, Timing timing)
throws ExecutionException {
if (mode.isInitial()) {
return;
}
-
- timing.begin("Rewrite code");
- MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
- OneTimeMethodProcessor.Builder methodProcessorBuilder =
- OneTimeMethodProcessor.builder(eventConsumer, appView);
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- clazz.forEachProgramMethodMatching(
- method ->
- method.hasCode()
- && !(method.getCode() instanceof IncompleteVerticalClassMergerBridgeCode),
- methodProcessorBuilder::add);
- }
-
- IRConverter converter = new IRConverter(appView);
- converter.clearEnumUnboxer();
- converter.clearServiceLoaderRewriter();
- OneTimeMethodProcessor methodProcessor = methodProcessorBuilder.build();
- methodProcessor.forEachWaveWithExtension(
- (method, methodProcessingContext) ->
- converter.processDesugaredMethod(
- method,
- OptimizationFeedbackIgnore.getInstance(),
- methodProcessor,
- methodProcessingContext,
- MethodConversionOptions.forLirPhase(appView)
- .disableStringSwitchConversion()
- .setFinalizeAfterLensCodeRewriter()),
- options.getThreadingModule(),
- executorService);
-
- // Clear type elements created during IR processing.
- dexItemFactory.clearTypeElementsCache();
- timing.end();
+ LirConverter.rewriteLirWithLens(appView, timing, executorService);
}
private void updateArtProfiles(
@@ -410,7 +372,7 @@
return;
}
timing.begin("Mark rewritten with lens");
- appView.clearCodeRewritings(executorService);
+ appView.clearCodeRewritings(executorService, timing);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index 7fa9f56..fcbf5eb 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -100,6 +100,10 @@
this.staticizedMethods = staticizedMethods;
}
+ public boolean hasInterfaceBeenMergedIntoClass(DexType type) {
+ return mergedClasses.hasInterfaceBeenMergedIntoClass(type);
+ }
+
private boolean isMerged(DexMethod method) {
return mergedMethods.contains(method);
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
index 31d25ab..dbe28ca 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
@@ -11,6 +11,7 @@
private final InternalOptions options;
private boolean enabled = true;
+ private boolean enableInitial = true;
public VerticalClassMergerOptions(InternalOptions options) {
this.options = options;
@@ -20,9 +21,18 @@
setEnabled(false);
}
+ public void disableInitial() {
+ enableInitial = false;
+ }
+
public boolean isEnabled(ClassMergerMode mode) {
- assert mode != null;
- return enabled && options.isOptimizing() && options.isShrinking();
+ if (!enabled || !options.isOptimizing() || !options.isShrinking()) {
+ return false;
+ }
+ if (mode.isInitial() && !enableInitial) {
+ return false;
+ }
+ return true;
}
public void setEnabled(boolean enabled) {
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 384043c..228536e 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -36,7 +36,7 @@
@Parameters(name = "{0}: {1}")
public static Collection<Object[]> data() {
return buildParameters(
- getTestParameters().withDexRuntimes().withAllApiLevels().build(), tests.keySet());
+ getTestParameters().withDexRuntimesAndAllApiLevels().build(), tests.keySet());
}
private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR;
@@ -106,6 +106,8 @@
tests = testsBuilder.build();
}
+ private static Set<String> invokeErrors = ImmutableSet.of("bad-codegen", "illegal-invokes");
+
private static Map<String, Set<String>> missingClasses =
ImmutableMap.of(
"try-catch", ImmutableSet.of("test.X"),
@@ -244,6 +246,10 @@
.addProgramDexFileData(Files.readAllBytes(originalDexFile))
.applyIf(testJarExists, p -> p.addProgramFiles(testJar))
.addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet()))
+ .addOptionsModification(
+ options ->
+ options.getTestingOptions().allowInvokeErrors =
+ invokeErrors.contains(directoryName))
.setMinApi(parameters)
.compile()
.run(parameters.getRuntime(), "Test")
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
index 1ba7dcf..4025566 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
@@ -4,18 +4,19 @@
package com.android.tools.r8.classmerging.vertical;
+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;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.codeinspector.TypeSubject;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -28,13 +29,17 @@
@RunWith(Parameterized.class)
public class ExceptionTablesTest extends VerticalClassMergerTestBase {
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+ private final boolean disableInitial;
+
+ @Parameters(name = "{1}, disable initial: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public ExceptionTablesTest(TestParameters parameters) {
+ public ExceptionTablesTest(boolean disableInitial, TestParameters parameters) {
super(parameters);
+ this.disableInitial = disableInitial;
}
@Test
@@ -42,7 +47,14 @@
testForR8(parameters.getBackend())
.addInnerClasses(ExceptionTablesTest.class)
.addKeepMainRule(TestClass.class)
- .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+ .applyIf(
+ disableInitial,
+ testBuilder ->
+ testBuilder.addOptionsModification(
+ options -> options.getVerticalClassMergerOptions().disableInitial()),
+ testBuilder ->
+ testBuilder.addVerticallyMergedClassesInspector(
+ this::inspectVerticallyMergedClasses))
.setMinApi(parameters)
.compile()
.inspect(this::inspect)
@@ -58,8 +70,8 @@
assertThat(inspector.clazz(TestClass.class), isPresent());
assertThat(inspector.clazz(ExceptionB.class), isPresent());
assertThat(inspector.clazz(Exception2.class), isPresent());
- assertThat(inspector.clazz(ExceptionA.class), not(isPresent()));
- assertThat(inspector.clazz(Exception1.class), not(isPresent()));
+ assertThat(inspector.clazz(ExceptionA.class), isAbsent());
+ assertThat(inspector.clazz(Exception1.class), isAbsent());
// Check that only two exception guard types remain.
MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java
new file mode 100644
index 0000000..ba6a83c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InvokeStaticToInterfaceVerticalClassMergerTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticToInterfaceVerticalClassMergerTest extends TestBase {
+
+ @Parameter(0)
+ public boolean disableInitialRoundOfVerticalClassMerging;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, disable initial: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .applyIf(
+ disableInitialRoundOfVerticalClassMerging,
+ b ->
+ b.addOptionsModification(
+ options -> options.getVerticalClassMergerOptions().disableInitial()))
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ // Check interface is removed due to class merging and the static interface method is
+ // present on the subclass except when moved as a result of static interface method
+ // desugaring.
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isAbsent());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("hello");
+ assertThat(
+ fooMethodSubject,
+ isPresentIf(parameters.canUseDefaultAndStaticInterfaceMethods()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // After vertical class merging, invoke should have the is-interface bit set to false.
+ I.hello();
+ System.out.println(new A());
+ }
+ }
+
+ interface I {
+
+ @NeverInline
+ static void hello() {
+ System.out.print("Hello");
+ }
+ }
+
+ static class A implements I {
+
+ @Override
+ public String toString() {
+ return ", world!";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
index a15add8..f683135 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
@@ -5,39 +5,43 @@
package com.android.tools.r8.desugar.staticinterfacemethod;
import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticInterfaceNestedTest.Library.foo;
-import static org.junit.Assert.assertThrows;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DesugarTestConfiguration;
-import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class InvokeStaticInterfaceNestedTest extends TestBase {
- private final TestParameters parameters;
- private final String UNEXPECTED_SUCCESS = "Hello World!";
+ private static final String UNEXPECTED_SUCCESS = "Hello World!";
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ @Parameter(0)
+ public boolean allowInvokeErrors;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, allow invalid invokes: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
}
- public InvokeStaticInterfaceNestedTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private void checkDexResult(TestRunResult<?> runResult, boolean isDesugared) {
+ private void inspectRunResult(TestRunResult<?> runResult, boolean isDesugared) {
boolean didDesugarInterfaceMethods =
isDesugared && !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring();
if (parameters.isCfRuntime()) {
@@ -67,6 +71,7 @@
@Test
public void testDesugar() throws Exception {
+ assumeFalse(allowInvokeErrors);
testForDesugaring(parameters)
.addProgramClassFileData(
rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
@@ -76,27 +81,27 @@
result ->
result.applyIf(
DesugarTestConfiguration::isDesugared,
- r -> checkDexResult(r, true),
- r -> checkDexResult(r, false)));
+ r -> inspectRunResult(r, true),
+ r -> inspectRunResult(r, false)));
}
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
- R8FullTestBuilder testBuilder =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(
- rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
- rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
- .addKeepAllClassesRule()
- .setMinApi(parameters)
- .addKeepMainRule(Main.class);
- if (parameters.isDexRuntime()) {
- checkDexResult(testBuilder.run(parameters.getRuntime(), Main.class), true);
- } else {
- // TODO(b/166213037): Should not throw an error.
- assertThrows(CompilationFailedException.class, testBuilder::compile);
- }
+ assertFailsCompilationIf(
+ !allowInvokeErrors,
+ () ->
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
+ rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
+ .addKeepAllClassesRule()
+ .addOptionsModification(
+ options -> options.getTestingOptions().allowInvokeErrors = allowInvokeErrors)
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .apply(runResult -> inspectRunResult(runResult, parameters.isDexRuntime())));
}
private byte[] rewriteToUseNonInterfaceMethodReference(Class<?> clazz, String methodName)
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
index e070f46..0e3f590 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -13,13 +13,13 @@
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 com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
@@ -27,25 +27,21 @@
private static final String EXPECTED = StringUtils.lines("Hello World!");
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public InvokeSpecialInterfaceWithBridge3Test(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testRuntime() throws Exception {
- TestRunResult<?> result =
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
- .addProgramClasses(I.class, A.class, Main.class)
- .addProgramClassFileData(getClassWithTransformedInvoked())
- .run(parameters.getRuntime(), Main.class)
- .apply(this::inspectRunResult);
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::inspectRunResult);
}
private void inspectRunResult(SingleTestRunResult<?> runResult) {
@@ -77,6 +73,7 @@
.addProgramClasses(I.class, A.class, Main.class)
.addProgramClassFileData(getClassWithTransformedInvoked())
.addKeepMainRule(Main.class)
+ .addOptionsModification(options -> options.getTestingOptions().allowInvokeErrors = true)
.setMinApi(parameters)
.run(parameters.getRuntime(), Main.class)
.applyIf(