Reland "Desugar lib Time API conversions"
This reverts commit 04dfb343db9e3af5533a6f9ffd3ffc8288410183.
Change-Id: Ie068c9f26efbcd2ee64fbd0e4b2691ef64c6161a
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 9a71dc7..e3757da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -33,6 +33,7 @@
}
public Value getReceiver() {
+ assert inValues.size() > 0;
return inValues.get(0);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 0922cd2..9f22ded 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -19,9 +19,12 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
// TODO(b/134732760): In progress.
// I convert library calls with desugared parameters/return values so they can work normally.
@@ -72,13 +75,17 @@
if (dexClass == null || !dexClass.isLibraryClass()) {
continue;
}
+ // Library methods do not understand desugared types, hence desugared types have to be
+ // converted around non desugared library calls for the invoke to resolve.
if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) {
- addReturnConversion(code, invokeMethod, iterator);
+ rewriteLibraryInvoke(code, invokeMethod, iterator);
+ continue;
}
for (int i = 0; i < invokedMethod.proto.parameters.values.length; i++) {
DexType argType = invokedMethod.proto.parameters.values[i];
if (appView.rewritePrefix.hasRewrittenType(argType)) {
- addParameterConversion(code, invokeMethod, iterator, argType, i);
+ rewriteLibraryInvoke(code, invokeMethod, iterator);
+ continue;
}
}
}
@@ -109,117 +116,126 @@
return vivifiedType;
}
- private void addParameterConversion(
- IRCode code,
- InvokeMethod invokeMethod,
- InstructionListIterator iterator,
- DexType argType,
- int parameter) {
- if (!appView
- .options()
- .desugaredLibraryConfiguration
- .getCustomConversions()
- .containsKey(argType)) {
- // TODO(b/134732760): Add Wrapper Conversions.
- warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
- return;
- }
-
- Value inValue = invokeMethod.inValues().get(parameter);
- DexType argVivifiedType = vivifiedTypeFor(argType);
- DexType conversionHolder =
- appView.options().desugaredLibraryConfiguration.getCustomConversions().get(argType);
-
- // ConversionType has static method "type convert(rewrittenType)".
- // But everything is going to be rewritten, so we need to call "vivifiedType convert(type)".
- DexMethod conversionMethod =
- factory.createMethod(
- conversionHolder,
- factory.createProto(argVivifiedType, argType),
- factory.convertMethodName);
- Value convertedValue =
- code.createValue(
- TypeLatticeElement.fromDexType(
- argVivifiedType, inValue.getTypeLattice().nullability(), appView));
- InvokeStatic conversionInstruction =
- new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
- conversionInstruction.setPosition(invokeMethod.getPosition());
- iterator.previous();
- iterator.add(conversionInstruction);
- iterator.next();
-
- // Rewrite invoke (signature and inValue to rewrite).
- DexMethod newDexMethod =
- dexMethodWithDifferentParameter(
- invokeMethod.getInvokedMethod(), argVivifiedType, parameter);
- Invoke newInvokeMethod =
- Invoke.create(
- invokeMethod.getType(),
- newDexMethod,
- newDexMethod.proto,
- invokeMethod.outValue(),
- invokeMethod.inValues());
- newInvokeMethod.replaceValue(parameter, conversionInstruction.outValue());
- iterator.replaceCurrentInstruction(newInvokeMethod);
- }
-
- private void addReturnConversion(
+ private void rewriteLibraryInvoke(
IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
- DexType returnType = invokeMethod.getReturnType();
- if (!appView
- .options()
- .desugaredLibraryConfiguration
- .getCustomConversions()
- .containsKey(returnType)) {
- // TODO(b/134732760): Add Wrapper Conversions.
- warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
- return;
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+
+ // Create return conversion if required.
+ Instruction returnConversion = null;
+ DexType newReturnType;
+ DexType returnType = invokedMethod.proto.returnType;
+ if (appView.rewritePrefix.hasRewrittenType(returnType)) {
+ if (appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getCustomConversions()
+ .containsKey(returnType)) {
+ newReturnType = vivifiedTypeFor(returnType);
+ // Return conversion added only if return value is used.
+ if (invokeMethod.outValue() != null
+ && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
+ > 0) {
+ returnConversion =
+ createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
+ }
+ } else {
+ // TODO(b/134732760): Add Wrapper Conversions.
+ warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
+ newReturnType = returnType;
+ }
+ } else {
+ newReturnType = returnType;
}
- DexType returnVivifiedType = vivifiedTypeFor(returnType);
- DexType conversionHolder =
- appView.options().desugaredLibraryConfiguration.getCustomConversions().get(returnType);
+ // Create parameter conversions if required.
+ List<Instruction> parameterConversions = new ArrayList<>();
+ List<Value> newInValues = new ArrayList<>();
+ if (invokeMethod.isInvokeMethodWithReceiver()) {
+ assert !appView.rewritePrefix.hasRewrittenType(invokedMethod.holder);
+ newInValues.add(invokeMethod.asInvokeMethodWithReceiver().getReceiver());
+ }
+ int receiverShift = BooleanUtils.intValue(invokeMethod.isInvokeMethodWithReceiver());
+ DexType[] parameters = invokedMethod.proto.parameters.values;
+ DexType[] newParameters = parameters.clone();
+ for (int i = 0; i < parameters.length; i++) {
+ DexType argType = parameters[i];
+ if (appView.rewritePrefix.hasRewrittenType(argType)) {
+ if (appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getCustomConversions()
+ .containsKey(argType)) {
+ DexType argVivifiedType = vivifiedTypeFor(argType);
+ Value inValue = invokeMethod.inValues().get(i + receiverShift);
+ newParameters[i] = argVivifiedType;
+ parameterConversions.add(
+ createParameterConversion(code, argType, argVivifiedType, inValue));
+ newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
+ } else {
+ // TODO(b/134732760): Add Wrapper Conversions.
+ warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
+ newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+ }
+ } else {
+ newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+ }
+ }
- // ConversionType has static method "rewrittenType convert(type)".
- // But everything is going to be rewritten, so we need to call "type convert(vivifiedType)".
- DexMethod conversionMethod =
- factory.createMethod(
- conversionHolder,
- factory.createProto(returnType, returnVivifiedType),
- factory.convertMethodName);
- Value convertedValue =
- code.createValue(
- TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView));
- invokeMethod.outValue().replaceUsers(convertedValue);
- InvokeStatic conversionInstruction =
- new InvokeStatic(
- conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
- conversionInstruction.setPosition(invokeMethod.getPosition());
-
- // Rewrite invoke (signature to rewrite).
+ // Patch the invoke with new types and new inValues.
+ DexProto newProto = factory.createProto(newReturnType, newParameters);
DexMethod newDexMethod =
- dexMethodWithDifferentReturn(invokeMethod.getInvokedMethod(), returnVivifiedType);
+ factory.createMethod(invokedMethod.holder, newProto, invokedMethod.name);
Invoke newInvokeMethod =
Invoke.create(
invokeMethod.getType(),
newDexMethod,
newDexMethod.proto,
invokeMethod.outValue(),
- invokeMethod.inValues());
+ newInValues);
+
+ // Insert and reschedule all instructions.
+ iterator.previous();
+ for (Instruction parameterConversion : parameterConversions) {
+ parameterConversion.setPosition(invokeMethod.getPosition());
+ iterator.add(parameterConversion);
+ }
+ assert iterator.peekNext() == invokeMethod;
+ iterator.next();
iterator.replaceCurrentInstruction(newInvokeMethod);
- iterator.add(conversionInstruction);
+ if (returnConversion != null) {
+ returnConversion.setPosition(invokeMethod.getPosition());
+ iterator.add(returnConversion);
+ }
}
- private DexMethod dexMethodWithDifferentParameter(
- DexMethod method, DexType newParameterType, int parameter) {
- DexType[] newParameters = method.proto.parameters.values.clone();
- newParameters[parameter] = newParameterType;
- DexProto newProto = factory.createProto(method.proto.returnType, newParameters);
- return factory.createMethod(method.holder, newProto, method.name);
+ private Instruction createParameterConversion(
+ IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
+ DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
+ // The value is null only if the input is null.
+ Value convertedValue =
+ createConversionValue(code, inValue.getTypeLattice().nullability(), argVivifiedType);
+ return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
}
- private DexMethod dexMethodWithDifferentReturn(DexMethod method, DexType newReturnType) {
- DexProto newProto = factory.createProto(newReturnType, method.proto.parameters.values);
- return factory.createMethod(method.holder, newProto, method.name);
+ private Instruction createReturnConversionAndReplaceUses(
+ IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
+ DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
+ Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
+ invokeMethod.outValue().replaceUsers(convertedValue);
+ return new InvokeStatic(
+ conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
+ }
+
+ private DexMethod createConversionMethod(DexType type, DexType srcType, DexType destType) {
+ // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
+ // But everything is going to be rewritten, so we need to use vivifiedType and type".
+ DexType conversionHolder =
+ appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
+ return factory.createMethod(
+ conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
+ }
+
+ private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
+ return code.createValue(TypeLatticeElement.fromDexType(valueType, nullability, appView));
}
}
diff --git a/src/test/desugaredLibraryConversions/stubs/Duration.java b/src/test/desugaredLibraryConversions/stubs/Duration.java
index e559a8f..e49693f 100644
--- a/src/test/desugaredLibraryConversions/stubs/Duration.java
+++ b/src/test/desugaredLibraryConversions/stubs/Duration.java
@@ -9,7 +9,7 @@
return null;
}
- public int getSeconds() {
+ public long getSeconds() {
return 0;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
new file mode 100644
index 0000000..71e1a3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import org.junit.Test;
+
+public class AllTimeConversionTest extends APIConversionTestBase {
+
+ @Test
+ public void testRewrittenAPICalls() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "1970-01-02T00:00Z[GMT]",
+ "PT0.000012345S",
+ "GMT",
+ "--03-02",
+ "-1000000000-01-01T00:00:00.999999999Z",
+ "GMT",
+ "GMT"));
+ }
+
+ static class Executor {
+
+ private static final String ZONE_ID = "GMT";
+
+ public static void main(String[] args) {
+ returnValueUsed();
+ returnValueUnused();
+ virtualMethods();
+ }
+
+ public static void returnValueUsed() {
+ System.out.println(
+ CustomLibClass.mix(
+ ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+ ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID))));
+ CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+ System.out.println(CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345)));
+ System.out.println(CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID)));
+ System.out.println(CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2)));
+ System.out.println(CustomLibClass.mix(Instant.MIN, Instant.MAX));
+ }
+
+ public static void returnValueUnused() {
+ CustomLibClass.mix(
+ ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+ ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)));
+ CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+ CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345));
+ CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID));
+ CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2));
+ CustomLibClass.mix(Instant.MIN, Instant.MAX);
+ }
+
+ public static void virtualMethods() {
+ ZoneId of = ZoneId.of(ZONE_ID);
+ CustomLibClass customLibClass = new CustomLibClass();
+ customLibClass.virtual(of);
+ customLibClass.virtualString(of);
+ System.out.println(customLibClass.virtual(of));
+ System.out.println(customLibClass.virtualString(of));
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. None of the methods make sense.
+ static class CustomLibClass {
+
+ public static ZonedDateTime mix(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
+ return zonedDateTime1.plusDays(zonedDateTime2.getDayOfMonth());
+ }
+
+ public static LocalDate mix(LocalDate localDate1, LocalDate localDate2) {
+ return localDate1.plusDays(localDate2.getDayOfYear());
+ }
+
+ public static Duration mix(Duration duration1, Duration duration2) {
+ return duration1.plus(duration2);
+ }
+
+ public static ZoneId mix(ZoneId zoneId1, ZoneId zoneId2) {
+ return zoneId1;
+ }
+
+ public static MonthDay mix(MonthDay monthDay1, MonthDay monthDay2) {
+ return monthDay1.withDayOfMonth(monthDay2.getDayOfMonth());
+ }
+
+ public static Instant mix(Instant instant1, Instant instant2) {
+ return instant1.plusNanos(instant2.getNano());
+ }
+
+ public ZoneId virtual(ZoneId zoneId) {
+ return zoneId;
+ }
+
+ public String virtualString(ZoneId zoneId) {
+ return zoneId.getId();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
index 2cdf6e7..e82ae21 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
@@ -25,7 +25,7 @@
import java.util.TimeZone;
import org.junit.Test;
-public class TimeConversionCompilationTest extends APIConversionTestBase {
+public class BasicTimeConversionTest extends APIConversionTestBase {
@Test
public void testTimeGeneratedDex() throws Exception {
@@ -53,7 +53,7 @@
public void testRewrittenAPICalls() throws Exception {
testForD8()
.setMinApi(AndroidApiLevel.B)
- .addInnerClasses(TimeConversionCompilationTest.class)
+ .addInnerClasses(BasicTimeConversionTest.class)
.enableCoreLibraryDesugaring(AndroidApiLevel.B)
.compile()
.inspect(this::checkAPIRewritten)