Rewrite primitive fields to smaller static types

Change-Id: I8a782a2811c10c9a3c5a6c10a1b9078fcfd4381a
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a2c0535..5335ac1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -1210,6 +1210,10 @@
     return recordComponents;
   }
 
+  public void setRecordComponents(List<RecordComponentInfo> recordComponents) {
+    this.recordComponents = recordComponents;
+  }
+
   public void clearRecordComponents() {
     recordComponents.clear();
   }
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 5c29cef..216ce0e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -275,13 +275,20 @@
     if (this.getReference() == field) {
       return this;
     }
-    return builder(this)
-        .setField(field)
-        .disableAndroidApiLevelCheckIf(
-            !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()
-                || !appView.enableWholeProgramOptimizations())
-        .apply(consumer)
-        .build();
+    Builder builder =
+        builder(this)
+            .setField(field)
+            .disableAndroidApiLevelCheckIf(
+                !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()
+                    || !appView.enableWholeProgramOptimizations())
+            .apply(consumer);
+    if (isStatic()
+        && hasExplicitStaticValue()
+        && getType().isPrimitiveType()
+        && getType().isNotIdenticalTo(field.getType())) {
+      builder.setStaticValue(getStaticValue().asDexValueNumber().toType(field.getType()));
+    }
+    return builder.build();
   }
 
   @SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 3c31228..5f406ef 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -21,8 +21,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
-import com.android.tools.r8.utils.internal.BooleanUtils;
 import com.android.tools.r8.utils.EncodedValueUtils;
+import com.android.tools.r8.utils.internal.BooleanUtils;
 import com.android.tools.r8.utils.internal.LongUtils;
 import com.android.tools.r8.utils.internal.ObjectUtils;
 import com.android.tools.r8.utils.internal.exceptions.Unreachable;
@@ -503,6 +503,8 @@
     public DexValueNumber asDexValueNumber() {
       return this;
     }
+
+    public abstract DexValueNumber toType(DexType type);
   }
 
   public static class DexValueByte extends DexValueNumber {
@@ -580,6 +582,13 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType();
+      assert value == 0 || value == 1;
+      return new DexValueBoolean(value == 1);
+    }
+
+    @Override
     public int hashCode() {
       return value * 3;
     }
@@ -677,6 +686,19 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType() || type.isByteType();
+      if (type.isBooleanType()) {
+        assert value == 0 || value == 1;
+        return new DexValueBoolean(value == 1);
+      } else {
+        assert Byte.MIN_VALUE <= value;
+        assert value <= Byte.MAX_VALUE;
+        return new DexValueByte((byte) value);
+      }
+    }
+
+    @Override
     public int hashCode() {
       return value * 7;
     }
@@ -764,6 +786,19 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType() || type.isByteType();
+      if (type.isBooleanType()) {
+        assert value == 0 || value == 1;
+        return new DexValueBoolean(value == 1);
+      } else {
+        assert Byte.MIN_VALUE <= value;
+        assert value <= Byte.MAX_VALUE;
+        return new DexValueByte((byte) value);
+      }
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
       int length = dest.putUnsignedEncodedValue(value, 2);
@@ -875,6 +910,27 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType() || type.isByteType() || type.isCharType() || type.isShortType();
+      if (type.isBooleanType()) {
+        assert value == 0 || value == 1;
+        return new DexValueBoolean(value == 1);
+      } else if (type.isByteType()) {
+        assert Byte.MIN_VALUE <= value;
+        assert value <= Byte.MAX_VALUE;
+        return new DexValueByte((byte) value);
+      } else if (type.isCharType()) {
+        assert Character.MIN_VALUE <= value;
+        assert value <= Character.MAX_VALUE;
+        return new DexValueChar((char) value);
+      } else {
+        assert Short.MIN_VALUE <= value;
+        assert value <= Short.MAX_VALUE;
+        return new DexValueShort((short) value);
+      }
+    }
+
+    @Override
     public int hashCode() {
       return value * 11;
     }
@@ -920,6 +976,27 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType() || type.isByteType() || type.isCharType() || type.isShortType();
+      if (type.isBooleanType()) {
+        assert value == 0 || value == 1;
+        return new DexValueBoolean(value == 1);
+      } else if (type.isByteType()) {
+        assert Byte.MIN_VALUE <= value;
+        assert value <= Byte.MAX_VALUE;
+        return new DexValueByte((byte) value);
+      } else if (type.isCharType()) {
+        assert Character.MIN_VALUE <= value;
+        assert value <= Character.MAX_VALUE;
+        return new DexValueChar((char) value);
+      } else {
+        assert Short.MIN_VALUE <= value;
+        assert value <= Short.MAX_VALUE;
+        return new DexValueShort((short) value);
+      }
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(DexValueKind.INT, value, Integer.BYTES, dest);
     }
@@ -1062,6 +1139,35 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert type.isBooleanType()
+          || type.isByteType()
+          || type.isCharType()
+          || type.isShortType()
+          || type.isIntType();
+      if (type.isBooleanType()) {
+        assert value == 0 || value == 1;
+        return new DexValueBoolean(value == 1);
+      } else if (type.isByteType()) {
+        assert Byte.MIN_VALUE <= value;
+        assert value <= Byte.MAX_VALUE;
+        return new DexValueByte((byte) value);
+      } else if (type.isCharType()) {
+        assert Character.MIN_VALUE <= value;
+        assert value <= Character.MAX_VALUE;
+        return new DexValueChar((char) value);
+      } else if (type.isShortType()) {
+        assert Short.MIN_VALUE <= value;
+        assert value <= Short.MAX_VALUE;
+        return new DexValueShort((short) value);
+      } else {
+        assert Integer.MIN_VALUE <= value;
+        assert value <= Integer.MAX_VALUE;
+        return new DexValueInt((int) value);
+      }
+    }
+
+    @Override
     public int hashCode() {
       return (int) value * 13;
     }
@@ -1169,6 +1275,12 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert false;
+      return this;
+    }
+
+    @Override
     public int hashCode() {
       return (int) (value * 19);
     }
@@ -1272,6 +1384,12 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert false;
+      return this;
+    }
+
+    @Override
     public int hashCode() {
       return (int) (value * 29);
     }
@@ -2021,6 +2139,12 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert false;
+      return this;
+    }
+
+    @Override
     public int hashCode() {
       return 42;
     }
@@ -2122,6 +2246,12 @@
     }
 
     @Override
+    public DexValueNumber toType(DexType type) {
+      assert false;
+      return this;
+    }
+
+    @Override
     public int hashCode() {
       return value ? 1234 : 4321;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsLongNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsLongNumberValue.java
index f5a5565..ea2d198 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsLongNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsLongNumberValue.java
@@ -124,6 +124,16 @@
   @Override
   public AbstractValue rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+    if (!newType.isLongType()) {
+      assert newType.isBooleanType()
+          || newType.isByteType()
+          || newType.isCharType()
+          || newType.isShortType()
+          || newType.isIntType();
+      return appView
+          .abstractValueFactory()
+          .createDefiniteBitsNumberValue((int) definitelySetBits, (int) definitelyUnsetBits);
+    }
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9df8a6e..9321559 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -95,6 +95,8 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilled;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NumberConversion;
+import com.android.tools.r8.ir.code.NumberConversionType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
@@ -961,21 +963,32 @@
       Value newOutValue,
       Set<Phi> affectedPhis,
       AffectedValues affectedValues) {
-    TypeElement castTypeElement =
-        TypeElement.fromDexType(
-            lookup.getReadCastType(), newOutValue.getType().nullability(), appView);
-    Value castOutValue = code.createValue(castTypeElement);
-    newOutValue.replaceUsers(castOutValue, affectedValues);
-    CheckCast checkCast =
-        SafeCheckCast.builder()
-            .setCastType(lookup.getReadCastType())
-            .setObject(newOutValue)
-            .setOutValue(castOutValue)
-            .setPosition(fieldGet.asFieldInstruction())
-            .build();
-    iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
-        code, blocks, ImmutableList.of(checkCast), options);
-    affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
+    DexType readCastType = lookup.getReadCastType();
+    if (readCastType.isReferenceType()) {
+      TypeElement castTypeElement =
+          TypeElement.fromDexType(readCastType, newOutValue.getType().nullability(), appView);
+      Value castOutValue = code.createValue(castTypeElement);
+      newOutValue.replaceUsers(castOutValue, affectedValues);
+      CheckCast checkCast =
+          SafeCheckCast.builder()
+              .setCastType(readCastType)
+              .setObject(newOutValue)
+              .setOutValue(castOutValue)
+              .setPosition(fieldGet.asFieldInstruction())
+              .build();
+      iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+          code, blocks, ImmutableList.of(checkCast), options);
+      affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
+    } else if (readCastType.isLongType() && newOutValue.getType().isInt()) {
+      Value castOutValue = code.createValue(TypeElement.getLong());
+      newOutValue.replaceUsers(castOutValue, affectedValues);
+      NumberConversion conversion =
+          new NumberConversion(NumberConversionType.INT_TO_LONG, castOutValue, newOutValue);
+      conversion.setPosition(fieldGet.asFieldInstruction().getPosition());
+      iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+          code, blocks, ImmutableList.of(conversion), options);
+      affectedPhis.addAll(conversion.outValue().uniquePhiUsers());
+    }
   }
 
   private void removeUnusedArguments(IRCode code, Set<UnusedArgument> unusedArguments) {
@@ -1042,32 +1055,43 @@
       FieldPut fieldPut,
       FieldLookupResult lookup) {
     if (lookup.hasWriteCastType()) {
-      iterator.previous();
-      CheckCast checkCast =
-          SafeCheckCast.builder()
-              .setObject(fieldPut.value())
-              .setFreshOutValue(
-                  code,
-                  lookup
-                      .getWriteCastType()
-                      .toTypeElement(appView, fieldPut.value().getType().nullability()))
-              .setCastType(lookup.getWriteCastType())
-              .setPosition(fieldPut.getPosition())
-              .build();
-      iterator.add(checkCast);
-      fieldPut.setValue(checkCast.outValue());
+      DexType writeCastType = lookup.getWriteCastType();
+      if (writeCastType.isReferenceType()) {
+        iterator.previous();
+        CheckCast checkCast =
+            SafeCheckCast.builder()
+                .setObject(fieldPut.value())
+                .setFreshOutValue(
+                    code,
+                    writeCastType.toTypeElement(appView, fieldPut.value().getType().nullability()))
+                .setCastType(writeCastType)
+                .setPosition(fieldPut.getPosition())
+                .build();
+        iterator.add(checkCast);
+        fieldPut.setValue(checkCast.outValue());
 
-      if (checkCast.getBlock().hasCatchHandlers()) {
-        // Split the block and reset the block iterator.
-        BasicBlock splitBlock = iterator.splitCopyCatchHandlers(code, blocks, appView.options());
-        BasicBlock previousBlock = blocks.previousUntil(block -> block == splitBlock);
-        assert previousBlock == splitBlock;
-        blocks.next();
-        iterator = splitBlock.listIterator();
+        if (checkCast.getBlock().hasCatchHandlers()) {
+          // Split the block and reset the block iterator.
+          BasicBlock splitBlock = iterator.splitCopyCatchHandlers(code, blocks, appView.options());
+          BasicBlock previousBlock = blocks.previousUntil(block -> block == splitBlock);
+          assert previousBlock == splitBlock;
+          blocks.next();
+          iterator = splitBlock.listIterator();
+        }
+
+        Instruction next = iterator.next();
+        assert next == fieldPut;
+      } else if (fieldPut.value().getType().isLong()) {
+        iterator.previous();
+        Value castOutValue = code.createValue(TypeElement.getInt());
+        NumberConversion conversion =
+            new NumberConversion(NumberConversionType.LONG_TO_INT, castOutValue, fieldPut.value());
+        conversion.setPosition(fieldPut.getPosition());
+        iterator.add(conversion);
+        fieldPut.setValue(castOutValue);
+        Instruction next = iterator.next();
+        assert next == fieldPut;
       }
-
-      Instruction next = iterator.next();
-      assert next == fieldPut;
     }
     return iterator;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 193ef40..b6351e2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -160,6 +160,7 @@
           .run(executorService, feedback, timing);
     }
 
+    enumUnboxer.rewriteWithLens();
     numberUnboxer.rewriteWithLens();
     outliner.rewriteWithLens();
     enumUnboxer.unboxEnums(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index 9aa0422..909f990 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -34,6 +34,11 @@
   }
 
   @Override
+  public void rewriteWithLens() {
+    // Intentionally empty.
+  }
+
+  @Override
   public void analyzeEnums(IRCode code, MethodProcessor methodProcessor) {
     // Intentionally empty.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c16b4f5..d5f40cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -33,6 +33,8 @@
   public abstract void prepareForPrimaryOptimizationPass(
       GraphLens graphLensForPrimaryOptimizationPass);
 
+  public abstract void rewriteWithLens();
+
   public abstract void analyzeEnums(IRCode code, MethodProcessor methodProcessor);
 
   public abstract void onMethodPruned(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 8025d74..0fb3a65 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -648,6 +648,11 @@
     initializeEnumUnboxingCandidates(graphLensForPrimaryOptimizationPass);
   }
 
+  @Override
+  public void rewriteWithLens() {
+    enumUnboxingCandidatesInfo.rewriteWithLens(appView.graphLens(), appView.codeLens());
+  }
+
   private void initializeCheckNotNullMethods(GraphLens graphLensForPrimaryOptimizationPass) {
     assert checkNotNullMethodsBuilder == null;
     checkNotNullMethodsBuilder =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index 2385af6..f4f2d3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.internal.SetUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -149,6 +150,12 @@
     enumTypeToInfo.clear();
   }
 
+  public void rewriteWithLens(GraphLens graphLens, GraphLens appliedLens) {
+    for (EnumUnboxingCandidateInfo info : enumTypeToInfo.values()) {
+      info.rewriteWithLens(graphLens, appliedLens);
+    }
+  }
+
   public boolean verifyAllSubtypesAreSet() {
     for (EnumUnboxingCandidateInfo value : enumTypeToInfo.values()) {
       assert value.subclasses != null;
@@ -203,5 +210,24 @@
     public Set<DexField> getRequiredInstanceFieldData() {
       return requiredInstanceFieldData;
     }
+
+    public void rewriteWithLens(GraphLens graphLens, GraphLens appliedLens) {
+      Set<DexField> pendingAdditions = null;
+      Iterator<DexField> iterator = requiredInstanceFieldData.iterator();
+      while (iterator.hasNext()) {
+        DexField field = iterator.next();
+        DexField rewrittenField = graphLens.getRenamedFieldSignature(field, appliedLens);
+        if (rewrittenField.isNotIdenticalTo(field)) {
+          if (pendingAdditions == null) {
+            pendingAdditions = Sets.newIdentityHashSet();
+          }
+          pendingAdditions.add(rewrittenField);
+          iterator.remove();
+        }
+      }
+      if (pendingAdditions != null) {
+        requiredInstanceFieldData.addAll(pendingAdditions);
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 9d5112b..2acb7f8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -187,7 +187,7 @@
           CheckCast checkCast = instruction.asCheckCast();
           DexType enumType = getEnumClassTypeOrNull(checkCast.getType());
           if (enumType != null) {
-            checkCast.outValue().replaceUsers(checkCast.object());
+            checkCast.outValue().replaceUsers(checkCast.object(), affectedValues);
             iterator.removeOrReplaceByDebugLocalRead();
           }
         } else if (instruction.isInitClass()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index 2eaff538..d0828a6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -76,6 +76,7 @@
   private void fixupClass(DexProgramClass clazz) {
     fixupFields(clazz);
     fixupMethods(clazz);
+    fixupRecordComponents(clazz);
   }
 
   private void fixupFields(DexProgramClass clazz) {
@@ -164,6 +165,10 @@
             });
   }
 
+  private void fixupRecordComponents(DexProgramClass clazz) {
+    clazz.setRecordComponents(fixupRecordComponents(clazz.getRecordComponents()));
+  }
+
   @Override
   public DexField fixupFieldReference(DexField field) {
     return graphLens.getNextFieldSignature(field);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index cf69494..381646c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.FieldLookupResult;
 import com.android.tools.r8.graph.lens.NestedGraphLens;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
@@ -60,10 +61,14 @@
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
     FieldLookupResult lookupResult = super.internalDescribeLookupField(previous);
     if (lookupResult.getReference().getType().isNotIdenticalTo(previous.getReference().getType())) {
+      DexType readCastType = previous.getReadCastType();
+      if (readCastType == null && previous.getReference().getType().isPrimitiveType()) {
+        readCastType = previous.getReference().getType();
+      }
       return FieldLookupResult.builder(this)
           .setReboundReference(lookupResult.getReboundReference())
           .setReference(lookupResult.getReference())
-          .setReadCastType(lookupResult.getReadCastType())
+          .setReadCastType(readCastType)
           .setWriteCastType(lookupResult.getReference().getType())
           .build();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 03bef1b..1fa3e30 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -30,6 +30,8 @@
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
 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.DefiniteBitsLongNumberValue;
+import com.android.tools.r8.ir.analysis.value.DefiniteBitsNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.conversion.ExtraUnusedIntParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
@@ -605,7 +607,7 @@
       Set<DexField> newFieldSignatures = Sets.newIdentityHashSet();
       Map<DexField, DexType> newFieldTypes = new IdentityHashMap<>();
       clazz.forEachProgramFieldMatching(
-          field -> field.getType().isClassType(),
+          field -> field.getType().isClassType() || field.getType().isPrimitiveType(),
           field -> {
             DexType newFieldType = getNewFieldType(field);
             if (newFieldType != field.getType()) {
@@ -616,7 +618,7 @@
             }
           });
       clazz.forEachProgramFieldMatching(
-          field -> field.getType().isClassType(),
+          field -> field.getType().isClassType() || field.getType().isPrimitiveType(),
           field -> {
             DexField newFieldSignature =
                 getNewFieldSignature(field, newFieldSignatures, newFieldTypes);
@@ -674,23 +676,36 @@
 
     @SuppressWarnings("ReferenceEquality")
     private DexType getNewFieldType(ProgramField field) {
-      DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
       DexType staticType = field.getType();
-      if (dynamicType.isUnknown()) {
+      KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+      if (!keepInfo.isFieldTypeStrengtheningAllowed(options)) {
         return staticType;
       }
 
-      KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+      if (staticType.isPrimitiveType()) {
+        AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
+        if (abstractValue.isDefiniteBitsNumberValue()) {
+          DefiniteBitsNumberValue value = abstractValue.asDefiniteBitsNumberValue();
+          return getSmallerPrimitiveType(
+              staticType, value.getMinInclusiveInt(), value.getMaxInclusiveInt());
+        } else if (abstractValue.isDefiniteBitsLongNumberValue()) {
+          DefiniteBitsLongNumberValue value = abstractValue.asDefiniteBitsLongNumberValue();
+          return getSmallerPrimitiveType(
+              staticType, value.getMinInclusiveLong(), value.getMaxInclusiveLong());
+        }
+        return staticType;
+      }
+
+      DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
+      if (dynamicType.isUnknown()) {
+        return staticType;
+      }
 
       // We don't have dynamic type information for fields that are kept, unless the static type of
       // the field is guaranteed to be null.
       assert !keepInfo.isPinned(options)
           || (field.getType().isAlwaysNull(appView) && dynamicType.isNullType());
 
-      if (!keepInfo.isFieldTypeStrengtheningAllowed(options)) {
-        return staticType;
-      }
-
       if (dynamicType.isNullType()) {
         // Don't optimize always null fields; these will be optimized anyway.
         return staticType;
@@ -742,6 +757,37 @@
       return newStaticFieldType;
     }
 
+    private DexType getSmallerPrimitiveType(
+        DexType staticType, long minInclusive, long maxInclusive) {
+      // Don't change fields typed as boolean, double, or float.
+      if (staticType.isBooleanType() || staticType.isDoubleType() || staticType.isFloatType()) {
+        return staticType;
+      }
+      // Due to stricter verification on Dalvik we only allow long -> int in that case.
+      if (!options.canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()) {
+        if (0 <= minInclusive && maxInclusive <= 1) {
+          return dexItemFactory.booleanType;
+        }
+        if (Byte.MIN_VALUE <= minInclusive && maxInclusive <= Byte.MAX_VALUE) {
+          return dexItemFactory.byteType;
+        }
+        if (Short.MIN_VALUE <= minInclusive
+            && maxInclusive <= Short.MAX_VALUE
+            && !staticType.isCharType()) {
+          return dexItemFactory.shortType;
+        }
+        if (Character.MIN_VALUE <= minInclusive && maxInclusive <= Character.MAX_VALUE) {
+          return dexItemFactory.charType;
+        }
+      }
+      if (Integer.MIN_VALUE <= minInclusive
+          && maxInclusive <= Integer.MAX_VALUE
+          && staticType.isLongType()) {
+        return dexItemFactory.intType;
+      }
+      return staticType;
+    }
+
     @SuppressWarnings("ReferenceEquality")
     private DexField getNewFieldSignature(
         ProgramField field,
diff --git a/src/test/java8/ir/com/android/tools/r8/optimize/argumentpropagation/PrimitiveFieldTypeStrengtheningArrayTest.java b/src/test/java8/ir/com/android/tools/r8/optimize/argumentpropagation/PrimitiveFieldTypeStrengtheningArrayTest.java
new file mode 100644
index 0000000..f0bd455
--- /dev/null
+++ b/src/test/java8/ir/com/android/tools/r8/optimize/argumentpropagation/PrimitiveFieldTypeStrengtheningArrayTest.java
@@ -0,0 +1,223 @@
+// Copyright (c) 2026, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.Arrays;
+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 PrimitiveFieldTypeStrengtheningArrayTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters)
+        .addInnerClasses(getClass())
+        .release()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "[1]",
+            "[\u0001, \u007F]",
+            "[1, 127]",
+            "[1, 127, 32767]",
+            "[1, 127, 32767, 2147483647]");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    boolean optimize = !parameters.canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug();
+    testForR8(parameters)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClass = inspector.clazz(Main.class);
+              assertThat(mainClass, isPresent());
+
+              // Byte field.
+              FieldSubject byteToBooleanField =
+                  mainClass.uniqueFieldWithOriginalName("byteToBooleanField");
+              assertThat(byteToBooleanField, isPresent());
+              assertEquals(
+                  optimize ? "boolean" : "byte",
+                  byteToBooleanField.getField().getType().getTypeName());
+
+              // Char fields.
+              FieldSubject charToBooleanField =
+                  mainClass.uniqueFieldWithOriginalName("charToBooleanField");
+              assertThat(charToBooleanField, isPresent());
+              assertEquals(
+                  optimize ? "boolean" : "char",
+                  charToBooleanField.getField().getType().getTypeName());
+
+              FieldSubject charToByteField =
+                  mainClass.uniqueFieldWithOriginalName("charToByteField");
+              assertThat(charToByteField, isPresent());
+              assertEquals(
+                  optimize ? "byte" : "char", charToByteField.getField().getType().getTypeName());
+
+              // Short fields.
+              FieldSubject shortToBooleanField =
+                  mainClass.uniqueFieldWithOriginalName("shortToBooleanField");
+              assertThat(shortToBooleanField, isPresent());
+              assertEquals(
+                  optimize ? "boolean" : "short",
+                  shortToBooleanField.getField().getType().getTypeName());
+
+              FieldSubject shortToByteField =
+                  mainClass.uniqueFieldWithOriginalName("shortToByteField");
+              assertThat(shortToByteField, isPresent());
+              assertEquals(
+                  optimize ? "byte" : "short", shortToByteField.getField().getType().getTypeName());
+
+              // Int fields.
+              FieldSubject intToBooleanField =
+                  mainClass.uniqueFieldWithOriginalName("intToBooleanField");
+              assertThat(intToBooleanField, isPresent());
+              assertEquals(
+                  optimize ? "boolean" : "int",
+                  intToBooleanField.getField().getType().getTypeName());
+
+              FieldSubject intToByteField = mainClass.uniqueFieldWithOriginalName("intToByteField");
+              assertThat(intToByteField, isPresent());
+              assertEquals(
+                  optimize ? "byte" : "int", intToByteField.getField().getType().getTypeName());
+
+              FieldSubject intToShortField =
+                  mainClass.uniqueFieldWithOriginalName("intToShortField");
+              assertThat(intToShortField, isPresent());
+              assertEquals(
+                  optimize ? "short" : "int", intToShortField.getField().getType().getTypeName());
+
+              // Long fields.
+              FieldSubject longToBooleanField =
+                  mainClass.uniqueFieldWithOriginalName("longToBooleanField");
+              assertThat(longToBooleanField, isPresent());
+              assertEquals(
+                  optimize ? "boolean" : "int",
+                  longToBooleanField.getField().getType().getTypeName());
+
+              FieldSubject longToByteField =
+                  mainClass.uniqueFieldWithOriginalName("longToByteField");
+              assertThat(longToByteField, isPresent());
+              assertEquals(
+                  optimize ? "byte" : "int", longToByteField.getField().getType().getTypeName());
+
+              FieldSubject longToShortField =
+                  mainClass.uniqueFieldWithOriginalName("longToShortField");
+              assertThat(longToShortField, isPresent());
+              assertEquals(
+                  optimize ? "short" : "int", longToShortField.getField().getType().getTypeName());
+
+              FieldSubject longToIntField = mainClass.uniqueFieldWithOriginalName("longToIntField");
+              assertThat(longToIntField, isPresent());
+              assertEquals("int", longToIntField.getField().getType().getTypeName());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "[1]",
+            "[\u0001, \u007F]",
+            "[1, 127]",
+            "[1, 127, 32767]",
+            "[1, 127, 32767, 2147483647]");
+  }
+
+  static class Main {
+
+    // Byte field.
+    static byte byteToBooleanField;
+
+    // Char fields.
+    static char charToBooleanField;
+    static char charToByteField;
+
+    // Short fields.
+    static short shortToBooleanField;
+    static short shortToByteField;
+
+    // Int fields.
+    static int intToBooleanField;
+    static int intToByteField;
+    static int intToShortField;
+
+    // Long fields.
+    static long longToBooleanField;
+    static long longToByteField;
+    static long longToIntField;
+    static long longToShortField;
+
+    public static void main(String[] args) {
+      // Byte field.
+      byteToBooleanField = 1;
+
+      // Char fields.
+      charToBooleanField = 1;
+      charToByteField = Byte.MAX_VALUE;
+
+      // Short fields.
+      shortToBooleanField = 1;
+      shortToByteField = Byte.MAX_VALUE;
+
+      // Int fields.
+      intToBooleanField = 1;
+      intToByteField = Byte.MAX_VALUE;
+      intToShortField = Short.MAX_VALUE;
+
+      // Long fields.
+      longToBooleanField = 1;
+      longToByteField = Byte.MAX_VALUE;
+      longToShortField = Short.MAX_VALUE;
+      longToIntField = Integer.MAX_VALUE;
+
+      // Use fields.
+      readFieldsIntoArrays();
+    }
+
+    @NeverInline
+    static void readFieldsIntoArrays() {
+      // Byte fields.
+      System.out.println(Arrays.toString(new byte[] {byteToBooleanField}));
+
+      // Char fields.
+      System.out.println(Arrays.toString(new char[] {charToBooleanField, charToByteField}));
+
+      // Short fields.
+      System.out.println(Arrays.toString(new short[] {shortToBooleanField, shortToByteField}));
+
+      // Int fields.
+      System.out.println(
+          Arrays.toString(new int[] {intToBooleanField, intToByteField, intToShortField}));
+
+      // Long fields.
+      System.out.println(
+          Arrays.toString(
+              new long[] {longToBooleanField, longToByteField, longToShortField, longToIntField}));
+    }
+  }
+}
diff --git a/src/test/java8/shaking/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java8/shaking/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index a646cc9..8a999db 100644
--- a/src/test/java8/shaking/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java8/shaking/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -172,7 +172,7 @@
     assertThat(clazz, isPresentAndRenamed());
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     assertThat(m, isPresentAndRenamed());
-    f = clazz.field("int", "intField");
+    f = clazz.uniqueFieldWithOriginalName("intField");
     assertThat(f, isPresentAndRenamed());
   }
 
@@ -288,7 +288,7 @@
     assertThat(clazz, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed());
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     assertThat(m, isPresentAndRenamed());
-    FieldSubject f = clazz.field("int", "intField");
+    FieldSubject f = clazz.uniqueFieldWithOriginalName("intField");
     assertThat(f, isPresentAndRenamed());
   }
 
@@ -317,7 +317,7 @@
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     // Method name is not renamed either, if triggered.
     assertThat(m, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed());
-    FieldSubject f = clazz.field("int", "intField");
+    FieldSubject f = clazz.uniqueFieldWithOriginalName("intField");
     assertThat(f, isPresentAndRenamed());
   }
 
@@ -345,7 +345,7 @@
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     // Only method name is not renamed, if triggered.
     assertThat(m, keepPrecondition ? isPresentAndNotRenamed() : isPresentAndRenamed());
-    FieldSubject f = clazz.field("int", "intField");
+    FieldSubject f = clazz.uniqueFieldWithOriginalName("intField");
     assertThat(f, isPresentAndRenamed());
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestParameters.java b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
index ab2e7aa..083dcc8 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
@@ -56,6 +56,10 @@
     return builder().withNoneRuntime().build();
   }
 
+  public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() {
+    return isCfRuntime() || getApiLevel().isLessThan(AndroidApiLevel.L);
+  }
+
   public boolean canHaveIssueWithInlinedMonitors() {
     return isCfRuntime() || getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M);
   }