Limit the size of pc2pc debug-info encoding.
This CL introduces pc2pc encoding points such that a given set of
methods is bounded by an expansion/non-sharing threshold.
The default value is 200.000 which is found to have very little
impact on size and avoids most regression time in dex2oat
speed-profile compilations.
For experimental purposes the defaults can be adjusted with:
-Dcom.android.tools.r8.pc2pcOverheadThreshold=100000
Bug: b/231903117
Change-Id: I61296334a284d7c8461329eee5e8c4929b2b7f13
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 19bf467..967453b 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -8,20 +8,24 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfo.PcBasedDebugInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LebUtils;
import com.android.tools.r8.utils.LineNumberOptimizer;
import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntIterators;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
@@ -34,12 +38,12 @@
public interface DebugRepresentationPredicate {
- int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
+ int getDexPcEncodingCutoff(ProgramMethod method);
}
public static DebugRepresentationPredicate none(InternalOptions options) {
assert !options.canUseDexPc2PcAsDebugInformation();
- return (holder, method) -> NO_PC_ENCODING;
+ return method -> NO_PC_ENCODING;
}
public static DebugRepresentationPredicate fromFiles(
@@ -48,29 +52,33 @@
return none(options);
}
if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
- return (holder, method) -> ALWAYS_PC_ENCODING;
+ return method -> ALWAYS_PC_ENCODING;
}
// TODO(b/220999985): Avoid the need to maintain a class-to-file map.
Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
for (VirtualFile file : files) {
+ if (options.testing.debugRepresentationCallback != null) {
+ options.testing.debugRepresentationCallback.accept(file.getDebugRepresentation());
+ }
file.classes().forEach(c -> classMapping.put(c, file));
}
- return (holder, method) -> {
- if (!isPcCandidate(method, options)) {
+ return method -> {
+ if (!isPcCandidate(method.getDefinition(), options)) {
return NO_PC_ENCODING;
}
- VirtualFile file = classMapping.get(holder);
+ VirtualFile file = classMapping.get(method.getHolder());
DebugRepresentation cutoffs = file.getDebugRepresentation();
int maxPc = cutoffs.getDexPcEncodingCutoff(method);
assert maxPc == NO_PC_ENCODING
- || verifyLastExecutableInstructionWithinBound(method.getCode().asDexCode(), maxPc);
+ || verifyLastExecutableInstructionWithinBound(
+ method.getDefinition().getCode().asDexCode(), maxPc);
return maxPc;
};
}
- private final Int2ReferenceMap<CostSummary> paramToInfo;
+ private final Int2ReferenceMap<ConversionInfo> paramToInfo;
- private DebugRepresentation(Int2ReferenceMap<CostSummary> paramToInfo) {
+ private DebugRepresentation(Int2ReferenceMap<ConversionInfo> paramToInfo) {
this.paramToInfo = paramToInfo;
}
@@ -113,23 +121,31 @@
int lastPc = lastInstruction.getOffset();
int debugInfoCost = estimatedDebugInfoSize(debugInfo);
paramCountToCosts
- .computeIfAbsent(debugInfo.getParameterCount(), DebugRepresentation.CostSummary::new)
+ .computeIfAbsent(debugInfo.getParameterCount(), CostSummary::new)
.addCost(lastPc, debugInfoCost);
}
}
// Second compute the cost of converting to a pc encoding.
- paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(appView));
+ Int2ReferenceMap<ConversionInfo> conversions =
+ new Int2ReferenceOpenHashMap<>(paramCountToCosts.size());
+ paramCountToCosts.forEach(
+ (param, summary) -> conversions.put(param, summary.computeConversionCosts(appView)));
// The result is stored on the virtual files for thread safety.
// TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
- file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
+ file.setDebugRepresentation(new DebugRepresentation(conversions));
}
- private int getDexPcEncodingCutoff(DexEncodedMethod method) {
- DexCode code = method.getCode().asDexCode();
+ private int getDexPcEncodingCutoff(ProgramMethod method) {
+ if (paramToInfo.isEmpty()) {
+ // This should only be the case if the method has overloads and thus *cannot* use pc encoding.
+ assert verifyMethodHasOverloads(method);
+ return NO_PC_ENCODING;
+ }
+ DexCode code = method.getDefinition().getCode().asDexCode();
int paramCount = method.getParameters().size();
assert code.getDebugInfo() == null || code.getDebugInfo().getParameterCount() == paramCount;
- CostSummary conversionInfo = paramToInfo.get(paramCount);
- if (conversionInfo == null || conversionInfo.cutoff < 0) {
+ ConversionInfo conversionInfo = paramToInfo.get(paramCount);
+ if (conversionInfo == null || !conversionInfo.hasConversions()) {
// We expect all methods calling this to have computed conversion info.
assert conversionInfo != null;
return NO_PC_ENCODING;
@@ -139,14 +155,24 @@
return NO_PC_ENCODING;
}
int maxPc = lastInstruction.getOffset();
- return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
+ return conversionInfo.getConversionPointFor(maxPc);
+ }
+
+ private boolean verifyMethodHasOverloads(ProgramMethod method) {
+ assert 1
+ < IterableUtils.size(method.getHolder().methods(m -> m.getName().equals(method.getName())));
+ return true;
}
@Override
public String toString() {
- List<CostSummary> sorted = new ArrayList<>(paramToInfo.values());
+ return toString(false);
+ }
+
+ public String toString(boolean printCostSummary) {
+ List<ConversionInfo> sorted = new ArrayList<>(paramToInfo.values());
sorted.sort(Comparator.comparing(i -> i.paramCount));
- return StringUtils.join("\n", sorted, CostSummary::toString);
+ return StringUtils.join("\n", sorted, c -> c.toString(printCostSummary));
}
private static boolean isPcCandidate(DexEncodedMethod method, InternalOptions options) {
@@ -157,14 +183,32 @@
return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
}
- /** The cost of representing normal debug info for all methods with this max pc value. */
- private static class PcNormalCost {
-
+ /** Cost information for debug info at a given PC. */
+ private static class PcCostInfo {
+ // PC point for which the information pertains to.
final int pc;
- int cost;
- int methods;
- public PcNormalCost(int pc) {
+ // Normal debug-info encoding cost.
+ int cost = 0;
+
+ // Number of methods this information pertains to.
+ int methods = 0;
+
+ @Override
+ public String toString() {
+ return "pc="
+ + pc
+ + ", cost="
+ + cost
+ + ", methods="
+ + methods
+ + ", saved="
+ + (cost - pc)
+ + ", overhead="
+ + getExpansionOverhead(pc, methods, cost);
+ }
+
+ public PcCostInfo(int pc) {
assert pc >= 0;
this.pc = pc;
}
@@ -176,20 +220,76 @@
}
}
+ /** Conversion judgment up to a given PC bound (the lower bound is context dependent). */
+ private static class PcConversionInfo {
+ static final PcConversionInfo NO_CONVERSION = new PcConversionInfo(-1, false, 0, 0);
+
+ // PC bound for which the information pertains to.
+ final int pc;
+
+ // Judgment of whether the methods in this grouping should be converted to pc2pc encoding.
+ final boolean converted;
+
+ // Info for debugging.
+ private final int methods;
+ private final int normalCost;
+
+ public PcConversionInfo(int pc, boolean converted, int methods, int normalCost) {
+ this.pc = pc;
+ this.converted = converted;
+ this.methods = methods;
+ this.normalCost = normalCost;
+ }
+
+ @Override
+ public String toString() {
+ return "pc="
+ + pc
+ + ", converted="
+ + converted
+ + ", cost="
+ + normalCost
+ + ", methods="
+ + methods
+ + ", saved="
+ + (normalCost - pc)
+ + ", overhead="
+ + getExpansionOverhead(pc, methods, normalCost);
+ }
+ }
+
+ // A pc2pc stream is approximately one event more than the pc.
+ private static int pcEventCount(int pc) {
+ return pc + 1;
+ }
+
+ /**
+ * Figure for the overhead that the pc2pc encoding can result in.
+ *
+ * <p>This overhead is not in the encoding size but rather if the encoding is expanded at each
+ * method referencing the shared PC encoding.
+ */
+ private static int getExpansionOverhead(int currentPc, int methodCount, int normalCost) {
+ long expansion = ((long) pcEventCount(currentPc)) * methodCount;
+ long cost = expansion - normalCost;
+ return cost > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) cost;
+ }
+
+ private static boolean isWithinExpansionThreshold(
+ int threshold, int currentPc, int methodCount, int normalCost) {
+ // A negative threshold denotes unbounded.
+ if (threshold < 0) {
+ return true;
+ }
+ return getExpansionOverhead(currentPc, methodCount, normalCost) <= threshold;
+ }
+
/** The summary of normal costs for all debug info with a particular parameter size. */
private static class CostSummary {
-
private final int paramCount;
// Values for the normal encoding costs per-pc.
- private final Int2ReferenceMap<PcNormalCost> pcToCost = new Int2ReferenceOpenHashMap<>();
- private int minPc = Integer.MAX_VALUE;
- private int maxPc = Integer.MIN_VALUE;
-
- // Values for the conversion costs. These are computed only after all per-pc costs are known.
- private int cutoff;
- private int normalPreCutoffCost;
- private int normalPostCutoffCost;
+ private Int2ReferenceMap<PcCostInfo> pcToCost = new Int2ReferenceOpenHashMap<>();
private CostSummary(int paramCount) {
assert paramCount >= 0;
@@ -198,82 +298,166 @@
private void addCost(int pc, int cost) {
assert pc >= 0;
- pcToCost.computeIfAbsent(pc, PcNormalCost::new).add(cost);
- minPc = Math.min(minPc, pc);
- maxPc = Math.max(maxPc, pc);
+ pcToCost.computeIfAbsent(pc, PcCostInfo::new).add(cost);
}
- private void computeConversionCosts(AppView<?> appView) {
+ private static class ConversionState {
+ Int2ReferenceSortedMap<PcConversionInfo> groups = new Int2ReferenceAVLTreeMap<>();
+ PcConversionInfo converted = PcConversionInfo.NO_CONVERSION;
+ int flushedPc = 0;
+ int unconvertedPc = 0;
+ int normalCost = 0;
+ int methods = 0;
+
+ void reset() {
+ converted = PcConversionInfo.NO_CONVERSION;
+ unconvertedPc = 0;
+ normalCost = 0;
+ methods = 0;
+ }
+
+ void add(PcCostInfo costInfo) {
+ methods += costInfo.methods;
+ normalCost += costInfo.cost;
+ }
+
+ void flush() {
+ if (flushedPc < converted.pc) {
+ groups.put(converted.pc, converted);
+ flushedPc = converted.pc;
+ }
+ if (flushedPc < unconvertedPc) {
+ if (0 < flushedPc && !groups.get(flushedPc).converted) {
+ groups.remove(flushedPc);
+ }
+ PcConversionInfo unconverted =
+ new PcConversionInfo(unconvertedPc, false, methods, normalCost);
+ groups.put(unconvertedPc, unconverted);
+ flushedPc = unconvertedPc;
+ }
+ reset();
+ }
+
+ void update(int currentPc, boolean convertToPc) {
+ if (convertToPc) {
+ converted = new PcConversionInfo(currentPc, true, methods, normalCost);
+ unconvertedPc = 0;
+ } else {
+ unconvertedPc = currentPc;
+ }
+ }
+
+ public Int2ReferenceSortedMap<PcConversionInfo> getFinalConversions() {
+ // If there is only a single group check it is actually a converted range.
+ if (groups.size() > 1
+ || (groups.size() == 1 && groups.values().iterator().next().converted)) {
+ return groups;
+ }
+ return null;
+ }
+ }
+
+ private ConversionInfo computeConversionCosts(AppView<?> appView) {
+ int threshold = appView.options().testing.pcBasedDebugEncodingOverheadThreshold;
boolean forcePcBasedEncoding = appView.options().testing.forcePcBasedEncoding;
assert !pcToCost.isEmpty();
- // Point at which it is estimated that conversion to PC-encoding is viable.
- int currentConvertedPc = -1;
- // The normal cost of the part that is viable for conversion (this is just for debugging).
- int normalConvertedCost = 0;
- // The normal cost of the part that is not yet part of the converted range.
- int normalOutstandingCost = 0;
// Iterate in ascending order as the point conversion cost is the sum of the preceding costs.
int[] sortedPcs = new int[pcToCost.size()];
IntIterators.unwrap(pcToCost.keySet().iterator(), sortedPcs);
Arrays.sort(sortedPcs);
+ ConversionState state = new ConversionState();
for (int currentPc : sortedPcs) {
- PcNormalCost pcSummary = pcToCost.get(currentPc);
- // The cost of the debug info unconverted is the sum of the unconverted up to this point.
- normalOutstandingCost += pcSummary.cost;
- // The cost of the conversion is the delta between the already converted and the current.
- // This does not account for the header overhead on converting the first point. However,
- // the few bytes overhead per param-count should not affect much.
- int costToConvert = currentPc - currentConvertedPc;
- // If the estimated cost is larger we convert. The order here could be either way as
- // both the normal cost and converted cost are estimates. Canonicalization could reduce
- // the former and compaction could reduce the latter.
- if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
- normalConvertedCost += normalOutstandingCost;
- normalOutstandingCost = 0;
- currentConvertedPc = currentPc;
+ PcCostInfo current = pcToCost.get(currentPc);
+ assert currentPc == current.pc;
+ // Don't extend the conversion group as it could potentially become too large if expanded.
+ // Any pending conversion can be flushed now.
+ if (!isWithinExpansionThreshold(
+ threshold,
+ currentPc,
+ state.methods + current.methods,
+ state.normalCost + current.cost)) {
+ state.flush();
}
+ state.add(current);
+ int costToConvert = pcEventCount(currentPc);
+ boolean canExpand =
+ isWithinExpansionThreshold(threshold, currentPc, state.methods, state.normalCost);
+ state.update(
+ currentPc, canExpand && (forcePcBasedEncoding || state.normalCost > costToConvert));
}
- cutoff = currentConvertedPc;
- normalPreCutoffCost = normalConvertedCost;
- normalPostCutoffCost = normalOutstandingCost;
- assert cutoff >= -1;
- assert normalPreCutoffCost >= 0;
- assert normalPostCutoffCost >= 0;
- assert preCutoffPcCost() >= 0;
- assert postCutoffPcCost() >= 0;
- }
-
- private int preCutoffPcCost() {
- return cutoff > 0 ? PcBasedDebugInfo.estimatedWriteSize(paramCount, cutoff) : 0;
- }
-
- private int postCutoffPcCost() {
- return cutoff < maxPc ? PcBasedDebugInfo.estimatedWriteSize(paramCount, maxPc - cutoff) : 0;
+ state.flush();
+ return new ConversionInfo(
+ paramCount,
+ state.getFinalConversions(),
+ appView.options().testing.debugRepresentationCallback != null ? this : null);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder
- .append("p:")
- .append(paramCount)
- .append(", c:")
- .append(cutoff)
- .append(", m:")
- .append(maxPc);
- if (cutoff > 0) {
- builder
- .append(", preCutNormal:")
- .append(normalPreCutoffCost)
- .append(", preCutPC:")
- .append(preCutoffPcCost());
+ builder.append("params:").append(paramCount).append('\n');
+ Collection<Integer> keys = CollectionUtils.sort(pcToCost.keySet(), Integer::compareTo);
+ for (int key : keys) {
+ builder.append(pcToCost.get(key)).append('\n');
}
- if (cutoff < maxPc) {
- builder
- .append(", postCutNormal:")
- .append(normalPostCutoffCost)
- .append(", postCutPC:")
- .append(postCutoffPcCost());
+ return builder.toString();
+ }
+ }
+
+ /** The computed conversion points all debug info with a particular parameter size. */
+ private static class ConversionInfo {
+ private final int paramCount;
+ private final Int2ReferenceSortedMap<PcConversionInfo> conversions;
+
+ // For debugging purposes.
+ private final CostSummary costSummary;
+
+ private ConversionInfo(
+ int paramCount,
+ Int2ReferenceSortedMap<PcConversionInfo> conversions,
+ CostSummary costSummary) {
+ assert paramCount >= 0;
+ this.paramCount = paramCount;
+ this.conversions = conversions;
+ this.costSummary = costSummary;
+ }
+
+ boolean hasConversions() {
+ return conversions != null;
+ }
+
+ int getConversionPointFor(int pc) {
+ Int2ReferenceSortedMap<PcConversionInfo> tailMap = conversions.tailMap(pc);
+ if (tailMap.isEmpty()) {
+ return -1;
+ }
+ int pcGroupBound = tailMap.firstIntKey();
+ PcConversionInfo entryUpToIncludingMax = conversions.get(pcGroupBound);
+ if (entryUpToIncludingMax.converted) {
+ assert pcGroupBound == entryUpToIncludingMax.pc;
+ return pcGroupBound;
+ }
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ return toString(false);
+ }
+
+ public String toString(boolean printCostSummaries) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("params:").append(paramCount).append('\n');
+ if (conversions != null) {
+ for (PcConversionInfo group : conversions.values()) {
+ builder.append(group).append('\n');
+ }
+ } else {
+ builder.append(" no conversions").append('\n');
+ }
+ if (printCostSummaries && costSummary != null) {
+ builder.append("Cost summaries:\n");
+ builder.append(costSummary);
}
return builder.toString();
}
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 c836c46..f0a8d56 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Backend;
import com.android.tools.r8.dex.Marker.Tool;
@@ -1778,6 +1779,8 @@
public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
+ public Consumer<DebugRepresentation> debugRepresentationCallback = null;
+
/**
* If this flag is enabled, we will also compute the set of possible targets for invoke-
* interface and invoke-virtual instructions that target a library method, and add the
@@ -1819,6 +1822,10 @@
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
public boolean forcePcBasedEncoding = false;
+ public int pcBasedDebugEncodingOverheadThreshold =
+ System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold") != null
+ ? Integer.parseInt(System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold"))
+ : 200000;
public Set<Inliner.Reason> validInliningReasons = null;
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 389fd36..660a5fd 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -76,9 +76,8 @@
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -354,10 +353,10 @@
private interface PcBasedDebugInfoRecorder {
/** Callback to record a code object with a given max instruction PC and parameter count. */
- void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
+ void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
/** Callback to record a code object with only a single "line". */
- void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
+ void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
/**
* Install the correct debug info objects.
@@ -406,32 +405,44 @@
singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
}
+ private int getLastInstructionOffset(DexCode code) {
+ return DebugRepresentation.getLastExecutableInstruction(code).getOffset();
+ }
+
+ private boolean cantAddToClearSet(ProgramMethod method) {
+ assert method.getDefinition().getCode().isDexCode();
+ if (singleLineCodesToClear == null) {
+ return true;
+ }
+ singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
+ return false;
+ }
+
@Override
- public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ assert method.getDefinition().getCode().isDexCode();
+ int parameterCount = method.getParameters().size();
+ DexCode code = method.getDefinition().getCode().asDexCode();
assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
- if (singleLineCodesToClear != null) {
- singleLineCodesToClear.add(code);
- return;
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ if (cantAddToClearSet(method)) {
+ recordPcMappingFor(method, maxEncodingPc);
}
- recordPcMappingFor(code, parameterCount, maxEncodingPc);
}
@Override
public void updateDebugInfoInCodeObjects() {
- Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
- new Object2ReferenceOpenHashMap<>();
+ Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
codesToUpdate.forEach(
entry -> {
assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
entry.code, entry.maxEncodingPc);
DexDebugInfo debugInfo =
- debugInfos.computeIfAbsent(
- entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+ debugInfos.computeIfAbsent(entry, Pc2PcMappingSupport::buildPc2PcDebugInfo);
assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
entry.code.setDebugInfo(debugInfo);
});
@@ -440,23 +451,26 @@
}
}
- private static DexDebugInfo buildPc2PcDebugInfo(int lastInstructionPc, int parameterCount) {
- return new DexDebugInfo.PcBasedDebugInfo(parameterCount, lastInstructionPc);
+ private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
+ return new DexDebugInfo.PcBasedDebugInfo(info.paramCount, info.maxEncodingPc);
}
}
private static class NativePcSupport implements PcBasedDebugInfoRecorder {
- @Override
- public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
- // Strip the info in full as the runtime will emit the PC directly.
- code.setDebugInfo(null);
+ private void clearDebugInfo(ProgramMethod method) {
+ // Always strip the info in full as the runtime will emit the PC directly.
+ method.getDefinition().getCode().asDexCode().setDebugInfo(null);
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
- // Strip the info at once as it does not conflict with any PC mapping update.
- code.setDebugInfo(null);
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
+ }
+
+ @Override
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
}
@Override
@@ -567,7 +581,7 @@
List<MappedPosition> mappedPositions;
Code code = definition.getCode();
int pcEncodingCutoff =
- methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+ methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
boolean canUseDexPc = pcEncodingCutoff > 0;
if (code != null) {
if (code.isDexCode()
@@ -575,7 +589,7 @@
if (canUseDexPc) {
mappedPositions =
optimizeDexCodePositionsForPc(
- definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
+ method, pcEncodingCutoff, appView, kotlinRemapper, pcBasedDebugInfo);
} else {
mappedPositions =
optimizeDexCodePositions(
@@ -749,8 +763,7 @@
if (definition.getCode().isDexCode()
&& definition.getCode().asDexCode().getDebugInfo()
== DexDebugInfoForSingleLineMethod.getInstance()) {
- pcBasedDebugInfo.recordSingleLineFor(
- definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
+ pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
}
} // for each method of the group
} // for each method group, grouped by name
@@ -1194,15 +1207,16 @@
}
private static List<MappedPosition> optimizeDexCodePositionsForPc(
- DexEncodedMethod method,
+ ProgramMethod method,
+ int pcEncodingCutoff,
AppView<?> appView,
PositionRemapper positionRemapper,
- PcBasedDebugInfoRecorder debugInfoProvider,
- int pcEncodingCutoff) {
+ PcBasedDebugInfoRecorder debugInfoProvider) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
- DexCode dexCode = method.getCode().asDexCode();
- EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
+ DexCode dexCode = method.getDefinition().getCode().asDexCode();
+ EventBasedDebugInfo debugInfo =
+ getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
IntBox firstDefaultEventPc = new IntBox(-1);
BooleanBox singleOriginalLine = new BooleanBox(true);
Pair<Integer, Position> lastPosition = new Pair<>();
@@ -1269,10 +1283,9 @@
&& lastPosition.getSecond() != null
&& (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- debugInfoProvider.recordSingleLineFor(
- dexCode, method.getParameters().size(), pcEncodingCutoff);
+ debugInfoProvider.recordSingleLineFor(method, pcEncodingCutoff);
} else {
- debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
+ debugInfoProvider.recordPcMappingFor(method, pcEncodingCutoff);
}
return mappedPositions;
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index 6891000..08b55f8 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -4,15 +4,20 @@
package com.android.tools.r8.debuginfo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -91,6 +96,25 @@
b -> b.getBuilder().setSourceFileProvider(environment -> CUSTOM_SOURCE_FILE))
.run(parameters.getRuntime(), TestClass.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
+ .inspectFailure(
+ i -> {
+ if (parameters.isDexRuntime()) {
+ Set<DexDebugInfo> debugInfos =
+ i.allClasses().stream()
+ .flatMap(c -> c.allMethods().stream())
+ .map(m -> m.getMethod().getCode().asDexCode().getDebugInfo())
+ .collect(Collectors.toSet());
+ if (isCompileWithPcAsLineNumberSupport() && !customSourceFile) {
+ // If debug info is stripped all items are null pointers.
+ assertEquals(Collections.singleton(null), debugInfos);
+ } else {
+ // If debug info remains it is two canonical items and one null pointer.
+ // The presence of 'null' debug info items is for methods with no actual lines at
+ // all.
+ assertEquals(3, debugInfos.size());
+ }
+ }
+ })
.inspectOriginalStackTrace(
stackTrace ->
assertThat(