Add ResourceConstNumber for tracking resource references
This will add a special resource value to the IR for tracking resource
value references no matter if they are in an R class or not.
This means that we can more freely optimize callsites and remove the
fields from the R class.
Note that this is exclusively used in the optimization pipeline, we
map out again before writing.
Also note, that the ResourceAccessAnalysis tracing of the resources is
currently a no-op since it does not give rise to any code tracing (and
the actual tracing for the output resource table is done in the second
round use the ResourceConstNumbers)
Bug: 287398085
Change-Id: Ic9d648bb4bd086548b4500fb6a148e7519ba1f28
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ed2b2cd..f9694e5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -928,7 +928,9 @@
LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build();
ShrinkerResult shrinkerResult;
if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
- shrinkerResult = shrinker.shrinkModel(appView.getResourceAnalysisResult().getModel(), true);
+ shrinkerResult =
+ shrinker.shrinkModel(
+ appView.getResourceShrinkerState().getR8ResourceShrinkerModel(), true);
} else {
shrinkerResult = shrinker.run();
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 84d8da6..e3c04a8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.classmerging.ClassMergerMode;
@@ -13,7 +14,6 @@
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
-import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis.ResourceAnalysisResult;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.graph.lens.AppliedGraphLens;
import com.android.tools.r8.graph.lens.ClearCodeRewritingGraphLens;
@@ -154,7 +154,7 @@
private SeedMapper applyMappingSeedMapper;
- private ResourceAnalysisResult resourceAnalysisResult = null;
+ private R8ResourceShrinkerState resourceShrinkerState = null;
// When input has been (partially) desugared these are the classes which has been library
// desugared. This information is populated in the IR converter.
@@ -909,12 +909,12 @@
testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
}
- public void setResourceAnalysisResult(ResourceAnalysisResult resourceAnalysisResult) {
- this.resourceAnalysisResult = resourceAnalysisResult;
+ public R8ResourceShrinkerState getResourceShrinkerState() {
+ return resourceShrinkerState;
}
- public ResourceAnalysisResult getResourceAnalysisResult() {
- return resourceAnalysisResult;
+ public void setResourceShrinkerState(R8ResourceShrinkerState resourceShrinkerState) {
+ this.resourceShrinkerState = resourceShrinkerState;
}
public boolean validateUnboxedEnumsHaveBeenPruned() {
@@ -1009,9 +1009,6 @@
setProguardCompatibilityActions(
getProguardCompatibilityActions().withoutPrunedItems(prunedItems, timing));
}
- if (resourceAnalysisResult != null) {
- resourceAnalysisResult.withoutPrunedItems(prunedItems, timing);
- }
if (hasRootSet()) {
rootSet.pruneItems(prunedItems, timing);
}
@@ -1229,18 +1226,6 @@
new ThreadTask() {
@Override
public void run(Timing threadTiming) {
- appView.resourceAnalysisResult.rewrittenWithLens(
- lens, appliedLensInModifiedLens, threadTiming);
- }
-
- @Override
- public boolean shouldRun() {
- return appView.resourceAnalysisResult != null;
- }
- },
- new ThreadTask() {
- @Override
- public void run(Timing threadTiming) {
appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, threadTiming));
}
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 1d9e498..d7d1b0b 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -158,6 +158,8 @@
registerTypeReference(type);
}
+ public void registerConstResourceNumber(int value) {}
+
public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index d9547d9..6b64f53 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -5,10 +5,10 @@
package com.android.tools.r8.graph.analysis;
import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
-import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel;
import com.android.tools.r8.AndroidResourceInput;
import com.android.tools.r8.AndroidResourceInput.Kind;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
@@ -19,8 +19,6 @@
import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -29,14 +27,10 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.Timing;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
@@ -66,21 +60,23 @@
public static void register(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
- if (enabled(appView)) {
+ if (enabled(appView, enqueuer)) {
enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
}
}
@Override
public void done(Enqueuer enqueuer) {
- appView.setResourceAnalysisResult(
- new ResourceAnalysisResult(resourceShrinkerState, fieldToValueMapping));
EnqueuerFieldAccessAnalysis.super.done(enqueuer);
}
- private static boolean enabled(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private static boolean enabled(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
return appView.options().androidResourceProvider != null
- && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking();
+ && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking()
+ // Only run this in the first round, we explicitly trace the resource values
+ // with ResourceConstNumber in the optimizing pipeline.
+ && enqueuer.getMode().isInitialTreeShaking();
}
@Override
@@ -101,8 +97,6 @@
assert fieldToValueMapping.containsKey(holderType);
RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType);
IntList integers = rClassFieldToValueStore.valueMapping.get(field);
- enqueuer.applyMinimumKeepInfoWhenLive(
- resolvedField, KeepFieldInfo.newEmptyJoiner().disallowOptimization());
for (Integer integer : integers) {
resourceShrinkerState.trace(integer);
}
@@ -140,6 +134,8 @@
if (definition.isConstNumber()) {
values = new IntArrayList(1);
values.add(definition.asConstNumber().getIntValue());
+ } else if (definition.isResourceConstNumber()) {
+ throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
} else if (definition.isNewArrayEmpty()) {
NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
values = new IntArrayList();
@@ -148,6 +144,8 @@
Value constValue = uniqueUser.asArrayPut().value();
if (constValue.isConstNumber()) {
values.add(constValue.getDefinition().asConstNumber().getIntValue());
+ } else if (constValue.isConstResourceNumber()) {
+ throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
}
} else {
assert uniqueUser == staticPut;
@@ -162,6 +160,8 @@
Instruction valueDefinition = inValue.definition;
if (valueDefinition.isConstNumber()) {
values.add(valueDefinition.asConstNumber().getIntValue());
+ } else if (valueDefinition.isResourceConstNumber()) {
+ throw new Unreachable("Only running ResourceAccessAnalysis in initial tree shaking");
}
}
} else {
@@ -192,79 +192,11 @@
if (result != null) {
return result;
}
- String simpleClassName =
- DescriptorUtils.getSimpleClassNameFromDescriptor(holder.getType().toDescriptorString());
- List<String> split = StringUtils.split(simpleClassName, '$');
-
- if (split.size() < 2) {
- cachedClassLookups.put(holder, false);
- return false;
- }
- String type = split.get(split.size() - 1);
- String rClass = split.get(split.size() - 2);
- // We match on R if:
- // - The name of the Class is R$type - we allow R to be an inner class.
- // - The inner type should be with lower case
- boolean isRClass = Character.isLowerCase(type.charAt(0)) && rClass.equals("R");
+ boolean isRClass = DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
cachedClassLookups.put(holder, isRClass);
return isRClass;
}
- public static class ResourceAnalysisResult {
-
- private final R8ResourceShrinkerState resourceShrinkerState;
- private Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap;
-
- private ResourceAnalysisResult(
- R8ResourceShrinkerState resourceShrinkerState,
- Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap) {
- this.resourceShrinkerState = resourceShrinkerState;
- this.rClassFieldToValueStoreMap = rClassFieldToValueStoreMap;
- }
-
- public R8ResourceShrinkerModel getModel() {
- return resourceShrinkerState.getR8ResourceShrinkerModel();
- }
-
- public void rewrittenWithLens(GraphLens lens, GraphLens appliedLens, Timing timing) {
- Map<DexType, DexType> changed = new IdentityHashMap<>();
- for (DexType dexType : rClassFieldToValueStoreMap.keySet()) {
- DexType rewritten = lens.lookupClassType(dexType, appliedLens);
- if (rewritten.isNotIdenticalTo(dexType)) {
- changed.put(dexType, rewritten);
- }
- }
- if (!changed.isEmpty()) {
- Map<DexType, RClassFieldToValueStore> rewrittenMap = new IdentityHashMap<>();
- rClassFieldToValueStoreMap.forEach(
- (type, map) -> {
- rewrittenMap.put(changed.getOrDefault(type, type), map);
- map.rewrittenWithLens(lens);
- });
- rClassFieldToValueStoreMap = rewrittenMap;
- }
- }
-
- public void withoutPrunedItems(PrunedItems prunedItems, Timing timing) {
- rClassFieldToValueStoreMap.keySet().removeIf(prunedItems::isRemoved);
- rClassFieldToValueStoreMap.values().forEach(store -> store.pruneItems(prunedItems));
- }
-
- public String getSingleStringValueForField(ProgramField programField) {
- RClassFieldToValueStore rClassFieldToValueStore =
- rClassFieldToValueStoreMap.get(programField.getHolderType());
- if (rClassFieldToValueStore == null) {
- return null;
- }
- if (!rClassFieldToValueStore.hasField(programField.getReference())) {
- return null;
- }
- return getModel()
- .getStringResourcesWithSingleValue()
- .get(rClassFieldToValueStore.getResourceId(programField.getReference()));
- }
- }
-
private static class RClassFieldToValueStore {
private Map<DexField, IntList> valueMapping;
@@ -272,40 +204,6 @@
this.valueMapping = valueMapping;
}
- boolean hasField(DexField field) {
- return valueMapping.containsKey(field);
- }
-
- void pruneItems(PrunedItems prunedItems) {
- valueMapping.keySet().removeIf(prunedItems::isRemoved);
- }
-
- int getResourceId(DexField field) {
- IntList integers = valueMapping.get(field);
- assert integers.size() == 1;
- return integers.get(0);
- }
-
- @SuppressWarnings("ReferenceEquality")
- public void rewrittenWithLens(GraphLens lens) {
- Map<DexField, DexField> changed = new IdentityHashMap<>();
- valueMapping
- .keySet()
- .forEach(
- dexField -> {
- DexField rewritten = lens.lookupField(dexField);
- if (rewritten != dexField) {
- changed.put(dexField, rewritten);
- }
- });
- if (changed.size() > 0) {
- Map<DexField, IntList> rewrittenMapping = new IdentityHashMap<>();
- valueMapping.forEach(
- (key, value) -> rewrittenMapping.put(changed.getOrDefault(key, key), value));
- valueMapping = rewrittenMapping;
- }
- }
-
public static class Builder {
private final Map<DexField, IntList> valueMapping = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c71f037..e050ed4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -205,6 +205,14 @@
return null;
}
+ public boolean isSingleResourceNumberValue() {
+ return false;
+ }
+
+ public SingleResourceNumberValue asSingleResourceNumberValue() {
+ return null;
+ }
+
public boolean isSingleStringValue() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 79eca14..567d39a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -26,6 +26,8 @@
new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues =
new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<Integer, SingleResourceNumberValue> singleResourceNumberValues =
+ new ConcurrentHashMap<>();
private final ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
@@ -122,6 +124,10 @@
return createUncheckedSingleNumberValue(value);
}
+ public SingleResourceNumberValue createSingleResourceNumberValue(int value) {
+ return singleResourceNumberValues.computeIfAbsent(value, SingleResourceNumberValue::new);
+ }
+
public SingleNumberValue createUncheckedSingleNumberValue(long value) {
return singleNumberValues.computeIfAbsent(value, SingleNumberValue::new);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
new file mode 100644
index 0000000..8be7c10
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.code.ValueFactory;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class SingleResourceNumberValue extends SingleConstValue {
+
+ private final int value;
+
+ /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+ SingleResourceNumberValue(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean hasSingleMaterializingInstruction() {
+ return true;
+ }
+
+ @Override
+ public boolean isSingleResourceNumberValue() {
+ return true;
+ }
+
+ @Override
+ public SingleResourceNumberValue asSingleResourceNumberValue() {
+ return this;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return "SingleResourceNumberValue(" + value + ")";
+ }
+
+ @Override
+ public Instruction[] createMaterializingInstructions(
+ AppView<?> appView,
+ ProgramMethod context,
+ ValueFactory valueFactory,
+ MaterializingInstructionsInfo info) {
+ ResourceConstNumber materializingInstruction =
+ createMaterializingInstruction(appView, valueFactory, info);
+ return new Instruction[] {materializingInstruction};
+ }
+
+ public ResourceConstNumber createMaterializingInstruction(
+ AppView<?> appView, ValueFactory valueFactory, MaterializingInstructionsInfo info) {
+ return createMaterializingInstruction(
+ appView, valueFactory, info.getOutType(), info.getLocalInfo(), info.getPosition());
+ }
+
+ public ResourceConstNumber createMaterializingInstruction(
+ AppView<?> appView,
+ ValueFactory valueFactory,
+ TypeElement type,
+ DebugLocalInfo localInfo,
+ Position position) {
+ assert type.isInt();
+ return ResourceConstNumber.builder()
+ .setFreshOutValue(valueFactory, type, localInfo)
+ .setPositionForNonThrowingInstruction(position, appView.options())
+ .setValue(value)
+ .build();
+ }
+
+ @Override
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
+ return true;
+ }
+
+ @Override
+ public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
+ return true;
+ }
+
+ @Override
+ public InstanceFieldInitializationInfo fixupAfterParametersChanged(
+ ArgumentInfoCollection argumentInfoCollection) {
+ return this;
+ }
+
+ @Override
+ public SingleValue rewrittenWithLens(
+ AppView<AppInfoWithLiveness> appView, DexType newType, GraphLens lens, GraphLens codeLens) {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index a21882a..a3bbdee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -95,6 +95,11 @@
}
@Override
+ public T visit(ResourceConstNumber instruction) {
+ return null;
+ }
+
+ @Override
public T visit(ConstString instruction) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 2d8cc3e..56f38c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -103,6 +103,10 @@
return get(Opcodes.CONST_NUMBER);
}
+ public boolean mayHaveResourceConstNumber() {
+ return get(Opcodes.RESOURCE_CONST_NUMBER);
+ }
+
public boolean mayHaveConstString() {
return get(Opcodes.CONST_STRING);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d0426ec..08143ae 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
@@ -869,6 +869,14 @@
return null;
}
+ public boolean isResourceConstNumber() {
+ return false;
+ }
+
+ public ResourceConstNumber asResourceConstNumber() {
+ return null;
+ }
+
public boolean isConstInstruction() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index 8318365..62bd3de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -38,6 +38,8 @@
T visit(ConstNumber instruction);
+ T visit(ResourceConstNumber instruction);
+
T visit(ConstString instruction);
T visit(DebugLocalRead instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 44ad346..58889ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -77,4 +77,5 @@
int XOR = 68;
int UNINITIALIZED_THIS_LOCAL_READ = 69;
int RECORD_FIELD_VALUES = 70;
+ int RESOURCE_CONST_NUMBER = 71;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
new file mode 100644
index 0000000..d18a410
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
+
+/**
+ * Instruction representing the SSA value an R class field value.
+ *
+ * <p>This instruction allows us to correctly trace inlined resource values in the second round of
+ * tree shaking.
+ *
+ * <p>The instruction is simple converted back to a const number before writing.
+ */
+public class ResourceConstNumber extends ConstInstruction {
+
+ private final int value;
+
+ public ResourceConstNumber(Value dest, int value) {
+ super(dest);
+ assert dest.type.isPrimitiveType();
+ this.value = value;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public int opcode() {
+ return Opcodes.RESOURCE_CONST_NUMBER;
+ }
+
+ @Override
+ public <T> T accept(InstructionVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ public boolean instructionTypeCanBeCanonicalized() {
+ return true;
+ }
+
+ @Override
+ public void buildDex(DexBuilder builder) {
+ throw new Unreachable("We never write out ResourceConstNumber");
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ throw new Unreachable("We never write cf code with resource numbers");
+ }
+
+ public Value dest() {
+ return outValue;
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ throw new Unreachable("We never write out a resource const number");
+ }
+
+ @Override
+ public int maxInValueRegister() {
+ throw new Unreachable("We map out of ResourceConstNumber before register allocation");
+ }
+
+ @Override
+ public int maxOutValueRegister() {
+ throw new Unreachable("We map out of ResourceConstNumber before register allocation");
+ }
+
+ @Override
+ public boolean identicalNonValueNonPositionParts(Instruction other) {
+ if (other == this) {
+ return true;
+ }
+ if (!other.isResourceConstNumber()) {
+ return false;
+ }
+ ResourceConstNumber otherNumber = other.asResourceConstNumber();
+ return otherNumber.getValue() == getValue();
+ }
+
+ @Override
+ public boolean isOutConstant() {
+ return true;
+ }
+
+ @Override
+ public boolean isResourceConstNumber() {
+ return true;
+ }
+
+ @Override
+ public TypeElement evaluate(AppView<?> appView) {
+ assert getOutType().isInt();
+ return TypeElement.getInt();
+ }
+
+ @Override
+ public AbstractValue getAbstractValue(
+ AppView<?> appView, ProgramMethod context, AbstractValueSupplier abstractValueSupplier) {
+ if (outValue.hasLocalInfo()) {
+ return AbstractValue.unknown();
+ }
+ return appView.abstractValueFactory().createSingleResourceNumberValue(getValue());
+ }
+
+ @Override
+ public void buildLir(LirBuilder<Value, ?> builder) {
+ builder.addResourceConstNumber(getValue());
+ }
+
+ public static ResourceConstNumber copyOf(IRCode code, ResourceConstNumber original) {
+ Value newValue = code.createValue(TypeElement.getInt(), original.getLocalInfo());
+ return new ResourceConstNumber(newValue, original.getValue());
+ }
+
+ @Override
+ public ResourceConstNumber asResourceConstNumber() {
+ return this;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static class Builder extends BuilderBase<Builder, ResourceConstNumber> {
+
+ private int value;
+
+ public Builder setValue(int value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public ResourceConstNumber build() {
+ return amend(new ResourceConstNumber(outValue, value));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ protected boolean verifyInstructionTypeCannotThrow() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index fb150fb..0a16889 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -842,6 +842,10 @@
return isConstant() && getConstInstruction().isConstNumber();
}
+ public boolean isConstResourceNumber() {
+ return isConstant() && getConstInstruction().isResourceConstNumber();
+ }
+
public boolean isConstNumber(long rawValue) {
return isConstant()
&& getConstInstruction().isConstNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 2c7d62b..5f5a9c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRemover;
+import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRewriter;
import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
@@ -34,6 +36,8 @@
assert appView.testing().canUseLir(appView);
assert appView.testing().isPreLirPhase();
appView.testing().enterLirSupportedPhase();
+ ConstResourceNumberRewriter constResourceNumberRewriter =
+ new ConstResourceNumberRewriter(appView);
// Convert code objects to LIR.
ThreadUtils.processItems(
appView.appInfo().classes(),
@@ -45,10 +49,11 @@
clazz.forEachProgramMethodMatching(
method ->
method.hasCode()
- && !method.isInitializer()
+ && !method.isInstanceInitializer()
&& !appView.isCfByteCodePassThrough(method),
method -> {
IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
+ constResourceNumberRewriter.run(code, Timing.empty());
LirCode<Integer> lirCode =
IR2LirConverter.translate(
code,
@@ -179,6 +184,8 @@
}
IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
assert irCode.verifyInvokeInterface(appView);
+ ConstResourceNumberRemover constResourceNumberRemover = new ConstResourceNumberRemover(appView);
+ constResourceNumberRemover.run(irCode, onThreadTiming);
FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
if (appView.options().isGeneratingDex() && changed) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java
new file mode 100644
index 0000000..ca379dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRemover.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+
+public class ConstResourceNumberRemover extends CodeRewriterPass<AppInfo> {
+
+ public ConstResourceNumberRemover(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getRewriterId() {
+ return "ConstResourceNumberRemover";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ return code.metadata().mayHaveResourceConstNumber();
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isResourceConstNumber()) {
+ ResourceConstNumber resourceConstNumber = current.asResourceConstNumber();
+ iterator.replaceCurrentInstruction(
+ new ConstNumber(resourceConstNumber.dest(), resourceConstNumber.getValue()));
+ hasChanged = true;
+ }
+ }
+ return CodeRewriterResult.hasChanged(hasChanged);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
new file mode 100644
index 0000000..e6806fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class ConstResourceNumberRewriter extends CodeRewriterPass<AppInfo> {
+ public ConstResourceNumberRewriter(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getRewriterId() {
+ return "ConstResourceNumberRewriter";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ return appView.options().isOptimizedResourceShrinking()
+ && code.context().getDefinition().isClassInitializer()
+ && isRClass(code.context().getHolder());
+ }
+
+ private boolean isRClass(DexProgramClass holder) {
+ return DescriptorUtils.isRClassDescriptor(holder.getType().toDescriptorString());
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isConstNumber()) {
+ ConstNumber constNumber = current.asConstNumber();
+ // The resource const numbers should always have a single value here
+ Value currentValue = current.outValue();
+ if (currentValue.hasSingleUniqueUser() && !currentValue.hasPhiUsers()) {
+ Instruction singleUser = currentValue.singleUniqueUser();
+ if (singleUser.isStaticPut()
+ || singleUser.isNewArrayFilled()
+ || (singleUser.isArrayPut() && singleUser.asArrayPut().value() == currentValue)) {
+ iterator.replaceCurrentInstruction(
+ new ResourceConstNumber(constNumber.dest(), constNumber.getIntValue()));
+ hasChanged = true;
+ }
+ }
+ }
+ }
+ return CodeRewriterResult.hasChanged(hasChanged);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
index 9584e8a..a213f7f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.RESOURCE_CONST_NUMBER;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import com.android.tools.r8.errors.Unreachable;
@@ -29,6 +30,7 @@
import com.android.tools.r8.ir.code.InstructionOrPhi;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -466,6 +468,9 @@
case CONST_NUMBER:
copy = ConstNumber.copyOf(code, instruction.asConstNumber());
break;
+ case RESOURCE_CONST_NUMBER:
+ copy = ResourceConstNumber.copyOf(code, instruction.asResourceConstNumber());
+ break;
case CONST_STRING:
copy = ConstString.copyOf(code, instruction.asConstString());
break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 6880ed1..f10854f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -440,7 +440,7 @@
isWrittenBefore.remove(fieldReference);
}
continue;
- } else if (fieldReference.type.isPrimitiveType()
+ } else if ((fieldReference.type.isPrimitiveType() && !hasPutOfConstResource(put))
|| fieldReference.type == dexItemFactory.stringType) {
finalFieldPuts.put(field, put);
unnecessaryStaticPuts.add(put);
@@ -507,6 +507,10 @@
return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
}
+ private boolean hasPutOfConstResource(StaticPut put) {
+ return put.value().isConstResourceNumber();
+ }
+
private Map<DexEncodedField, StaticPut> validateFinalFieldPuts(
Map<DexEncodedField, StaticPut> finalFieldPuts,
Map<DexField, Set<StaticPut>> isWrittenBefore) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 1ef539a..c1f203a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.RESOURCE_CONST_NUMBER;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
@@ -39,6 +40,7 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
@@ -164,6 +166,8 @@
case CONST_NUMBER:
return Long.hashCode(candidate.asConstNumber().getRawValue())
+ 13 * candidate.outType().hashCode();
+ case RESOURCE_CONST_NUMBER:
+ return Integer.hashCode(candidate.asResourceConstNumber().getValue());
case CONST_STRING:
return candidate.asConstString().getValue().hashCode();
case DEX_ITEM_BASED_CONST_STRING:
@@ -349,6 +353,7 @@
switch (newInstruction.opcode()) {
case CONST_CLASS:
case CONST_NUMBER:
+ case RESOURCE_CONST_NUMBER:
case CONST_STRING:
case DEX_ITEM_BASED_CONST_STRING:
case STATIC_GET:
@@ -388,6 +393,8 @@
return ConstClass.copyOf(code, canonicalizedConstant.asConstClass());
case CONST_NUMBER:
return ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber());
+ case RESOURCE_CONST_NUMBER:
+ return ResourceConstNumber.copyOf(code, canonicalizedConstant.asResourceConstNumber());
case CONST_STRING:
return ConstString.copyOf(code, canonicalizedConstant.asConstString());
case DEX_ITEM_BASED_CONST_STRING:
@@ -434,6 +441,7 @@
return false;
}
break;
+ case RESOURCE_CONST_NUMBER:
case CONST_NUMBER:
break;
case CONST_STRING:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index ee03422..d9acaba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -442,6 +442,7 @@
|| instruction.isConstMethodHandle()
|| instruction.isConstMethodType()
|| instruction.isConstNumber()
+ || instruction.isResourceConstNumber()
|| instruction.isConstString()
|| instruction.isDebugInstruction()
|| instruction.isDexItemBasedConstString()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
index 1246516..207d757 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
@@ -8,13 +8,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldResolutionResult;
-import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -127,19 +124,16 @@
assert invokedMethod.isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod);
assert invoke.inValues().size() == 2;
Instruction valueDefinition = invoke.getLastArgument().definition;
- if (valueDefinition != null && valueDefinition.isStaticGet()) {
- DexField field = valueDefinition.asStaticGet().getField();
- FieldResolutionResult fieldResolutionResult =
- appView.appInfo().resolveField(field, code.context());
- ProgramField resolvedField = fieldResolutionResult.getProgramField();
- if (resolvedField != null) {
- String singleStringValueForField =
- appView.getResourceAnalysisResult().getSingleStringValueForField(resolvedField);
- if (singleStringValueForField != null) {
- DexString value = dexItemFactory.createString(singleStringValueForField);
- instructionIterator.replaceCurrentInstructionWithConstString(
- appView, code, value, affectedValues);
- }
+ if (valueDefinition.isResourceConstNumber()) {
+ String singleStringValue =
+ appView
+ .getResourceShrinkerState()
+ .getR8ResourceShrinkerModel()
+ .getSingleStringValueOrNull(valueDefinition.asResourceConstNumber().getValue());
+ if (singleStringValue != null) {
+ DexString value = dexItemFactory.createString(singleStringValue);
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView, code, value, affectedValues);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 46e73aa..d7ee8d3 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -83,6 +83,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.RecordFieldValues;
import com.android.tools.r8.ir.code.Rem;
+import com.android.tools.r8.ir.code.ResourceConstNumber;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.Shl;
@@ -518,6 +519,12 @@
}
@Override
+ public void onConstResourceNumber(int value) {
+ Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+ addInstruction(new ResourceConstNumber(dest, value));
+ }
+
+ @Override
public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
Value dest = getOutValueForNextInstruction(valueTypeElement(type));
addInstruction(
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 5d988d8..c243c7b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -579,6 +579,13 @@
}
}
+ public LirBuilder<V, EV> addResourceConstNumber(int value) {
+ advanceInstructionState();
+ writer.writeInstruction(LirOpcodes.RESOURCENUMBER, ByteUtils.intEncodingSize(value));
+ ByteUtils.writeEncodedInt(value, writer::writeOperand);
+ return this;
+ }
+
public LirBuilder<V, EV> addConstString(DexString string) {
return addOneItemInstruction(LirOpcodes.LDC, string);
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 393cd05..702dbba 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -212,6 +212,7 @@
int CHECKCAST_IGNORE_COMPAT = 225;
int CONSTCLASS_IGNORE_COMPAT = 226;
int STRINGSWITCH = 227;
+ int RESOURCENUMBER = 228;
static String toString(int opcode) {
switch (opcode) {
@@ -551,6 +552,8 @@
return "CONSTCLASS_IGNORE_COMPAT";
case STRINGSWITCH:
return "STRINGSWITCH";
+ case RESOURCENUMBER:
+ return "RESOURCENUMBER";
default:
throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index cdd50cc..4fe7d83 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -82,6 +82,10 @@
onConstNumber(NumericType.INT, value);
}
+ public void onConstResourceNumber(int value) {
+ onInstruction();
+ }
+
public void onConstFloat(int value) {
onConstNumber(NumericType.FLOAT, value);
}
@@ -1278,6 +1282,12 @@
onRecordFieldValues(payload.fields, values);
return;
}
+ case LirOpcodes.RESOURCENUMBER:
+ {
+ int value = view.getNextIntegerOperand();
+ onConstResourceNumber(value);
+ return;
+ }
default:
throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index 2e0273d..c27b1ed 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -153,6 +153,11 @@
}
@Override
+ public void onConstResourceNumber(int value) {
+ appendOutValue().append(value);
+ }
+
+ @Override
public void onConstFloat(int value) {
appendOutValue().append(Float.intBitsToFloat(value));
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
index 7b78efa..3c0961e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
@@ -352,6 +352,9 @@
case CONSTCLASS_IGNORE_COMPAT:
return DexConstClass.SIZE;
+ case RESOURCENUMBER:
+ return DexConst4.SIZE;
+
default:
throw new Unreachable("Unexpected LIR opcode: " + opcode);
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
index 83a34f1..d97b0a9 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
@@ -227,6 +227,11 @@
}
@Override
+ public void onConstResourceNumber(int value) {
+ registry.registerConstResourceNumber(value);
+ }
+
+ @Override
public void onRecordFieldValues(DexField[] fields, List<EV> values) {
registry.registerRecordFieldValues(fields);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 7e988c4..736f285 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -57,6 +57,12 @@
}
@Override
+ public void registerConstResourceNumber(int value) {
+ super.registerConstResourceNumber(value);
+ enqueuer.traceResourceValue(value);
+ }
+
+ @Override
public void registerInvokeVirtual(DexMethod invokedMethod) {
super.registerInvokeVirtual(invokedMethod);
enqueuer.traceInvokeVirtual(invokedMethod, getContext());
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 5279f1d..34cb2ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -17,7 +17,11 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static java.util.Collections.emptySet;
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
+import com.android.tools.r8.AndroidResourceInput;
+import com.android.tools.r8.AndroidResourceInput.Kind;
import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -479,6 +483,8 @@
private final ProfileCollectionAdditions profileCollectionAdditions;
+ private final R8ResourceShrinkerState r8ResourceShrinkerState;
+
Enqueuer(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProfileCollectionAdditions profileCollectionAdditions,
@@ -537,6 +543,27 @@
objectAllocationInfoCollection =
ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
+ r8ResourceShrinkerState = setupResourceShrinkerState(appView);
+ }
+
+ private R8ResourceShrinkerState setupResourceShrinkerState(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ R8ResourceShrinkerState r8ResourceShrinkerState = new R8ResourceShrinkerState();
+ if (options.resourceShrinkerConfiguration.isOptimizedShrinking()
+ && options.androidResourceProvider != null) {
+ try {
+ for (AndroidResourceInput androidResource :
+ options.androidResourceProvider.getAndroidResources()) {
+ if (androidResource.getKind() == Kind.RESOURCE_TABLE) {
+ r8ResourceShrinkerState.setResourceTableInput(androidResource.getByteStream());
+ break;
+ }
+ }
+ } catch (ResourceException e) {
+ throw appView.reporter().fatalError("Failed initializing resource table");
+ }
+ }
+ return r8ResourceShrinkerState;
}
private AppInfoWithClassHierarchy appInfo() {
@@ -1107,6 +1134,10 @@
}
}
+ public void traceResourceValue(int value) {
+ r8ResourceShrinkerState.trace(value);
+ }
+
public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
deferredTracing.notifyReflectiveFieldAccess(field, context);
if (registerReflectiveFieldWrite(field, context)) {
@@ -3740,6 +3771,7 @@
EnqueuerResult result = createEnqueuerResult(appInfo, timing);
profileCollectionAdditions.commit(appView);
timing.end();
+ appView.setResourceShrinkerState(r8ResourceShrinkerState);
return result;
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index df0ac25..7d8e995 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.nio.file.Path;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -805,6 +806,22 @@
return new ModuleAndDescriptor(module, 'L' + descriptor + ';');
}
+ public static boolean isRClassDescriptor(String descriptor) {
+ String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+ List<String> split = StringUtils.split(simpleClassName, '$');
+
+ if (split.size() < 2) {
+ return false;
+ }
+ String type = split.get(split.size() - 1);
+ String rClass = split.get(split.size() - 2);
+ // We match on R if:
+ // - The name of the Class is R$type - we allow R to be an inner class.
+ // - The inner type should be with lower case
+ boolean isRClass = Character.isLowerCase(type.charAt(0)) && rClass.equals("R");
+ return isRClass;
+ }
+
public static String getPathFromDescriptor(String descriptor) {
// We are quite loose on names here to support testing illegal names, too.
assert descriptor.startsWith("L");
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index afc71ff..29f4710 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -229,6 +229,10 @@
return proguardConfiguration != null;
}
+ public boolean isOptimizedResourceShrinking() {
+ return androidResourceProvider != null && resourceShrinkerConfiguration.isOptimizedShrinking();
+ }
+
public ProguardConfiguration getProguardConfiguration() {
return proguardConfiguration;
}
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index 088547e..5e0a8ae 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -51,8 +51,8 @@
super(debugReporter, supportMultipackages);
}
- public Map<Integer, String> getStringResourcesWithSingleValue() {
- return stringResourcesWithSingleValue;
+ public String getSingleStringValueOrNull(int id) {
+ return stringResourcesWithSingleValue.get(id);
}
// Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index f803d78..c9b9401 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -946,4 +946,12 @@
return addProgramClassFileData(testResource.getRClass().getClassFileData());
}
+ public T setAndroidResourcesFromPath(Path input, Path output) {
+ resourceShrinkerOutput = output;
+ getBuilder()
+ .setAndroidResourceProvider(
+ new ArchiveProtoAndroidResourceProvider(input, new PathOrigin(input)));
+ getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output, input));
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
new file mode 100644
index 0000000..60ce942
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.androidresources;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 ResourceIDCannonicalizationTest extends TestBase {
+ public static final int EXPECTED_RESOURCE_NUMBER = 0x7f010001;
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(FooBar.class)
+ .addAndroidResources(getTestResources(temp))
+ .addKeepMainRule(FooBar.class)
+ .enableOptimizedShrinking()
+ .compile()
+ .inspect(
+ codeInspector -> {
+ // We should canonicalize the resource numbers separately from the normal const
+ // numbers.
+ // This implies that the output have two distinct const numbers with the same value.
+ long constNumbers =
+ codeInspector
+ .clazz(FooBar.class)
+ .mainMethod()
+ .streamInstructions()
+ .filter(i -> i.isConstNumber(EXPECTED_RESOURCE_NUMBER))
+ .count();
+ assertEquals(2, constNumbers);
+ })
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "foo");
+ });
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(EXPECTED_RESOURCE_NUMBER);
+ System.out.println(R.string.foo);
+ System.out.println(EXPECTED_RESOURCE_NUMBER);
+ System.out.println(R.string.foo);
+ System.out.println(EXPECTED_RESOURCE_NUMBER);
+ System.out.println(EXPECTED_RESOURCE_NUMBER);
+ }
+ }
+ }
+
+ public static class R {
+ public static class string {
+ public static int foo;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
index 662d00f..74c7102 100644
--- a/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
+++ b/src/test/java/com/android/tools/r8/androidresources/optimizedshrinking/TestOptimizedShrinking.java
@@ -71,7 +71,7 @@
resourceTableInspector.assertContainsResourceWithName("drawable", "foobar");
// In debug mode legacy shrinker will not attribute our $R inner class as an R class
// (this is only used for testing, _real_ R classes are not inner classes.
- if (!debug || optimized) {
+ if (!debug) {
resourceTableInspector.assertDoesNotContainResourceWithName(
"string", "unused_string");
resourceTableInspector.assertDoesNotContainResourceWithName(
@@ -107,7 +107,7 @@
// In optimized mode we track these correctly, so we should not unconditionally keep
// all attributes.
- if (optimized) {
+ if (optimized && !debug) {
resourceTableInspector.assertDoesNotContainResourceWithName(
"attr", "attr_unused_styleable" + i);
} else {