[KeepAnno] Maintain field preconditions using witnesses
This adds an original-field witness type and instructions such that the
origin of the field can be traced when the field and/or holder is
inlined. Currently only the class inliner will allow inlining as the
abstract value should prohibit other value usages.
Bug: b/323816623
Bug: b/334822108
Change-Id: If9da780ea4f31a1da278954cf6b7a19e95987a3a
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index a3da4f7..75ee123 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -138,13 +138,16 @@
writeAnnotations(null, field.annotations(), ps);
ps.print(field.accessFlags + " ");
ps.print(retracer.toSourceString(field.getReference()));
- if (!retracer.isEmpty()) {
- ps.println("# Residual: '" + field.getReference().toSourceString() + "'");
- }
if (field.isStatic() && field.hasExplicitStaticValue()) {
ps.print(" = " + field.getStaticValue());
}
ps.println();
+ if (!retracer.isEmpty()) {
+ ps.println("# Residual: '" + field.getReference().toSourceString() + "'");
+ }
+ if (field.hasNonIdentityOriginalFieldWitness()) {
+ ps.println("# Original: '" + field.getOriginalFieldWitness() + "'");
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 2093937..5f72db6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
import com.android.tools.r8.kotlin.KotlinMetadataUtils;
import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -31,6 +32,7 @@
public final FieldAccessFlags accessFlags;
private DexValue staticValue;
+ private OriginalFieldWitness originalFieldWitness = null;
private final boolean deprecated;
/** Generic signature information if the attribute is present in the input */
private FieldTypeSignature genericSignature;
@@ -358,6 +360,28 @@
return true;
}
+ void recordOriginalFieldWitness(
+ ProgramField field, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ assert ObjectUtils.identical(this, field.getDefinition());
+ originalFieldWitness = OriginalFieldWitness.forProgramField(field);
+ optimizationInfo =
+ optimizationInfo
+ .toMutableOptimizationInfo()
+ .addOriginalFieldWitness(originalFieldWitness, field, appView);
+ }
+
+ public OriginalFieldWitness getOriginalFieldWitness() {
+ return originalFieldWitness;
+ }
+
+ public boolean hasOriginalFieldWitness() {
+ return originalFieldWitness != null;
+ }
+
+ public boolean hasNonIdentityOriginalFieldWitness() {
+ return hasOriginalFieldWitness() && !originalFieldWitness.isEqualToDexField(getReference());
+ }
+
public static class Builder {
private DexField field;
@@ -366,6 +390,7 @@
private FieldTypeSignature genericSignature = FieldTypeSignature.noSignature();
private KotlinFieldLevelInfo kotlinInfo = KotlinMetadataUtils.getNoKotlinInfo();
private DexValue staticValue = null;
+ private OriginalFieldWitness originalFieldWitness = null;
private ComputedApiLevel apiLevel = ComputedApiLevel.notSet();
private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
private boolean deprecated;
@@ -388,6 +413,7 @@
kotlinInfo = from.getKotlinInfo();
annotations = from.annotations();
staticValue = from.staticValue;
+ originalFieldWitness = from.originalFieldWitness;
apiLevel = from.getApiLevel();
optimizationInfo =
from.optimizationInfo.isMutableOptimizationInfo()
@@ -487,6 +513,7 @@
d8R8Synthesized);
dexEncodedField.setKotlinMemberInfo(kotlinInfo);
dexEncodedField.optimizationInfo = optimizationInfo;
+ dexEncodedField.originalFieldWitness = originalFieldWitness;
buildConsumer.accept(dexEncodedField);
return dexEncodedField;
}
diff --git a/src/main/java/com/android/tools/r8/graph/OriginalFieldWitness.java b/src/main/java/com/android/tools/r8/graph/OriginalFieldWitness.java
new file mode 100644
index 0000000..a66cc33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/OriginalFieldWitness.java
@@ -0,0 +1,87 @@
+// 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.graph;
+
+import com.android.tools.r8.lightir.LirConstant;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class OriginalFieldWitness implements LirConstant, StructuralItem<OriginalFieldWitness> {
+ private final OriginalFieldWitness parent;
+ private final DexField originalField;
+
+ private static void specify(StructuralSpecification<OriginalFieldWitness, ?> spec) {
+ spec.withItem(d -> d.originalField).withNullableItem(d -> d.parent);
+ }
+
+ private OriginalFieldWitness(OriginalFieldWitness parent, DexField originalField) {
+ this.parent = parent;
+ this.originalField = originalField;
+ }
+
+ @Override
+ public OriginalFieldWitness self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<OriginalFieldWitness> getStructuralMapping() {
+ return OriginalFieldWitness::specify;
+ }
+
+ public static OriginalFieldWitness forProgramField(ProgramField field) {
+ return new OriginalFieldWitness(null, field.getReference());
+ }
+
+ public boolean isEqualToDexField(DexField field) {
+ return parent == null && originalField.isIdenticalTo(field);
+ }
+
+ public void forEachReference(Consumer<? super DexField> fn) {
+ fn.accept(originalField);
+ if (parent != null) {
+ parent.forEachReference(fn);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (parent == null) {
+ return originalField.toString();
+ }
+ return originalField + ":" + parent.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return Equatable.equalsImpl(this, obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(originalField, parent);
+ }
+
+ @Override
+ public LirConstantOrder getLirConstantOrder() {
+ return LirConstantOrder.ORIGINAL_FIELD_WITNESS;
+ }
+
+ @Override
+ public int internalLirConstantAcceptCompareTo(LirConstant other, CompareToVisitor visitor) {
+ return visitor.visit(this, (OriginalFieldWitness) other, OriginalFieldWitness::specify);
+ }
+
+ @Override
+ public void internalLirConstantAcceptHashing(HashingVisitor visitor) {
+ visitor.visit(this, OriginalFieldWitness::specify);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 60d3b0e..7ed8b59 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -99,4 +99,8 @@
public KotlinFieldLevelInfo getKotlinInfo() {
return getDefinition().getKotlinInfo();
}
+
+ public void recordOriginalFieldWitness(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ getDefinition().recordOriginalFieldWitness(this, appView);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 6238dd5..5d92dc1 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -65,6 +65,8 @@
assert position.hasCallerPosition();
}
+ public void registerOriginalFieldWitness(OriginalFieldWitness witness) {}
+
public void registerRecordFieldValues(DexField[] fields) {
registerTypeReference(appView.dexItemFactory().objectArrayType);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index fdb9d4b..cc8cfb9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -237,6 +237,10 @@
return null;
}
+ public boolean hasWitness() {
+ return false;
+ }
+
public boolean isUnknown() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
index 047e4ef..1814214 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
@@ -38,6 +38,11 @@
|| abstractValue.equals(otherAbstractValue)) {
return abstractValue;
}
+ if (abstractValue.hasWitness() || otherAbstractValue.hasWitness()) {
+ // TODO(b/334822108): Implement support for actually joining values with witness.
+ assert !(abstractValue.hasWitness() && otherAbstractValue.hasWitness());
+ return unknown();
+ }
if (type.isReferenceType()) {
return joinReference(abstractValue, otherAbstractValue);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueWithWitness.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueWithWitness.java
new file mode 100644
index 0000000..b11af88
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueWithWitness.java
@@ -0,0 +1,52 @@
+// 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.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class AbstractValueWithWitness extends AbstractValue {
+
+ private static final AbstractValueWithWitness INSTANCE = new AbstractValueWithWitness();
+
+ private AbstractValueWithWitness() {}
+
+ public static AbstractValueWithWitness getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean hasWitness() {
+ return true;
+ }
+
+ @Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
+ public AbstractValue rewrittenWithLens(
+ AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractValueWithWitness";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index e111001..c0888c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -111,6 +111,10 @@
return get(Opcodes.RESOURCE_CONST_NUMBER);
}
+ public boolean mayHaveOriginalFieldWitness() {
+ return get(Opcodes.ORIGINAL_FIELD_WITNESS);
+ }
+
public boolean mayHaveConstString() {
return get(Opcodes.CONST_STRING);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e81b4d0..6b9743a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -880,6 +880,14 @@
return null;
}
+ public boolean isOriginalFieldWitness() {
+ return false;
+ }
+
+ public OriginalFieldWitnessInstruction asOriginalFieldWitness() {
+ return null;
+ }
+
public boolean isConstInstruction() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 58889ac..5ee18f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -78,4 +78,5 @@
int UNINITIALIZED_THIS_LOCAL_READ = 69;
int RECORD_FIELD_VALUES = 70;
int RESOURCE_CONST_NUMBER = 71;
+ int ORIGINAL_FIELD_WITNESS = 72;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/OriginalFieldWitnessInstruction.java b/src/main/java/com/android/tools/r8/ir/code/OriginalFieldWitnessInstruction.java
new file mode 100644
index 0000000..7fbab29
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/OriginalFieldWitnessInstruction.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.ir.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.OriginalFieldWitness;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
+
+public class OriginalFieldWitnessInstruction extends Move {
+
+ private final OriginalFieldWitness witness;
+
+ public OriginalFieldWitnessInstruction(
+ OriginalFieldWitness witness, Value outValue, Value inValue) {
+ super(outValue, inValue);
+ this.witness = witness;
+ }
+
+ public OriginalFieldWitness getWitness() {
+ return witness;
+ }
+
+ @Override
+ public int opcode() {
+ return Opcodes.ORIGINAL_FIELD_WITNESS;
+ }
+
+ @Override
+ public boolean isOriginalFieldWitness() {
+ return true;
+ }
+
+ @Override
+ public OriginalFieldWitnessInstruction asOriginalFieldWitness() {
+ return this;
+ }
+
+ @Override
+ public <T> T accept(InstructionVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addOriginalFieldWitness(getWitness(), src());
+ }
+
+ @Override
+ public boolean identicalNonValueNonPositionParts(Instruction other) {
+ if (this == other) {
+ return true;
+ }
+ if (!other.isOriginalFieldWitness()) {
+ return false;
+ }
+ OriginalFieldWitnessInstruction o = other.asOriginalFieldWitness();
+ return witness.isEqualTo(o.witness);
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ throw new Unreachable("We never write out witness instructions");
+ }
+
+ @Override
+ public void buildDex(DexBuilder builder) {
+ throw new Unreachable("We never write out witness instructions");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 8810391..1f92cc0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.conversion.passes.DexItemBasedConstStringRemover;
import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
import com.android.tools.r8.ir.conversion.passes.InitClassRemover;
+import com.android.tools.r8.ir.conversion.passes.OriginalFieldWitnessRemover;
import com.android.tools.r8.ir.conversion.passes.StringSwitchConverter;
import com.android.tools.r8.ir.conversion.passes.StringSwitchRemover;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
@@ -147,6 +148,7 @@
new CodeRewriterPassCollection(
new AdaptClassStringsRewriter(appView),
new ConstResourceNumberRemover(appView),
+ new OriginalFieldWitnessRemover(appView),
// Must run before DexItemBasedConstStringRemover.
new StringSwitchRemover(appView),
new DexItemBasedConstStringRemover(appView),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/OriginalFieldWitnessRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/OriginalFieldWitnessRemover.java
new file mode 100644
index 0000000..aebea12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/OriginalFieldWitnessRemover.java
@@ -0,0 +1,47 @@
+// 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.OriginalFieldWitnessInstruction;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+
+public class OriginalFieldWitnessRemover extends CodeRewriterPass<AppInfo> {
+
+ public OriginalFieldWitnessRemover(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getRewriterId() {
+ return "OriginalFieldWitnessRemover";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ return code.metadata().mayHaveOriginalFieldWitness();
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isOriginalFieldWitness()) {
+ OriginalFieldWitnessInstruction instruction = current.asOriginalFieldWitness();
+ instruction.outValue().replaceUsers(instruction.src());
+ iterator.removeOrReplaceByDebugLocalRead();
+ hasChanged = true;
+ }
+ }
+ return CodeRewriterResult.hasChanged(hasChanged);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 46d4883..75c8231 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.IRCode;
@@ -49,6 +50,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.OriginalFieldWitnessInstruction;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
@@ -765,11 +767,20 @@
AffectedValues affectedValues,
Map<DexField, FieldValueHelper> fieldHelpers) {
Value value = fieldRead.outValue();
+ Instruction replacement = null;
if (value != null) {
FieldValueHelper helper =
fieldHelpers.computeIfAbsent(
fieldRead.getField(), field -> new FieldValueHelper(field, code, root, appView));
Value newValue = helper.getValueForFieldRead(fieldRead.getBlock(), fieldRead);
+ DexEncodedField definition = eligibleClass.lookupInstanceField(fieldRead.getField());
+ if (definition.getOriginalFieldWitness() != null) {
+ Value dest = code.createValue(newValue.getType(), newValue.getLocalInfo());
+ replacement =
+ new OriginalFieldWitnessInstruction(
+ definition.getOriginalFieldWitness(), dest, newValue);
+ newValue = dest;
+ }
value.replaceUsers(newValue);
for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) {
fieldValueHelper.replaceValue(value, newValue);
@@ -781,7 +792,12 @@
affectedValues.add(newValue);
affectedValues.addAll(newValue.affectedValues());
}
- removeInstruction(fieldRead);
+ if (replacement != null) {
+ BasicBlockInstructionListIterator it = fieldRead.getBlock().listIterator(code, fieldRead);
+ it.replaceCurrentInstruction(replacement, affectedValues);
+ } else {
+ removeInstruction(fieldRead);
+ }
}
private void removeFieldReadsFromStaticGet(IRCode code, AffectedValues affectedValues) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 6c7a777..821a5ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -6,12 +6,17 @@
import static java.util.Collections.emptySet;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueFieldJoiner;
+import com.android.tools.r8.ir.analysis.value.AbstractValueWithWitness;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
@@ -66,6 +71,18 @@
return setAbstractValue(abstractValue);
}
+ public MutableFieldOptimizationInfo addOriginalFieldWitness(
+ OriginalFieldWitness unused,
+ ProgramField field,
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ AbstractValueWithWitness witnessValue = AbstractValueWithWitness.getInstance();
+ if (abstractValue.isUnknown()) {
+ return setAbstractValue(witnessValue);
+ }
+ return setAbstractValue(
+ new AbstractValueFieldJoiner(appView).join(abstractValue, witnessValue, field));
+ }
+
private MutableFieldOptimizationInfo setAbstractValue(AbstractValue abstractValue) {
assert getAbstractValue().isUnknown()
|| abstractValue.isNonTrivial()
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 42c94e5..27f4575 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.proto.ArgumentInfo;
import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -80,6 +81,7 @@
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.OriginalFieldWitnessInstruction;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.RecordFieldValues;
@@ -1037,5 +1039,12 @@
Value dest = getOutValueForNextInstruction(typeElement);
addInstruction(new RecordFieldValues(fields, dest, getValues(values)));
}
+
+ @Override
+ public void onOriginalFieldWitness(OriginalFieldWitness witness, EV value) {
+ // The instruction is a move and its type must be computed as that of its source value.
+ Value dest = getOutValueForNextInstruction(TypeElement.getBottom());
+ addInstruction(new OriginalFieldWitnessInstruction(witness, dest, getValue(value)));
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index a35a20f..ba37ad1 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -1114,4 +1115,11 @@
return addInstructionTemplate(
LirOpcodes.RECORDFIELDVALUES, Collections.singletonList(payload), values);
}
+
+ public LirBuilder<V, EV> addOriginalFieldWitness(OriginalFieldWitness witness, V value) {
+ return addInstructionTemplate(
+ LirOpcodes.ORIGINALFIELDWITNESS,
+ Collections.singletonList(witness),
+ Collections.singletonList(value));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirConstant.java b/src/main/java/com/android/tools/r8/lightir/LirConstant.java
index 41a7115..c889c00 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirConstant.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirConstant.java
@@ -25,7 +25,8 @@
STRING_SWITCH,
FILL_ARRAY,
NAME_COMPUTATION,
- RECORD_FIELD_VALUES
+ RECORD_FIELD_VALUES,
+ ORIGINAL_FIELD_WITNESS
}
class LirConstantStructuralAcceptor implements StructuralAcceptor<LirConstant> {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 702dbba..d1881c3 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -213,6 +213,7 @@
int CONSTCLASS_IGNORE_COMPAT = 226;
int STRINGSWITCH = 227;
int RESOURCENUMBER = 228;
+ int ORIGINALFIELDWITNESS = 229;
static String toString(int opcode) {
switch (opcode) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index 4fe7d83..953890c 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NumericType;
@@ -526,6 +527,10 @@
onInstruction();
}
+ public void onOriginalFieldWitness(OriginalFieldWitness witness, EV value) {
+ onInstruction();
+ }
+
private LirConstant getConstantItem(int index) {
return code.getConstantItem(index);
}
@@ -1288,6 +1293,14 @@
onConstResourceNumber(value);
return;
}
+ case LirOpcodes.ORIGINALFIELDWITNESS:
+ {
+ OriginalFieldWitness witness =
+ (OriginalFieldWitness) getConstantItem(view.getNextConstantOperand());
+ EV value = getNextValueOperand(view);
+ onOriginalFieldWitness(witness, value);
+ return;
+ }
default:
throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index c27b1ed..e01877a 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.MemberType;
@@ -443,4 +444,10 @@
public void onRecordFieldValues(DexField[] fields, List<EV> values) {
appendValueArguments(values);
}
+
+ @Override
+ public void onOriginalFieldWitness(OriginalFieldWitness witness, EV value) {
+ appendOutValue().append(witness);
+ appendValueArguments(value);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
index d97b0a9..7b8eeac 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
@@ -235,4 +236,9 @@
public void onRecordFieldValues(DexField[] fields, List<EV> values) {
registry.registerRecordFieldValues(fields);
}
+
+ @Override
+ public void onOriginalFieldWitness(OriginalFieldWitness witness, EV value) {
+ registry.registerOriginalFieldWitness(witness);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java
index c25ed35..4fed1f1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java
@@ -3,21 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.propagation;
-import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.type.DynamicType;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
public class FlowGraphFieldNode extends FlowGraphNode {
@@ -38,42 +26,6 @@
return field.getType();
}
- void addDefaultValue(AppView<AppInfoWithLiveness> appView, Action onChangedAction) {
- AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
- AbstractValue defaultValue;
- if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
- defaultValue = field.getDefinition().getStaticValue().toAbstractValue(abstractValueFactory);
- } else if (field.getType().isPrimitiveType()) {
- defaultValue = abstractValueFactory.createZeroValue();
- } else {
- defaultValue = abstractValueFactory.createUncheckedNullValue();
- }
- NonEmptyValueState fieldStateToAdd;
- if (field.getType().isArrayType()) {
- Nullability defaultNullability = Nullability.definitelyNull();
- fieldStateToAdd = ConcreteArrayTypeValueState.create(defaultNullability);
- } else if (field.getType().isClassType()) {
- assert defaultValue.isNull() || defaultValue.isSingleStringValue();
- DynamicType dynamicType =
- defaultValue.isNull()
- ? DynamicType.definitelyNull()
- : DynamicType.createExact(
- TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
- fieldStateToAdd = ConcreteClassTypeValueState.create(defaultValue, dynamicType);
- } else {
- assert field.getType().isPrimitiveType();
- fieldStateToAdd = ConcretePrimitiveTypeValueState.create(defaultValue);
- }
- if (fieldStateToAdd.isConcrete()) {
- addState(appView, fieldStateToAdd.asConcrete(), onChangedAction);
- } else {
- // We should always be able to map static field values to an unknown abstract value.
- assert false;
- setStateToUnknown();
- onChangedAction.execute();
- }
- }
-
@Override
ValueState getState() {
return fieldState;
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 9e6fbf6..667479b 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.code.Position;
@@ -105,6 +106,12 @@
}
@Override
+ public void registerOriginalFieldWitness(OriginalFieldWitness witness) {
+ super.registerOriginalFieldWitness(witness);
+ enqueuer.traceOriginalFieldWitness(witness);
+ }
+
+ @Override
public void registerInitClass(DexType clazz) {
super.registerInitClass(clazz);
enqueuer.traceInitClass(clazz, getContext());
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 629d003..0b345b0 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -80,6 +80,7 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.OriginalFieldWitness;
import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramDerivedContext;
@@ -1689,7 +1690,15 @@
}
}
- void markEffectivelyLiveOriginalReference(DexReference reference) {
+ void traceOriginalFieldWitness(OriginalFieldWitness witness) {
+ markEffectivelyLiveOriginalReference(witness);
+ }
+
+ private void markEffectivelyLiveOriginalReference(OriginalFieldWitness witness) {
+ witness.forEachReference(this::markEffectivelyLiveOriginalReference);
+ }
+
+ private void markEffectivelyLiveOriginalReference(DexReference reference) {
// TODO(b/325014359): It might be reasonable to reduce this map size by tracking which items
// actually are used in preconditions.
if (effectivelyLiveOriginalReferences.add(reference) && reference.isDexMember()) {
@@ -3279,7 +3288,11 @@
if (!options.testing.isKeepAnnotationsEnabled()) {
return;
}
- markEffectivelyLiveOriginalReference(field.getReference());
+ if (field.getDefinition().hasOriginalFieldWitness()) {
+ markEffectivelyLiveOriginalReference(field.getDefinition().getOriginalFieldWitness());
+ } else {
+ markEffectivelyLiveOriginalReference(field.getReference());
+ }
}
private void markFieldAsLive(ProgramField field, ProgramMethod context) {
@@ -3851,7 +3864,7 @@
if (mode.isInitialTreeShaking()) {
applicableRules =
KeepAnnotationMatcher.computeInitialRules(
- appInfo, keepDeclarations, options.getThreadingModule(), executorService);
+ appView, keepDeclarations, options.getThreadingModule(), executorService);
// Amend library methods with covariant return types.
timing.begin("Model library");
modelLibraryMethodsWithCovariantReturnTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluator.java b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluator.java
index ea65365..ab1abbb 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluator.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.shaking.rules;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
import java.util.ArrayList;
@@ -65,11 +68,22 @@
rules.add(rule);
}
- public ApplicableRulesEvaluator build() {
+ public ApplicableRulesEvaluator build(AppView<? extends AppInfoWithClassHierarchy> appView) {
if (rootConsequences.isEmpty() && rules.isEmpty()) {
return ApplicableRulesEvaluator.empty();
}
- return new ApplicableRulesEvaluatorImpl<>(rootConsequences, new ArrayList<>(rules));
+ return new ApplicableRulesEvaluatorImpl<>(
+ rootConsequences,
+ new ArrayList<>(rules),
+ (rule, enqueuer) -> {
+ // When evaluating the initial rules, if a satisfied rule has a field precondition,
+ // mark it to maintain its original field witness.
+ for (ProgramDefinition precondition : rule.getSatisfiedPreconditions()) {
+ if (precondition.isProgramField()) {
+ precondition.asProgramField().recordOriginalFieldWitness(appView);
+ }
+ }
+ });
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
index 5e65713..d8b3d66 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
@@ -11,8 +11,10 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.BiConsumer;
-public class ApplicableRulesEvaluatorImpl<T> extends ApplicableRulesEvaluator {
+public class ApplicableRulesEvaluatorImpl<T, R extends PendingConditionalRuleBase<T>>
+ extends ApplicableRulesEvaluator {
private final MinimumKeepInfoCollection rootConsequences;
@@ -20,16 +22,25 @@
private static final int reallocMinThreshold = 1;
private static final int reallocRatioThreshold = 10;
private int prunedCount = 0;
- private List<PendingConditionalRuleBase<T>> pendingConditionalRules;
+ private List<R> pendingConditionalRules;
private final List<MaterializedConditionalRule> materializedRules = new ArrayList<>();
+ private BiConsumer<R, Enqueuer> onSatisfiedRuleCallback;
+
+ ApplicableRulesEvaluatorImpl(
+ MinimumKeepInfoCollection rootConsequences, List<R> conditionalRules) {
+ this(rootConsequences, conditionalRules, (unusedRule, unusedEnqueuer) -> {});
+ }
+
ApplicableRulesEvaluatorImpl(
MinimumKeepInfoCollection rootConsequences,
- List<PendingConditionalRuleBase<T>> conditionalRules) {
+ List<R> conditionalRules,
+ BiConsumer<R, Enqueuer> onSatisfiedRuleCallback) {
assert !rootConsequences.isEmpty() || !conditionalRules.isEmpty();
this.rootConsequences = rootConsequences;
this.pendingConditionalRules = conditionalRules;
+ this.onSatisfiedRuleCallback = onSatisfiedRuleCallback;
}
@Override
@@ -48,12 +59,13 @@
// TODO(b/323816623): If we tracked newly live, we could speed up finding rules.
// TODO(b/323816623): Parallelize this.
for (int i = 0; i < pendingConditionalRules.size(); i++) {
- PendingConditionalRuleBase<T> rule = pendingConditionalRules.get(i);
+ R rule = pendingConditionalRules.get(i);
if (rule != null && rule.isSatisfiedAfterUpdate(enqueuer)) {
++prunedCount;
pendingConditionalRules.set(i, null);
enqueuer.includeMinimumKeepInfo(rule.getConsequences());
materializedRules.add(rule.asMaterialized());
+ onSatisfiedRuleCallback.accept(rule, enqueuer);
}
}
@@ -68,8 +80,8 @@
Math.max(reallocMinThreshold, pendingConditionalRules.size() / reallocRatioThreshold);
if (prunedCount >= threshold) {
int newSize = pendingConditionalRules.size() - prunedCount;
- List<PendingConditionalRuleBase<T>> newPending = new ArrayList<>(newSize);
- for (PendingConditionalRuleBase<T> rule : pendingConditionalRules) {
+ List<R> newPending = new ArrayList<>(newSize);
+ for (R rule : pendingConditionalRules) {
if (rule != null) {
assert rule.isOutstanding();
newPending.add(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
index 82c28fb..6a74308 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.Unimplemented;
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.ProgramDefinition;
@@ -56,20 +57,20 @@
public class KeepAnnotationMatcher {
public static ApplicableRulesEvaluator computeInitialRules(
- AppInfoWithClassHierarchy appInfo,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
List<KeepDeclaration> keepDeclarations,
ThreadingModule threadingModule,
ExecutorService executorService)
throws ExecutionException {
KeepAnnotationMatcherPredicates predicates =
- new KeepAnnotationMatcherPredicates(appInfo.dexItemFactory());
+ new KeepAnnotationMatcherPredicates(appView.dexItemFactory());
ApplicableRulesEvaluator.Builder builder = ApplicableRulesEvaluator.initialRulesBuilder();
ThreadUtils.processItems(
keepDeclarations,
- declaration -> processDeclaration(declaration, appInfo, predicates, builder),
+ declaration -> processDeclaration(declaration, appView.appInfo(), predicates, builder),
threadingModule,
executorService);
- return builder.build();
+ return builder.build(appView);
}
private static void processDeclaration(
@@ -107,19 +108,6 @@
minimumKeepInfoCollection.getOrCreateMinimumKeepInfoFor(item.getReference());
updateWithConstraints(item, joiner, result.constraints.get(i), result.edge);
});
- // TODO(b/323816623): Encode originals instead of soft-pinning class/field preconditions.
- for (ProgramDefinition precondition : result.preconditions) {
- if (precondition.isClass() || precondition.isField()) {
- minimumKeepInfoCollection
- .getOrCreateMinimumKeepInfoFor(precondition.getReference())
- .disallowOptimization();
- if (precondition.isField()) {
- minimumKeepInfoCollection
- .getOrCreateMinimumKeepInfoFor(precondition.getContextType())
- .disallowOptimization();
- }
- }
- }
return minimumKeepInfoCollection;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRule.java b/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRule.java
index 22d06ed..952fd50 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRule.java
@@ -16,8 +16,8 @@
}
@Override
- DexReference getReference(DexReference item) {
- return item;
+ List<DexReference> getReferences(List<DexReference> items) {
+ return items;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRuleBase.java b/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRuleBase.java
index 5716e74..8ca2654 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/PendingConditionalRuleBase.java
@@ -8,12 +8,14 @@
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
public abstract class PendingConditionalRuleBase<T> {
- private final List<T> outstandingPreconditions;
- private final List<DexReference> satisfiedPreconditions;
+ private List<T> outstandingPreconditions;
+ private final List<T> satisfiedPreconditions;
private final MinimumKeepInfoCollection consequences;
PendingConditionalRuleBase(List<T> preconditions, MinimumKeepInfoCollection consequences) {
@@ -22,7 +24,11 @@
this.consequences = consequences;
}
- abstract DexReference getReference(T item);
+ public List<T> getSatisfiedPreconditions() {
+ return satisfiedPreconditions;
+ }
+
+ abstract List<DexReference> getReferences(List<T> items);
abstract boolean isSatisfied(T item, Enqueuer enqueuer);
@@ -32,7 +38,7 @@
MaterializedConditionalRule asMaterialized() {
assert !isOutstanding();
- return new MaterializedConditionalRule(satisfiedPreconditions, consequences);
+ return new MaterializedConditionalRule(getReferences(satisfiedPreconditions), consequences);
}
final boolean isOutstanding() {
@@ -45,7 +51,7 @@
for (T precondition : outstandingPreconditions) {
if (isSatisfied(precondition, enqueuer)) {
++newlySatisfied;
- satisfiedPreconditions.add(getReference(precondition));
+ satisfiedPreconditions.add(precondition);
}
}
// Not satisfied and nothing changed.
@@ -54,13 +60,26 @@
}
// The rule is satisfied in full.
if (newlySatisfied == outstandingPreconditions.size()) {
- outstandingPreconditions.clear();
+ outstandingPreconditions = Collections.emptyList();
return true;
}
// Partially satisfied.
// This is expected to be the uncommon case so the update to outstanding is delayed to here.
- outstandingPreconditions.removeIf(
- outstanding -> satisfiedPreconditions.contains(getReference(outstanding)));
+ int newOutstandingSize = outstandingPreconditions.size() - newlySatisfied;
+ assert newOutstandingSize > 0;
+ List<DexReference> satisfied = getReferences(satisfiedPreconditions);
+ List<DexReference> outstanding = getReferences(outstandingPreconditions);
+ List<T> newOutstanding = new ArrayList<>();
+ Iterator<T> it = outstandingPreconditions.iterator();
+ for (DexReference reference : outstanding) {
+ T precondition = it.next();
+ if (!satisfied.contains(reference)) {
+ newOutstanding.add(precondition);
+ }
+ }
+ assert !it.hasNext();
+ assert newOutstanding.size() == newOutstandingSize;
+ outstandingPreconditions = newOutstanding;
assert isOutstanding();
return false;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/PendingInitialConditionalRule.java b/src/main/java/com/android/tools/r8/shaking/rules/PendingInitialConditionalRule.java
index 8983f20..8e916c9 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/PendingInitialConditionalRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/PendingInitialConditionalRule.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
+import com.android.tools.r8.utils.ListUtils;
import java.util.List;
public class PendingInitialConditionalRule extends PendingConditionalRuleBase<ProgramDefinition> {
@@ -19,8 +20,8 @@
}
@Override
- DexReference getReference(ProgramDefinition item) {
- return item.getReference();
+ List<DexReference> getReferences(List<ProgramDefinition> items) {
+ return ListUtils.map(items, ProgramDefinition::getReference);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
index 15f96ab..c68a01d 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -49,8 +49,15 @@
}
private void checkOutput(CodeInspector inspector) {
- assertThat(inspector.clazz(A.class), isPresent());
- assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent());
+ if (parameters.isNativeR8()) {
+ // A and its field are completely eliminated despite being a precondition.
+ assertThat(inspector.clazz(A.class), isAbsent());
+ } else {
+ // A and its field are soft-pinned in the rules-based extraction.
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(
+ inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent());
+ }
assertThat(inspector.clazz(B.class), isPresent());
assertThat(inspector.clazz(B.class).init(), isPresent());
assertThat(inspector.clazz(B.class).init("int"), isAbsent());