Merge commit 'a2ffb5e15aa342650f0aa5fdd16f8effc77661ee' into dev-release Change-Id: Ic73cfbd73d08c5800226461f02bf48d8b63df2c3
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index 0bd58e6..1372ccb 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -387,7 +387,7 @@ R8Command.Builder r8Builder = R8Command.builder(getReporter()) .addProgramResourceProvider((ProgramResourceProvider) l8CfConsumer) - .setEnableEmptyMemberRulesToDefaultInitRuleConversion(false) + .enableLegacyFullModeForKeepRules(false) .setSynthesizedClassesPrefix( desugaredLibrarySpecification.getSynthesizedLibraryClassesPackagePrefix()) .setMinApiLevel(getMinApiLevel())
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index 52ae388..489f9e0 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -506,10 +506,15 @@ * * <p>This currently defaults to true in stable versions. */ - public Builder setEnableEmptyMemberRulesToDefaultInitRuleConversion( - boolean enableEmptyMemberRulesToDefaultInitRuleConversion) { - parserOptionsBuilder.setEnableEmptyMemberRulesToDefaultInitRuleConversion( - enableEmptyMemberRulesToDefaultInitRuleConversion); + public Builder enableLegacyFullModeForKeepRules(boolean enableLegacyFullModeForKeepRules) { + parserOptionsBuilder.setEnableLegacyFullModeForKeepRules(enableLegacyFullModeForKeepRules); + return this; + } + + public Builder enableLegacyFullModeForKeepRulesWarnings( + boolean enableLegacyFullModeForKeepRulesWarnings) { + parserOptionsBuilder.setEnableLegacyFullModeForKeepRulesWarnings( + enableLegacyFullModeForKeepRulesWarnings); return this; }
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java index 42b5b5b..2fc67d4 100644 --- a/src/main/java/com/android/tools/r8/R8Partial.java +++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -220,7 +220,7 @@ .addClasspathFiles(d8Program) .addProgramFiles(r8Program) .addProguardConfigurationFiles(dump.getProguardConfigFile(), traceReferencesRules) - .setEnableEmptyMemberRulesToDefaultInitRuleConversion(true) + .enableLegacyFullModeForKeepRules(true) .setMode(dump.getBuildProperties().getCompilationMode()) .setOutput(r8Output, OutputMode.DexIndexed); if (dump.hasDesugaredLibrary()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 8f6be1b..3ac2bd6 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -693,6 +693,8 @@ new JavaLangReflectArrayMembers(); public final JavaLangAnnotationRetentionPolicyMembers javaLangAnnotationRetentionPolicyMembers = new JavaLangAnnotationRetentionPolicyMembers(); + public final JavaLangInvokeVarHandleMembers javaLangInvokeVarHandleMembers = + new JavaLangInvokeVarHandleMembers(); public final JavaLangSystemMembers javaLangSystemMembers = new JavaLangSystemMembers(); public final JavaIoPrintStreamMembers javaIoPrintStreamMembers = new JavaIoPrintStreamMembers(); public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods(); @@ -2023,6 +2025,14 @@ private JavaLangAnnotationRetentionPolicyMembers() {} } + public class JavaLangInvokeVarHandleMembers { + + public final DexMethod storeStoreFence = + createMethod(varHandleType, createProto(voidType), "storeStoreFence"); + + private JavaLangInvokeVarHandleMembers() {} + } + public class JavaLangReflectArrayMembers { public final DexMethod newInstanceMethodWithDimensions =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java index 93b681a..9528327 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -33,6 +33,7 @@ import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover; import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.kotlin.KotlinInlineMethodRemover; import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; import com.android.tools.r8.threading.ThreadingModule; @@ -57,6 +58,7 @@ public void convert(AppView<AppInfo> appView, ExecutorService executorService) throws ExecutionException { LambdaDeserializationMethodRemover.run(appView); + new KotlinInlineMethodRemover(appView).run(executorService); workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService); DexApplication application = appView.appInfo().app(); ProfileCollectionAdditions profileCollectionAdditions =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index 473d188..a3881fc 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -489,6 +489,7 @@ // Make sure constructor inlining is legal. if (singleTarget.getDefinition().isInstanceInitializer() && !canInlineInstanceInitializer( + actionBuilder, code, invoke.asInvokeDirect(), singleTarget, @@ -655,6 +656,7 @@ @Override @SuppressWarnings("ReferenceEquality") public boolean canInlineInstanceInitializer( + InlineAction.Builder actionBuilder, IRCode code, InvokeDirect invoke, ProgramMethod singleTarget, @@ -687,7 +689,6 @@ return true; } - @SuppressWarnings("ReferenceEquality") // Only allow inlining a constructor into a non-constructor if: // (1) the first use of the uninitialized object is the receiver of an invoke of <init>(), // (2) the constructor does not initialize any final fields, as such is only allowed from within @@ -727,11 +728,24 @@ InstancePut instancePut = instruction.asInstancePut(); DexField field = instancePut.getField(); DexEncodedField target = appView.appInfo().lookupInstanceTarget(field); - if (target == null || target.isFinal()) { - whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment( + if (target == null) { + whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToMissingFieldAssignment( instancePut); return false; } + if (target.isFinal()) { + // TODO(b/278964529): We should unset the final flag for good measure. Find a way to do + // this in a thread safe manner. + if (inlinerOptions.enableConstructorInliningWithFinalFields + && options.canAssignFinalInstanceFieldOutsideConstructor() + && options.canUseJavaLangVarHandleStoreStoreFence(appView)) { + actionBuilder.setShouldEnsureStoreStoreBarrier(); + } else { + whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment( + instancePut); + return false; + } + } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java index a3fb7e7..5b67472 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -84,6 +84,7 @@ @Override public boolean canInlineInstanceInitializer( + InlineAction.Builder actionBuilder, IRCode code, InvokeDirect invoke, ProgramMethod singleTarget,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 99463ac..d73ab2a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -37,6 +37,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Monitor; import com.android.tools.r8.ir.code.MonitorType; @@ -564,6 +565,7 @@ final Reason reason; private boolean shouldEnsureStaticInitialization; + private boolean shouldEnsureStoreStoreBarrier; private DexProgramClass downcastClass; @@ -594,6 +596,10 @@ shouldEnsureStaticInitialization = true; } + void setShouldEnsureStoreStoreBarrier() { + shouldEnsureStoreStoreBarrier = true; + } + boolean mustBeInlined() { return reason == Reason.ALWAYS; } @@ -620,6 +626,11 @@ failingBlock -> synthesizeInitClass(code, failingBlock)); } + // Insert a store-store barrier at the end of the method. + if (shouldEnsureStoreStoreBarrier) { + synthesizeStoreStoreBarrier(appView, code); + } + // Insert a null check if this is needed to preserve the implicit null check for the receiver. // This is only needed if we do not also insert a monitor-enter instruction, since that will // throw a NPE if the receiver is null. @@ -812,6 +823,29 @@ } } + private void synthesizeStoreStoreBarrier(AppView<AppInfoWithLiveness> appView, IRCode code) { + assert target.getDefinition().isInstanceInitializer(); + BasicBlockIterator blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + if (!block.isReturnBlock()) { + continue; + } + // It is an invariant that return blocks do not have catch handlers. + assert !block.hasCatchHandlers(); + InstructionListIterator instructionIterator = + block.listIterator(code, block.getInstructions().size() - 1); + DexMethod storeStoreFenceMethod = + appView.dexItemFactory().javaLangInvokeVarHandleMembers.storeStoreFence; + assert appView.definitionFor(storeStoreFenceMethod) != null; + instructionIterator.add( + InvokeStatic.builder() + .setMethod(storeStoreFenceMethod) + .setPosition(Position.syntheticNone()) + .build()); + } + } + private void setRemoveInnerFramePositionForReceiverUse(Instruction instruction) { Position position = instruction.getPosition(); if (position == null) { @@ -831,6 +865,7 @@ private InvokeMethod invoke; private Reason reason; private boolean shouldEnsureStaticInitialization; + private boolean shouldEnsureStoreStoreBarrier; private ProgramMethod target; Builder setDowncastClass(DexProgramClass downcastClass) { @@ -853,6 +888,11 @@ return this; } + Builder setShouldEnsureStoreStoreBarrier() { + this.shouldEnsureStoreStoreBarrier = true; + return this; + } + Builder setTarget(ProgramMethod target) { this.target = target; return this; @@ -866,6 +906,9 @@ if (shouldEnsureStaticInitialization) { action.setShouldEnsureStaticInitialization(); } + if (shouldEnsureStoreStoreBarrier) { + action.setShouldEnsureStoreStoreBarrier(); + } return action; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java index a684ab7..e24b2c1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -30,6 +30,7 @@ AppView<AppInfoWithLiveness> appView(); boolean canInlineInstanceInitializer( + InlineAction.Builder actionBuilder, IRCode code, InvokeDirect invoke, ProgramMethod singleTarget,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java index ab67530..aa435cf 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -748,7 +748,9 @@ FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object); ExistingValue value = new ExistingValue(instancePut.value()); if (field.isFinalOrEffectivelyFinal(appView)) { - assert !field.getDefinition().isFinal() || method.getDefinition().isInstanceInitializer(); + assert !field.getDefinition().isFinal() + || method.getDefinition().isInstanceInitializer() + || options.inlinerOptions().enableConstructorInliningWithFinalFields; activeState.putFinalOrEffectivelyFinalInstanceField(fieldAndObject, value); } else { activeState.putNonFinalInstanceField(fieldAndObject, value);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java index cb7523f..0f939fc 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -118,6 +118,9 @@ public void reportUnsafeConstructorInliningDueToFinalFieldAssignment(InstancePut instancePut) {} @Override + public void reportUnsafeConstructorInliningDueToMissingFieldAssignment(InstancePut instancePut) {} + + @Override public void reportUnsafeConstructorInliningDueToIndirectConstructorCall(InvokeDirect invoke) {} @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java index ca2d508..e70e49e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -114,6 +114,9 @@ public abstract void reportUnsafeConstructorInliningDueToFinalFieldAssignment( InstancePut instancePut); + public abstract void reportUnsafeConstructorInliningDueToMissingFieldAssignment( + InstancePut instancePut); + public abstract void reportUnsafeConstructorInliningDueToIndirectConstructorCall( InvokeDirect invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java index 8a04363..857fa35 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -251,6 +251,11 @@ } @Override + public void reportUnsafeConstructorInliningDueToMissingFieldAssignment(InstancePut instancePut) { + report("assignment to missing field `" + instancePut.getField() + "`."); + } + + @Override public void reportUnsafeConstructorInliningDueToIndirectConstructorCall(InvokeDirect invoke) { report( "must invoke a constructor from the class being instantiated (would invoke `"
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodRemover.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodRemover.java new file mode 100644 index 0000000..595e6a9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodRemover.java
@@ -0,0 +1,97 @@ +// 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.kotlin; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DefaultUseRegistryWithResult; +import com.android.tools.r8.graph.DexEncodedMethod; +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.graph.ProgramMethod; +import com.android.tools.r8.utils.ThreadUtils; +import com.google.common.collect.Sets; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +public class KotlinInlineMethodRemover { + + private final AppView<AppInfo> appView; + private final DexType kotlinInlineMarkerType; + private final DexType kotlinMetadataType; + + public KotlinInlineMethodRemover(AppView<AppInfo> appView) { + this.appView = appView; + this.kotlinInlineMarkerType = + appView.dexItemFactory().createType("Lkotlin/jvm/internal/InlineMarker;"); + this.kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType; + } + + public void run(ExecutorService executorService) throws ExecutionException { + if (appView.options().isGeneratingDex()) { + ThreadUtils.processItems( + this::forEachKotlinClass, + this::processClass, + appView.options().getThreadingModule(), + executorService); + } + } + + private void forEachKotlinClass(Consumer<DexProgramClass> consumer) { + for (DexProgramClass clazz : appView.appInfo().classes()) { + if (clazz.annotations().hasAnnotation(kotlinMetadataType)) { + consumer.accept(clazz); + } else { + assert verifyNoKotlinInlineFunctions(clazz); + } + } + } + + private void processClass(DexProgramClass clazz) { + Set<DexEncodedMethod> methodsToRemove = null; + for (ProgramMethod method : clazz.programMethods()) { + if (isKotlinInlineFunction(method)) { + assert method.getHolder().annotations().hasAnnotation(kotlinMetadataType); + if (methodsToRemove == null) { + methodsToRemove = Sets.newIdentityHashSet(); + } + methodsToRemove.add(method.getDefinition()); + } + } + if (methodsToRemove != null) { + clazz.getMethodCollection().removeMethods(methodsToRemove); + } + } + + private boolean isKotlinInlineFunction(ProgramMethod method) { + return method.registerCodeReferencesWithResult( + new DefaultUseRegistryWithResult<>(appView, method, false) { + + @Override + public void registerInvokeStatic(DexMethod method) { + if (method.getHolderType().isIdenticalTo(kotlinInlineMarkerType)) { + setResult(true); + } + } + + @Override + public void registerInvokeSpecial(DexMethod method) { + // Intentionally empty. Override the default use registry behavior that attempts to + // convert this invoke-special to its corresponding dex invoke type. This is only + // possible after desugaring. + } + }); + } + + private boolean verifyNoKotlinInlineFunctions(DexProgramClass clazz) { + for (ProgramMethod method : clazz.programMethods()) { + assert !isKotlinInlineFunction(method); + } + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java index ceb9671..8c6aa57 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -129,7 +129,7 @@ dexItemFactory, reporter, ProguardConfigurationParserOptions.builder() - .setEnableEmptyMemberRulesToDefaultInitRuleConversion(false) + .setEnableLegacyFullModeForKeepRules(false) .setEnableExperimentalCheckEnumUnboxed(false) .setEnableExperimentalConvertCheckNotNull(false) .setEnableExperimentalWhyAreYouNotInlining(false) @@ -852,13 +852,12 @@ Position end = getPosition(); ProguardKeepRule rule = keepRuleBuilder.setSource(getSourceSnippet(contents, start, end)).setEnd(end).build(); - if (options.isEmptyMemberRulesToDefaultInitRuleConversionEnabled(configurationBuilder) + if (options.isLegacyFullModeForKeepRulesEnabled(configurationBuilder) && rule.getMemberRules().isEmpty() && rule.getType() != ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS) { // If there are no member rules, a default rule for the parameterless constructor applies // in compatibility mode. - if (options.isEmptyMemberRulesToDefaultInitRuleConversionWarningsEnabled( - configurationBuilder)) { + if (options.isLegacyFullModeForKeepRulesWarningsEnabled(configurationBuilder)) { reporter.warning( EmptyMemberRulesToDefaultInitRuleConversionDiagnostic.Factory.create(rule)); }
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 02373b6..ab5db09 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -6,20 +6,18 @@ import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault; -import com.android.tools.r8.utils.OptionalBool; - public class ProguardConfigurationParserOptions { - private final OptionalBool enableEmptyMemberRulesToDefaultInitRuleConversion; - private final boolean enableEmptyMemberRulesToDefaultInitRuleConversionWarnings; + private final boolean enableLegacyFullModeForKeepRules; + private final boolean enableLegacyFullModeForKeepRulesWarnings; private final boolean enableExperimentalCheckEnumUnboxed; private final boolean enableExperimentalConvertCheckNotNull; private final boolean enableExperimentalWhyAreYouNotInlining; private final boolean enableTestingOptions; ProguardConfigurationParserOptions( - OptionalBool enableEmptyMemberRulesToDefaultInitRuleConversion, - boolean enableEmptyMemberRulesToDefaultInitRuleConversionWarnings, + boolean enableLegacyFullModeForKeepRules, + boolean enableLegacyFullModeForKeepRulesWarnings, boolean enableExperimentalCheckEnumUnboxed, boolean enableExperimentalConvertCheckNotNull, boolean enableExperimentalWhyAreYouNotInlining, @@ -28,28 +26,25 @@ this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull; this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining; this.enableTestingOptions = enableTestingOptions; - this.enableEmptyMemberRulesToDefaultInitRuleConversion = - enableEmptyMemberRulesToDefaultInitRuleConversion; - this.enableEmptyMemberRulesToDefaultInitRuleConversionWarnings = - enableEmptyMemberRulesToDefaultInitRuleConversionWarnings; + this.enableLegacyFullModeForKeepRules = enableLegacyFullModeForKeepRules; + this.enableLegacyFullModeForKeepRulesWarnings = enableLegacyFullModeForKeepRulesWarnings; } public static Builder builder() { return new Builder(); } - public boolean isEmptyMemberRulesToDefaultInitRuleConversionEnabled( + public boolean isLegacyFullModeForKeepRulesEnabled( ProguardConfiguration.Builder configurationBuilder) { // TODO(b/356344563): Disable in full mode in the next major version. - return configurationBuilder.isForceProguardCompatibility() - || enableEmptyMemberRulesToDefaultInitRuleConversion.getOrDefault(true); + return configurationBuilder.isForceProguardCompatibility() || enableLegacyFullModeForKeepRules; } - public boolean isEmptyMemberRulesToDefaultInitRuleConversionWarningsEnabled( + public boolean isLegacyFullModeForKeepRulesWarningsEnabled( ProguardConfiguration.Builder configurationBuilder) { - assert isEmptyMemberRulesToDefaultInitRuleConversionEnabled(configurationBuilder); + assert isLegacyFullModeForKeepRulesEnabled(configurationBuilder); return !configurationBuilder.isForceProguardCompatibility() - && enableEmptyMemberRulesToDefaultInitRuleConversionWarnings; + && enableLegacyFullModeForKeepRulesWarnings; } public boolean isExperimentalCheckEnumUnboxedEnabled() { @@ -70,22 +65,20 @@ public static class Builder { - private OptionalBool enableEmptyMemberRulesToDefaultInitRuleConversion = OptionalBool.UNKNOWN; - private boolean enableEmptyMemberRulesToDefaultInitRuleConversionWarnings = false; + private boolean enableLegacyFullModeForKeepRules = true; + private boolean enableLegacyFullModeForKeepRulesWarnings = false; private boolean enableExperimentalCheckEnumUnboxed; private boolean enableExperimentalConvertCheckNotNull; private boolean enableExperimentalWhyAreYouNotInlining; private boolean enableTestingOptions; public Builder readEnvironment() { - enableEmptyMemberRulesToDefaultInitRuleConversion = + enableLegacyFullModeForKeepRules = parseSystemPropertyOrDefault( - "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion", - OptionalBool.UNKNOWN); - enableEmptyMemberRulesToDefaultInitRuleConversionWarnings = + "com.android.tools.r8.enableLegacyFullModeForKeepRules", true); + enableLegacyFullModeForKeepRulesWarnings = parseSystemPropertyOrDefault( - "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversionWarnings", - false); + "com.android.tools.r8.enableLegacyFullModeForKeepRulesWarnings", false); enableExperimentalCheckEnumUnboxed = parseSystemPropertyOrDefault( "com.android.tools.r8.experimental.enablecheckenumunboxed", false); @@ -100,10 +93,14 @@ return this; } - public Builder setEnableEmptyMemberRulesToDefaultInitRuleConversion( - boolean enableEmptyMemberRulesToDefaultInitRuleConversion) { - this.enableEmptyMemberRulesToDefaultInitRuleConversion = - OptionalBool.of(enableEmptyMemberRulesToDefaultInitRuleConversion); + public Builder setEnableLegacyFullModeForKeepRules(boolean enableLegacyFullModeForKeepRules) { + this.enableLegacyFullModeForKeepRules = enableLegacyFullModeForKeepRules; + return this; + } + + public Builder setEnableLegacyFullModeForKeepRulesWarnings( + boolean enableLegacyFullModeForKeepRulesWarnings) { + this.enableLegacyFullModeForKeepRulesWarnings = enableLegacyFullModeForKeepRulesWarnings; return this; } @@ -132,8 +129,8 @@ public ProguardConfigurationParserOptions build() { return new ProguardConfigurationParserOptions( - enableEmptyMemberRulesToDefaultInitRuleConversion, - enableEmptyMemberRulesToDefaultInitRuleConversionWarnings, + enableLegacyFullModeForKeepRules, + enableLegacyFullModeForKeepRulesWarnings, enableExperimentalCheckEnumUnboxed, enableExperimentalConvertCheckNotNull, enableExperimentalWhyAreYouNotInlining,
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 ad6845e..cbd5675 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.utils.AndroidApiLevel.B; import static com.android.tools.r8.utils.CfUtils.extractClassDescriptor; import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault; +import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault; import com.android.tools.r8.AndroidResourceConsumer; import com.android.tools.r8.AndroidResourceProvider; @@ -54,6 +55,7 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexClasspathClass; +import com.android.tools.r8.graph.DexDefinitionSupplier; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; @@ -1831,6 +1833,9 @@ public static class InlinerOptions { public boolean enableConstructorInlining = true; + public boolean enableConstructorInliningWithFinalFields = + parseSystemPropertyOrDefault( + "com.android.tools.r8.enableConstructorInliningWithFinalFields", false); public boolean enableInlining = !parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false); @@ -2659,6 +2664,11 @@ return !appView.enableWholeProgramOptimizations(); } + public boolean canAssignFinalInstanceFieldOutsideConstructor() { + // ART does not check this property. + return isGeneratingDex(); + } + /** * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this. * @@ -2859,6 +2869,14 @@ return intermediate || hasMinApi(AndroidApiLevel.L); } + public boolean canUseJavaLangVarHandleStoreStoreFence(DexDefinitionSupplier definitions) { + if (isGeneratingDex() && hasMinApi(AndroidApiLevel.P)) { + DexItemFactory factory = definitions.dexItemFactory(); + return definitions.hasDefinitionFor(factory.javaLangInvokeVarHandleMembers.storeStoreFence); + } + return false; + } + public boolean canUseJavaUtilObjects() { return hasFeaturePresentFrom(AndroidApiLevel.K); }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java index e4ed3bb..cb50996 100644 --- a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java +++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
@@ -98,7 +98,7 @@ .addProguardConfiguration( Collections.singletonList("-keep class NotPresent {}"), Origin.unknown()) .addLibraryFiles(getJava8RuntimeJar()) - .setEnableEmptyMemberRulesToDefaultInitRuleConversion(false) + .enableLegacyFullModeForKeepRules(false) .setProgramConsumer(DexIndexedConsumer.emptyConsumer()) .setMinApiLevel(1) .build());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineConstructorWithFinalFieldsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineConstructorWithFinalFieldsTest.java new file mode 100644 index 0000000..e5cca20 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineConstructorWithFinalFieldsTest.java
@@ -0,0 +1,108 @@ +// 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.inliner; + +import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; + +import com.android.tools.r8.AlwaysInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.MethodReferenceUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 InlineConstructorWithFinalFieldsTest 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()) + // Use most recent android.jar so that VarHandle is present. + .applyIf( + parameters.isDexRuntime(), + testBuilder -> testBuilder.addLibraryFiles(ToolHelper.getMostRecentAndroidJar())) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> { + assertFalse(options.inlinerOptions().enableConstructorInliningWithFinalFields); + options.inlinerOptions().enableConstructorInliningWithFinalFields = true; + }) + .enableAlwaysInliningAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + ClassSubject mainClassSubject = inspector.clazz(Main.class); + assertThat(mainClassSubject, isPresent()); + + MethodSubject initMethodSubject = mainClassSubject.init("int", "int"); + assertThat( + initMethodSubject, + isAbsentIf( + parameters.isDexRuntime() + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.P))); + + MethodSubject mainMethodSubject = mainClassSubject.mainMethod(); + assertThat(mainMethodSubject, isPresent()); + if (initMethodSubject.isPresent()) { + assertThat(mainMethodSubject, invokesMethod(initMethodSubject)); + } else { + assertThat( + mainMethodSubject, + invokesMethod(MethodReferenceUtils.instanceConstructor(Object.class))); + assertThat( + mainMethodSubject, + invokesMethod( + Reference.methodFromDescriptor( + "Ljava/lang/invoke/VarHandle;", "storeStoreFence", "()V"))); + } + }) + .run(parameters.getRuntime(), Main.class, "20", "22") + .assertSuccessWithOutputLines("42"); + } + + static class Main { + + final int x; + final int y; + + @AlwaysInline + Main(int x, int y) { + this.x = x; + this.y = y; + } + + public static void main(String[] args) { + Main main = new Main(Integer.parseInt(args[0]), Integer.parseInt(args[1])); + System.out.println(main); + } + + @Override + public String toString() { + return Integer.toString(x + y); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java new file mode 100644 index 0000000..d2b9b6e --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.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.ir.regalloc; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 ArgumentIn4BitRegisterTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimesAndAllApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .release() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + MethodSubject testMethodSubject = + inspector.clazz(Main.class).uniqueMethodWithOriginalName("test"); + assertThat(testMethodSubject, isPresent()); + // TODO(b/374266460): Should be 0. + assertEquals( + 1, + testMethodSubject + .streamInstructions() + .filter(InstructionSubject::isMove) + .count()); + }); + } + + static class Main { + + public static void test( + Object a, long b, long c, long d, long e, long f, long g, long h, long i) { + Main main = (Main) a; + // Keep all argument alive. + use(b); + use(c); + use(d); + use(e); + use(f); + use(h); + use(i); + } + + private static void use(long a) {} + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java new file mode 100644 index 0000000..d1977ab --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
@@ -0,0 +1,61 @@ +// 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.regalloc; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +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 RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build(); + } + + @Test + public void testD8() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .release() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + MethodSubject testMethodSubject = + inspector.clazz(Main.class).uniqueMethodWithOriginalName("test"); + assertThat(testMethodSubject, isPresent()); + // TODO(b/374262806): We should not need to move the argument value to a local + // register. Instead, we should use the argument register for the phi and load the + // constant 42 into this register, thereby avoiding any moves. + assertTrue( + testMethodSubject.streamInstructions().anyMatch(InstructionSubject::isMove)); + }); + } + + static class Main { + + static void test(long a, long b, long c, long d, long e, long f, long g, long h, int def) { + long i = (def & 1) == 0 ? a : 42; + accept(i); + } + + static void accept(long a) {} + } +}
diff --git a/src/test/testbase/java/com/android/tools/r8/AlwaysInline.java b/src/test/testbase/java/com/android/tools/r8/AlwaysInline.java index 97a799a..30fc4bd 100644 --- a/src/test/testbase/java/com/android/tools/r8/AlwaysInline.java +++ b/src/test/testbase/java/com/android/tools/r8/AlwaysInline.java
@@ -6,5 +6,5 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Target; -@Target({ElementType.METHOD}) +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface AlwaysInline {}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java index 72fd0ae..51174be 100644 --- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java +++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -153,7 +153,7 @@ libraryDesugaringTestConfiguration.configure(builder); builder.setAndroidPlatformBuild(androidPlatformBuild); if (!enableEmptyMemberRulesToDefaultInitRuleConversion.isUnknown()) { - builder.setEnableEmptyMemberRulesToDefaultInitRuleConversion( + builder.enableLegacyFullModeForKeepRules( enableEmptyMemberRulesToDefaultInitRuleConversion.toBoolean()); } builder.setEnableIsolatedSplits(enableIsolatedSplits);
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java index 9c37579..2c24643 100644 --- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java +++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -397,6 +397,11 @@ } @Override + public boolean isMove() { + return false; + } + + @Override public boolean isFilledNewArray() { return false; }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java index 5afb54f..f4e6a57 100644 --- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java +++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -95,6 +95,15 @@ import com.android.tools.r8.dex.code.DexIputWide; import com.android.tools.r8.dex.code.DexMonitorEnter; import com.android.tools.r8.dex.code.DexMonitorExit; +import com.android.tools.r8.dex.code.DexMove; +import com.android.tools.r8.dex.code.DexMove16; +import com.android.tools.r8.dex.code.DexMoveFrom16; +import com.android.tools.r8.dex.code.DexMoveObject; +import com.android.tools.r8.dex.code.DexMoveObject16; +import com.android.tools.r8.dex.code.DexMoveObjectFrom16; +import com.android.tools.r8.dex.code.DexMoveWide; +import com.android.tools.r8.dex.code.DexMoveWide16; +import com.android.tools.r8.dex.code.DexMoveWideFrom16; import com.android.tools.r8.dex.code.DexMulDouble; import com.android.tools.r8.dex.code.DexMulDouble2Addr; import com.android.tools.r8.dex.code.DexMulFloat; @@ -642,6 +651,19 @@ } @Override + public boolean isMove() { + return instruction instanceof DexMove + || instruction instanceof DexMove16 + || instruction instanceof DexMoveFrom16 + || instruction instanceof DexMoveObject + || instruction instanceof DexMoveObject16 + || instruction instanceof DexMoveObjectFrom16 + || instruction instanceof DexMoveWide + || instruction instanceof DexMoveWide16 + || instruction instanceof DexMoveWideFrom16; + } + + @Override public boolean isFilledNewArray() { return instruction instanceof DexFilledNewArray || instruction instanceof DexFilledNewArrayRange;
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java index 0dbe112..1852710 100644 --- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java +++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -164,6 +164,8 @@ boolean isMonitorExit(); + boolean isMove(); + boolean isFilledNewArray(); boolean isFilledNewArrayData();
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 index b76801c..089244c 100644 --- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 +++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@ -9c8c027ae1cd5b8768574a7fb2bb0698ef09b907 \ No newline at end of file +aa0b23115292ec22a24d4166101c1116d264e1cb \ No newline at end of file