Merge "CF backend: Implement InvokeCustom (JVM InvokeDynamic)"
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index ea6eb54..39d916a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.cf.code.CfInstanceOf;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfMonitor;
@@ -228,6 +229,13 @@
appendMethod(invoke.getMethod());
}
+ public void print(CfInvokeDynamic invoke) {
+ indent();
+ builder.append(opcodeName(Opcodes.INVOKEDYNAMIC)).append(' ');
+ builder.append(invoke.getCallSite().methodName);
+ builder.append(invoke.getCallSite().methodProto.toDescriptorString());
+ }
+
public void print(CfFrame frame) {
StringBuilder builder = new StringBuilder("frame: [");
String separator = "";
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 5a07602..86903fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -14,7 +14,7 @@
private final int opcode;
public CfInvoke(int opcode, DexMethod method) {
- assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEDYNAMIC;
+ assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEINTERFACE;
this.opcode = opcode;
this.method = method;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
new file mode 100644
index 0000000..43e6133
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -0,0 +1,79 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import java.util.List;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+public class CfInvokeDynamic extends CfInstruction {
+
+ private final DexCallSite callSite;
+
+ public CfInvokeDynamic(DexCallSite callSite) {
+ this.callSite = callSite;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+ List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
+ Object[] bsmArgs = new Object[bootstrapArgs.size()];
+ for (int i = 0; i < bootstrapArgs.size(); i++) {
+ bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i));
+ }
+ Handle bsmHandle = bootstrapMethod.toAsmHandle();
+ visitor.visitInvokeDynamicInsn(
+ callSite.methodName.toString(),
+ callSite.methodProto.toDescriptorString(),
+ bsmHandle,
+ bsmArgs);
+ }
+
+ private Object decodeBootstrapArgument(DexValue dexValue) {
+ if (dexValue instanceof DexValueInt) {
+ return ((DexValueInt) dexValue).getValue();
+ } else if (dexValue instanceof DexValueLong) {
+ return ((DexValueLong) dexValue).getValue();
+ } else if (dexValue instanceof DexValueFloat) {
+ return ((DexValueFloat) dexValue).getValue();
+ } else if (dexValue instanceof DexValueDouble) {
+ return ((DexValueDouble) dexValue).getValue();
+ } else if (dexValue instanceof DexValueString) {
+ return ((DexValueString) dexValue).getValue();
+ } else if (dexValue instanceof DexValueType) {
+ return Type.getType(((DexValueType) dexValue).value.toDescriptorString());
+ } else if (dexValue instanceof DexValueMethodType) {
+ return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString());
+ } else if (dexValue instanceof DexValueMethodHandle) {
+ return ((DexValueMethodHandle) dexValue).value.toAsmHandle();
+ } else {
+ throw new Unreachable(
+ "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
+ }
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public DexCallSite getCallSite() {
+ return callSite;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 38d83ed..0579362 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -3,9 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
import com.android.tools.r8.code.InvokeCustomRange;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -70,6 +74,11 @@
}
@Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfInvokeDynamic(getCallSite()));
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.isInvokeCustom() && callSite == other.asInvokeCustom().callSite;
}
@@ -95,4 +104,30 @@
public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
return Constraint.NEVER;
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ // Essentially the same as InvokeMethod but with call site's method proto
+ // instead of a static called method.
+ helper.loadInValues(this, it);
+ if (getCallSite().methodProto.returnType.isVoidType()) {
+ return;
+ }
+ if (outValue == null) {
+ helper.popOutType(getCallSite().methodProto.returnType, this, it);
+ } else {
+ assert outValue.isUsed();
+ helper.storeOutValue(this, it);
+ }
+ }
+
+ @Override
+ public boolean hasInvariantVerificationType() {
+ return true;
+ }
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return getCallSite().methodProto.returnType;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTest.java b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
new file mode 100644
index 0000000..983f1b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
@@ -0,0 +1,17 @@
+// 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.cf;
+
+import java.util.function.Function;
+
+public class LambdaTest {
+ public static void main(String[] args) {
+ twicePrint(args.length, i -> i + 21);
+ }
+
+ private static void twicePrint(int v, Function<Integer, Integer> f) {
+ System.out.println(f.andThen(f).apply(v));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
new file mode 100644
index 0000000..21d0292
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -0,0 +1,118 @@
+// 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.cf;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.JarCode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexInspector;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InvokeDynamicInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public class LambdaTestRunner {
+
+ private static final Class<?> CLASS = LambdaTest.class;
+ private static final String METHOD = "main";
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void test() throws Exception {
+ // Test that the InvokeDynamic instruction in LambdaTest.main()
+ // is not modified by the R8 compilation.
+ // First, extract the InvokeDynamic instruction from the input class.
+ byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
+ int opcode = Opcodes.INVOKEDYNAMIC;
+ InvokeDynamicInsnNode insnInput = findFirstInMethod(inputClass, opcode);
+ // Compile with R8 and extract the InvokeDynamic instruction from the output class.
+ AndroidAppConsumers appBuilder = new AndroidAppConsumers();
+ Path outPath = temp.getRoot().toPath().resolve("out.jar");
+ R8.run(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
+ .addClassProgramData(inputClass, Origin.unknown())
+ .build());
+ AndroidApp app = appBuilder.build();
+ InvokeDynamicInsnNode insnOutput = findFirstInMethod(app, opcode);
+ // Check that the InvokeDynamic instruction is not modified.
+ assertEquals(insnInput.name, insnOutput.name);
+ assertEquals(insnInput.desc, insnOutput.desc);
+ assertEquals(insnInput.bsm, insnOutput.bsm);
+ assertArrayEquals(insnInput.bsmArgs, insnOutput.bsmArgs);
+ // Check that execution gives the same output.
+ ProcessResult inputResult =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getName());
+ ProcessResult outputResult = ToolHelper.runJava(outPath, CLASS.getName());
+ assertEquals(inputResult.toString(), outputResult.toString());
+ }
+
+ private InvokeDynamicInsnNode findFirstInMethod(AndroidApp app, int opcode) throws Exception {
+ String returnType = "void";
+ DexInspector inspector = new DexInspector(app);
+ List<String> args = Collections.singletonList(String[].class.getTypeName());
+ DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
+ JarCode jarCode = method.getCode().asJarCode();
+ MethodNode outputMethod = jarCode.getNode();
+ return (InvokeDynamicInsnNode) findFirstInstruction(outputMethod, opcode);
+ }
+
+ private InvokeDynamicInsnNode findFirstInMethod(byte[] clazz, int opcode) {
+ MethodNode[] method = {null};
+ new ClassReader(clazz)
+ .accept(
+ new ClassNode(Opcodes.ASM6) {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals(METHOD)) {
+ method[0] = new MethodNode(access, name, desc, signature, exceptions);
+ return method[0];
+ } else {
+ return null;
+ }
+ }
+ },
+ 0);
+ return (InvokeDynamicInsnNode) findFirstInstruction(method[0], opcode);
+ }
+
+ private AbstractInsnNode findFirstInstruction(MethodNode node, int opcode) {
+ assert node != null;
+ InsnList asmInsns = node.instructions;
+ for (ListIterator<AbstractInsnNode> it = asmInsns.iterator(); it.hasNext(); ) {
+ AbstractInsnNode insn = it.next();
+ if (insn.getOpcode() == opcode) {
+ return insn;
+ }
+ }
+ throw new RuntimeException("Instruction not found");
+ }
+}