Merge commit '9881674a871f9b2b9650c1af6119386d72b9a5c0' into dev-release Change-Id: I2443c14cb168e2979ca36e696ce7463171be2643
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt index 1f748ae..b54edc3 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -403,6 +403,7 @@ const val kotlinMetadataVersion = "2.0.0" const val mockito = "2.10.0" const val smaliVersion = "3.0.3" + const val protobufVersion = "3.19.3" } object Deps { @@ -422,6 +423,7 @@ val mockito by lazy { "org.mockito:mockito-core:${Versions.mockito}" } val smali by lazy { "com.android.tools.smali:smali:${Versions.smaliVersion}" } val smaliUtil by lazy { "com.android.tools.smali:smali-util:${Versions.smaliVersion}" } + val protobuf by lazy { "com.google.protobuf:protobuf-java:${Versions.protobufVersion}" } } object ThirdPartyDeps {
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts index a747444..f7c128b 100644 --- a/d8_r8/keepanno/build.gradle.kts +++ b/d8_r8/keepanno/build.gradle.kts
@@ -51,7 +51,7 @@ dependencies { compileOnly(Deps.asm) compileOnly(Deps.guava) - implementation("com.google.protobuf:protobuf-java:3.19.3") + compileOnly(Deps.protobuf) } tasks {
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts index fd3282d..c6e6cd7 100644 --- a/d8_r8/main/build.gradle.kts +++ b/d8_r8/main/build.gradle.kts
@@ -47,6 +47,7 @@ compileOnly(Deps.gson) compileOnly(Deps.guava) compileOnly(Deps.kotlinMetadata) + compileOnly(Deps.protobuf) errorprone(Deps.errorprone) } @@ -223,7 +224,7 @@ } exclude("META-INF/*.kotlin_module") exclude("**/*.kotlin_metadata") - exclude("keepanno.proto") + exclude("keepspec.proto") destinationDirectory.set(getRoot().resolveAll("build", "libs")) archiveFileName.set("r8-full-exclude-deps.jar") } @@ -268,6 +269,7 @@ exclude("README.md") exclude("javax/annotation/**") exclude("wireless/**") + exclude("google/protobuf/**") duplicatesStrategy = DuplicatesStrategy.EXCLUDE archiveFileName.set("deps.jar") }
diff --git a/d8_r8/resourceshrinker/build.gradle.kts b/d8_r8/resourceshrinker/build.gradle.kts index db14c2f..3016524 100644 --- a/d8_r8/resourceshrinker/build.gradle.kts +++ b/d8_r8/resourceshrinker/build.gradle.kts
@@ -36,9 +36,9 @@ dependencies { compileOnly(Deps.asm) compileOnly(Deps.guava) + compileOnly(Deps.protobuf) compileOnly(files(resolve(ThirdPartyDeps.r8, "r8lib_8.2.20-dev.jar"))) implementation("com.android.tools.build:aapt2-proto:8.2.0-alpha10-10154469") - implementation("com.google.protobuf:protobuf-java:3.19.3") implementation("com.android.tools.layoutlib:layoutlib-api:31.5.0-alpha04") implementation("com.android.tools:common:31.5.0-alpha04") implementation("com.android.tools:sdk-common:31.5.0-alpha04")
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java index 5981336..0c86c6c 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypePatternArray; import com.google.common.base.Strings; import java.util.Objects; @@ -78,4 +79,19 @@ public String toString() { return baseType + Strings.repeat("[]", dimensions); } + + public static KeepArrayTypePattern fromProto(TypePatternArray array) { + KeepTypePattern baseType = + array.hasBaseType() + ? KeepTypePattern.fromProto(array.getBaseType()) + : KeepTypePattern.any(); + int dimensions = Math.max(1, array.getDimensions()); + return new KeepArrayTypePattern(baseType, dimensions); + } + + public TypePatternArray.Builder buildProto() { + return TypePatternArray.newBuilder() + .setDimensions(dimensions) + .setBaseType(baseType.buildProto()); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java index 481a6f2..a8df7aa 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.keepanno.ast; import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.BindingReference; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -85,4 +86,8 @@ public final int hashCode() { return Objects.hash(isClassType(), name); } + + public BindingReference.Builder buildProto() { + return BindingReference.newBuilder().setName(name.toString()); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java index 4df258d..28ff0b1 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
@@ -3,9 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Bindings; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -85,6 +88,17 @@ .collect(Collectors.joining(", ")); } + public KeepSpecProtos.Bindings.Builder buildProto() { + KeepSpecProtos.Bindings.Builder builder = KeepSpecProtos.Bindings.newBuilder(); + bindings.forEach( + ((symbol, binding) -> + builder.addBindings( + KeepSpecProtos.Binding.newBuilder() + .setName(symbol.toString()) + .setItem(binding.getItem().buildItemProto())))); + return builder; + } + /** * A unique binding. * @@ -190,11 +204,53 @@ private final Map<String, KeepBindingSymbol> reserved = new HashMap<>(); private final Map<KeepBindingSymbol, KeepItemPattern> bindings = new IdentityHashMap<>(); + public Builder applyProto(Bindings proto) { + List<KeepSpecProtos.Binding> protoList = proto.getBindingsList(); + // The structure of keep edges and checks requires at least one consequent/check item. + // Thus, we should never be building empty binding lists, but the code is not incorrect. + assert !protoList.isEmpty(); + + // Two pass build. + // First pass validates and allocates symbols for each binding. + for (KeepSpecProtos.Binding binding : protoList) { + String protoName = binding.getName(); + if (protoName.isEmpty()) { + throw new KeepEdgeException("Invalid binding to empty name"); + } + create(protoName); + } + // Second pass constructs the items which may themselves have references to symbols. + for (KeepSpecProtos.Binding binding : protoList) { + KeepBindingSymbol symbol = reserved.get(binding.getName()); + // We can only create a binding for an item that is present. + if (binding.hasItem()) { + KeepItemPattern itemPattern = + KeepItemPattern.fromItemProto(binding.getItem(), null, reserved::get); + // It also must be a class/member kind. + if (itemPattern != null) { + addBinding(symbol, itemPattern); + } + } + } + // We expect the bindings to have been read (a format change could invalidate this). + assert bindings.size() == protoList.size(); + return this; + } + public KeepBindingSymbol generateFreshSymbol(String hint) { // Allocate a fresh non-forgeable symbol. The actual name is chosen at build time. return new KeepBindingSymbol(hint); } + public KeepBindingReference getBindingReferenceForUserBinding(String name) { + KeepBindingSymbol symbol = reserved.get(name); + if (symbol == null) { + throw new KeepEdgeException("Undefined binding for name '" + name + "'"); + } + KeepItemPattern item = getItemForBinding(symbol); + return KeepBindingReference.forItem(symbol, item); + } + public KeepBindingSymbol create(String name) { KeepBindingSymbol symbol = new KeepBindingSymbol(name); KeepBindingSymbol old = reserved.put(name, symbol);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCheck.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCheck.java index d29459f..c59fe13 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCheck.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCheck.java
@@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Check; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.CheckKind; + public class KeepCheck extends KeepDeclaration { public enum KeepCheckKind { @@ -17,6 +21,37 @@ private KeepBindings bindings = KeepBindings.none(); private KeepBindingReference itemReference; + public Builder applyProto(KeepSpecProtos.Check proto, KeepSpecVersion version) { + // MetaInfo is optional, but that is handled in its applyProto. + setMetaInfo(KeepEdgeMetaInfo.fromProto(proto.getMetaInfo(), version)); + + switch (proto.getKindValue()) { + case CheckKind.CHECK_OPTIMIZED_OUT_VALUE: + setKind(KeepCheckKind.OPTIMIZED_OUT); + break; + case CheckKind.CHECK_REMOVED_VALUE: + default: + // The kind is not optional, but in the even of a format change we assume removed. + assert proto.getKind() == CheckKind.CHECK_REMOVED; + setKind(KeepCheckKind.REMOVED); + } + + // Bindings are not optional. + if (!proto.hasBindings()) { + throw new KeepEdgeException("Invalid Check, must have valid bindings."); + } + KeepBindings.Builder bindingsBuilder = KeepBindings.builder().applyProto(proto.getBindings()); + setBindings(bindingsBuilder.build()); + + // The check item reference is not optional. + if (!proto.hasItem() || proto.getItem().getName().isEmpty()) { + throw new KeepEdgeException("Invalid check, must have a valid item reference."); + } + setItemReference( + bindingsBuilder.getBindingReferenceForUserBinding(proto.getItem().getName())); + return this; + } + public Builder setMetaInfo(KeepEdgeMetaInfo metaInfo) { this.metaInfo = metaInfo; return this; @@ -98,4 +133,19 @@ public String toString() { return "KeepCheck{kind=" + kind + ", item=" + itemReference + "}"; } + + public static KeepCheck fromCheckProto(Check proto, KeepSpecVersion version) { + return builder().applyProto(proto, version).build(); + } + + public Check.Builder buildCheckProto() { + return Check.newBuilder() + .setMetaInfo(getMetaInfo().buildProto()) + .setBindings(getBindings().buildProto()) + .setItem(itemReference.buildProto()) + .setKind( + kind == KeepCheckKind.REMOVED + ? KeepSpecProtos.CheckKind.CHECK_REMOVED + : KeepSpecProtos.CheckKind.CHECK_OPTIMIZED_OUT); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java index a051b62..8d27307 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.ClassItemPattern; import java.util.Collection; import java.util.Collections; import java.util.Objects; @@ -18,6 +19,16 @@ return new Builder(); } + public static KeepClassItemPattern fromClassProto(ClassItemPattern proto) { + return builder().applyProto(proto).build(); + } + + public ClassItemPattern.Builder buildClassProto() { + // TODO(b/343389186): Add instance-of. + // TODO(b/343389186): Add annotated-by. + return ClassItemPattern.newBuilder().setClassName(classNamePattern.buildProto()); + } + public static class Builder { private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any(); @@ -27,6 +38,17 @@ private Builder() {} + public Builder applyProto(ClassItemPattern protoItem) { + assert classNamePattern.isAny(); + if (protoItem.hasClassName()) { + setClassNamePattern(KeepQualifiedClassNamePattern.fromProto(protoItem.getClassName())); + } + + // TODO(b/343389186): Add instance-of. + // TODO(b/343389186): Add annotated-by. + return this; + } + public Builder copyFrom(KeepClassItemPattern pattern) { return setClassNamePattern(pattern.getClassNamePattern()) .setInstanceOfPattern(pattern.getInstanceOfPattern())
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java index e80bfbd..12d1d31 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
@@ -3,10 +3,12 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Declaration; import java.util.function.Consumer; import java.util.function.Function; -/** Base class for the declarations represented in the keep annoations library. */ +/** Base class for the declarations represented in the keep annotations library. */ public abstract class KeepDeclaration { public abstract KeepEdgeMetaInfo getMetaInfo(); @@ -47,4 +49,22 @@ public final int hashCode() { throw new RuntimeException(); } + + public final Declaration.Builder buildDeclarationProto() { + Declaration.Builder builder = Declaration.newBuilder(); + return apply( + edge -> builder.setEdge(edge.buildEdgeProto()), + check -> builder.setCheck(check.buildCheckProto())); + } + + public static KeepDeclaration fromProto( + KeepSpecProtos.Declaration declaration, KeepSpecVersion version) { + if (declaration.hasEdge()) { + return KeepEdge.builder().applyProto(declaration.getEdge(), version).build(); + } + if (declaration.hasCheck()) { + return KeepCheck.builder().applyProto(declaration.getCheck(), version).build(); + } + return null; + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java index 6a741ce..3060010 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -3,7 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; -import com.android.tools.r8.keepanno.proto.KeepAnnoProtos; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Edge; +import com.android.tools.r8.keepanno.utils.Unimplemented; /** * An edge in the keep graph. @@ -146,8 +147,9 @@ private Builder() {} - public Builder applyProto(KeepAnnoProtos.Edge edge) { + public Builder applyProto(Edge edge, KeepSpecVersion version) { // TODO(b/343389186): implement this. + KeepEdgeMetaInfo.builder().applyProto(edge.getMetaInfo(), version).build(); return this; } @@ -264,7 +266,8 @@ + '}'; } - public void buildProto(KeepAnnoProtos.Edge.Builder builder) { - // TODO(b/343389186): implement this. + public Edge.Builder buildEdgeProto() { + Edge.newBuilder().setMetaInfo(getMetaInfo().buildProto()); + throw new Unimplemented(); } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java index 9cf3305..1377a05 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
@@ -3,24 +3,24 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import static com.android.tools.r8.keepanno.ast.KeepSpecUtils.desc; + import com.android.tools.r8.keepanno.keeprules.RulePrintingUtils; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Context; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.FieldDesc; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MetaInfo; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MethodDesc; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypeDesc; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import org.objectweb.asm.Type; public class KeepEdgeMetaInfo { - public enum KeepEdgeVersion { - UNKNOWN; - - public String toVersionString() { - return name(); - } - } - private static final KeepEdgeMetaInfo NONE = new KeepEdgeMetaInfo( - KeepEdgeVersion.UNKNOWN, KeepEdgeContext.none(), KeepEdgeDescription.empty()); + KeepSpecVersion.UNKNOWN, KeepEdgeContext.none(), KeepEdgeDescription.empty()); public static KeepEdgeMetaInfo none() { return NONE; @@ -30,12 +30,12 @@ return new Builder(); } - private final KeepEdgeVersion version; + private final KeepSpecVersion version; private final KeepEdgeContext context; private final KeepEdgeDescription description; private KeepEdgeMetaInfo( - KeepEdgeVersion version, KeepEdgeContext context, KeepEdgeDescription description) { + KeepSpecVersion version, KeepEdgeContext context, KeepEdgeDescription description) { this.version = version; this.context = context; this.description = description; @@ -58,10 +58,10 @@ } public boolean hasVersion() { - return version != KeepEdgeVersion.UNKNOWN; + return version != KeepSpecVersion.UNKNOWN; } - public KeepEdgeVersion getVersion() { + public KeepSpecVersion getVersion() { return version; } @@ -80,30 +80,73 @@ return "MetaInfo{" + String.join(", ", props) + "}"; } + public static KeepEdgeMetaInfo fromProto(MetaInfo proto, KeepSpecVersion version) { + return builder().applyProto(proto, version).build(); + } + + public MetaInfo.Builder buildProto() { + MetaInfo.Builder builder = MetaInfo.newBuilder(); + builder.setContext(context.buildProto()); + if (!description.isEmpty()) { + builder.setDescription(description.description); + } + return builder; + } + public static class Builder { + private KeepSpecVersion version = KeepSpecVersion.UNKNOWN; private KeepEdgeContext context = KeepEdgeContext.none(); private KeepEdgeDescription description = KeepEdgeDescription.empty(); + public Builder applyProto(MetaInfo proto, KeepSpecVersion version) { + // Version is a non-optional value (not part of the proto MetaInfo). + setVersion(version); + // The proto MetaInfo is optional so guard for the null case here. + if (proto != null) { + if (proto.hasContext()) { + setContext(KeepEdgeContext.fromProto(proto.getContext())); + } + setDescription(proto.getDescription()); + } + return this; + } + + public Builder setVersion(KeepSpecVersion version) { + this.version = version; + return this; + } + public Builder setDescription(String description) { - this.description = new KeepEdgeDescription(description); + this.description = + description.isEmpty() ? KeepEdgeDescription.EMPTY : new KeepEdgeDescription(description); + return this; + } + + public Builder setContext(KeepEdgeContext context) { + this.context = context; return this; } public Builder setContextFromClassDescriptor(String classDescriptor) { - context = new KeepEdgeClassContext(classDescriptor); - return this; + return setContext(new KeepEdgeClassContext(classDescriptor)); } public Builder setContextFromMethodDescriptor( String classDescriptor, String methodName, String methodDescriptor) { - context = new KeepEdgeMethodContext(classDescriptor, methodName, methodDescriptor); - return this; + Type methodType = Type.getMethodType(methodDescriptor); + Type[] argumentTypes = methodType.getArgumentTypes(); + List<String> parameters = new ArrayList<>(argumentTypes.length); + for (Type argumentType : argumentTypes) { + parameters.add(argumentType.getDescriptor()); + } + return setContext( + new KeepEdgeMethodContext( + classDescriptor, methodName, methodType.getReturnType().getDescriptor(), parameters)); } public Builder setContextFromFieldDescriptor( String classDescriptor, String fieldName, String fieldType) { - context = new KeepEdgeFieldContext(classDescriptor, fieldName, fieldType); - return this; + return setContext(new KeepEdgeFieldContext(classDescriptor, fieldName, fieldType)); } public KeepEdgeMetaInfo build() { @@ -111,7 +154,7 @@ && description.equals(KeepEdgeDescription.empty())) { return none(); } - return new KeepEdgeMetaInfo(KeepEdgeVersion.UNKNOWN, context, description); + return new KeepEdgeMetaInfo(version, context, description); } } @@ -124,6 +167,32 @@ private KeepEdgeContext() {} + public static KeepEdgeContext fromProto(Context context) { + if (context.hasClassDesc()) { + return new KeepEdgeClassContext(context.getClassDesc().getDesc()); + } + if (context.hasMethodDesc()) { + MethodDesc methodDesc = context.getMethodDesc(); + List<String> parameters = new ArrayList<>(methodDesc.getParameterTypesCount()); + for (TypeDesc typeDesc : methodDesc.getParameterTypesList()) { + parameters.add(typeDesc.getDesc()); + } + return new KeepEdgeMethodContext( + methodDesc.getHolder().getDesc(), + methodDesc.getName(), + methodDesc.getReturnType().getDesc(), + parameters); + } + if (context.hasFieldDesc()) { + FieldDesc fieldDesc = context.getFieldDesc(); + return new KeepEdgeFieldContext( + fieldDesc.getHolder().getDesc(), + fieldDesc.getName(), + fieldDesc.getFieldType().getDesc()); + } + return none(); + } + public String getDescriptorString() { throw new KeepEdgeException("Invalid attempt to get descriptor string from none context"); } @@ -137,6 +206,15 @@ public int hashCode() { return System.identityHashCode(this); } + + public final Context.Builder buildProto() { + return buildProto(Context.newBuilder()); + } + + public Context.Builder buildProto(Context.Builder builder) { + assert this == none(); + return builder; + } } private static class KeepEdgeClassContext extends KeepEdgeContext { @@ -168,26 +246,41 @@ public int hashCode() { return classDescriptor.hashCode(); } + + @Override + public Context.Builder buildProto(Context.Builder builder) { + return builder.setClassDesc(desc(classDescriptor)); + } } private static class KeepEdgeMethodContext extends KeepEdgeContext { private final String classDescriptor; private final String methodName; - private final String methodDescriptor; + private final String methodReturnType; + private final List<String> methodParameters; public KeepEdgeMethodContext( - String classDescriptor, String methodName, String methodDescriptor) { + String classDescriptor, + String methodName, + String methodReturnType, + List<String> methodParameters) { assert classDescriptor != null; assert methodName != null; - assert methodDescriptor != null; + assert methodParameters != null; this.classDescriptor = classDescriptor; this.methodName = methodName; - this.methodDescriptor = methodDescriptor; + this.methodReturnType = methodReturnType; + this.methodParameters = methodParameters; } @Override public String getDescriptorString() { - return classDescriptor + methodName + methodDescriptor; + StringBuilder builder = new StringBuilder(); + builder.append(classDescriptor).append(methodName).append('('); + for (String parameter : methodParameters) { + builder.append(parameter); + } + return builder.append(')').append(methodReturnType).toString(); } @Override @@ -201,12 +294,26 @@ KeepEdgeMethodContext that = (KeepEdgeMethodContext) o; return classDescriptor.equals(that.classDescriptor) && methodName.equals(that.methodName) - && methodDescriptor.equals(that.methodDescriptor); + && methodReturnType.equals(that.methodReturnType) + && methodParameters.equals(that.methodParameters); } @Override public int hashCode() { - return Objects.hash(classDescriptor, methodName, methodDescriptor); + return Objects.hash(classDescriptor, methodName, methodReturnType, methodParameters); + } + + @Override + public Context.Builder buildProto(Context.Builder builder) { + MethodDesc.Builder methodBuilder = + MethodDesc.newBuilder() + .setHolder(desc(classDescriptor)) + .setName(methodName) + .setReturnType(desc(methodReturnType)); + for (String methodParameter : methodParameters) { + methodBuilder.addParameterTypes(desc(methodParameter)); + } + return builder.setMethodDesc(methodBuilder.build()); } } @@ -244,6 +351,16 @@ public int hashCode() { return Objects.hash(classDescriptor, fieldName, fieldType); } + + @Override + public Context.Builder buildProto(Context.Builder builder) { + return builder.setFieldDesc( + FieldDesc.newBuilder() + .setHolder(desc(classDescriptor)) + .setName(fieldName) + .setFieldType(desc(fieldType)) + .build()); + } } private static class KeepEdgeDescription { @@ -261,12 +378,11 @@ } @Override - @SuppressWarnings("EqualsGetClass") public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof KeepEdgeDescription)) { return false; } KeepEdgeDescription that = (KeepEdgeDescription) o; @@ -282,5 +398,9 @@ public String toString() { return description; } + + public boolean isEmpty() { + return description.isEmpty(); + } } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java index f6aab3e..f863c2e 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MemberPatternField; +import com.android.tools.r8.keepanno.utils.Unimplemented; import java.util.Objects; public final class KeepFieldPattern extends KeepMemberPattern { @@ -145,4 +147,8 @@ + typePattern + '}'; } + + public MemberPatternField.Builder buildFieldProto() { + throw new Unimplemented(); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java index f11578e..d08573c 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.ItemPattern; import java.util.Collection; import java.util.function.Consumer; import java.util.function.Function; @@ -10,12 +12,10 @@ /** * A pattern for matching items in the program. * - * <p>An item pattern can be any item, or it can describe a family of classes or a family of members - * on a classes. + * <p>An item pattern can describe a family of classes or a family of members. * * <p>A pattern cannot describe both a class *and* a member of a class. Either it is a pattern on - * classes or it is a pattern on members. The distinction is defined by having a "none" member - * pattern. + * classes or it is a pattern on members. */ public abstract class KeepItemPattern { @@ -54,5 +54,26 @@ Consumer<KeepClassItemPattern> onClass, Consumer<KeepMemberItemPattern> onMember) { apply(AstUtils.toVoidFunction(onClass), AstUtils.toVoidFunction(onMember)); } + + public static KeepItemPattern fromItemProto( + ItemPattern proto, + KeepItemPattern defaultValue, + Function<String, KeepBindingSymbol> getSymbol) { + if (proto.hasClassItem()) { + return KeepClassItemPattern.fromClassProto(proto.getClassItem()); + } + if (proto.hasMemberItem()) { + return KeepMemberItemPattern.fromMemberProto(proto.getMemberItem(), getSymbol); + } + return defaultValue; + } + + public ItemPattern.Builder buildItemProto() { + ItemPattern.Builder builder = ItemPattern.newBuilder(); + match( + clazz -> builder.setClassItem(clazz.buildClassProto()), + member -> builder.setMemberItem(member.buildMemberProto())); + return builder; + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java index 0c22c66..6b3dcc2 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java
@@ -4,9 +4,13 @@ package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.BindingReference; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MemberItemPattern; import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.function.Function; public class KeepMemberItemPattern extends KeepItemPattern { @@ -14,6 +18,17 @@ return new Builder(); } + public static KeepItemPattern fromMemberProto( + MemberItemPattern protoMember, Function<String, KeepBindingSymbol> getSymbol) { + return builder().applyProto(protoMember, getSymbol).build(); + } + + public MemberItemPattern.Builder buildMemberProto() { + return MemberItemPattern.newBuilder() + .setClassReference(classReference.buildProto()) + .setMemberPattern(memberPattern.buildProto()); + } + public static class Builder { private KeepClassBindingReference classReference = null; @@ -21,6 +36,30 @@ private Builder() {} + public Builder applyProto( + MemberItemPattern protoMember, Function<String, KeepBindingSymbol> getSymbol) { + // The class-reference is special as we can't artificially insert an "any class" without + // patching up the bindings. Thus, the following hard fails if the format is missing any + // part of the class-reference structure, both here and in the bindings themselves. + if (!protoMember.hasClassReference()) { + throw new KeepEdgeException("Invalid MemberItemPattern, must have a valid class reference"); + } + BindingReference protoClassRef = protoMember.getClassReference(); + String protoName = protoClassRef.getName(); + KeepBindingSymbol symbol = getSymbol.apply(protoName); + if (symbol == null) { + throw new KeepEdgeException( + "Invalid MemberItemPattern, reference to unbound binding: '" + protoName + "'"); + } + setClassReference(KeepBindingReference.forClass(symbol)); + + assert memberPattern.isAllMembers(); + if (protoMember.hasMemberPattern()) { + setMemberPattern(KeepMemberPattern.fromMemberProto(protoMember.getMemberPattern())); + } + return this; + } + public Builder copyFrom(KeepMemberItemPattern pattern) { return setClassReference(pattern.getClassReference()) .setMemberPattern(pattern.getMemberPattern());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java index dfce0f6..5c784bc 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -3,6 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MemberPattern; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MemberPatternGeneral; +import com.android.tools.r8.keepanno.utils.Unimplemented; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -90,6 +93,10 @@ + accessPattern + '}'; } + + public MemberPatternGeneral.Builder buildGeneralProto() { + throw new Unimplemented(); + } } KeepMemberPattern() {} @@ -145,4 +152,28 @@ AstUtils.toVoidFunction(onFieldMember), AstUtils.toVoidFunction(onMethodMember)); } + + public MemberPattern.Builder buildProto() { + MemberPattern.Builder builder = MemberPattern.newBuilder(); + match( + general -> builder.setGeneralMember(((Some) general).buildGeneralProto()), + field -> builder.setFieldMember(field.buildFieldProto()), + method -> builder.setMethodMember(method.buildMethodProto())); + return builder; + } + + public static KeepMemberPattern fromMemberProto(MemberPattern memberPattern) { + if (memberPattern.hasGeneralMember()) { + // return KeepMemberPattern.memberBuilder().applyProto(memberPattern.getGeneralMember()); + throw new Unimplemented(); + } + if (memberPattern.hasFieldMember()) { + // return KeepFieldPattern.builder().applyProto(memberPattern.getFieldMember()); + throw new Unimplemented(); + } + if (memberPattern.hasMethodMember()) { + return KeepMethodPattern.fromMethodProto(memberPattern.getMethodMember()); + } + return KeepMemberPattern.allMembers(); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java index 2a7061d..e9100ee 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MethodParameterTypesPattern; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.List; @@ -36,6 +37,8 @@ return null; } + public abstract MethodParameterTypesPattern.Builder buildProto(); + public static class Builder { ImmutableList.Builder<KeepTypePattern> parameterPatterns = ImmutableList.builder(); @@ -95,6 +98,15 @@ + parameterPatterns.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")"; } + + @Override + public MethodParameterTypesPattern.Builder buildProto() { + MethodParameterTypesPattern.Builder builder = MethodParameterTypesPattern.newBuilder(); + for (KeepTypePattern parameterPattern : parameterPatterns) { + builder.addTypes(parameterPattern.buildProto()); + } + return builder; + } } private static class Any extends KeepMethodParametersPattern { @@ -123,5 +135,10 @@ public String toString() { return "(...)"; } + + @Override + public MethodParameterTypesPattern.Builder buildProto() { + throw new KeepEdgeException("Attempt to build message of any type encoded as absent."); + } } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java index 38cb49a..504bba8 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MemberPatternMethod; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MethodParameterTypesPattern; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MethodReturnTypePattern; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypePattern; import java.util.Objects; public final class KeepMethodPattern extends KeepMemberPattern { @@ -30,6 +34,42 @@ return this; } + public Builder applyProto(MemberPatternMethod methodMember) { + assert namePattern.isAny(); + if (methodMember.hasName()) { + setNamePattern( + KeepMethodNamePattern.fromStringPattern( + KeepStringPattern.fromProto(methodMember.getName()))); + } + + assert returnTypePattern.isAny(); + if (methodMember.hasReturnType()) { + MethodReturnTypePattern returnType = methodMember.getReturnType(); + if (returnType.hasVoidType()) { + setReturnTypeVoid(); + } else if (returnType.hasSomeType()) { + setReturnTypePattern( + KeepMethodReturnTypePattern.fromType( + KeepTypePattern.fromProto(returnType.getSomeType()))); + } + } + + assert parametersPattern.isAny(); + if (methodMember.hasParameterTypes()) { + MethodParameterTypesPattern parameterTypes = methodMember.getParameterTypes(); + KeepMethodParametersPattern.Builder parametersBuilder = + KeepMethodParametersPattern.builder(); + for (TypePattern typePattern : parameterTypes.getTypesList()) { + parametersBuilder.addParameterTypePattern(KeepTypePattern.fromProto(typePattern)); + } + setParametersPattern(parametersBuilder.build()); + } + + // TODO(b/343389186): Add annotated-by. + // TODO(b/343389186): Add access. + return this; + } + public Builder copyFromMemberPattern(KeepMemberPattern memberPattern) { assert memberPattern.isGeneralMember(); return setAccessPattern( @@ -176,4 +216,21 @@ + parametersPattern + '}'; } + + public static KeepMemberPattern fromMethodProto(MemberPatternMethod methodMember) { + return builder().applyProto(methodMember).build(); + } + + public MemberPatternMethod.Builder buildMethodProto() { + MemberPatternMethod.Builder builder = + MemberPatternMethod.newBuilder() + .setName(namePattern.asStringPattern().buildProto()) + .setReturnType(returnTypePattern.buildProto()); + if (!parametersPattern.isAny()) { + builder.setParameterTypes(parametersPattern.buildProto()); + } + // TODO(b/343389186): Add annotated-by. + // TODO(b/343389186): Add access. + return builder; + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java index bd8d66f..d289c58 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.MethodReturnTypePattern; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypeVoid; public abstract class KeepMethodReturnTypePattern { @@ -101,4 +103,16 @@ return typePattern.toString(); } } + + public MethodReturnTypePattern.Builder buildProto() { + MethodReturnTypePattern.Builder builder = MethodReturnTypePattern.newBuilder(); + if (isAny()) { + // The unset oneof denotes any return type. + return builder; + } + if (isVoid()) { + return builder.setVoidType(TypeVoid.getDefaultInstance()); + } + return builder.setSomeType(asType().buildProto()); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java index 69fc5b4..16d9ef2 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java
@@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.PackagePattern; +import com.android.tools.r8.keepanno.utils.Unimplemented; + public abstract class KeepPackagePattern { public static Builder builder() { @@ -21,10 +25,48 @@ return KeepPackagePattern.builder().exact(fullPackage).build(); } + public static KeepPackagePattern fromProto(PackagePattern proto) { + return builder().applyProto(proto).build(); + } + + public PackagePattern.Builder buildProto() { + PackagePattern.Builder builder = PackagePattern.newBuilder(); + if (isAny()) { + // An unset oneof implies "any package" (including multiple package parts). + return builder; + } + if (isTop()) { + // The top/unspecified package is encoded as the empty package name. + return builder.setName(KeepSpecProtos.StringPattern.newBuilder().setExact("")); + } + // TODO(b/343389186): Rewrite the package patterns to use the tree structure. + return builder.setExactPackageHack(getExactPackageAsString()); + } + public static class Builder { private KeepPackagePattern pattern; + public Builder applyProto(PackagePattern pkg) { + if (pkg.hasExactPackageHack()) { + exact(pkg.getExactPackageHack()); + return this; + } + if (pkg.hasName()) { + KeepStringPattern stringPattern = KeepStringPattern.fromProto(pkg.getName()); + if (stringPattern.isExact() && stringPattern.asExactString().isEmpty()) { + return top(); + } + throw new Unimplemented(); + } + if (pkg.hasNode()) { + throw new Unimplemented(); + } + // The unset oneof implies any package. + assert pattern.isAny(); + return this; + } + public Builder any() { pattern = Any.getInstance(); return this;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java index f79093d..ca5d756 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypePatternPrimitive; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.function.Consumer; @@ -92,4 +93,56 @@ public static void forEachPrimitive(Consumer<KeepPrimitiveTypePattern> fn) { PRIMITIVES.values().forEach(fn); } + + public static KeepPrimitiveTypePattern fromProto(TypePatternPrimitive primitive) { + switch (primitive.getNumber()) { + case TypePatternPrimitive.PRIMITIVE_BOOLEAN_VALUE: + return getBoolean(); + case TypePatternPrimitive.PRIMITIVE_BYTE_VALUE: + return getByte(); + case TypePatternPrimitive.PRIMITIVE_CHAR_VALUE: + return getChar(); + case TypePatternPrimitive.PRIMITIVE_SHORT_VALUE: + return getShort(); + case TypePatternPrimitive.PRIMITIVE_INT_VALUE: + return getInt(); + case TypePatternPrimitive.PRIMITIVE_LONG_VALUE: + return getLong(); + case TypePatternPrimitive.PRIMITIVE_FLOAT_VALUE: + return getFloat(); + case TypePatternPrimitive.PRIMITIVE_DOUBLE_VALUE: + return getDouble(); + default: + return getAny(); + } + } + + public TypePatternPrimitive buildProto() { + if (this == BOOLEAN) { + return TypePatternPrimitive.PRIMITIVE_BOOLEAN; + } + if (this == BYTE) { + return TypePatternPrimitive.PRIMITIVE_BYTE; + } + if (this == CHAR) { + return TypePatternPrimitive.PRIMITIVE_CHAR; + } + if (this == SHORT) { + return TypePatternPrimitive.PRIMITIVE_SHORT; + } + if (this == INT) { + return TypePatternPrimitive.PRIMITIVE_INT; + } + if (this == LONG) { + return TypePatternPrimitive.PRIMITIVE_LONG; + } + if (this == FLOAT) { + return TypePatternPrimitive.PRIMITIVE_FLOAT; + } + if (this == DOUBLE) { + return TypePatternPrimitive.PRIMITIVE_DOUBLE; + } + assert isAny(); + return TypePatternPrimitive.PRIMITIVE_UNSPECIFIED; + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java index 25b98af..4b1fcf7 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.ClassNamePattern; import java.util.Objects; public final class KeepQualifiedClassNamePattern { @@ -44,13 +45,36 @@ .build(); } + public static KeepQualifiedClassNamePattern fromProto(ClassNamePattern clazz) { + return KeepQualifiedClassNamePattern.builder().applyProto(clazz).build(); + } + + public ClassNamePattern.Builder buildProto() { + return ClassNamePattern.newBuilder() + .setPackage(packagePattern.buildProto()) + .setUnqualifiedName(namePattern.buildProto()); + } + public static class Builder { - private KeepPackagePattern packagePattern; - private KeepUnqualfiedClassNamePattern namePattern; + private KeepPackagePattern packagePattern = KeepPackagePattern.any(); + private KeepUnqualfiedClassNamePattern namePattern = KeepUnqualfiedClassNamePattern.any(); private Builder() {} + public Builder applyProto(ClassNamePattern proto) { + assert packagePattern.isAny(); + if (proto.hasPackage()) { + setPackagePattern(KeepPackagePattern.fromProto(proto.getPackage())); + } + + assert namePattern.isAny(); + if (proto.hasUnqualifiedName()) { + setNamePattern(KeepUnqualfiedClassNamePattern.fromProto(proto.getUnqualifiedName())); + } + return this; + } + public Builder setPackagePattern(KeepPackagePattern packagePattern) { this.packagePattern = packagePattern; return this;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java new file mode 100644 index 0000000..b5f934b --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecUtils.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2024, 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.ast; + +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypeDesc; +import com.google.protobuf.MessageOrBuilder; +import java.util.function.Consumer; + +public final class KeepSpecUtils { + + private KeepSpecUtils() {} + + public static TypeDesc desc(String descriptor) { + return TypeDesc.newBuilder().setDesc(descriptor).build(); + } +}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecVersion.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecVersion.java new file mode 100644 index 0000000..4a06ff5 --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepSpecVersion.java
@@ -0,0 +1,45 @@ +// Copyright (c) 2024, 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.ast; + +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Version; + +public enum KeepSpecVersion { + UNKNOWN(0, 0, 0), + ALPHA(0, 1, 0); + + private final int major; + private final int minor; + private final int patch; + + KeepSpecVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + public static KeepSpecVersion getCurrent() { + return ALPHA; + } + + public static KeepSpecVersion fromProto(Version version) { + for (KeepSpecVersion value : values()) { + if (value.major == version.getMajor() + && value.minor == version.getMinor() + && value.patch == version.getPatch()) { + return value; + } + } + return UNKNOWN; + } + + public String toVersionString() { + return "" + major + "." + minor + "." + patch; + } + + public Version.Builder buildProto() { + return Version.newBuilder().setMajor(major).setMinor(minor).setPatch(patch); + } +}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepStringPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepStringPattern.java index a9b1772..549e12f 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepStringPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepStringPattern.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.StringPattern; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.StringPatternInexact; import java.util.Objects; public class KeepStringPattern { @@ -22,6 +24,25 @@ return new Builder(); } + public static KeepStringPattern fromProto(StringPattern proto) { + return builder().applyProto(proto).build(); + } + + public StringPattern.Builder buildProto() { + StringPattern.Builder builder = StringPattern.newBuilder(); + if (isAny()) { + // An unset oneof signifies the "any" string pattern. + return builder; + } + if (isExact()) { + return builder.setExact(exact); + } + return builder.setInexact( + StringPatternInexact.newBuilder() + .setPrefix(prefix == null ? "" : prefix) + .setSuffix(suffix == null ? "" : suffix)); + } + public static class Builder { private String exact = null; private String prefix = null; @@ -29,6 +50,21 @@ private Builder() {} + public Builder applyProto(StringPattern name) { + if (name.hasExact()) { + return setExact(name.getExact()); + } + if (name.hasInexact()) { + StringPatternInexact inexact = name.getInexact(); + setPrefix(inexact.getPrefix()); + setSuffix(inexact.getSuffix()); + return this; + } + // The unset oneof implies any. Don't assign any fields. + assert build().isAny(); + return this; + } + public Builder setExact(String exact) { this.exact = exact; return this;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java index 7e3c088..a123e61 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.TypePattern; +import com.android.tools.r8.keepanno.utils.Unimplemented; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Objects; @@ -56,6 +58,21 @@ throw new KeepEdgeException("Invalid type descriptor: " + typeDescriptor); } + public static KeepTypePattern fromProto(TypePattern typeProto) { + if (typeProto.hasPrimitive()) { + return KeepTypePattern.fromPrimitive( + KeepPrimitiveTypePattern.fromProto(typeProto.getPrimitive())); + } + if (typeProto.hasArray()) { + return KeepTypePattern.fromArray(KeepArrayTypePattern.fromProto(typeProto.getArray())); + } + if (typeProto.hasClazz()) { + return KeepTypePattern.fromClass( + KeepQualifiedClassNamePattern.fromProto(typeProto.getClazz())); + } + return KeepTypePattern.any(); + } + public abstract <T> T apply( Supplier<T> onAny, Function<KeepPrimitiveTypePattern, T> onPrimitive, @@ -263,4 +280,19 @@ return onInstanceOf.apply(instanceOf); } } + + public TypePattern.Builder buildProto() { + TypePattern.Builder builder = TypePattern.newBuilder(); + match( + () -> { + // The unset oneof is "any type". + }, + primitive -> builder.setPrimitive(primitive.buildProto()), + array -> builder.setArray(array.buildProto()), + clazz -> builder.setClazz(clazz.buildProto()), + instanceOf -> { + throw new Unimplemented(); + }); + return builder; + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java index a974f67..e00ed78 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.UnqualifiedNamePattern; + public class KeepUnqualfiedClassNamePattern { private static final KeepUnqualfiedClassNamePattern ANY = @@ -68,10 +70,26 @@ return unqualifiedNamePattern.toString(); } + public static KeepUnqualfiedClassNamePattern fromProto(UnqualifiedNamePattern proto) { + return builder().applyProto(proto).build(); + } + + public UnqualifiedNamePattern.Builder buildProto() { + return UnqualifiedNamePattern.newBuilder().setName(unqualifiedNamePattern.buildProto()); + } + public static class Builder { private KeepStringPattern pattern = KeepStringPattern.any(); + public Builder applyProto(UnqualifiedNamePattern proto) { + assert pattern.isAny(); + if (proto.hasName()) { + setPattern(KeepStringPattern.fromProto(proto.getName())); + } + return this; + } + public Builder any() { pattern = KeepStringPattern.any(); return this;
diff --git a/src/keepanno/proto/keepanno.proto b/src/keepanno/proto/keepanno.proto deleted file mode 100644 index a4f96d7..0000000 --- a/src/keepanno/proto/keepanno.proto +++ /dev/null
@@ -1,56 +0,0 @@ -// Copyright (c) 2024, 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. -syntax = "proto3"; - -package com.android.tools.r8.keepanno.proto; - -// All messages are placed under the outer class. This makes it a bit nicer to -// implement the AST <-> Proto conversions without type conflicts. -option java_multiple_files = false; - -// Descriptive name of the outer class (default would have been `Keepanno`). -option java_outer_classname = "KeepAnnoProtos"; - -// Java package consistent with R8 convention. -option java_package = "com.android.tools.r8.keepanno.proto"; - -message Version { - uint32 major = 1; - uint32 minor = 2; - uint32 patch = 3; -} - -message Context { - oneof context_oneof { - string class_descriptor = 1; - string method_descriptor = 2; - string field_descriptor = 3; - } -} - -message MetaInfo { - Version version = 1; - Context context = 2; -} - -message Declaration { - MetaInfo meta_info = 1; - oneof decl_oneof { - Edge edge = 2; - CheckRemoved check_removed = 3; - CheckDiscarded check_discarded = 4; - } -} - -message CheckRemoved { - // TODO(b/343389186): Add content. -} - -message CheckDiscarded { - // TODO(b/343389186): Add content. -} - -message Edge { - // TODO(b/343389186): Add content. -}
diff --git a/src/keepanno/proto/keepspec.proto b/src/keepanno/proto/keepspec.proto new file mode 100644 index 0000000..451b137 --- /dev/null +++ b/src/keepanno/proto/keepspec.proto
@@ -0,0 +1,226 @@ +// Copyright (c) 2024, 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. +syntax = "proto3"; + +package com.android.tools.r8.keepanno.proto; + +// All messages are placed under the outer class. This makes it a bit nicer to +// implement the AST <-> Proto conversions without type conflicts. +option java_multiple_files = false; + +// Camel-case the outer class name (default is `Keepspec`). +option java_outer_classname = "KeepSpecProtos"; + +// Java package consistent with R8 convention. +option java_package = "com.android.tools.r8.keepanno.proto"; + +// Top-level container for the keep specification +message KeepSpec { + Version version = 1; + repeated Declaration declarations = 2; +} + +message Version { + uint32 major = 1; + uint32 minor = 2; + uint32 patch = 3; +} + +message Declaration { + oneof decl_oneof { + Edge edge = 2; + Check check = 3; + } +} + +// Note: the messages and fields avoid the use of `descriptor` in any place as +// that name is used internally in the protobuf encodings. We consistently use +// the short-form `desc` throughout. + +message Context { + oneof context_oneof { + TypeDesc class_desc = 1; + MethodDesc method_desc = 2; + FieldDesc field_desc = 3; + } +} + +message TypeDesc { + string desc = 1; +} + +message MethodDesc { + string name = 1; + TypeDesc holder = 2; + TypeDesc return_type = 3; + repeated TypeDesc parameter_types = 4; +} + +message FieldDesc { + string name = 1; + TypeDesc holder = 2; + TypeDesc field_type = 3; +} + +message MetaInfo { + optional Context context = 1; + optional string description = 2; +} + +enum CheckKind { + CHECK_UNSPECIFIED = 0; + CHECK_REMOVED = 1; + CHECK_OPTIMIZED_OUT = 2; +} + +message Check { + optional MetaInfo meta_info = 1; + CheckKind kind = 2; + Bindings bindings = 3; + BindingReference item = 4; +} + +message Edge { + MetaInfo meta_info = 1; + // TODO(b/343389186): Add content. +} + +message Bindings { + repeated Binding bindings = 1; +} + +message Binding { + string name = 1; + ItemPattern item = 2; +} + +message BindingReference { + string name = 1; +} + +message ItemPattern { + oneof item_oneof { + ClassItemPattern class_item = 1; + MemberItemPattern member_item = 2; + } +} + +message ClassItemPattern { + optional ClassNamePattern class_name = 1; + // TODO(b/343389186): Add instance-of. + // TODO(b/343389186): Add annotated-by. +} + +message ClassNamePattern { + optional PackagePattern package = 1; + optional UnqualifiedNamePattern unqualified_name = 2; +} + +message PackagePattern { + oneof package_oneof { + // An unset oneof implies any package (including multiple package parts). + StringPattern name = 1; + PackageNode node = 2; + // TODO(b/343389186): Rewrite package pattern AST to the tree structure. + string exact_package_hack = 3; + } +} + +message PackageNode { + PackagePattern lhs = 1; + PackagePattern rhs = 2; +} + +message StringPattern { + // The string pattern is split in two so that we can distinguish the exact + // empty string, from the inexact patterns. + oneof pattern_oneof { + // Unset oneof denotes any type. + string exact = 1; + StringPatternInexact inexact = 2; + } +} + +message StringPatternInexact { + optional string prefix = 2; + optional string suffix = 3; +} + +message UnqualifiedNamePattern { + optional StringPattern name = 1; +} + +message MemberItemPattern { + BindingReference class_reference = 1; + optional MemberPattern member_pattern = 2; +} + +message MemberPattern { + oneof member_oneof { + MemberPatternGeneral general_member = 1; + MemberPatternField field_member = 2; + MemberPatternMethod method_member = 3; + } +} + +message MemberPatternGeneral { + // TODO(b/343389186): Add content. +} + +message MemberPatternField { + // TODO(b/343389186): Add content. +} + +message MemberPatternMethod { + optional StringPattern name = 1; + optional MethodReturnTypePattern return_type = 2; + optional MethodParameterTypesPattern parameter_types = 3; + // TODO(b/343389186): Add annotated-by. + // TODO(b/343389186): Add access. +} + +message MethodReturnTypePattern { + oneof return_type_oneof { + // Unset type denotes any type. + TypeVoid void_type = 1; + TypePattern some_type = 2; + } +} + +message MethodParameterTypesPattern { + repeated TypePattern types = 1; +} + +message TypeVoid { + // Placeholder to denote a 'void' method return type. +} + +message TypePattern { + oneof type_oneof { + // Unset type denotes any type. + TypePatternPrimitive primitive = 1; + TypePatternArray array = 2; + ClassNamePattern clazz = 3; + // TODO(b/343389186): Add instance-of. + } +} + +enum TypePatternPrimitive { + PRIMITIVE_UNSPECIFIED = 0; // Denotes any primitive. + PRIMITIVE_BOOLEAN = 1; + PRIMITIVE_BYTE = 2; + PRIMITIVE_CHAR = 3; + PRIMITIVE_SHORT = 4; + PRIMITIVE_INT = 5; + PRIMITIVE_LONG = 6; + PRIMITIVE_FLOAT = 7; + PRIMITIVE_DOUBLE = 8; +} + +message TypePatternArray { + // An unset or zero-valued dimensions will be interpreted as 1. + optional uint32 dimensions = 1; + optional TypePattern base_type = 2; +} +
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index a6e5d5f..a16189b 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -396,8 +396,6 @@ if (globalsOutputPath != null) { builder.setGlobalSyntheticsOutput(globalsOutputPath); } - builder.setOutput(outputPath, outputMode); - builder.setEnableExperimentalMissingLibraryApiModeling(true); - return builder; + return builder.setOutput(outputPath, outputMode); } }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 851df61..74f9c92 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -94,6 +94,7 @@ import com.android.tools.r8.shaking.Enqueuer.Mode; import com.android.tools.r8.shaking.EnqueuerFactory; import com.android.tools.r8.shaking.EnqueuerResult; +import com.android.tools.r8.shaking.KeepSpecificationSource; import com.android.tools.r8.shaking.MainDexInfo; import com.android.tools.r8.shaking.MainDexListBuilder; import com.android.tools.r8.shaking.ProguardConfigurationRule; @@ -105,6 +106,7 @@ import com.android.tools.r8.shaking.TreePruner; import com.android.tools.r8.shaking.TreePrunerConfiguration; import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer; +import com.android.tools.r8.startup.NonStartupInStartupOutliner; import com.android.tools.r8.synthesis.SyntheticFinalization; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.AndroidApp; @@ -309,6 +311,8 @@ timing.begin("Register references and more setup"); assert ArtProfileCompletenessChecker.verify(appView); + readKeepSpecifications(appView, keepDeclarations); + // Check for potentially having pass-through of Cf-code for kotlin libraries. options.enableCfByteCodePassThrough = options.isGeneratingClassFiles() && KotlinMetadataUtils.mayProcessKotlinMetadata(appView); @@ -706,6 +710,8 @@ appView.getArtProfileCollection().withoutMissingItems(appView)); appView.setStartupProfile(appView.getStartupProfile().withoutMissingItems(appView)); + new NonStartupInStartupOutliner(appView).runIfNecessary(executorService, timing); + if (appView.appInfo().hasLiveness()) { SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing); } else { @@ -891,6 +897,19 @@ } } + private void readKeepSpecifications( + AppView<AppInfoWithClassHierarchy> appView, List<KeepDeclaration> keepDeclarations) { + timing.begin("Read keep specifications"); + try { + for (KeepSpecificationSource source : appView.options().getKeepSpecifications()) { + source.parse(keepDeclarations::add); + } + } catch (ResourceException e) { + options.reporter.error(new ExceptionDiagnostic(e, e.getOrigin())); + } + timing.end(); + } + private void writeKeepDeclarationsToConfigurationConsumer( List<KeepDeclaration> keepDeclarations) { if (options.configurationConsumer == null) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index adfaf29..36f1ff3 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.profile.art.ArtProfileForRewriting; import com.android.tools.r8.shaking.FilteredClassPath; +import com.android.tools.r8.shaking.KeepSpecificationSource; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardConfigurationParser; import com.android.tools.r8.shaking.ProguardConfigurationParserOptions; @@ -120,6 +121,7 @@ private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumerForTesting = null; private Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null; private StringConsumer desugaredLibraryKeepRuleConsumer = null; + private final List<KeepSpecificationSource> keepSpecifications = new ArrayList<>(); private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>(); private boolean disableTreeShaking = false; private boolean disableMinification = false; @@ -235,6 +237,20 @@ return self(); } + public Builder addKeepSpecificationFiles(Path... paths) { + return addKeepSpecificationFiles(Arrays.asList(paths)); + } + + public Builder addKeepSpecificationFiles(Collection<Path> paths) { + paths.forEach(p -> keepSpecifications.add(KeepSpecificationSource.fromFile(p))); + return self(); + } + + public Builder addKeepSpecificationData(byte[] data, Origin origin) { + keepSpecifications.add(KeepSpecificationSource.fromBytes(origin, data)); + return self(); + } + /** * Set Proguard compatibility mode. * @@ -755,7 +771,8 @@ getCancelCompilationChecker(), androidResourceProvider, androidResourceConsumer, - resourceShrinkerConfiguration); + resourceShrinkerConfiguration, + keepSpecifications); if (inputDependencyGraphConsumer != null) { inputDependencyGraphConsumer.finished(); @@ -933,6 +950,7 @@ private final List<ProguardConfigurationRule> mainDexKeepRules; private final ProguardConfiguration proguardConfiguration; + private final List<KeepSpecificationSource> keepSpecifications; private final boolean enableTreeShaking; private final boolean enableMinification; private final boolean forceProguardCompatibility; @@ -1051,7 +1069,8 @@ CancelCompilationChecker cancelCompilationChecker, AndroidResourceProvider androidResourceProvider, AndroidResourceConsumer androidResourceConsumer, - ResourceShrinkerConfiguration resourceShrinkerConfiguration) { + ResourceShrinkerConfiguration resourceShrinkerConfiguration, + List<KeepSpecificationSource> keepSpecifications) { super( inputApp, mode, @@ -1078,6 +1097,7 @@ assert mainDexKeepRules != null; this.mainDexKeepRules = mainDexKeepRules; this.proguardConfiguration = proguardConfiguration; + this.keepSpecifications = keepSpecifications; this.enableTreeShaking = enableTreeShaking; this.enableMinification = enableMinification; this.forceProguardCompatibility = forceProguardCompatibility; @@ -1105,6 +1125,7 @@ super(printHelp, printVersion); mainDexKeepRules = ImmutableList.of(); proguardConfiguration = null; + keepSpecifications = null; enableTreeShaking = false; enableMinification = false; forceProguardCompatibility = false; @@ -1164,6 +1185,8 @@ && !internal.isShrinking() && !internal.isMinifying()); + internal.setKeepSpecificationSources(keepSpecifications); + assert !internal.verbose; internal.mainDexKeepRules = mainDexKeepRules; internal.minimalMainDex = internal.debug;
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index 825655a..175abb4 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -269,8 +269,9 @@ return newAccessFlags; } - public void promoteToStatic() { + public T promoteToStatic() { promote(Constants.ACC_STATIC); + return self(); } private boolean wasSet(int flag) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index d6216c3..30f63fd 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1003,10 +1003,12 @@ DexDebugInfo newDebugInfo = dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory()); assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount()); dexCode.setDebugInfo(newDebugInfo); - } else { - assert code.isCfCode(); + } else if (code.isCfCode()) { CfCode cfCode = code.asCfCode(); cfCode.addFakeThisParameter(appView.dexItemFactory()); + } else if (code.isLirCode()) { + assert appView.options().isRelease(); + assert code.asLirCode().getDebugLocalInfoTable() == null; } }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 2aad1c9..e198ebf 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -460,6 +460,10 @@ return null; } + public boolean isNonStartupInStartupOutlinerLens() { + return false; + } + public boolean isProtoNormalizerLens() { return false; }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java index fd7a46d..4363bdf 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -27,8 +27,8 @@ * <p>Subclasses can override the lookup methods. * * <p>For method mapping where invocation type can change just override {@link - * #mapInvocationType(DexMethod, DexMethod, InvokeType)} if the default name mapping applies, and - * only invocation type might need to change. + * #mapInvocationType(DexMethod, DexMethod, DexMethod, InvokeType)} if the default name mapping + * applies, and only invocation type might need to change. */ public class NestedGraphLens extends DefaultNonIdentityGraphLens { @@ -199,7 +199,10 @@ rewrittenReboundReference)) .setType( mapInvocationType( - rewrittenReboundReference, previous.getReference(), previous.getType())) + rewrittenReference, + rewrittenReboundReference, + previous.getReference(), + previous.getType())) .build(); } else { // TODO(b/168282032): We should always have the rebound reference, so this should become @@ -220,7 +223,8 @@ return MethodLookupResult.builder(this, codeLens) .setReference(newMethod) .setPrototypeChanges(newPrototypeChanges) - .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType())) + .setType( + mapInvocationType(newMethod, newMethod, previous.getReference(), previous.getType())) .build(); } } @@ -271,7 +275,7 @@ * method or {@link #lookupMethod(DexMethod, DexMethod, InvokeType)} */ protected InvokeType mapInvocationType( - DexMethod newMethod, DexMethod originalMethod, InvokeType type) { + DexMethod newMethod, DexMethod newReboundMethod, DexMethod originalMethod, InvokeType type) { return type; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java index c1dee99..5a42ddc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -17,6 +17,8 @@ TypeElement getOutType(); + FieldInstruction asFieldInstruction(); + boolean isInstanceGet(); InstanceGet asInstanceGet();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index 13ce76b..ca0f4269 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -76,6 +76,7 @@ import com.android.tools.r8.ir.code.ConstMethodHandle; import com.android.tools.r8.ir.code.ConstMethodType; import com.android.tools.r8.ir.code.DexItemBasedConstString; +import com.android.tools.r8.ir.code.FieldGet; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.FieldPut; import com.android.tools.r8.ir.code.IRCode; @@ -626,21 +627,8 @@ } if (newOutValue != null) { if (lookup.hasReadCastType() && newOutValue.hasNonDebugUsers()) { - TypeElement castType = - TypeElement.fromDexType( - lookup.getReadCastType(), newOutValue.getType().nullability(), appView); - Value castOutValue = code.createValue(castType); - newOutValue.replaceUsers(castOutValue); - CheckCast checkCast = - SafeCheckCast.builder() - .setCastType(lookup.getReadCastType()) - .setObject(newOutValue) - .setOutValue(castOutValue) - .setPosition(instanceGet) - .build(); - iterator.addThrowingInstructionToPossiblyThrowingBlock( - code, blocks, checkCast, options); - affectedPhis.addAll(checkCast.outValue().uniquePhiUsers()); + insertReadCast( + code, blocks, iterator, instanceGet, lookup, newOutValue, affectedPhis); } else if (newOutValue.getType() != instanceGet.getOutType()) { affectedPhis.addAll(newOutValue.uniquePhiUsers()); } @@ -684,21 +672,8 @@ } if (newOutValue != null) { if (lookup.hasReadCastType() && newOutValue.hasNonDebugUsers()) { - TypeElement castType = - TypeElement.fromDexType( - lookup.getReadCastType(), newOutValue.getType().nullability(), appView); - Value castOutValue = code.createValue(castType); - newOutValue.replaceUsers(castOutValue); - CheckCast checkCast = - SafeCheckCast.builder() - .setCastType(lookup.getReadCastType()) - .setObject(newOutValue) - .setOutValue(castOutValue) - .setPosition(staticGet) - .build(); - iterator.addThrowingInstructionToPossiblyThrowingBlock( - code, blocks, checkCast, options); - affectedPhis.addAll(checkCast.outValue().uniquePhiUsers()); + insertReadCast( + code, blocks, iterator, staticGet, lookup, newOutValue, affectedPhis); } else if (newOutValue.getType() != staticGet.getOutType()) { affectedPhis.addAll(newOutValue.uniquePhiUsers()); } @@ -1067,6 +1042,30 @@ instructionIterator.removeOrReplaceByDebugLocalRead(); } + private void insertReadCast( + IRCode code, + BasicBlockIterator blocks, + InstructionListIterator iterator, + FieldGet fieldGet, + FieldLookupResult lookup, + Value newOutValue, + Set<Phi> affectedPhis) { + TypeElement castTypeElement = + TypeElement.fromDexType( + lookup.getReadCastType(), newOutValue.getType().nullability(), appView); + Value castOutValue = code.createValue(castTypeElement); + newOutValue.replaceUsers(castOutValue); + CheckCast checkCast = + SafeCheckCast.builder() + .setCastType(lookup.getReadCastType()) + .setObject(newOutValue) + .setOutValue(castOutValue) + .setPosition(fieldGet.asFieldInstruction()) + .build(); + iterator.addThrowingInstructionToPossiblyThrowingBlock(code, blocks, checkCast, options); + affectedPhis.addAll(checkCast.outValue().uniquePhiUsers()); + } + private Argument rewriteArgumentType( IRCode code, Argument argument,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java index 5d60881..ca1d499 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -196,7 +196,7 @@ .setPrototypeChanges( internalDescribePrototypeChanges( previous.getPrototypeChanges(), previous.getReference(), result)) - .setType(mapInvocationType(result, previous.getReference(), previous.getType())) + .setType(mapInvocationType(result, result, previous.getReference(), previous.getType())) .build(); } @@ -244,7 +244,7 @@ @Override @SuppressWarnings("ReferenceEquality") protected InvokeType mapInvocationType( - DexMethod newMethod, DexMethod originalMethod, InvokeType type) { + DexMethod newMethod, DexMethod newReboundMethod, DexMethod originalMethod, InvokeType type) { if (typeMap.containsKey(originalMethod.getHolderType())) { // Methods moved from unboxed enums to the utility class are either static or statified. assert newMethod != originalMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java index 0dd4db5..7cd5d83 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
@@ -69,6 +69,7 @@ private static class Traversal extends DepthFirstTopDownClassHierarchyTraversal { + private final AppView<AppInfoWithLiveness> appViewWithLiveness; private final MethodResolutionOptimizationInfoCollection.Builder builder; private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>(); @@ -77,6 +78,7 @@ MethodResolutionOptimizationInfoCollection.Builder builder, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { super(appView, immediateSubtypingInfo); + this.appViewWithLiveness = appView; this.builder = builder; } @@ -145,7 +147,7 @@ states .getOrDefault(subClass, UpwardsTraversalState.empty()) .asUpwardsTraversalState(); - newState.join(appView, subClassState); + newState.join(appViewWithLiveness, subClassState); // If the current class is an interface and the current subclass is not, then we need // special handling to account for the fact that invoke-interface instructions may @@ -156,26 +158,30 @@ } }); ObjectAllocationInfoCollection objectAllocationInfoCollection = - appView.appInfo().getObjectAllocationInfoCollection(); + appViewWithLiveness.appInfo().getObjectAllocationInfoCollection(); if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(clazz)) { for (DexEncodedMethod method : clazz.virtualMethods()) { newState.joinMethodOptimizationInfo( - appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance()); + appViewWithLiveness, + method.getSignature(), + DefaultMethodOptimizationInfo.getInstance()); } } else { for (DexEncodedMethod method : clazz.virtualMethods()) { - KeepMethodInfo keepInfo = appView.getKeepInfo().getMethodInfo(method, clazz); - if (!keepInfo.isShrinkingAllowed(appView.options())) { + KeepMethodInfo keepInfo = appViewWithLiveness.getKeepInfo().getMethodInfo(method, clazz); + if (!keepInfo.isShrinkingAllowed(appViewWithLiveness.options())) { // Method is kept and could be overridden outside app (e.g., in tests). Verify we don't // have any optimization info recorded for non-abstract methods. assert method.isAbstract() || method.getOptimizationInfo().isDefault() || method.getOptimizationInfo().returnValueHasBeenPropagated(); newState.joinMethodOptimizationInfo( - appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance()); + appViewWithLiveness, + method.getSignature(), + DefaultMethodOptimizationInfo.getInstance()); } else if (!method.isAbstract()) { newState.joinMethodOptimizationInfo( - appView, method.getSignature(), method.getOptimizationInfo()); + appViewWithLiveness, method.getSignature(), method.getOptimizationInfo()); } } } @@ -214,7 +220,7 @@ for (DexMethodSignature method : interfaceMethodsInClassOrAbove) { MethodResolutionResult resolutionResult = - appView.appInfo().resolveMethodOnClass(subClass, method); + appViewWithLiveness.appInfo().resolveMethodOnClass(subClass, method); if (resolutionResult.isFailedResolution()) { assert resolutionResult.asFailedResolution().hasMethodsCausingError(); continue; @@ -223,7 +229,7 @@ if (resolutionResult.isMultiMethodResolutionResult()) { // Conservatively drop the current optimization info. newState.joinMethodOptimizationInfo( - appView, method, DefaultMethodOptimizationInfo.getInstance()); + appViewWithLiveness, method, DefaultMethodOptimizationInfo.getInstance()); continue; } @@ -231,7 +237,7 @@ DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair(); if (!resolvedMethod.getHolder().isInterface() && resolvedMethod.getHolder() != subClass) { newState.joinMethodOptimizationInfo( - appView, method, resolvedMethod.getOptimizationInfo()); + appViewWithLiveness, method, resolvedMethod.getOptimizationInfo()); } } }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java index b5ab4d1..0b52815 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -21,6 +21,7 @@ import com.android.tools.r8.graph.lens.FieldLookupResult; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.MethodLookupResult; +import com.android.tools.r8.graph.lens.NonIdentityGraphLens; import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens; import com.android.tools.r8.ir.code.IRCode; @@ -54,6 +55,8 @@ private final GraphLens codeLens; private final LensCodeRewriterUtils helper; + private final boolean isNonStartupInStartupOutlinerLens; + private int numberOfInvokeOpcodeChanges = 0; private Map<LirConstant, LirConstant> constantPoolMapping = null; @@ -71,6 +74,15 @@ this.graphLens = appView.graphLens(); this.codeLens = context.getDefinition().getCode().getCodeLens(appView); this.helper = helper; + NonIdentityGraphLens nonStartupInStartupOutlinerLens = + graphLens.isNonIdentityLens() + ? graphLens + .asNonIdentityLens() + .find(l -> l.isNonStartupInStartupOutlinerLens() || l == codeLens) + : null; + this.isNonStartupInStartupOutlinerLens = + nonStartupInStartupOutlinerLens != null + && nonStartupInStartupOutlinerLens.isNonStartupInStartupOutlinerLens(); } @Override @@ -120,13 +132,14 @@ InvokeType newType = result.getType(); boolean newIsInterface = lookupIsInterface(method, opcode, result); int newOpcode = newType.getLirOpcode(newIsInterface); - assert newMethod.getArity() == method.getArity(); + assert newMethod.getArity() == method.getArity() || newType.isStatic(); if (newOpcode != opcode) { assert type == newType - || (type.isDirect() && (newType.isInterface() || newType.isVirtual())) - || (type.isInterface() && newType.isVirtual()) - || (type.isSuper() && newType.isVirtual()) - || (type.isVirtual() && newType.isInterface()) + || (type.isDirect() + && (newType.isInterface() || newType.isStatic() || newType.isVirtual())) + || (type.isInterface() && (newType.isStatic() || newType.isVirtual())) + || (type.isSuper() && (newType.isStatic() || newType.isVirtual())) + || (type.isVirtual() && (newType.isInterface() || newType.isStatic())) : type + " -> " + newType; numberOfInvokeOpcodeChanges++; } else { @@ -281,6 +294,20 @@ if (opcode == LirOpcodes.INVOKEINTERFACE) { return InvokeType.INTERFACE; } + if (isNonStartupInStartupOutlinerLens) { + if (LirOpcodeUtils.isInvokeDirect(opcode)) { + return InvokeType.DIRECT; + } + if (LirOpcodeUtils.isInvokeInterface(opcode)) { + return InvokeType.INTERFACE; + } + if (LirOpcodeUtils.isInvokeSuper(opcode)) { + return InvokeType.SUPER; + } + if (LirOpcodeUtils.isInvokeVirtual(opcode)) { + return InvokeType.VIRTUAL; + } + } if (graphLens.isVerticalClassMergerLens()) { if (opcode == LirOpcodes.INVOKESTATIC_ITF) { return InvokeType.STATIC;
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java index 7b110eb..d9efe9b 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java +++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
@@ -54,6 +54,14 @@ } } + public static boolean isInvokeDirect(int opcode) { + return opcode == INVOKEDIRECT || opcode == INVOKEDIRECT_ITF; + } + + public static boolean isInvokeInterface(int opcode) { + return opcode == INVOKEINTERFACE; + } + public static boolean isInvokeMethod(int opcode) { switch (opcode) { case INVOKEDIRECT: @@ -69,4 +77,12 @@ return false; } } + + public static boolean isInvokeSuper(int opcode) { + return opcode == INVOKESUPER || opcode == INVOKESUPER_ITF; + } + + public static boolean isInvokeVirtual(int opcode) { + return opcode == INVOKEVIRTUAL; + } }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java index a5a4a61..87a811b 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -89,13 +89,13 @@ @Override protected InvokeType mapInvocationType( - DexMethod newMethod, DexMethod originalMethod, InvokeType type) { - return hasPrototypeChanges(newMethod) - && getPrototypeChanges(newMethod) + DexMethod newMethod, DexMethod newReboundMethod, DexMethod originalMethod, InvokeType type) { + return hasPrototypeChanges(newReboundMethod) + && getPrototypeChanges(newReboundMethod) .getArgumentInfoCollection() .isConvertedToStaticMethod() ? InvokeType.STATIC - : super.mapInvocationType(newMethod, originalMethod, type); + : super.mapInvocationType(newMethod, newReboundMethod, originalMethod, type); } public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java index c380eab..28d6b5c 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysisBase.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.optimize.argumentpropagation.codescanner; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexMethod; @@ -10,7 +11,6 @@ import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.collections.DexMethodSignatureMap; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; @@ -140,7 +140,8 @@ protected final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>(); protected VirtualRootMethodsAnalysisBase( - AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + AppView<? extends AppInfoWithClassHierarchy> appView, + ImmediateProgramSubtypingInfo immediateSubtypingInfo) { super(appView, immediateSubtypingInfo); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java index 22b1a0e..c882a7c 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -43,6 +43,7 @@ // Contains the argument information for each interface method (including inherited interface // methods) on the seen but not finished interfaces. + final AppView<AppInfoWithLiveness> appViewWithLiveness; final Map<DexProgramClass, MethodStateCollectionBySignature> methodStatesToPropagate = new IdentityHashMap<>(); final Consumer<DexMethodSignature> interfaceDispatchOutsideProgram; @@ -53,6 +54,7 @@ MethodStateCollectionByReference methodStates, Consumer<DexMethodSignature> interfaceDispatchOutsideProgram) { super(appView, immediateSubtypingInfo, methodStates); + this.appViewWithLiveness = appView; this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram; } @@ -98,7 +100,7 @@ MethodStateCollectionBySignature implementedInterfaceState = methodStatesToPropagate.get(superclass); assert implementedInterfaceState != null; - interfaceState.addMethodStates(appView, implementedInterfaceState); + interfaceState.addMethodStates(appViewWithLiveness, implementedInterfaceState); }); // Add any argument information for virtual methods on the current interface to the state. @@ -116,7 +118,7 @@ } assert methodState.isUnknown() || methodState.asConcrete().isPolymorphic(); - interfaceState.addMethodState(appView, method, methodState); + interfaceState.addMethodState(appViewWithLiveness, method, methodState); }); methodStatesToPropagate.put(interfaceDefinition, interfaceState); @@ -134,7 +136,9 @@ interfaceState.forEach( (interfaceMethod, interfaceMethodState) -> { MethodResolutionResult resolutionResult = - appView.appInfo().resolveMethodOnClassLegacy(subclass, interfaceMethod); + appViewWithLiveness + .appInfo() + .resolveMethodOnClassLegacy(subclass, interfaceMethod); if (resolutionResult.isFailedResolution()) { // TODO(b/190154391): Do we need to propagate argument information to the first // virtual method above the inaccessible method in the class hierarchy? @@ -155,10 +159,14 @@ MethodState transformedInterfaceMethodState = transformInterfaceMethodStateForClassMethod( - appView, subclass, resolvedMethod, interfaceMethodState, methodStates); + appViewWithLiveness, + subclass, + resolvedMethod, + interfaceMethodState, + methodStates); if (!transformedInterfaceMethodState.isBottom()) { methodStates.addMethodState( - appView, resolvedMethod, transformedInterfaceMethodState); + appViewWithLiveness, resolvedMethod, transformedInterfaceMethodState); } })); }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java index e14addb..14eb036 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -70,14 +70,14 @@ assert parentState != null; // Add the argument information that must be propagated to all method overrides. - active.addMethodStates(appView, parentState.active); + active.addMethodStates(appViewWithLiveness, parentState.active); // Add the argument information that is active until a given lower bound. parentState.activeUntilLowerBound.forEach( (lowerBound, activeMethodState) -> { - TypeElement lowerBoundType = lowerBound.toTypeElement(appView); - TypeElement currentType = clazz.getType().toTypeElement(appView); - if (lowerBoundType.lessThanOrEqual(currentType, appView)) { + TypeElement lowerBoundType = lowerBound.toTypeElement(appViewWithLiveness); + TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness); + if (lowerBoundType.lessThanOrEqual(currentType, appViewWithLiveness)) { addActiveUntilLowerBound(lowerBound, activeMethodState); } else { // No longer active. @@ -109,21 +109,22 @@ // interface method is not applied. The information is propagated to the class // method that implements the interface method below. ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType(); - TypeElement currentType = clazz.getType().toTypeElement(appView); - if (lowerBound.lessThanOrEqual(currentType, appView)) { - DexType activeUntilLowerBoundType = lowerBound.toDexType(appView.dexItemFactory()); + TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness); + if (lowerBound.lessThanOrEqual(currentType, appViewWithLiveness)) { + DexType activeUntilLowerBoundType = + lowerBound.toDexType(appViewWithLiveness.dexItemFactory()); addActiveUntilLowerBound(activeUntilLowerBoundType, inactiveMethodStates); } else { return; } } else { - active.addMethodStates(appView, inactiveMethodStates); + active.addMethodStates(appViewWithLiveness, inactiveMethodStates); } inactiveMethodStates.forEach( (signature, methodState) -> { SingleResolutionResult<?> resolutionResult = - appView + appViewWithLiveness .appInfo() .resolveMethodOnLegacy(clazz, signature) .asSingleResolution(); @@ -132,7 +133,7 @@ while (resolutionResult != null && resolutionResult.getResolvedMethod().belongsToDirectPool()) { resolutionResult = - appView + appViewWithLiveness .appInfo() .resolveMethodOnClassLegacy( resolutionResult.getResolvedHolder().getSuperType(), signature) @@ -160,28 +161,28 @@ DexType lowerBound, ProgramMethod method, MethodState methodState) { activeUntilLowerBound .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodState(appView, method, methodState); + .addMethodState(appViewWithLiveness, method, methodState); } private void addActiveUntilLowerBound( DexType lowerBound, MethodStateCollectionBySignature methodStates) { activeUntilLowerBound .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodStates(appView, methodStates); + .addMethodStates(appViewWithLiveness, methodStates); } private void addInactiveUntilUpperBound( DynamicTypeWithUpperBound upperBound, ProgramMethod method, MethodState methodState) { inactiveUntilUpperBound .computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodState(appView, method, methodState); + .addMethodState(appViewWithLiveness, method, methodState); } private void addInactiveUntilUpperBound( DynamicTypeWithUpperBound upperBound, MethodStateCollectionBySignature methodStates) { inactiveUntilUpperBound .computeIfAbsent(upperBound, ignoreKey(MethodStateCollectionBySignature::create)) - .addMethodStates(appView, methodStates); + .addMethodStates(appViewWithLiveness, methodStates); } private MethodState computeMethodStateForPolymorphicMethod(ProgramMethod method) { @@ -192,7 +193,10 @@ for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) { methodState = methodState.mutableJoin( - appView, methodSignature, methodStates.get(method), StateCloner.getCloner()); + appViewWithLiveness, + methodSignature, + methodStates.get(method), + StateCloner.getCloner()); } } if (methodState.isMonomorphic()) { @@ -226,8 +230,9 @@ dynamicType.asDynamicTypeWithUpperBound(); TypeElement dynamicUpperBoundType = dynamicTypeWithUpperBound.getDynamicUpperBoundType(); TypeElement staticUpperBoundType = - method.getHolderType().toTypeElement(appView, definitelyNotNull()); - if (dynamicUpperBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView)) { + method.getHolderType().toTypeElement(appViewWithLiveness, definitelyNotNull()); + if (dynamicUpperBoundType.lessThanOrEqualUpToNullability( + staticUpperBoundType, appViewWithLiveness)) { DynamicType newDynamicType = dynamicType.withNullability(definitelyNotNull()); assert newDynamicType.equals(dynamicType) || !dynamicType.getNullability().isDefinitelyNotNull(); @@ -237,25 +242,27 @@ if (dynamicLowerBoundType == null) { return DynamicType.definitelyNotNull(); } - assert dynamicLowerBoundType.lessThanOrEqualUpToNullability(staticUpperBoundType, appView); + assert dynamicLowerBoundType.lessThanOrEqualUpToNullability( + staticUpperBoundType, appViewWithLiveness); if (dynamicLowerBoundType.equalUpToNullability(staticUpperBoundType)) { return DynamicType.createExact(dynamicLowerBoundType.asDefinitelyNotNull()); } return DynamicType.create( - appView, staticUpperBoundType, dynamicLowerBoundType.asDefinitelyNotNull()); + appViewWithLiveness, staticUpperBoundType, dynamicLowerBoundType.asDefinitelyNotNull()); } @SuppressWarnings("ReferenceEquality") private boolean shouldActivateMethodStateGuardedByBounds( ClassTypeElement upperBound, DexProgramClass currentClass, DexProgramClass superClass) { ClassTypeElement classType = - TypeElement.fromDexType(currentClass.getType(), maybeNull(), appView).asClassType(); + TypeElement.fromDexType(currentClass.getType(), maybeNull(), appViewWithLiveness) + .asClassType(); // When propagating argument information for interface methods downwards from an interface to // a non-interface we need to account for the parent classes of the current class. if (superClass.isInterface() && !currentClass.isInterface() - && currentClass.getSuperType() != appView.dexItemFactory().objectType) { - return classType.lessThanOrEqualUpToNullability(upperBound, appView); + && currentClass.getSuperType() != appViewWithLiveness.dexItemFactory().objectType) { + return classType.lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness); } // If the upper bound does not have any interfaces we simply activate the method state when // meeting the upper bound class type in the downwards traversal over the class hierarchy. @@ -264,19 +271,21 @@ } // If the upper bound has interfaces, we check if the current class is a subtype of *both* the // upper bound class type and the upper bound interface types. - return classType.lessThanOrEqualUpToNullability(upperBound, appView); + return classType.lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness); } boolean verifyActiveUntilLowerBoundRelevance(DexProgramClass clazz) { - TypeElement currentType = clazz.getType().toTypeElement(appView); + TypeElement currentType = clazz.getType().toTypeElement(appViewWithLiveness); for (DexType lowerBound : activeUntilLowerBound.keySet()) { - TypeElement lowerBoundType = lowerBound.toTypeElement(appView); - assert lowerBoundType.lessThanOrEqual(currentType, appView); + TypeElement lowerBoundType = lowerBound.toTypeElement(appViewWithLiveness); + assert lowerBoundType.lessThanOrEqual(currentType, appViewWithLiveness); } return true; } } + final AppView<AppInfoWithLiveness> appViewWithLiveness; + // For each class, stores the argument information for each virtual method on this class and all // direct and indirect super classes. // @@ -290,6 +299,7 @@ ImmediateProgramSubtypingInfo immediateSubtypingInfo, MethodStateCollectionByReference methodStates) { super(appView, immediateSubtypingInfo, methodStates); + this.appViewWithLiveness = appView; } @Override @@ -336,7 +346,8 @@ polymorphicMethodState.forEach( (bounds, methodStateForBounds) -> { if (bounds.isUnknown()) { - propagationState.active.addMethodState(appView, method, methodStateForBounds); + propagationState.active.addMethodState( + appViewWithLiveness, method, methodStateForBounds); } else { // TODO(b/190154391): Verify that the bounds are not trivial according to the // static receiver type. @@ -347,19 +358,20 @@ // class. ClassTypeElement lowerBound = bounds.getDynamicLowerBoundType(); DexType activeUntilLowerBoundType = - lowerBound.toDexType(appView.dexItemFactory()); + lowerBound.toDexType(appViewWithLiveness.dexItemFactory()); assert !bounds.isExactClassType() || activeUntilLowerBoundType.isIdenticalTo(clazz.getType()); propagationState.addActiveUntilLowerBound( activeUntilLowerBoundType, method, methodStateForBounds); } else { - propagationState.active.addMethodState(appView, method, methodStateForBounds); + propagationState.active.addMethodState( + appViewWithLiveness, method, methodStateForBounds); } } else { assert !clazz .getType() - .toTypeElement(appView) - .lessThanOrEqualUpToNullability(upperBound, appView); + .toTypeElement(appViewWithLiveness) + .lessThanOrEqualUpToNullability(upperBound, appViewWithLiveness); propagationState.addInactiveUntilUpperBound( bounds, method, methodStateForBounds); } @@ -372,8 +384,9 @@ } private boolean isUpperBoundSatisfied(ClassTypeElement upperBound, DexProgramClass currentClass) { - DexType upperBoundType = upperBound.toDexType(appView.dexItemFactory()); - DexProgramClass upperBoundClass = asProgramClassOrNull(appView.definitionFor(upperBoundType)); + DexType upperBoundType = upperBound.toDexType(appViewWithLiveness.dexItemFactory()); + DexProgramClass upperBoundClass = + asProgramClassOrNull(appViewWithLiveness.definitionFor(upperBoundType)); if (upperBoundClass == null) { // We should generally never have a dynamic receiver upper bound for a program method which is // not a program class. However, since the program may not type change or there could be
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java index a843a58..3585041 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
@@ -6,11 +6,11 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -34,7 +34,7 @@ FINISHED } - protected final AppView<AppInfoWithLiveness> appView; + protected final AppView<? extends AppInfoWithClassHierarchy> appView; protected final ImmediateProgramSubtypingInfo immediateSubtypingInfo; // Contains the traversal state for each class. If a given class is not in the map the class is @@ -53,7 +53,8 @@ private final List<DexProgramClass> newlySeenButNotFinishedRoots = new ArrayList<>(); public DepthFirstTopDownClassHierarchyTraversal( - AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + AppView<? extends AppInfoWithClassHierarchy> appView, + ImmediateProgramSubtypingInfo immediateSubtypingInfo) { this.appView = appView; this.immediateSubtypingInfo = immediateSubtypingInfo; }
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java index 00588b5..fbbf49f 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -5,6 +5,7 @@ import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DefaultUseRegistry; import com.android.tools.r8.graph.DexClass; @@ -387,7 +388,7 @@ if (superTargets != null) { return superTargets; } - AppView<AppInfoWithLiveness> appViewWithLiveness = appView; + AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy = appView; superTargets = ProgramMethodSet.create(); WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(root); while (worklist.hasNext()) { @@ -402,9 +403,10 @@ public void registerInvokeSuper(DexMethod method) { ProgramMethod superTarget = asProgramMethodOrNull( - appViewWithLiveness + appViewWithClassHierarchy .appInfo() - .lookupSuperTarget(method, getContext(), appViewWithLiveness)); + .lookupSuperTarget( + method, getContext(), appViewWithClassHierarchy)); if (superTarget != null) { superTargets.add(superTarget); }
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java index 8d589a8..77d3764 100644 --- a/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/MonomorphicVirtualMethodsAnalysis.java
@@ -3,11 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.optimize.singlecaller; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysisBase; -import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.List; @@ -18,12 +18,13 @@ public class MonomorphicVirtualMethodsAnalysis extends VirtualRootMethodsAnalysisBase { public MonomorphicVirtualMethodsAnalysis( - AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) { + AppView<? extends AppInfoWithClassHierarchy> appView, + ImmediateProgramSubtypingInfo immediateSubtypingInfo) { super(appView, immediateSubtypingInfo); } public static ProgramMethodSet computeMonomorphicVirtualRootMethods( - AppView<AppInfoWithLiveness> appView, + AppView<? extends AppInfoWithClassHierarchy> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo, List<Set<DexProgramClass>> stronglyConnectedComponents, ExecutorService executorService) @@ -43,7 +44,7 @@ } private static ProgramMethodSet computeMonomorphicVirtualRootMethodsInComponent( - AppView<AppInfoWithLiveness> appView, + AppView<? extends AppInfoWithClassHierarchy> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo, Set<DexProgramClass> stronglyConnectedComponent) { MonomorphicVirtualMethodsAnalysis analysis =
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java index 5645247..e16c128 100644 --- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java +++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexMethod; @@ -69,7 +70,7 @@ public void run(ExecutorService executorService) throws ExecutionException { ProgramMethodSet monomorphicVirtualMethods = - computeMonomorphicVirtualRootMethods(executorService); + computeMonomorphicVirtualRootMethods(appView, executorService); ProgramMethodMap<ProgramMethod> singleCallerMethods = new SingleCallerScanner(appView, monomorphicVirtualMethods) .getSingleCallerMethods(executorService); @@ -87,8 +88,8 @@ // deal with (rooted) virtual methods that do not override abstract/interface methods. In order to // also deal with virtual methods that override abstract/interface methods we would need to record // calls to the abstract/interface methods as calls to the non-abstract virtual method. - @SuppressWarnings("UnusedMethod") - private ProgramMethodSet computeMonomorphicVirtualRootMethods(ExecutorService executorService) + public static ProgramMethodSet computeMonomorphicVirtualRootMethods( + AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService) throws ExecutionException { ImmediateProgramSubtypingInfo immediateSubtypingInfo = ImmediateProgramSubtypingInfo.create(appView);
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java index ff8c494..dd76864 100644 --- a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java +++ b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
@@ -4,12 +4,19 @@ package com.android.tools.r8.profile; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.profile.AbstractProfile.Builder; import com.android.tools.r8.utils.ThrowingConsumer; +import java.util.function.BiConsumer; public interface AbstractProfile< - ClassRule extends AbstractProfileClassRule, MethodRule extends AbstractProfileMethodRule> { + ClassRule extends AbstractProfileClassRule, + MethodRule extends AbstractProfileMethodRule, + Profile extends AbstractProfile<ClassRule, MethodRule, Profile, ProfileBuilder>, + ProfileBuilder extends Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> { boolean containsClassRule(DexType type); @@ -24,18 +31,60 @@ MethodRule getMethodRule(DexMethod method); + ProfileBuilder toEmptyBuilderWithCapacity(); + + default Profile toProfileWithSuperclasses(AppView<?> appView) { + return transform( + (classRule, builder) -> builder.addClassAndParentClasses(classRule.getReference(), appView), + (methodRule, builder) -> { + builder.addClassAndParentClasses(methodRule.getReference().getHolderType(), appView); + builder.addMethodRule(methodRule); + }); + } + + default Profile transform( + BiConsumer<ClassRule, ProfileBuilder> classRuleTransformer, + BiConsumer<MethodRule, ProfileBuilder> methodRuleTransformer) { + ProfileBuilder builder = toEmptyBuilderWithCapacity(); + forEachRule( + classRule -> classRuleTransformer.accept(classRule, builder), + methodRule -> methodRuleTransformer.accept(methodRule, builder)); + return builder.build(); + } + interface Builder< ClassRule extends AbstractProfileClassRule, MethodRule extends AbstractProfileMethodRule, - Profile extends AbstractProfile<ClassRule, MethodRule>, + Profile extends AbstractProfile<ClassRule, MethodRule, Profile, ProfileBuilder>, ProfileBuilder extends Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> { ProfileBuilder addRule(AbstractProfileRule rule); ProfileBuilder addClassRule(ClassRule classRule); + boolean addClassRule(DexType type); + + default void addClassAndParentClasses(DexType type, AppView<?> appView) { + DexProgramClass definition = appView.app().programDefinitionFor(type); + if (definition != null) { + addClassAndParentClasses(definition, appView); + } + } + + private void addClassAndParentClasses(DexProgramClass clazz, AppView<?> appView) { + if (addClassRule(clazz.getType())) { + addParentClasses(clazz, appView); + } + } + + private void addParentClasses(DexProgramClass clazz, AppView<?> appView) { + clazz.forEachImmediateSupertype(supertype -> addClassAndParentClasses(supertype, appView)); + } + ProfileBuilder addMethodRule(MethodRule methodRule); Profile build(); + + int size(); } }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java index f33323f..16058e4 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -4,8 +4,6 @@ package com.android.tools.r8.profile.art; -import static com.android.tools.r8.utils.MapUtils.ignoreKey; - import com.android.tools.r8.TextInputStream; import com.android.tools.r8.TextOutputStream; import com.android.tools.r8.graph.AppInfo; @@ -24,18 +22,17 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.ThrowingConsumer; -import com.android.tools.r8.utils.TriConsumer; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UncheckedIOException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Function; -public class ArtProfile implements AbstractProfile<ArtProfileClassRule, ArtProfileMethodRule> { +public class ArtProfile + implements AbstractProfile< + ArtProfileClassRule, ArtProfileMethodRule, ArtProfile, ArtProfile.Builder> { private final Map<DexReference, ArtProfileRule> rules; @@ -52,6 +49,15 @@ return new Builder(artProfileProvider, options); } + public static Builder builderWithCapacity(int capacity) { + return new Builder(capacity); + } + + @Override + public Builder toEmptyBuilderWithCapacity() { + return builderWithCapacity(rules.size()); + } + @Override public boolean containsClassRule(DexType type) { return rules.containsKey(type); @@ -99,31 +105,33 @@ return rewrittenWithLens(appView, lens.asEnumUnboxerLens()); } return transform( - (classRule, classRuleBuilderFactory) -> { + (classRule, builder) -> { DexType newClassRule = lens.lookupType(classRule.getType()); assert newClassRule.isClassType(); - classRuleBuilderFactory.accept(newClassRule); + builder.addClassRule(ArtProfileClassRule.builder().setType(newClassRule).build()); }, - (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> - methodRuleBuilderFactory - .apply(lens.getRenamedMethodSignature(methodRule.getMethod())) - .acceptMethodRuleInfoBuilder( - methodRuleInfoBuilder -> - methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()))); + (methodRule, builder) -> + builder.addMethodRule( + ArtProfileMethodRule.builder() + .setMethod(lens.getRenamedMethodSignature(methodRule.getMethod())) + .acceptMethodRuleInfoBuilder( + methodRuleInfoBuilder -> + methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())) + .build())); } @SuppressWarnings("ReferenceEquality") public ArtProfile rewrittenWithLens(AppView<?> appView, EnumUnboxingLens lens) { return transform( - (classRule, classRuleBuilderFactory) -> { + (classRule, builder) -> { DexType newClassRule = lens.lookupType(classRule.getType()); if (newClassRule.isClassType()) { - classRuleBuilderFactory.accept(newClassRule); + builder.addClassRule(ArtProfileClassRule.builder().setType(newClassRule).build()); } else { assert newClassRule.isIntType(); } }, - (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> { + (methodRule, builder) -> { DexMethod newMethod = lens.getRenamedMethodSignature(methodRule.getMethod()); // When moving non-synthetic methods from an enum class to its enum utility class we also // add a rule for the utility class. @@ -132,47 +140,61 @@ .getSyntheticItems() .isSyntheticOfKind( newMethod.getHolderType(), naming -> naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS); - classRuleBuilderFactory.accept(newMethod.getHolderType()); + builder.addClassRule( + ArtProfileClassRule.builder().setType(newMethod.getHolderType()).build()); } - methodRuleBuilderFactory - .apply(newMethod) - .acceptMethodRuleInfoBuilder( - methodRuleInfoBuilder -> - methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())); + builder.addMethodRule( + ArtProfileMethodRule.builder() + .setMethod(newMethod) + .acceptMethodRuleInfoBuilder( + methodRuleInfoBuilder -> + methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())) + .build()); }); } public ArtProfile rewrittenWithLens(AppView<?> appView, NamingLens lens) { + if (lens.isIdentityLens()) { + return this; + } DexItemFactory dexItemFactory = appView.dexItemFactory(); assert !lens.isIdentityLens(); return transform( - (classRule, classRuleBuilderFactory) -> - classRuleBuilderFactory.accept(lens.lookupType(classRule.getType(), dexItemFactory)), - (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> - methodRuleBuilderFactory - .apply(lens.lookupMethod(methodRule.getMethod(), dexItemFactory)) - .acceptMethodRuleInfoBuilder( - methodRuleInfoBuilder -> - methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()))); + (classRule, builder) -> + builder.addClassRule( + ArtProfileClassRule.builder() + .setType(lens.lookupType(classRule.getType(), dexItemFactory)) + .build()), + (methodRule, builder) -> + builder.addMethodRule( + ArtProfileMethodRule.builder() + .setMethod(lens.lookupMethod(methodRule.getMethod(), dexItemFactory)) + .acceptMethodRuleInfoBuilder( + methodRuleInfoBuilder -> + methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())) + .build())); } public ArtProfile withoutMissingItems(AppView<?> appView) { AppInfo appInfo = appView.appInfo(); return transform( - (classRule, classRuleBuilderFactory) -> { + (classRule, builder) -> { if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getType())) { - classRuleBuilderFactory.accept(classRule.getType()); + builder.addClassRule( + ArtProfileClassRule.builder().setType(classRule.getType()).build()); } }, - (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> { + (methodRule, builder) -> { DexClass clazz = appInfo.definitionForWithoutExistenceAssert(methodRule.getMethod().getHolderType()); if (methodRule.getMethod().isDefinedOnClass(clazz)) { - methodRuleBuilderFactory - .apply(methodRule.getMethod()) - .acceptMethodRuleInfoBuilder( - methodRuleInfoBuilder -> - methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())); + builder.addMethodRule( + ArtProfileMethodRule.builder() + .setMethod(methodRule.getMethod()) + .acceptMethodRuleInfoBuilder( + methodRuleInfoBuilder -> + methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())) + .build()); } }); } @@ -182,40 +204,6 @@ return this; } - private ArtProfile transform( - BiConsumer<ArtProfileClassRule, Consumer<DexType>> classTransformation, - TriConsumer< - ArtProfileMethodRule, - Consumer<DexType>, - Function<DexMethod, ArtProfileMethodRule.Builder>> - methodTransformation) { - Map<DexReference, ArtProfileRule.Builder> ruleBuilders = new LinkedHashMap<>(); - Consumer<DexType> classRuleBuilderFactory = - newType -> - ruleBuilders - .computeIfAbsent( - newType, ignoreKey(() -> ArtProfileClassRule.builder().setType(newType))) - .asClassRuleBuilder(); - Function<DexMethod, ArtProfileMethodRule.Builder> methodRuleBuilderFactory = - newMethod -> - ruleBuilders - .computeIfAbsent( - newMethod, ignoreKey(() -> ArtProfileMethodRule.builder().setMethod(newMethod))) - .asMethodRuleBuilder(); - forEachRule( - // Supply a factory method for creating a builder. If the current rule should be included in - // the rewritten profile, the caller should call the provided builder factory method to - // create a class rule builder. If two rules are mapped to the same reference, the same rule - // builder is reused so that the two rules are merged into a single rule (with their flags - // merged). - classRule -> classTransformation.accept(classRule, classRuleBuilderFactory), - // As above. - methodRule -> - methodTransformation.accept( - methodRule, classRuleBuilderFactory, methodRuleBuilderFactory)); - return builder().addRuleBuilders(ruleBuilders.values()).build(); - } - public void supplyConsumer(ArtProfileConsumer consumer, Reporter reporter) { if (consumer != null) { TextOutputStream textOutputStream = consumer.getHumanReadableArtProfileConsumer(); @@ -263,12 +251,21 @@ private final ArtProfileProvider artProfileProvider; private final DexItemFactory dexItemFactory; private Reporter reporter; - private final Map<DexReference, ArtProfileRule> rules = new LinkedHashMap<>(); + private final Map<DexReference, ArtProfileRule> rules; Builder() { + this(new LinkedHashMap<>()); + } + + Builder(Map<DexReference, ArtProfileRule> rules) { this.artProfileProvider = null; this.dexItemFactory = null; this.reporter = null; + this.rules = rules; + } + + Builder(int capacity) { + this(new LinkedHashMap<>(capacity)); } // Constructor for building the initial ART profile. The input is based on the Reference API, so @@ -278,6 +275,7 @@ this.artProfileProvider = artProfileProvider; this.dexItemFactory = options.dexItemFactory(); this.reporter = options.reporter; + this.rules = new LinkedHashMap<>(); } @Override @@ -292,6 +290,13 @@ } @Override + public boolean addClassRule(DexType type) { + int oldSize = size(); + addClassRule(ArtProfileClassRule.builder().setType(type).build()); + return size() > oldSize; + } + + @Override public Builder addMethodRule(ArtProfileMethodRule methodRule) { rules.compute( methodRule.getReference(), @@ -351,5 +356,10 @@ public ArtProfile build() { return new ArtProfile(rules); } + + @Override + public int size() { + return rules.size(); + } } }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java index c0b377f..527734f 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
@@ -105,7 +105,7 @@ private final DexItemFactory dexItemFactory; private DexMethod method; - private ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder = + private final ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder = ArtProfileMethodRuleInfoImpl.builder(); Builder() {
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java index c98371b..926bab4 100644 --- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java +++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -81,19 +81,18 @@ return; } } - NonEmptyArtProfileCollection collection = - appView.getNamingLens().isIdentityLens() - ? this - : rewrittenWithLens(appView, appView.getNamingLens()); InternalOptions options = appView.options(); Collection<ArtProfileForRewriting> inputs = options.getArtProfileOptions().getArtProfilesForRewriting(); assert !inputs.isEmpty(); - assert collection.artProfiles.size() == inputs.size(); + assert artProfiles.size() == inputs.size(); Iterator<ArtProfileForRewriting> inputIterator = inputs.iterator(); - for (ArtProfile artProfile : collection.artProfiles) { + for (ArtProfile artProfile : artProfiles) { ArtProfileForRewriting input = inputIterator.next(); - artProfile.supplyConsumer(input.getResidualArtProfileConsumer(), options.reporter); + artProfile + .toProfileWithSuperclasses(appView) + .rewrittenWithLens(appView, appView.getNamingLens()) + .supplyConsumer(input.getResidualArtProfileConsumer(), options.reporter); } }
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileAdditions.java index 877df9a..0269114 100644 --- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileAdditions.java +++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileAdditions.java
@@ -47,7 +47,7 @@ MethodRule extends AbstractProfileMethodRule, MethodRuleBuilder extends AbstractProfileMethodRule.Builder<MethodRule, MethodRuleBuilder>, ProfileRule extends AbstractProfileRule, - Profile extends AbstractProfile<ClassRule, MethodRule>, + Profile extends AbstractProfile<ClassRule, MethodRule, Profile, ProfileBuilder>, ProfileBuilder extends AbstractProfile.Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java index e6406bc..62ee585 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java +++ b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
@@ -17,6 +17,14 @@ public class StartupOptions { /** + * When enabled, attempts to move or outline all non-startup methods on startup classes. + * + * <p>Currently only supported in R8. + */ + private boolean enableOutlining = + parseSystemPropertyOrDefault("com.android.tools.r8.startup.outline", false); + + /** * When enabled, all startup classes will be placed in the primary classes.dex file. All other * (non-startup) classes will be placed in classes2.dex, ..., classesN.dex. */ @@ -68,6 +76,10 @@ Collections::emptyList); } + public boolean isOutliningEnabled() { + return enableOutlining; + } + public boolean isMinimalStartupDexEnabled() { return enableMinimalStartupDex; } @@ -99,6 +111,11 @@ return enableStartupLayoutOptimization; } + public StartupOptions setEnableOutlining(boolean enableOutlining) { + this.enableOutlining = enableOutlining; + return this; + } + public StartupOptions setEnableStartupCompletenessCheckForTesting() { return setEnableStartupCompletenessCheckForTesting(true); }
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java index a051dc9..666338a 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/EmptyStartupProfile.java
@@ -66,6 +66,11 @@ } @Override + public Builder toEmptyBuilderWithCapacity() { + return builder(); + } + + @Override public EmptyStartupProfile toStartupProfileForWriting(AppView<?> appView) { return this; }
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java index 5ccba97..f675a24 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
@@ -8,7 +8,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexReference; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.PrunedItems; @@ -21,7 +20,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; public class NonEmptyStartupProfile extends StartupProfile { @@ -105,6 +103,11 @@ return startupRules.size(); } + @Override + public Builder toEmptyBuilderWithCapacity() { + return builderWithCapacity(size()); + } + /** * This is called to process the startup profile before computing the startup layouts. * @@ -118,42 +121,7 @@ */ @Override public StartupProfile toStartupProfileForWriting(AppView<?> appView) { - return transform( - (classRule, builder) -> addStartupItem(classRule, builder, appView), - (methodRule, builder) -> addStartupItem(methodRule, builder, appView)); - } - - private static void addStartupItem( - StartupProfileRule startupItem, Builder builder, AppView<?> appView) { - startupItem.accept( - classRule -> addClassAndParentClasses(classRule.getReference(), builder, appView), - builder::addMethodRule); - } - - private static boolean addClass(DexProgramClass clazz, Builder builder) { - int oldSize = builder.size(); - builder.addClassRule( - StartupProfileClassRule.builder().setClassReference(clazz.getType()).build()); - return builder.size() > oldSize; - } - - private static void addClassAndParentClasses(DexType type, Builder builder, AppView<?> appView) { - DexProgramClass definition = appView.app().programDefinitionFor(type); - if (definition != null) { - addClassAndParentClasses(definition, builder, appView); - } - } - - private static void addClassAndParentClasses( - DexProgramClass clazz, Builder builder, AppView<?> appView) { - if (addClass(clazz, builder)) { - addParentClasses(clazz, builder, appView); - } - } - - private static void addParentClasses(DexProgramClass clazz, Builder builder, AppView<?> appView) { - clazz.forEachImmediateSupertype( - supertype -> addClassAndParentClasses(supertype, builder, appView)); + return toProfileWithSuperclasses(appView); } @Override @@ -194,14 +162,4 @@ timing.end(); return result; } - - private StartupProfile transform( - BiConsumer<StartupProfileClassRule, Builder> classRuleTransformer, - BiConsumer<StartupProfileMethodRule, Builder> methodRuleTransformer) { - Builder builder = builderWithCapacity(startupRules.size()); - forEachRule( - classRule -> classRuleTransformer.accept(classRule, builder), - methodRule -> methodRuleTransformer.accept(methodRule, builder)); - return builder.build(); - } }
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java index 8ee1fd5..e6a4cb5 100644 --- a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java +++ b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
@@ -36,7 +36,8 @@ import java.util.function.Function; public abstract class StartupProfile - implements AbstractProfile<StartupProfileClassRule, StartupProfileMethodRule> { + implements AbstractProfile< + StartupProfileClassRule, StartupProfileMethodRule, StartupProfile, StartupProfile.Builder> { protected StartupProfile() {} @@ -195,6 +196,13 @@ } @Override + public boolean addClassRule(DexType type) { + int oldSize = size(); + addClassRule(StartupProfileClassRule.builder().setClassReference(type).build()); + return size() > oldSize; + } + + @Override public Builder addMethodRule(StartupProfileMethodRule methodRule) { return addStartupItem(methodRule); } @@ -258,6 +266,7 @@ return this; } + @Override public int size() { return startupItems.size(); }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 530ec46..abee4a2 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3372,6 +3372,8 @@ markFieldAsLive(field, context, reason); } + handleFieldAccessWithInaccessibleFieldType(field, context); + if (liveFields.contains(field) || !reachableInstanceFields .computeIfAbsent(field.getHolder(), ignore -> ProgramFieldSet.create()) @@ -3386,6 +3388,21 @@ analyses.forEach(analysis -> analysis.notifyMarkFieldAsReachable(field, worklist)); } + private void handleFieldAccessWithInaccessibleFieldType( + ProgramField field, ProgramDefinition context) { + if (mode.isFinalTreeShaking() && options.isOptimizing() && !field.getAccessFlags().isStatic()) { + DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory()); + if (fieldBaseType.isClassType()) { + DexClass clazz = definitionFor(fieldBaseType, context); + if (clazz != null + && AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) { + applyMinimumKeepInfoWhenLive( + field.getHolder(), KeepClassInfo.newEmptyJoiner().disallowHorizontalClassMerging()); + } + } + } + } + private void traceFieldDefinition(ProgramField field) { markTypeAsLive(field.getHolder(), field); markTypeAsLive(field.getType(), field);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java index e524b2a..990c260 100644 --- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -440,13 +440,16 @@ assert !info.isPinned(options) || info.isMinificationAllowed(options) || newMethod.name.isIdenticalTo(method.name); - assert !info.isPinned(options) || newMethod.getArity() == method.getArity(); + assert !info.isPinned(options) + || newMethod.getArity() == method.getArity() + || (info.isShrinkingAllowed(options) && lens.isNonStartupInStartupOutlinerLens()); assert !info.isPinned(options) || Streams.zip( newMethod.getParameters().stream(), method.getParameters().stream().map(lens::lookupType), Object::equals) - .allMatch(x -> x); + .allMatch(x -> x) + || (info.isShrinkingAllowed(options) && lens.isNonStartupInStartupOutlinerLens()); assert !info.isPinned(options) || newMethod.getReturnType().isIdenticalTo(lens.lookupType(method.getReturnType())); KeepMethodInfo previous = newMethodInfo.put(newMethod, info);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepSpecificationSource.java b/src/main/java/com/android/tools/r8/shaking/KeepSpecificationSource.java new file mode 100644 index 0000000..1dfda01 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/KeepSpecificationSource.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2024, 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.shaking; + +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.keepanno.ast.KeepDeclaration; +import com.android.tools.r8.keepanno.ast.KeepSpecVersion; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.Declaration; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.KeepSpec; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.origin.PathOrigin; +import com.google.protobuf.InvalidProtocolBufferException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Consumer; + +public abstract class KeepSpecificationSource { + + public static KeepSpecificationSource fromFile(Path path) { + return new KeepSpecificationFile(path); + } + + public static KeepSpecificationSource fromBytes(Origin origin, byte[] bytes) { + return new KeepSpecificationBytes(origin, bytes); + } + + private final Origin origin; + + private KeepSpecificationSource(Origin origin) { + this.origin = origin; + } + + public Origin getOrigin() { + return origin; + } + + public void parse(Consumer<KeepDeclaration> consumer) throws ResourceException { + KeepSpec spec = read(); + KeepSpecVersion version = KeepSpecVersion.fromProto(spec.getVersion()); + if (version == KeepSpecVersion.UNKNOWN) { + throw new ResourceException(getOrigin(), "Unknown keepspec version " + spec.getVersion()); + } + for (Declaration declaration : spec.getDeclarationsList()) { + KeepDeclaration parsedDeclaration = KeepDeclaration.fromProto(declaration, version); + if (parsedDeclaration == null) { + throw new ResourceException(getOrigin(), "Unable to parse declaration " + declaration); + } else { + consumer.accept(parsedDeclaration); + } + } + } + + abstract KeepSpec read() throws ResourceException; + + private static class KeepSpecificationFile extends KeepSpecificationSource { + + private final Path path; + + private KeepSpecificationFile(Path path) { + super(new PathOrigin(path)); + this.path = path; + } + + @Override + KeepSpec read() throws ResourceException { + try (InputStream stream = Files.newInputStream(path)) { + return KeepSpec.parseFrom(stream); + } catch (IOException e) { + throw new ResourceException(getOrigin(), e); + } + } + } + + private static class KeepSpecificationBytes extends KeepSpecificationSource { + + private final byte[] content; + + public KeepSpecificationBytes(Origin origin, byte[] bytes) { + super(origin); + this.content = bytes; + } + + @Override + KeepSpec read() throws ResourceException { + try { + return KeepSpec.parseFrom(content); + } catch (InvalidProtocolBufferException e) { + throw new ResourceException(getOrigin(), e); + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java index acdd37c..a584eb4 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -72,9 +72,10 @@ "com.android.tools.r8.experimental.enablewhyareyounotinlining", false); enableTestingOptions = parseSystemPropertyOrDefault("com.android.tools.r8.allowTestProguardOptions", false); + // TODO(b/323136645): This should default to false. forceEnableEmptyMemberRulesToDefaultInitRuleConversion = parseSystemPropertyOrDefault( - "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion", false); + "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion", true); return this; }
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/MaterializedConditionalRule.java b/src/main/java/com/android/tools/r8/shaking/rules/MaterializedConditionalRule.java index cbae8fe..b6c85d0 100644 --- a/src/main/java/com/android/tools/r8/shaking/rules/MaterializedConditionalRule.java +++ b/src/main/java/com/android/tools/r8/shaking/rules/MaterializedConditionalRule.java
@@ -26,23 +26,8 @@ } public boolean pruneItems(PrunedItems prunedItems) { - for (DexReference precondition : preconditions) { - if (precondition.isDexType()) { - if (prunedItems.getRemovedClasses().contains(precondition.asDexType())) { - return true; - } - } else if (precondition.isDexField()) { - if (prunedItems.getRemovedFields().contains(precondition.asDexField())) { - return true; - } - } else { - assert precondition.isDexMethod(); - if (prunedItems.getRemovedMethods().contains(precondition.asDexMethod())) { - return true; - } - } - } - // Preconditions are in place, so trim down consequences. + // Preconditions cannot be pruned as they reference "original" program references which may be + // in inlined positions even when the items themselves are "pruned". consequences.pruneItems(prunedItems); return consequences.isEmpty(); }
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java new file mode 100644 index 0000000..5e5f3bd --- /dev/null +++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
@@ -0,0 +1,400 @@ +// Copyright (c) 2024, 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.startup; + +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.contexts.CompilationContext.ProcessorContext; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.Code; +import com.android.tools.r8.graph.DefaultUseRegistryWithResult; +import com.android.tools.r8.graph.DexClassAndField; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.graph.MethodResolutionResult; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder; +import com.android.tools.r8.optimize.singlecaller.SingleCallerInliner; +import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; +import com.android.tools.r8.profile.startup.profile.StartupProfile; +import com.android.tools.r8.shaking.KeepMethodInfo.Joiner; +import com.android.tools.r8.synthesis.CommittedItems; +import com.android.tools.r8.synthesis.SyntheticItems; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +// TODO(b/275292237): Preserve class initialization side effects using InitClass. +// TODO(b/275292237): Preserve receiver NPE semantics by inserting null checks. +public class NonStartupInStartupOutliner { + + private final AppView<? extends AppInfoWithClassHierarchy> appView; + private final DexItemFactory factory; + private final NonStartupInStartupOutlinerLens.Builder lensBuilder = + NonStartupInStartupOutlinerLens.builder(); + private final StartupProfile startupProfile; + private final ProgramMethodSet syntheticMethods; + + public NonStartupInStartupOutliner(AppView<? extends AppInfoWithClassHierarchy> appView) { + this.appView = appView; + this.factory = appView.dexItemFactory(); + this.startupProfile = appView.getStartupProfile(); + this.syntheticMethods = ProgramMethodSet.createConcurrent(); + } + + public void runIfNecessary(ExecutorService executorService, Timing timing) + throws ExecutionException { + if (!startupProfile.isEmpty() && appView.options().getStartupOptions().isOutliningEnabled()) { + timing.time("NonStartupInStartupOutliner", () -> run(executorService, timing)); + } + } + + private void run(ExecutorService executorService, Timing timing) throws ExecutionException { + Map<DexProgramClass, List<ProgramMethod>> methodsToOutline = + getMethodsToOutline(executorService); + if (methodsToOutline.isEmpty()) { + return; + } + ProfileCollectionAdditions profileCollectionAdditions = + ProfileCollectionAdditions.create(appView); + performOutlining(methodsToOutline, executorService, profileCollectionAdditions); + profileCollectionAdditions.commit(appView); + commitPendingSyntheticClasses(); + setSyntheticKeepInfo(); + rewriteWithLens(executorService, timing); + } + + private Map<DexProgramClass, List<ProgramMethod>> getMethodsToOutline( + ExecutorService executorService) throws ExecutionException { + Map<DexProgramClass, List<ProgramMethod>> methodsToOutline = new ConcurrentHashMap<>(); + ThreadUtils.processItems( + appView.appInfo().classes(), + clazz -> + forEachMethodToOutline( + clazz, + method -> + methodsToOutline.computeIfAbsent(clazz, ignoreKey(ArrayList::new)).add(method)), + appView.options().getThreadingModule(), + executorService); + return methodsToOutline; + } + + private void forEachMethodToOutline(DexProgramClass clazz, Consumer<ProgramMethod> fn) { + if (!startupProfile.isStartupClass(clazz.getType())) { + return; + } + clazz.forEachProgramMethodMatching( + DexEncodedMethod::hasCode, + method -> { + if (!startupProfile.containsMethodRule(method.getReference()) + && !method.getDefinition().isInitializer() + && canCodeBeMoved(method)) { + fn.accept(method); + } + }); + } + + // TODO(b/275292237): Extend to cover all possible accesses to private items (e.g., consider + // method handles). + private boolean canCodeBeMoved(ProgramMethod method) { + return method.registerCodeReferencesWithResult( + new DefaultUseRegistryWithResult<>(appView, method, true) { + + private AppInfoWithClassHierarchy appInfo() { + return NonStartupInStartupOutliner.this.appView.appInfo(); + } + + private void setCodeCannotBeMoved() { + setResult(false); + } + + // Field accesses. + + @Override + public void registerInstanceFieldRead(DexField field) { + registerFieldAccess(field); + } + + @Override + public void registerInstanceFieldWrite(DexField field) { + registerFieldAccess(field); + } + + @Override + public void registerStaticFieldRead(DexField field) { + registerFieldAccess(field); + } + + @Override + public void registerStaticFieldWrite(DexField field) { + registerFieldAccess(field); + } + + private void registerFieldAccess(DexField field) { + DexClassAndField resolvedField = + appInfo().resolveField(field, getContext()).getResolutionPair(); + if (resolvedField == null) { + return; + } + if (resolvedField.getAccessFlags().isPrivate()) { + setCodeCannotBeMoved(); + } else if (resolvedField.getAccessFlags().isProtected() + && !resolvedField.isSamePackage(getContext())) { + setCodeCannotBeMoved(); + } + } + + // Invokes. + + @Override + public void registerInvokeDirect(DexMethod method) { + registerInvokeMethod(appInfo().unsafeResolveMethodDueToDexFormat(method)); + } + + @Override + public void registerInvokeInterface(DexMethod method) { + registerInvokeMethod(appInfo().resolveMethod(method, true)); + } + + @Override + public void registerInvokeStatic(DexMethod method) { + registerInvokeMethod(appInfo().unsafeResolveMethodDueToDexFormat(method)); + } + + @Override + public void registerInvokeSuper(DexMethod method) { + setCodeCannotBeMoved(); + } + + @Override + public void registerInvokeVirtual(DexMethod method) { + registerInvokeMethod(appInfo().resolveMethod(method, false)); + } + + private void registerInvokeMethod(MethodResolutionResult resolutionResult) { + DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair(); + if (resolvedMethod == null) { + return; + } + if (resolvedMethod.getAccessFlags().isPrivate()) { + setCodeCannotBeMoved(); + } else if (resolvedMethod.getAccessFlags().isProtected() + && !resolvedMethod.isSamePackage(getContext())) { + setCodeCannotBeMoved(); + } + } + }); + } + + private void performOutlining( + Map<DexProgramClass, List<ProgramMethod>> methodsToOutline, + ExecutorService executorService, + ProfileCollectionAdditions profileCollectionAdditions) + throws ExecutionException { + // TODO(b/275292237): Only compute this information for virtual methods in startup classes. + ProcessorContext processorContext = appView.createProcessorContext(); + ProgramMethodSet monomorphicVirtualMethods = + SingleCallerInliner.computeMonomorphicVirtualRootMethods(appView, executorService); + ThreadUtils.processMap( + methodsToOutline, + (clazz, methods) -> + performOutliningForClass( + clazz, + methods, + monomorphicVirtualMethods, + processorContext, + profileCollectionAdditions), + appView.options().getThreadingModule(), + executorService); + } + + private void performOutliningForClass( + DexProgramClass clazz, + List<ProgramMethod> methodsToOutline, + ProgramMethodSet monomorphicVirtualMethods, + ProcessorContext processorContext, + ProfileCollectionAdditions profileCollectionAdditions) { + Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet(); + for (ProgramMethod method : methodsToOutline) { + MethodProcessingContext methodProcessingContext = + processorContext.createMethodProcessingContext(method); + ProgramMethod syntheticMethod; + boolean isMove = isMoveable(method, monomorphicVirtualMethods); + if (isMove) { + syntheticMethod = performMove(method, methodProcessingContext); + methodsToRemove.add(method.getDefinition()); + } else { + syntheticMethod = performOutliningForMethod(method, methodProcessingContext); + } + profileCollectionAdditions.applyIfContextIsInProfile( + method.getReference(), + additionsBuilder -> { + additionsBuilder + .addClassRule(syntheticMethod.getHolderType()) + .addMethodRule(syntheticMethod.getReference()); + if (isMove) { + additionsBuilder.removeMovedMethodRule(method, syntheticMethod); + } + }); + syntheticMethods.add(syntheticMethod); + } + clazz.getMethodCollection().removeMethods(methodsToRemove); + } + + private boolean isMoveable(ProgramMethod method, ProgramMethodSet monomorphicVirtualMethods) { + // If we extend this to D8 then we can never move any methods since this would require a mapping + // file for retracing. + assert appView.enableWholeProgramOptimizations(); + if (!appView.getKeepInfo(method).isShrinkingAllowed(appView.options())) { + // Kept methods can never be moved. + return false; + } + if (method.getAccessFlags().isStatic()) { + // Static methods can always be moved. Class initialization side effects can be preserved by + // inserting an InitClass instruction in the beginning of the moved method. + return true; + } + if (method.getAccessFlags().isPrivate()) { + // Private methods have direct dispatch and can always be made public static. + return true; + } + // Virtual methods can only be staticized and moved if they are monomorphic. + assert method.getAccessFlags().belongsToVirtualPool(); + return method.getDefinition().isLibraryMethodOverride().isFalse() + && monomorphicVirtualMethods.contains(method); + } + + private ProgramMethod performMove( + ProgramMethod method, MethodProcessingContext methodProcessingContext) { + ProgramMethod movedMethod = + createSyntheticMethod( + method, + methodProcessingContext, + method.getAccessFlags().copy().promoteToPublic().promoteToStatic()); + + // Record the move in the lens for correct lens code rewriting. + lensBuilder.recordMove(method, movedMethod); + + return movedMethod; + } + + private ProgramMethod performOutliningForMethod( + ProgramMethod method, MethodProcessingContext methodProcessingContext) { + ProgramMethod outlinedMethod = + createSyntheticMethod( + method, methodProcessingContext, MethodAccessFlags.createPublicStaticSynthetic()); + + // Rewrite the non-synthetic method to call the synthetic method. + method.setCode( + ForwardMethodBuilder.builder(factory) + .applyIf( + method.getAccessFlags().isStatic(), + codeBuilder -> codeBuilder.setStaticSource(method.getReference()), + codeBuilder -> codeBuilder.setNonStaticSource(method.getReference())) + .setStaticTarget(outlinedMethod.getReference(), false) + .buildLir(appView), + appView); + + return outlinedMethod; + } + + private ProgramMethod createSyntheticMethod( + ProgramMethod method, + MethodProcessingContext methodProcessingContext, + MethodAccessFlags accessFlags) { + return appView + .getSyntheticItems() + .createMethod( + kinds -> kinds.NON_STARTUP_IN_STARTUP_OUTLINE, + methodProcessingContext.createUniqueContext(), + appView, + builder -> + builder + .setAccessFlags(accessFlags) + .setApiLevelForCode(method.getDefinition().getApiLevelForCode()) + .setApiLevelForDefinition(method.getDefinition().getApiLevelForDefinition()) + .setProto( + factory.prependHolderToProtoIf( + method.getReference(), !method.getAccessFlags().isStatic())) + .setCode( + syntheticMethod -> { + Code code; + if (method.getDefinition().getCode().supportsPendingInlineFrame()) { + code = method.getDefinition().getCode(); + builder.setInlineFrame( + method.getReference(), method.getDefinition().isD8R8Synthesized()); + } else { + code = + method + .getDefinition() + .getCode() + .getCodeAsInlining( + syntheticMethod, + true, + method.getReference(), + method.getDefinition().isD8R8Synthesized(), + factory); + } + if (!method.getAccessFlags().isStatic()) { + DexEncodedMethod.setDebugInfoWithFakeThisParameter( + code, syntheticMethod.getArity(), appView); + } + return code; + })); + } + + private void commitPendingSyntheticClasses() { + SyntheticItems syntheticItems = appView.getSyntheticItems(); + if (!syntheticItems.hasPendingSyntheticClasses()) { + return; + } + CommittedItems committedItems = syntheticItems.commit(appView.app()); + if (appView.hasLiveness()) { + appView + .withLiveness() + .setAppInfo(appView.appInfoWithLiveness().rebuildWithLiveness(committedItems)); + } else { + appView + .withClassHierarchy() + .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems)); + } + } + + private void setSyntheticKeepInfo() { + appView + .getKeepInfo() + .mutate( + keepInfo -> + syntheticMethods.forEach( + syntheticMethod -> { + keepInfo.registerCompilerSynthesizedMethod(syntheticMethod); + keepInfo.joinMethod(syntheticMethod, Joiner::disallowInlining); + })); + } + + private void rewriteWithLens(ExecutorService executorService, Timing timing) + throws ExecutionException { + if (lensBuilder.isEmpty()) { + return; + } + NonStartupInStartupOutlinerLens lens = lensBuilder.build(appView); + appView.rewriteWithLens(lens, executorService, timing); + } +}
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java new file mode 100644 index 0000000..61ee4ec --- /dev/null +++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerLens.java
@@ -0,0 +1,55 @@ +// Copyright (c) 2024, 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.startup; + +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.lens.NestedGraphLens; +import com.android.tools.r8.ir.code.InvokeType; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; +import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; +import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; + +public class NonStartupInStartupOutlinerLens extends NestedGraphLens { + + public NonStartupInStartupOutlinerLens( + AppView<?> appView, BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap) { + super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean isNonStartupInStartupOutlinerLens() { + return true; + } + + @Override + protected InvokeType mapInvocationType( + DexMethod newMethod, DexMethod newReboundMethod, DexMethod previousMethod, InvokeType type) { + return newMethod.isIdenticalTo(previousMethod) ? type : InvokeType.STATIC; + } + + public static class Builder { + + private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures = + new BidirectionalOneToOneHashMap<>(); + + public boolean isEmpty() { + return newMethodSignatures.isEmpty(); + } + + public synchronized void recordMove(ProgramMethod from, ProgramMethod to) { + newMethodSignatures.put(from.getReference(), to.getReference()); + } + public NonStartupInStartupOutlinerLens build( + AppView<? extends AppInfoWithClassHierarchy> appView) { + return new NonStartupInStartupOutlinerLens(appView, newMethodSignatures); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java index b758859..27ae067 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -35,6 +35,7 @@ private DexProto proto = null; private CfVersion classFileVersion; private SyntheticCodeGenerator codeGenerator = null; + private DexMethod pendingInlineFrame = null; private MethodAccessFlags accessFlags = null; private MethodTypeSignature genericSignature = MethodTypeSignature.noSignature(); private DexAnnotationSet annotations = DexAnnotationSet.empty(); @@ -95,6 +96,14 @@ return this; } + public SyntheticMethodBuilder setInlineFrame(DexMethod callee, boolean calleeIsD8R8Synthesized) { + if (!calleeIsD8R8Synthesized) { + // // No need to record a compiler synthesized inline frame. + this.pendingInlineFrame = callee; + } + return this; + } + public SyntheticMethodBuilder setAccessFlags(MethodAccessFlags accessFlags) { this.accessFlags = accessFlags; return this; @@ -144,6 +153,7 @@ .setAnnotations(annotations) .setParameterAnnotations(parameterAnnotationsList) .setCode(code) + .applyIf(pendingInlineFrame != null, b -> b.setInlineFrame(pendingInlineFrame, false)) .setClassFileVersion(classFileVersion) .setApiLevelForDefinition(apiLevelForDefinition) .setApiLevelForCode(apiLevelForCode)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java index 1ab00b6..0030cec 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -102,6 +102,8 @@ generator.forSingleMethod("ApiModelOutline"); public final SyntheticKind DESUGARED_LIBRARY_BRIDGE = generator.forSingleMethod("DesugaredLibraryBridge"); + public final SyntheticKind NON_STARTUP_IN_STARTUP_OUTLINE = + generator.forSingleMethodWithGlobalMerging("NonStartupInStartupOutline"); private final List<SyntheticKind> ALL_KINDS; private String lazyVersionHash = null;
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 b3d395b..ef49e3a 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -100,6 +100,7 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.Enqueuer; import com.android.tools.r8.shaking.GlobalKeepInfoConfiguration; +import com.android.tools.r8.shaking.KeepSpecificationSource; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardConfigurationRule; import com.android.tools.r8.threading.ThreadingModule; @@ -118,6 +119,7 @@ import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Deque; @@ -240,6 +242,8 @@ private final ProguardConfiguration proguardConfiguration; public final Reporter reporter; + private Collection<KeepSpecificationSource> keepSpecificationSources = Collections.emptyList(); + // TODO(zerny): Make this private-final once we have full program-consumer support. public ProgramConsumer programConsumer = null; @@ -314,6 +318,14 @@ return lazyThreadingModule; } + public Collection<KeepSpecificationSource> getKeepSpecifications() { + return keepSpecificationSources; + } + + public void setKeepSpecificationSources(Collection<KeepSpecificationSource> specifications) { + keepSpecificationSources = specifications; + } + private void keepDebugRelatedInformation() { assert !proguardConfiguration.isObfuscating(); getProguardConfiguration().getKeepAttributes().sourceFile = true;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java index 7f78da8..516241e 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -462,7 +462,11 @@ assert residualIsD8R8Synthesized || originalMethod.isIdenticalTo(lensOriginalMethod) // TODO(b/326562454): In some case the lens is mapping two methods to a common original. - || originalMethod.getHolderType().isIdenticalTo(lensOriginalMethod.getHolderType()); + || originalMethod.getHolderType().isIdenticalTo(lensOriginalMethod.getHolderType()) + || appView + .getSyntheticItems() + .isSyntheticOfKind( + method.getHolderType(), kinds -> kinds.NON_STARTUP_IN_STARTUP_OUTLINE); return true; }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java index f6583d2..7860fed 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -222,7 +222,12 @@ MethodLookupResult.builder(this, codeLens) .setReboundReference(newReboundReference) .setReference(newReference) - .setType(mapInvocationType(newReference, previous.getReference(), previous.getType())) + .setType( + mapInvocationType( + newReference, + newReboundReference, + previous.getReference(), + previous.getType())) .setPrototypeChanges( internalDescribePrototypeChanges( previous.getPrototypeChanges(), @@ -287,8 +292,8 @@ @Override protected InvokeType mapInvocationType( - DexMethod newMethod, DexMethod previousMethod, InvokeType type) { - if (isStaticized(newMethod)) { + DexMethod newMethod, DexMethod newReboundMethod, DexMethod previousMethod, InvokeType type) { + if (isStaticized(newReboundMethod)) { return InvokeType.STATIC; } if (type.isInterface()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InaccessibleFieldTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InaccessibleFieldTypeMergingTest.java new file mode 100644 index 0000000..185ac46 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InaccessibleFieldTypeMergingTest.java
@@ -0,0 +1,71 @@ +// Copyright (c) 2024, 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.classmerging.horizontal; + +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.classmerging.horizontal.testclasses.InaccessibleFieldTypeMergingTestClasses; +import com.android.tools.r8.classmerging.horizontal.testclasses.InaccessibleFieldTypeMergingTestClasses.BarGreeterContainer; +import com.android.tools.r8.classmerging.horizontal.testclasses.InaccessibleFieldTypeMergingTestClasses.FooGreeterContainer; +import com.android.tools.r8.classmerging.horizontal.testclasses.InaccessibleFieldTypeMergingTestClasses.Greeter; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +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 InaccessibleFieldTypeMergingTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass(), InaccessibleFieldTypeMergingTestClasses.class) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .enableInliningAnnotations() + .enableNoAccessModificationAnnotationsForClasses() + .enableNoHorizontalClassMergingAnnotations() + .enableNoMethodStaticizingAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Foo!", "Bar!"); + } + + static class Main { + + public static void main(String[] args) { + FooGreeterContainer fooGreeterContainer = createFooGreeterContainer(); + Greeter fooGreeter = fooGreeterContainer.f; + fooGreeter.greet(); + BarGreeterContainer barGreeterContainer = createBarGreeterContainer(); + Greeter barGreeter = barGreeterContainer.f; + barGreeter.greet(); + } + + @NeverInline + static FooGreeterContainer createFooGreeterContainer() { + return new FooGreeterContainer(); + } + + @NeverInline + static BarGreeterContainer createBarGreeterContainer() { + return new BarGreeterContainer(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InaccessibleFieldTypeMergingTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InaccessibleFieldTypeMergingTestClasses.java new file mode 100644 index 0000000..702f432 --- /dev/null +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InaccessibleFieldTypeMergingTestClasses.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2024, 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.classmerging.horizontal.testclasses; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoAccessModification; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; +import com.android.tools.r8.NoVerticalClassMerging; + +public class InaccessibleFieldTypeMergingTestClasses { + + @NoVerticalClassMerging + public interface Greeter { + + void greet(); + } + + @NoAccessModification + @NoHorizontalClassMerging + static class FooGreeter implements Greeter { + + @NeverInline + @NoMethodStaticizing + @Override + public void greet() { + System.out.println("Foo!"); + } + } + + public static class FooGreeterContainer { + + public FooGreeter f = new FooGreeter(); + } + + @NoAccessModification + @NoHorizontalClassMerging + static class BarGreeter implements Greeter { + + @NeverInline + @NoMethodStaticizing + @Override + public void greet() { + System.out.println("Bar!"); + } + } + + public static class BarGreeterContainer { + + public BarGreeter f = new BarGreeter(); + } +}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java index bb999d8..1bd723a 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java +++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -333,8 +333,7 @@ @Test public void testWithMembersStarRuleFullR8() throws Exception { - testWithMembersStarRule( - testForR8(parameters.getBackend()).allowUnusedProguardConfigurationRules()); + testWithMembersStarRule(testForR8(parameters.getBackend())); } // Tests for "-keepclassmembernames" and *no* minification.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/Regress343136777Test.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/Regress343136777Test.java new file mode 100644 index 0000000..2adf213 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/Regress343136777Test.java
@@ -0,0 +1,130 @@ +// Copyright (c) 2024, 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.assertThrows; + +import com.android.tools.r8.CompilationFailedException; +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 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 Regress343136777Test extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!"); + + @Test + public void testD8Debug() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .setMinApi(parameters) + .debug() + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8Release() throws Exception { + parameters.assumeDexRuntime(); + // TODO(b/343136777: This should not fail. + assertThrows( + CompilationFailedException.class, + () -> + testForD8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .setMinApi(parameters) + .release() + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + // TODO(b/343136777: This should not fail. + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .setMinApi(parameters) + .addKeepMainRule(TestClass.class) + .compile() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + static class TestClass { + /* + // This is the original code from b/343136777 failing the D8 release build. + + class b { + ArrayList<String> A = new ArrayList<>(); + } + + public static StackTraceElement getCaller(b pool, StackTraceElement[] callers, int start) { + StackTraceElement targetStackTrace = null; + boolean shouldTrace = false; + int i = start; + while (i < callers.length) { + StackTraceElement caller = callers[i]; + boolean isLogMethod = false; + for (String className : pool.A) { + if (caller.getClassName().equals(className)) { + isLogMethod = true; + break; + } + } + if (shouldTrace && !isLogMethod) { + targetStackTrace = caller; + break; + } + shouldTrace = isLogMethod; + i++; + } + return targetStackTrace; + } + */ + + public static void m() { + boolean a = false; + for (int i = 0; i < 2; i++) { + boolean b = false; + for (int j = 0; j < 2; j++) { + if (i == j) { + b = true; + break; + } + } + if (a && b) { + break; + } + a = b; + } + } + + public static void main(String[] args) { + m(); + System.out.println("Hello, world!"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java index 27b74ee..f39df8f 100644 --- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java +++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -23,11 +23,16 @@ import com.android.tools.r8.keepanno.asm.KeepEdgeReader; import com.android.tools.r8.keepanno.asm.KeepEdgeWriter; import com.android.tools.r8.keepanno.ast.KeepDeclaration; +import com.android.tools.r8.keepanno.ast.KeepSpecVersion; import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions; +import com.android.tools.r8.keepanno.proto.KeepSpecProtos.KeepSpec; +import com.android.tools.r8.keepanno.utils.Unimplemented; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -66,6 +71,12 @@ return keepAnnoParams.parameters(); } + public KeepAnnoTestBuilder addInnerClasses(Class<?> clazz) throws IOException { + return addProgramFiles(new ArrayList<>(ToolHelper.getClassFilesForInnerClasses(clazz))); + } + + public abstract KeepAnnoTestBuilder addProgramFiles(List<Path> programFiles) throws IOException; + public abstract KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException; @@ -148,6 +159,12 @@ } @Override + public KeepAnnoTestBuilder addProgramFiles(List<Path> programFiles) { + builder.addProgramFiles(programFiles); + return this; + } + + @Override public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) { builder.addProgramClasses(programClasses); return this; @@ -230,6 +247,14 @@ } @Override + public KeepAnnoTestBuilder addProgramFiles(List<Path> programFiles) throws IOException { + for (Path programFile : programFiles) { + extractAndAdd(Files.readAllBytes(programFile)); + } + return this; + } + + @Override public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException { for (Class<?> programClass : programClasses) { extractAndAdd(ToolHelper.getClassAsBytes(programClass)); @@ -258,6 +283,23 @@ if (isNormalizeEdges()) { List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(classFileData); if (!declarations.isEmpty()) { + List<KeepDeclaration> legacyExtract = new ArrayList<>(); + KeepSpec.Builder keepSpecBuilder = KeepSpec.newBuilder(); + keepSpecBuilder.setVersion(KeepSpecVersion.getCurrent().buildProto()); + for (KeepDeclaration declaration : declarations) { + try { + keepSpecBuilder.addDeclarations(declaration.buildDeclarationProto()); + } catch (Unimplemented e) { + legacyExtract.add(declaration); + } + } + builder + .getBuilder() + .addKeepSpecificationData(keepSpecBuilder.build().toByteArray(), Origin.unknown()); + if (legacyExtract.isEmpty()) { + // TODO(b/343389186): Finish the proto encoding and remove the below extraction. + return; + } String binaryName = DescriptorUtils.getBinaryNameFromDescriptor(extractClassDescriptor(classFileData)); String synthesizingTarget = binaryName + "$$ExtractedKeepEdges"; @@ -270,7 +312,7 @@ "java/lang/Object", null); KeepEdgeWriter.writeExtractedEdges( - declarations, + legacyExtract, (descriptor, visible) -> KeepAnnoTestUtils.wrap(classWriter.visitAnnotation(descriptor, visible))); classWriter.visitEnd(); @@ -320,6 +362,14 @@ } @Override + public KeepAnnoTestBuilder addProgramFiles(List<Path> programFiles) throws IOException { + List<String> rules = KeepAnnoTestUtils.extractRulesFromFiles(programFiles, extractorOptions); + builder.addProgramFiles(programFiles); + builder.addKeepRules(rules); + return this; + } + + @Override public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException { List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions); builder.addProgramClasses(programClasses); @@ -372,6 +422,14 @@ } @Override + public KeepAnnoTestBuilder addProgramFiles(List<Path> programFiles) throws IOException { + List<String> rules = KeepAnnoTestUtils.extractRulesFromFiles(programFiles, extractorOptions); + builder.addProgramFiles(programFiles); + builder.addKeepRules(rules); + return this; + } + + @Override public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException { List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions); builder.addProgramClasses(programClasses);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepTypePatternWithInstanceOfTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepTypePatternWithInstanceOfTest.java new file mode 100644 index 0000000..ce7c1d7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/KeepTypePatternWithInstanceOfTest.java
@@ -0,0 +1,192 @@ +// Copyright (c) 2024, 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 org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestShrinkerBuilder; +import com.android.tools.r8.keepanno.annotations.ClassNamePattern; +import com.android.tools.r8.keepanno.annotations.InstanceOfPattern; +import com.android.tools.r8.keepanno.annotations.KeepTarget; +import com.android.tools.r8.keepanno.annotations.TypePattern; +import com.android.tools.r8.keepanno.annotations.UsesReflection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +@RunWith(Parameterized.class) +public class KeepTypePatternWithInstanceOfTest extends KeepAnnoTestBase { + + @Parameter public KeepAnnoParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static List<KeepAnnoParameters> data() { + return createParameters( + getTestParameters() + .withDefaultRuntimes() + .withApiLevel(AndroidApiLevel.B) + .enableApiLevelsForCf() + .build()); + } + + @Test + public void test() throws Exception { + if (parameters.isReference() || parameters.isNativeR8()) { + testForKeepAnno(parameters) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .run(TestClass.class) + .assertSuccessWithOutput(getExpectedResult(parameters.isReference() ? 9 : 4)); + } else { + // It's either Unimplemented or CompilationFailedException. + assertThrows( + Exception.class, + () -> + testForKeepAnno(parameters) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .run(TestClass.class)); + } + } + + private String getExpectedResult(int numMethods) { + return StringUtils.lines( + "Num methods: " + numMethods, + "5", + "8", + "9", + "6", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9"); + } + + private void clearClassMerging(TestShrinkerBuilder<?, ?, ?, ?, ?> sb) { + sb.addOptionsModification( + opt -> { + opt.horizontalClassMergerOptions().disable(); + opt.getVerticalClassMergerOptions().disable(); + }); + } + + public static class Top {} + + public static class Top1 extends Top {} + + public static class Sub1 extends Top1 {} + + public static class Subb1 extends Top1 {} + + public static class Top2 extends Top {} + + public static class Sub2 extends Top2 {} + + public static class Subb2 extends Top2 {} + + public static class A { + + public void foo(Top1 a, Top2 b) { + System.out.println("1"); + } + + public void foo(Sub1 a, Top2 b) { + System.out.println("2"); + } + + public void foo(Subb1 a, Top2 b) { + System.out.println("3"); + } + + public void foo(Top1 a, Sub2 b) { + System.out.println("4"); + } + + public void foo(Sub1 a, Sub2 b) { + System.out.println("5"); + } + + public void foo(Subb1 a, Sub2 b) { + System.out.println("6"); + } + + public void foo(Top1 a, Subb2 b) { + System.out.println("7"); + } + + public void foo(Sub1 a, Subb2 b) { + System.out.println("8"); + } + + public void foo(Subb1 a, Subb2 b) { + System.out.println("9"); + } + } + + static class TestClass { + + public static void main(String[] args) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + System.out.println("Num methods: " + A.class.getDeclaredMethods().length); + for (Method method : reflectiveMethods()) { + method.invoke(new A(), null, null); + } + // Make sure all the methods are live, they can be inlined. + runAll(); + } + + private static void runAll() { + A a = new A(); + a.foo(new Top1(), new Top2()); + a.foo(new Sub1(), new Top2()); + a.foo(new Subb1(), new Top2()); + a.foo(new Top1(), new Sub2()); + a.foo(new Sub1(), new Sub2()); + a.foo(new Subb1(), new Sub2()); + a.foo(new Top1(), new Subb2()); + a.foo(new Sub1(), new Subb2()); + a.foo(new Subb1(), new Subb2()); + } + + @UsesReflection( + @KeepTarget( + classConstant = A.class, + methodName = "foo", + methodParameterTypePatterns = { + @TypePattern( + instanceOfPattern = + @InstanceOfPattern( + inclusive = false, + classNamePattern = @ClassNamePattern(constant = Top1.class))), + @TypePattern( + instanceOfPattern = + @InstanceOfPattern( + inclusive = false, + classNamePattern = @ClassNamePattern(constant = Top2.class))) + })) + public static Method[] reflectiveMethods() throws NoSuchMethodException { + return new Method[] { + A.class.getDeclaredMethod(getFoo(), Sub1.class, Sub2.class), + A.class.getDeclaredMethod(getFoo(), Sub1.class, Subb2.class), + A.class.getDeclaredMethod(getFoo(), Subb1.class, Subb2.class), + A.class.getDeclaredMethod(getFoo(), Subb1.class, Sub2.class) + }; + } + + private static String getFoo() { + return System.currentTimeMillis() > 0 ? "foo" : "bar"; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java index ff134b5..1d1e173 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -99,7 +99,7 @@ && kotlinParameters.getCompilerVersion().isLessThan(KOTLINC_1_5_0)) { // Don't check exactly how J-style Kotlin lambdas are merged for kotlinc before 1.5.0. assertEquals( - parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods() ? 2 : 10, + parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods() ? 3 : 10, inspector.getMergeGroups().size()); return; } @@ -242,7 +242,7 @@ kotlinParameters.getCompilerVersion().isGreaterThanOrEqualTo(KOTLINC_1_5_0) ? 0 : (parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods() - ? 2 + ? 3 : 8), lambdasInOutput.size()); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java index 811cb53..39ef47d 100644 --- a/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
@@ -113,6 +113,9 @@ libraryDesugaringSpecification.functionPrefix(parameters) + ".util.function.Consumer"); assertThat(consumerClassSubject, isPresent()); + ClassSubject baseStreamClassSubject = inspector.clazz("j$.util.stream.BaseStream"); + assertThat(baseStreamClassSubject, isPresentAndRenamed(compilationSpecification.isL8Shrink())); + ClassSubject streamClassSubject = inspector.clazz("j$.util.stream.Stream"); assertThat(streamClassSubject, isPresentAndNotRenamed()); @@ -124,7 +127,10 @@ && libraryDesugaringSpecification == LibraryDesugaringSpecification.JDK8)); assertEquals(consumerClassSubject.asTypeSubject(), forEachMethodSubject.getParameter(0)); - profileInspector.assertContainsMethodRule(forEachMethodSubject).assertContainsNoOtherRules(); + profileInspector + .assertContainsClassRules(streamClassSubject, baseStreamClassSubject) + .assertContainsMethodRule(forEachMethodSubject) + .assertContainsNoOtherRules(); } static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/IncludeTransitiveSuperClassesInArtProfileTest.java b/src/test/java/com/android/tools/r8/profile/art/IncludeTransitiveSuperClassesInArtProfileTest.java new file mode 100644 index 0000000..322c207 --- /dev/null +++ b/src/test/java/com/android/tools/r8/profile/art/IncludeTransitiveSuperClassesInArtProfileTest.java
@@ -0,0 +1,73 @@ +// Copyright (c) 2024, 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.profile.art; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +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.profile.art.model.ExternalArtProfile; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +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 IncludeTransitiveSuperClassesInArtProfileTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepAllClassesRuleWithAllowObfuscation() + .addArtProfileForRewriting( + ExternalArtProfile.builder().addClassRule(Reference.classFromClass(C.class)).build()) + .setMinApi(parameters) + .compile() + .inspectResidualArtProfile( + (profile, inspector) -> + profile + .assertContainsClassRules( + getFinalReference(A.class, inspector), + getFinalReference(B.class, inspector), + getFinalReference(C.class, inspector), + getFinalReference(I.class, inspector), + getFinalReference(J.class, inspector), + getFinalReference(K.class, inspector)) + .assertContainsNoOtherRules()); + } + + private static ClassReference getFinalReference(Class<?> clazz, CodeInspector inspector) { + ClassSubject classSubject = inspector.clazz(clazz); + assertThat(classSubject, isPresentAndRenamed()); + return classSubject.getFinalReference(); + } + + interface I {} + + interface J {} + + interface K extends J {} + + static class A implements I {} + + static class B extends A {} + + static class C extends B implements K {} +}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingShardTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingShardTest.java index 709703c..aa6d410 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingShardTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingShardTest.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.profile.art.model.ExternalArtProfile; import com.android.tools.r8.profile.art.utils.ArtProfileInspector; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.BooleanUtils; @@ -145,6 +146,7 @@ // Verify the residual profile contains the outline method and its holder when present. profileInspector + .assertContainsClassRule(Reference.classFromClass(mainClass)) .assertContainsMethodRule(MethodReferenceUtils.mainMethod(mainClass)) .applyIf( !isLibraryClassAlwaysPresent(), @@ -172,8 +174,11 @@ // Verify the residual profile contains the outline method and its holder when present. profileInspector - .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class)) - .assertContainsMethodRule(MethodReferenceUtils.mainMethod(OtherMain.class)) + .assertContainsClassRules( + Reference.classFromClass(Main.class), Reference.classFromClass(OtherMain.class)) + .assertContainsMethodRules( + MethodReferenceUtils.mainMethod(Main.class), + MethodReferenceUtils.mainMethod(OtherMain.class)) .applyIf( !isLibraryClassAlwaysPresent(), i ->
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java index acd0d3f..12d2ef4 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.profile.art.model.ExternalArtProfile; import com.android.tools.r8.profile.art.utils.ArtProfileInspector; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.MethodReferenceUtils; @@ -146,6 +147,7 @@ // Verify the residual profile contains the outline method and its holder when present. profileInspector + .assertContainsClassRule(Reference.classFromClass(Main.class)) .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class)) .applyIf( !isLibraryClassAlwaysPresent,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java index c3d0996..3a3b463 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.profile.art.model.ExternalArtProfile; import com.android.tools.r8.profile.art.utils.ArtProfileInspector; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.InternalOptions.InlinerOptions; @@ -96,6 +97,7 @@ // Verify residual profile contains the backported method and its holder. profileInspector + .assertContainsClassRule(Reference.classFromClass(Main.class)) .assertContainsMethodRules(MethodReferenceUtils.mainMethod(Main.class)) .applyIf( isBackportingObjectsNonNull,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java index c5adb71..c9db8f6 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java
@@ -95,7 +95,9 @@ MethodSubject interfaceMethodSubject = iClassSubject.uniqueMethodWithOriginalName("m"); assertThat(interfaceMethodSubject, isPresent()); - profileInspector.assertContainsMethodRule(interfaceMethodSubject); + profileInspector + .assertContainsClassRule(iClassSubject) + .assertContainsMethodRule(interfaceMethodSubject); } else { ClassSubject iClassSubject = inspector.clazz(I.class); assertThat(iClassSubject, isPresent()); @@ -125,7 +127,8 @@ assertThat(movedMethodSubject, isPresent()); profileInspector - .assertContainsClassRule(companionClassSubject) + .assertContainsClassRules( + aClassSubject, bClassSubject, iClassSubject, companionClassSubject) .assertContainsMethodRules( interfaceMethodSubject, aForwardingMethodSubject,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java index 04133ca..74cd8dd 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
@@ -99,7 +99,9 @@ profileInspector .assertContainsClassRules( - enumUnboxingLocalUtilityClassSubject, enumUnboxingSharedUtilityClassSubject) + mainClassSubject, + enumUnboxingLocalUtilityClassSubject, + enumUnboxingSharedUtilityClassSubject) .assertContainsMethodRules( mainClassSubject.mainMethod(), localGreetMethodSubject,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java index faea413..c909872 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
@@ -76,6 +76,7 @@ assertThat(syntheticConstructorSubject, isPresent()); profileInspector + .assertContainsClassRule(aClassSubject) .assertContainsMethodRules(syntheticConstructorSubject) .applyIf( this == A_CONSTRUCTOR,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java index e4196f3..0ef50da 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java
@@ -70,6 +70,7 @@ assertEquals(aClassSubject.asTypeSubject(), syntheticBridgeMethodSubject.getParameter(0)); profileInspector + .assertContainsClassRule(aClassSubject) .assertContainsMethodRules(movedMethodSubject, syntheticBridgeMethodSubject) .assertContainsNoOtherRules(); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java index 0b423ed..08803aa 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java
@@ -132,6 +132,7 @@ // Verify residual profile contains private synthetic method when present. profileInspector + .assertContainsClassRule(mainClassSubject) .assertContainsMethodRules(mMethodSubject, mMovedMethodSubject) .assertContainsNoOtherRules(); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java index 6eac7c2..56731cd 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java
@@ -4,8 +4,8 @@ package com.android.tools.r8.profile.art.completeness; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; @@ -75,7 +75,7 @@ } private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) { - inspect(profileInspector, inspector, false, false); + inspect(profileInspector, inspector, false, false, false); } private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) { @@ -83,37 +83,44 @@ profileInspector, inspector, parameters.canHaveNonReboundConstructorInvoke(), - parameters.isCfRuntime()); + parameters.isCfRuntime(), + true); } public void inspect( ArtProfileInspector profileInspector, CodeInspector inspector, boolean canHaveNonReboundConstructorInvoke, - boolean canUseLambdas) { + boolean canUseLambdas, + boolean isR8) { ClassSubject mainClassSubject = inspector.clazz(Main.class); assertThat(mainClassSubject, isPresent()); MethodSubject mainMethodSubject = mainClassSubject.mainMethod(); assertThat(mainMethodSubject, isPresent()); + ClassSubject setSupplierClassSubject = inspector.clazz(SetSupplier.class); + assertThat(setSupplierClassSubject, isAbsentIf(!canUseLambdas && isR8)); + // Check the presence of the lambda class and its methods. ClassSubject lambdaClassSubject = inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0)); - assertThat(lambdaClassSubject, notIf(isPresent(), canUseLambdas)); + assertThat(lambdaClassSubject, isAbsentIf(canUseLambdas)); MethodSubject lambdaInitializerSubject = lambdaClassSubject.uniqueInstanceInitializer(); - assertThat(lambdaInitializerSubject, notIf(isPresent(), canUseLambdas)); + assertThat(lambdaInitializerSubject, isAbsentIf(canUseLambdas)); MethodSubject lambdaMainMethodSubject = lambdaClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); - assertThat(lambdaMainMethodSubject, notIf(isPresent(), canUseLambdas)); + assertThat(lambdaMainMethodSubject, isAbsentIf(canUseLambdas)); - if (canUseLambdas) { - profileInspector.assertContainsMethodRule(mainMethodSubject); - } else { + profileInspector + .assertContainsClassRules(mainClassSubject) + .assertContainsMethodRule(mainMethodSubject); + if (!canUseLambdas) { profileInspector .assertContainsClassRules(lambdaClassSubject) + .applyIf(!isR8, i -> i.assertContainsClassRule(setSupplierClassSubject)) .assertContainsMethodRules( mainMethodSubject, lambdaInitializerSubject, lambdaMainMethodSubject); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedPrivateInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedPrivateInterfaceMethodProfileRewritingTest.java index 632fc0b..53ab39f 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedPrivateInterfaceMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedPrivateInterfaceMethodProfileRewritingTest.java
@@ -125,6 +125,7 @@ assertThat(privateInterfaceMethodSubject, isPresent()); profileInspector + .assertContainsClassRule(iClassSubject) .assertContainsMethodRule(privateInterfaceMethodSubject) .assertContainsNoOtherRules(); } else {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java index ebf293e..7d06408 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
@@ -141,6 +141,7 @@ assertThat(staticInterfaceMethodSubject, isPresent()); profileInspector + .assertContainsClassRule(iClassSubject) .assertContainsMethodRule(staticInterfaceMethodSubject) .assertContainsNoOtherRules(); } else {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java index f296a71..5756f2e 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java
@@ -104,7 +104,12 @@ private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) throws Exception { - inspect(profileInspector, inspector, false, parameters.canUseNestBasedAccessesWhenDesugaring()); + inspect( + profileInspector, + inspector, + false, + parameters.canUseNestBasedAccessesWhenDesugaring(), + false); } private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) @@ -113,14 +118,16 @@ profileInspector, inspector, parameters.canHaveNonReboundConstructorInvoke(), - parameters.canUseNestBasedAccesses()); + parameters.canUseNestBasedAccesses(), + true); } private void inspect( ArtProfileInspector profileInspector, CodeInspector inspector, boolean canHaveNonReboundConstructorInvoke, - boolean canUseNestBasedAccesses) + boolean canUseNestBasedAccesses, + boolean isR8) throws Exception { ClassSubject nestMemberClassSubject = inspector.clazz(NestMember.class); assertThat(nestMemberClassSubject, isPresent()); @@ -197,8 +204,12 @@ // Verify the residual profile contains the synthetic nest based access bridges and the // synthetic constructor argument class. profileInspector + .assertContainsClassRule(Reference.classFromClass(Main.class)) .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class)) .applyIf( + !isR8 || parameters.isDexRuntime(), + i -> i.assertContainsClassRule(nestMemberClassSubject)) + .applyIf( !canUseNestBasedAccesses, i -> i.assertContainsMethodRules(
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java index 1c0ac50..0c4ceca 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.profile.art.model.ExternalArtProfile; import com.android.tools.r8.profile.art.utils.ArtProfileInspector; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.InternalOptions.InlinerOptions; import com.android.tools.r8.utils.MethodReferenceUtils; @@ -70,6 +71,7 @@ // TODO(b/265729283): Should contain the outline class and method. profileInspector + .assertContainsClassRule(Reference.classFromClass(Main.class)) .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class)) .assertContainsNoOtherRules(); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java index cbab790..f0cd1ab 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -300,7 +300,7 @@ toStringMethodSubject, ifThen(!canUseRecords, invokesMethod(toStringHelperMethodSubject))); profileInspector - .assertContainsClassRule(personRecordClassSubject) + .assertContainsClassRules(mainClassSubject, personRecordClassSubject) .assertContainsMethodRules( mainMethodSubject, personInstanceInitializerSubject,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java index 53fb532..a919381 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java
@@ -134,11 +134,13 @@ switch (this) { case MAIN_METHOD: profileInspector + .assertContainsClassRule(mainClassSubject) .assertContainsMethodRule(mainMethodSubject) .assertContainsNoOtherRules(); break; case IMPLEMENTATION_METHOD: profileInspector + .assertContainsClassRule(mainClassSubject) .assertContainsMethodRules( lambdaImplementationMethod, otherLambdaImplementationMethod) .assertContainsNoOtherRules(); @@ -153,7 +155,8 @@ // with their initializers. Since Main.lambda$main$*() is not in the art profile, the // interface method implementation does not need to be included in the profile. profileInspector - .assertContainsClassRules(lambdaClassSubject, otherLambdaClassSubject) + .assertContainsClassRules( + mainClassSubject, lambdaClassSubject, otherLambdaClassSubject) .assertContainsMethodRules(mainMethodSubject) .applyIf( !canHaveNonReboundConstructorInvoke, @@ -166,6 +169,8 @@ // Since Main.lambda$main$*() is in the art profile, so should the two accessibility // bridges be along with the main virtual methods of the lambda classes. profileInspector + .assertContainsClassRules( + mainClassSubject, lambdaClassSubject, otherLambdaClassSubject) .assertContainsMethodRules( lambdaImplementationMethod, lambdaMainMethodSubject,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java index b8db6f5..f840c33f 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
@@ -159,11 +159,13 @@ barClassSubject.uniqueMethodWithOriginalName("$closeResource"); assertThat(barCloseResourceMethodSubject, isPresent()); - profileInspector.assertContainsMethodRules( - fooMethodSubject, - fooCloseResourceMethodSubject, - barMethodSubject, - barCloseResourceMethodSubject); + profileInspector + .assertContainsClassRules(fooClassSubject, barClassSubject) + .assertContainsMethodRules( + fooMethodSubject, + fooCloseResourceMethodSubject, + barMethodSubject, + barCloseResourceMethodSubject); // There is 1 backport, 2 synthetic API outlines, and 3 twr classes for both Foo and Bar. for (JavaExampleClassProxy clazz : ImmutableList.of(FOO, BAR)) {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java index 4493de9..10b893b 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
@@ -78,6 +78,7 @@ assertThat(syntheticBridgeMethodSubject, isPresent()); profileInspector + .assertContainsClassRule(bClassSubject) .assertContainsMethodRules(movedMethodSubject, syntheticBridgeMethodSubject) .assertContainsNoOtherRules(); }
diff --git a/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithCommentsAndWhitespaceTest.java b/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithCommentsAndWhitespaceTest.java index c5fdf6c..77ffaf9 100644 --- a/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithCommentsAndWhitespaceTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithCommentsAndWhitespaceTest.java
@@ -100,7 +100,10 @@ } private void inspectResidualArtProfile(ArtProfileInspector profileInspector) { - profileInspector.assertContainsMethodRule(MAIN_METHOD_REFERENCE).assertContainsNoOtherRules(); + profileInspector + .assertContainsClassRule(MAIN_METHOD_REFERENCE.getHolderClass()) + .assertContainsMethodRule(MAIN_METHOD_REFERENCE) + .assertContainsNoOtherRules(); } static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithFlagsInAnyOrderTest.java b/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithFlagsInAnyOrderTest.java index 45ab398..f4a074d 100644 --- a/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithFlagsInAnyOrderTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/format/ArtProfileWithFlagsInAnyOrderTest.java
@@ -114,6 +114,7 @@ private void inspectResidualArtProfile(ArtProfileInspector profileInspector) { profileInspector + .assertContainsClassRule(MAIN_METHOD_REFERENCE.getHolderClass()) .inspectMethodRule( MAIN_METHOD_REFERENCE, ruleInspector -> ruleInspector.assertIsHot().assertIsStartup().assertIsPostStartup())
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java index 96aa8fd..ab71079 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
@@ -62,7 +62,7 @@ ClassSubject classSubject = inspector.clazz(StaticallyReferenced.class); assertThat(classSubject, isPresent()); assertEquals(0, classSubject.allFields().size()); - assertEquals(0, classSubject.allMethods().size()); + assertEquals(1, classSubject.allMethods().size()); }); }
diff --git a/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java b/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java new file mode 100644 index 0000000..535a69c --- /dev/null +++ b/src/test/java/com/android/tools/r8/startup/NonStartupInStartupOutlinerTest.java
@@ -0,0 +1,206 @@ +// Copyright (c) 2024, 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.startup; + +import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticNonStartupInStartupOutlineClass; +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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoAccessModification; +import com.android.tools.r8.NoMethodStaticizing; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.startup.profile.ExternalStartupClass; +import com.android.tools.r8.startup.profile.ExternalStartupItem; +import com.android.tools.r8.startup.profile.ExternalStartupMethod; +import com.android.tools.r8.startup.utils.StartupTestingUtils; +import com.android.tools.r8.utils.MethodReferenceUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.Lists; +import java.util.List; +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 NonStartupInStartupOutlinerTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withDexRuntimes() + .withApiLevelsStartingAtIncluding(apiLevelWithNativeMultiDexSupport()) + .build(); + } + + @Test + public void test() throws Exception { + List<ExternalStartupItem> startupProfile = + Lists.newArrayList( + ExternalStartupClass.builder() + .setClassReference(Reference.classFromClass(StartupMain.class)) + .build(), + ExternalStartupMethod.builder() + .setMethodReference(MethodReferenceUtils.mainMethod(StartupMain.class)) + .build()); + + R8TestCompileResult compileResult = + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRules(StartupMain.class, NonStartupMain.class) + .addKeepRules( + "-keepclassmembers class " + StartupMain.class.getTypeName() + " {", + " void outlinePinnedInstance();", + " void outlinePinnedStatic();", + "}") + .addOptionsModification(options -> options.getStartupOptions().setEnableOutlining(true)) + .apply(StartupTestingUtils.addStartupProfile(startupProfile)) + .allowDiagnosticInfoMessages() + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoAccessModificationAnnotationsForMembers() + .enableNoMethodStaticizingAnnotations() + // To allow inspecting the individual outline classes. + .noHorizontalClassMergingOfSynthetics() + .setMinApi(parameters) + .compile() + .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex); + + compileResult + .run(parameters.getRuntime(), StartupMain.class) + .assertSuccessWithOutputLines("main"); + + compileResult + .run(parameters.getRuntime(), NonStartupMain.class) + .assertSuccessWithOutputLines( + "movePrivate", "moveStatic", "outlinePinnedInstance", "outlinePinnedStatic"); + } + + private void inspectPrimaryDex(CodeInspector inspector) { + assertEquals(1, inspector.allClasses().size()); + + ClassSubject startupMainClassSubject = inspector.clazz(StartupMain.class); + assertThat(startupMainClassSubject, isPresent()); + assertEquals( + parameters.canInitNewInstanceUsingSuperclassConstructor() ? 4 : 3, + startupMainClassSubject.allMethods().size()); + + assertThat(startupMainClassSubject.mainMethod(), isPresent()); + assertThat(startupMainClassSubject.init(), isAbsent()); + assertThat(startupMainClassSubject.uniqueMethodWithOriginalName("movePrivate"), isAbsent()); + assertThat( + startupMainClassSubject.uniqueMethodWithOriginalName("movePrivateAccessor"), isPresent()); + assertThat(startupMainClassSubject.uniqueMethodWithOriginalName("moveStatic"), isAbsent()); + assertThat( + startupMainClassSubject.uniqueMethodWithOriginalName("outlinePinnedInstance"), isPresent()); + assertThat( + startupMainClassSubject.uniqueMethodWithOriginalName("outlinePinnedStatic"), isPresent()); + } + + private void inspectSecondaryDex(CodeInspector inspector) { + assertThat(inspector.clazz(NonStartupMain.class), isPresent()); + + ClassSubject movePrivateOutline = + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 0)); + assertThat(movePrivateOutline, isPresent()); + assertTrue( + movePrivateOutline + .uniqueMethod() + .streamInstructions() + .anyMatch(i -> i.isConstString("movePrivate"))); + + ClassSubject outlinePinnedStaticOutline = + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 1)); + assertThat( + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 1)), + isPresent()); + assertTrue( + outlinePinnedStaticOutline + .uniqueMethod() + .streamInstructions() + .anyMatch(i -> i.isConstString("outlinePinnedStatic"))); + + ClassSubject outlinePinnedInstanceOutline = + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 2)); + assertThat(outlinePinnedInstanceOutline, isPresent()); + assertTrue( + outlinePinnedInstanceOutline + .uniqueMethod() + .streamInstructions() + .anyMatch(i -> i.isConstString("outlinePinnedInstance"))); + + ClassSubject moveStaticOutline = + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 3)); + assertThat(moveStaticOutline, isPresent()); + assertTrue( + moveStaticOutline + .uniqueMethod() + .streamInstructions() + .anyMatch(i -> i.isConstString("moveStatic"))); + + assertThat( + inspector.clazz(syntheticNonStartupInStartupOutlineClass(StartupMain.class, 4)), + isAbsent()); + } + + @NeverClassInline + static class StartupMain { + + public static void main(String[] args) { + System.out.println("main"); + } + + // Moved to synthetic non-startup class. + @NeverInline + @NoAccessModification + @NoMethodStaticizing + private void movePrivate() { + System.out.println("movePrivate"); + } + + @NeverInline + @NoMethodStaticizing + void movePrivateAccessor() { + movePrivate(); + } + + // Moved to synthetic non-startup class. + @NeverInline + static void moveStatic() { + System.out.println("moveStatic"); + } + + void outlinePinnedInstance() { + System.out.println("outlinePinnedInstance"); + } + + static void outlinePinnedStatic() { + System.out.println("outlinePinnedStatic"); + } + } + + static class NonStartupMain { + + public static void main(String[] args) { + new StartupMain().movePrivateAccessor(); + StartupMain.moveStatic(); + new StartupMain().outlinePinnedInstance(); + StartupMain.outlinePinnedStatic(); + } + } +}
diff --git a/src/test/testbase/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java b/src/test/testbase/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java index 2e21529..93384e8 100644 --- a/src/test/testbase/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java +++ b/src/test/testbase/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
@@ -61,6 +61,21 @@ return archive; } + public static List<String> extractRulesFromFiles( + List<Path> inputFiles, KeepRuleExtractorOptions extractorOptions) { + return extractRulesFromBytes( + ListUtils.map( + inputFiles, + path -> { + try { + return Files.readAllBytes(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }), + extractorOptions); + } + public static List<String> extractRules( List<Class<?>> inputClasses, KeepRuleExtractorOptions extractorOptions) { return extractRulesFromBytes(
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java index b1744ee..de1aec9 100644 --- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java +++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -227,6 +227,15 @@ originalMethod.getMethodDescriptor()); } + public static ClassReference syntheticNonStartupInStartupOutlineClass(Class<?> clazz, int id) { + return syntheticNonStartupInStartupOutlineClass(Reference.classFromClass(clazz), id); + } + + public static ClassReference syntheticNonStartupInStartupOutlineClass( + ClassReference reference, int id) { + return syntheticClass(reference, naming.NON_STARTUP_IN_STARTUP_OUTLINE, id); + } + public static MethodReference syntheticPrivateInterfaceMethodAsCompanionMethod(Method method) { MethodReference originalMethod = Reference.methodFromMethod(method); ClassReference companionClassReference =
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py index 51e9366..487cb7c 100755 --- a/tools/apk_masseur.py +++ b/tools/apk_masseur.py
@@ -23,6 +23,10 @@ 'assets/dexopt/', default=False, action='store_true') + parser.add_option('--compress-dex', + help='Whether the dex should be stored compressed', + action='store_true', + default=False) parser.add_option('--dex', help='Directory or archive with dex files to use instead ' 'of those in the apk', @@ -67,8 +71,8 @@ return file.endswith('.zip') or file.endswith('.jar') -def repack(apk, clear_profile, processed_out, desugared_library_dex, resources, - temp, quiet, logging): +def repack(apk, clear_profile, processed_out, desugared_library_dex, + compress_dex, resources, temp, quiet, logging): processed_apk = os.path.join(temp, 'processed.apk') shutil.copyfile(apk, processed_apk) @@ -119,7 +123,12 @@ dex_files = glob.glob('*.dex') dex_files.sort() resource_files = glob.glob(resources) if resources else [] - cmd = ['zip', '-u', '-0', processed_apk] + dex_files + resource_files + cmd = ['zip', '-u'] + if not compress_dex: + cmd.append('-0') + cmd.append(processed_apk) + cmd.extend(dex_files) + cmd.extend(resource_files) utils.RunCmd(cmd, quiet=quiet, logging=logging) return processed_apk @@ -143,6 +152,7 @@ clear_profile=False, dex=None, desugared_library_dex=None, + compress_dex=False, resources=None, out=None, adb_options=None, @@ -159,8 +169,8 @@ processed_apk = None if dex or clear_profile: processed_apk = repack(apk, clear_profile, dex, - desugared_library_dex, resources, temp, - quiet, logging) + desugared_library_dex, compress_dex, + resources, temp, quiet, logging) else: assert not desugared_library_dex utils.Print('Signing original APK without modifying apk',
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py index 6ce19ab..0a4ae59 100755 --- a/tools/startup/adb_utils.py +++ b/tools/startup/adb_utils.py
@@ -41,10 +41,14 @@ OFF_UNLOCKED = 2 ON_LOCKED = 3 ON_UNLOCKED = 4 + UNKNOWN = 5 def is_off(self): return self == ScreenState.OFF_LOCKED or self == ScreenState.OFF_UNLOCKED + def is_off_or_unknown(self): + return self.is_off() or self.is_unknown() + def is_on(self): return self == ScreenState.ON_LOCKED or self == ScreenState.ON_UNLOCKED @@ -54,6 +58,15 @@ def is_on_and_unlocked(self): return self == ScreenState.ON_UNLOCKED + def is_on_and_unlocked_or_unknown(self): + return self.is_on_and_unlocked() or self.is_unknown() + + def is_on_or_unknown(self): + return self.is_on() or self.is_unknown() + + def is_unknown(self): + return self == ScreenState.UNKNOWN + def broadcast(action, component, device_id=None): print('Sending broadcast %s' % action) @@ -147,13 +160,13 @@ def ensure_screen_on(device_id=None): if get_screen_state(device_id).is_off(): toggle_screen(device_id) - assert get_screen_state(device_id).is_on() + assert get_screen_state(device_id).is_on_or_unknown() def ensure_screen_off(device_id=None): if get_screen_state(device_id).is_on(): toggle_screen(device_id) - assert get_screen_state(device_id).is_off() + assert get_screen_state(device_id).is_off_or_unknown() def force_compilation(app_id, device_id=None): @@ -243,7 +256,11 @@ def get_screen_state(device_id=None): cmd = create_adb_cmd('shell dumpsys nfc', device_id) - stdout = subprocess.check_output(cmd).decode('utf-8').strip() + process_result = subprocess.run(cmd, capture_output=True) + stderr = process_result.stderr.decode('utf-8') + if "Can't find service: nfc" in stderr: + return ScreenState.UNKNOWN + stdout = process_result.stdout.decode('utf-8').strip() screen_state_value = None for line in stdout.splitlines(): if line.startswith('mScreenState='): @@ -480,6 +497,8 @@ def unlock(device_id=None, device_pin=None): ensure_screen_on(device_id) screen_state = get_screen_state(device_id) + if screen_state.is_unknown(): + return assert screen_state.is_on(), 'was %s' % screen_state if screen_state.is_on_and_locked(): if device_pin is not None:
diff --git a/tools/startup/measure_startup.py b/tools/startup/measure_startup.py index 7e97735..322dfbe 100755 --- a/tools/startup/measure_startup.py +++ b/tools/startup/measure_startup.py
@@ -138,7 +138,8 @@ def teardown_for_run(out_dir, options, teardown_options): - assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked() + assert adb_utils.get_screen_state( + options.device_id).is_on_and_unlocked_or_unknown() if options.capture_screen: target = os.path.join(out_dir, 'screen.png') @@ -153,7 +154,8 @@ def run(out_dir, options, tmp_dir): - assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked() + assert adb_utils.get_screen_state( + options.device_id).is_on_and_unlocked_or_unknown() # Start logcat for time to fully drawn. logcat_process = None @@ -445,7 +447,7 @@ if options.cooldown == 0: teardown_options = adb_utils.prepare_for_interaction_with_device( options.device_id, options.device_pin) - assert adb_utils.get_screen_state(options.device_id).is_on() + assert adb_utils.get_screen_state(options.device_id).is_on_or_unknown() else: adb_utils.ensure_screen_off(options.device_id) return teardown_options