Initial support for desugaring lambdas when compiling to CF
Except for adding an extra flag a few places, the main change is to
maintain proper debug information by replacing the this local variable
with a fake one, since the method is now static.
Bug: 146031650
Change-Id: I69d30284caed247665482fd0ec25efb073bd9f15
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index b9e8757..2753232 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
+import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
+
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfIinc;
@@ -26,6 +29,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Strings;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -518,4 +522,32 @@
}
return constraint;
}
+
+ void addFakeThisParameter(DexItemFactory factory) {
+ if (localVariables == null || localVariables.isEmpty()) {
+ // We have no debugging info in the code.
+ return;
+ }
+ int largestPrefix = 1;
+ int existingThisIndex = -1;
+ for (int i = 0; i < localVariables.size(); i++) {
+ LocalVariableInfo localVariable = localVariables.get(i);
+ largestPrefix =
+ Math.max(largestPrefix, DexCode.getLargestPrefix(factory, localVariable.local.name));
+ if (localVariable.local.name.toString().equals("this")) {
+ existingThisIndex = i;
+ }
+ }
+ if (existingThisIndex < 0) {
+ return;
+ }
+ String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX;
+ DebugLocalInfo debugLocalInfo =
+ new DebugLocalInfo(factory.createString(fakeThisName), this.originalHolder, null);
+ LocalVariableInfo thisLocalInfo = localVariables.get(existingThisIndex);
+ this.localVariables.set(
+ existingThisIndex,
+ new LocalVariableInfo(
+ thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
+ }
}
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 a5b1097..d32abdf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -32,8 +32,8 @@
// DexCode corresponds to code item in dalvik/dex-format.html
public class DexCode extends Code {
- private static final String FAKE_THIS_PREFIX = "_";
- private static final String FAKE_THIS_SUFFIX = "this";
+ static final String FAKE_THIS_PREFIX = "_";
+ static final String FAKE_THIS_SUFFIX = "this";
public final int registerSize;
public final int incomingRegisterSize;
@@ -132,7 +132,7 @@
return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
}
- private static int getLargestPrefix(DexItemFactory factory, DexString name) {
+ public static int getLargestPrefix(DexItemFactory factory, DexString name) {
if (name != null && name.endsWith(factory.thisName)) {
String string = name.toString();
for (int i = 0; i < string.length(); i++) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index b276b8b..e4fff90 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -758,8 +758,11 @@
assert (dexCode.getDebugInfo() == null)
|| (arity == dexCode.getDebugInfo().parameters.length);
} else {
- // TODO(b/134732760): Patch Cf debug info.
- assert appView.options().isDesugaredLibraryCompilation();
+ assert appView.options().isDesugaredLibraryCompilation()
+ || appView.options().enableCfInterfaceMethodDesugaring;
+ assert code.isCfCode();
+ CfCode cfCode = code.asCfCode();
+ cfCode.addFakeThisParameter(appView.dexItemFactory());
}
}
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 e7ba0b5..ce77849 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,6 +29,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Suppliers;
import com.google.common.primitives.Longs;
import java.nio.ByteBuffer;
@@ -494,7 +495,8 @@
}
boolean holderIsInterface() {
- if (!rewriter.converter.appView.options().isGeneratingClassFiles()) {
+ InternalOptions options = rewriter.converter.appView.options();
+ if (!options.isGeneratingClassFiles()) {
// When generating dex the value of this flag on invokes does not matter (unused).
// We cannot know if definitionFor(implMethod.holder) is null or not in that case,
// so we cannot set the flag and just return false.
@@ -503,14 +505,11 @@
// The only case where we do Lambda desugaring with Cf to Cf is in L8.
// If the compilation is not coreLibraryCompilation, then the assertion
// implMethodHolder != null may fail, hence the assertion.
- assert rewriter.converter.appView.options().isDesugaredLibraryCompilation();
+ assert options.isDesugaredLibraryCompilation() || options.enableCfInterfaceMethodDesugaring;
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
if (implMethodHolder == null) {
- assert rewriter
- .converter
- .appView
- .options()
+ assert options
.desugaredLibraryConfiguration
.getBackportCoreLibraryMember()
.containsKey(implMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 6aee26c..1600057 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -113,7 +113,9 @@
}
String markerString = marker.toString();
for (DexProgramClass clazz : application.classes()) {
- if (clazz.getSynthesizedFrom().isEmpty() || options.isDesugaredLibraryCompilation()) {
+ if (clazz.getSynthesizedFrom().isEmpty()
+ || options.isDesugaredLibraryCompilation()
+ || options.enableCfInterfaceMethodDesugaring) {
writeClass(clazz, consumer, markerString);
} else {
throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
@@ -201,8 +203,11 @@
// In this case bridges have been introduced for the Cf back-end,
// which do not have class file version.
assert options.testing.enableForceNestBasedAccessDesugaringForTest
- || options.isDesugaredLibraryCompilation();
- return 0;
+ || options.isDesugaredLibraryCompilation()
+ || options.enableCfInterfaceMethodDesugaring;
+ // TODO(b/146424042): We may call static methods on interface classes so we have to go for
+ // version 52.
+ return options.enableCfInterfaceMethodDesugaring ? 52 : 0;
}
return method.getClassFileVersion();
}
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 98ac732..c4815ae 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -217,6 +217,7 @@
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
public boolean enableSourceDebugExtensionRewriter = false;
+ public boolean enableCfInterfaceMethodDesugaring = false;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
@@ -1124,7 +1125,7 @@
}
return enableDesugaring
&& interfaceMethodDesugaring == OffOrAuto.Auto
- && !canUseDefaultAndStaticInterfaceMethods();
+ && (!canUseDefaultAndStaticInterfaceMethods() || enableCfInterfaceMethodDesugaring);
}
public boolean isStringSwitchConversionEnabled() {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index fa2c751..0f613d5 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -43,7 +43,7 @@
// Default initialized setup. Can be overwritten if needed.
private boolean useDefaultRuntimeLibrary = true;
- final List<Path> additionalRunClassPath = new ArrayList<>();
+ private final List<Path> additionalRunClassPath = new ArrayList<>();
private ProgramConsumer programConsumer;
private StringConsumer mainDexListConsumer;
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
index 10d74a8..feda70f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -3,8 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.debug.DebugTestBase;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
import com.android.tools.r8.debug.DebugTestConfig;
@@ -17,25 +24,43 @@
import java.util.Collections;
import java.util.function.Function;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class DefaultLambdaWithUnderscoreThisTestRunner extends DebugTestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultLambdaWithUnderscoreThisTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
final Class<?> CLASS = DefaultLambdaWithUnderscoreThisTest.class;
final String EXPECTED = StringUtils
.lines("stateful(My _this variable foo Another ___this variable)");
- private void runDebugger(DebugTestConfig config) throws Throwable {
+ private void runDebugger(DebugTestConfig config, boolean desugared) throws Throwable {
MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
MethodReference stateful = Reference.methodFromMethod(I.class.getMethod("stateful"));
- Function<String, Command> checkThis = (String desugarThis) -> conditional((state) ->
- state.isCfRuntime()
- ? Collections.singletonList(checkLocal("this"))
- : ImmutableList.of(
- checkNoLocal("this"),
- checkLocal(desugarThis)));
+ Function<String, Command> checkThis =
+ (String desugarThis) ->
+ conditional(
+ (state) ->
+ !desugared
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(checkNoLocal("this"), checkLocal(desugarThis)));
- runDebugTest(config, CLASS,
+ runDebugTest(
+ config,
+ CLASS,
breakpoint(main, 22),
run(),
checkLine(22),
@@ -55,27 +80,53 @@
checkLine(15),
// Desugaring will insert '____this' in place of 'this' here.
checkThis.apply("____this"),
- checkLocals("_this", "___this"),
+ checkLocals("_this", "___this"),
run());
}
@Test
public void testJvm() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
JvmTestBuilder builder = testForJvm().addTestClasspath();
- builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
- runDebugger(builder.debugConfig());
+ builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(builder.debugConfig(), false);
}
@Test
public void testD8() throws Throwable {
- D8TestCompileResult compileResult = testForD8()
- .addProgramClassesAndInnerClasses(CLASS)
- .setMinApiThreshold(AndroidApiLevel.K)
- .compile();
+ assumeTrue(parameters.isDexRuntime());
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApiThreshold(AndroidApiLevel.K)
+ .compile();
compileResult
// TODO(b/123506120): Add .assertNoMessages()
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED);
- runDebugger(compileResult.debugConfig());
+ runDebugger(compileResult.debugConfig(), true);
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(CLASS)
+ .addKeepAllClassesRule()
+ .noMinification()
+ .setMode(CompilationMode.DEBUG)
+ .addOptionsModification(
+ internalOptions -> {
+ if (parameters.isCfRuntime()) {
+ internalOptions.enableDesugaring = true;
+ internalOptions.enableCfInterfaceMethodDesugaring = true;
+ }
+ });
+ if (parameters.isDexRuntime()) {
+ r8FullTestBuilder.setMinApiThreshold(AndroidApiLevel.K);
+ }
+ R8TestCompileResult compileResult = r8FullTestBuilder.compile();
+ compileResult.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig(), true);
}
}