Merge "Fix race in main-dex list building"
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 840efae..e5d2952 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -10,7 +10,7 @@
abstract class Format10t extends Base1Format {
- public final /* offset */ byte AA;
+ public /* offset */ byte AA;
// +AA | op
Format10t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index 89d740b..b3e3627 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -10,7 +10,7 @@
abstract class Format20t extends Base2Format {
- public final /* offset */ short AAAA;
+ public /* offset */ short AAAA;
// øø | op | +AAAA
Format20t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 12b752b..26f7650 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
-abstract class Format21t extends Base2Format {
+public abstract class Format21t extends Base2Format {
public final short AA;
- public final /* offset */ short BBBB;
+ public /* offset */ short BBBB;
// AA | op | +BBBB
Format21t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 6cd51d3..3ff5190 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -11,11 +11,11 @@
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
-abstract class Format22t extends Base2Format {
+public abstract class Format22t extends Base2Format {
public final byte A;
public final byte B;
- public final /* offset */ short CCCC;
+ public /* offset */ short CCCC;
// vB | vA | op | +CCCC
Format22t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index 927c879..77cb49d 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -10,7 +10,7 @@
abstract class Format30t extends Base3Format {
- public final /* offset */ int AAAAAAAA;
+ public /* offset */ int AAAAAAAA;
// øø | op | AAAAlo | AAAAhi
Format30t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index bdcbafe..a8f56a7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -41,7 +41,6 @@
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ProgramClassVisitor;
-import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -153,14 +152,16 @@
return this;
}
- private void rewriteCodeWithJumboStrings(IRConverter converter, DexEncodedMethod method) {
+ private void rewriteCodeWithJumboStrings(DexEncodedMethod method) {
if (method.getCode() == null) {
return;
}
DexCode code = method.getCode().asDexCode();
if (code.highestSortingString != null) {
if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
- converter.processJumboStrings(method, mapping.getFirstJumboString());
+ JumboStringRewriter rewriter =
+ new JumboStringRewriter(method, mapping.getFirstJumboString(), options.itemFactory);
+ rewriter.rewrite();
}
}
}
@@ -176,9 +177,8 @@
return this;
}
// At least one method needs a jumbo string.
- IRConverter converter = new IRConverter(application, appInfo, options, false);
for (DexProgramClass clazz : classes) {
- clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(converter, method));
+ clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(method));
}
return this;
}
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
new file mode 100644
index 0000000..4b97c51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -0,0 +1,568 @@
+// Copyright (c) 2017, 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.dex;
+
+import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Format31t;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.Goto16;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfGe;
+import com.android.tools.r8.code.IfGez;
+import com.android.tools.r8.code.IfGt;
+import com.android.tools.r8.code.IfGtz;
+import com.android.tools.r8.code.IfLe;
+import com.android.tools.r8.code.IfLez;
+import com.android.tools.r8.code.IfLt;
+import com.android.tools.r8.code.IfLtz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class JumboStringRewriter {
+
+ private static class TryTargets {
+ private Instruction start;
+ private Instruction end;
+ private boolean endsAfterLastInstruction;
+
+ TryTargets(Instruction start, Instruction end, boolean endsAfterLastInstruction) {
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.endsAfterLastInstruction = endsAfterLastInstruction;
+ }
+
+ void replaceTarget(Instruction target, Instruction newTarget) {
+ if (start == target) {
+ start = newTarget;
+ }
+ if (end == target) {
+ end = newTarget;
+ }
+ }
+
+ int getStartOffset() {
+ return start.getOffset();
+ }
+
+ int getStartToEndDelta() {
+ if (endsAfterLastInstruction) {
+ return end.getOffset() + end.getSize() - start.getOffset();
+ }
+ return end.getOffset() - start.getOffset();
+ }
+ }
+
+ private final DexEncodedMethod method;
+ private final DexString firstJumboString;
+ private final DexItemFactory factory;
+ private final Map<Instruction, List<Instruction>> instructionTargets = new IdentityHashMap<>();
+ private final Int2ReferenceMap<Instruction> debugEventTargets
+ = new Int2ReferenceOpenHashMap<>();
+ private final Map<Instruction, Instruction> payloadToSwitch = new IdentityHashMap<>();
+ private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>();
+ private final Map<TryHandler, List<Instruction>> handlerTargets = new IdentityHashMap<>();
+
+ public JumboStringRewriter(
+ DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+ this.method = method;
+ this.firstJumboString = firstJumboString;
+ this.factory = factory;
+ }
+
+ public void rewrite() {
+ // Build maps from everything in the code that uses offsets or direct addresses to reference
+ // instructions to the actual instruction referenced.
+ recordTargets();
+ // Expand the code by rewriting jumbo strings and branching instructions.
+ List<Instruction> newInstructions = expandCode();
+ // Commit to the new instruction offsets and update instructions, try-catch structures
+ // and debug info with the new offsets.
+ rewriteInstructionOffsets(newInstructions);
+ Try[] newTries = rewriteTryOffsets();
+ TryHandler[] newHandlers = rewriteHandlerOffsets();
+ DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
+ // Set the new code on the method.
+ DexCode code = method.getCode().asDexCode();
+ method.setDexCode(new DexCode(
+ code.registerSize,
+ code.incomingRegisterSize,
+ code.outgoingRegisterSize,
+ newInstructions.toArray(new Instruction[newInstructions.size()]),
+ newTries,
+ newHandlers,
+ newDebugInfo,
+ code.highestSortingString));
+ }
+
+ private void rewriteInstructionOffsets(List<Instruction> instructions) {
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ condition.CCCC = (short) offset;
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ condition.BBBB = (short) offset;
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ assert Byte.MIN_VALUE <= offset && offset <= Byte.MAX_VALUE;
+ jump.AA = (byte) offset;
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ jump.AAAA = (short) offset;
+ } else if (instruction instanceof Goto32) {
+ Goto32 jump = (Goto32) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ jump.AAAAAAAA = offset;
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Format31t payloadUser = (Format31t) instruction;
+ int offset =
+ instructionTargets.get(payloadUser).get(0).getOffset() - instruction.getOffset();
+ payloadUser.setPayloadOffset(offset);
+ } else if (instruction instanceof SwitchPayload) {
+ SwitchPayload payload = (SwitchPayload) instruction;
+ Instruction switchInstruction = payloadToSwitch.get(payload);
+ List<Instruction> switchTargets = instructionTargets.get(payload);
+ int[] targets = payload.switchTargetOffsets();
+ for (int i = 0; i < switchTargets.size(); i++) {
+ Instruction target = switchTargets.get(i);
+ targets[i] = target.getOffset() - switchInstruction.getOffset();
+ }
+ }
+ }
+ }
+
+ private Try[] rewriteTryOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ Try[] result = new Try[code.tries.length];
+ for (int i = 0; i < code.tries.length; i++) {
+ Try theTry = code.tries[i];
+ TryTargets targets = tryTargets.get(theTry);
+ result[i] = new Try(targets.getStartOffset(), targets.getStartToEndDelta(), -1);
+ result[i].handlerIndex = theTry.handlerIndex;
+ }
+ return result;
+ }
+
+ private TryHandler[] rewriteHandlerOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ if (code.handlers == null) {
+ return null;
+ }
+ TryHandler[] result = new TryHandler[code.handlers.length];
+ for (int i = 0; i < code.handlers.length; i++) {
+ TryHandler handler = code.handlers[i];
+ List<Instruction> targets = handlerTargets.get(handler);
+ Iterator<Instruction> it = targets.iterator();
+ int catchAllAddr = NO_HANDLER;
+ if (handler.catchAllAddr != NO_HANDLER) {
+ catchAllAddr = it.next().getOffset();
+ }
+ TypeAddrPair[] newPairs = new TypeAddrPair[handler.pairs.length];
+ for (int j = 0; j < handler.pairs.length; j++) {
+ TypeAddrPair pair = handler.pairs[j];
+ newPairs[j] = new TypeAddrPair(pair.type, it.next().getOffset());
+ }
+ result[i] = new TryHandler(newPairs, catchAllAddr);
+ }
+ return result;
+ }
+
+ private DexDebugInfo rewriteDebugInfoOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ if (debugEventTargets.size() != 0) {
+ int lastOriginalOffset = 0;
+ int lastNewOffset = 0;
+ List<DexDebugEvent> events = new ArrayList<>();
+ for (DexDebugEvent event : code.getDebugInfo().events) {
+ if (event instanceof AdvancePC) {
+ AdvancePC advance = (AdvancePC) event;
+ lastOriginalOffset += advance.delta;
+ Instruction target = debugEventTargets.get(lastOriginalOffset);
+ int pcDelta = target.getOffset() - lastNewOffset;
+ addAdvancementEvents(0, pcDelta, events);
+ lastNewOffset = target.getOffset();
+ } else if (event instanceof Default) {
+ Default defaultEvent = (Default) event;
+ lastOriginalOffset += defaultEvent.getPCDelta();
+ Instruction target = debugEventTargets.get(lastOriginalOffset);
+ int lineDelta = defaultEvent.getLineDelta();
+ int pcDelta = target.getOffset() - lastNewOffset;
+ addAdvancementEvents(lineDelta, pcDelta, events);
+ lastNewOffset = target.getOffset();
+ } else {
+ events.add(event);
+ }
+ }
+ return new DexDebugInfo(
+ code.getDebugInfo().startLine,
+ code.getDebugInfo().parameters,
+ events.toArray(new DexDebugEvent[events.size()]));
+ }
+ return code.getDebugInfo();
+ }
+
+ private void addAdvancementEvents(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
+ if (lineDelta < Constants.DBG_LINE_BASE
+ || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
+ events.add(factory.createAdvanceLine(lineDelta));
+ lineDelta = 0;
+ if (pcDelta == 0) {
+ return;
+ }
+ }
+ if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
+ events.add(factory.createAdvancePC(pcDelta));
+ pcDelta = 0;
+ if (lineDelta == 0) {
+ return;
+ }
+ }
+ int specialOpcode =
+ 0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
+ assert specialOpcode >= 0x0a;
+ assert specialOpcode <= 0xff;
+ events.add(factory.createDefault(specialOpcode));
+ }
+
+ private List<Instruction> expandCode() {
+ LinkedList<Instruction> instructions = new LinkedList<>();
+ Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+ int offsetDelta;
+ do {
+ ListIterator<Instruction> it = instructions.listIterator();
+ offsetDelta = 0;
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ instruction.setOffset(instruction.getOffset() + offsetDelta);
+ if (instruction instanceof ConstString) {
+ ConstString string = (ConstString) instruction;
+ if (string.getString().compareTo(firstJumboString) >= 0) {
+ ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString());
+ jumboString.setOffset(string.getOffset());
+ offsetDelta++;
+ it.set(jumboString);
+ replaceTarget(instruction, jumboString);
+ }
+ } else if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ int offset =
+ instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Format22t newCondition = null;
+ switch (condition.getType().inverted()) {
+ case EQ:
+ newCondition = new IfEq(condition.A, condition.B, 0);
+ break;
+ case GE:
+ newCondition = new IfGe(condition.A, condition.B, 0);
+ break;
+ case GT:
+ newCondition = new IfGt(condition.A, condition.B, 0);
+ break;
+ case LE:
+ newCondition = new IfLe(condition.A, condition.B, 0);
+ break;
+ case LT:
+ newCondition = new IfLt(condition.A, condition.B, 0);
+ break;
+ case NE:
+ newCondition = new IfNe(condition.A, condition.B, 0);
+ break;
+ }
+ offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+ }
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ int offset =
+ instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Format21t newCondition = null;
+ switch (condition.getType().inverted()) {
+ case EQ:
+ newCondition = new IfEqz(condition.AA, 0);
+ break;
+ case GE:
+ newCondition = new IfGez(condition.AA, 0);
+ break;
+ case GT:
+ newCondition = new IfGtz(condition.AA, 0);
+ break;
+ case LE:
+ newCondition = new IfLez(condition.AA, 0);
+ break;
+ case LT:
+ newCondition = new IfLtz(condition.AA, 0);
+ break;
+ case NE:
+ newCondition = new IfNez(condition.AA, 0);
+ break;
+ }
+ offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+ }
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ int offset =
+ instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ if (Byte.MIN_VALUE > offset || offset > Byte.MAX_VALUE) {
+ Instruction newJump;
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ newJump = new Goto32(offset);
+ } else {
+ newJump = new Goto16(offset);
+ }
+ newJump.setOffset(jump.getOffset());
+ it.set(newJump);
+ offsetDelta += (newJump.getSize() - jump.getSize());
+ replaceTarget(jump, newJump);
+ List<Instruction> targets = instructionTargets.remove(jump);
+ instructionTargets.put(newJump, targets);
+ }
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ int offset =
+ instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Instruction newJump = new Goto32(offset);
+ newJump.setOffset(jump.getOffset());
+ it.set(newJump);
+ offsetDelta += (newJump.getSize() - jump.getSize());
+ replaceTarget(jump, newJump);
+ List<Instruction> targets = instructionTargets.remove(jump);
+ instructionTargets.put(newJump, targets);
+ }
+ } else if (instruction instanceof Goto32) {
+ // Instruction big enough for any offset.
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ // Instruction big enough for any offset.
+ } else if (instruction instanceof SwitchPayload
+ || instruction instanceof FillArrayDataPayload) {
+ if (instruction.getOffset() % 2 != 0) {
+ offsetDelta++;
+ it.previous();
+ Nop nop = new Nop();
+ nop.setOffset(instruction.getOffset());
+ it.add(nop);
+ it.next();
+ instruction.setOffset(instruction.getOffset() + 1);
+ }
+ // Instruction big enough for any offset.
+ }
+ }
+ } while (offsetDelta > 0);
+ return instructions;
+ }
+
+ private int rewriteIfToIfAndGoto(
+ int offsetDelta,
+ ListIterator<Instruction> it,
+ Instruction condition,
+ Instruction newCondition) {
+ int jumpOffset = condition.getOffset() + condition.getSize();
+ Goto32 jump = new Goto32(0);
+ jump.setOffset(jumpOffset);
+ newCondition.setOffset(condition.getOffset());
+ it.set(newCondition);
+ replaceTarget(condition, newCondition);
+ it.add(jump);
+ offsetDelta += jump.getSize();
+ instructionTargets.put(jump, instructionTargets.remove(condition));
+ Instruction fallthroughInstruction = it.next();
+ instructionTargets.put(newCondition, Lists.newArrayList(fallthroughInstruction));
+ it.previous();
+ return offsetDelta;
+ }
+
+ private void replaceTarget(Instruction target, Instruction newTarget) {
+ for (List<Instruction> instructions : instructionTargets.values()) {
+ instructions.replaceAll((i) -> i == target ? newTarget : i);
+ }
+ for (Int2ReferenceMap.Entry<Instruction> entry : debugEventTargets.int2ReferenceEntrySet()) {
+ if (entry.getValue() == target) {
+ entry.setValue(newTarget);
+ }
+ }
+ for (Entry<Try, TryTargets> entry : tryTargets.entrySet()) {
+ entry.getValue().replaceTarget(target, newTarget);
+ }
+ for (List<Instruction> instructions : handlerTargets.values()) {
+ instructions.replaceAll((i) -> i == target ? newTarget : i);
+ }
+ }
+
+ private void recordInstructionTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+ Instruction[] instructions = method.getCode().asDexCode().instructions;
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ Instruction target = offsetToInstruction.get(condition.getOffset() + condition.CCCC);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ Instruction target = offsetToInstruction.get(condition.getOffset() + condition.BBBB);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto32) {
+ Goto32 jump = (Goto32) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAAAAAA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Format31t offsetInstruction = (Format31t) instruction;
+ Instruction target = offsetToInstruction.get(
+ offsetInstruction.getOffset() + offsetInstruction.getPayloadOffset());
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof SwitchPayload) {
+ SwitchPayload payload = (SwitchPayload) instruction;
+ int[] targetOffsets = payload.switchTargetOffsets();
+ int switchOffset = payloadToSwitch.get(instruction).getOffset();
+ List<Instruction> targets = new ArrayList<>();
+ for (int i = 0; i < targetOffsets.length; i++) {
+ Instruction target = offsetToInstruction.get(switchOffset + targetOffsets[i]);
+ assert target != null;
+ targets.add(target);
+ }
+ instructionTargets.put(instruction, targets);
+ }
+ }
+ }
+
+ private void recordDebugEventTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+ DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+ if (debugInfo != null) {
+ int address = 0;
+ for (DexDebugEvent event : debugInfo.events) {
+ if (event instanceof AdvancePC) {
+ AdvancePC advance = (AdvancePC) event;
+ address += advance.delta;
+ Instruction target = offsetToInstruction.get(address);
+ assert target != null;
+ debugEventTargets.put(address, target);
+ } else if (event instanceof Default) {
+ Default defaultEvent = (Default) event;
+ address += defaultEvent.getPCDelta();
+ Instruction target = offsetToInstruction.get(address);
+ assert target != null;
+ debugEventTargets.put(address, target);
+ }
+ }
+ }
+ }
+
+ private void recordTryAndHandlerTargets(
+ Int2ReferenceMap<Instruction> offsetToInstruction,
+ Instruction lastInstruction) {
+ DexCode code = method.getCode().asDexCode();
+ for (Try theTry : code.tries) {
+ Instruction start = offsetToInstruction.get(theTry.startAddress);
+ int endAddress = theTry.startAddress + theTry.instructionCount;
+ TryTargets targets;
+ if (endAddress > lastInstruction.getOffset()) {
+ targets = new TryTargets(start, lastInstruction, true);
+ } else {
+ Instruction end = offsetToInstruction.get(endAddress);
+ targets = new TryTargets(start, end, false);
+ }
+ assert theTry.startAddress == targets.getStartOffset();
+ assert theTry.instructionCount == targets.getStartToEndDelta();
+ tryTargets.put(theTry, targets);
+ }
+ if (code.handlers != null) {
+ for (TryHandler handler : code.handlers) {
+ List<Instruction> targets = new ArrayList<>();
+ if (handler.catchAllAddr != NO_HANDLER) {
+ Instruction target = offsetToInstruction.get(handler.catchAllAddr);
+ assert target != null;
+ targets.add(target);
+ }
+ for (TypeAddrPair pair : handler.pairs) {
+ Instruction target = offsetToInstruction.get(pair.addr);
+ assert target != null;
+ targets.add(target);
+ }
+ handlerTargets.put(handler, targets);
+ }
+ }
+ }
+
+ private void recordTargets() {
+ Int2ReferenceMap<Instruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
+ Instruction[] instructions = method.getCode().asDexCode().instructions;
+ boolean containsPayloads = false;
+ for (Instruction instruction : instructions) {
+ offsetToInstruction.put(instruction.getOffset(), instruction);
+ if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ containsPayloads = true;
+ }
+ }
+ if (containsPayloads) {
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Instruction payload =
+ offsetToInstruction.get(instruction.getOffset() + instruction.getPayloadOffset());
+ assert payload != null;
+ payloadToSwitch.put(payload, instruction);
+ }
+ }
+ }
+ recordInstructionTargets(offsetToInstruction);
+ recordDebugEventTargets(offsetToInstruction);
+ Instruction lastInstruction = instructions[instructions.length - 1];
+ recordTryAndHandlerTargets(offsetToInstruction, lastInstruction);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index a2edd92..7915df3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -314,7 +314,7 @@
public static final int NO_INDEX = -1;
- private final int handlerOffset;
+ public final int handlerOffset;
public /* offset */ int startAddress;
public /* offset */ int instructionCount;
public int handlerIndex;
@@ -378,7 +378,7 @@
public static final int NO_HANDLER = -1;
public final TypeAddrPair[] pairs;
- public /* offset */ int catchAllAddr;
+ public final /* offset */ int catchAllAddr;
public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) {
this.pairs = pairs;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 572e655..db99359 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -31,10 +31,9 @@
public abstract void addToBuilder(DexDebugEntryBuilder builder);
-
public static class AdvancePC extends DexDebugEvent {
- final int delta;
+ public final int delta;
public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
writer.putByte(Constants.DBG_ADVANCE_PC);
@@ -350,6 +349,16 @@
builder.setPosition(address, line);
}
+ public int getPCDelta() {
+ int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+ return adjustedOpcode / Constants.DBG_LINE_RANGE;
+ }
+
+ public int getLineDelta() {
+ int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+ return Constants.DBG_LINE_BASE + (adjustedOpcode % Constants.DBG_LINE_RANGE);
+ }
+
public String toString() {
return "DEFAULT " + value;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 700e45a..e66b984 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -171,14 +171,6 @@
code = builder.build(method.getArity());
}
- // Replaces the dex code in the method by setting code to result of compiling the IR.
- public void setCode(IRCode ir, RegisterAllocator registerAllocator,
- DexItemFactory dexItemFactory, DexString firstJumboString) {
- final DexBuilder builder =
- new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
- code = builder.build(method.getArity());
- }
-
public String toString() {
return "Encoded method " + method;
}
@@ -206,6 +198,10 @@
return code;
}
+ public void setDexCode(DexCode code) {
+ this.code = code;
+ }
+
public void removeCode() {
code = null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 82db97a..bb1ff32 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import com.android.tools.r8.code.ConstStringJumbo;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -30,11 +29,7 @@
public void buildDex(DexBuilder builder) {
builder.registerStringReference(value);
int dest = builder.allocatedRegister(dest(), getNumber());
- if (builder.isJumboString(value)) {
- builder.add(this, new ConstStringJumbo(dest, value));
- } else {
- builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
- }
+ builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737dc46..da067d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -85,9 +85,6 @@
// List of generated FillArrayData dex instructions.
private final List<FillArrayDataInfo> fillArrayDataInfos = new ArrayList<>();
- // First jumbo string if known.
- private final DexString firstJumboString;
-
// Set of if instructions that have offsets that are so large that they cannot be encoded in
// the if instruction format.
private Set<BasicBlock> ifsNeedingRewrite = Sets.newIdentityHashSet();
@@ -116,18 +113,6 @@
this.ir = ir;
this.registerAllocator = registerAllocator;
this.dexItemFactory = dexItemFactory;
- this.firstJumboString = null;
- }
-
- public DexBuilder(IRCode ir, RegisterAllocator registerAllocator,
- DexItemFactory dexItemFactory, DexString firstJumboString) {
- assert ir != null;
- assert registerAllocator != null;
- assert dexItemFactory != null;
- this.ir = ir;
- this.registerAllocator = registerAllocator;
- this.dexItemFactory = dexItemFactory;
- this.firstJumboString = firstJumboString;
}
private void reset() {
@@ -143,15 +128,6 @@
nextBlock = null;
}
- public boolean isJumboString(DexString string) {
- if (firstJumboString == null) {
- return false;
- }
- // We have to use compareTo here, as slowCompareTo will return the wrong order when minification
- // is used.
- return firstJumboString.compareTo(string) <= 0;
- }
-
/**
* Build the dex instructions added to this builder.
*
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 659a503..6a4777d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -325,10 +325,6 @@
return builder.build();
}
- public void processJumboStrings(DexEncodedMethod method, DexString firstJumboString) {
- convertMethodJumboStringsOnly(method, firstJumboString);
- }
-
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -534,44 +530,6 @@
}
}
- // Convert a method ensuring that strings sorting equal or higher than the argument
- // firstJumboString are encoded as jumbo strings.
- // TODO(sgjesse): Consider replacing this with a direct dex2dex converter instead of going
- // through IR.
- private void convertMethodJumboStringsOnly(
- DexEncodedMethod method, DexString firstJumboString) {
- // This is only used for methods already converted to Dex, but missing jumbo strings.
- assert method.getCode() != null && method.getCode().isDexCode();
- if (options.verbose) {
- System.out.println("Processing jumbo strings: " + method.toSourceString());
- }
- if (Log.ENABLED) {
- Log.debug(getClass(), "Original code for %s:\n%s",
- method.toSourceString(), logCode(options, method));
- }
- IRCode code = method.buildIR(options);
- if (Log.ENABLED) {
- Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s",
- method.toSourceString(), code);
- }
- // Compilation header if printing CFGs for this method.
- printC1VisualizerHeader(method);
- printMethod(code, "Initial IR (SSA)");
-
- // Methods passed through here should have been through IR processing already and
- // therefore, we skip most of the IR processing.
-
- // Perform register allocation.
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, appInfo.dexItemFactory, firstJumboString);
-
- if (Log.ENABLED) {
- Log.debug(getClass(), "Resulting dex code for %s:\n%s",
- method.toSourceString(), logCode(options, method));
- }
- printMethod(code, "Final IR (non-SSA)");
- }
-
private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
new file mode 100644
index 0000000..98f1071
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2017, 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.dex;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class JumboStringProcessing {
+
+ @Test
+ public void branching() {
+ DexItemFactory factory = new DexItemFactory();
+ DexString string = factory.createString("turn into jumbo");
+ factory.sort(NamingLens.getIdentityLens());
+ Instruction[] instructions = buildInstructions(string, false);
+ DexCode code = jumboStringProcess(factory, string, instructions);
+ Instruction[] rewrittenInstructions = code.instructions;
+ assert rewrittenInstructions[1] instanceof IfEq;
+ IfEq condition = (IfEq) rewrittenInstructions[1];
+ assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset();
+ assert rewrittenInstructions[2] instanceof Goto32;
+ Goto32 jump = (Goto32) rewrittenInstructions[2];
+ Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+ assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+ }
+
+ @Test
+ public void branching2() {
+ DexItemFactory factory = new DexItemFactory();
+ DexString string = factory.createString("turn into jumbo");
+ factory.sort(NamingLens.getIdentityLens());
+ Instruction[] instructions = buildInstructions(string, true);
+ DexCode code = jumboStringProcess(factory, string, instructions);
+ Instruction[] rewrittenInstructions = code.instructions;
+ assert rewrittenInstructions[1] instanceof IfEqz;
+ IfEqz condition = (IfEqz) rewrittenInstructions[1];
+ assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset();
+ assert rewrittenInstructions[2] instanceof Goto32;
+ Goto32 jump = (Goto32) rewrittenInstructions[2];
+ Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+ assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+ }
+
+ private Instruction[] buildInstructions(DexString string, boolean zeroCondition) {
+ List<Instruction> instructions = new ArrayList<>();
+ int offset = 0;
+ Instruction instr = new Const4(0, 0);
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ int lastInstructionOffset = 15000 * 2 + 2 + offset;
+ if (zeroCondition) {
+ instr = new IfNez(0, lastInstructionOffset - offset);
+ } else {
+ instr = new IfNe(0, 0, lastInstructionOffset - offset);
+ }
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ for (int i = 0; i < 15000; i++) {
+ instr = new ConstString(0, string);
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ }
+ instr = new ReturnVoid();
+ instr.setOffset(offset);
+ instructions.add(instr);
+ assert instr.getOffset() == lastInstructionOffset;
+ return instructions.toArray(new Instruction[instructions.size()]);
+ }
+
+ private DexCode jumboStringProcess(
+ DexItemFactory factory, DexString string, Instruction[] instructions) {
+ DexCode code = new DexCode(
+ 1,
+ 0,
+ 0,
+ instructions,
+ new Try[0],
+ null,
+ null,
+ null);
+ DexAccessFlags flags = new DexAccessFlags(0);
+ flags.setPublic();
+ DexEncodedMethod method = new DexEncodedMethod(null, flags, null, null, code);
+ new JumboStringRewriter(method, string, factory).rewrite();
+ return method.getCode().asDexCode();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
index d0ade31..61b624e 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -16,7 +16,6 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
-import org.junit.Ignore;
import org.junit.Test;
public class JumboStringTests extends JasminTestBase {
@@ -30,7 +29,6 @@
EXTRA_STRINGS_PER_CLASSES_COUNT + MIN_STRING_COUNT / CLASSES_COUNT;
@Test
- @Ignore("b/35701208")
public void test() throws Exception {
JasminBuilder builder = new JasminBuilder();
LinkedHashMap<String, MethodSignature> classes = new LinkedHashMap<>(CLASSES_COUNT);