Skip read-for-write instructions in is-field-read analysis

Bug: 149673849
Change-Id: I291205160a3baf258b3b0a2a223e348ea39575f9
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index ab9165b..2f90085 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -44,11 +46,14 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -75,7 +80,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.objectweb.asm.Opcodes;
 
 /** Rudimentary printer to print the source representation for creating CfCode object. */
 public class CfCodePrinter extends CfPrinter {
@@ -531,24 +535,28 @@
   }
 
   @Override
-  public void print(CfFieldInstruction insn) {
-    printNewInstruction(
-        "CfFieldInstruction", fieldInstructionOpcode(insn), dexField(insn.getField()));
+  public void print(CfInstanceFieldRead insn) {
+    printNewInstruction("CfInstanceFieldRead", dexField(insn.getField()));
   }
 
-  private String fieldInstructionOpcode(CfFieldInstruction insn) {
-    switch (insn.getOpcode()) {
-      case Opcodes.GETSTATIC:
-        return asmOpcodesType() + ".GETSTATIC";
-      case Opcodes.PUTSTATIC:
-        return asmOpcodesType() + ".PUTSTATIC";
-      case Opcodes.GETFIELD:
-        return asmOpcodesType() + ".GETFIELD";
-      case Opcodes.PUTFIELD:
-        return asmOpcodesType() + ".PUTFIELD";
-      default:
-        throw new Unimplemented();
-    }
+  @Override
+  public void print(CfInstanceFieldWrite insn) {
+    printNewInstruction("CfInstanceFieldWrite", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfStaticFieldRead insn) {
+    printNewInstruction("CfStaticFieldRead", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfStaticFieldWrite insn) {
+    printNewInstruction("CfStaticFieldWrite", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfFieldInstruction insn) {
+    throw new Unreachable();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 3bdfcbf..d0a3c23 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -25,6 +25,8 @@
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfIinc;
 import com.android.tools.r8.cf.code.CfInitClass;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -46,6 +48,8 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfSwitch.Kind;
@@ -472,6 +476,22 @@
     appendClass(insn.getType());
   }
 
+  public void print(CfInstanceFieldRead insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfInstanceFieldWrite insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfStaticFieldRead insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfStaticFieldWrite insn) {
+    print(insn.asFieldInstruction());
+  }
+
   public void print(CfFieldInstruction insn) {
     indent();
     switch (insn.getOpcode()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index a2f4e80..2585329 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -10,14 +10,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -28,11 +26,10 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
-import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfFieldInstruction extends CfInstruction {
+public abstract class CfFieldInstruction extends CfInstruction {
 
   private final int opcode;
   private final DexField field;
@@ -53,6 +50,21 @@
     assert field.type == declaringField.type;
   }
 
+  public static CfFieldInstruction create(int opcode, DexField field, DexField declaringField) {
+    switch (opcode) {
+      case Opcodes.GETSTATIC:
+        return new CfStaticFieldRead(field, declaringField);
+      case Opcodes.PUTSTATIC:
+        return new CfStaticFieldWrite(field, declaringField);
+      case Opcodes.GETFIELD:
+        return new CfInstanceFieldRead(field, declaringField);
+      case Opcodes.PUTFIELD:
+        return new CfInstanceFieldWrite(field, declaringField);
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
+
   public DexField getField() {
     return field;
   }
@@ -110,27 +122,6 @@
   }
 
   @Override
-  void internalRegisterUse(
-      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
-    switch (opcode) {
-      case Opcodes.GETFIELD:
-        registry.registerInstanceFieldRead(field);
-        break;
-      case Opcodes.PUTFIELD:
-        registry.registerInstanceFieldWrite(field);
-        break;
-      case Opcodes.GETSTATIC:
-        registry.registerStaticFieldRead(field);
-        break;
-      case Opcodes.PUTSTATIC:
-        registry.registerStaticFieldWrite(field);
-        break;
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
   public boolean canThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
new file mode 100644
index 0000000..145bc94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstanceFieldRead extends CfFieldInstruction implements CfOrDexInstanceFieldRead {
+
+  public CfInstanceFieldRead(DexField field) {
+    this(field, field);
+  }
+
+  public CfInstanceFieldRead(DexField field, DexField declaringField) {
+    super(Opcodes.GETFIELD, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerInstanceFieldReadInstruction(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
new file mode 100644
index 0000000..6d01f49
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstanceFieldWrite extends CfFieldInstruction {
+
+  public CfInstanceFieldWrite(DexField field) {
+    this(field, field);
+  }
+
+  public CfInstanceFieldWrite(DexField field, DexField declaringField) {
+    super(Opcodes.PUTFIELD, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerInstanceFieldWrite(getField());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 742a2be..4427f41 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -110,6 +111,11 @@
     return true;
   }
 
+  @Override
+  public Instruction asDexInstruction() {
+    return null;
+  }
+
   public CfRecordFieldValues asRecordFieldValues() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
new file mode 100644
index 0000000..66fc48e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfStaticFieldRead extends CfFieldInstruction implements CfOrDexStaticFieldRead {
+
+  public CfStaticFieldRead(DexField field) {
+    super(Opcodes.GETSTATIC, field);
+  }
+
+  public CfStaticFieldRead(DexField field, DexField declaringField) {
+    super(Opcodes.GETSTATIC, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerStaticFieldReadInstruction(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
new file mode 100644
index 0000000..2cd0f26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfStaticFieldWrite extends CfFieldInstruction {
+
+  public CfStaticFieldWrite(DexField field) {
+    super(Opcodes.PUTSTATIC, field);
+  }
+
+  public CfStaticFieldWrite(DexField field, DexField declaringField) {
+    super(Opcodes.PUTSTATIC, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerStaticFieldWrite(getField());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java
new file mode 100644
index 0000000..3d560fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.code;
+
+import com.android.tools.r8.graph.DexField;
+
+public interface CfOrDexInstanceFieldRead extends CfOrDexInstruction {
+
+  DexField getField();
+}
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
index e85c75c..e8ac111 100644
--- a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
@@ -11,4 +11,6 @@
   CfInstruction asCfInstruction();
 
   boolean isCfInstruction();
+
+  Instruction asDexInstruction();
 }
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java b/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java
new file mode 100644
index 0000000..121023a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.code;
+
+import com.android.tools.r8.graph.DexField;
+
+public interface CfOrDexStaticFieldRead extends CfOrDexInstruction {
+
+  DexField getField();
+}
diff --git a/src/main/java/com/android/tools/r8/code/Iget.java b/src/main/java/com/android/tools/r8/code/Iget.java
index 71c11c3..ee8e7a0 100644
--- a/src/main/java/com/android/tools/r8/code/Iget.java
+++ b/src/main/java/com/android/tools/r8/code/Iget.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class Iget extends IgetOrIput {
+public class Iget extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x52;
   public static final String NAME = "Iget";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetBoolean.java b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
index 36c00b6..3212878 100644
--- a/src/main/java/com/android/tools/r8/code/IgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetBoolean extends IgetOrIput {
+public class IgetBoolean extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x55;
   public static final String NAME = "IgetBoolean";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetByte.java b/src/main/java/com/android/tools/r8/code/IgetByte.java
index 8d57bb5..a876d70 100644
--- a/src/main/java/com/android/tools/r8/code/IgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/IgetByte.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetByte extends IgetOrIput {
+public class IgetByte extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x56;
   public static final String NAME = "IgetByte";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetChar.java b/src/main/java/com/android/tools/r8/code/IgetChar.java
index 2ba0e26..efa458d 100644
--- a/src/main/java/com/android/tools/r8/code/IgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/IgetChar.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetChar extends IgetOrIput {
+public class IgetChar extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x57;
   public static final String NAME = "IgetChar";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetObject.java b/src/main/java/com/android/tools/r8/code/IgetObject.java
index c685229..93540d6 100644
--- a/src/main/java/com/android/tools/r8/code/IgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/IgetObject.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetObject extends IgetOrIput {
+public class IgetObject extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x54;
   public static final String NAME = "IgetObject";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetShort.java b/src/main/java/com/android/tools/r8/code/IgetShort.java
index 77667ba..23bd792 100644
--- a/src/main/java/com/android/tools/r8/code/IgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/IgetShort.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetShort extends IgetOrIput {
+public class IgetShort extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x58;
   public static final String NAME = "IgetShort";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetWide.java b/src/main/java/com/android/tools/r8/code/IgetWide.java
index a71b95a..12ad976 100644
--- a/src/main/java/com/android/tools/r8/code/IgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/IgetWide.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetWide extends IgetOrIput {
+public class IgetWide extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x53;
   public static final String NAME = "IgetWide";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 03d7a3d..1a594e9 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -151,6 +151,11 @@
     return false;
   }
 
+  @Override
+  public Instruction asDexInstruction() {
+    return this;
+  }
+
   public CheckCast asCheckCast() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/code/Sget.java b/src/main/java/com/android/tools/r8/code/Sget.java
index c2d93df..2bd4653 100644
--- a/src/main/java/com/android/tools/r8/code/Sget.java
+++ b/src/main/java/com/android/tools/r8/code/Sget.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class Sget extends SgetOrSput {
+public class Sget extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x60;
   public static final String NAME = "Sget";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetBoolean.java b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
index 3d79934..fb51776 100644
--- a/src/main/java/com/android/tools/r8/code/SgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetBoolean extends SgetOrSput {
+public class SgetBoolean extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x63;
   public static final String NAME = "SgetBoolean";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetByte.java b/src/main/java/com/android/tools/r8/code/SgetByte.java
index 08f2b97..ee693ac 100644
--- a/src/main/java/com/android/tools/r8/code/SgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/SgetByte.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetByte extends SgetOrSput {
+public class SgetByte extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x64;
   public static final String NAME = "SgetByte";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetChar.java b/src/main/java/com/android/tools/r8/code/SgetChar.java
index 5ce7f8c..5f61f90 100644
--- a/src/main/java/com/android/tools/r8/code/SgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/SgetChar.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetChar extends SgetOrSput {
+public class SgetChar extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x65;
   public static final String NAME = "SgetChar";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetObject.java b/src/main/java/com/android/tools/r8/code/SgetObject.java
index e4ae240..8e5018e 100644
--- a/src/main/java/com/android/tools/r8/code/SgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/SgetObject.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetObject extends SgetOrSput {
+public class SgetObject extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x62;
   public static final String NAME = "SgetObject";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetShort.java b/src/main/java/com/android/tools/r8/code/SgetShort.java
index 62e2de7..99fc4ee 100644
--- a/src/main/java/com/android/tools/r8/code/SgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/SgetShort.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetShort extends SgetOrSput {
+public class SgetShort extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x66;
   public static final String NAME = "SgetShort";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetWide.java b/src/main/java/com/android/tools/r8/code/SgetWide.java
index 962d0c1..75a4343 100644
--- a/src/main/java/com/android/tools/r8/code/SgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/SgetWide.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetWide extends SgetOrSput {
+public class SgetWide extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x61;
   public static final String NAME = "SgetWide";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 205422c..0d6c31e 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
@@ -30,6 +32,10 @@
         + getClass().getCanonicalName());
   }
 
+  public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
+    return null;
+  }
+
   public abstract void registerCodeReferences(ProgramMethod method, UseRegistry registry);
 
   public abstract void registerCodeReferencesForDesugaring(
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 49d72ca..af3bcde 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
@@ -115,6 +116,11 @@
     return this;
   }
 
+  @Override
+  public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
+    return getMetadata(instruction.asDexInstruction());
+  }
+
   public BytecodeInstructionMetadata getMetadata(Instruction instruction) {
     return metadata.getMetadata(instruction);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index f111c9e..ba5faec 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -782,7 +782,7 @@
           factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
       // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
       // renaming in the backend, but it is not available here in the frontend.
-      instructions.add(new CfFieldInstruction(opcode, field, field));
+      instructions.add(CfFieldInstruction.create(opcode, field, field));
     }
 
     @Override
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 4dd921c..8ac7c59 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ListIterator;
@@ -84,6 +86,10 @@
 
   public abstract void registerInstanceFieldRead(DexField field);
 
+  public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) {
+    registerInstanceFieldRead(instruction.getField());
+  }
+
   public void registerInstanceFieldReadFromMethodHandle(DexField field) {
     registerInstanceFieldRead(field);
   }
@@ -108,6 +114,10 @@
 
   public abstract void registerStaticFieldRead(DexField field);
 
+  public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) {
+    registerStaticFieldRead(instruction.getField());
+  }
+
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
     registerStaticFieldRead(field);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 37e08d2..45bff3f 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -109,9 +110,9 @@
           CfConstNumber.class,
           CfGoto.class,
           CfConstNumber.class,
-          CfFieldInstruction.class);
+          CfStaticFieldWrite.class);
   private static List<Class<?>> r8InstructionSequence =
-      ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfFieldInstruction.class);
+      ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfStaticFieldWrite.class);
   private static List<Class<?>> jacocoInstructionSequence =
       ImmutableList.of(CfLoad.class, CfConstNumber.class, CfConstNumber.class, CfArrayStore.class);
 
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
index 8935c35..97839cf 100644
--- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
@@ -10,14 +10,47 @@
  */
 public class BytecodeInstructionMetadata {
 
+  /**
+   * Set for instance and static field read instructions which are only used to write the same
+   * field.
+   *
+   * <p>Used by {@link com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor}
+   * to skip such instructions in the "is-field-read" analysis.
+   */
+  private final boolean isReadForWrite;
+
+  BytecodeInstructionMetadata(boolean isReadForWrite) {
+    this.isReadForWrite = isReadForWrite;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
 
+  public static BytecodeInstructionMetadata none() {
+    return null;
+  }
+
+  public boolean isReadForWrite() {
+    return isReadForWrite;
+  }
+
   public static class Builder {
 
+    private boolean isReadForWrite;
+
+    private boolean isEmpty() {
+      return !isReadForWrite;
+    }
+
+    public Builder setIsReadForWrite() {
+      isReadForWrite = true;
+      return this;
+    }
+
     public BytecodeInstructionMetadata build() {
-      return new BytecodeInstructionMetadata();
+      assert !isEmpty();
+      return new BytecodeInstructionMetadata(isReadForWrite);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java
index c1923b3..ad7dee4 100644
--- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java
@@ -8,6 +8,7 @@
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * A collection of information that pertains to the instructions in a piece of {@link
@@ -20,6 +21,7 @@
   private final Map<I, BytecodeInstructionMetadata> backing;
 
   BytecodeMetadata(Map<I, BytecodeInstructionMetadata> backing) {
+    assert backing.values().stream().noneMatch(Objects::isNull);
     this.backing = backing;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
index f255c74..02ba8d4 100644
--- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
@@ -67,7 +67,7 @@
       }
       Map<Instruction, BytecodeInstructionMetadata> backing =
           new IdentityHashMap<>(builders.size());
-      builders.forEach(((instruction, builder) -> backing.put(instruction, builder.build())));
+      builders.forEach((instruction, builder) -> backing.put(instruction, builder.build()));
       return new BytecodeMetadataProvider(backing);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index 66251ba..2affeaf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -129,7 +129,7 @@
       int classIdLocalIndex = maxLocals - 1;
       instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
       instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
-      instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, group.getClassIdField()));
+      instructionBuilder.add(new CfInstanceFieldWrite(group.getClassIdField()));
       maxStack.set(2);
     } else {
       assert !hasClassId;
@@ -187,7 +187,7 @@
           int stackSizeForInitializationInfo =
               addCfInstructionsForInitializationInfo(
                   instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field));
+          instructionBuilder.add(new CfInstanceFieldWrite(field));
           maxStack.setMax(stackSizeForInitializationInfo + 1);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
index 93a8c71..3b4f8dc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
@@ -5,10 +5,17 @@
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata.Builder;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.FieldGet;
 import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.WorkList;
 
 public class FieldReadForWriteAnalysis {
 
@@ -22,6 +29,53 @@
       FieldInstruction instruction,
       ProgramField field,
       BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder) {
-    // TODO(b/149673849): Determine if field read is only used for field write.
+    if (instruction.isFieldPut()) {
+      return;
+    }
+
+    FieldGet fieldGet = instruction.asFieldGet();
+    if (isValueOnlyUsedToWriteField(fieldGet.outValue(), field)) {
+      bytecodeMetadataProviderBuilder.addMetadata(instruction, Builder::setIsReadForWrite);
+    }
+  }
+
+  private boolean isValueOnlyUsedToWriteField(Value value, ProgramField field) {
+    WorkList<Instruction> users = WorkList.newIdentityWorkList(value.uniqueUsers());
+    if (!enqueueUsersForAnalysis(value, users)) {
+      return false;
+    }
+    boolean foundWrite = false;
+    while (users.hasNext()) {
+      Instruction user = users.next();
+      if (user.isArithmeticBinop() || user.isLogicalBinop() || user.isUnop()) {
+        if (enqueueUsersForAnalysis(user.outValue(), users)) {
+          // OK.
+          continue;
+        }
+      } else if (user.isFieldPut()) {
+        FieldPut fieldPut = user.asFieldPut();
+        DexField writtenFieldReference = fieldPut.getField();
+        if (writtenFieldReference.match(field.getReference())
+            && fieldPut.isStaticPut() == field.getAccessFlags().isStatic()) {
+          ProgramField writtenField =
+              appView.appInfo().resolveField(writtenFieldReference).getProgramField();
+          if (writtenField != null && writtenField.isStructurallyEqualTo(field)) {
+            // OK.
+            foundWrite = true;
+            continue;
+          }
+        }
+      }
+      return false;
+    }
+    return foundWrite;
+  }
+
+  private boolean enqueueUsersForAnalysis(Value value, WorkList<Instruction> users) {
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return false;
+    }
+    users.addIfNotSeen(value.uniqueUsers());
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 7e6d5d3..f392dec 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
 import com.android.tools.r8.graph.AbstractAccessContexts;
 import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.AppView;
@@ -19,6 +23,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -26,7 +31,6 @@
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -95,7 +99,14 @@
 
     constantFields.forEach(this::markFieldAsDead);
     readFields.keySet().forEach(this::markFieldAsDead);
-    writtenFields.keySet().forEach(this::markFieldAsDead);
+    writtenFields.keySet().forEach(this::markWriteOnlyFieldAsDead);
+  }
+
+  private void markWriteOnlyFieldAsDead(DexEncodedField field) {
+    markFieldAsDead(field);
+    getSimpleFeedback()
+        .recordFieldHasAbstractValue(
+            field, appView, appView.abstractValueFactory().createNullValue());
   }
 
   private void markFieldAsDead(DexEncodedField field) {
@@ -104,7 +115,7 @@
     if (appView.appInfo().isPinned(field)) {
       assert field.getType().isAlwaysNull(appView);
     } else {
-      OptimizationFeedbackSimple.getInstance().markFieldAsDead(field);
+      getSimpleFeedback().markFieldAsDead(field);
     }
   }
 
@@ -294,7 +305,11 @@
       super(appView, method);
     }
 
-    private void registerFieldAccess(DexField reference, boolean isStatic, boolean isWrite) {
+    private void registerFieldAccess(
+        DexField reference,
+        boolean isStatic,
+        boolean isWrite,
+        BytecodeInstructionMetadata metadata) {
       SuccessfulFieldResolutionResult resolutionResult =
           appView.appInfo().resolveField(reference).asSuccessfulResolution();
       if (resolutionResult == null) {
@@ -311,6 +326,13 @@
         return;
       }
 
+      if (metadata != null && metadata.isReadForWrite()) {
+        // Ignore this read. If the field ends up only being written, then we will still reprocess
+        // the method with the read-for-write instruction, since the method contains a write that
+        // requires reprocessing.
+        return;
+      }
+
       // Record access.
       if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
         if (field.getAccessFlags().isStatic() == isStatic) {
@@ -372,22 +394,36 @@
 
     @Override
     public void registerInstanceFieldRead(DexField field) {
-      registerFieldAccess(field, false, false);
+      registerFieldAccess(field, false, false, BytecodeInstructionMetadata.none());
+    }
+
+    @Override
+    public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) {
+      BytecodeInstructionMetadata metadata =
+          getContext().getDefinition().getCode().getMetadata(instruction);
+      registerFieldAccess(instruction.getField(), false, false, metadata);
     }
 
     @Override
     public void registerInstanceFieldWrite(DexField field) {
-      registerFieldAccess(field, false, true);
+      registerFieldAccess(field, false, true, null);
     }
 
     @Override
     public void registerStaticFieldRead(DexField field) {
-      registerFieldAccess(field, true, false);
+      registerFieldAccess(field, true, false, BytecodeInstructionMetadata.none());
+    }
+
+    @Override
+    public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) {
+      BytecodeInstructionMetadata metadata =
+          getContext().getDefinition().getCode().getMetadata(instruction);
+      registerFieldAccess(instruction.getField(), true, false, metadata);
     }
 
     @Override
     public void registerStaticFieldWrite(DexField field) {
-      registerFieldAccess(field, true, true);
+      registerFieldAccess(field, true, true, null);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
new file mode 100644
index 0000000..3f62f1f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, 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;
+
+public interface FieldGet {
+
+  Value outValue();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 090812b..347ca58 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.code.Iget;
 import com.android.tools.r8.code.IgetBoolean;
 import com.android.tools.r8.code.IgetByte;
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
-public class InstanceGet extends FieldInstruction implements InstanceFieldInstruction {
+public class InstanceGet extends FieldInstruction implements FieldGet, InstanceFieldInstruction {
 
   public InstanceGet(Value dest, Value object, DexField field) {
     super(field, dest, object);
@@ -148,6 +148,16 @@
   }
 
   @Override
+  public boolean isFieldGet() {
+    return true;
+  }
+
+  @Override
+  public FieldGet asFieldGet() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceFieldInstruction() {
     return true;
   }
@@ -190,9 +200,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.GETFIELD, getField(), builder.resolveField(getField())));
+    builder.add(new CfInstanceFieldRead(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 3dff277..2a5d60e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.code.Iput;
 import com.android.tools.r8.code.IputBoolean;
 import com.android.tools.r8.code.IputByte;
@@ -195,6 +195,16 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
+  public FieldPut asFieldPut() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceFieldInstruction() {
     return true;
   }
@@ -226,9 +236,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.PUTFIELD, getField(), builder.resolveField(getField())));
+    builder.add(new CfInstanceFieldWrite(getField(), builder.resolveField(getField())));
   }
 
   @Override
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 090fb45..3409c01 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
@@ -956,12 +956,20 @@
     return null;
   }
 
-  public final boolean isFieldGet() {
-    return isInstanceGet() || isStaticGet();
+  public boolean isFieldGet() {
+    return false;
   }
 
-  public final boolean isFieldPut() {
-    return isInstancePut() || isStaticPut();
+  public FieldGet asFieldGet() {
+    return null;
+  }
+
+  public boolean isFieldPut() {
+    return false;
+  }
+
+  public FieldPut asFieldPut() {
+    return null;
   }
 
   public boolean isInstancePut() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b4caedd..c7e076c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.code.Sget;
 import com.android.tools.r8.code.SgetBoolean;
 import com.android.tools.r8.code.SgetByte;
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
-public class StaticGet extends FieldInstruction implements StaticFieldInstruction {
+public class StaticGet extends FieldInstruction implements FieldGet, StaticFieldInstruction {
 
   public StaticGet(Value dest, DexField field) {
     super(field, dest, (Value) null);
@@ -183,6 +183,16 @@
   }
 
   @Override
+  public boolean isFieldGet() {
+    return true;
+  }
+
+  @Override
+  public FieldGet asFieldGet() {
+    return this;
+  }
+
+  @Override
   public boolean isStaticFieldInstruction() {
     return true;
   }
@@ -204,9 +214,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.GETSTATIC, getField(), builder.resolveField(getField())));
+    builder.add(new CfStaticFieldRead(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 92eea52..8cbeb6d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.code.Sput;
 import com.android.tools.r8.code.SputBoolean;
 import com.android.tools.r8.code.SputByte;
@@ -185,6 +185,16 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
+  public FieldPut asFieldPut() {
+    return this;
+  }
+
+  @Override
   public boolean isStaticFieldInstruction() {
     return true;
   }
@@ -206,9 +216,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.PUTSTATIC, getField(), builder.resolveField(getField())));
+    builder.add(new CfStaticFieldWrite(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index fd7a8b9..b0abf2a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.graph.CfCode;
 import com.google.common.collect.ImmutableList;
 import org.objectweb.asm.Opcodes;
@@ -29,7 +29,7 @@
             new CfNew(lambda.type),
             new CfStackInstruction(Opcode.Dup),
             new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
-            new CfFieldInstruction(Opcodes.PUTSTATIC, lambda.lambdaField, lambda.lambdaField),
+            new CfStaticFieldWrite(lambda.lambdaField, lambda.lambdaField),
             new CfReturnVoid()),
         ImmutableList.of(),
         ImmutableList.of());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index 65ead77..7ccba56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
@@ -42,7 +42,7 @@
       ValueType type = ValueType.fromDexType(field.type);
       instructions.add(new CfLoad(ValueType.OBJECT, 0));
       instructions.add(new CfLoad(type, maxLocals));
-      instructions.add(new CfFieldInstruction(Opcodes.PUTFIELD, field, field));
+      instructions.add(new CfInstanceFieldWrite(field));
       maxLocals += type.requiredRegisters();
       maxStack += type.requiredRegisters();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index acfee7b..eb5a2eb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
@@ -233,7 +233,7 @@
       DexField field = lambda.getCaptureField(i);
       ValueType valueType = ValueType.fromDexType(field.type);
       instructions.add(new CfLoad(ValueType.OBJECT, 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      instructions.add(new CfInstanceFieldRead(field));
       maxStack += valueType.requiredRegisters();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 3d7ae96..3f9e078 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -7,9 +7,7 @@
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_ICCE;
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_NSME;
 import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-import static org.objectweb.asm.Opcodes.GETSTATIC;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
-import static org.objectweb.asm.Opcodes.PUTSTATIC;
 
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstClass;
@@ -17,7 +15,6 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
@@ -30,6 +27,8 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -242,7 +241,7 @@
     CfLabel tryCatchTarget = new CfLabel();
     CfLabel tryCatchEndFinally = new CfLabel();
 
-    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldRead(initializedValueField));
     instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrue));
 
     instructions.add(new CfConstClass(builder.getType()));
@@ -251,13 +250,13 @@
     instructions.add(new CfMonitor(Monitor.Type.ENTER));
     instructions.add(tryCatchStart);
 
-    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldRead(initializedValueField));
     instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrueSecond));
 
     invokeBootstrapMethod(instructions);
-    instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
+    instructions.add(new CfStaticFieldWrite(constantValueField));
     instructions.add(new CfConstNumber(1, ValueType.INT));
-    instructions.add(new CfFieldInstruction(PUTSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldWrite(initializedValueField));
 
     instructions.add(initializedTrueSecond);
     instructions.add(
@@ -287,7 +286,7 @@
 
     instructions.add(initializedTrue);
     instructions.add(new CfFrame(ImmutableInt2ReferenceSortedMap.empty(), ImmutableDeque.of()));
-    instructions.add(new CfFieldInstruction(GETSTATIC, constantValueField));
+    instructions.add(new CfStaticFieldRead(constantValueField));
     instructions.add(new CfReturn(ValueType.OBJECT));
 
     List<CfTryCatch> tryCatchRanges =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 6b76836..7226037 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -6,11 +6,11 @@
 
 
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInitClass;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -44,7 +44,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
-import org.objectweb.asm.Opcodes;
 
 public class InterfaceDesugaringSyntheticHelper {
 
@@ -493,10 +492,7 @@
                   isWide ? 2 : 1,
                   0,
                   ImmutableList.of(
-                      new CfFieldInstruction(
-                          Opcodes.GETSTATIC,
-                          clinitField.getReference(),
-                          clinitField.getReference()),
+                      new CfStaticFieldRead(clinitField.getReference(), clinitField.getReference()),
                       isWide
                           ? new CfStackInstruction(Opcode.Pop2)
                           : new CfStackInstruction(Opcode.Pop),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index 206c276..4c45086 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.lambda;
 
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -123,8 +123,7 @@
 
     if (lambdaClass.isStateless()) {
       return ImmutableList.of(
-          new CfFieldInstruction(
-              Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField));
+          new CfStaticFieldRead(lambdaClass.lambdaField, lambdaClass.lambdaField));
     }
 
     DexTypeList captureTypes = lambdaClass.descriptor.captures;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 10ea774..90d49ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
@@ -18,6 +17,8 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -267,7 +268,7 @@
         instructions.add(new CfConstNumber(i + 1, ValueType.INT));
         instructions.add(new CfArrayStore(MemberType.INT));
       }
-      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, valuesField.getReference()));
+      instructions.add(new CfStaticFieldWrite(valuesField.getReference()));
       instructions.add(new CfReturnVoid());
 
       int maxStack = 4;
@@ -317,7 +318,7 @@
               new CfNewArray(dexItemFactory.intArrayType),
               new CfStore(ValueType.OBJECT, resultLocalSlot),
               // System.arraycopy(SharedUtilityClass.$VALUES, 0, result, 0, size);
-              new CfFieldInstruction(Opcodes.GETSTATIC, valuesField.getReference()),
+              new CfStaticFieldRead(valuesField.getReference()),
               new CfConstNumber(0, ValueType.INT),
               new CfLoad(ValueType.OBJECT, resultLocalSlot),
               new CfConstNumber(0, ValueType.INT),
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 7395cbf..6777d57 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -7,10 +7,11 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -80,7 +81,7 @@
       // use vivifiedTypes.
 
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
       int index = 1;
       int stackIndex = 1;
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
@@ -250,7 +251,7 @@
     @Override
     void generatePushReceiver(List<CfInstruction> instructions) {
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
     }
 
     @Override
@@ -299,8 +300,7 @@
       instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
       instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
       instructions.add(new CfCheckCast(reverseWrapperField.holder));
-      instructions.add(
-          new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
+      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
       instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
       instructions.add(unwrapDest);
       instructions.add(new CfFrame(locals, ImmutableDeque.of()));
@@ -410,7 +410,7 @@
               false));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
-      instructions.add(new CfFieldInstruction(Opcodes.PUTFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldWrite(wrapperField));
       instructions.add(new CfReturnVoid());
       return standardCfCodeFromInstructions(instructions);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index cb5de3e..5ef065a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
@@ -20,6 +19,8 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -241,17 +242,17 @@
       //    return VALUES$com$x$MyEnum;
       List<CfInstruction> instructions = new ArrayList<>();
       CfLabel nullDest = new CfLabel();
-      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldRead(utilityField, utilityField));
       instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
       instructions.add((new CfConstNumber(numEnumInstances, ValueType.INT)));
       assert initializationMethod.getArity() == 1;
       instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, initializationMethod, false));
-      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldWrite(utilityField, utilityField));
       instructions.add(nullDest);
       instructions.add(
           new CfFrame(
               ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableDeque.of()));
-      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldRead(utilityField, utilityField));
       instructions.add(new CfReturn(ValueType.OBJECT));
       return standardCfCodeFromInstructions(instructions);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
index d6794c6..f549990 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -103,7 +103,7 @@
     // Get or set the field.
     int opcode =
         Opcodes.GETSTATIC + BooleanUtils.intValue(isSetter()) + (isInstanceField.ordinal() << 1);
-    instructions.add(new CfFieldInstruction(opcode, field, field));
+    instructions.add(CfFieldInstruction.create(opcode, field, field));
 
     // Return.
     if (isSetter()) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index 46124b9..be34a74 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -118,7 +118,7 @@
     private void loadFieldAsObject(List<CfInstruction> instructions, DexField field) {
       DexItemFactory factory = appView.dexItemFactory();
       instructions.add(new CfLoad(ValueType.OBJECT, 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      instructions.add(new CfInstanceFieldRead(field));
       if (field.type.isPrimitiveType()) {
         factory.primitiveToBoxed.forEach(
             (primitiveType, boxedType) -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
new file mode 100644
index 0000000..0c4e336
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.FieldReadForWriteTest.R.anim;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldReadForWriteTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject animClassSubject = inspector.clazz(anim.class);
+              if (parameters.isCfRuntime()) {
+                assertThat(animClassSubject, isPresent());
+                assertThat(animClassSubject.uniqueFieldWithName("abc_fade_in"), isPresent());
+              } else {
+                assertThat(animClassSubject, isAbsent());
+              }
+            });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int packageId = args.length;
+      R.onResourcesLoaded(packageId);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class R {
+
+    static boolean sResourcesDidLoad;
+
+    @NoHorizontalClassMerging
+    static class anim {
+      public static int abc_fade_in = 0x7f010039;
+    }
+
+    static void onResourcesLoaded(int packageId) {
+      if (sResourcesDidLoad) {
+        return;
+      }
+      sResourcesDidLoad = true;
+      int packageIdTransform = (packageId ^ 0x7f) << 24;
+      onResourcesLoadedAnim(packageIdTransform);
+    }
+
+    static void onResourcesLoadedAnim(int packageIdTransform) {
+      // Arithmethic binop (add, div, mul, rem, sub).
+      anim.abc_fade_in += packageIdTransform;
+      anim.abc_fade_in /= packageIdTransform;
+      anim.abc_fade_in *= packageIdTransform;
+      anim.abc_fade_in %= packageIdTransform;
+      anim.abc_fade_in -= packageIdTransform;
+      // Logical binop (and, or, shl, shr, ush, xor).
+      anim.abc_fade_in &= packageIdTransform;
+      anim.abc_fade_in |= packageIdTransform;
+      anim.abc_fade_in <<= packageIdTransform;
+      anim.abc_fade_in >>= packageIdTransform;
+      anim.abc_fade_in >>>= packageIdTransform;
+      anim.abc_fade_in ^= packageIdTransform;
+      // Unop (number conversion, but also: inc, neg, not).
+      anim.abc_fade_in = (int) ((long) anim.abc_fade_in);
+    }
+  }
+}