aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Zerny <zerny@google.com>2017-10-26 09:29:25 +0200
committerIan Zerny <zerny@google.com>2017-10-26 09:29:25 +0200
commitdbb891e86e8cf34b71c7096e1710009292d8e72e (patch)
tree040d07473872dc9313bb81d76c45ce4cd767f9da
parent2e49cc0222901d4220fbd46b1785187b7e98d0e8 (diff)
downloadr8-dbb891e86e8cf34b71c7096e1710009292d8e72e.tar.gz
Partial identity transformation from Java bytecode to Java bytecode.
This change minimally enables writing out Java bytecode from Java bytecode inputs for a simple hello world program. Follow-up work will extend the support to the full program structure and producing code via our IR. Bug: 65390962 Change-Id: I978c7400f9a21323dc0c5e95fc55272300839878
-rw-r--r--src/main/java/com/android/tools/r8/BaseCompilerCommand.java2
-rw-r--r--src/main/java/com/android/tools/r8/OutputSink.java22
-rw-r--r--src/main/java/com/android/tools/r8/R8.java12
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexProgramClass.java10
-rw-r--r--src/main/java/com/android/tools/r8/graph/JarClassFileReader.java5
-rw-r--r--src/main/java/com/android/tools/r8/graph/JarCode.java5
-rw-r--r--src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java16
-rw-r--r--src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java94
-rw-r--r--src/main/java/com/android/tools/r8/utils/AndroidApp.java4
-rw-r--r--src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java29
-rw-r--r--src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java16
-rw-r--r--src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java6
-rw-r--r--src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java6
-rw-r--r--src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java6
-rw-r--r--src/main/java/com/android/tools/r8/utils/InternalOptions.java2
-rw-r--r--src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java11
-rw-r--r--src/test/java/com/android/tools/r8/R8RunExamplesTest.java55
-rw-r--r--src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java8
-rw-r--r--src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java35
19 files changed, 316 insertions, 28 deletions
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5f438492d..6c4b9bcd0 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -81,6 +81,8 @@ abstract class BaseCompilerCommand extends BaseCommand {
if (outputPath == null) {
return new IgnoreContentsOutputSink();
} else {
+ // TODO(zerny): Calling getInternalOptions here is incorrect since any modifications by an
+ // options consumer will not be visible to the sink.
return FileSystemOutputSink.create(outputPath, getInternalOptions());
}
}
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index 8ffb792a1..a73391ae2 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -32,7 +32,8 @@ public interface OutputSink {
* gives the current file count.
* <p>
* Files are not necessarily generated in order and files might be written concurrently. However,
- * for each fileId only one file is ever written.
+ * for each fileId only one file is ever written. If this method is called, the other writeDexFile
+ * and writeClassFile methods will not be called.
*/
void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId) throws IOException;
@@ -46,13 +47,28 @@ public interface OutputSink {
* primaryClassName only one file is ever written.
* <p>
* This method is only invoked by D8 and only if compiling each class into its own dex file, e.g.,
- * for incremental compilation. If this method is called, the other writeDexFile method will
- * not be called.
+ * for incremental compilation. If this method is called, the other writeDexFile and
+ * writeClassFile methods will not be called.
*/
void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
throws IOException;
/**
+ * Write a Java classfile that contains the class primaryClassName and its companion classes.
+ * <p>
+ * This is equivalent to writing out the file com/foo/bar/Test.class given a primaryClassName of
+ * com.foo.bar.Test.
+ * <p>
+ * There is no guaranteed order and files might be written concurrently. However, for each
+ * primaryClassName only one file is ever written.
+ * <p>
+ * This method is only invoked by R8 and only if compiling to Java bytecode. If this method is
+ * called, the other writeDexFile and writeClassFile methods will not be called.
+ */
+ void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
+ throws IOException;
+
+ /**
* Provides the raw bytes that would be generated for the <code>-printusage</code> flag.
* <p>
* This method is only invoked by R8 and only if R8 is instructed to generate printusage
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5255fc5d6..d9066bdf1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
+import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.naming.Minifier;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.SourceFileRewriter;
@@ -92,11 +93,14 @@ public class R8 {
throws ExecutionException, DexOverflowException {
try {
Marker marker = getMarker(options);
- new ApplicationWriter(
- application, options, marker, deadCode, namingLens, proguardSeedsData)
- .write(outputSink, executorService);
+ if (options.outputClassFiles) {
+ new CfApplicationWriter(application, options).write(outputSink, executorService);
+ } else {
+ new ApplicationWriter(application, options, marker, deadCode, namingLens, proguardSeedsData)
+ .write(outputSink, executorService);
+ }
} catch (IOException e) {
- throw new RuntimeException("Cannot write dex application", e);
+ throw new RuntimeException("Cannot write application", e);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index b64053ae0..ad1af107f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -25,6 +25,7 @@ public class DexProgramClass extends DexClass implements Supplier<DexProgramClas
private final ProgramResource.Kind originKind;
private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED;
private final Collection<DexProgramClass> synthesizedFrom;
+ private int classFileVersion = -1;
public DexProgramClass(
DexType type,
@@ -296,4 +297,13 @@ public class DexProgramClass extends DexClass implements Supplier<DexProgramClas
public DexProgramClass get() {
return this;
}
+
+ public void setClassFileVersion(int classFileVersion) {
+ this.classFileVersion = classFileVersion;
+ }
+
+ public int getClassFileVersion() {
+ assert classFileVersion != -1;
+ return classFileVersion;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 001480fcb..9742df7d3 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -101,6 +101,7 @@ public class JarClassFileReader {
private final ReparseContext context = new ReparseContext();
// DexClass data.
+ private int version;
private DexType type;
private DexAccessFlags accessFlags;
private DexType superType;
@@ -174,6 +175,7 @@ public class JarClassFileReader {
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
+ this.version = version;
accessFlags = createAccessFlags(access);
// Unset the (in dex) non-existent ACC_SUPER flag on the class.
assert Constants.ACC_SYNCHRONIZED == Opcodes.ACC_SUPER;
@@ -263,6 +265,9 @@ public class JarClassFileReader {
if (classKind == ClassKind.PROGRAM) {
context.owner = clazz.asProgramClass();
}
+ if (clazz.isProgramClass()) {
+ clazz.asProgramClass().setClassFileVersion(version);
+ }
classConsumer.accept(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 5988ae5ce..e8420284f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -27,6 +27,11 @@ import org.objectweb.asm.util.TraceMethodVisitor;
public class JarCode extends Code {
+ // TODO(zerny): Write via the IR.
+ public void writeTo(MethodVisitor visitor) {
+ node.accept(visitor);
+ }
+
public static class ReparseContext {
// This will hold the content of the whole class. Once all the methods of the class are swapped
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 063bee1dd..dda8031a6 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
@@ -581,7 +581,23 @@ public class IRConverter {
}
printMethod(code, "Optimized IR (SSA)");
+ finalizeIR(method, code, feedback);
+ }
+
+ private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ if (options.outputClassFiles) {
+ finalizeToCf(method, code, feedback);
+ } else {
+ finalizeToDex(method, code, feedback);
+ }
+ }
+
+ private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ assert method.getCode().isJarCode();
+ // TODO(zerny): Actually convert IR back to Java bytecode.
+ }
+ private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
method.setCode(code, registerAllocator, options);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
new file mode 100644
index 000000000..4662cd0a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2017, 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.jar;
+
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+
+import com.android.tools.r8.OutputSink;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import jdk.internal.org.objectweb.asm.Type;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfApplicationWriter {
+ private final DexApplication application;
+ private final InternalOptions options;
+
+ public CfApplicationWriter(DexApplication application, InternalOptions options) {
+ this.application = application;
+ this.options = options;
+ }
+
+ public void write(OutputSink outputSink, ExecutorService executor) throws IOException {
+ application.timing.begin("CfApplicationWriter.write");
+ try {
+ writeApplication(outputSink, executor);
+ } finally {
+ application.timing.end();
+ }
+ }
+
+ private void writeApplication(OutputSink outputSink, ExecutorService executor)
+ throws IOException {
+ for (DexProgramClass clazz : application.classes()) {
+ if (clazz.getSynthesizedFrom().isEmpty()) {
+ writeClass(clazz, outputSink);
+ } else {
+ throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
+ }
+ }
+ }
+
+ private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ writer.visitSource(clazz.sourceFile.toString(), null);
+ int version = clazz.getClassFileVersion();
+ int access = classAndInterfaceAccessFlags(clazz.accessFlags);
+ String desc = clazz.type.toDescriptorString();
+ String name = internalName(clazz.type);
+ String signature = null; // TODO(zerny): Support generic signatures.
+ String superName =
+ clazz.type == options.itemFactory.objectType ? null : internalName(clazz.superType);
+ String[] interfaces = new String[clazz.interfaces.values.length];
+ for (int i = 0; i < clazz.interfaces.values.length; i++) {
+ interfaces[i] = internalName(clazz.interfaces.values[i]);
+ }
+ writer.visit(version, access, name, signature, superName, interfaces);
+ // TODO(zerny): Methods and fields.
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ writeMethod(method, writer);
+ }
+ outputSink.writeClassFile(writer.toByteArray(), Collections.singleton(desc), desc);
+ }
+
+ private void writeMethod(DexEncodedMethod method, ClassWriter writer) {
+ int access = method.accessFlags.get();
+ String name = method.method.name.toString();
+ String desc = method.descriptor();
+ String signature = null; // TODO(zerny): Support generic signatures.
+ String[] exceptions = null;
+ MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
+ method.getCode().asJarCode().writeTo(visitor);
+ }
+
+ private static int classAndInterfaceAccessFlags(DexAccessFlags accessFlags) {
+ // TODO(zerny): Refactor access flags to account for the union of both DEX and Java flags.
+ int access = accessFlags.get();
+ access |= ACC_SUPER;
+ return access;
+ }
+
+ private static String internalName(DexType type) {
+ return Type.getType(type.toDescriptorString()).getInternalName();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index b082d9068..24f5c08b2 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -600,6 +600,10 @@ public class AndroidApp {
return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data));
}
+ public Builder addClassProgramData(Origin origin, byte[] data, Set<String> classDescriptors) {
+ return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data, classDescriptors));
+ }
+
/**
* Set dead-code data.
*/
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index 914428404..a4f223663 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.OutputSink;
+import com.android.tools.r8.Resource.Origin;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import java.util.TreeMap;
@@ -13,6 +16,7 @@ public class AndroidAppOutputSink extends ForwardingOutputSink {
private final AndroidApp.Builder builder = AndroidApp.builder();
private final TreeMap<String, DescriptorsWithContents> dexFilesWithPrimary = new TreeMap<>();
private final TreeMap<Integer, DescriptorsWithContents> dexFilesWithId = new TreeMap<>();
+ private final List<DescriptorsWithContents> classFiles = new ArrayList<>();
private boolean closed = false;
public AndroidAppOutputSink(OutputSink forwardTo) {
@@ -26,6 +30,7 @@ public class AndroidAppOutputSink extends ForwardingOutputSink {
@Override
public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
throws IOException {
+ assert dexFilesWithPrimary.isEmpty() && classFiles.isEmpty();
// Sort the files by id so that their order is deterministic. Some tests depend on this.
dexFilesWithId.put(fileId, new DescriptorsWithContents(classDescriptors, contents));
super.writeDexFile(contents, classDescriptors, fileId);
@@ -35,6 +40,7 @@ public class AndroidAppOutputSink extends ForwardingOutputSink {
public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors,
String primaryClassName)
throws IOException {
+ assert dexFilesWithId.isEmpty() && classFiles.isEmpty();
// Sort the files by their name for good measure.
dexFilesWithPrimary
.put(primaryClassName, new DescriptorsWithContents(classDescriptors, contents));
@@ -42,6 +48,14 @@ public class AndroidAppOutputSink extends ForwardingOutputSink {
}
@Override
+ public synchronized void writeClassFile(
+ byte[] contents, Set<String> classDescriptors, String primaryClassName) throws IOException {
+ assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty();
+ classFiles.add(new DescriptorsWithContents(classDescriptors, contents));
+ super.writeClassFile(contents, classDescriptors, primaryClassName);
+ }
+
+ @Override
public void writePrintUsedInformation(byte[] contents) throws IOException {
builder.setDeadCode(contents);
super.writePrintUsedInformation(contents);
@@ -68,9 +82,18 @@ public class AndroidAppOutputSink extends ForwardingOutputSink {
@Override
public void close() throws IOException {
assert !closed;
- assert dexFilesWithId.isEmpty() || dexFilesWithPrimary.isEmpty();
- dexFilesWithPrimary.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors, v));
- dexFilesWithId.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors));
+ if (!dexFilesWithPrimary.isEmpty()) {
+ assert dexFilesWithId.isEmpty() && classFiles.isEmpty();
+ dexFilesWithPrimary.forEach(
+ (v, d) -> builder.addDexProgramData(d.contents, d.descriptors, v));
+ } else if (!dexFilesWithId.isEmpty()) {
+ assert dexFilesWithPrimary.isEmpty() && classFiles.isEmpty();
+ dexFilesWithId.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors));
+ } else if (!classFiles.isEmpty()) {
+ assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty();
+ classFiles.forEach(
+ d -> builder.addClassProgramData(Origin.unknown(), d.contents, d.descriptors));
+ }
closed = true;
super.close();
}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
index 56e0ea149..6c1090a8c 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -43,7 +46,18 @@ public class DirectoryOutputSink extends FileSystemOutputSink {
@Override
public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
throws IOException {
- Path target = outputDirectory.resolve(getOutputFileName(primaryClassName));
+ writeFileFromDescriptor(contents, primaryClassName, DEX_EXTENSION);
+ }
+
+ @Override
+ public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
+ throws IOException {
+ writeFileFromDescriptor(contents, primaryClassName, CLASS_EXTENSION);
+ }
+
+ private void writeFileFromDescriptor(byte[] contents, String descriptor, String extension)
+ throws IOException {
+ Path target = outputDirectory.resolve(getOutputFileName(descriptor, extension));
Files.createDirectories(target.getParent());
writeToFile(target, null, contents);
}
diff --git a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
index c0aef0a6b..1ca0cf979 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -28,13 +28,13 @@ public abstract class FileSystemOutputSink implements OutputSink {
}
String getOutputFileName(int index) {
+ assert !options.outputClassFiles;
return index == 0 ? "classes.dex" : ("classes" + (index + 1) + FileUtils.DEX_EXTENSION);
}
- String getOutputFileName(String classDescriptor) throws IOException {
+ String getOutputFileName(String classDescriptor, String extension) throws IOException {
assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
- return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor)
- + FileUtils.DEX_EXTENSION;
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + extension;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
index 3ac0e2b54..f1763291a 100644
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
@@ -33,6 +33,12 @@ public abstract class ForwardingOutputSink implements OutputSink {
}
@Override
+ public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
+ throws IOException {
+ forwardTo.writeClassFile(contents, classDescriptors, primaryClassName);
+ }
+
+ @Override
public void writePrintUsedInformation(byte[] contents) throws IOException {
forwardTo.writePrintUsedInformation(contents);
}
diff --git a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
index 8dbf3bcbd..384c7d5bc 100644
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
@@ -20,6 +20,12 @@ public class IgnoreContentsOutputSink implements OutputSink {
}
@Override
+ public void writeClassFile(
+ byte[] contents, Set<String> classDescriptors, String primaryClassName) {
+ // Intentionally left empty.
+ }
+
+ @Override
public void writePrintUsedInformation(byte[] contents) {
// Intentionally left empty.
}
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 75f89beac..a04e28fe5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -51,6 +51,8 @@ public class InternalOptions {
public boolean printTimes = false;
+ public boolean outputClassFiles = false;
+
// Optimization-related flags. These should conform to -dontoptimize.
public boolean skipDebugLineNumberOpt = false;
public boolean skipClassMerging = true;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java b/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
index d290b564f..c1ad6c933 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -31,7 +34,13 @@ public class ZipFileOutputSink extends FileSystemOutputSink {
@Override
public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
throws IOException {
- writeToZipFile(getOutputFileName(primaryClassName), contents);
+ writeToZipFile(getOutputFileName(primaryClassName, DEX_EXTENSION), contents);
+ }
+
+ @Override
+ public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
+ throws IOException {
+ writeToZipFile(getOutputFileName(primaryClassName, CLASS_EXTENSION), contents);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index f1ebcbb3f..96d9e8b5b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -7,6 +7,7 @@ import static com.android.tools.r8.TestCondition.R8_COMPILER;
import static com.android.tools.r8.TestCondition.match;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
@@ -42,6 +43,11 @@ public class R8RunExamplesTest {
DX, JAVAC, JAVAC_ALL, JAVAC_NONE
}
+ enum Output {
+ DEX,
+ CF
+ }
+
private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_BUILD_DIR;
// For local testing on a specific Art version(s) change this set. e.g. to
@@ -65,7 +71,7 @@ public class R8RunExamplesTest {
TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
.build();
- @Parameters(name = "{0}_{1}_{2}_{3}")
+ @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
public static Collection<String[]> data() {
String[] tests = {
"arithmetic.Arithmetic",
@@ -115,6 +121,10 @@ public class R8RunExamplesTest {
"switchmaps.Switches",
};
+ String[] javaBytecodeTests = {
+ "hello.Hello",
+ };
+
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
for (String test : tests) {
fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
@@ -130,13 +140,26 @@ public class R8RunExamplesTest {
test));
fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
}
+ // TODO(zerny): Once all tests pass create the java tests in the main test loop.
+ for (String test : javaBytecodeTests) {
+ fullTestList.add(
+ makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG, test, Output.CF));
+ fullTestList.add(
+ makeTest(
+ Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
+ }
return fullTestList;
}
private static String[] makeTest(
Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz) {
+ return makeTest(input, compiler, mode, clazz, Output.DEX);
+ }
+
+ private static String[] makeTest(
+ Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz, Output output) {
String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
- return new String[]{pkg, input.name(), compiler.name(), mode.name(), clazz};
+ return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
}
@Rule
@@ -147,18 +170,21 @@ public class R8RunExamplesTest {
private final CompilationMode mode;
private final String pkg;
private final String mainClass;
+ private final Output output;
public R8RunExamplesTest(
String pkg,
String input,
String compiler,
String mode,
- String mainClass) {
+ String mainClass,
+ String output) {
this.pkg = pkg;
this.input = Input.valueOf(input);
this.compiler = CompilerUnderTest.valueOf(compiler);
this.mode = CompilationMode.valueOf(mode);
this.mainClass = mainClass;
+ this.output = Output.valueOf(output);
}
private Path getOutputFile() {
@@ -192,15 +218,19 @@ public class R8RunExamplesTest {
return input == Input.DX ? DexTool.DX : DexTool.NONE;
}
+ private Path getOutputPath() {
+ return temp.getRoot().toPath().resolve("out.jar");
+ }
+
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void compile()
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
- Path out = temp.getRoot().toPath();
switch (compiler) {
case D8: {
+ assertTrue(output == Output.DEX);
ToolHelper.runD8(D8Command.builder()
.addProgramFiles(getInputFile())
.setOutputPath(getOutputFile())
@@ -213,7 +243,8 @@ public class R8RunExamplesTest {
.addProgramFiles(getInputFile())
.setOutputPath(getOutputFile())
.setMode(mode)
- .build());
+ .build(),
+ options -> options.outputClassFiles = (output == Output.CF));
break;
}
default:
@@ -236,6 +267,20 @@ public class R8RunExamplesTest {
fail("JVM failed for: " + mainClass);
}
+ if (output == Output.CF) {
+ ToolHelper.ProcessResult result =
+ ToolHelper.runJava(ImmutableList.of(generated.toString()), mainClass);
+ if (result.exitCode != 0) {
+ System.err.println(result.stderr);
+ fail("JVM failed on compiled output for: " + mainClass);
+ }
+ assertEquals(
+ "JavaC/JVM and " + compiler.name() + "/JVM output differ",
+ javaResult.stdout,
+ result.stdout);
+ return;
+ }
+
DexVm vm = ToolHelper.getDexVm();
TestCondition condition = failingRun.get(mainClass);
if (condition != null && condition.test(getTool(), compiler, vm.getVersion(), mode)) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 663901f3b..9baed4897 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class JasminBuilder {
@@ -45,6 +46,10 @@ public class JasminBuilder {
return name + ".j";
}
+ public String getDescriptor() {
+ return "L" + name + ";";
+ }
+
public MethodSignature addVirtualMethod(
String name,
List<String> argumentTypes,
@@ -185,7 +190,8 @@ public class JasminBuilder {
return clazz.getSourceFile();
}
};
- builder.addClassProgramData(origin, compile(clazz));
+ builder.addClassProgramData(
+ origin, compile(clazz), Collections.singleton(clazz.getDescriptor()));
}
return builder.build();
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 6962f976f..67decf2a7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8;
+import com.android.tools.r8.Resource;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.ApplicationReader;
@@ -27,10 +28,13 @@ import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
import jasmin.ClassFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -48,20 +52,37 @@ public class JasminTestBase {
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ public static String getPathFromDescriptor(String classDescriptor) {
+ assert classDescriptor.startsWith("L");
+ assert classDescriptor.endsWith(";");
+ return classDescriptor.substring(1, classDescriptor.length() - 1) + ".class";
+ }
+
protected ProcessResult runOnJavaRaw(JasminBuilder builder, String main) throws Exception {
- File out = temp.newFolder("classes");
- for (ClassBuilder clazz : builder.getClasses()) {
- ClassFile file = new ClassFile();
- file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
- Path path = out.toPath().resolve(clazz.name + ".class");
+ return runOnJavaRaw(builder.build(), main);
+ }
+
+ protected ProcessResult runOnJavaRaw(AndroidApp app, String main) throws Exception {
+ File out = temp.newFolder();
+ for (Resource clazz : app.getClassProgramResources()) {
+ assert clazz.getClassDescriptors().size() == 1;
+ String desc = clazz.getClassDescriptors().iterator().next();
+ Path path = out.toPath().resolve(getPathFromDescriptor(desc));
Files.createDirectories(path.getParent());
- file.write(new FileOutputStream(path.toFile()));
+ try (InputStream input = clazz.getStream();
+ OutputStream output = Files.newOutputStream(path)) {
+ ByteStreams.copy(input, output);
+ }
}
return ToolHelper.runJava(ImmutableList.of(out.getPath()), main);
}
protected String runOnJava(JasminBuilder builder, String main) throws Exception {
- ProcessResult result = runOnJavaRaw(builder, main);
+ return runOnJava(builder.build(), main);
+ }
+
+ protected String runOnJava(AndroidApp app, String main) throws Exception {
+ ProcessResult result = runOnJavaRaw(app, main);
if (result.exitCode != 0) {
System.out.println("Std out:");
System.out.println(result.stdout);