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);
}