[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());