Merge "The meet of precise types is only defined for identical types."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
}
public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+ DexClass clazz = definitionFor(type);
+ assert clazz != null;
+ return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+ }
+
+ public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
// Process superclass chain.
- DexType clazz = type;
- while (clazz != null && clazz != dexItemFactory.objectType) {
- DexClass definition = definitionFor(clazz);
- if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+ DexClass current = clazz;
+ while (current != null && current.type != dexItemFactory.objectType) {
+ if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
return true;
}
- knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
- clazz = definition.superType;
+ knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+ current = current.superType != null ? definitionFor(current.superType) : null;
}
// Process interfaces.
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterators;
import java.util.Arrays;
@@ -138,16 +137,6 @@
}
}
- public <E extends Throwable> void forEachMethodThrowing(
- ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
- for (DexEncodedMethod method : directMethods()) {
- consumer.accept(method);
- }
- for (DexEncodedMethod method : virtualMethods()) {
- consumer.accept(method);
- }
- }
-
public DexEncodedMethod[] allMethodsSorted() {
int vLen = virtualMethods.length;
int dLen = directMethods.length;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d5f923e..3ea97e4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -176,7 +176,8 @@
}
this.classInliner =
(options.enableClassInlining && options.enableInlining && inliner != null)
- ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+ ? new ClassInliner(
+ appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
: null;
}
@@ -356,12 +357,7 @@
ExecutorService executor) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- futures.add(
- executor.submit(
- () -> {
- clazz.forEachMethodThrowing(this::convertMethodToDex);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
+ futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
}
ThreadUtils.awaitFutures(futures);
}
@@ -565,7 +561,7 @@
try {
codeRewriter.enterCachedClass(clazz);
// Process the generated class, but don't apply any outlining.
- clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ clazz.forEachMethod(this::optimizeSynthesizedMethod);
} finally {
codeRewriter.leaveCachedClass(clazz);
}
@@ -733,11 +729,6 @@
assert code.isConsistentSSA();
}
- if (interfaceMethodRewriter != null) {
- interfaceMethodRewriter.rewriteMethodReferences(method, code);
- assert code.isConsistentSSA();
- }
-
if (classInliner != null) {
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it is not get collected by merger.
@@ -755,6 +746,11 @@
assert code.isConsistentSSA();
}
+ if (interfaceMethodRewriter != null) {
+ interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (lambdaMerger != null) {
lambdaMerger.processMethodCode(method, code);
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
final Target target;
final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+ private final Supplier<DexProgramClass> lazyDexClass =
+ Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
return rewriter.factory.createType(lambdaClassDescriptor.toString());
}
- final DexProgramClass synthesizeLambdaClass() {
+ final DexProgramClass getLambdaClass() {
+ return lazyDexClass.get();
+ }
+
+ private DexProgramClass synthesizeLambdaClass() {
return new DexProgramClass(
type,
null,
@@ -171,7 +179,7 @@
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+ new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new SynthesizedCode(
- new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+ () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
}
return methods;
}
@@ -208,7 +216,7 @@
true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+ new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
// Class constructor for stateless lambda classes.
if (stateless) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..51b8b8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -141,7 +141,6 @@
if (method.name == deserializeLambdaMethodName &&
method.proto == deserializeLambdaMethodProto) {
assert encoded.accessFlags.isStatic();
- assert encoded.accessFlags.isPrivate();
assert encoded.accessFlags.isSynthetic();
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -170,10 +169,19 @@
}
}
+ /**
+ * Returns a synthetic class for desugared lambda or `null` if the `type`
+ * does not represent one. Method can be called concurrently.
+ */
+ public DexProgramClass getLambdaClass(DexType type) {
+ LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+ return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+ }
+
/** Generates lambda classes and adds them to the builder. */
public void synthesizeLambdaClasses(Builder<?> builder) {
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+ DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
converter.optimizeSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
@Override
public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
- assert target.isProcessed();
+ // Do nothing. If the method is not yet processed, we still should
+ // be able to build IR for inlining, though.
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
public final class ClassInliner {
private final DexItemFactory factory;
+ private final LambdaRewriter lambdaRewriter;
private final int totalMethodInstructionLimit;
- private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
public interface InlinerAction {
void inline(Map<InvokeMethod, InliningInfo> methods);
}
- public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+ public ClassInliner(DexItemFactory factory,
+ LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.totalMethodInstructionLimit = totalMethodInstructionLimit;
}
@@ -142,8 +145,8 @@
while (rootsIterator.hasNext()) {
Instruction root = rootsIterator.next();
InlineCandidateProcessor processor =
- new InlineCandidateProcessor(factory, appInfo,
- type -> isClassEligible(appInfo, type),
+ new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+ clazz -> isClassEligible(appInfo, clazz),
isProcessedConcurrently, method, root);
// Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
} while (repeat);
}
- private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+ private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
Boolean eligible = knownClasses.get(clazz);
if (eligible == null) {
Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
// - is not an abstract class or interface
// - does not declare finalizer
// - does not trigger any static initializers except for its own
- private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
- DexClass definition = appInfo.definitionFor(clazz);
- if (definition == null || definition.isLibraryClass() ||
- definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+ private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+ if (clazz == null || clazz.isLibraryClass() ||
+ clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
return false;
}
// Class must not define finalizer.
- for (DexEncodedMethod method : definition.virtualMethods()) {
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
if (method.method.name == factory.finalizeMethodName &&
method.method.proto == factory.objectMethods.finalize.proto) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
private final DexItemFactory factory;
private final AppInfoWithLiveness appInfo;
- private final Predicate<DexType> isClassEligible;
+ private final LambdaRewriter lambdaRewriter;
+ private final Predicate<DexClass> isClassEligible;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final DexEncodedMethod method;
private final Instruction root;
@@ -61,6 +63,7 @@
private Value eligibleInstance;
private DexType eligibleClass;
private DexClass eligibleClassDefinition;
+ private boolean isDesugaredLambda;
private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
= new IdentityHashMap<>();
@@ -73,10 +76,11 @@
InlineCandidateProcessor(
DexItemFactory factory, AppInfoWithLiveness appInfo,
- Predicate<DexType> isClassEligible,
+ LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
Predicate<DexEncodedMethod> isProcessedConcurrently,
DexEncodedMethod method, Instruction root) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.isClassEligible = isClassEligible;
this.method = method;
this.root = root;
@@ -99,6 +103,11 @@
eligibleClass = isNewInstance() ?
root.asNewInstance().clazz : root.asStaticGet().getField().type;
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ if (eligibleClassDefinition == null && lambdaRewriter != null) {
+ // Check if the class is synthesized for a desugared lambda
+ eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+ isDesugaredLambda = eligibleClassDefinition != null;
+ }
return eligibleClassDefinition != null;
}
@@ -114,7 +123,7 @@
// * class has class initializer marked as TrivialClassInitializer, and
// class initializer initializes the field we are reading here.
boolean isClassAndUsageEligible() {
- if (!isClassEligible.test(eligibleClass)) {
+ if (!isClassEligible.test(eligibleClassDefinition)) {
return false;
}
@@ -129,6 +138,11 @@
assert root.isStaticGet();
+ // We know that desugared lambda classes satisfy eligibility requirements.
+ if (isDesugaredLambda) {
+ return true;
+ }
+
// Checking if we can safely inline class implemented following singleton-like
// pattern, by which we assume a static final field holding on to the reference
// initialized in class constructor.
@@ -444,7 +458,7 @@
: "Inlined constructor? [invoke: " + initInvoke +
", expected class: " + eligibleClass + "]";
- DexEncodedMethod definition = appInfo.definitionFor(init);
+ DexEncodedMethod definition = findSingleTarget(init, true);
if (definition == null || isProcessedConcurrently.test(definition)) {
return null;
}
@@ -455,6 +469,12 @@
return null;
}
+ if (isDesugaredLambda) {
+ // Lambda desugaring synthesizes eligible constructors.
+ markSizeForInlining(definition);
+ return new InliningInfo(definition, eligibleClass);
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -499,7 +519,7 @@
private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
- DexEncodedMethod singleTarget = findSingleTarget(callee);
+ DexEncodedMethod singleTarget = findSingleTarget(callee, false);
if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
return null;
}
@@ -507,6 +527,16 @@
return null; // Don't inline itself.
}
+ if (isDesugaredLambda) {
+ // If this is the call to method of the desugared lambda, we consider only calls
+ // to main lambda method eligible (for both direct and indirect calls).
+ if (singleTarget.accessFlags.isBridge()) {
+ return null;
+ }
+ markSizeForInlining(singleTarget);
+ return new InliningInfo(singleTarget, eligibleClass);
+ }
+
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
DexType inlineeHolder = inlinee.method.holder;
+ if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+ return true;
+ }
if (appInfo.isPinned(inlineeHolder)) {
return false;
}
@@ -674,14 +707,16 @@
return root.isNewInstance();
}
- private DexEncodedMethod findSingleTarget(DexMethod callee) {
+ private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
// We don't use computeSingleTarget(...) on invoke since it sometimes fails to
// find the single target, while this code may be more successful since we exactly
// know what is the actual type of the receiver.
// Note that we also intentionally limit ourselves to methods directly defined in
// the instance's class. This may be improved later.
- return eligibleClassDefinition.lookupVirtualMethod(callee);
+ return isDirect
+ ? eligibleClassDefinition.lookupDirectMethod(callee)
+ : eligibleClassDefinition.lookupVirtualMethod(callee);
}
private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -10,25 +10,33 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Consumer;
+import java.util.function.Supplier;
public final class SynthesizedCode extends Code {
- private final SourceCode sourceCode;
+ private final Supplier<SourceCode> sourceCodeProvider;
private final Consumer<UseRegistry> registryCallback;
- public SynthesizedCode(SourceCode sourceCode) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(SourceCode sourceCodeProvider) {
+ this(() -> sourceCodeProvider);
+ }
+
+ public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+ this.sourceCodeProvider = sourceCodeProvider;
this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
}
- public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(
+ SourceCode sourceCode, Consumer<UseRegistry> callback) {
+ this.sourceCodeProvider = () -> sourceCode;
this.registryCallback = callback;
}
@@ -40,7 +48,15 @@
@Override
public final IRCode buildIR(
DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+ return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+ }
+
+ @Override
+ public IRCode buildInliningIR(
+ DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+ return new IRBuilder(encodedMethod, appInfo,
+ sourceCodeProvider.get(), options, valueNumberGenerator).build();
}
@Override
@@ -59,17 +75,16 @@
@Override
protected final int computeHashCode() {
- return sourceCode.hashCode();
+ throw new Unreachable();
}
@Override
protected final boolean computeEquals(Object other) {
- return other instanceof SynthesizedCode &&
- this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+ throw new Unreachable();
}
@Override
public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return "SynthesizedCode: " + sourceCode.toString();
+ return "SynthesizedCode";
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Set;
/** Class provides basic information about symbols related to Kotlin support. */
public final class Kotlin {
+ // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+ private static final String KOTLIN =
+ String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+ static String addKotlinPrefix(String str) {
+ return KOTLIN + str;
+ }
+
public final DexItemFactory factory;
public final Functional functional;
@@ -44,14 +53,15 @@
//
// This implementation just ignores lambdas with arity > 22.
for (int i = 0; i <= 22; i++) {
- functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+ functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
}
}
public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
- public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
- public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+ public final DexType functionBase =
+ factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+ public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
public final DexMethod lambdaInitializerMethod = factory.createMethod(
lambdaType,
@@ -64,7 +74,7 @@
}
public final class Metadata {
- public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+ public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
public final DexString kind = factory.createString("k");
public final DexString metadataVersion = factory.createString("mv");
public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
// kotlin.jvm.internal.Intrinsics class
public final class Intrinsics {
- public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+ public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
public final DexMethod throwParameterIsNullException = factory.createMethod(type,
factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
new StringDiagnostic("Class " + clazz.type.toSourceString()
+ " has malformed kotlin.Metadata: " + e.getMessage()));
} catch (Throwable e) {
- reporter.warning(
+ reporter.warning(
new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
+ "'s kotlin.Metadata: " + e.getMessage()));
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..be9261f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,11 +4,15 @@
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -94,6 +98,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -112,6 +118,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -131,6 +139,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
@@ -151,6 +161,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
+ private void checkLambdaCount(DexInspector inspector, int expectedCount, String prefix) {
+ int count = 0;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.isSynthesizedJavaLambdaClass() &&
+ clazz.getOriginalName().startsWith(prefix)) {
+ count++;
+ }
+ }
+ assertEquals(expectedCount, count);
+ }
+
class R8TestRunner extends TestRunner<R8TestRunner> {
R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2667ce2..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,9 @@
public static byte[] getClassAsBytes(Class clazz) throws IOException {
String s = clazz.getSimpleName() + ".class";
Class outer = clazz.getEnclosingClass();
- if (outer != null) {
+ while (outer != null) {
s = outer.getSimpleName() + '$' + s;
+ outer = outer.getEnclosingClass();
}
return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..e7a8285 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -301,6 +302,37 @@
assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
}
+ @Test
+ public void testDesugaredLambdas() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(LambdasTestClass.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+ String javaOutput = runOnJava(LambdasTestClass.class);
+ String artOutput = runOnArt(app, LambdasTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatelessLambda", "void"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+ assertEquals(0,
+ inspector.allClasses().stream()
+ .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+ private static int ID = 0;
+
+ private static int nextInt() {
+ return ID++;
+ }
+
+ private static String next() {
+ return Integer.toString(nextInt());
+ }
+
+ public interface Iface {
+ String foo();
+ }
+
+ public static class IfaceUtil {
+ public static void act(Iface iface) {
+ System.out.println("" + next() + "> " + iface.foo());
+ }
+ }
+
+ public static void main(String[] args) {
+ LambdasTestClass test = new LambdasTestClass();
+ test.testStatelessLambda();
+ test.testStatefulLambda(next(), next());
+ }
+
+ public static String exact() {
+ return next();
+ }
+
+ public static String almost(String... s) {
+ return next();
+ }
+
+ private synchronized void testStatelessLambda() {
+ IfaceUtil.act(() -> next());
+ IfaceUtil.act(LambdasTestClass::next);
+ IfaceUtil.act(LambdasTestClass::exact);
+ IfaceUtil.act(LambdasTestClass::almost);
+ }
+
+ private synchronized void testStatefulLambda(String a, String b) {
+ IfaceUtil.act(() -> a);
+ IfaceUtil.act(() -> a + b);
+ IfaceUtil.act((a + b)::toLowerCase);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index eba3be8..955d1ee 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -423,6 +423,8 @@
public abstract boolean isAnonymousClass();
+ public abstract boolean isSynthesizedJavaLambdaClass();
+
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
@@ -514,6 +516,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return false;
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return null;
}
@@ -709,6 +716,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return dexClass.type.getName().contains("$Lambda$");
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return DexInspector.this.getOriginalSignatureAttribute(
dexClass.annotations, GenericSignatureParser::parseClassSignature);