Merge commit 'f13d18f7f2778efd7aea9fbd270558ddde38df74' into dev-release
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
new file mode 100644
index 0000000..cd00e00
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, 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.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to mark a class, field or method as being accessed from native code via JNI.
+ *
+ * <p>When a class is annotated, member patterns can be used to define which members are to be kept.
+ * When no member patterns are specified the default pattern is to match just the class.
+ *
+ * <p>When a member is annotated, the member patterns cannot be used as the annotated member itself
+ * fully defines the item to be kept (i.e., itself).
+ */
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface UsedByNative {
+ String description() default "";
+
+ /**
+ * Conditions that should be satisfied for the annotation to be in effect.
+ *
+ * <p>Defaults to no conditions, thus trivially/unconditionally satisfied.
+ */
+ KeepCondition[] preconditions() default {};
+
+ /** Additional targets to be kept in addition to the annotated class/members. */
+ KeepTarget[] additionalTargets() default {};
+
+ /**
+ * The target kind to be kept.
+ *
+ * <p>When annotating a class without member patterns, the default kind is {@link
+ * KeepItemKind#ONLY_CLASS}.
+ *
+ * <p>When annotating a class with member patterns, the default kind is {@link
+ * KeepItemKind#CLASS_AND_MEMBERS}.
+ *
+ * <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}.
+ *
+ * <p>It is not possible to use ONLY_CLASS if annotating a member.
+ */
+ KeepItemKind kind() default KeepItemKind.DEFAULT;
+
+ // Member patterns. See KeepTarget for documentation.
+ MemberAccessFlags[] memberAccess() default {};
+
+ MethodAccessFlags[] methodAccess() default {};
+
+ String methodName() default "";
+
+ String methodReturnType() default "";
+
+ String[] methodParameters() default {""};
+
+ FieldAccessFlags[] fieldAccess() default {};
+
+ String fieldName() default "";
+
+ String fieldType() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
new file mode 100644
index 0000000..3b065ca
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2023, 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.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to mark a class, field or method as being reflectively accessed.
+ *
+ * <p>Note: Before using this annotation, consider if instead you can annotate the code that is
+ * doing reflection with {@link UsesReflection}. Annotating the reflecting code is generally more
+ * clear and maintainable, and it also naturally gives rise to edges that describe just the
+ * reflected aspects of the program. The {@link UsedByReflection} annotation is suitable for cases
+ * where the reflecting code is not under user control, or in migrating away from rules.
+ *
+ * <p>When a class is annotated, member patterns can be used to define which members are to be kept.
+ * When no member patterns are specified the default pattern is to match just the class.
+ *
+ * <p>When a member is annotated, the member patterns cannot be used as the annotated member itself
+ * fully defines the item to be kept (i.e., itself).
+ */
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface UsedByReflection {
+ String description() default "";
+
+ /**
+ * Conditions that should be satisfied for the annotation to be in effect.
+ *
+ * <p>Defaults to no conditions, thus trivially/unconditionally satisfied.
+ */
+ KeepCondition[] preconditions() default {};
+
+ /** Additional targets to be kept in addition to the annotated class/members. */
+ KeepTarget[] additionalTargets() default {};
+
+ /**
+ * The target kind to be kept.
+ *
+ * <p>When annotating a class without member patterns, the default kind is {@link
+ * KeepItemKind#ONLY_CLASS}.
+ *
+ * <p>When annotating a class with member patterns, the default kind is {@link
+ * KeepItemKind#CLASS_AND_MEMBERS}.
+ *
+ * <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}.
+ *
+ * <p>It is not possible to use ONLY_CLASS if annotating a member.
+ */
+ KeepItemKind kind() default KeepItemKind.DEFAULT;
+
+ // Member patterns. See KeepTarget for documentation.
+ MemberAccessFlags[] memberAccess() default {};
+
+ MethodAccessFlags[] methodAccess() default {};
+
+ String methodName() default "";
+
+ String methodReturnType() default "";
+
+ String[] methodParameters() default {""};
+
+ FieldAccessFlags[] fieldAccess() default {};
+
+ String fieldName() default "";
+
+ String fieldType() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index c8894fc..7c24ff0 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Option;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflection;
import com.android.tools.r8.keepanno.ast.KeepBindings;
import com.android.tools.r8.keepanno.ast.KeepClassReference;
@@ -53,6 +54,7 @@
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -116,6 +118,10 @@
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
return new ForApiClassVisitor(parent, this::setContext, className);
}
+ if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
+ || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
+ return new UsedByReflectionClassVisitor(descriptor, parent, this::setContext, className);
+ }
return null;
}
@@ -189,6 +195,11 @@
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
return new ForApiMemberVisitor(parent, this::setContext, createItemContext());
}
+ if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
+ || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
+ return new UsedByReflectionMemberVisitor(
+ descriptor, parent, this::setContext, createItemContext());
+ }
return null;
}
@@ -246,6 +257,11 @@
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
return new ForApiMemberVisitor(parent, this::setContext, createItemContext());
}
+ if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
+ || descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
+ return new UsedByReflectionMemberVisitor(
+ descriptor, parent, this::setContext, createItemContext());
+ }
return null;
}
}
@@ -483,6 +499,196 @@
}
}
+ /**
+ * Parsing of @UsedByReflection or @UsedByNative on a class context.
+ *
+ * <p>When used on a class context the annotation allows the member related content of a normal
+ * item. This parser extends the base item visitor and throws an error if any class specific
+ * properties are encountered.
+ */
+ private static class UsedByReflectionClassVisitor extends KeepItemVisitorBase {
+ private final String annotationDescriptor;
+ private final String className;
+ private final Parent<KeepEdge> parent;
+ private final KeepEdge.Builder builder = KeepEdge.builder();
+ private final KeepConsequences.Builder consequences = KeepConsequences.builder();
+ private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+
+ UsedByReflectionClassVisitor(
+ String annotationDescriptor,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ String className) {
+ this.annotationDescriptor = annotationDescriptor;
+ this.className = className;
+ this.parent = parent;
+ addContext.accept(metaInfoBuilder);
+ // The class context/holder is the annotated class.
+ visit(Item.className, className);
+ }
+
+ @Override
+ public String getAnnotationName() {
+ int sep = annotationDescriptor.lastIndexOf('/');
+ return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals(Edge.description) && value instanceof String) {
+ metaInfoBuilder.setDescription((String) value);
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(Edge.preconditions)) {
+ return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
+ }
+ if (name.equals(UsedByReflection.additionalTargets)) {
+ return new KeepConsequencesVisitor(
+ getAnnotationName(),
+ additionalConsequences -> {
+ additionalConsequences.forEachTarget(consequences::addTarget);
+ });
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (getKind() == null && !isDefaultMemberDeclaration()) {
+ // If no explict kind is set and member declarations have been made, keep the class too.
+ visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
+ }
+ super.visitEnd();
+ KeepItemReference item = getItemReference();
+ if (item.isBindingReference()) {
+ // TODO(b/248408342): The edge can have preconditions so it should support bindings!
+ throw new KeepEdgeException("@" + getAnnotationName() + " cannot reference bindings");
+ }
+ KeepItemPattern itemPattern = item.asItemPattern();
+ String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
+ String itemDescriptor =
+ itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+ if (!descriptor.equals(itemDescriptor)) {
+ throw new KeepEdgeException(
+ "@" + getAnnotationName() + " must reference its class context " + className);
+ }
+ if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
+ throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its class");
+ }
+ if (!itemPattern.getExtendsPattern().isAny()) {
+ throw new KeepEdgeException(
+ "@" + getAnnotationName() + " cannot define an 'extends' pattern.");
+ }
+ consequences.addTarget(KeepTarget.builder().setItemPattern(itemPattern).build());
+ parent.accept(
+ builder
+ .setMetaInfo(metaInfoBuilder.build())
+ .setConsequences(consequences.build())
+ .build());
+ }
+ }
+
+ /**
+ * Parsing of @UsedByReflection or @UsedByNative on a member context.
+ *
+ * <p>When used on a member context the annotation does not allow member related patterns.
+ */
+ private static class UsedByReflectionMemberVisitor extends AnnotationVisitorBase {
+ private final String annotationDescriptor;
+ private final Parent<KeepEdge> parent;
+ private final KeepItemPattern context;
+ private final KeepEdge.Builder builder = KeepEdge.builder();
+ private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+
+ private final KeepConsequences.Builder consequences = KeepConsequences.builder();
+ private KeepItemKind kind = KeepItemKind.ONLY_MEMBERS;
+
+ UsedByReflectionMemberVisitor(
+ String annotationDescriptor,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ KeepItemPattern context) {
+ this.annotationDescriptor = annotationDescriptor;
+ this.parent = parent;
+ this.context = context;
+ addContext.accept(metaInfoBuilder);
+ }
+
+ @Override
+ public String getAnnotationName() {
+ int sep = annotationDescriptor.lastIndexOf('/');
+ return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals(Edge.description) && value instanceof String) {
+ metaInfoBuilder.setDescription((String) value);
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ if (!descriptor.equals(AnnotationConstants.Kind.DESCRIPTOR)) {
+ super.visitEnum(name, descriptor, value);
+ }
+ switch (value) {
+ case Kind.DEFAULT:
+ // The default value is obtained by not assigning a kind (e.g., null in the builder).
+ break;
+ case Kind.ONLY_CLASS:
+ kind = KeepItemKind.ONLY_CLASS;
+ break;
+ case Kind.ONLY_MEMBERS:
+ kind = KeepItemKind.ONLY_MEMBERS;
+ break;
+ case Kind.CLASS_AND_MEMBERS:
+ kind = KeepItemKind.CLASS_AND_MEMBERS;
+ break;
+ default:
+ super.visitEnum(name, descriptor, value);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(Edge.preconditions)) {
+ return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
+ }
+ if (name.equals(UsedByReflection.additionalTargets)) {
+ return new KeepConsequencesVisitor(
+ getAnnotationName(),
+ additionalConsequences -> {
+ additionalConsequences.forEachTarget(consequences::addTarget);
+ });
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (kind.equals(KeepItemKind.ONLY_CLASS)) {
+ throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its member");
+ }
+ consequences.addTarget(
+ KeepTarget.builder()
+ .setItemPattern(KeepItemPattern.builder().copyFrom(context).setKind(kind).build())
+ .build());
+ parent.accept(
+ builder
+ .setMetaInfo(metaInfoBuilder.build())
+ .setConsequences(consequences.build())
+ .build());
+ }
+ }
+
private static class UsesReflectionVisitor extends AnnotationVisitorBase {
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
@@ -766,11 +972,11 @@
}
private static class MethodDeclaration extends Declaration<KeepMethodPattern> {
- private final String annotationName;
+ private final Supplier<String> annotationName;
private KeepMethodAccessPattern.Builder accessBuilder = null;
private KeepMethodPattern.Builder builder = null;
- private MethodDeclaration(String annotationName) {
+ private MethodDeclaration(Supplier<String> annotationName) {
this.annotationName = annotationName;
}
@@ -845,11 +1051,11 @@
}
private static class FieldDeclaration extends Declaration<KeepFieldPattern> {
- private final String annotationName;
+ private final Supplier<String> annotationName;
private KeepFieldAccessPattern.Builder accessBuilder = null;
private KeepFieldPattern.Builder builder = null;
- public FieldDeclaration(String annotationName) {
+ public FieldDeclaration(Supplier<String> annotationName) {
this.annotationName = annotationName;
}
@@ -911,12 +1117,12 @@
}
private static class MemberDeclaration extends Declaration<KeepMemberPattern> {
- private final String annotationName;
+ private final Supplier<String> annotationName;
private KeepMemberAccessPattern.Builder accessBuilder = null;
private final MethodDeclaration methodDeclaration;
private final FieldDeclaration fieldDeclaration;
- MemberDeclaration(String annotationName) {
+ MemberDeclaration(Supplier<String> annotationName) {
this.annotationName = annotationName;
methodDeclaration = new MethodDeclaration(annotationName);
fieldDeclaration = new FieldDeclaration(annotationName);
@@ -985,7 +1191,7 @@
private KeepItemReference itemReference = null;
KeepItemVisitorBase() {
- memberDeclaration = new MemberDeclaration(getAnnotationName());
+ memberDeclaration = new MemberDeclaration(this::getAnnotationName);
}
public KeepItemReference getItemReference() {
@@ -1124,18 +1330,18 @@
}
private static class StringArrayVisitor extends AnnotationVisitorBase {
- private final String annotationName;
+ private final Supplier<String> annotationName;
private final Consumer<List<String>> fn;
private final List<String> strings = new ArrayList<>();
- public StringArrayVisitor(String annotationName, Consumer<List<String>> fn) {
+ public StringArrayVisitor(Supplier<String> annotationName, Consumer<List<String>> fn) {
this.annotationName = annotationName;
this.fn = fn;
}
@Override
public String getAnnotationName() {
- return annotationName;
+ return annotationName.get();
}
@Override
@@ -1302,18 +1508,18 @@
}
private static class MemberAccessVisitor extends AnnotationVisitorBase {
- private final String annotationName;
+ private final Supplier<String> annotationName;
KeepMemberAccessPattern.BuilderBase<?, ?> builder;
public MemberAccessVisitor(
- String annotationName, KeepMemberAccessPattern.BuilderBase<?, ?> builder) {
+ Supplier<String> annotationName, KeepMemberAccessPattern.BuilderBase<?, ?> builder) {
this.annotationName = annotationName;
this.builder = builder;
}
@Override
public String getAnnotationName() {
- return annotationName;
+ return annotationName.get();
}
static boolean withNormalizedAccessFlag(String flag, BiPredicate<String, Boolean> fn) {
@@ -1376,7 +1582,8 @@
KeepMethodAccessPattern.Builder builder;
- public MethodAccessVisitor(String annotationName, KeepMethodAccessPattern.Builder builder) {
+ public MethodAccessVisitor(
+ Supplier<String> annotationName, KeepMethodAccessPattern.Builder builder) {
super(annotationName, builder);
this.builder = builder;
}
@@ -1421,7 +1628,8 @@
KeepFieldAccessPattern.Builder builder;
- public FieldAccessVisitor(String annotationName, KeepFieldAccessPattern.Builder builder) {
+ public FieldAccessVisitor(
+ Supplier<String> annotationName, KeepFieldAccessPattern.Builder builder) {
super(annotationName, builder);
this.builder = builder;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 0daeda1..876d04d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -70,6 +70,23 @@
public static final String additionalPreconditions = "additionalPreconditions";
}
+ public static final class UsedByReflection {
+ public static final Class<com.android.tools.r8.keepanno.annotations.UsedByReflection> CLASS =
+ com.android.tools.r8.keepanno.annotations.UsedByReflection.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String description = "description";
+ public static final String preconditions = "preconditions";
+ public static final String additionalTargets = "additionalTargets";
+ public static final String memberAccess = "memberAccess";
+ }
+
+ public static final class UsedByNative {
+ public static final Class<com.android.tools.r8.keepanno.annotations.UsedByNative> CLASS =
+ com.android.tools.r8.keepanno.annotations.UsedByNative.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ // Content is the same as UsedByReflection.
+ }
+
// Implicit hidden item which is "super type" of Condition and Target.
public static final class Item {
public static final String classFromBinding = "classFromBinding";
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index fa30c4f..8bf79ed 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -102,7 +102,7 @@
private static final String ZIP_EXTENSION = ".zip";
private static boolean isArchive(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(APK_EXTENSION)
|| name.endsWith(JAR_EXTENSION)
|| name.endsWith(ZIP_EXTENSION);
diff --git a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
index ac084da..831530f 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
@@ -55,6 +55,9 @@
/**
* Add program files to extract marker information from.
*
+ * <p>Each file added here will result in exactly one callback to {@link
+ * MarkerInfoConsumer#acceptMarkerInfo}.
+ *
* <p>All program files supported by the input and output of D8/R8 can be passed here.
*/
public Builder addProgramFiles(Collection<Path> programFiles) {
@@ -62,13 +65,23 @@
return this;
}
- /** Add dex encoded bytes to extract marker information from. */
+ /**
+ * Add dex encoded bytes to extract marker information from.
+ *
+ * <p>Each data & origin pair added here will result in exactly one callback to {@link
+ * MarkerInfoConsumer#acceptMarkerInfo}.
+ */
public Builder addDexProgramData(byte[] data, Origin origin) {
dexData.add(new Pair<>(origin, data));
return this;
}
- /** Add classfile encoded bytes to extract marker information from. */
+ /**
+ * Add classfile encoded bytes to extract marker information from.
+ *
+ * <p>Each data & origin pair added here will result in exactly one callback to {@link
+ * MarkerInfoConsumer#acceptMarkerInfo}.
+ */
public Builder addClassProgramData(byte[] data, Origin origin) {
cfData.add(new Pair<>(origin, data));
return this;
diff --git a/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java b/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java
index 48e4a30..87342e4 100644
--- a/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java
+++ b/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java
@@ -7,7 +7,19 @@
@Keep
public interface MarkerInfoConsumer {
+ /**
+ * Callback that provides the marker information of a resource.
+ *
+ * <p>This callback is called exactly once for each resource in the {@link ExtractMarkerCommand},
+ * also when no marker information is present in that resource.
+ */
void acceptMarkerInfo(MarkerInfoConsumerData data);
+ /**
+ * Callback to inform the extraction of marker information is complete.
+ *
+ * <p>After the callback is invoked no further calls to {@link
+ * MarkerInfoConsumer#acceptMarkerInfo} will occur.
+ */
void finished();
}
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 e03dc38..8ad4f29 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -77,6 +77,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.RetracerForCodePrinting;
+import com.android.tools.r8.utils.StringUtils;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -597,7 +598,7 @@
}
private String ifPostfix(IfType kind) {
- return kind.toString().toLowerCase();
+ return StringUtils.toLowerCase(kind.toString());
}
public void print(CfIf conditional) {
@@ -797,7 +798,7 @@
}
private String opcodeName(int opcode) {
- return Printer.OPCODES[opcode].toLowerCase();
+ return StringUtils.toLowerCase(Printer.OPCODES[opcode]);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index d56f09a..110e7ca 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -104,6 +104,7 @@
// Linear scan over instructions.
CfFrameState state = initialState.asContinue().getValue();
+ int actualInstructionIndexForReporting = 0;
for (int i = 0; i < code.getInstructions().size(); i++) {
CfInstruction instruction = code.getInstruction(i);
assert !state.isError();
@@ -120,7 +121,11 @@
if (state.isError()) {
return fail(
CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, i, instruction, state.asError().getMessage(), appView));
+ method,
+ actualInstructionIndexForReporting,
+ instruction,
+ state.asError().getMessage(),
+ appView));
}
}
}
@@ -143,7 +148,8 @@
state = traversalContinuation.asContinue().getValue();
}
TraversalContinuation<CfCodeDiagnostics, CfFrameState> traversalContinuation =
- computeStateForNextInstruction(instruction, i, state, labelToFrameMap);
+ computeStateForNextInstruction(
+ instruction, i, actualInstructionIndexForReporting, state, labelToFrameMap);
if (traversalContinuation.isContinue()) {
state = traversalContinuation.asContinue().getValue();
} else {
@@ -152,12 +158,23 @@
if (state.isError()) {
return fail(
CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, i, instruction, state.asError().getMessage(), appView));
+ method,
+ actualInstructionIndexForReporting,
+ instruction,
+ state.asError().getMessage(),
+ appView));
+ }
+ if (isActualCfInstruction(instruction)) {
+ ++actualInstructionIndexForReporting;
}
}
return StackMapStatus.VALID;
}
+ private static boolean isActualCfInstruction(CfInstruction instruction) {
+ return !instruction.isLabel() && !instruction.isFrame() && !instruction.isPosition();
+ }
+
private TraversalContinuation<CfCodeDiagnostics, Map<CfLabel, CfFrame>> buildLabelToFrameMap() {
Map<CfLabel, CfFrame> labelToFrameMap = new IdentityHashMap<>();
List<CfLabel> labels = new ArrayList<>();
@@ -319,6 +336,7 @@
private TraversalContinuation<CfCodeDiagnostics, CfFrameState> computeStateForNextInstruction(
CfInstruction instruction,
int instructionIndex,
+ int actualInstructionIndexForReporting,
CfFrameState state,
Map<CfLabel, CfFrame> labelToFrameMap) {
if (!instruction.isJump()) {
@@ -334,8 +352,7 @@
if (instruction.asJump().hasFallthrough()) {
return TraversalContinuation.doContinue(state);
}
- int nextInstructionIndex = instructionIndex + 1;
- CfInstruction nextInstruction = code.getInstruction(nextInstructionIndex);
+ CfInstruction nextInstruction = code.getInstruction(instructionIndex + 1);
CfFrame nextFrame = null;
if (nextInstruction.isFrame()) {
nextFrame = nextInstruction.asFrame();
@@ -352,7 +369,11 @@
}
return TraversalContinuation.doBreak(
CfCodeStackMapValidatingException.invalidStackMapForInstruction(
- method, nextInstructionIndex, nextInstruction, "Expected frame instruction", appView));
+ method,
+ actualInstructionIndexForReporting + 1,
+ nextInstruction,
+ "Expected frame instruction",
+ appView));
}
private boolean isFinalAndExitInstruction(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 2055d69..bc293d3 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -121,7 +121,6 @@
public void close() {
// This close behavior is needed to reduce peak memory usage of D8/R8.
indexedItems = null;
- codes = null;
offsetMap = null;
dexReader = null;
stringIDs = null;
@@ -130,9 +129,6 @@
// Mapping from indexes to indexable dex items.
private OffsetToObjectMapping indexedItems = new OffsetToObjectMapping();
- // Mapping from offset to code item;
- private Int2ReferenceMap<DexCode> codes = new Int2ReferenceOpenHashMap<>();
-
// Mapping from offset to dex item;
private Int2ReferenceMap<Object> offsetMap = new Int2ReferenceOpenHashMap<>();
@@ -172,32 +168,28 @@
this.options = options;
}
- private void ensureCodesInited(int offset) {
+ // We explicitly reread the code objects even if they are deduplicated in the input (i.e., two
+ // methods point to the same code object) to allow us to change code objects in our pipeline.
+ private DexCode readCodeObject(int offset) {
if (offset == 0) {
- return;
- }
-
- if (codes == null) {
- codes = new Int2ReferenceOpenHashMap<>();
+ return null;
}
if (classKind == ClassKind.LIBRARY) {
// Ignore contents of library files.
- return;
+ return null;
}
DexSection dexSection = lookupSection(Constants.TYPE_CODE_ITEM);
if (dexSection.length == 0) {
- return;
+ return null;
}
- if (!codes.containsKey(offset)) {
- int currentPos = dexReader.position();
- dexReader.position(offset);
- dexReader.align(4);
- DexCode code = parseCodeItem();
- codes.put(offset, code); // Update the file local offset to code mapping.
- dexReader.position(currentPos);
- }
+ int currentPos = dexReader.position();
+ dexReader.position(offset);
+ dexReader.align(4);
+ DexCode code = parseCodeItem();
+ dexReader.position(currentPos);
+ return code;
}
private DexTypeList parseTypeList() {
@@ -758,9 +750,7 @@
int codeOff = dexReader.getUleb128();
DexCode code = null;
if (!skipCodes) {
- ensureCodesInited(codeOff);
- assert codeOff == 0 || codes.get(codeOff) != null;
- code = codes.get(codeOff);
+ code = readCodeObject(codeOff);
}
DexMethod method = indexedItems.getMethod(methodIndex);
accessFlags.setConstructor(method, dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 4470baf..0fa52a1 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.StringUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -156,7 +157,7 @@
public Marker setCompilationMode(CompilationMode mode) {
assert !jsonObject.has(COMPILATION_MODE);
- jsonObject.addProperty(COMPILATION_MODE, mode.toString().toLowerCase());
+ jsonObject.addProperty(COMPILATION_MODE, StringUtils.toLowerCase(mode.toString()));
return this;
}
@@ -167,22 +168,24 @@
public String getBackend() {
if (!hasBackend()) {
// Before adding backend we would always compile to dex if min-api was specified.
- return hasMinApi() ? Backend.DEX.name().toLowerCase() : Backend.CF.name().toLowerCase();
+ return hasMinApi()
+ ? StringUtils.toLowerCase(Backend.DEX.name())
+ : StringUtils.toLowerCase(Backend.CF.name());
}
return jsonObject.get(BACKEND).getAsString();
}
public boolean isCfBackend() {
- return getBackend().equals(Backend.CF.name().toLowerCase());
+ return getBackend().equals(StringUtils.toLowerCase(Backend.CF.name()));
}
public boolean isDexBackend() {
- return getBackend().equals(Backend.DEX.name().toLowerCase());
+ return getBackend().equals(StringUtils.toLowerCase(Backend.DEX.name()));
}
public Marker setBackend(Backend backend) {
assert !hasBackend();
- jsonObject.addProperty(BACKEND, backend.name().toLowerCase());
+ jsonObject.addProperty(BACKEND, StringUtils.toLowerCase(backend.name()));
return this;
}
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index 58f2b54..cd99771 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -95,7 +96,7 @@
}
ProguardPathFilter filter = getFilter.apply(proguardConfiguration);
return filter.isEnabled()
- && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && !StringUtils.toLowerCase(file.getName()).endsWith(FileUtils.CLASS_EXTENSION)
&& filter.matches(file.getName());
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 63975c0..875a4fe 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -165,14 +166,14 @@
public static String deriveCommonPrefixAndSanityCheck(List<String> fileNames) {
Iterator<String> nameIterator = fileNames.iterator();
String first = nameIterator.next();
- if (!first.toLowerCase().endsWith(FileUtils.DEX_EXTENSION)) {
+ if (!StringUtils.toLowerCase(first).endsWith(FileUtils.DEX_EXTENSION)) {
throw new RuntimeException("Illegal suffix for dex file: `" + first + "`.");
}
String prefix = first.substring(0, first.length() - FileUtils.DEX_EXTENSION.length());
int index = 2;
while (nameIterator.hasNext()) {
String next = nameIterator.next();
- if (!next.toLowerCase().endsWith(FileUtils.DEX_EXTENSION)) {
+ if (!StringUtils.toLowerCase(next).endsWith(FileUtils.DEX_EXTENSION)) {
throw new RuntimeException("Illegal suffix for dex file: `" + first + "`.");
}
if (!next.startsWith(prefix)) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
index cc3f47c..59788bc 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -70,7 +70,7 @@
String detailMessage,
AppView<?> appView) {
StringBuilder sb =
- new StringBuilder("Invalid stack map table at instruction ")
+ new StringBuilder("Invalid stack map table at instruction index ")
.append(instructionIndex)
.append(": ")
.append(instruction)
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
index 02ba8d4..eab9e7b 100644
--- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
@@ -43,6 +43,13 @@
return backing.get(instruction);
}
+ public void remap(Instruction oldKey, Instruction newKey) {
+ BytecodeInstructionMetadata value = backing.remove(oldKey);
+ if (value != null) {
+ backing.put(newKey, value);
+ }
+ }
+
public static class Builder {
private final Map<Instruction, BytecodeInstructionMetadata.Builder> builders =
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 7f3a0b9..a4ed229 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.lightir.LirBuilder;
public class AlwaysMaterializingDefinition extends ConstInstruction {
@@ -49,6 +50,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index 0e09c04..cd4d6a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class AlwaysMaterializingNop extends Instruction {
@@ -46,6 +47,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("Unexpected use of materializing NOP prior to CF/DEX finalization.");
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other instanceof AlwaysMaterializingNop;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 21b02ba..a5cf0e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class AlwaysMaterializingUser extends Instruction {
@@ -46,6 +47,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index e0a6238..174b913 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
import java.util.Objects;
import java.util.Set;
@@ -173,6 +174,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable(ERROR_MESSAGE);
+ }
+
+ @Override
public int maxInValueRegister() {
throw new Unreachable(ERROR_MESSAGE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 15afa68..58928b0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class ConstMethodHandle extends ConstInstruction {
@@ -72,6 +73,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addConstMethodHandle(methodHandle);
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.isConstMethodHandle() && other.asConstMethodHandle().methodHandle == methodHandle;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index 0b586ca..87031d2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class ConstMethodType extends ConstInstruction {
@@ -70,6 +71,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addConstMethodType(methodType);
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.isConstMethodType() && other.asConstMethodType().methodType == methodType;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 1ff7509..089ee34 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
import com.android.tools.r8.utils.StringUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -136,6 +137,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 2c9c3be..41dea29 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class Dup extends Instruction {
@@ -67,6 +68,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used before finalizing to CF.");
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
if (this.inValues.get(0).getType().isWidePrimitive()) {
builder.add(new CfStackInstruction(Opcode.Dup2), this);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 7eff1c4..eebb405 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
import com.google.common.collect.ImmutableList;
public class Dup2 extends Instruction {
@@ -85,6 +86,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used before finalizing to CF.");
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
builder.add(new CfStackInstruction(Opcode.Dup2), this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java
index de99253..72b2bf2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Inc.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.constant.LatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
import java.util.function.Function;
public class Inc extends Unop {
@@ -82,6 +83,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
// Check that this instruction does not have any metadata attached, as it might not materialize
// as an iinc in CfCode.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 511c157..9321203 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -225,10 +225,7 @@
public abstract void buildCf(CfBuilder builder);
- // TODO(b/225838009): Make this abstract.
- public void buildLir(LirBuilder<Value, ?> builder) {
- throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
- }
+ public abstract void buildLir(LirBuilder<Value, ?> builder);
public void replaceValue(Value oldValue, Value newValue) {
for (int i = 0; i < inValues.size(); i++) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index fe565bc..ac3865d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class Load extends Instruction {
@@ -69,6 +70,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used in LIR.");
+ }
+
+ @Override
public void buildDex(DexBuilder builder) {
throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 58e1507..5dc04da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class Move extends Instruction {
private static final String ERROR_MESSAGE =
@@ -58,6 +59,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable(ERROR_MESSAGE);
+ }
+
+ @Override
public int maxInValueRegister() {
return Constants.U16BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 01bdc96..5839b47 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class Pop extends Instruction {
@@ -85,6 +86,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used in LIR.");
+ }
+
+ @Override
public void buildDex(DexBuilder builder) {
throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
index db1cfaf..7ab1c92 100644
--- a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
import java.util.Arrays;
import java.util.List;
@@ -26,6 +27,7 @@
public RecordFieldValues(DexField[] fields, Value outValue, List<Value> fieldValues) {
super(outValue, fieldValues);
+ assert fields.length == fieldValues.size();
this.fields = fields;
}
@@ -77,6 +79,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addRecordFieldValues(getFields(), inValues());
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
if (!other.isRecordFieldValues()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
index 1aee996..bfb48e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
public class SafeCheckCast extends CheckCast {
@@ -28,6 +29,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addSafeCheckCast(getType(), object());
+ }
+
+ @Override
DexCheckCast createCheckCast(int register) {
return new DexSafeCheckCast(register, getType());
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 6714e0d..0ace979 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
public class Store extends Instruction {
@@ -71,6 +72,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used in LIR.");
+ }
+
+ @Override
public void buildDex(DexBuilder builder) {
throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
index e25685c..5d079559 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
@@ -95,6 +96,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 87ed900..3e826c8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
import com.google.common.collect.ImmutableList;
public class Swap extends Instruction {
@@ -61,6 +62,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable("This classfile-specific IR should not be used in LIR.");
+ }
+
+ @Override
public void buildDex(DexBuilder builder) {
throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
index 3d8419d..3561840 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
/**
* To preserve stack-map table information regarding uninitializedThis flags the
@@ -50,6 +51,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable(ERROR_MESSAGE);
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
throw new Unreachable(ERROR_MESSAGE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
index 29fc073..85c3490 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
/**
* A special instruction to load the value of an argument that has been removed as a result of code
@@ -42,6 +43,11 @@
}
@Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ throw new Unreachable();
+ }
+
+ @Override
public DeadInstructionResult canBeDeadCode(AppView<?> appview, IRCode code) {
return DeadInstructionResult.deadIfOutValueIsDead();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 588ce91..f297e8d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1492,7 +1492,8 @@
}
public void addRecordFieldValues(DexField[] fields, IntList registers, int outValue) {
- List<Value> arguments = new ArrayList<>();
+ assert fields.length == registers.size();
+ List<Value> arguments = new ArrayList<>(registers.size());
for (int register : registers) {
arguments.add(readRegister(register, ValueTypeConstraint.OBJECT));
}
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 431a610..8f9378e 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
@@ -25,8 +25,12 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.BinopRewriter;
+import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination;
import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.SplitBranchOnKnownBoolean;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
@@ -110,6 +114,8 @@
private final ClassInliner classInliner;
protected final InternalOptions options;
public final CodeRewriter codeRewriter;
+ public final CommonSubexpressionElimination commonSubexpressionElimination;
+ private final SplitBranchOnKnownBoolean splitBranchOnKnownBoolean;
public final AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter;
private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
public final MemberValuePropagation<?> memberValuePropagation;
@@ -122,6 +128,7 @@
private final TypeChecker typeChecker;
protected final ServiceLoaderRewriter serviceLoaderRewriter;
private final EnumValueOptimizer enumValueOptimizer;
+ private final BinopRewriter binopRewriter;
protected final EnumUnboxer enumUnboxer;
protected final InstanceInitializerOutliner instanceInitializerOutliner;
protected final RemoveVerificationErrorForUnknownReturnedValues
@@ -159,6 +166,8 @@
this.appView = appView;
this.options = appView.options();
this.codeRewriter = new CodeRewriter(appView);
+ this.commonSubexpressionElimination = new CommonSubexpressionElimination(appView);
+ this.splitBranchOnKnownBoolean = new SplitBranchOnKnownBoolean(appView);
this.assertionErrorTwoArgsConstructorRewriter =
appView.options().desugarState.isOn()
? new AssertionErrorTwoArgsConstructorRewriter(appView)
@@ -209,6 +218,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
+ this.binopRewriter = null;
this.enumUnboxer = EnumUnboxer.empty();
this.assumeInserter = null;
this.instanceInitializerOutliner = null;
@@ -271,6 +281,10 @@
: null;
this.enumValueOptimizer =
options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
+ this.binopRewriter =
+ options.testing.enableBinopOptimization && !options.debug
+ ? new BinopRewriter(appView)
+ : null;
} else {
AppView<AppInfo> appViewWithoutClassHierarchy = appView.withoutClassHierarchy();
this.assumeInserter = null;
@@ -291,6 +305,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
+ this.binopRewriter = null;
this.enumUnboxer = EnumUnboxer.empty();
}
this.stringSwitchRemover =
@@ -733,9 +748,7 @@
code, methodProcessor, methodProcessingContext);
timing.end();
}
- timing.begin("Run CSE");
- codeRewriter.commonSubexpressionElimination(code);
- timing.end();
+ commonSubexpressionElimination.run(context, code, timing);
timing.begin("Simplify arrays");
codeRewriter.simplifyArrayConstruction(code);
timing.end();
@@ -764,6 +777,7 @@
timing.end();
}
timing.end();
+ splitBranchOnKnownBoolean.run(code.context(), code, timing);
if (options.enableRedundantConstNumberOptimization) {
timing.begin("Remove const numbers");
codeRewriter.redundantConstNumberRemoval(code);
@@ -774,6 +788,9 @@
new RedundantFieldLoadAndStoreElimination(appView, code).run();
timing.end();
}
+ if (binopRewriter != null) {
+ binopRewriter.run(context, code, timing);
+ }
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
@@ -1083,9 +1100,20 @@
doRoundtripWithStrategy(code, new ExternalPhisStrategy(), "indirect phis", timing);
IRCode round2 =
doRoundtripWithStrategy(round1, new PhiInInstructionsStrategy(), "inline phis", timing);
+ remapBytecodeMetadataProvider(code, round2, bytecodeMetadataProvider);
return round2;
}
+ private static void remapBytecodeMetadataProvider(
+ IRCode oldCode, IRCode newCode, BytecodeMetadataProvider bytecodeMetadataProvider) {
+ InstructionIterator it1 = oldCode.instructionIterator();
+ InstructionIterator it2 = newCode.instructionIterator();
+ while (it1.hasNext() && it2.hasNext()) {
+ bytecodeMetadataProvider.remap(it1.next(), it2.next());
+ }
+ assert !it1.hasNext() && !it2.hasNext();
+ }
+
private <EV, S extends LirStrategy<Value, EV>> IRCode doRoundtripWithStrategy(
IRCode code, S strategy, String name, Timing timing) {
timing.begin("IR->LIR (" + name + ")");
@@ -1175,8 +1203,11 @@
}
public String printMethod(IRCode code, String title, String previous) {
- if (options.extensiveLoggingFilter.size() > 0
- && options.extensiveLoggingFilter.contains(code.method().getReference().toSourceString())) {
+ if (options.extensiveLoggingFilter.isEmpty()) {
+ return previous;
+ }
+ String methodString = code.method().getReference().toSourceString();
+ if (options.extensiveLoggingFilter.contains(methodString)) {
String current = code.toString();
System.out.println();
System.out.println("-----------------------------------------------------------------------");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
new file mode 100644
index 0000000..93ad3ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -0,0 +1,212 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Binop;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Div;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Mul;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Rem;
+import com.android.tools.r8.ir.code.Shl;
+import com.android.tools.r8.ir.code.Shr;
+import com.android.tools.r8.ir.code.Sub;
+import com.android.tools.r8.ir.code.Ushr;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class BinopRewriter extends CodeRewriterPass<AppInfo> {
+
+ private static final int ALL_BITS_SET = -1;
+
+ public BinopRewriter(AppView<?> appView) {
+ super(appView);
+ }
+
+ private final Map<Class<?>, BinopDescriptor> descriptors = createBinopDescriptors();
+
+ private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
+ ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
+ builder.put(Add.class, new BinopDescriptor(0, 0, null, null));
+ builder.put(Sub.class, new BinopDescriptor(null, 0, null, null));
+ builder.put(Mul.class, new BinopDescriptor(1, 1, 0, 0));
+ // The following two can be improved if we handle ZeroDivide.
+ builder.put(Div.class, new BinopDescriptor(null, 1, null, null));
+ builder.put(Rem.class, new BinopDescriptor(null, null, null, null));
+ builder.put(And.class, new BinopDescriptor(ALL_BITS_SET, ALL_BITS_SET, 0, 0));
+ builder.put(Or.class, new BinopDescriptor(0, 0, ALL_BITS_SET, ALL_BITS_SET));
+ builder.put(Xor.class, new BinopDescriptor(0, 0, null, null));
+ builder.put(Shl.class, new BinopDescriptor(null, 0, 0, null));
+ builder.put(Shr.class, new BinopDescriptor(null, 0, 0, null));
+ builder.put(Ushr.class, new BinopDescriptor(null, 0, 0, null));
+ return builder.build();
+ }
+
+ /**
+ * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
+ * In a space K, for a binop *:
+ * - i is left identity if for each x in K, i * x = x.
+ * - i is right identity if for each x in K, x * i = x.
+ * - a is left absorbing if for each x in K, a * x = a.
+ * - a is right absorbing if for each x in K, x * a = a.
+ * </code>
+ */
+ private static class BinopDescriptor {
+
+ final Integer leftIdentity;
+ final Integer rightIdentity;
+ final Integer leftAbsorbing;
+ final Integer rightAbsorbing;
+
+ private BinopDescriptor(
+ Integer leftIdentity,
+ Integer rightIdentity,
+ Integer leftAbsorbing,
+ Integer rightAbsorbing) {
+ this.leftIdentity = leftIdentity;
+ this.rightIdentity = rightIdentity;
+ this.leftAbsorbing = leftAbsorbing;
+ this.rightAbsorbing = rightAbsorbing;
+ }
+ }
+
+ @Override
+ String getTimingId() {
+ return "BinopRewriter";
+ }
+
+ @Override
+ boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+ return true;
+ }
+
+ @Override
+ public void rewriteCode(ProgramMethod method, IRCode code) {
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction next = iterator.next();
+ if (next.isBinop() && !next.isCmp()) {
+ Binop binop = next.asBinop();
+ if (binop.getNumericType() == NumericType.INT
+ || binop.getNumericType() == NumericType.LONG) {
+ BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
+ assert binopDescriptor != null;
+ ConstNumber constNumber = getConstNumber(binop.leftValue());
+ if (constNumber != null) {
+ if (simplify(
+ binop,
+ iterator,
+ constNumber,
+ binopDescriptor.leftIdentity,
+ binop.rightValue(),
+ binopDescriptor.leftAbsorbing,
+ binop.leftValue())) {
+ continue;
+ }
+ }
+ constNumber = getConstNumber(binop.rightValue());
+ if (constNumber != null) {
+ simplify(
+ binop,
+ iterator,
+ constNumber,
+ binopDescriptor.rightIdentity,
+ binop.leftValue(),
+ binopDescriptor.rightAbsorbing,
+ binop.rightValue());
+ }
+ }
+ }
+ }
+ code.removeAllDeadAndTrivialPhis();
+ assert code.isConsistentSSA(appView);
+ }
+
+ private ConstNumber getConstNumber(Value val) {
+ ConstNumber constNumber = getConstNumberIfConstant(val);
+ if (constNumber != null) {
+ return constNumber;
+ }
+ // phi(v1(0), v2(0)) is equivalent to ConstNumber(0) for the simplification.
+ if (val.isPhi() && getConstNumberIfConstant(val.asPhi().getOperands().get(0)) != null) {
+ ConstNumber phiConstNumber = null;
+ WorkList<Phi> phiWorkList = WorkList.newIdentityWorkList(val.asPhi());
+ while (phiWorkList.hasNext()) {
+ Phi next = phiWorkList.next();
+ for (Value operand : next.getOperands()) {
+ ConstNumber operandConstNumber = getConstNumberIfConstant(operand);
+ if (operandConstNumber != null) {
+ if (phiConstNumber == null) {
+ phiConstNumber = operandConstNumber;
+ } else if (operandConstNumber.getRawValue() == phiConstNumber.getRawValue()) {
+ assert operandConstNumber.getOutType() == phiConstNumber.getOutType();
+ } else {
+ // Different const numbers, cannot conclude a value from the phi.
+ return null;
+ }
+ } else if (operand.isPhi()) {
+ phiWorkList.addIfNotSeen(operand.asPhi());
+ } else {
+ return null;
+ }
+ }
+ }
+ return phiConstNumber;
+ }
+ return null;
+ }
+
+ private static ConstNumber getConstNumberIfConstant(Value val) {
+ if (val.isConstant() && val.getConstInstruction().isConstNumber()) {
+ return val.getConstInstruction().asConstNumber();
+ }
+ return null;
+ }
+
+ private boolean simplify(
+ Binop binop,
+ InstructionListIterator iterator,
+ ConstNumber constNumber,
+ Integer identityElement,
+ Value identityReplacement,
+ Integer absorbingElement,
+ Value absorbingReplacement) {
+ int intValue;
+ if (constNumber.outValue().getType().isInt()) {
+ intValue = constNumber.getIntValue();
+ } else {
+ assert constNumber.outValue().getType().isLong();
+ long longValue = constNumber.getLongValue();
+ intValue = (int) longValue;
+ if ((long) intValue != longValue) {
+ return false;
+ }
+ }
+ if (identityElement != null && identityElement == intValue) {
+ binop.outValue().replaceUsers(identityReplacement);
+ iterator.remove();
+ return true;
+ }
+ if (absorbingElement != null && absorbingElement == intValue) {
+ binop.outValue().replaceUsers(absorbingReplacement);
+ iterator.remove();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
new file mode 100644
index 0000000..69c4e20
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Binop;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.List;
+
+public class CommonSubexpressionElimination extends CodeRewriterPass<AppInfo> {
+
+ public CommonSubexpressionElimination(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getTimingId() {
+ return "CommonSubexpressionElimination";
+ }
+
+ @Override
+ boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+ return true;
+ }
+
+ @Override
+ void rewriteCode(ProgramMethod method, IRCode code) {
+ int noCandidate = code.reserveMarkingColor();
+ if (hasCSECandidate(code, noCandidate)) {
+ final ListMultimap<Wrapper<Instruction>, Value> instructionToValue =
+ ArrayListMultimap.create();
+ final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(options);
+ final DominatorTree dominatorTree = new DominatorTree(code);
+ for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
+ BasicBlock block = dominatorTree.getSortedBlocks()[i];
+ if (block.isMarked(noCandidate)) {
+ continue;
+ }
+ InstructionListIterator iterator = block.listIterator(code);
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (isCSEInstructionCandidate(instruction)) {
+ List<Value> candidates = instructionToValue.get(equivalence.wrap(instruction));
+ boolean eliminated = false;
+ if (candidates.size() > 0) {
+ for (Value candidate : candidates) {
+ if (dominatorTree.dominatedBy(block, candidate.definition.getBlock())
+ && shareCatchHandlers(instruction, candidate.definition)) {
+ instruction.outValue().replaceUsers(candidate);
+ candidate.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ eliminated = true;
+ iterator.removeOrReplaceByDebugLocalRead();
+ break; // Don't try any more candidates.
+ }
+ }
+ }
+ if (!eliminated) {
+ instructionToValue.put(equivalence.wrap(instruction), instruction.outValue());
+ }
+ }
+ }
+ }
+ }
+ code.returnMarkingColor(noCandidate);
+ assert code.isConsistentSSA(appView);
+ }
+
+ private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
+
+ private final InternalOptions options;
+
+ private CSEExpressionEquivalence(InternalOptions options) {
+ this.options = options;
+ }
+
+ @Override
+ protected boolean doEquivalent(Instruction a, Instruction b) {
+ // Some Dalvik VMs incorrectly handle Cmp instructions which leads to a requirement
+ // that we do not perform common subexpression elimination for them. See comment on
+ // canHaveCmpLongBug for details.
+ if (a.isCmp() && options.canHaveCmpLongBug()) {
+ return false;
+ }
+ // Note that we don't consider positions because CSE can at most remove an instruction.
+ if (!a.identicalNonValueNonPositionParts(b)) {
+ return false;
+ }
+ // For commutative binary operations any order of in-values are equal.
+ if (a.isBinop() && a.asBinop().isCommutative()) {
+ Value a0 = a.inValues().get(0);
+ Value a1 = a.inValues().get(1);
+ Value b0 = b.inValues().get(0);
+ Value b1 = b.inValues().get(1);
+ return (identicalValue(a0, b0) && identicalValue(a1, b1))
+ || (identicalValue(a0, b1) && identicalValue(a1, b0));
+ } else {
+ // Compare all in-values.
+ assert a.inValues().size() == b.inValues().size();
+ for (int i = 0; i < a.inValues().size(); i++) {
+ if (!identicalValue(a.inValues().get(i), b.inValues().get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ @Override
+ protected int doHash(Instruction instruction) {
+ final int prime = 29;
+ int hash = instruction.getClass().hashCode();
+ if (instruction.isBinop()) {
+ Binop binop = instruction.asBinop();
+ Value in0 = instruction.inValues().get(0);
+ Value in1 = instruction.inValues().get(1);
+ if (binop.isCommutative()) {
+ hash += hash * prime + getHashCode(in0) * getHashCode(in1);
+ } else {
+ hash += hash * prime + getHashCode(in0);
+ hash += hash * prime + getHashCode(in1);
+ }
+ return hash;
+ } else {
+ for (Value value : instruction.inValues()) {
+ hash += hash * prime + getHashCode(value);
+ }
+ }
+ return hash;
+ }
+
+ private static boolean identicalValue(Value a, Value b) {
+ if (a.equals(b)) {
+ return true;
+ }
+ if (a.isConstNumber() && b.isConstNumber()) {
+ // Do not take assumption that constants are canonicalized.
+ return a.definition.identicalNonValueNonPositionParts(b.definition);
+ }
+ return false;
+ }
+
+ private static int getHashCode(Value a) {
+ if (a.isConstNumber()) {
+ // Do not take assumption that constants are canonicalized.
+ return Long.hashCode(a.definition.asConstNumber().getRawValue());
+ }
+ return a.hashCode();
+ }
+ }
+
+ private boolean shareCatchHandlers(Instruction i0, Instruction i1) {
+ if (!i0.instructionTypeCanThrow()) {
+ assert !i1.instructionTypeCanThrow();
+ return true;
+ }
+ assert i1.instructionTypeCanThrow();
+ // TODO(sgjesse): This could be even better by checking for the exceptions thrown, e.g. div
+ // and rem only ever throw ArithmeticException.
+ CatchHandlers<BasicBlock> ch0 = i0.getBlock().getCatchHandlers();
+ CatchHandlers<BasicBlock> ch1 = i1.getBlock().getCatchHandlers();
+ return ch0.equals(ch1);
+ }
+
+ private boolean isCSEInstructionCandidate(Instruction instruction) {
+ return (instruction.isBinop()
+ || instruction.isUnop()
+ || instruction.isInstanceOf()
+ || instruction.isCheckCast())
+ && instruction.getLocalInfo() == null
+ && !instruction.hasInValueWithLocalInfo();
+ }
+
+ private boolean hasCSECandidate(IRCode code, int noCandidate) {
+ for (BasicBlock block : code.blocks) {
+ for (Instruction instruction : block.getInstructions()) {
+ if (isCSEInstructionCandidate(instruction)) {
+ return true;
+ }
+ }
+ block.mark(noCandidate);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java
new file mode 100644
index 0000000..134d2bc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Goto;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class SplitBranchOnKnownBoolean extends CodeRewriterPass<AppInfo> {
+
+ private static final boolean ALLOW_PARTIAL_REWRITE = true;
+
+ public SplitBranchOnKnownBoolean(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ String getTimingId() {
+ return "SplitBranchOnKnownBoolean";
+ }
+
+ @Override
+ boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+ return true;
+ }
+
+ /**
+ * Simplify Boolean branches for example: <code>
+ * boolean b = i == j; if (b) { ... } else { ... }
+ * </code> ends up first creating a branch for the boolean b, then a second branch on b. D8/R8
+ * rewrites to: <code>
+ * if (i == j) { ... } else { ... }
+ * </code> More complex control flow are also supported to some extent, including cases where the
+ * input of the second branch comes from a set of dependent phis, and a subset of the inputs are
+ * known boolean values.
+ */
+ @Override
+ void rewriteCode(ProgramMethod method, IRCode code) {
+ List<BasicBlock> candidates = computeCandidates(code);
+ if (candidates.isEmpty()) {
+ return;
+ }
+ Map<Goto, BasicBlock> newTargets = findGotosToRetarget(candidates);
+ if (newTargets.isEmpty()) {
+ return;
+ }
+ retargetGotos(newTargets);
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ affectedValues.addAll(code.removeUnreachableBlocks());
+ code.removeAllDeadAndTrivialPhis(affectedValues);
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+ if (ALLOW_PARTIAL_REWRITE) {
+ code.splitCriticalEdges();
+ }
+ assert code.isConsistentSSA(appView);
+ }
+
+ private void retargetGotos(Map<Goto, BasicBlock> newTargets) {
+ newTargets.forEach(
+ (goTo, newTarget) -> {
+ BasicBlock initialTarget = goTo.getTarget();
+ for (Phi phi : initialTarget.getPhis()) {
+ int index = initialTarget.getPredecessors().indexOf(goTo.getBlock());
+ phi.removeOperand(index);
+ }
+ goTo.setTarget(newTarget);
+ });
+ }
+
+ private Map<Goto, BasicBlock> findGotosToRetarget(List<BasicBlock> candidates) {
+ Map<Goto, BasicBlock> newTargets = new LinkedHashMap<>();
+ for (BasicBlock block : candidates) {
+ // We need to verify any instruction in between the if and the chain of phis is empty (we
+ // could duplicate instruction, but the common case is empty).
+ // Then we can redirect any known value. This can lead to dead code.
+ If theIf = block.exit().asIf();
+ Set<Phi> allowedPhis = getAllowedPhis(theIf.lhs().asPhi());
+ Set<Phi> foundPhis = Sets.newIdentityHashSet();
+ WorkList.newIdentityWorkList(block)
+ .process(
+ (current, workList) -> {
+ if (current.getInstructions().size() > 1) {
+ return;
+ }
+ if (current != block && !current.exit().isGoto()) {
+ return;
+ }
+ if (allowedPhis.containsAll(current.getPhis())) {
+ foundPhis.addAll(current.getPhis());
+ } else {
+ return;
+ }
+ workList.addIfNotSeen(current.getPredecessors());
+ });
+ if (!ALLOW_PARTIAL_REWRITE) {
+ for (Phi phi : foundPhis) {
+ for (Value value : phi.getOperands()) {
+ if (!value.isConstant() && !(value.isPhi() && foundPhis.contains(value.asPhi()))) {
+ return newTargets;
+ }
+ }
+ }
+ }
+ List<Phi> sortedFoundPhis = new ArrayList<>(foundPhis);
+ sortedFoundPhis.sort(Phi::compareTo);
+ for (Phi phi : sortedFoundPhis) {
+ BasicBlock phiBlock = phi.getBlock();
+ for (int i = 0; i < phi.getOperands().size(); i++) {
+ Value value = phi.getOperand(i);
+ if (value.isConstant()) {
+ recordNewTargetForGoto(value, phiBlock.getPredecessors().get(i), theIf, newTargets);
+ }
+ }
+ }
+ }
+ return newTargets;
+ }
+
+ private List<BasicBlock> computeCandidates(IRCode code) {
+ List<BasicBlock> candidates = new ArrayList<>();
+ for (BasicBlock block : ListUtils.filter(code.blocks, block -> block.entry().isIf())) {
+ If theIf = block.exit().asIf();
+ if (theIf.isZeroTest()
+ && theIf.lhs().getType().isInt()
+ && theIf.lhs().isPhi()
+ && theIf.lhs().hasSingleUniqueUser()
+ && !theIf.lhs().hasPhiUsers()) {
+ candidates.add(block);
+ }
+ }
+ return candidates;
+ }
+
+ private void recordNewTargetForGoto(
+ Value value, BasicBlock basicBlock, If theIf, Map<Goto, BasicBlock> newTargets) {
+ // The GoTo at the end of basicBlock should target the phiBlock, and should target instead
+ // the correct if destination.
+ assert basicBlock.exit().isGoto();
+ assert value.isConstant();
+ assert value.getType().isInt();
+ assert theIf.isZeroTest();
+ BasicBlock newTarget = theIf.targetFromCondition(value.getConstInstruction().asConstNumber());
+ Goto aGoto = basicBlock.exit().asGoto();
+ newTargets.put(aGoto, newTarget);
+ }
+
+ private Set<Phi> getAllowedPhis(Phi initialPhi) {
+ WorkList<Phi> workList = WorkList.newIdentityWorkList(initialPhi);
+ while (workList.hasNext()) {
+ Phi phi = workList.next();
+ for (Value operand : phi.getOperands()) {
+ if (operand.isPhi()
+ && (operand.uniqueUsers().isEmpty() || phi == initialPhi)
+ && workList.getSeenSet().containsAll(operand.uniquePhiUsers())) {
+ workList.addIfNotSeen(operand.asPhi());
+ }
+ }
+ }
+ return workList.getSeenSet();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 692eeee..c0d3e6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -113,13 +113,9 @@
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -499,6 +495,7 @@
// TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
public abstract static class InstructionBuilder<T> {
+
protected int blockNumber;
protected final Position position;
@@ -515,6 +512,7 @@
}
public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
+
private Value value;
private final Int2ReferenceSortedMap<BasicBlock> keyToTarget = new Int2ReferenceAVLTreeMap<>();
private BasicBlock fallthrough;
@@ -530,7 +528,7 @@
public SwitchBuilder setValue(Value value) {
this.value = value;
- return this;
+ return this;
}
public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
@@ -575,6 +573,7 @@
}
public static class IfBuilder extends InstructionBuilder<IfBuilder> {
+
private final IRCode code;
private Value left;
private int right;
@@ -593,12 +592,12 @@
public IfBuilder setLeft(Value left) {
this.left = left;
- return this;
+ return this;
}
public IfBuilder setRight(int right) {
this.right = right;
- return this;
+ return this;
}
public IfBuilder setTarget(BasicBlock target) {
@@ -2242,6 +2241,7 @@
}
private static class FilledArrayCandidate {
+
final NewArrayEmpty newArrayEmpty;
final int size;
final boolean encodeAsFilledNewArray;
@@ -2548,165 +2548,8 @@
}
}
- private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
-
- private final InternalOptions options;
-
- private CSEExpressionEquivalence(InternalOptions options) {
- this.options = options;
- }
-
- @Override
- protected boolean doEquivalent(Instruction a, Instruction b) {
- // Some Dalvik VMs incorrectly handle Cmp instructions which leads to a requirement
- // that we do not perform common subexpression elimination for them. See comment on
- // canHaveCmpLongBug for details.
- if (a.isCmp() && options.canHaveCmpLongBug()) {
- return false;
- }
- // Note that we don't consider positions because CSE can at most remove an instruction.
- if (!a.identicalNonValueNonPositionParts(b)) {
- return false;
- }
- // For commutative binary operations any order of in-values are equal.
- if (a.isBinop() && a.asBinop().isCommutative()) {
- Value a0 = a.inValues().get(0);
- Value a1 = a.inValues().get(1);
- Value b0 = b.inValues().get(0);
- Value b1 = b.inValues().get(1);
- return (identicalValue(a0, b0) && identicalValue(a1, b1))
- || (identicalValue(a0, b1) && identicalValue(a1, b0));
- } else {
- // Compare all in-values.
- assert a.inValues().size() == b.inValues().size();
- for (int i = 0; i < a.inValues().size(); i++) {
- if (!identicalValue(a.inValues().get(i), b.inValues().get(i))) {
- return false;
- }
- }
- return true;
- }
- }
-
- @Override
- protected int doHash(Instruction instruction) {
- final int prime = 29;
- int hash = instruction.getClass().hashCode();
- if (instruction.isBinop()) {
- Binop binop = instruction.asBinop();
- Value in0 = instruction.inValues().get(0);
- Value in1 = instruction.inValues().get(1);
- if (binop.isCommutative()) {
- hash += hash * prime + getHashCode(in0) * getHashCode(in1);
- } else {
- hash += hash * prime + getHashCode(in0);
- hash += hash * prime + getHashCode(in1);
- }
- return hash;
- } else {
- for (Value value : instruction.inValues()) {
- hash += hash * prime + getHashCode(value);
- }
- }
- return hash;
- }
-
- private static boolean identicalValue(Value a, Value b) {
- if (a.equals(b)) {
- return true;
- }
- if (a.isConstNumber() && b.isConstNumber()) {
- // Do not take assumption that constants are canonicalized.
- return a.definition.identicalNonValueNonPositionParts(b.definition);
- }
- return false;
- }
-
- private static int getHashCode(Value a) {
- if (a.isConstNumber()) {
- // Do not take assumption that constants are canonicalized.
- return Long.hashCode(a.definition.asConstNumber().getRawValue());
- }
- return a.hashCode();
- }
- }
-
- private boolean shareCatchHandlers(Instruction i0, Instruction i1) {
- if (!i0.instructionTypeCanThrow()) {
- assert !i1.instructionTypeCanThrow();
- return true;
- }
- assert i1.instructionTypeCanThrow();
- // TODO(sgjesse): This could be even better by checking for the exceptions thrown, e.g. div
- // and rem only ever throw ArithmeticException.
- CatchHandlers<BasicBlock> ch0 = i0.getBlock().getCatchHandlers();
- CatchHandlers<BasicBlock> ch1 = i1.getBlock().getCatchHandlers();
- return ch0.equals(ch1);
- }
-
- private boolean isCSEInstructionCandidate(Instruction instruction) {
- return (instruction.isBinop()
- || instruction.isUnop()
- || instruction.isInstanceOf()
- || instruction.isCheckCast())
- && instruction.getLocalInfo() == null
- && !instruction.hasInValueWithLocalInfo();
- }
-
- private boolean hasCSECandidate(IRCode code, int noCandidate) {
- for (BasicBlock block : code.blocks) {
- for (Instruction instruction : block.getInstructions()) {
- if (isCSEInstructionCandidate(instruction)) {
- return true;
- }
- }
- block.mark(noCandidate);
- }
- return false;
- }
-
- public void commonSubexpressionElimination(IRCode code) {
- int noCandidate = code.reserveMarkingColor();
- if (hasCSECandidate(code, noCandidate)) {
- final ListMultimap<Wrapper<Instruction>, Value> instructionToValue =
- ArrayListMultimap.create();
- final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(options);
- final DominatorTree dominatorTree = new DominatorTree(code);
- for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
- BasicBlock block = dominatorTree.getSortedBlocks()[i];
- if (block.isMarked(noCandidate)) {
- continue;
- }
- InstructionListIterator iterator = block.listIterator(code);
- while (iterator.hasNext()) {
- Instruction instruction = iterator.next();
- if (isCSEInstructionCandidate(instruction)) {
- List<Value> candidates = instructionToValue.get(equivalence.wrap(instruction));
- boolean eliminated = false;
- if (candidates.size() > 0) {
- for (Value candidate : candidates) {
- if (dominatorTree.dominatedBy(block, candidate.definition.getBlock())
- && shareCatchHandlers(instruction, candidate.definition)) {
- instruction.outValue().replaceUsers(candidate);
- candidate.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
- eliminated = true;
- iterator.removeOrReplaceByDebugLocalRead();
- break; // Don't try any more candidates.
- }
- }
- }
- if (!eliminated) {
- instructionToValue.put(equivalence.wrap(instruction), instruction.outValue());
- }
- }
- }
- }
- }
- code.returnMarkingColor(noCandidate);
- assert code.isConsistentSSA(appView);
- }
-
static class ControlFlowSimplificationResult {
+
private boolean anyAffectedValues;
private boolean anySimplifications;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index 5ca2e28..d56f416 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
+import com.android.tools.r8.utils.StringUtils;
public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
@@ -71,7 +72,7 @@
String fieldName = field.getName().toString();
if (field.getHolderType() == getSynthesizingContext().getType()) {
return factory.createString(
- "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
+ "get" + StringUtils.toUpperCase(fieldName.substring(0, 1)) + fieldName.substring(1));
}
assert field == factory.enumMembers.nameField || field == factory.enumMembers.ordinalField;
return field.getName();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index b0b64ee..a4e5008 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.StringUtils;
import java.util.Set;
public class BooleanMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -79,7 +80,7 @@
if (definition.isConstString()) {
ConstString constString = definition.asConstString();
if (!constString.instructionInstanceCanThrow()) {
- String value = constString.getValue().toString().toLowerCase();
+ String value = StringUtils.toLowerCase(constString.getValue().toString());
if (value.equals("true")) {
instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
} else if (value.equals("false")) {
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 421be9b..ea4f7b1 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -29,6 +30,8 @@
import com.android.tools.r8.ir.code.Cmp;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstMethodHandle;
+import com.android.tools.r8.ir.code.ConstMethodType;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DebugLocalRead;
@@ -73,8 +76,10 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.code.RecordFieldValues;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.Shl;
import com.android.tools.r8.ir.code.Shr;
import com.android.tools.r8.ir.code.StaticGet;
@@ -88,6 +93,7 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
import com.android.tools.r8.lightir.LirCode.PositionEntry;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
@@ -167,12 +173,14 @@
// instruction denotes a new block.
if (currentBlock == null) {
currentBlock = getBasicBlock(nextInstructionIndex);
- CatchHandlers<Integer> handlers =
- code.getTryCatchTable().getHandlersForBlock(nextInstructionIndex);
- if (handlers != null) {
- List<BasicBlock> targets = ListUtils.map(handlers.getAllTargets(), this::getBasicBlock);
- targets.forEach(currentBlock::link);
- currentBlock.linkCatchSuccessors(handlers.getGuards(), targets);
+ TryCatchTable tryCatchTable = code.getTryCatchTable();
+ if (tryCatchTable != null) {
+ CatchHandlers<Integer> handlers = tryCatchTable.getHandlersForBlock(nextInstructionIndex);
+ if (handlers != null) {
+ List<BasicBlock> targets = ListUtils.map(handlers.getAllTargets(), this::getBasicBlock);
+ targets.forEach(currentBlock::link);
+ currentBlock.linkCatchSuccessors(handlers.getGuards(), targets);
+ }
}
} else {
assert !blocks.containsKey(nextInstructionIndex);
@@ -509,6 +517,24 @@
}
@Override
+ public void onConstMethodHandle(DexMethodHandle methodHandle) {
+ TypeElement handleType =
+ TypeElement.fromDexType(
+ appView.dexItemFactory().methodHandleType, Nullability.definitelyNotNull(), appView);
+ Value dest = getOutValueForNextInstruction(handleType);
+ addInstruction(new ConstMethodHandle(dest, methodHandle));
+ }
+
+ @Override
+ public void onConstMethodType(DexProto methodType) {
+ TypeElement typeElement =
+ TypeElement.fromDexType(
+ appView.dexItemFactory().methodTypeType, Nullability.definitelyNotNull(), appView);
+ Value dest = getOutValueForNextInstruction(typeElement);
+ addInstruction(new ConstMethodType(dest, methodType));
+ }
+
+ @Override
public void onNumberConversion(NumericType from, NumericType to, EV value) {
Value dest =
getOutValueForNextInstruction(
@@ -634,7 +660,7 @@
@Override
public void onInvokePolymorphic(DexMethod target, DexProto proto, List<EV> arguments) {
- Value dest = getInvokeInstructionOutputValue(target);
+ Value dest = getInvokeInstructionOutputValue(proto);
List<Value> ssaArgumentValues = getValues(arguments);
InvokePolymorphic instruction = new InvokePolymorphic(target, proto, dest, ssaArgumentValues);
addInstruction(instruction);
@@ -719,6 +745,12 @@
}
@Override
+ public void onSafeCheckCast(DexType type, EV value) {
+ Value dest = getOutValueForNextInstruction(type.toTypeElement(appView));
+ addInstruction(new SafeCheckCast(dest, getValue(value), type));
+ }
+
+ @Override
public void onInstanceOf(DexType type, EV value) {
Value dest = getOutValueForNextInstruction(TypeElement.getInt());
addInstruction(new InstanceOf(dest, getValue(value), type));
@@ -859,5 +891,14 @@
Value dest = getOutValueForNextInstruction(TypeElement.getInt());
addInstruction(new InitClass(dest, clazz));
}
+
+ @Override
+ public void onRecordFieldValues(DexField[] fields, List<EV> values) {
+ TypeElement typeElement =
+ TypeElement.fromDexType(
+ appView.dexItemFactory().objectArrayType, Nullability.definitelyNotNull(), appView);
+ Value dest = getOutValueForNextInstruction(typeElement);
+ addInstruction(new RecordFieldValues(fields, dest, getValues(values)));
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 60df1eb..b88f1f7 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.cf.code.CfLogicalBinop;
import com.android.tools.r8.cf.code.CfNumberConversion;
import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexCallSite;
@@ -16,6 +15,7 @@
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -82,11 +82,6 @@
// Mapping from instruction to the end usage of SSA values with debug local info.
private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap<>();
- // TODO(b/225838009): Reconsider this fixed space as the operand count for phis is much larger.
- // Pre-allocated space for caching value indexes when writing instructions.
- private static final int MAX_VALUE_COUNT = 256;
- private int[] valueIndexBuffer = new int[MAX_VALUE_COUNT];
-
/**
* Internal "DexItem" for the instruction payloads such that they can be put in the pool.
*
@@ -137,6 +132,14 @@
}
}
+ public static class RecordFieldValuesPayload extends InstructionPayload {
+ public final DexField[] fields;
+
+ public RecordFieldValuesPayload(DexField[] fields) {
+ this.fields = fields;
+ }
+ }
+
public LirBuilder(DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) {
this.factory = factory;
constants = new Reference2IntOpenHashMap<>();
@@ -178,6 +181,7 @@
private void setPositionIndex(int instructionIndex, Position position) {
assert positionTable.isEmpty()
|| ListUtils.last(positionTable).fromInstructionIndex < instructionIndex;
+ assert positionTable.isEmpty() || !ListUtils.last(positionTable).position.equals(position);
positionTable.add(new PositionEntry(instructionIndex, position));
}
@@ -287,7 +291,6 @@
private LirBuilder<V, EV> addInstructionTemplate(
int opcode, List<DexItem> items, List<V> values) {
- assert values.size() < MAX_VALUE_COUNT;
int instructionIndex = advanceInstructionState();
int operandSize = 0;
for (DexItem item : items) {
@@ -297,14 +300,16 @@
EV value = getEncodedValue(values.get(i));
int encodedValueIndex = getEncodedValueIndex(value, instructionIndex);
operandSize += encodedValueIndexSize(encodedValueIndex);
- valueIndexBuffer[i] = encodedValueIndex;
}
writer.writeInstruction(opcode, operandSize);
for (DexItem item : items) {
writeConstantIndex(item);
}
for (int i = 0; i < values.size(); i++) {
- writeEncodedValueIndex(valueIndexBuffer[i]);
+ // TODO(b/225838009): Consider backpatching operand size to avoid recomputing value indexes.
+ EV value = getEncodedValue(values.get(i));
+ int encodedValueIndex = getEncodedValueIndex(value, instructionIndex);
+ writeEncodedValueIndex(encodedValueIndex);
}
return this;
}
@@ -379,7 +384,7 @@
case DOUBLE:
return addConstDouble(value);
default:
- throw new Unimplemented();
+ throw new Unreachable();
}
}
@@ -391,6 +396,14 @@
return addOneItemInstruction(LirOpcodes.LDC, type);
}
+ public LirBuilder<V, EV> addConstMethodHandle(DexMethodHandle methodHandle) {
+ return addOneItemInstruction(LirOpcodes.LDC, methodHandle);
+ }
+
+ public LirBuilder<V, EV> addConstMethodType(DexProto methodType) {
+ return addOneItemInstruction(LirOpcodes.LDC, methodType);
+ }
+
public LirBuilder<V, EV> addNeg(NumericType type, V value) {
int opcode;
switch (type) {
@@ -475,6 +488,13 @@
LirOpcodes.CHECKCAST, Collections.singletonList(type), Collections.singletonList(value));
}
+ public LirBuilder<V, EV> addSafeCheckCast(DexType type, V value) {
+ return addInstructionTemplate(
+ LirOpcodes.CHECKCAST_SAFE,
+ Collections.singletonList(type),
+ Collections.singletonList(value));
+ }
+
public LirBuilder<V, EV> addInstanceOf(DexType type, V value) {
return addInstructionTemplate(
LirOpcodes.INSTANCEOF, Collections.singletonList(type), Collections.singletonList(value));
@@ -688,6 +708,8 @@
constants.forEach((item, index) -> constantTable[index] = item);
DebugLocalInfoTable<EV> debugTable =
debugLocals.isEmpty() ? null : new DebugLocalInfoTable<>(debugLocals, debugLocalEnds);
+ TryCatchTable tryCatchTable =
+ tryCatchRanges.isEmpty() ? null : new TryCatchTable(tryCatchRanges);
return new LirCode<>(
metadata,
constantTable,
@@ -695,7 +717,7 @@
argumentCount,
byteWriter.toByteArray(),
instructionCount,
- new TryCatchTable(tryCatchRanges),
+ tryCatchTable,
debugTable,
strategy.getStrategyInfo());
}
@@ -860,4 +882,10 @@
public LirBuilder<V, EV> addInitClass(DexType clazz) {
return addOneItemInstruction(LirOpcodes.INITCLASS, clazz);
}
+
+ public LirBuilder<V, EV> addRecordFieldValues(DexField[] fields, List<V> values) {
+ RecordFieldValuesPayload payload = new RecordFieldValuesPayload(fields);
+ return addInstructionTemplate(
+ LirOpcodes.RECORDFIELDVALUES, Collections.singletonList(payload), values);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 3f84cdf..f1c04ec 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -10,7 +10,9 @@
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.Position;
+import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
@@ -30,7 +32,9 @@
final Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers;
public TryCatchTable(Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers) {
- this.tryCatchHandlers = tryCatchHandlers;
+ assert !tryCatchHandlers.isEmpty();
+ // Copy the map to ensure it has not over-allocated the backing store.
+ this.tryCatchHandlers = new Int2ReferenceOpenHashMap<>(tryCatchHandlers);
}
public CatchHandlers<Integer> getHandlersForBlock(int blockIndex) {
@@ -45,9 +49,20 @@
public DebugLocalInfoTable(
Map<EV, DebugLocalInfo> valueToLocalMap, Int2ReferenceMap<int[]> instructionToEndUseMap) {
assert !valueToLocalMap.isEmpty();
- assert !instructionToEndUseMap.isEmpty();
- this.valueToLocalMap = valueToLocalMap;
- this.instructionToEndUseMap = instructionToEndUseMap;
+ // TODO(b/283049198): Debug ends may not be maintained so we can't assume they are non-empty.
+ // Copy the maps to ensure they have not over-allocated the backing store.
+ this.valueToLocalMap = ImmutableMap.copyOf(valueToLocalMap);
+ this.instructionToEndUseMap =
+ instructionToEndUseMap.isEmpty()
+ ? null
+ : new Int2ReferenceOpenHashMap<>(instructionToEndUseMap);
+ }
+
+ public int[] getEnds(int index) {
+ if (instructionToEndUseMap == null) {
+ return null;
+ }
+ return instructionToEndUseMap.get(index);
}
public void forEachLocalDefinition(BiConsumer<EV, DebugLocalInfo> fn) {
@@ -73,7 +88,7 @@
/** Cached value for the number of logical instructions (excludes arguments, includes phis). */
private final int instructionCount;
- /** Table of try-catch handlers for each basic block. */
+ /** Table of try-catch handlers for each basic block (if present). */
private final TryCatchTable tryCatchTable;
/** Table of debug local information for each SSA value (if present). */
@@ -153,9 +168,7 @@
}
public int[] getDebugLocalEnds(int instructionValueIndex) {
- return debugLocalInfoTable == null
- ? null
- : debugLocalInfoTable.instructionToEndUseMap.get(instructionValueIndex);
+ return debugLocalInfoTable == null ? null : debugLocalInfoTable.getEnds(instructionValueIndex);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 0f0811d..8863892 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -207,6 +207,8 @@
int DEBUGLOCALREAD = 220;
int INITCLASS = 221;
int INVOKEPOLYMORPHIC = 222;
+ int RECORDFIELDVALUES = 223;
+ int CHECKCAST_SAFE = 224;
static String toString(int opcode) {
switch (opcode) {
@@ -537,6 +539,10 @@
return "INITCLASS";
case INVOKEPOLYMORPHIC:
return "INVOKEPOLYMORPHIC";
+ case RECORDFIELDVALUES:
+ return "RECORDFIELDVALUES";
+ case CHECKCAST_SAFE:
+ return "CHECKCAST_SAFE";
default:
throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index 781db23..73ad12b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -20,6 +21,7 @@
import com.android.tools.r8.lightir.LirBuilder.FillArrayPayload;
import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
import com.android.tools.r8.lightir.LirBuilder.NameComputationPayload;
+import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import java.util.ArrayList;
import java.util.List;
@@ -101,6 +103,14 @@
onInstruction();
}
+ public void onConstMethodHandle(DexMethodHandle methodHandle) {
+ onInstruction();
+ }
+
+ public void onConstMethodType(DexProto methodType) {
+ onInstruction();
+ }
+
private void onArrayGetInternal(MemberType type, LirInstructionView view) {
if (type.isObject()) {
DexType destType = (DexType) getConstantItem(view.getNextConstantOperand());
@@ -462,6 +472,10 @@
onInstruction();
}
+ public void onSafeCheckCast(DexType type, EV value) {
+ onInstruction();
+ }
+
public void onInstanceOf(DexType type, EV value) {
onInstruction();
}
@@ -491,6 +505,10 @@
onInstruction();
}
+ public void onRecordFieldValues(DexField[] fields, List<EV> values) {
+ onInstruction();
+ }
+
private DexItem getConstantItem(int index) {
return code.getConstantItem(index);
}
@@ -515,6 +533,14 @@
onConstClass((DexType) item);
return;
}
+ if (item instanceof DexMethodHandle) {
+ onConstMethodHandle((DexMethodHandle) item);
+ return;
+ }
+ if (item instanceof DexProto) {
+ onConstMethodType((DexProto) item);
+ return;
+ }
throw new Unimplemented();
}
case LirOpcodes.ICONST_M1:
@@ -1087,6 +1113,13 @@
onCheckCast(type, value);
return;
}
+ case LirOpcodes.CHECKCAST_SAFE:
+ {
+ DexType type = getNextDexTypeOperand(view);
+ EV value = getNextValueOperand(view);
+ onSafeCheckCast(type, value);
+ return;
+ }
case LirOpcodes.INSTANCEOF:
{
DexType type = getNextDexTypeOperand(view);
@@ -1204,6 +1237,14 @@
onInitClass(clazz);
return;
}
+ case LirOpcodes.RECORDFIELDVALUES:
+ {
+ RecordFieldValuesPayload payload =
+ (RecordFieldValuesPayload) getConstantItem(view.getNextConstantOperand());
+ List<EV> values = getInvokeInstructionArguments(view);
+ onRecordFieldValues(payload.fields, values);
+ return;
+ }
default:
throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index b20e303..5092894 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
@@ -166,6 +167,16 @@
}
@Override
+ public void onConstMethodHandle(DexMethodHandle methodHandle) {
+ appendOutValue().append("methodHandle(").append(methodHandle).append(")");
+ }
+
+ @Override
+ public void onConstMethodType(DexProto methodType) {
+ appendOutValue().append("methodType(").append(methodType).append(")");
+ }
+
+ @Override
public void onBinop(NumericType type, EV left, EV right) {
appendOutValue();
appendValueArguments(left, right);
@@ -339,6 +350,11 @@
}
@Override
+ public void onSafeCheckCast(DexType type, EV value) {
+ onCheckCast(type, value);
+ }
+
+ @Override
public void onInstanceOf(DexType type, EV value) {
appendOutValue();
appendValueArguments(value);
@@ -402,4 +418,9 @@
public void onInitClass(DexType clazz) {
builder.append(clazz);
}
+
+ @Override
+ public void onRecordFieldValues(DexField[] fields, List<EV> values) {
+ appendValueArguments(values);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
index 0b0d537..0428126 100644
--- a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
+++ b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
@@ -72,12 +72,21 @@
@Override
public LirStrategyInfo<Integer> getStrategyInfo() {
- return new LirStrategyInfo<Integer>() {
- @Override
- public LirSsaValueStrategy<Integer> getReferenceStrategy() {
- return referenceStrategy;
- }
- };
+ return new StrategyInfo(referenceStrategy);
+ }
+ }
+
+ private static class StrategyInfo extends LirStrategyInfo<Integer> {
+
+ private final LirSsaValueStrategy<Integer> referenceStrategy;
+
+ public StrategyInfo(LirSsaValueStrategy<Integer> referenceStrategy) {
+ this.referenceStrategy = referenceStrategy;
+ }
+
+ @Override
+ public LirSsaValueStrategy<Integer> getReferenceStrategy() {
+ return referenceStrategy;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 9a801b9..cca3952 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
@@ -62,7 +63,7 @@
if (options.getProguardConfiguration().hasDontUseMixedCaseClassnames()) {
allowMixedCaseNaming = false;
- isUsed = candidate -> usedTypeNames.contains(candidate.toLowerCase());
+ isUsed = candidate -> usedTypeNames.contains(StringUtils.toLowerCase(candidate));
} else {
allowMixedCaseNaming = true;
isUsed = usedTypeNames::contains;
@@ -70,7 +71,7 @@
}
private void setUsedTypeName(String typeName) {
- usedTypeNames.add(allowMixedCaseNaming ? typeName : typeName.toLowerCase());
+ usedTypeNames.add(allowMixedCaseNaming ? typeName : StringUtils.toLowerCase(typeName));
}
static class ClassRenaming {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 05681d8..50f9d4d 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -358,6 +358,7 @@
if (isCommentLineWithJsonBrace()) {
final String currentRenamedNameFinal = previousRenamedName;
final MappedRange currentRange = activeMappedRange;
+ final MemberNaming lastAddedNamingFinal = lastAddedNaming;
// Reading global info should cause member mapping to return since we are now reading
// headers pertaining to what could be a concatinated file.
BooleanBox readGlobalInfo = new BooleanBox(false);
@@ -414,8 +415,10 @@
currentResidualSignature.clear();
return;
}
- currentRange.setResidualSignatureInternal(
- residualSignature.asMethodSignature());
+ if (!isMappedRangeLastAddedNaming(lastAddedNamingFinal, currentRange)) {
+ currentRange.setResidualSignatureInternal(
+ residualSignature.asMethodSignature());
+ }
}
}
}
@@ -477,10 +480,7 @@
if (activeMappedRange != null) {
if (residualSignature != null) {
activeMappedRange.setResidualSignatureInternal(residualSignature);
- } else if (lastAddedNaming != null
- && lastAddedNaming
- .getOriginalSignature()
- .equals(activeMappedRange.getOriginalSignature())) {
+ } else if (isMappedRangeLastAddedNaming(lastAddedNaming, activeMappedRange)) {
// If we already parsed a residual signature for the newly read mapped range and have
// lost the information about the residual signature we re-create it again.
activeMappedRange.setResidualSignatureInternal(
@@ -527,6 +527,12 @@
}
}
+ private boolean isMappedRangeLastAddedNaming(
+ MemberNaming lastAddedNaming, MappedRange activeMappedRange) {
+ return lastAddedNaming != null
+ && lastAddedNaming.getOriginalSignature().equals(activeMappedRange.getOriginalSignature());
+ }
+
private MemberNaming addMemberEntryOrCopyInformation(
MemberNaming lastAddedNaming,
Signature originalSignature,
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index c4612a7..e1bf867 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -18,6 +18,7 @@
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -63,7 +64,7 @@
Arrays.asList("--art-profile", "--feature-jar");
private static boolean FileUtils_isArchive(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = path.getFileName().toString().toLowerCase(Locale.ROOT);
return name.endsWith(".apk")
|| name.endsWith(".jar")
|| name.endsWith(".zip")
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index a7caf57..347e254 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -38,17 +38,17 @@
System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
public static boolean isDexFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(DEX_EXTENSION);
}
public static boolean isVDexFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(VDEX_EXTENSION);
}
public static boolean isClassFile(String path) {
- String name = path.toLowerCase();
+ String name = StringUtils.toLowerCase(path);
// Android does not support Java 9 module, thus skip module-info.
if (name.equals(MODULE_INFO_CLASS)) {
return false;
@@ -61,32 +61,32 @@
}
public static boolean isJarFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(JAR_EXTENSION);
}
public static boolean isZipFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(ZIP_EXTENSION);
}
public static boolean isApkFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(APK_EXTENSION);
}
public static boolean isAarFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(AAR_EXTENSION);
}
public static boolean isJavaFile(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(JAVA_EXTENSION);
}
public static boolean isArchive(Path path) {
- String name = path.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(path.getFileName().toString());
return name.endsWith(APK_EXTENSION)
|| name.endsWith(JAR_EXTENSION)
|| name.endsWith(ZIP_EXTENSION)
@@ -109,12 +109,14 @@
return Files.readAllLines(file);
}
- public static void writeTextFile(Path file, List<String> lines) throws IOException {
+ public static Path writeTextFile(Path file, List<String> lines) throws IOException {
Files.write(file, lines);
+ return file;
}
- public static void writeTextFile(Path file, String... lines) throws IOException {
+ public static Path writeTextFile(Path file, String... lines) throws IOException {
Files.write(file, Arrays.asList(lines));
+ return file;
}
public static Path validateOutputFile(Path path, Reporter reporter) {
@@ -157,7 +159,7 @@
}
public static boolean isClassesDexFile(Path file) {
- String name = file.getFileName().toString().toLowerCase();
+ String name = StringUtils.toLowerCase(file.getFileName().toString());
if (!name.startsWith("classes") || !name.endsWith(DEX_EXTENSION)) {
return false;
}
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 8a733a5..2f66e6f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2082,6 +2082,8 @@
public boolean calculateItemUseCountInDex = false;
public boolean calculateItemUseCountInDexDumpSingleUseStrings = false;
+ public boolean enableBinopOptimization = true;
+
private DeterminismChecker getDeterminismChecker() {
// Lazily read the env-var so that it can be set after options init.
if (determinismChecker == null && !hasReadCheckDeterminism) {
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index af857c4..1cbcad2 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -13,6 +13,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -393,11 +394,11 @@
}
public static boolean isFalsy(String string) {
- return string.equals("0") || string.toLowerCase().equals("false");
+ return string.equals("0") || StringUtils.toLowerCase(string).equals("false");
}
public static boolean isTruthy(String string) {
- return string.equals("1") || string.toLowerCase().equals("true");
+ return string.equals("1") || StringUtils.toLowerCase(string).equals("true");
}
public static boolean isWhitespace(int codePoint) {
@@ -482,7 +483,7 @@
if (stringToCapitalize == null || stringToCapitalize.isEmpty()) {
return stringToCapitalize;
}
- return stringToCapitalize.substring(0, 1).toUpperCase() + stringToCapitalize.substring(1);
+ return toUpperCase(stringToCapitalize.substring(0, 1)) + stringToCapitalize.substring(1);
}
public static int indexOf(String s, char ch1, char ch2) {
@@ -492,4 +493,12 @@
if (i2 == -1) return i1;
return Math.min(i1, i2);
}
+
+ public static String toLowerCase(String s) {
+ return s.toLowerCase(Locale.ROOT);
+ }
+
+ public static String toUpperCase(String s) {
+ return s.toUpperCase(Locale.ROOT);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 39c6354..068350c 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -230,19 +230,20 @@
}
public static boolean isDexFile(String entry) {
- String name = entry.toLowerCase();
+ String name = StringUtils.toLowerCase(entry);
return name.endsWith(DEX_EXTENSION);
}
public static boolean isClassFile(String entry) {
- String name = entry.toLowerCase();
- if (name.endsWith(MODULE_INFO_CLASS)) {
+ if (entry.endsWith(MODULE_INFO_CLASS)) {
return false;
}
- if (name.startsWith("meta-inf") || name.startsWith("/meta-inf")) {
+ // Only check for upper case META-INF. See JAR File Specification,
+ // https://docs.oracle.com/en/java/javase/17/docs/specs/jar/jar.html.
+ if (entry.startsWith("META-INF") || entry.startsWith("/META-INF")) {
return false;
}
- return name.endsWith(CLASS_EXTENSION);
+ return entry.endsWith(CLASS_EXTENSION);
}
public static class ZipBuilder {
@@ -262,6 +263,14 @@
return stream;
}
+ public ZipBuilder addFile(String name, Path file) throws IOException {
+ ZipEntry zipEntry = new ZipEntry(name);
+ stream.putNextEntry(zipEntry);
+ Files.copy(file, stream);
+ stream.closeEntry();
+ return this;
+ }
+
public ZipBuilder addFilesRelative(Path basePath, Collection<Path> filesToAdd)
throws IOException {
for (Path path : filesToAdd) {
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 906f88c..80a2845 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -534,7 +535,7 @@
}
private static boolean isClassFile(String file) {
- file = file.toLowerCase();
+ file = StringUtils.toLowerCase(file);
return file.endsWith(".class");
}
@@ -543,7 +544,7 @@
}
private static boolean isDexFile(String file) {
- file = file.toLowerCase();
+ file = StringUtils.toLowerCase(file);
return file.endsWith(".dex");
}
@@ -552,7 +553,7 @@
}
private static boolean isArchive(String file) {
- file = file.toLowerCase();
+ file = StringUtils.toLowerCase(file);
return file.endsWith(".zip") || file.endsWith(".jar");
}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 4b86cea..521ccba 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -528,7 +529,7 @@
}
private static boolean isClassFile(String file) {
- file = file.toLowerCase();
+ file = StringUtils.toLowerCase(file);
return file.endsWith(".class");
}
@@ -537,7 +538,7 @@
}
private static boolean isArchive(String file) {
- file = file.toLowerCase();
+ file = StringUtils.toLowerCase(file);
return file.endsWith(".zip") || file.endsWith(".jar");
}
diff --git a/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
index b1280e4..9cdc2d1 100644
--- a/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
@@ -48,9 +48,9 @@
public void testMultiReleaseJars() throws IOException {
Path jar = temporaryFolder.getRoot().toPath().resolve("classes.jar");
try (ZipOutputStream output = new ZipOutputStream(Files.newOutputStream(jar))) {
- output.putNextEntry(new ZipEntry("meta-inf/9/Test.class"));
+ output.putNextEntry(new ZipEntry("META-INF/9/Test.class"));
output.closeEntry();
- output.putNextEntry(new ZipEntry("/meta-inf/9/Test.class"));
+ output.putNextEntry(new ZipEntry("/META-INF/9/Test.class"));
output.closeEntry();
}
ArchiveClassFileProvider provider = new ArchiveClassFileProvider(jar);
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index c7a260a..c97ab32 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ExtractMarkerUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collection;
import java.util.Set;
import org.junit.Assume;
@@ -51,7 +51,8 @@
private void verifyMarkerDex(Marker marker, Tool tool) {
assertEquals(tool, marker.getTool());
assertEquals(Version.LABEL, marker.getVersion());
- assertEquals(CompilationMode.DEBUG.toString().toLowerCase(), marker.getCompilationMode());
+ assertEquals(
+ StringUtils.toLowerCase(CompilationMode.DEBUG.toString()), marker.getCompilationMode());
assertEquals(parameters.getApiLevel().getLevel(), marker.getMinApi().intValue());
assertEquals(includeClassesChecksum, marker.getHasChecksums());
}
@@ -94,7 +95,8 @@
private static void verifyMarkerCf(Marker marker, Tool tool) {
assertEquals(tool, marker.getTool());
assertEquals(Version.LABEL, marker.getVersion());
- assertEquals(CompilationMode.DEBUG.toString().toLowerCase(), marker.getCompilationMode());
+ assertEquals(
+ StringUtils.toLowerCase(CompilationMode.DEBUG.toString()), marker.getCompilationMode());
assertFalse(marker.getHasChecksums());
}
diff --git a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
index ba323ee..b7404f3 100644
--- a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
+++ b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Test;
@@ -119,8 +120,7 @@
diagnotics.getErrors().stream()
.allMatch(
s ->
- s.getDiagnosticMessage()
- .toLowerCase()
+ StringUtils.toLowerCase(s.getDiagnosticMessage())
.contains("unsupported class file major version")));
});
} catch (CompilationFailedException e) {
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index bbd1952..242a752 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.HashSet;
@@ -113,7 +114,7 @@
return new MarkerMatcher() {
@Override
protected boolean eval(Marker marker) {
- return marker.getCompilationMode().equals(compilationMode.name().toLowerCase());
+ return marker.getCompilationMode().equals(StringUtils.toLowerCase(compilationMode.name()));
}
@Override
@@ -127,12 +128,14 @@
return new MarkerMatcher() {
@Override
protected boolean eval(Marker marker) {
- return marker.getBackend().equals(backend.name().toLowerCase());
+ return marker.getBackend().equals(StringUtils.toLowerCase(backend.name()));
}
@Override
protected void explain(Description description) {
- description.appendText(Marker.BACKEND + " ").appendText(backend.name().toLowerCase());
+ description
+ .appendText(Marker.BACKEND + " ")
+ .appendText(StringUtils.toLowerCase(backend.name()));
}
};
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index a1a6580..802e714 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -44,6 +44,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -1888,7 +1889,8 @@
fileNames.add(file.getCanonicalPath());
}
- File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
+ File resultDir =
+ temp.newFolder(firstCompilerUnderTest.toString().toLowerCase(Locale.ROOT) + "-output");
runArtTestDoRunOnArt(
dexVm, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 0da6332..48689c3 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -60,7 +60,6 @@
options.testing.allowUnnecessaryDontWarnWildcards = false;
options.horizontalClassMergerOptions().enable();
options.horizontalClassMergerOptions().setEnableInterfaceMerging();
- options.getArtProfileOptions().setEnableCompletenessCheckForTesting(true);
options
.getCfCodeAnalysisOptions()
.setAllowUnreachableCfBlocks(false)
@@ -88,6 +87,7 @@
private ByteArrayOutputStream stderr = null;
private PrintStream oldStderr = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
+ private boolean isBenchmarkRunner = false;
private Optional<Integer> isAndroidBuildVersionAdded = null;
@@ -225,6 +225,7 @@
if (System.getProperty("com.android.tools.r8.printtimes") != null) {
allowStdoutMessages();
}
+ isBenchmarkRunner = true;
return internalCompileAndBenchmark(results);
}
@@ -265,7 +266,10 @@
: getMinApiLevel();
builder.setMinApiLevel(minApi);
}
- if (!noMinApiLevel && backend.isDex() && (isD8TestBuilder() || isR8TestBuilder())) {
+ if (!noMinApiLevel
+ && backend.isDex()
+ && (isD8TestBuilder() || isR8TestBuilder())
+ && !isBenchmarkRunner) {
int minApiLevel = builder.getMinApiLevel();
allowedGlobalSynthetics.computeIfAbsent(
minApiLevel, TestCompilerBuilder::computeAllGlobalSynthetics);
@@ -284,6 +288,12 @@
}
};
}
+
+ if ((isD8TestBuilder() || isR8TestBuilder()) && !isBenchmarkRunner) {
+ addOptionsModification(
+ o -> o.getArtProfileOptions().setEnableCompletenessCheckForTesting(true));
+ }
+
builder.setOptimizeMultidexForLinearAlloc(optimizeMultidexForLinearAlloc);
if (useDefaultRuntimeLibrary) {
builder.addLibraryFiles(getDefaultLibraryFiles());
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 4f95deb..3669e77 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
@@ -322,7 +323,7 @@
@Override
public String name() {
- return vm.name().toLowerCase();
+ return StringUtils.toLowerCase(vm.name());
}
public Path getJavaHome() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bc066b0..655350b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1526,7 +1526,7 @@
public static ProcessResult forkR8WithJavaOptions(
Path dir, List<String> javaOptions, String... args) throws IOException {
String r8Jar = R8_JAR.toAbsolutePath().toString();
- return forkJavaWithJarAndJavaOptions(dir, r8Jar, Arrays.asList(args), javaOptions);
+ return forkJavaWithJarAndJavaOptions(dir, javaOptions, r8Jar, Arrays.asList(args));
}
public static ProcessResult forkR8Jar(Path dir, String... args) throws IOException {
@@ -1553,11 +1553,11 @@
private static ProcessResult forkJavaWithJar(Path dir, String jarPath, List<String> args)
throws IOException {
- return forkJavaWithJarAndJavaOptions(dir, jarPath, args, ImmutableList.of());
+ return forkJavaWithJarAndJavaOptions(dir, ImmutableList.of(), jarPath, args);
}
private static ProcessResult forkJavaWithJarAndJavaOptions(
- Path dir, String jarPath, List<String> args, List<String> javaOptions) throws IOException {
+ Path dir, List<String> javaOptions, String jarPath, List<String> args) throws IOException {
List<String> command =
new ImmutableList.Builder<String>()
.add(getJavaExecutable())
@@ -1569,6 +1569,19 @@
return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
}
+ public static ProcessResult forkJavaWithJavaOptions(
+ Path dir, List<String> javaOptions, Class clazz, List<String> args) throws IOException {
+ List<String> command =
+ new ImmutableList.Builder<String>()
+ .add(getJavaExecutable())
+ .addAll(javaOptions)
+ .add("-cp")
+ .add(System.getProperty("java.class.path"))
+ .add(clazz.getCanonicalName())
+ .addAll(args)
+ .build();
+ return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
+ }
private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
throws IOException {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
index 4bf5334..26e9029 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
@@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Locale;
public class BenchmarkDependency {
@@ -34,7 +35,7 @@
this.directoryName = directoryName;
this.location = location;
String firstChar = name.substring(0, 1);
- if (!firstChar.equals(firstChar.toLowerCase()) || name.contains("_")) {
+ if (!firstChar.equals(firstChar.toLowerCase(Locale.ROOT)) || name.contains("_")) {
throw new BenchmarkConfigError("Benchmark name should use lowerCamelCase, found: " + name);
}
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
index 0f1384c..f210e18 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.benchmarks;
+import com.android.tools.r8.utils.StringUtils;
+
public class BenchmarkRunner {
public interface BenchmarkRunnerFunction {
@@ -15,7 +17,7 @@
@Override
public String toString() {
- return name().toLowerCase();
+ return StringUtils.toLowerCase(name());
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 4e8caaa..fda115e 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -362,7 +362,8 @@
assertTrue(split > 0);
String targetMethodRaw = name.substring("invoke".length(), split);
String targetMethod =
- targetMethodRaw.substring(0, 1).toLowerCase() + targetMethodRaw.substring(1);
+ StringUtils.toLowerCase(targetMethodRaw.substring(0, 1))
+ + targetMethodRaw.substring(1);
String targetHolderRaw = name.substring(split + 2);
String targetHolderType =
InvokeSuperTest.class.getTypeName() + "$" + targetHolderRaw;
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java
new file mode 100644
index 0000000..390d6ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java
@@ -0,0 +1,1063 @@
+// Copyright (c) 2023, 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;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdentityAbsorbingTest extends TestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "2147483647",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "2147483646",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "-2147483648",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "-2147483647",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "9223372036854775807",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "9223372036854775806",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "-9223372036854775808",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "-9223372036854775807",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "0",
+ "-1",
+ "-1",
+ "-1",
+ "0",
+ "0",
+ "0");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public IdentityAbsorbingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ inspector
+ .clazz(Main.class)
+ .forAllMethods(
+ m ->
+ assertTrue(
+ m.streamInstructions()
+ .noneMatch(
+ i -> i.isIntOrLongLogicalBinop() || i.isIntOrLongArithmeticBinop())));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ intTests(Integer.MAX_VALUE);
+ intTests(Integer.MAX_VALUE - 1);
+ intTests(Integer.MIN_VALUE);
+ intTests(Integer.MIN_VALUE + 1);
+ intTests(System.currentTimeMillis() > 0 ? 0 : 1);
+ intTests(System.currentTimeMillis() > 0 ? 1 : 9);
+ intTests(System.currentTimeMillis() > 0 ? -1 : 1);
+
+ longTests(Long.MAX_VALUE);
+ longTests(Long.MAX_VALUE - 1);
+ longTests(Long.MIN_VALUE);
+ longTests(Long.MIN_VALUE + 1);
+ longTests(System.currentTimeMillis() > 0 ? 0L : 1L);
+ longTests(System.currentTimeMillis() > 0 ? 1L : 9L);
+ longTests(System.currentTimeMillis() > 0 ? -1L : 1L);
+ }
+
+ private static void longTests(long val) {
+ identityLongTest(val);
+ absorbingLongTest(val);
+ identityDoubleLongTest(val);
+ absorbingDoubleLongTest(val);
+ }
+
+ private static void intTests(int val) {
+ identityIntTest(val);
+ absorbingIntTest(val);
+ identityDoubleIntTest(val);
+ absorbingDoubleIntTest(val);
+ chainIntTest(val);
+ }
+
+ @NeverInline
+ private static void identityDoubleIntTest(int val) {
+ System.out.println(val + 0 + 0);
+ System.out.println(0 + val + 0);
+ System.out.println(0 + 0 + val);
+ System.out.println(val - 0 - 0);
+ System.out.println(val * 1 * 1);
+ System.out.println(1 * val * 1);
+ System.out.println(1 * 1 * val);
+ System.out.println(val / 1 / 1);
+
+ System.out.println(val & -1 & -1);
+ System.out.println(-1 & val & -1);
+ System.out.println(-1 & -1 & val);
+ System.out.println(val | 0 | 0);
+ System.out.println(0 | val | 0);
+ System.out.println(0 | 0 | val);
+ System.out.println(val ^ 0 ^ 0);
+ System.out.println(0 ^ val ^ 0);
+ System.out.println(0 ^ 0 ^ val);
+ System.out.println(val << 0 << 0);
+ System.out.println(val >> 0 >> 0);
+ System.out.println(val >>> 0 >>> 0);
+ }
+
+ @NeverInline
+ private static void identityDoubleLongTest(long val) {
+ System.out.println(val + 0L + 0L);
+ System.out.println(0L + val + 0L);
+ System.out.println(0L + 0L + val);
+ System.out.println(val - 0L - 0L);
+ System.out.println(val * 1L * 1L);
+ System.out.println(1L * val * 1L);
+ System.out.println(1L * 1L * val);
+ System.out.println(val / 1L / 1L);
+
+ System.out.println(val & -1L & -1L);
+ System.out.println(-1L & val & -1L);
+ System.out.println(-1L & -1L & val);
+ System.out.println(val | 0L | 0L);
+ System.out.println(0L | val | 0L);
+ System.out.println(0L | 0L | val);
+ System.out.println(val ^ 0L ^ 0L);
+ System.out.println(0L ^ val ^ 0L);
+ System.out.println(0L ^ 0L ^ val);
+ System.out.println(val << 0L << 0L);
+ System.out.println(val >> 0L >> 0L);
+ System.out.println(val >>> 0L >>> 0L);
+ }
+
+ @NeverInline
+ private static void identityIntTest(int val) {
+ System.out.println(val + 0);
+ System.out.println(0 + val);
+ System.out.println(val - 0);
+ System.out.println(val * 1);
+ System.out.println(1 * val);
+ System.out.println(val / 1);
+
+ System.out.println(val & -1);
+ System.out.println(-1 & val);
+ System.out.println(val | 0);
+ System.out.println(0 | val);
+ System.out.println(val ^ 0);
+ System.out.println(0 ^ val);
+ System.out.println(val << 0);
+ System.out.println(val >> 0);
+ System.out.println(val >>> 0);
+ }
+
+ @NeverInline
+ private static void identityLongTest(long val) {
+ System.out.println(val + 0L);
+ System.out.println(0L + val);
+ System.out.println(val - 0L);
+ System.out.println(val * 1L);
+ System.out.println(1L * val);
+ System.out.println(val / 1L);
+
+ System.out.println(val & -1L);
+ System.out.println(-1L & val);
+ System.out.println(val | 0L);
+ System.out.println(0L | val);
+ System.out.println(val ^ 0L);
+ System.out.println(0L ^ val);
+ System.out.println(val << 0L);
+ System.out.println(val >> 0L);
+ System.out.println(val >>> 0L);
+ }
+
+ @NeverInline
+ private static void absorbingDoubleIntTest(int val) {
+ System.out.println(val * 0 * 0);
+ System.out.println(0 * val * 0);
+ System.out.println(0 * 0 * val);
+ // val would need to be proven non zero.
+ // System.out.println(0 / val);
+ // System.out.println(0 % val);
+
+ System.out.println(0 & 0 & val);
+ System.out.println(0 & val & 0);
+ System.out.println(val & 0 & 0);
+ System.out.println(-1 | -1 | val);
+ System.out.println(-1 | val | -1);
+ System.out.println(val | -1 | -1);
+ System.out.println(0 << 0 << val);
+ System.out.println(0 >> 0 >> val);
+ System.out.println(0 >>> 0 >>> val);
+ }
+
+ @NeverInline
+ private static void absorbingDoubleLongTest(long val) {
+ System.out.println(val * 0L * 0L);
+ System.out.println(0L * val * 0L);
+ System.out.println(0L * 0L * val);
+ // val would need to be proven non zero.
+ // System.out.println(0L / val);
+ // System.out.println(0L % val);
+
+ System.out.println(0L & 0L & val);
+ System.out.println(0L & val & 0L);
+ System.out.println(val & 0L & 0L);
+ System.out.println(-1L | -1L | val);
+ System.out.println(-1L | val | -1L);
+ System.out.println(val | -1L | -1L);
+ System.out.println(0L << 0L << val);
+ System.out.println(0L >> 0L >> val);
+ System.out.println(0L >>> 0L >>> val);
+ }
+
+ @NeverInline
+ private static void absorbingIntTest(int val) {
+ System.out.println(val * 0);
+ System.out.println(0 * val);
+ // val would need to be proven non zero.
+ // System.out.println(0 / val);
+ // System.out.println(0 % val);
+
+ System.out.println(0 & val);
+ System.out.println(val & 0);
+ System.out.println(-1 | val);
+ System.out.println(val | -1);
+ System.out.println(0 << val);
+ System.out.println(0 >> val);
+ System.out.println(0 >>> val);
+ }
+
+ @NeverInline
+ private static void absorbingLongTest(long val) {
+ System.out.println(val * 0L);
+ System.out.println(0L * val);
+ // val would need to be proven non zero.
+ // System.out.println(0L / val);
+ // System.out.println(0L % val);
+
+ System.out.println(0L & val);
+ System.out.println(val & 0L);
+ System.out.println(-1L | val);
+ System.out.println(val | -1L);
+ System.out.println(0L << val);
+ System.out.println(0L >> val);
+ System.out.println(0L >>> val);
+ }
+
+ private static void chainIntTest(int val) {
+ int abs = System.currentTimeMillis() > 0 ? val * 0 : 0 * val;
+ System.out.println(abs * val);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
index 609b2c9..277faa0 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
@@ -38,7 +38,7 @@
}
public String getTestName() {
- return toString().toLowerCase() + "ConstraintOnTrivialPhiTest";
+ return StringUtils.toLowerCase(toString()) + "ConstraintOnTrivialPhiTest";
}
public String getConstInstruction() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondTest.java
new file mode 100644
index 0000000..cf928e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2023, 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.ifs;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.AlwaysInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 DoubleDiamondTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DoubleDiamondTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableAlwaysInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "5", "1", "1", "5", "1", "5", "5", "1", "5", "5", "1", "1", "1", "5", "5", "5", "1",
+ "1", "1", "5");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ for (FoundMethodSubject method : inspector.clazz(Main.class).allMethods()) {
+ if (!method.getOriginalName().equals("main")) {
+ long count = method.streamInstructions().filter(InstructionSubject::isIf).count();
+ assertEquals(method.getOriginalName().contains("Double") ? 2 : 1, count);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(indirectEquals(2, 6));
+ System.out.println(indirectEquals(3, 3));
+
+ System.out.println(indirectEqualsNegated(2, 6));
+ System.out.println(indirectEqualsNegated(3, 3));
+
+ System.out.println(indirectLessThan(2, 6));
+ System.out.println(indirectLessThan(7, 3));
+
+ System.out.println(indirectLessThanNegated(2, 6));
+ System.out.println(indirectLessThanNegated(7, 3));
+
+ System.out.println(indirectDoubleEquals(2, 6, 6));
+ System.out.println(indirectDoubleEquals(7, 7, 3));
+ System.out.println(indirectDoubleEquals(1, 1, 1));
+
+ System.out.println(indirectDoubleEqualsNegated(2, 6, 6));
+ System.out.println(indirectDoubleEqualsNegated(2, 2, 6));
+ System.out.println(indirectDoubleEqualsNegated(7, 7, 7));
+
+ System.out.println(indirectDoubleEqualsSplit(2, 6, 6));
+ System.out.println(indirectDoubleEqualsSplit(7, 7, 3));
+ System.out.println(indirectDoubleEqualsSplit(1, 1, 1));
+
+ System.out.println(indirectDoubleEqualsSplitNegated(2, 6, 6));
+ System.out.println(indirectDoubleEqualsSplitNegated(2, 2, 6));
+ System.out.println(indirectDoubleEqualsSplitNegated(7, 7, 7));
+ }
+
+ @AlwaysInline
+ public static boolean doubleEqualsSplit(int i, int j, int k) {
+ if (i != j) {
+ return false;
+ }
+ return j == k;
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsSplit(int i, int j, int k) {
+ if (doubleEqualsSplit(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsSplitNegated(int i, int j, int k) {
+ if (!doubleEqualsSplit(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static boolean doubleEquals(int i, int j, int k) {
+ return i == j && j == k;
+ }
+
+ @NeverInline
+ public static int indirectDoubleEquals(int i, int j, int k) {
+ if (doubleEquals(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsNegated(int i, int j, int k) {
+ if (!doubleEquals(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static boolean equals(int i, int j) {
+ return i == j;
+ }
+
+ @NeverInline
+ public static int indirectEquals(int i, int j) {
+ if (equals(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectEqualsNegated(int i, int j) {
+ if (!equals(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static boolean lessThan(int i, int j) {
+ return i <= j;
+ }
+
+ @NeverInline
+ public static int indirectLessThan(int i, int j) {
+ if (lessThan(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectLessThanNegated(int i, int j) {
+ if (!lessThan(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
index 572020d..8f8541c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
@@ -27,7 +27,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection parameters() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ return getTestParameters().withDefaultRuntimes().withAllApiLevels().build();
}
@Test
@@ -39,7 +39,9 @@
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters)
- .compile()
+ .addOptionsModification(o -> o.testing.roundtripThroughLir = true)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42")
.inspect(inspector -> assertThat(inspector.clazz(anim.class), isAbsent()));
}
@@ -86,6 +88,7 @@
anim.abc_fade_in ^= packageIdTransform;
// Unop (number conversion, but also: inc, neg, not).
anim.abc_fade_in = (int) ((long) anim.abc_fade_in);
+ System.out.println("42");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
new file mode 100644
index 0000000..b3a1579
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByNative;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsedByNativeAnnotationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepUsedByNativeAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), isAbsent());
+ assertThat(inspector.clazz(A.class).method("void", "bar"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int"), isAbsent());
+ }
+
+ @UsedByNative(
+ description = "Ensure that the class A remains as we are assuming the contents of its name.",
+ preconditions = {@KeepCondition(classConstant = A.class, methodName = "foo")},
+ // The kind will default to ONLY_CLASS, so setting this to include members will keep the
+ // otherwise unused bar method.
+ kind = KeepItemKind.CLASS_AND_MEMBERS)
+ static class A {
+
+ public void foo() throws Exception {
+ Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B"));
+ clazz.getDeclaredMethod("bar").invoke(clazz);
+ }
+
+ public void bar() {
+ // Unused but kept by the annotation.
+ }
+ }
+
+ static class B {
+
+ @UsedByNative(
+ // Only if A.foo is live do we need to keep this.
+ preconditions = {@KeepCondition(classConstant = A.class, methodName = "foo")},
+ // Both the class and method are reflectively accessed.
+ kind = KeepItemKind.CLASS_AND_MEMBERS)
+ public static void bar() {
+ System.out.println("Hello, world");
+ }
+
+ public static void bar(int ignore) {
+ throw new RuntimeException("UNUSED");
+ }
+ }
+
+ static class C {
+ // Unused.
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
new file mode 100644
index 0000000..c8ea937
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2023, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsedByReflectionAnnotationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepUsedByReflectionAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), isAbsent());
+ assertThat(inspector.clazz(B.class).method("void", "bar"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int"), isAbsent());
+ }
+
+ @UsedByReflection(
+ description = "Ensure that the class A remains as we are assuming the contents of its name.")
+ static class A {
+
+ public void foo() throws Exception {
+ Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B"));
+ clazz.getDeclaredMethod("bar").invoke(clazz);
+ }
+ }
+
+ static class B {
+
+ @UsedByReflection(
+ // Only if A.foo is live do we need to keep this.
+ preconditions = {@KeepCondition(classConstant = A.class, methodName = "foo")},
+ // Both the class and method are reflectively accessed.
+ kind = KeepItemKind.CLASS_AND_MEMBERS)
+ public static void bar() {
+ System.out.println("Hello, world");
+ }
+
+ public static void bar(int ignore) {
+ throw new RuntimeException("UNUSED");
+ }
+ }
+
+ static class C {
+ // Unused.
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/locale/TurkishLocaleMultiReleaseJarTest.java b/src/test/java/com/android/tools/r8/locale/TurkishLocaleMultiReleaseJarTest.java
new file mode 100644
index 0000000..48f8121
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/locale/TurkishLocaleMultiReleaseJarTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, 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.locale;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TurkishLocaleMultiReleaseJarTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ Path workingDir = temp.getRoot().toPath();
+ ProcessResult result =
+ ToolHelper.forkJavaWithJavaOptions(
+ workingDir,
+ ImmutableList.of("-Duser.language=tr"),
+ D8.class,
+ ImmutableList.of(
+ "--min-api",
+ Integer.toString(parameters.getApiLevel().getLevel()),
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.U).toAbsolutePath().toString(),
+ buildMultiReleaseJarWithUpperCaseMetaInf(workingDir).toAbsolutePath().toString()));
+ assertEquals(0, result.exitCode);
+ runArtOnClassesDotDex(workingDir);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Path workingDir = temp.getRoot().toPath();
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.U).toAbsolutePath().toString(),
+ "--pg-conf",
+ FileUtils.writeTextFile(temp.newFile("test.pro").toPath(), "-keep class * { *; }")
+ .toAbsolutePath()
+ .toString(),
+ buildMultiReleaseJarWithUpperCaseMetaInf(workingDir).toAbsolutePath().toString());
+ if (parameters.isCfRuntime()) {
+ builder.add("--classfile");
+ } else {
+ builder.add("--min-api", Integer.toString(parameters.getApiLevel().getLevel()));
+ }
+
+ ProcessResult result =
+ ToolHelper.forkJavaWithJavaOptions(
+ workingDir, ImmutableList.of("-Duser.language=tr"), R8.class, builder.build());
+ assertEquals(0, result.exitCode);
+ runArtOnClassesDotDex(workingDir);
+ }
+
+ private Path buildMultiReleaseJarWithUpperCaseMetaInf(Path dir) throws Exception {
+ // Compiler will to check String.toLowerCase() of zip entries.
+ Path jar = dir.resolve("test.jar");
+ ZipBuilder.builder(jar)
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(TestClass.class))
+ .addFile(
+ Paths.get("META-INF/versions/9")
+ .resolve(
+ ToolHelper.getClassPathForTests()
+ .relativize(ToolHelper.getClassFileForTestClass(TestClass.class)))
+ .toString(),
+ ToolHelper.getClassFileForTestClass(TestClass.class))
+ .build();
+ return jar;
+ }
+
+ private void runArtOnClassesDotDex(Path dir) throws Exception {
+ if (parameters.getRuntime().isCf()) {
+ return;
+ }
+ ArtCommandBuilder builder = new ArtCommandBuilder(parameters.getRuntime().asDex().getVm());
+ builder.appendClasspath(dir.resolve("classes.dex").toAbsolutePath().toString());
+ builder.setMainClass(TestClass.class.getTypeName());
+ String stdout = ToolHelper.runArt(builder);
+ assertEquals(StringUtils.lines("Hello, world!"), stdout);
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/locale/TurkishLocaleZipFileInputTest.java b/src/test/java/com/android/tools/r8/locale/TurkishLocaleZipFileInputTest.java
new file mode 100644
index 0000000..4231616
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/locale/TurkishLocaleZipFileInputTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, 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.locale;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TurkishLocaleZipFileInputTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ Path workingDir = temp.getRoot().toPath();
+ ProcessResult result =
+ ToolHelper.forkJavaWithJavaOptions(
+ workingDir,
+ // See b/281774632 for context.
+ ImmutableList.of("-Duser.language=tr"),
+ D8.class,
+ ImmutableList.of(
+ "--min-api",
+ Integer.toString(parameters.getApiLevel().getLevel()),
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.U).toAbsolutePath().toString(),
+ buildZipWithUpperCaseExtension(workingDir).toAbsolutePath().toString()));
+ assertEquals(0, result.exitCode);
+ runArtOnClassesDotDex(workingDir);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Path workingDir = temp.getRoot().toPath();
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.U).toAbsolutePath().toString(),
+ "--pg-conf",
+ FileUtils.writeTextFile(temp.newFile("test.pro").toPath(), "-keep class * { *; }")
+ .toAbsolutePath()
+ .toString(),
+ buildZipWithUpperCaseExtension(workingDir).toAbsolutePath().toString());
+ if (parameters.isCfRuntime()) {
+ builder.add("--classfile");
+ } else {
+ builder.add("--min-api", Integer.toString(parameters.getApiLevel().getLevel()));
+ }
+
+ ProcessResult result =
+ ToolHelper.forkJavaWithJavaOptions(
+ workingDir,
+ // See b/281774632 for context.
+ ImmutableList.of("-Duser.language=tr"),
+ R8.class,
+ builder.build());
+ assertEquals(0, result.exitCode);
+ runArtOnClassesDotDex(workingDir);
+ }
+
+ private Path buildZipWithUpperCaseExtension(Path dir) throws Exception {
+ // Compiler will to check String.toLowerCase() of file extensions.
+ Path jar = dir.resolve("test.ZIP");
+ ZipBuilder.builder(jar)
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(TestClass.class))
+ .build();
+ return jar;
+ }
+
+ private void runArtOnClassesDotDex(Path dir) throws Exception {
+ if (parameters.getRuntime().isCf()) {
+ return;
+ }
+ ArtCommandBuilder builder = new ArtCommandBuilder(parameters.getRuntime().asDex().getVm());
+ builder.appendClasspath(dir.resolve("classes.dex").toAbsolutePath().toString());
+ builder.setMainClass(TestClass.class.getTypeName());
+ String stdout = ToolHelper.runArt(builder);
+ assertEquals(StringUtils.lines("Hello, world!"), stdout);
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index dc61ce0..2d73302 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.DataResourceConsumerForTesting;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
@@ -173,9 +174,8 @@
AdaptResourceFileContentsTestClass.B.class),
getProguardConfig(true, null),
null,
- getDataResources()
- .stream()
- .filter(x -> !x.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION))
+ getDataResources().stream()
+ .filter(x -> !StringUtils.toLowerCase(x.getName()).endsWith(FileUtils.CLASS_EXTENSION))
.collect(Collectors.toList()));
// Visit each of the resources in the jar and check that their contents are as expected.
diff --git a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
index 6fa54e4..310fe36 100644
--- a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
@@ -58,7 +59,9 @@
.inspect(
inspector -> {
String finalName = Main.class.getPackage().getName() + "." + FINAL_CLASS_NAME;
- assertEquals(finalName.toLowerCase(), Main.class.getTypeName().toLowerCase());
+ assertEquals(
+ StringUtils.toLowerCase(finalName),
+ StringUtils.toLowerCase(Main.class.getTypeName()));
if (dontUseMixedCase) {
assertNotEquals(finalName, inspector.clazz(A.class).getFinalName());
} else {
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
index 839c96d..0c2c31d 100644
--- a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.io.IOException;
import java.nio.file.Path;
@@ -76,10 +77,12 @@
ClassSubject bSubject = inspector.clazz(A.class);
if (dontUseMixedCase) {
assertNotEquals(
- aSubject.getFinalName().toLowerCase(), bSubject.getFinalName().toLowerCase());
+ StringUtils.toLowerCase(aSubject.getFinalName()),
+ StringUtils.toLowerCase(bSubject.getFinalName()));
} else {
assertEquals(
- aSubject.getFinalName().toLowerCase(), bSubject.getFinalName().toLowerCase());
+ StringUtils.toLowerCase(aSubject.getFinalName()),
+ StringUtils.toLowerCase(bSubject.getFinalName()));
}
});
}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 9f82884..3644aa8 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -72,6 +72,7 @@
import com.android.tools.r8.retrace.stacktraces.OverloadSameLineTest;
import com.android.tools.r8.retrace.stacktraces.OverloadedWithAndWithoutRangeStackTrace;
import com.android.tools.r8.retrace.stacktraces.PreambleLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ResidualSignatureOnOuterFrameStackTrace;
import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
import com.android.tools.r8.retrace.stacktraces.SingleLineNoLineNumberStackTrace;
import com.android.tools.r8.retrace.stacktraces.SourceFileNameSynthesizeStackTrace;
@@ -437,6 +438,11 @@
}
@Test
+ public void testResidualSignatureOnOuterFrameStackTrace() throws Exception {
+ runRetraceTest(new ResidualSignatureOnOuterFrameStackTrace());
+ }
+
+ @Test
public void testMapVersionWarningStackTrace() throws Exception {
// TODO(b/204289928): Internalize the diagnostics checking.
assumeFalse(external);
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ResidualSignatureOnOuterFrameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ResidualSignatureOnOuterFrameStackTrace.java
new file mode 100644
index 0000000..f464a62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ResidualSignatureOnOuterFrameStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2023, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collections;
+import java.util.List;
+
+/** This is a reproduction of b/283837159 */
+public class ResidualSignatureOnOuterFrameStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Collections.singletonList("\tat mapping.g(SourceFile)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.joinLines(
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+ "kotlinx.coroutines.BuildersKt -> mapping:",
+ " 1:1:void pruned.class.method(kotlinx.coroutines.CoroutineScope):10:10 -> g",
+ " 2:2:void pruned.class.method(kotlinx.coroutines.CoroutineScope):0:0 -> g",
+ " 2:2:void pruned.class.method(kotlinx.coroutines.CoroutineScope):0 -> g",
+ // The residual signature should be placed on the first mapped range.
+ " # {'id':'com.android.tools.r8.residualsignature', 'signature':'(LX;)V'}",
+ " 3:3:void pruned.class.method(kotlinx.coroutines.CoroutineScope):30:30 -> g");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Collections.singletonList("\tat pruned.class.method(class.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Collections.singletonList(
+ "\tat pruned.class.void method(kotlinx.coroutines.CoroutineScope)(class.java)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 223140b..1dba3c9 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
@@ -119,7 +120,7 @@
Paths.get(
EXAMPLES_DIR,
"shaking1",
- "print-mapping-" + backend.name().toLowerCase() + ".ref")),
+ "print-mapping-" + StringUtils.toLowerCase(backend.name()) + ".ref")),
StandardCharsets.UTF_8);
assertEquals(sorted(refMapping), sorted(actualMapping));
});
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index f6f59dd..b012f3d 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -86,7 +86,7 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
whyAreYouKeepingConsumer.printWhyAreYouKeeping(fooClassRef, new PrintStream(baos));
assertThat(
- baos.toString().replace(getClass().getTypeName(), "<test>").toLowerCase(),
+ StringUtils.toLowerCase(baos.toString().replace(getClass().getTypeName(), "<test>")),
not(anyOf(containsString("cyclic"), containsString("cycle"))));
// The only root should be the keep main-method rule.
diff --git a/src/test/java/com/android/tools/r8/smali/BinopLiteralTest.java b/src/test/java/com/android/tools/r8/smali/BinopLiteralTest.java
index afd71b9..798fe0c 100644
--- a/src/test/java/com/android/tools/r8/smali/BinopLiteralTest.java
+++ b/src/test/java/com/android/tools/r8/smali/BinopLiteralTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.code.DexConst16;
import com.android.tools.r8.dex.code.DexFormat22b;
@@ -14,6 +15,7 @@
import com.android.tools.r8.dex.code.DexReturn;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.AndroidApp;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -46,6 +48,11 @@
Short.MAX_VALUE,
};
+ protected AndroidApp processApplication(AndroidApp application)
+ throws CompilationFailedException {
+ return processApplication(application, opt -> opt.testing.enableBinopOptimization = false);
+ }
+
@Test
public void lit8PassthroughTest() {
List<String> lit8Binops = Arrays.asList(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 2e2e926..23684e1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -24,6 +24,7 @@
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.CfLogicalBinop;
import com.android.tools.r8.cf.code.CfMonitor;
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.cf.code.CfNewArray;
@@ -39,6 +40,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.MonitorType;
+import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
import java.util.Iterator;
import org.objectweb.asm.Opcodes;
@@ -337,6 +339,20 @@
}
@Override
+ public boolean isIntOrLongArithmeticBinop() {
+ return instruction instanceof CfArithmeticBinop
+ && (((CfArithmeticBinop) instruction).getType() == NumericType.INT
+ || ((CfArithmeticBinop) instruction).getType() == NumericType.LONG);
+ }
+
+ @Override
+ public boolean isIntOrLongLogicalBinop() {
+ return instruction instanceof CfLogicalBinop
+ && (((CfLogicalBinop) instruction).getType() == NumericType.INT
+ || ((CfLogicalBinop) instruction).getType() == NumericType.LONG);
+ }
+
+ @Override
public boolean isMultiplication() {
if (!(instruction instanceof CfArithmeticBinop)) {
return false;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 7513e06..16a41b1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -4,6 +4,12 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.dex.code.DexAddInt;
+import com.android.tools.r8.dex.code.DexAddInt2Addr;
+import com.android.tools.r8.dex.code.DexAddIntLit16;
+import com.android.tools.r8.dex.code.DexAddIntLit8;
+import com.android.tools.r8.dex.code.DexAddLong;
+import com.android.tools.r8.dex.code.DexAddLong2Addr;
import com.android.tools.r8.dex.code.DexAget;
import com.android.tools.r8.dex.code.DexAgetBoolean;
import com.android.tools.r8.dex.code.DexAgetByte;
@@ -11,6 +17,12 @@
import com.android.tools.r8.dex.code.DexAgetObject;
import com.android.tools.r8.dex.code.DexAgetShort;
import com.android.tools.r8.dex.code.DexAgetWide;
+import com.android.tools.r8.dex.code.DexAndInt;
+import com.android.tools.r8.dex.code.DexAndInt2Addr;
+import com.android.tools.r8.dex.code.DexAndIntLit16;
+import com.android.tools.r8.dex.code.DexAndIntLit8;
+import com.android.tools.r8.dex.code.DexAndLong;
+import com.android.tools.r8.dex.code.DexAndLong2Addr;
import com.android.tools.r8.dex.code.DexAput;
import com.android.tools.r8.dex.code.DexAputBoolean;
import com.android.tools.r8.dex.code.DexAputByte;
@@ -31,6 +43,12 @@
import com.android.tools.r8.dex.code.DexConstWide16;
import com.android.tools.r8.dex.code.DexConstWide32;
import com.android.tools.r8.dex.code.DexConstWideHigh16;
+import com.android.tools.r8.dex.code.DexDivInt;
+import com.android.tools.r8.dex.code.DexDivInt2Addr;
+import com.android.tools.r8.dex.code.DexDivIntLit16;
+import com.android.tools.r8.dex.code.DexDivIntLit8;
+import com.android.tools.r8.dex.code.DexDivLong;
+import com.android.tools.r8.dex.code.DexDivLong2Addr;
import com.android.tools.r8.dex.code.DexGoto;
import com.android.tools.r8.dex.code.DexIfEq;
import com.android.tools.r8.dex.code.DexIfEqz;
@@ -87,7 +105,19 @@
import com.android.tools.r8.dex.code.DexNewArray;
import com.android.tools.r8.dex.code.DexNewInstance;
import com.android.tools.r8.dex.code.DexNop;
+import com.android.tools.r8.dex.code.DexOrInt;
+import com.android.tools.r8.dex.code.DexOrInt2Addr;
+import com.android.tools.r8.dex.code.DexOrIntLit16;
+import com.android.tools.r8.dex.code.DexOrIntLit8;
+import com.android.tools.r8.dex.code.DexOrLong;
+import com.android.tools.r8.dex.code.DexOrLong2Addr;
import com.android.tools.r8.dex.code.DexPackedSwitch;
+import com.android.tools.r8.dex.code.DexRemInt;
+import com.android.tools.r8.dex.code.DexRemInt2Addr;
+import com.android.tools.r8.dex.code.DexRemIntLit16;
+import com.android.tools.r8.dex.code.DexRemIntLit8;
+import com.android.tools.r8.dex.code.DexRemLong;
+import com.android.tools.r8.dex.code.DexRemLong2Addr;
import com.android.tools.r8.dex.code.DexReturn;
import com.android.tools.r8.dex.code.DexReturnObject;
import com.android.tools.r8.dex.code.DexReturnVoid;
@@ -98,6 +128,16 @@
import com.android.tools.r8.dex.code.DexSgetObject;
import com.android.tools.r8.dex.code.DexSgetShort;
import com.android.tools.r8.dex.code.DexSgetWide;
+import com.android.tools.r8.dex.code.DexShlInt;
+import com.android.tools.r8.dex.code.DexShlInt2Addr;
+import com.android.tools.r8.dex.code.DexShlIntLit8;
+import com.android.tools.r8.dex.code.DexShlLong;
+import com.android.tools.r8.dex.code.DexShlLong2Addr;
+import com.android.tools.r8.dex.code.DexShrInt;
+import com.android.tools.r8.dex.code.DexShrInt2Addr;
+import com.android.tools.r8.dex.code.DexShrIntLit8;
+import com.android.tools.r8.dex.code.DexShrLong;
+import com.android.tools.r8.dex.code.DexShrLong2Addr;
import com.android.tools.r8.dex.code.DexSparseSwitch;
import com.android.tools.r8.dex.code.DexSput;
import com.android.tools.r8.dex.code.DexSputBoolean;
@@ -106,7 +146,22 @@
import com.android.tools.r8.dex.code.DexSputObject;
import com.android.tools.r8.dex.code.DexSputShort;
import com.android.tools.r8.dex.code.DexSputWide;
+import com.android.tools.r8.dex.code.DexSubInt;
+import com.android.tools.r8.dex.code.DexSubInt2Addr;
+import com.android.tools.r8.dex.code.DexSubLong;
+import com.android.tools.r8.dex.code.DexSubLong2Addr;
import com.android.tools.r8.dex.code.DexThrow;
+import com.android.tools.r8.dex.code.DexUshrInt;
+import com.android.tools.r8.dex.code.DexUshrInt2Addr;
+import com.android.tools.r8.dex.code.DexUshrIntLit8;
+import com.android.tools.r8.dex.code.DexUshrLong;
+import com.android.tools.r8.dex.code.DexUshrLong2Addr;
+import com.android.tools.r8.dex.code.DexXorInt;
+import com.android.tools.r8.dex.code.DexXorInt2Addr;
+import com.android.tools.r8.dex.code.DexXorIntLit16;
+import com.android.tools.r8.dex.code.DexXorIntLit8;
+import com.android.tools.r8.dex.code.DexXorLong;
+import com.android.tools.r8.dex.code.DexXorLong2Addr;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.SingleConstant;
@@ -445,6 +500,73 @@
return instruction instanceof DexSparseSwitch;
}
+ public boolean isIntOrLongArithmeticBinop() {
+ return instruction instanceof DexMulInt
+ || instruction instanceof DexMulIntLit8
+ || instruction instanceof DexMulIntLit16
+ || instruction instanceof DexMulInt2Addr
+ || instruction instanceof DexMulLong
+ || instruction instanceof DexMulLong2Addr
+ || instruction instanceof DexAddInt
+ || instruction instanceof DexAddIntLit8
+ || instruction instanceof DexAddIntLit16
+ || instruction instanceof DexAddInt2Addr
+ || instruction instanceof DexAddLong
+ || instruction instanceof DexAddLong2Addr
+ || instruction instanceof DexSubInt
+ || instruction instanceof DexSubInt2Addr
+ || instruction instanceof DexSubLong
+ || instruction instanceof DexSubLong2Addr
+ || instruction instanceof DexDivInt
+ || instruction instanceof DexDivIntLit8
+ || instruction instanceof DexDivIntLit16
+ || instruction instanceof DexDivInt2Addr
+ || instruction instanceof DexDivLong
+ || instruction instanceof DexDivLong2Addr
+ || instruction instanceof DexRemInt
+ || instruction instanceof DexRemIntLit8
+ || instruction instanceof DexRemIntLit16
+ || instruction instanceof DexRemInt2Addr
+ || instruction instanceof DexRemLong
+ || instruction instanceof DexRemLong2Addr;
+ }
+
+ public boolean isIntOrLongLogicalBinop() {
+ return instruction instanceof DexAndInt
+ || instruction instanceof DexAndIntLit8
+ || instruction instanceof DexAndIntLit16
+ || instruction instanceof DexAndInt2Addr
+ || instruction instanceof DexAndLong
+ || instruction instanceof DexAndLong2Addr
+ || instruction instanceof DexOrInt
+ || instruction instanceof DexOrIntLit8
+ || instruction instanceof DexOrIntLit16
+ || instruction instanceof DexOrInt2Addr
+ || instruction instanceof DexOrLong
+ || instruction instanceof DexOrLong2Addr
+ || instruction instanceof DexXorInt
+ || instruction instanceof DexXorIntLit8
+ || instruction instanceof DexXorIntLit16
+ || instruction instanceof DexXorInt2Addr
+ || instruction instanceof DexXorLong
+ || instruction instanceof DexXorLong2Addr
+ || instruction instanceof DexShrInt
+ || instruction instanceof DexShrIntLit8
+ || instruction instanceof DexShrInt2Addr
+ || instruction instanceof DexShrLong
+ || instruction instanceof DexShrLong2Addr
+ || instruction instanceof DexShlInt
+ || instruction instanceof DexShlIntLit8
+ || instruction instanceof DexShlInt2Addr
+ || instruction instanceof DexShlLong
+ || instruction instanceof DexShlLong2Addr
+ || instruction instanceof DexUshrInt
+ || instruction instanceof DexUshrIntLit8
+ || instruction instanceof DexUshrInt2Addr
+ || instruction instanceof DexUshrLong
+ || instruction instanceof DexUshrLong2Addr;
+ }
+
@Override
public boolean isMultiplication() {
return instruction instanceof DexMulInt
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 05bd760..7fd166e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -140,6 +140,10 @@
boolean isSparseSwitch();
+ boolean isIntOrLongArithmeticBinop();
+
+ boolean isIntOrLongLogicalBinop();
+
boolean isMultiplication();
boolean isNewArray();
diff --git a/tools/archive.py b/tools/archive.py
index 558842c..27c3b25 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -166,6 +166,7 @@
utils.R8RETRACE,
utils.R8RETRACE_NO_DEPS,
utils.LIBRARY_DESUGAR_CONVERSIONS,
+ utils.KEEPANNO_ANNOTATIONS_TARGET,
'-Pno_internal'
])
@@ -238,6 +239,7 @@
utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL_MAVEN_ZIP,
utils.DESUGAR_CONFIGURATION_JDK11_MAVEN_ZIP,
utils.DESUGAR_CONFIGURATION_JDK11_NIO_MAVEN_ZIP,
+ utils.KEEPANNO_ANNOTATIONS_JAR,
utils.GENERATED_LICENSE,
]:
file_name = os.path.basename(file)
diff --git a/tools/utils.py b/tools/utils.py
index 5cb2cbe..b691b16 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -51,6 +51,7 @@
R8_TESTS_DEPS_TARGET = 'RepackageTestDeps'
R8LIB_TESTS_TARGET = 'configureTestForR8Lib'
R8LIB_TESTS_DEPS_TARGET = R8_TESTS_DEPS_TARGET
+KEEPANNO_ANNOTATIONS_TARGET = 'keepAnnoJar'
ALL_DEPS_JAR = os.path.join(LIBS, 'deps_all.jar')
R8_JAR = os.path.join(LIBS, 'r8.jar')
@@ -70,6 +71,7 @@
MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP = os.path.join(LIBS, 'library_desugar_conversions_legacy.jar')
LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.jar')
+KEEPANNO_ANNOTATIONS_JAR = os.path.join(LIBS, 'keepanno-annotations.jar')
DESUGAR_CONFIGURATION = os.path.join(
'src', 'library_desugar', 'desugar_jdk_libs.json')