Merge commit '38f9d71955d87ddea83a96fd960443225a4a7038' into dev-release
diff --git a/build.gradle b/build.gradle
index 35cc8df..8a0cc1f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -217,6 +217,8 @@
exclude group: 'com.google.code.findbugs'
exclude group: 'com.google.j2objc'
exclude group: 'org.codehaus.mojo'
+ exclude group: 'com.google.guava', module: 'listenablefuture'
+ exclude group: 'com.google.guava', module: 'failureaccess'
})
implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
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 37fad8c..073cc8d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -947,10 +947,7 @@
DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
DexWritableCode rewrittenCode =
code.rewriteCodeWithJumboStrings(
- method,
- mapping,
- application.dexItemFactory,
- options.testing.forceJumboStringProcessing);
+ method, mapping, appView, options.testing.forceJumboStringProcessing);
method.setCode(rewrittenCode.asCode(), appView);
});
}
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index a68f99f..13f6812 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -53,6 +53,7 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.BooleanSupplier;
public class JumboStringRewriter {
@@ -92,6 +93,7 @@
private final DexEncodedMethod method;
private final DexString firstJumboString;
+ private final BooleanSupplier materializeInfoForNativePc;
private final DexItemFactory factory;
private final Map<DexInstruction, List<DexInstruction>> instructionTargets =
new IdentityHashMap<>();
@@ -105,12 +107,20 @@
private final Map<TryHandler, List<DexInstruction>> handlerTargets = new IdentityHashMap<>();
public JumboStringRewriter(
- DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+ DexEncodedMethod method,
+ DexString firstJumboString,
+ BooleanSupplier materializeInfoForNativePc,
+ DexItemFactory factory) {
this.method = method;
this.firstJumboString = firstJumboString;
+ this.materializeInfoForNativePc = materializeInfoForNativePc;
this.factory = factory;
}
+ private DexCode getCode() {
+ return method.getCode().asDexCode();
+ }
+
public DexCode rewrite() {
// Build maps from everything in the code that uses offsets or direct addresses to reference
// instructions to the actual instruction referenced.
@@ -124,7 +134,7 @@
TryHandler[] newHandlers = rewriteHandlerOffsets();
DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
// Set the new code on the method.
- DexCode oldCode = method.getCode().asDexCode();
+ DexCode oldCode = getCode();
DexCode newCode =
new DexCode(
oldCode.registerSize,
@@ -185,7 +195,7 @@
}
private Try[] rewriteTryOffsets() {
- DexCode code = method.getCode().asDexCode();
+ DexCode code = getCode();
Try[] result = new Try[code.tries.length];
for (int i = 0; i < code.tries.length; i++) {
Try theTry = code.tries[i];
@@ -197,7 +207,7 @@
}
private TryHandler[] rewriteHandlerOffsets() {
- DexCode code = method.getCode().asDexCode();
+ DexCode code = getCode();
TryHandler[] result = new TryHandler[code.handlers.length];
for (int i = 0; i < code.handlers.length; i++) {
TryHandler handler = code.handlers[i];
@@ -218,7 +228,7 @@
}
private DexDebugInfo rewriteDebugInfoOffsets() {
- DexCode code = method.getCode().asDexCode();
+ DexCode code = getCode();
if (!debugEventTargets.isEmpty()) {
assert debugEventBasedInfo != null;
int lastOriginalOffset = 0;
@@ -256,7 +266,7 @@
@SuppressWarnings("JdkObsolete")
private List<DexInstruction> expandCode() {
LinkedList<DexInstruction> instructions = new LinkedList<>();
- Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+ Collections.addAll(instructions, getCode().instructions);
int offsetDelta;
do {
ListIterator<DexInstruction> it = instructions.listIterator();
@@ -436,7 +446,7 @@
}
private void recordInstructionTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) {
- DexInstruction[] instructions = method.getCode().asDexCode().instructions;
+ DexInstruction[] instructions = getCode().instructions;
for (DexInstruction instruction : instructions) {
if (instruction instanceof DexFormat22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
DexFormat22t condition = (DexFormat22t) instruction;
@@ -486,14 +496,15 @@
}
private void recordDebugEventTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) {
- // TODO(b/213411850): Merging pc based D8 builds will map out of PC for any jumbo processed
- // method. Instead we should rather retain the PC encoding by bumping the max-pc and recording
- // the line number translation. We actually need to do so to support merging with native PC
- // support as in that case we can't reflect the change in the line table, only in the mapping.
- EventBasedDebugInfo eventBasedInfo =
- DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory);
+ EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(getCode(), factory);
if (eventBasedInfo == null) {
- return;
+ if (materializeInfoForNativePc.getAsBoolean()) {
+ eventBasedInfo =
+ DexDebugInfo.createEventBasedDebugInfoForNativePc(
+ method.getParameters().size(), getCode(), factory);
+ } else {
+ return;
+ }
}
debugEventBasedInfo = eventBasedInfo;
int address = 0;
@@ -516,7 +527,7 @@
private void recordTryAndHandlerTargets(
Int2ReferenceMap<DexInstruction> offsetToInstruction, DexInstruction lastInstruction) {
- DexCode code = method.getCode().asDexCode();
+ DexCode code = getCode();
for (Try theTry : code.tries) {
DexInstruction start = offsetToInstruction.get(theTry.startAddress);
DexInstruction end = null;
@@ -553,7 +564,7 @@
private void recordTargets() {
Int2ReferenceMap<DexInstruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
- DexInstruction[] instructions = method.getCode().asDexCode().instructions;
+ DexInstruction[] instructions = getCode().instructions;
boolean containsPayloads = false;
for (DexInstruction instruction : instructions) {
offsetToInstruction.put(instruction.getOffset(), instruction);
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index f9acf30..b252ad4 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -66,20 +66,23 @@
}
// Next perform startup checks.
- StartupProfile startupProfile = appView.getStartupProfile();
- OptionalBool callerIsStartupMethod = isStartupMethod(caller, startupProfile);
- if (callerIsStartupMethod.isTrue()) {
- // If the caller is a startup method, then only allow inlining if the callee is also a startup
- // method.
- if (isStartupMethod(callee, startupProfile).isFalse()) {
- return false;
- }
- } else if (callerIsStartupMethod.isFalse()) {
- // If the caller is not a startup method, then only allow inlining if the caller is not a
- // startup class or the callee is a startup class.
- if (startupProfile.isStartupClass(caller.getHolderType())
- && !startupProfile.isStartupClass(callee.getHolderType())) {
- return false;
+ if (!callee.getOptimizationInfo().forceInline()) {
+ StartupProfile startupProfile = appView.getStartupProfile();
+ OptionalBool callerIsStartupMethod = isStartupMethod(caller, startupProfile);
+ if (callerIsStartupMethod.isTrue()) {
+ // If the caller is a startup method, then only allow inlining if the callee is also a
+ // startup
+ // method.
+ if (isStartupMethod(callee, startupProfile).isFalse()) {
+ return false;
+ }
+ } else if (callerIsStartupMethod.isFalse()) {
+ // If the caller is not a startup method, then only allow inlining if the caller is not a
+ // startup class or the callee is a startup class.
+ if (startupProfile.isStartupClass(caller.getHolderType())
+ && !startupProfile.isStartupClass(callee.getHolderType())) {
+ return false;
+ }
}
}
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index b0e0aba..31b2efe 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -324,7 +324,7 @@
@Override
public DexWritableCode rewriteCodeWithJumboStrings(
- ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+ ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
// Intentionally empty. This piece of code does not have any const-string instructions.
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 039a0f0..5932bb5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -170,6 +170,17 @@
};
}
+ public DexCode withNewInstructions(DexInstruction[] newInstructions) {
+ return new DexCode(
+ this.registerSize,
+ this.incomingRegisterSize,
+ this.outgoingRegisterSize,
+ newInstructions,
+ this.tries,
+ this.handlers,
+ this.getDebugInfo());
+ }
+
@Override
public DexCode self() {
return this;
@@ -201,7 +212,7 @@
@Override
public DexWritableCode rewriteCodeWithJumboStrings(
- ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+ ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
DexString firstJumboString = null;
if (force) {
firstJumboString = mapping.getFirstString();
@@ -215,7 +226,12 @@
}
}
return firstJumboString != null
- ? new JumboStringRewriter(method.getDefinition(), firstJumboString, factory).rewrite()
+ ? new JumboStringRewriter(
+ method.getDefinition(),
+ firstJumboString,
+ () -> appView.options().shouldMaterializeLineInfoForNativePcEncoding(method),
+ appView.dexItemFactory())
+ .rewrite()
: this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 8826ec4..1a0023a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -314,11 +314,18 @@
code, pcBasedDebugInfo.maxPc);
// Generate a line event at each throwing instruction.
DexInstruction[] instructions = code.instructions;
- return forceConvertToEventBasedDebugInfo(pcBasedDebugInfo, instructions, factory);
+ return forceConvertToEventBasedDebugInfo(
+ PcBasedDebugInfo.START_LINE, pcBasedDebugInfo.getParameterCount(), instructions, factory);
}
- public static EventBasedDebugInfo forceConvertToEventBasedDebugInfo(
- PcBasedDebugInfo pcBasedDebugInfo, DexInstruction[] instructions, DexItemFactory factory) {
+ public static EventBasedDebugInfo createEventBasedDebugInfoForNativePc(
+ int parameterCount, DexCode code, DexItemFactory factory) {
+ assert code.getDebugInfo() == null;
+ return forceConvertToEventBasedDebugInfo(0, parameterCount, code.instructions, factory);
+ }
+
+ private static EventBasedDebugInfo forceConvertToEventBasedDebugInfo(
+ int startLine, int parameterCount, DexInstruction[] instructions, DexItemFactory factory) {
List<DexDebugEvent> events = new ArrayList<>(instructions.length);
int delta = 0;
for (DexInstruction instruction : instructions) {
@@ -329,9 +336,7 @@
delta += instruction.getSize();
}
return new EventBasedDebugInfo(
- PcBasedDebugInfo.START_LINE,
- new DexString[pcBasedDebugInfo.getParameterCount()],
- events.toArray(DexDebugEvent.EMPTY_ARRAY));
+ startLine, new DexString[parameterCount], events.toArray(DexDebugEvent.EMPTY_ARRAY));
}
public static DexDebugInfoForWriting convertToWritable(DexDebugInfo debugInfo) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index 99f8679..afe2afb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -96,7 +96,7 @@
/** Rewrites the code to have JumboString bytecode if required by mapping. */
DexWritableCode rewriteCodeWithJumboStrings(
- ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force);
+ ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force);
void setCallSiteContexts(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 19d78c1..4905063 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -197,7 +197,7 @@
@Override
public DexWritableCode rewriteCodeWithJumboStrings(
- ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+ ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
// Intentionally empty. This piece of code does not have any const-string instructions.
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index 707e45a..90c6d8d 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -219,7 +219,7 @@
@Override
public DexWritableCode rewriteCodeWithJumboStrings(
- ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+ ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
// Intentionally empty. This piece of code does not have any const-string instructions.
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 0bb5989..ed02fe2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -288,7 +288,7 @@
@Override
public void buildLir(LirBuilder<Value, ?> builder) {
- builder.addCheckCast(type, object());
+ builder.addCheckCast(type, object(), ignoreCompatRules);
}
public static class Builder extends BuilderBase<Builder, CheckCast> {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index a42e312..86c95b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -223,7 +223,7 @@
@Override
public void buildLir(LirBuilder<Value, ?> builder) {
- builder.addConstClass(getType());
+ builder.addConstClass(getType(), ignoreCompatRules);
}
public static class Builder extends BuilderBase<Builder, ConstClass> {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
index 9a3a024..7e17e7e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -96,7 +97,7 @@
// Then we can redirect any known value. This can lead to dead code.
If theIf = block.exit().asIf();
Set<Phi> allowedPhis = getAllowedPhis(nonConstNumberOperand(theIf).asPhi());
- Set<Phi> foundPhis = Sets.newIdentityHashSet();
+ Set<Phi> foundPhis = new LinkedHashSet<>();
WorkList.newIdentityWorkList(block)
.process(
(current, workList) -> {
@@ -135,9 +136,7 @@
}
}
}
- List<Phi> sortedFoundPhis = new ArrayList<>(foundPhis);
- sortedFoundPhis.sort(Phi::compareTo);
- for (Phi phi : sortedFoundPhis) {
+ for (Phi phi : foundPhis) {
BasicBlock phiBlock = phi.getBlock();
for (int i = 0; i < phi.getOperands().size(); i++) {
Value value = phi.getOperand(i);
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 4643e0a..f715e77 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
@@ -322,7 +322,7 @@
.contextIndependentDefinitionForWithResolutionResult(type)
.toSingleClassWithProgramOverLibrary();
assert theApi.equals(api.max(appView.options().getMinApiLevel()))
- || (clazz != null && clazz.isProgramClass());
+ || (clazz != null && !clazz.isLibraryClass());
});
return true;
}
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index ea4f7b1..8777089 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -509,11 +509,11 @@
}
@Override
- public void onConstClass(DexType type) {
+ public void onConstClass(DexType type, boolean ignoreCompatRules) {
Value dest =
getOutValueForNextInstruction(
type.toTypeElement(appView, Nullability.definitelyNotNull()));
- addInstruction(new ConstClass(dest, type));
+ addInstruction(new ConstClass(dest, type, ignoreCompatRules));
}
@Override
@@ -739,9 +739,9 @@
}
@Override
- public void onCheckCast(DexType type, EV value) {
+ public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) {
Value dest = getOutValueForNextInstruction(type.toTypeElement(appView));
- addInstruction(new CheckCast(dest, getValue(value), type));
+ addInstruction(new CheckCast(dest, getValue(value), type, ignoreCompatRules));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index b88f1f7..f6ca030 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -392,8 +392,9 @@
return addOneItemInstruction(LirOpcodes.LDC, string);
}
- public LirBuilder<V, EV> addConstClass(DexType type) {
- return addOneItemInstruction(LirOpcodes.LDC, type);
+ public LirBuilder<V, EV> addConstClass(DexType type, boolean ignoreCompatRules) {
+ int opcode = ignoreCompatRules ? LirOpcodes.CONSTCLASS_IGNORE_COMPAT : LirOpcodes.LDC;
+ return addOneItemInstruction(opcode, type);
}
public LirBuilder<V, EV> addConstMethodHandle(DexMethodHandle methodHandle) {
@@ -483,9 +484,10 @@
return addOneValueInstruction(LirOpcodes.ARRAYLENGTH, array);
}
- public LirBuilder<V, EV> addCheckCast(DexType type, V value) {
+ public LirBuilder<V, EV> addCheckCast(DexType type, V value, boolean ignoreCompatRules) {
+ int opcode = ignoreCompatRules ? LirOpcodes.CHECKCAST_IGNORE_COMPAT : LirOpcodes.CHECKCAST;
return addInstructionTemplate(
- LirOpcodes.CHECKCAST, Collections.singletonList(type), Collections.singletonList(value));
+ opcode, Collections.singletonList(type), Collections.singletonList(value));
}
public LirBuilder<V, EV> addSafeCheckCast(DexType type, V value) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 8863892..6803d0b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -209,6 +209,8 @@
int INVOKEPOLYMORPHIC = 222;
int RECORDFIELDVALUES = 223;
int CHECKCAST_SAFE = 224;
+ int CHECKCAST_IGNORE_COMPAT = 225;
+ int CONSTCLASS_IGNORE_COMPAT = 226;
static String toString(int opcode) {
switch (opcode) {
@@ -543,6 +545,10 @@
return "RECORDFIELDVALUES";
case CHECKCAST_SAFE:
return "CHECKCAST_SAFE";
+ case CHECKCAST_IGNORE_COMPAT:
+ return "CHECKCAST_IGNORE_COMPAT";
+ case CONSTCLASS_IGNORE_COMPAT:
+ return "CONSTCLASS_IGNORE_COMPAT";
default:
throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index 73ad12b..444df4e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -99,7 +99,7 @@
onInstruction();
}
- public void onConstClass(DexType type) {
+ public void onConstClass(DexType type, boolean ignoreCompatRules) {
onInstruction();
}
@@ -468,7 +468,7 @@
onInstruction();
}
- public void onCheckCast(DexType type, EV value) {
+ public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) {
onInstruction();
}
@@ -530,7 +530,7 @@
return;
}
if (item instanceof DexType) {
- onConstClass((DexType) item);
+ onConstClass((DexType) item, false);
return;
}
if (item instanceof DexMethodHandle) {
@@ -543,6 +543,12 @@
}
throw new Unimplemented();
}
+ case LirOpcodes.CONSTCLASS_IGNORE_COMPAT:
+ {
+ DexItem item = getConstantItem(view.getNextConstantOperand());
+ onConstClass((DexType) item, true);
+ return;
+ }
case LirOpcodes.ICONST_M1:
case LirOpcodes.ICONST_0:
case LirOpcodes.ICONST_1:
@@ -1110,7 +1116,14 @@
{
DexType type = getNextDexTypeOperand(view);
EV value = getNextValueOperand(view);
- onCheckCast(type, value);
+ onCheckCast(type, value, false);
+ return;
+ }
+ case LirOpcodes.CHECKCAST_IGNORE_COMPAT:
+ {
+ DexType type = getNextDexTypeOperand(view);
+ EV value = getNextValueOperand(view);
+ onCheckCast(type, value, true);
return;
}
case LirOpcodes.CHECKCAST_SAFE:
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index 5092894..96d9cef 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -162,7 +162,7 @@
}
@Override
- public void onConstClass(DexType type) {
+ public void onConstClass(DexType type, boolean ignoreCompatRules) {
appendOutValue().append("class(").append(type).append(")");
}
@@ -343,7 +343,7 @@
}
@Override
- public void onCheckCast(DexType type, EV value) {
+ public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) {
appendOutValue();
appendValueArguments(value);
builder.append(type);
@@ -351,7 +351,7 @@
@Override
public void onSafeCheckCast(DexType type, EV value) {
- onCheckCast(type, value);
+ onCheckCast(type, value, true);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 43c2c1c..fce5c53 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
@@ -91,11 +92,28 @@
return;
}
if (code.isDexCode()) {
- for (DexInstruction instruction : code.asDexCode().instructions) {
- if (instruction.isConstString()) {
- DexConstString cnst = instruction.asConstString();
- cnst.BBBB = getRenamedStringLiteral(cnst.getString());
- }
+ DexInstruction[] instructions = code.asDexCode().instructions;
+ DexInstruction[] newInstructions =
+ ArrayUtils.map(
+ instructions,
+ (DexInstruction instruction) -> {
+ if (instruction.isConstString()) {
+ DexConstString cnst = instruction.asConstString();
+ DexString renamedStringLiteral = getRenamedStringLiteral(cnst.getString());
+ if (!renamedStringLiteral.equals(cnst.getString())) {
+ DexConstString dexConstString =
+ new DexConstString(instruction.asConstString().AA, renamedStringLiteral);
+ dexConstString.setOffset(instruction.getOffset());
+ return dexConstString;
+ }
+ }
+ return instruction;
+ },
+ DexInstruction.EMPTY_ARRAY);
+ if (instructions != newInstructions) {
+ encodedMethod.setCode(
+ code.asDexCode().withNewInstructions(newInstructions),
+ encodedMethod.getParameterInfo());
}
} else if (code.isCfCode()) {
for (CfInstruction instruction : code.asCfCode().getInstructions()) {
@@ -163,22 +181,24 @@
assert code != null;
if (code.isDexCode()) {
DexInstruction[] instructions = code.asDexCode().instructions;
- boolean updated = false;
- for (int i = 0; i < instructions.length; ++i) {
- DexInstruction instruction = instructions[i];
- if (instruction.isDexItemBasedConstString()) {
- DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
- DexString replacement =
- cnst.getNameComputationInfo()
- .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
- DexConstString constString = new DexConstString(cnst.AA, replacement);
- constString.setOffset(instruction.getOffset());
- instructions[i] = constString;
- updated = true;
- }
- }
- if (updated) {
- code.asDexCode().flushCachedValues();
+ DexInstruction[] newInstructions =
+ ArrayUtils.map(
+ instructions,
+ (DexInstruction instruction) -> {
+ if (instruction.isDexItemBasedConstString()) {
+ DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
+ DexString replacement =
+ cnst.getNameComputationInfo()
+ .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
+ DexConstString constString = new DexConstString(cnst.AA, replacement);
+ constString.setOffset(instruction.getOffset());
+ return constString;
+ }
+ return instruction;
+ },
+ DexInstruction.EMPTY_ARRAY);
+ if (newInstructions != instructions) {
+ programMethod.setCode(code.asDexCode().withNewInstructions(newInstructions), appView);
}
} else if (code.isCfCode()) {
List<CfInstruction> instructions = code.asCfCode().getInstructions();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index a1aec5a..313b6ca 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -170,7 +170,14 @@
&& currentClass.getSuperType() != appView.dexItemFactory().objectType) {
return classType.lessThanOrEqualUpToNullability(upperBound, appView);
}
- return classType.equalUpToNullability(upperBound);
+ // If the upper bound does not have any interfaces we simply activate the method state when
+ // meeting the upper bound class type in the downwards traversal over the class hierarchy.
+ if (classType.getInterfaces().isEmpty()) {
+ return classType.equalUpToNullability(upperBound);
+ }
+ // If the upper bound has interfaces, we check if the current class is a subtype of *both* the
+ // upper bound class type and the upper bound interface types.
+ return classType.lessThanOrEqualUpToNullability(upperBound, appView);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index b6236d8..e730778 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -392,15 +392,6 @@
newInstructions[i] = instruction;
}
}
- return modified
- ? new DexCode(
- code.registerSize,
- code.incomingRegisterSize,
- code.outgoingRegisterSize,
- newInstructions,
- code.tries,
- code.handlers,
- code.getDebugInfo())
- : code;
+ return modified ? code.withNewInstructions(newInstructions) : code;
}
}
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 b2ce050..89edec3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -52,6 +52,7 @@
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -2515,6 +2516,10 @@
throw new Unreachable();
}
+ public boolean canUseDexPc2PcAsDebugInformation() {
+ return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON;
+ }
+
// Debug entries may be dropped only if the source file content allows being omitted from
// stack traces, or if the VM will report the source file even with a null valued debug info.
public boolean allowDiscardingResidualDebugInfo() {
@@ -2522,8 +2527,10 @@
return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile();
}
- public boolean canUseDexPc2PcAsDebugInformation() {
- return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON;
+ public boolean allowDiscardingResidualDebugInfo(ProgramMethod method) {
+ // TODO(b/146565491): We can drop debug info once fixed at a known min-api.
+ DexString sourceFile = method.getHolder().getSourceFile();
+ return sourceFile == null || sourceFile.equals(itemFactory.defaultSourceFileAttribute);
}
public boolean canUseNativeDexPcInsteadOfDebugInfo() {
@@ -2532,6 +2539,19 @@
&& allowDiscardingResidualDebugInfo();
}
+ public boolean canUseNativeDexPcInsteadOfDebugInfo(ProgramMethod method) {
+ return canUseDexPc2PcAsDebugInformation()
+ && hasMinApi(AndroidApiLevel.O)
+ && allowDiscardingResidualDebugInfo(method);
+ }
+
+ public boolean shouldMaterializeLineInfoForNativePcEncoding(ProgramMethod method) {
+ assert method.getDefinition().getCode().asDexCode() != null;
+ assert method.getDefinition().getCode().asDexCode().getDebugInfo() == null;
+ return method.getHolder().originatesFromDexResource()
+ && canUseNativeDexPcInsteadOfDebugInfo(method);
+ }
+
public boolean isInterfaceMethodDesugaringEnabled() {
// This condition is to filter out tests that never set program consumer.
if (!hasConsumer()) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java b/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java
new file mode 100644
index 0000000..311ea41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java
@@ -0,0 +1,143 @@
+// 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.debuginfo.composepc;
+
+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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.OptionalInt;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ComposePcEncodingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ComposePcEncodingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private byte[] getTransformedClass() throws Exception {
+ return transformer(TestClass.class)
+ .removeLineNumberTable(MethodPredicate.onName("unusedKeptAndNoLineInfo"))
+ .transform();
+ }
+
+ private boolean isNativePcSupported() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference unusedKeptAndNoLineInfo =
+ Reference.methodFromMethod(TestClass.class.getDeclaredMethod("unusedKeptAndNoLineInfo"));
+
+ // R8 compiles to DEX with pc2pc encoding or native-pc encoding.
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClass())
+ .addKeepMainRule(TestClass.class)
+ .addKeepMethodRules(unusedKeptAndNoLineInfo)
+ .setMinApi(parameters)
+ .addKeepAttributeLineNumberTable()
+ .compile()
+ .inspect(
+ inspector -> {
+ // Expected residual line info of 1 for pc2pc encoding and some value for native.
+ int residualLine = isNativePcSupported() ? 123 : 1;
+ // Check the expected status of the DEX debug info object for the "no lines".
+ MethodSubject methodNoLines = inspector.method(unusedKeptAndNoLineInfo);
+ assertThat(methodNoLines, isPresent());
+ // TODO(b/232212653): This should be true in pc2pc compilation with a single line.
+ assertFalse(methodNoLines.hasLineNumberTable());
+ // Check that "retracing" the pinned method with no lines maps to "noline/zero".
+ RetraceFrameResult retraceResult =
+ inspector
+ .retrace()
+ .retraceFrame(
+ RetraceStackTraceContext.empty(),
+ OptionalInt.of(residualLine),
+ unusedKeptAndNoLineInfo);
+ assertFalse(retraceResult.isAmbiguous());
+ RetraceFrameElement frameElement = retraceResult.stream().findFirst().get();
+ assertEquals(0, frameElement.getOuterFrames().size());
+ RetracedMethodReference topFrame = frameElement.getTopFrame();
+ assertTrue(topFrame.isKnown());
+ // TODO(b/232212653): Retrace should map back to the "no line" value of zero.
+ assertFalse(topFrame.hasPosition());
+ });
+
+ compileResult
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectStackTrace(ComposePcEncodingTest::checkStackTrace);
+
+ Path r8OutputDex = compileResult.writeToZip();
+ Path r8OutputMap =
+ FileUtils.writeTextFile(
+ temp.newFolder().toPath().resolve("out.map"), compileResult.getProguardMap());
+
+ // D8 (re)merges DEX with an artificial jumbo-string to force a remapping of PC values.
+ testForD8(parameters.getBackend())
+ .addProgramFiles(r8OutputDex)
+ .setMinApi(parameters)
+ // We only optimize line info in release mode and with a mapping file output enabled.
+ .release()
+ .internalEnableMappingOutput()
+ .apply(b -> b.getBuilder().setProguardInputMapFile(r8OutputMap))
+ // Forcing jumbo processing will shift the PC values on the methods.
+ .addOptionsModification(o -> o.testing.forceJumboStringProcessing = true)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectFailure(
+ inspector -> {
+ MethodSubject methodNoLines = inspector.method(unusedKeptAndNoLineInfo);
+ assertThat(methodNoLines, isPresent());
+ // TODO(b/213411850): This should depend on native pc support.
+ assertTrue(methodNoLines.hasLineNumberTable());
+ })
+ .inspectStackTrace(ComposePcEncodingTest::checkStackTrace);
+ }
+
+ private static void checkStackTrace(StackTrace stackTrace) {
+ StackTraceLine.Builder builder =
+ StackTraceLine.builder()
+ .setClassName(typeName(TestClass.class))
+ .setFileName(TestClass.class.getSimpleName() + ".java");
+ assertThat(
+ stackTrace,
+ StackTrace.isSame(
+ StackTrace.builder()
+ .add(builder.setMethodName("bar").setLineNumber(15).build())
+ .add(builder.setMethodName("main").setLineNumber(25).build())
+ .build()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java b/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java
new file mode 100644
index 0000000..caae028
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java
@@ -0,0 +1,33 @@
+// 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.debuginfo.composepc;
+
+class TestClass {
+
+ public static void foo() {
+ System.out.println("AAA");
+ }
+
+ public static void bar() {
+ System.out.println("BBB");
+ if (System.nanoTime() > 0) {
+ throw new RuntimeException(); // LINE 15 - update ComposePcEncodingTest if changed.
+ }
+ }
+
+ public static void baz() {
+ System.out.println("CCC");
+ }
+
+ public static void main(String[] args) {
+ foo();
+ bar(); // LINE 25 - update ComposePcEncodingTest if changed.
+ baz();
+ }
+
+ // Line removed by transform.
+ public static void unusedKeptAndNoLineInfo() {
+ System.out.println("DDDD");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index bf49805..7bc5d20 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -158,6 +158,6 @@
.disableMethodNotNullCheck()
.disableAndroidApiLevelCheck()
.build();
- return new JumboStringRewriter(method, string, factory).rewrite();
+ return new JumboStringRewriter(method, string, () -> false, factory).rewrite();
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationUpperBoundWithInterfacesTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationUpperBoundWithInterfacesTest.java
new file mode 100644
index 0000000..7ac6f56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationUpperBoundWithInterfacesTest.java
@@ -0,0 +1,89 @@
+// 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+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.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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;
+
+/** Regression test for b/284188592. */
+@RunWith(Parameterized.class)
+public class ArgumentPropagationUpperBoundWithInterfacesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // A value with upper bound class type B and interface I.
+ B b = System.currentTimeMillis() > 0 ? new C() : new D();
+ // A virtual invoke with arguments [42] and upper bound class type B and interface I.
+ b.foo(42);
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ interface I {}
+
+ @NoVerticalClassMerging
+ abstract static class A {
+
+ abstract void foo(int i);
+ }
+
+ abstract static class B extends A {}
+
+ @NoHorizontalClassMerging
+ static class C extends B implements I {
+
+ @Override
+ void foo(int i) {
+ System.out.println(i);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class D extends B implements I {
+
+ @Override
+ void foo(int i) {
+ System.out.println(i);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/ForceInlineAfterVerticalClassMergingStartupTest.java b/src/test/java/com/android/tools/r8/startup/ForceInlineAfterVerticalClassMergingStartupTest.java
new file mode 100644
index 0000000..b3951cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/ForceInlineAfterVerticalClassMergingStartupTest.java
@@ -0,0 +1,81 @@
+// 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.startup;
+
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+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;
+
+/** Regression test for b/284334258. */
+@RunWith(Parameterized.class)
+public class ForceInlineAfterVerticalClassMergingStartupTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ // Create a startup profile with classes A and B to allow that A is merged into B. The method
+ // B.<init>() is marked as a startup method, but A.<init>() is not, despite being called from
+ // B.<init>().
+ Collection<ExternalStartupItem> startupProfile =
+ ImmutableList.of(
+ ExternalStartupClass.builder()
+ .setClassReference(Reference.classFromClass(A.class))
+ .build(),
+ ExternalStartupClass.builder()
+ .setClassReference(Reference.classFromClass(B.class))
+ .build(),
+ ExternalStartupMethod.builder()
+ .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
+ .build());
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .apply(testBuilder -> StartupTestingUtils.addStartupProfile(testBuilder, startupProfile))
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new B());
+ }
+ }
+
+ static class A {}
+
+ static class B extends A {
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+}