Merge "Handle default methods properly during tree shaking."
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
new file mode 100644
index 0000000..2207e6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -0,0 +1,21 @@
+// 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.cf.code;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfGoto extends CfInstruction {
+
+ private final CfLabel target;
+
+ public CfGoto(CfLabel target) {
+ this.target = target;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
new file mode 100644
index 0000000..b8397e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -0,0 +1,47 @@
+// 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.cf.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfIf extends CfInstruction {
+
+ private final If.Type kind;
+ private final ValueType type;
+ private final CfLabel target;
+
+ public CfIf(If.Type kind, ValueType type, CfLabel target) {
+ this.kind = kind;
+ this.type = type;
+ this.target = target;
+ }
+
+ private int getOpcode() {
+ switch (kind) {
+ case EQ:
+ return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
+ case GE:
+ return Opcodes.IFGE;
+ case GT:
+ return Opcodes.IFGT;
+ case LE:
+ return Opcodes.IFLE;
+ case LT:
+ return Opcodes.IFLT;
+ case NE:
+ return type.isObject() ? Opcodes.IFNONNULL : Opcodes.IFNE;
+ default:
+ throw new Unreachable("Unexpected type " + type);
+ }
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ visitor.visitJumpInsn(getOpcode(), target.getLabel());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
new file mode 100644
index 0000000..9c6560d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -0,0 +1,24 @@
+// 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.cf.code;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLabel extends CfInstruction {
+
+ private Label label = null;
+
+ public Label getLabel() {
+ if (label == null) {
+ label = new Label();
+ }
+ return label;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ visitor.visitLabel(getLabel());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
new file mode 100644
index 0000000..a0d1f6a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -0,0 +1,32 @@
+// 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.cf.code;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfTryCatch {
+ public final CfLabel start;
+ public final CfLabel end;
+ public final List<DexType> guards;
+ public final List<CfLabel> targets;
+
+ public CfTryCatch(
+ CfLabel start,
+ CfLabel end,
+ CatchHandlers<BasicBlock> handlers,
+ CfBuilder builder) {
+ this.start = start;
+ this.end = end;
+ guards = handlers.getGuards();
+ targets = new ArrayList<>(handlers.getAllTargets().size());
+ for (BasicBlock block : handlers.getAllTargets()) {
+ targets.add(builder.getLabel(block));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index e1f9c63..7c76969 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,32 @@
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
+import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
public class CfCode extends Code {
private final int maxStack;
private final int maxLocals;
private final List<CfInstruction> instructions;
+ private final List<CfTryCatch> tryCatchRanges;
- public CfCode(int maxStack, int maxLocals, List<CfInstruction> instructions) {
+ public CfCode(
+ int maxStack,
+ int maxLocals,
+ List<CfInstruction> instructions,
+ List<CfTryCatch> tryCatchRanges) {
this.maxStack = maxStack;
this.maxLocals = maxLocals;
this.instructions = instructions;
+ this.tryCatchRanges = tryCatchRanges;
}
@Override
@@ -40,6 +49,21 @@
}
visitor.visitEnd();
visitor.visitMaxs(maxStack, maxLocals);
+ for (CfTryCatch tryCatch : tryCatchRanges) {
+ Label start = tryCatch.start.getLabel();
+ Label end = tryCatch.end.getLabel();
+ for (int i = 0; i < tryCatch.guards.size(); i++) {
+ DexType guard = tryCatch.guards.get(i);
+ Label target = tryCatch.targets.get(i).getLabel();
+ visitor.visitTryCatchBlock(
+ start,
+ end,
+ target,
+ guard == DexItemFactory.catchAllType
+ ? null
+ : Type.getType(guard.toDescriptorString()).getInternalName());
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index eef666b..4b88706 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import java.util.List;
@@ -94,4 +97,14 @@
public Goto asGoto() {
return this;
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfGoto(builder.getLabel(getTarget())));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 95dd4ba..934d3db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -402,7 +402,8 @@
assert instruction.isDebugInstruction()
|| instruction.isJumpInstruction()
|| instruction.isConstInstruction()
- || instruction.isNewArrayFilledData();
+ || instruction.isNewArrayFilledData()
+ || instruction.isStore();
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index d2e16f6..8cdf209 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -6,7 +6,10 @@
import static com.android.tools.r8.dex.Constants.U4BIT_MAX;
import static com.android.tools.r8.dex.Constants.U8BIT_MAX;
+import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import java.util.List;
@@ -186,4 +189,15 @@
public If asIf() {
return this;
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ stack.loadInValues(this, it);
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ assert inValues.size() == 1;
+ builder.add(new CfIf(type, inValues.get(0).type, builder.getLabel(getTrueTarget())));
+ }
}
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 cdcf070..eeee6f8 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
@@ -938,6 +938,22 @@
return null;
}
+ public boolean isStore() {
+ return false;
+ }
+
+ public Store asStore() {
+ return null;
+ }
+
+ public boolean isLoad() {
+ return false;
+ }
+
+ public Load asLoad() {
+ return null;
+ }
+
public boolean canBeFolded() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 734b2dd..401a880 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class Load extends Instruction {
@@ -17,6 +18,16 @@
}
@Override
+ public boolean isLoad() {
+ return true;
+ }
+
+ @Override
+ public Load asLoad() {
+ return this;
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return true;
}
@@ -46,4 +57,9 @@
Value value = inValues.get(0);
builder.add(new CfLoad(value.outType(), builder.getLocalRegister(value)));
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ // Nothing to do. This is only hit because loads and stores are insert for phis.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 0f72182..2b419fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
@@ -61,7 +63,7 @@
@Override
public boolean canBeDeadCode(IRCode code, InternalOptions options) {
- return !options.debug;
+ return !options.debug && !options.outputClassFiles;
}
@Override
@@ -69,4 +71,18 @@
// TODO(64432527): Revisit this constraint.
return Constraint.NEVER;
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ if (outValue.isUsed()) {
+ stack.storeOutValue(this, it);
+ } else {
+ stack.popOutValue(outValue.type, this, it);
+ }
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ // Nothing to do. The exception is implicitly pushed on the stack.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 33d5557..7dd62ae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.utils.InternalOptions;
public class Pop extends Instruction {
@@ -45,4 +46,10 @@
public void buildCf(CfBuilder builder) {
builder.add(new CfPop(inValues.get(0).type));
}
+
+ @Override
+ public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+ // Pop cannot be dead code as it modifies the stack height.
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index f670621..3bf1417 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -5,12 +5,16 @@
public class StackValue extends Value {
- public StackValue(ValueType type) {
+ private final int height;
+
+ public StackValue(ValueType type, int height) {
super(Value.UNDEFINED_NUMBER, type, null);
+ this.height = height;
+ assert height >= 0;
}
@Override
public String toString() {
- return "s";
+ return "s" + height;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ddc4f50..bc1e419 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -8,7 +8,10 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
+import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.utils.InternalOptions;
public class Store extends Instruction {
@@ -17,6 +20,16 @@
}
@Override
+ public boolean isStore() {
+ return true;
+ }
+
+ @Override
+ public Store asStore() {
+ return this;
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return true;
}
@@ -45,4 +58,14 @@
public void buildCf(CfBuilder builder) {
builder.add(new CfStore(outType(), builder.getLocalRegister(outValue)));
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ // Nothing to do. This is only hit because loads and stores are insert for phis.
+ }
+
+ @Override
+ public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+ return !(outValue instanceof FixedLocal);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index aed20acb..f07b61e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.CfCode;
@@ -11,6 +13,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -20,7 +23,9 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Load;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Pop;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Store;
import com.android.tools.r8.ir.code.Value;
@@ -28,43 +33,64 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.utils.InternalOptions;
-import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
public class CfBuilder {
private final DexEncodedMethod method;
private final IRCode code;
+
+ private int maxLocals = -1;
+ private int maxStack = 0;
+ private int currentStack = 0;
private List<CfInstruction> instructions;
- private Reference2IntMap<Value> argumentRegisters;
- private int maxLocals;
+ private Reference2IntMap<Value> registers;
+ private Map<BasicBlock, CfLabel> labels;
+
+ /**
+ * Value that represents a shared physical location defined by the phi value.
+ *
+ * This value is introduced to represent the store instructions used to unify the location of
+ * in-flowing values to phi's. After introducing this fixed location the graph is no longer in
+ * SSA since the fixed location signifies a place that can be written to from multiple places.
+ */
+ public static class FixedLocal extends Value {
+
+ private final Phi phi;
+
+ public FixedLocal(Phi phi) {
+ super(phi.getNumber(), phi.outType(), phi.getLocalInfo());
+ this.phi = phi;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "fixed:v" + phi.getNumber();
+ }
+ }
public static class StackHelper {
+ private int currentStackHeight = 0;
+
public void loadInValues(Instruction instruction, InstructionListIterator it) {
+ int topOfStack = currentStackHeight;
it.previous();
- for (int i = 0; i < instruction.inValues().size(); i++) {
- Value value = instruction.inValues().get(i);
- StackValue stackValue = new StackValue(value.outType());
- Instruction load;
- if (value.isConstant()) {
- ConstInstruction constant = value.getConstInstruction();
- if (constant.isConstNumber()) {
- load = new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
- } else if (constant.isConstString()) {
- load = new ConstString(stackValue, constant.asConstString().getValue());
- } else if (constant.isConstClass()) {
- load = new ConstClass(stackValue, constant.asConstClass().getValue());
- } else {
- throw new Unreachable("Unexpected constant value: " + value);
- }
- } else {
- load = new Load(stackValue, value);
- }
- add(load, instruction, it);
+ for (Value value : instruction.inValues()) {
+ StackValue stackValue = new StackValue(value.outType(), topOfStack++);
+ add(load(stackValue, value), instruction, it);
value.removeUser(instruction);
instruction.replaceValue(value, stackValue);
}
@@ -72,26 +98,61 @@
}
public void storeOutValue(Instruction instruction, InstructionListIterator it) {
- if (instruction.isOutConstant()) {
+ if (instruction.outValue() instanceof StackValue) {
+ assert instruction.isConstInstruction();
return;
}
- StackValue newOutValue = new StackValue(instruction.outType());
+ StackValue newOutValue = new StackValue(instruction.outType(), currentStackHeight);
Value oldOutValue = instruction.swapOutValue(newOutValue);
add(new Store(oldOutValue, newOutValue), instruction, it);
}
public void popOutValue(ValueType type, Instruction instruction, InstructionListIterator it) {
- StackValue newOutValue = new StackValue(type);
+ StackValue newOutValue = new StackValue(type, currentStackHeight);
instruction.swapOutValue(newOutValue);
add(new Pop(newOutValue), instruction, it);
}
+ public void movePhi(Phi phi, Value value, InstructionListIterator it) {
+ StackValue tmp = new StackValue(phi.outType(), currentStackHeight);
+ FixedLocal out = new FixedLocal(phi);
+ add(load(tmp, value), phi.getBlock(), Position.none(), it);
+ add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
+ value.removePhiUser(phi);
+ phi.replaceUsers(out);
+ }
+
+ private Instruction load(StackValue stackValue, Value value) {
+ if (value.isConstant()) {
+ ConstInstruction constant = value.getConstInstruction();
+ if (constant.isConstNumber()) {
+ return new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
+ } else if (constant.isConstString()) {
+ return new ConstString(stackValue, constant.asConstString().getValue());
+ } else if (constant.isConstClass()) {
+ return new ConstClass(stackValue, constant.asConstClass().getValue());
+ } else {
+ throw new Unreachable("Unexpected constant value: " + value);
+ }
+ }
+ return new Load(stackValue, value);
+ }
+
private static void add(
Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
- newInstruction.setBlock(existingInstruction.getBlock());
- newInstruction.setPosition(existingInstruction.getPosition());
+ add(newInstruction, existingInstruction.getBlock(), existingInstruction.getPosition(), it);
+ }
+
+ private static void add(
+ Instruction newInstruction,
+ BasicBlock block,
+ Position position,
+ InstructionListIterator it) {
+ newInstruction.setBlock(block);
+ newInstruction.setPosition(position);
it.add(newInstruction);
}
+
}
public CfBuilder(DexEncodedMethod method, IRCode code) {
@@ -101,6 +162,7 @@
public Code build(CodeRewriter rewriter, InternalOptions options) {
try {
+ splitExceptionalBlocks();
loadStoreInsertion();
DeadCodeRemover.removeDeadCode(code, rewriter, options);
removeUnneededLoadsAndStores();
@@ -113,8 +175,55 @@
}
}
+ // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
+ // instructions that might define values prior to the throwing exception are excluded from the
+ // try-catch range. Failure to do so will result in code that does not verify on the JVM.
+ private void splitExceptionalBlocks() {
+ ListIterator<BasicBlock> it = code.listIterator();
+ while (it.hasNext()) {
+ BasicBlock block = it.next();
+ if (!block.hasCatchHandlers()) {
+ continue;
+ }
+ int size = block.getInstructions().size();
+ boolean isThrow = block.exit().isThrow();
+ if ((isThrow && size == 1) || (!isThrow && size == 2)) {
+ // Fast-path to avoid processing blocks with just a single throwing instruction.
+ continue;
+ }
+ InstructionListIterator instructions = block.listIterator();
+ boolean hasOutValues = false;
+ while (instructions.hasNext()) {
+ Instruction instruction = instructions.next();
+ if (instruction.instructionTypeCanThrow()) {
+ break;
+ }
+ hasOutValues |= instruction.outValue() != null;
+ }
+ if (hasOutValues) {
+ instructions.previous();
+ instructions.split(code, it);
+ }
+ }
+ }
+
private void loadStoreInsertion() {
StackHelper stack = new StackHelper();
+ // Insert phi stores in all predecessors.
+ for (BasicBlock block : code.blocks) {
+ if (!block.getPhis().isEmpty()) {
+ for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
+ BasicBlock pred = block.getPredecessors().get(predIndex);
+ for (Phi phi : block.getPhis()) {
+ Value value = phi.getOperand(predIndex);
+ InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+ it.previous();
+ stack.movePhi(phi, value, it);
+ }
+ }
+ }
+ }
+ // Insert per-instruction loads and stores.
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator();
while (it.hasNext()) {
@@ -131,6 +240,10 @@
InstructionListIterator it = block.listIterator();
while (it.hasNext()) {
Instruction store = it.next();
+ // Eliminate unneeded loads of stores:
+ // v <- store si
+ // si <- load v
+ // where |users(v)| == 1 (ie, the load is the only user)
if (store instanceof Store && store.outValue().numberOfAllUsers() == 1) {
Instruction load = it.peekNext();
if (load instanceof Load && load.inValues().get(0) == store.outValue()) {
@@ -154,60 +267,115 @@
private void allocateLocalRegisters() {
// TODO(zerny): Allocate locals based on live ranges.
InstructionIterator it = code.instructionIterator();
- argumentRegisters = new Reference2IntArrayMap<>(
- method.method.proto.parameters.values.length + (method.accessFlags.isStrict() ? 0 : 1));
- int argumentRegister = 0;
- int maxRegister = -1;
+ registers = new Reference2IntOpenHashMap<>();
+ int nextFreeRegister = 0;
while (it.hasNext()) {
Instruction instruction = it.next();
- if (instruction.isArgument()) {
- argumentRegisters.put(instruction.outValue(), argumentRegister);
- argumentRegister += instruction.outValue().requiredRegisters();
- maxRegister = argumentRegister - 1;
- } else if (instruction.outValue() != null
- && !(instruction.outValue() instanceof StackValue)) {
- maxRegister = Math.max(maxRegister, 2 * instruction.outValue().getNumber());
+ Value outValue = instruction.outValue();
+ if (outValue instanceof FixedLocal) {
+ // Phi stores are marked by a "fixed-local" value which share the same local index.
+ FixedLocal fixed = (FixedLocal) outValue;
+ if (!registers.containsKey(fixed.phi)) {
+ registers.put(fixed.phi, nextFreeRegister);
+ nextFreeRegister += fixed.requiredRegisters();
+ }
+ } else if (outValue != null && !(outValue instanceof StackValue)) {
+ registers.put(instruction.outValue(), nextFreeRegister);
+ nextFreeRegister += instruction.outValue().requiredRegisters();
}
}
- maxLocals = maxRegister + 1;
+ maxLocals = nextFreeRegister;
+ }
+
+ private void push(Value value) {
+ assert value instanceof StackValue;
+ currentStack += value.requiredRegisters();
+ maxStack = Math.max(maxStack, currentStack);
+ }
+
+ private void pop(Value value) {
+ assert value instanceof StackValue;
+ currentStack -= value.requiredRegisters();
}
private CfCode buildCfCode() {
- int maxStack = 0;
- int currentStack = 0;
+ List<CfTryCatch> tryCatchRanges = new ArrayList<>();
+ labels = new HashMap<>(code.blocks.size());
instructions = new ArrayList<>();
Iterator<BasicBlock> blockIterator = code.listIterator();
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- InstructionIterator it = block.iterator();
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.outValue() != null) {
- Value outValue = instruction.outValue();
- if (outValue instanceof StackValue) {
- currentStack += outValue.requiredRegisters();
- maxStack = Math.max(maxStack, currentStack);
+ BasicBlock block = blockIterator.next();
+ CfLabel tryCatchStart = null;
+ CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
+ do {
+ CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
+ if (!tryCatchHandlers.equals(handlers)) {
+ if (!tryCatchHandlers.isEmpty()) {
+ // Close try-catch and save the range.
+ CfLabel tryCatchEnd = getLabel(block);
+ tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
+ if (instructions.get(instructions.size() - 1) != tryCatchEnd) {
+ instructions.add(tryCatchEnd);
}
}
- for (Value inValue : instruction.inValues()) {
- if (inValue instanceof StackValue) {
- currentStack -= inValue.requiredRegisters();
+ if (!handlers.isEmpty()) {
+ // Open a try-catch.
+ tryCatchStart = getLabel(block);
+ if (instructions.isEmpty()
+ || instructions.get(instructions.size() - 1) != tryCatchStart) {
+ instructions.add(tryCatchStart);
}
}
- instruction.buildCf(this);
+ tryCatchHandlers = handlers;
}
- }
+ BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
+ buildCfInstructions(block, nextBlock);
+ block = nextBlock;
+ } while (block != null);
assert currentStack == 0;
- return new CfCode(maxStack, maxLocals, instructions);
+ return new CfCode(maxStack, maxLocals, instructions, tryCatchRanges);
+ }
+
+ private void buildCfInstructions(BasicBlock block, BasicBlock nextBlock) {
+ boolean fallthrough = false;
+ InstructionIterator it = block.iterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock) {
+ fallthrough = true;
+ continue;
+ }
+ for (Value inValue : instruction.inValues()) {
+ if (inValue instanceof StackValue) {
+ pop(inValue);
+ }
+ }
+ if (instruction.outValue() != null) {
+ Value outValue = instruction.outValue();
+ if (outValue instanceof StackValue) {
+ push(outValue);
+ }
+ }
+ instruction.buildCf(this);
+ }
+ if (nextBlock == null || (fallthrough && nextBlock.getPredecessors().size() == 1)) {
+ return;
+ }
+ instructions.add(getLabel(nextBlock));
}
// Callbacks
+ public CfLabel getLabel(BasicBlock target){
+ return labels.computeIfAbsent(target, (block) -> new CfLabel());
+ }
+
public int getLocalRegister(Value value) {
- if (value.isArgument()) {
- return argumentRegisters.getInt(value);
+ if (value instanceof FixedLocal) {
+ // Phi stores are marked by a "fixed-local" value which share the same local index.
+ FixedLocal fixed = (FixedLocal) value;
+ return registers.getInt(fixed.phi);
}
- return 2 * value.getNumber();
+ return registers.getInt(value);
}
public void add(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a1c529a..8b83a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -710,6 +710,9 @@
// Replace result uses for methods where something is known about what is returned.
public void rewriteMoveResult(IRCode code) {
+ if (options.outputClassFiles) {
+ return;
+ }
AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 5783ad4..55ecb45 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.And;
import com.android.tools.r8.ir.code.ArithmeticBinop;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -751,6 +752,7 @@
while (!unhandled.isEmpty()) {
LiveIntervals unhandledInterval = unhandled.poll();
+ setHintForDestRegOfCheckCast(unhandledInterval);
setHintToPromote2AddrInstruction(unhandledInterval);
// If this interval value is the src of an argument move. Fix the registers for the
@@ -804,6 +806,20 @@
return true;
}
+ private void setHintForDestRegOfCheckCast(LiveIntervals unhandledInterval) {
+ if (unhandledInterval.getHint() == null &&
+ unhandledInterval.getValue().definition instanceof CheckCast) {
+ CheckCast checkcast = unhandledInterval.getValue().definition.asCheckCast();
+ Value checkcastInput = checkcast.inValues().get(0);
+ assert checkcastInput != null;
+ if (checkcastInput.getLiveIntervals() != null &&
+ !checkcastInput.getLiveIntervals().overlaps(unhandledInterval) &&
+ checkcastInput.getLocalInfo() == unhandledInterval.getValue().definition.getLocalInfo()) {
+ unhandledInterval.setHint(checkcastInput.getLiveIntervals());
+ }
+ }
+ }
+
/*
* This method tries to promote arithmetic binary instruction to use the 2Addr form.
* To achieve this goal the output interval of the binary instruction is set with an hint
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 3509357..b2760e4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -57,27 +57,15 @@
}
}
+ // TODO(zerny): Implement stack-map computation to support Java 1.6 and above.
+ private int downgrade(int version) {
+ return version > 49 ? 49 : version;
+ }
+
private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
- // If there are CF representations for all methods manually compute stack height and frames.
- // TODO(zerny): This is a temporary hack since we will need to manually compute stack maps for
- // any methods that are IR processed.
- int flags = 0;
- for (DexEncodedMethod method : clazz.directMethods()) {
- if (!method.getCode().isCfCode()) {
- flags = ClassWriter.COMPUTE_FRAMES;
- break;
- }
- }
- if (flags == 0) {
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- if (!method.getCode().isCfCode()) {
- flags = ClassWriter.COMPUTE_FRAMES;
- }
- }
- }
- ClassWriter writer = new ClassWriter(flags);
+ ClassWriter writer = new ClassWriter(0);
writer.visitSource(clazz.sourceFile.toString(), null);
- int version = clazz.getClassFileVersion();
+ int version = downgrade(clazz.getClassFileVersion());
int access = clazz.accessFlags.getAsCfAccessFlags();
String desc = clazz.type.toDescriptorString();
String name = internalName(clazz.type);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 0fc2489..c79719a 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -39,6 +39,8 @@
@RunWith(Parameterized.class)
public class R8RunExamplesTest {
+ private static final boolean ONLY_RUN_CF_TESTS = false;
+
enum Input {
DX, JAVAC, JAVAC_ALL, JAVAC_NONE
}
@@ -125,22 +127,25 @@
"constants.Constants",
"hello.Hello",
"arithmetic.Arithmetic",
+ "barray.BArray",
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
- for (String test : tests) {
- fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
- fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
- test));
- fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
- test));
- fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
- test));
- fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
- test));
- fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
- test));
- fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+ if (!ONLY_RUN_CF_TESTS) {
+ for (String test : tests) {
+ fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
+ fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
+ test));
+ fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
+ test));
+ fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
+ test));
+ fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
+ test));
+ fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
+ test));
+ fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+ }
}
// TODO(zerny): Once all tests pass create the java tests in the main test loop.
for (String test : javaBytecodeTests) {
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 5dc1e75..162db06 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -444,6 +444,6 @@
// TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
// expectation might need changing with other optimizations.
// TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
- assertEquals(27, code.instructions.length);
+ assertEquals(26, code.instructions.length);
}
}