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')