Merge "Keep static members matched by -keepclassmembers on kept classes"
diff --git a/build.gradle b/build.gradle
index 61360dc..c0a183b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -77,6 +77,12 @@
]
}
}
+ apiUsageSample {
+ java {
+ srcDirs = ['src/test/apiUsageSample']
+ }
+ output.resourcesDir = 'build/classes/apiUsageSample'
+ }
debugTestResources {
java {
srcDirs = ['src/test/debugTestResources']
@@ -191,6 +197,7 @@
supportLibs 'com.android.support:support-v4:25.4.0'
supportLibs 'junit:junit:4.12'
supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+ apiUsageSampleCompile sourceSets.main.output
debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.4-3'
apt 'com.google.auto.value:auto-value:1.5'
}
@@ -630,6 +637,12 @@
dependsOn createJctfTests
}
+task buildApiUsageSample(type: Jar) {
+ from sourceSets.apiUsageSample.output
+ baseName 'api_usage_sample'
+ destinationDir file('tests')
+}
+
task buildDebugInfoExamplesDex {
def examplesDir = file("src/test/java")
def hostJar = "debuginfo_examples.jar"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a9f3329..18096f4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
public class R8 {
@@ -106,51 +105,6 @@
}
}
- static DexApplication optimize(
- DexApplication application,
- AppInfoWithSubtyping appInfo,
- InternalOptions options)
- throws ApiLevelException, ExecutionException, IOException {
- return new R8(options).optimize(application, appInfo);
- }
-
- private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
- throws IOException, ApiLevelException, ExecutionException {
- return optimize(application, appInfo, GraphLense.getIdentityLense(),
- Executors.newSingleThreadExecutor());
- }
-
- private DexApplication optimize(
- DexApplication application,
- AppInfoWithSubtyping appInfo,
- GraphLense graphLense,
- ExecutorService executorService)
- throws IOException, ApiLevelException, ExecutionException {
- final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
-
- timing.begin("Create IR");
- try {
- IRConverter converter = new IRConverter(
- appInfo, options, timing, printer, graphLense);
- application = converter.optimize(application, executorService);
- } finally {
- timing.end();
- }
-
- if (options.printCfg) {
- if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
- System.out.print(printer.toString());
- } else {
- try (OutputStreamWriter writer = new OutputStreamWriter(
- new FileOutputStream(options.printCfgFile),
- StandardCharsets.UTF_8)) {
- writer.write(printer.toString());
- }
- }
- }
- return application;
- }
-
private Set<DexType> filterMissingClasses(Set<DexType> missingClasses,
ProguardClassFilter dontWarnPatterns) {
Set<DexType> result = new HashSet<>(missingClasses);
@@ -278,7 +232,26 @@
graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withSubtyping()).run();
- application = optimize(application, appInfo, graphLense, executorService);
+ timing.begin("Create IR");
+ CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
+ try {
+ IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense);
+ application = converter.optimize(application, executorService);
+ } finally {
+ timing.end();
+ }
+
+ if (options.printCfg) {
+ if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
+ System.out.print(printer.toString());
+ } else {
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(options.printCfgFile),
+ StandardCharsets.UTF_8)) {
+ writer.write(printer.toString());
+ }
+ }
+ }
// Overwrite SourceFile if specified. This step should be done after IR conversion.
timing.begin("Rename SourceFile");
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
new file mode 100644
index 0000000..6242e9b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -0,0 +1,162 @@
+// 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;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.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;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class LoadStoreHelper {
+
+ private final IRCode code;
+ private final Map<Value, DexType> types;
+
+ public LoadStoreHelper(IRCode code, Map<Value, DexType> types) {
+ this.code = code;
+ this.types = types;
+ }
+
+ public void insertLoadsAndStores() {
+ // 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);
+ List<Phi> phis = block.getPhis();
+ List<Value> values = new ArrayList<>(phis.size());
+ for (Phi phi : phis) {
+ values.add(phi.getOperand(predIndex));
+ }
+ InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+ it.previous();
+ movePhis(phis, values, it);
+ }
+ }
+ }
+ // Insert per-instruction loads and stores.
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction current = it.next();
+ current.insertLoadAndStores(it, this);
+ }
+ }
+ }
+
+ private StackValue createStackValue(Value value, int height) {
+ if (value.outType().isObject()) {
+ return StackValue.forObjectType(types.get(value), height);
+ }
+ return StackValue.forNonObjectType(value.outType(), height);
+ }
+
+ private StackValue createStackValue(DexType type, int height) {
+ if (type.isPrimitiveType()) {
+ return StackValue.forNonObjectType(ValueType.fromDexType(type), height);
+ }
+ return StackValue.forObjectType(type, height);
+ }
+
+ public void loadInValues(Instruction instruction, InstructionListIterator it) {
+ int topOfStack = 0;
+ it.previous();
+ for (Value value : instruction.inValues()) {
+ StackValue stackValue = createStackValue(value, topOfStack++);
+ add(load(stackValue, value), instruction, it);
+ value.removeUser(instruction);
+ instruction.replaceValue(value, stackValue);
+ }
+ it.next();
+ }
+
+ public void storeOutValue(Instruction instruction, InstructionListIterator it) {
+ if (instruction.outValue() instanceof StackValue) {
+ assert instruction.isConstInstruction();
+ return;
+ }
+ StackValue newOutValue = createStackValue(instruction.outValue(), 0);
+ Value oldOutValue = instruction.swapOutValue(newOutValue);
+ add(new Store(oldOutValue, newOutValue), instruction, it);
+ }
+
+ public void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
+ StackValue newOutValue = createStackValue(value, 0);
+ instruction.swapOutValue(newOutValue);
+ add(new Pop(newOutValue), instruction, it);
+ }
+
+ public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
+ StackValue newOutValue = createStackValue(type, 0);
+ instruction.swapOutValue(newOutValue);
+ add(new Pop(newOutValue), instruction, it);
+ }
+
+ public void movePhis(List<Phi> phis, List<Value> values, InstructionListIterator it) {
+ // TODO(zerny): Accounting for non-interfering phis would lower the max stack size.
+ int topOfStack = 0;
+ List<StackValue> temps = new ArrayList<>(phis.size());
+ for (int i = 0; i < phis.size(); i++) {
+ Phi phi = phis.get(i);
+ Value value = values.get(i);
+ StackValue tmp = createStackValue(phi, topOfStack++);
+ add(load(tmp, value), phi.getBlock(), Position.none(), it);
+ temps.add(tmp);
+ value.removePhiUser(phi);
+ }
+ for (int i = phis.size() - 1; i >= 0; i--) {
+ Phi phi = phis.get(i);
+ StackValue tmp = temps.get(i);
+ FixedLocal out = new FixedLocal(phi);
+ add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
+ 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) {
+ 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);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
new file mode 100644
index 0000000..d22ff65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -0,0 +1,119 @@
+// 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;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+// Compute the types of all reference values.
+// The actual types are needed to emit stack-map frames for Java 1.6 and above.
+public class TypeVerificationHelper {
+
+ private final IRCode code;
+ private final DexItemFactory factory;
+
+ private Map<Value, DexType> types;
+
+ public TypeVerificationHelper(IRCode code, DexItemFactory factory) {
+ this.code = code;
+ this.factory = factory;
+ }
+
+ public DexItemFactory getFactory() {
+ return factory;
+ }
+
+ public DexType getType(Value value) {
+ assert value.outType().isObject();
+ return types.get(value);
+ }
+
+ // Helper to compute the join of a set of reference types.
+ public DexType join(Set<DexType> types) {
+ if (types.isEmpty()) {
+ return null; // Bottom element. Unknown type.
+ }
+ if (types.size() != 1) {
+ throw new Unimplemented("compute the join of the types");
+ }
+ return types.iterator().next();
+ }
+
+ public Map<Value, DexType> computeVerificationTypes() {
+ types = new HashMap<>();
+ Set<Value> worklist = new HashSet<>();
+ {
+ InstructionIterator it = code.instructionIterator();
+ Instruction instruction = null;
+ // Set the out-value types of each argument based on the method signature.
+ int argumentIndex = code.method.accessFlags.isStatic() ? 0 : -1;
+ while (it.hasNext()) {
+ instruction = it.next();
+ if (!instruction.isArgument()) {
+ break;
+ }
+ DexType argumentType =
+ (argumentIndex < 0)
+ ? code.method.method.getHolder()
+ : code.method.method.proto.parameters.values[argumentIndex];
+ Value outValue = instruction.outValue();
+ types.put(outValue, argumentType);
+ addUsers(outValue, worklist);
+ ++argumentIndex;
+ }
+ // Compute the out-value type of each normal instruction with an out-value but no in values.
+ while (instruction != null) {
+ assert !instruction.isArgument();
+ if (instruction.outValue() != null && instruction.outType().isObject()) {
+ Value outValue = instruction.outValue();
+ if (instruction.hasInvariantVerificationType()) {
+ DexType type = instruction.computeVerificationType(this);
+ assert type != null;
+ types.put(outValue, type);
+ addUsers(outValue, worklist);
+ }
+ }
+ instruction = it.hasNext() ? it.next() : null;
+ }
+ }
+ // Compute the fixed-point of all the out-value types.
+ while (!worklist.isEmpty()) {
+ Value item = worklist.iterator().next();
+ worklist.remove(item);
+ DexType previousType = types.get(item);
+ DexType refinedType = computeVerificationType(item);
+ if (previousType != refinedType) {
+ types.put(item, refinedType);
+ addUsers(item, worklist);
+ }
+ }
+ return types;
+ }
+
+ private DexType computeVerificationType(Value value) {
+ return value.isPhi()
+ ? value.asPhi().computeVerificationType(this)
+ : value.definition.computeVerificationType(this);
+ }
+
+ private static void addUsers(Value value, Set<Value> worklist) {
+ worklist.addAll(value.uniquePhiUsers());
+ for (Instruction instruction : value.uniqueUsers()) {
+ if (instruction.outValue() != null
+ && instruction.outType().isObject()
+ && !instruction.hasInvariantVerificationType()) {
+ worklist.add(instruction.outValue());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
new file mode 100644
index 0000000..78a350d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -0,0 +1,66 @@
+// 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 static org.objectweb.asm.Opcodes.F_NEW;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class CfFrame extends CfInstruction {
+
+ private final Int2ReferenceSortedMap<DexType> locals;
+
+ public CfFrame(Int2ReferenceSortedMap<DexType> locals) {
+ this.locals = locals;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor) {
+ int type = F_NEW;
+ int localsSize = locals.size();
+ Object[] localsCopy = new Object[localsSize];
+ int localIndex = 0;
+ for (Entry<DexType> entry : locals.int2ReferenceEntrySet()) {
+ Object typeOpcode = getType(entry.getValue());
+ if (typeOpcode == Opcodes.LONG || typeOpcode == Opcodes.DOUBLE) {
+ localsCopy[localIndex++] = Opcodes.TOP;
+ }
+ localsCopy[localIndex++] = typeOpcode;
+ }
+ // TODO(zerny): Compute the stack types too.
+ visitor.visitFrame(type, localsSize, localsCopy, 0, new Object[0]);
+ }
+
+ private Object getType(DexType type) {
+ if (type == DexItemFactory.nullValueType) {
+ return Opcodes.NULL;
+ }
+ switch (type.toShorty()) {
+ case 'L':
+ return Type.getType(type.toDescriptorString()).getInternalName();
+ case 'I':
+ return Opcodes.INTEGER;
+ case 'F':
+ return Opcodes.FLOAT;
+ case 'J':
+ return Opcodes.LONG;
+ case 'D':
+ return Opcodes.DOUBLE;
+ default:
+ throw new Unreachable("Unexpected value type: " + type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
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 a05299c..083c865 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -161,16 +162,18 @@
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
throws ApiLevelException {
- DexSourceCode source = new DexSourceCode(this, encodedMethod);
+ DexSourceCode source = new DexSourceCode(this, encodedMethod, null);
IRBuilder builder = new IRBuilder(encodedMethod, source, options);
return builder.build();
}
public IRCode buildIR(
DexEncodedMethod encodedMethod,
- InternalOptions options, ValueNumberGenerator valueNumberGenerator)
+ InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator,
+ Position callerPosition)
throws ApiLevelException {
- DexSourceCode source = new DexSourceCode(this, encodedMethod);
+ DexSourceCode source = new DexSourceCode(this, encodedMethod, callerPosition);
IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
return builder.build();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index f083794..908372a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableMap;
import java.util.SortedSet;
@@ -16,19 +17,27 @@
public final boolean prologueEnd;
public final boolean epilogueBegin;
public final ImmutableMap<Integer, DebugLocalInfo> locals;
+ public final DexMethod method;
+ public final Position callerPosition;
- public DexDebugEntry(int address,
+ public DexDebugEntry(
+ int address,
int line,
DexString sourceFile,
boolean prologueEnd,
boolean epilogueBegin,
- ImmutableMap<Integer, DebugLocalInfo> locals) {
+ ImmutableMap<Integer, DebugLocalInfo> locals,
+ DexMethod method,
+ Position callerPosition) {
this.address = address;
this.line = line;
this.sourceFile = sourceFile;
this.prologueEnd = prologueEnd;
this.epilogueBegin = epilogueBegin;
this.locals = locals;
+ this.method = method;
+ assert method != null;
+ this.callerPosition = callerPosition;
}
@Override
@@ -46,6 +55,10 @@
if (sourceFile != null) {
builder.append(", file ").append(sourceFile);
}
+ if (callerPosition != null) {
+ builder.append(", method ").append(method);
+ builder.append(" <-(").append(callerPosition).append(")");
+ }
if (prologueEnd) {
builder.append(", prologue_end = true");
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index c51e6e3..6ca2268 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
@@ -43,10 +44,13 @@
private int currentPc = 0;
private int currentLine;
private DexString currentFile = null;
+ private DexMethod currentMethod = null;
+ private Position currentCallerPosition = null;
private boolean prologueEnd = false;
private boolean epilogueBegin = false;
private final Map<Integer, LocalEntry> locals = new HashMap<>();
private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
+ private final DexMethod method;
// Delayed construction of an entry. Is finalized once locals information has been collected.
private DexDebugEntry pending = null;
@@ -57,11 +61,16 @@
// Resulting debug entries.
private List<DexDebugEntry> entries = new ArrayList<>();
- public DexDebugEntryBuilder(int startLine) {
+ public DexDebugEntryBuilder(int startLine, DexMethod method) {
currentLine = startLine;
+ this.method = method;
+ assert this.method != null;
+ currentMethod = method;
}
public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
+ this.method = method.method;
+ assert this.method != null;
DexCode code = method.getCode().asDexCode();
DexDebugInfo info = code.getDebugInfo();
int argumentRegister = code.registerSize - code.incomingRegisterSize;
@@ -81,6 +90,7 @@
argumentRegister += ValueType.fromDexType(types[i]).requiredRegisters();
}
currentLine = info.startLine;
+ currentMethod = this.method;
for (DexDebugEvent event : info.events) {
event.addToBuilder(this);
}
@@ -94,6 +104,13 @@
currentFile = file;
}
+ public void setInlineFrame(DexMethod callee, Position caller) {
+ assert (caller == null && callee == method)
+ || (caller != null && caller.getOutermostCaller().method == method);
+ currentMethod = callee;
+ currentCallerPosition = caller;
+ }
+
public void advancePC(int pcDelta) {
assert pcDelta >= 0;
currentPc += pcDelta;
@@ -133,15 +150,29 @@
assert pcDelta >= 0;
if (pending != null) {
// Local changes contribute to the pending position entry.
- entries.add(new DexDebugEntry(
- pending.address, pending.line, pending.sourceFile,
- pending.prologueEnd, pending.epilogueBegin,
- getLocals()));
+ entries.add(
+ new DexDebugEntry(
+ pending.address,
+ pending.line,
+ pending.sourceFile,
+ pending.prologueEnd,
+ pending.epilogueBegin,
+ getLocals(),
+ pending.method,
+ pending.callerPosition));
}
currentPc += pcDelta;
currentLine += lineDelta;
- pending = new DexDebugEntry(
- currentPc, currentLine, currentFile, prologueEnd, epilogueBegin, null);
+ pending =
+ new DexDebugEntry(
+ currentPc,
+ currentLine,
+ currentFile,
+ prologueEnd,
+ epilogueBegin,
+ null,
+ currentMethod,
+ currentCallerPosition);
prologueEnd = false;
epilogueBegin = false;
}
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 8d64cf4..724c10b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.dex.DebugBytecodeWriter;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.ir.code.Position;
+import java.util.Objects;
abstract public class DexDebugEvent extends DexItem {
@@ -379,6 +381,47 @@
}
}
+ public static class SetInlineFrame extends DexDebugEvent {
+
+ final DexMethod callee;
+ final Position caller;
+
+ SetInlineFrame(DexMethod callee, Position caller) {
+ assert callee != null;
+ this.callee = callee;
+ this.caller = caller;
+ }
+
+ @Override
+ public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+ // CallerPosition will not be written.
+ }
+
+ @Override
+ public void addToBuilder(DexDebugEntryBuilder builder) {
+ builder.setInlineFrame(callee, caller);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SET_INLINE_FRAME %s %s", callee, caller);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * callee.hashCode() + Objects.hashCode(caller);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof SetInlineFrame)) {
+ return false;
+ }
+ SetInlineFrame o = (SetInlineFrame) other;
+ return callee == o.callee && Objects.equals(caller, o.caller);
+ }
+ }
+
public static class Default extends DexDebugEvent {
final int value;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index c52d12c..1c71b9b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -210,7 +210,7 @@
}
assert emittedPosition.isNone();
startLine = position.line;
- emittedPosition = position;
+ emittedPosition = new Position(position.line, null, method.method, null);
}
debugPositionListBuilder.add(position.line, position.line);
emitAdvancementEvents(emittedPc, emittedPosition, pc, position, events, factory);
@@ -250,6 +250,10 @@
if (nextPosition.file != previousPosition.file) {
events.add(factory.createSetFile(nextPosition.file));
}
+ if (nextPosition.method != previousPosition.method
+ || nextPosition.callerPosition != previousPosition.callerPosition) {
+ events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
+ }
if (lineDelta < Constants.DBG_LINE_BASE
|| lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
events.add(factory.createAdvanceLine(lineDelta));
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index ae669a2..99d1227 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -24,8 +24,8 @@
hashCode();
}
- public List<DexDebugEntry> computeEntries() {
- DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine);
+ public List<DexDebugEntry> computeEntries(DexMethod method) {
+ DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method);
for (DexDebugEvent event : events) {
event.addToBuilder(builder);
}
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 c8932be..8fbcbce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -273,7 +274,15 @@
throws ApiLevelException {
return code == null
? null
- : code.asDexCode().buildIR(this, options, valueNumberGenerator);
+ : code.asDexCode().buildIR(this, options, valueNumberGenerator, null);
+ }
+
+ public IRCode buildIR(
+ InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
+ throws ApiLevelException {
+ return code == null
+ ? null
+ : code.asDexCode().buildIR(this, options, valueNumberGenerator, callerPosition);
}
public void setCode(Code code) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a7d44ba..8f45a2e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -12,8 +12,10 @@
import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.naming.NamingLens;
import com.google.common.collect.ImmutableSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -47,11 +49,22 @@
private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
private final Map<DexString, SetFile> setFiles = new HashMap<>();
+ private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
boolean sorted = false;
public static final DexType catchAllType = new DexType(new DexString("CATCH_ALL"));
- private static final Set<DexItem> internalSentinels = ImmutableSet.of(catchAllType);
+
+ // Internal type containing only the null value.
+ public static final DexType nullValueType = new DexType(new DexString("NULL"));
+
+ private static final Set<DexItem> internalSentinels = ImmutableSet.of(
+ catchAllType,
+ nullValueType);
+
+ public static boolean isInternalSentinel(DexItem item) {
+ return internalSentinels.contains(item);
+ }
public final DexString booleanDescriptor = createString("Z");
public final DexString byteDescriptor = createString("B");
@@ -291,7 +304,7 @@
private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
assert item != null;
- assert !internalSentinels.contains(item);
+ assert !DexItemFactory.isInternalSentinel(item);
T previous = map.putIfAbsent(item, item);
return previous == null ? item : previous;
}
@@ -326,7 +339,7 @@
result = new DexType(descriptor);
assert result.isArrayType() || result.isClassType() || result.isPrimitiveType() ||
result.isVoidType();
- assert !internalSentinels.contains(result);
+ assert !isInternalSentinel(result);
types.put(descriptor, result);
}
return result;
@@ -452,6 +465,13 @@
}
}
+ // TODO(tamaskenez) b/69024229 Measure if canonicalization is worth it.
+ public SetInlineFrame createSetInlineFrame(DexMethod callee, Position caller) {
+ synchronized (setInlineFrames) {
+ return setInlineFrames.computeIfAbsent(new SetInlineFrame(callee, caller), p -> p);
+ }
+ }
+
public boolean isConstructor(DexMethod method) {
return method.name == constructorMethodName;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index d4deb54..69a71f9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -228,8 +228,8 @@
public String toSourceString() {
if (toStringCache == null) {
// TODO(ager): Pass in a ProguardMapReader to map names back to original names.
- if (this == DexItemFactory.catchAllType) {
- toStringCache = "CATCH_ALL";
+ if (DexItemFactory.isInternalSentinel(this)) {
+ toStringCache = descriptor.toString();
} else {
toStringCache = DescriptorUtils.descriptorToJavaType(toDescriptorString());
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 44bc529..ce2de41 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.Resource.Origin;
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.JarSourceCode;
@@ -94,39 +95,49 @@
throws ApiLevelException {
triggerDelayedParsingIfNeccessary();
return options.debug
- ? internalBuildWithLocals(encodedMethod, options, null)
- : internalBuild(encodedMethod, options, null);
+ ? internalBuildWithLocals(encodedMethod, options, null, null)
+ : internalBuild(encodedMethod, options, null, null);
}
public IRCode buildIR(
- DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+ DexEncodedMethod encodedMethod,
+ InternalOptions options,
+ ValueNumberGenerator generator,
+ Position callerPosition)
throws ApiLevelException {
assert generator != null;
triggerDelayedParsingIfNeccessary();
return options.debug
- ? internalBuildWithLocals(encodedMethod, options, generator)
- : internalBuild(encodedMethod, options, generator);
+ ? internalBuildWithLocals(encodedMethod, options, generator, callerPosition)
+ : internalBuild(encodedMethod, options, generator, callerPosition);
}
private IRCode internalBuildWithLocals(
- DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+ DexEncodedMethod encodedMethod,
+ InternalOptions options,
+ ValueNumberGenerator generator,
+ Position callerPosition)
throws ApiLevelException {
try {
- return internalBuild(encodedMethod, options, generator);
+ return internalBuild(encodedMethod, options, generator, callerPosition);
} catch (InvalidDebugInfoException e) {
options.warningInvalidDebugInfo(encodedMethod, origin, e);
node.localVariables.clear();
- return internalBuild(encodedMethod, options, generator);
+ return internalBuild(encodedMethod, options, generator, callerPosition);
}
}
private IRCode internalBuild(
- DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+ DexEncodedMethod encodedMethod,
+ InternalOptions options,
+ ValueNumberGenerator generator,
+ Position callerPosition)
throws ApiLevelException {
if (!options.debug) {
node.localVariables.clear();
}
- JarSourceCode source = new JarSourceCode(clazz, node, application);
+ JarSourceCode source =
+ new JarSourceCode(clazz, node, application, encodedMethod.method, callerPosition);
IRBuilder builder =
(generator == null)
? new IRBuilder(encodedMethod, source, options)
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 44ac9c8..7f7ec36 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,11 +1,15 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
public class SmaliWriter extends DexByteCodeWriter {
@@ -15,12 +19,15 @@
}
/** Return smali source for the application code. */
- public static String smali(DexApplication application, InternalOptions options) {
- SmaliWriter writer = new SmaliWriter(application, options);
+ public static String smali(AndroidApp application, InternalOptions options) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (PrintStream ps = new PrintStream(os)) {
+ DexApplication dexApplication = new ApplicationReader(application, options,
+ new Timing("SmaliWriter"))
+ .read();
+ SmaliWriter writer = new SmaliWriter(dexApplication, options);
writer.write(ps);
- } catch (IOException e) {
+ } catch (IOException | ExecutionException e) {
throw new CompilationError("Failed to generate smali sting", e);
}
return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 4da6eaf..a33c150 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -3,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
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;
@@ -80,11 +82,16 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
// Arguments are defined by locals so nothing to load or store.
}
@Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ throw new Unreachable();
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
builder.addArgument(this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index be0e6db..0fbac8f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.code.CfArrayLoad;
import com.android.tools.r8.code.Aget;
import com.android.tools.r8.code.AgetBoolean;
@@ -16,7 +17,6 @@
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.ir.regalloc.RegisterAllocator;
@@ -131,9 +131,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.loadInValues(this, it);
- stack.storeOutValue(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.loadInValues(this, it);
+ helper.storeOutValue(this, it);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index c78f9ad..d02863f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.code.Const;
@@ -14,8 +16,8 @@
import com.android.tools.r8.code.ConstWide32;
import com.android.tools.r8.code.ConstWideHigh16;
import com.android.tools.r8.dex.Constants;
+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.utils.NumberUtils;
@@ -134,8 +136,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.storeOutValue(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
}
@Override
@@ -240,4 +242,10 @@
public ConstNumber asConstNumber() {
return this;
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ assert outType().isObject();
+ return helper.getFactory().nullValueType;
+ }
}
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 07c7c53..febbda3 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,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexString;
+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.utils.InternalOptions;
@@ -89,12 +91,17 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.storeOutValue(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
}
@Override
public void buildCf(CfBuilder builder) {
builder.add(new CfConstString(value));
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getFactory().stringType;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 6833568..4355598 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.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.TypeVerificationHelper;
+import com.android.tools.r8.graph.DexType;
+
/**
* Instruction introducing an SSA value with attached local information.
*
@@ -42,4 +45,14 @@
assert other.isDebugLocalWrite();
return true;
}
+
+ @Override
+ public boolean hasInvariantVerificationType() {
+ return false;
+ }
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getType(src());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index dbcf734..78ce128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.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;
@@ -66,7 +66,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
// Nothing to do for positions which are not actual instructions.
}
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 4b88706..95089a7 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,9 +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.LoadStoreHelper;
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;
@@ -99,7 +99,7 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
// Nothing to do.
}
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 8cdf209..c6e62af 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,10 +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.LoadStoreHelper;
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;
@@ -191,8 +191,8 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.loadInValues(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.loadInValues(this, it);
}
@Override
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 eeee6f8..ca68f64 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
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DebugLocalInfo;
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.ir.regalloc.RegisterAllocator;
@@ -965,7 +966,19 @@
// Returns the inlining constraint for this instruction.
public abstract Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder);
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
throw new Unimplemented("Implement load/store insertion for: " + getInstructionName());
}
+
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ throw new Unimplemented("Implement verification type computation for: " + getInstructionName());
+ }
+
+ public boolean hasInvariantVerificationType() {
+ if (inValues().isEmpty()) {
+ return true;
+ }
+ throw new Unimplemented(
+ "Implement has-invariant verification type for: " + getInstructionName());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index fef03c3..e7ca1b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -84,15 +85,26 @@
public abstract InlineAction computeInlining(InliningOracle decider);
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.loadInValues(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.loadInValues(this, it);
if (method.proto.returnType.isVoidType()) {
return;
}
if (outValue == null) {
- stack.popOutValue(ValueType.fromDexType(method.proto.returnType), this, it);
+ helper.popOutType(method.proto.returnType, this, it);
} else {
- stack.storeOutValue(this, it);
+ assert outValue.isUsed();
+ helper.storeOutValue(this, it);
}
}
+
+ @Override
+ public boolean hasInvariantVerificationType() {
+ return true;
+ }
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return getInvokedMethod().proto.returnType;
+ }
}
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 401a880..74cf861 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
@@ -3,12 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.errors.Unreachable;
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 {
@@ -59,7 +60,12 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getType(inValues.get(0));
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
// 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 2b419fd..23fdebf 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
@@ -3,14 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
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;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
public class MoveException extends Instruction {
@@ -73,11 +77,11 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
if (outValue.isUsed()) {
- stack.storeOutValue(this, it);
+ helper.storeOutValue(this, it);
} else {
- stack.popOutValue(outValue.type, this, it);
+ helper.popOutValue(outValue, this, it);
}
}
@@ -85,4 +89,24 @@
public void buildCf(CfBuilder builder) {
// Nothing to do. The exception is implicitly pushed on the stack.
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ Set<DexType> exceptionTypes = new HashSet<>(getBlock().getPredecessors().size());
+ for (BasicBlock block : getBlock().getPredecessors()) {
+ int size = block.getCatchHandlers().size();
+ List<BasicBlock> targets = block.getCatchHandlers().getAllTargets();
+ List<DexType> guards = block.getCatchHandlers().getGuards();
+ for (int i = 0; i < size; i++) {
+ if (targets.get(i) == getBlock()) {
+ DexType guard = guards.get(i);
+ exceptionTypes.add(
+ guard == helper.getFactory().catchAllType
+ ? helper.getFactory().throwableType
+ : guard);
+ }
+ }
+ }
+ return helper.join(exceptionTypes);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 1e77217..7c24034 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.code.NewArray;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -75,4 +76,14 @@
public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
return Constraint.classIsVisible(holder, type, info);
}
+
+ @Override
+ public boolean hasInvariantVerificationType() {
+ return true;
+ }
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return type;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 979b491..f3426a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -3,12 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfNew;
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;
@@ -81,12 +82,17 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.storeOutValue(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
}
@Override
public void buildCf(CfBuilder builder) {
builder.add(new CfNew(clazz));
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return clazz;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index faab702..2d9efb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.utils.CfgPrinter;
@@ -371,4 +373,16 @@
public boolean usesValueOneTime(Value usedValue) {
return operands.indexOf(usedValue) == operands.lastIndexOf(usedValue);
}
+
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ assert outType().isObject();
+ Set<DexType> operandTypes = new HashSet<>(operands.size());
+ for (Value operand : operands) {
+ DexType operandType = helper.getType(operand);
+ if (operandType != null) {
+ operandTypes.add(operandType);
+ }
+ }
+ return helper.join(operandTypes);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 08298bb..fa6532f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,42 +3,75 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import java.util.Objects;
public class Position {
- private static final Position NO_POSITION = new Position(-1, null, false);
+ private static final Position NO_POSITION = new Position(-1, null, null, null, false);
public final int line;
public final DexString file;
public final boolean synthetic;
- public Position(int line, DexString file) {
- this(line, file, false);
+ // If there's no inlining, callerPosition is null.
+ //
+ // For an inlined instruction its Position contains the inlinee's line and method and
+ // callerPosition is the position of the invoke instruction in the caller.
+
+ public final DexMethod method;
+ public final Position callerPosition;
+
+ public Position(int line, DexString file, DexMethod method, Position callerPosition) {
+ this(line, file, method, callerPosition, false);
assert line >= 0;
}
- private Position(int line, DexString file, boolean synthetic) {
+ private Position(
+ int line, DexString file, DexMethod method, Position callerPosition, boolean synthetic) {
this.line = line;
this.file = file;
this.synthetic = synthetic;
+ this.method = method;
+ this.callerPosition = callerPosition;
+ assert callerPosition == null || callerPosition.method != null;
+ assert line == -1 || method != null; // It's NO_POSITION or must have valid method.
}
- public static Position synthetic(int line) {
- return new Position(line, null, true);
+ public static Position synthetic(int line, DexMethod method, Position callerPosition) {
+ assert line >= 0;
+ return new Position(line, null, method, callerPosition, true);
}
public static Position none() {
return NO_POSITION;
}
+ // This factory method is used by the Inliner to create Positions when the caller has no valid
+ // positions. Since the callee still may have valid positions we need a non-null Position to set
+ // it as the caller of the inlined Positions.
+ public static Position noneWithMethod(DexMethod method, Position callerPosition) {
+ assert method != null;
+ return new Position(-1, null, method, callerPosition, false);
+ }
+
public boolean isNone() {
- return this == NO_POSITION;
+ return line == -1;
}
public boolean isSome() {
- return this != NO_POSITION;
+ return !isNone();
+ }
+
+ // Follow the linked list of callerPositions and return the last.
+ // Return this if no inliner.
+ public Position getOutermostCaller() {
+ Position lastPosition = this;
+ while (lastPosition.callerPosition != null) {
+ lastPosition = lastPosition.callerPosition;
+ }
+ return lastPosition;
}
@Override
@@ -48,7 +81,11 @@
}
if (other instanceof Position) {
Position o = (Position) other;
- return !isNone() && line == o.line && file == o.file;
+ return !isNone()
+ && line == o.line
+ && file == o.file
+ && method == o.method
+ && Objects.equals(callerPosition, o.callerPosition);
}
return false;
}
@@ -58,11 +95,12 @@
int result = line;
result = 31 * result + Objects.hashCode(file);
result = 31 * result + (synthetic ? 1 : 0);
+ result = 31 * result + Objects.hashCode(method);
+ result = 31 * result + Objects.hashCode(callerPosition);
return result;
}
- @Override
- public String toString() {
+ private String toString(boolean forceMethod) {
if (isNone()) {
return "--";
}
@@ -70,7 +108,18 @@
if (file != null) {
builder.append(file).append(":");
}
- builder.append(line);
+ if (method != null && (forceMethod || callerPosition != null)) {
+ builder.append("[").append(method).append("]");
+ }
+ builder.append("#").append(line);
+ if (callerPosition != null) {
+ builder.append(" <- ").append(callerPosition.toString(true));
+ }
return builder.toString();
}
+
+ @Override
+ public String toString() {
+ return toString(false);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index a197c87..1177abb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.code.MoveType;
import com.android.tools.r8.code.ReturnObject;
@@ -13,7 +14,6 @@
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;
@@ -117,9 +117,9 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
if (!isReturnVoid()) {
- stack.loadInValues(this, it);
+ helper.loadInValues(this, it);
}
}
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 3bf1417..65c4d3f 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
@@ -3,16 +3,40 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
public class StackValue extends Value {
private final int height;
+ private final DexType objectType;
- public StackValue(ValueType type, int height) {
- super(Value.UNDEFINED_NUMBER, type, null);
+ private StackValue(DexType objectType, ValueType valueType, int height) {
+ super(Value.UNDEFINED_NUMBER, valueType, null);
this.height = height;
+ this.objectType = objectType;
assert height >= 0;
}
+ public static StackValue forObjectType(DexType type, int height) {
+ assert DexItemFactory.nullValueType == type || type.isClassType();
+ return new StackValue(type, ValueType.OBJECT, height);
+ }
+
+ public static StackValue forNonObjectType(ValueType valueType, int height) {
+ assert valueType.isPreciseType() && !valueType.isObject();
+ return new StackValue(null, valueType, height);
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public DexType getObjectType() {
+ assert outType().isObject();
+ return objectType;
+ }
+
@Override
public String toString() {
return "s" + height;
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 23069f2..32871b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfStaticGet;
import com.android.tools.r8.code.Sget;
import com.android.tools.r8.code.SgetBoolean;
@@ -18,7 +20,6 @@
import com.android.tools.r8.graph.DexField;
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;
public class StaticGet extends FieldInstruction {
@@ -121,12 +122,17 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
- stack.storeOutValue(this, it);
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.storeOutValue(this, it);
}
@Override
public void buildCf(CfBuilder builder) {
builder.add(new CfStaticGet(field));
}
+
+ @Override
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return field.type;
+ }
}
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 bc1e419..7bdf7ad 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
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.errors.Unreachable;
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;
@@ -60,7 +61,12 @@
}
@Override
- public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+ public DexType computeVerificationType(TypeVerificationHelper helper) {
+ return helper.getType(inValues.get(0));
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
// 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/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index f07b61e..d4cc49c 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
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfTryCatch;
@@ -11,31 +14,30 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
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;
-import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
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;
-import com.android.tools.r8.ir.code.ValueType;
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.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -44,15 +46,16 @@
public class CfBuilder {
+ private final DexItemFactory factory;
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 Map<Value, DexType> types;
private Reference2IntMap<Value> registers;
private Map<BasicBlock, CfLabel> labels;
+ private List<CfInstruction> instructions;
/**
* Value that represents a shared physical location defined by the phi value.
@@ -81,94 +84,44 @@
}
}
- public static class StackHelper {
+ // Internal abstraction of the stack values and height.
+ private static class Stack {
+ int maxHeight = 0;
+ int height = 0;
- private int currentStackHeight = 0;
-
- public void loadInValues(Instruction instruction, InstructionListIterator it) {
- int topOfStack = currentStackHeight;
- it.previous();
- 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);
- }
- it.next();
+ boolean isEmpty() {
+ return height == 0;
}
- public void storeOutValue(Instruction instruction, InstructionListIterator it) {
- if (instruction.outValue() instanceof StackValue) {
- assert instruction.isConstInstruction();
- return;
- }
- StackValue newOutValue = new StackValue(instruction.outType(), currentStackHeight);
- Value oldOutValue = instruction.swapOutValue(newOutValue);
- add(new Store(oldOutValue, newOutValue), instruction, it);
+ void push(Value value) {
+ assert value instanceof StackValue;
+ height += value.requiredRegisters();
+ maxHeight = Math.max(maxHeight, height);
}
- public void popOutValue(ValueType type, Instruction instruction, InstructionListIterator it) {
- StackValue newOutValue = new StackValue(type, currentStackHeight);
- instruction.swapOutValue(newOutValue);
- add(new Pop(newOutValue), instruction, it);
+ void pop(Value value) {
+ assert value instanceof StackValue;
+ height -= value.requiredRegisters();
}
-
- 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) {
- 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) {
+ public CfBuilder(DexEncodedMethod method, IRCode code, DexItemFactory factory) {
this.method = method;
this.code = code;
+ this.factory = factory;
}
public Code build(CodeRewriter rewriter, InternalOptions options) {
try {
+ types = new TypeVerificationHelper(code, factory).computeVerificationTypes();
splitExceptionalBlocks();
- loadStoreInsertion();
+ new LoadStoreHelper(code, types).insertLoadsAndStores();
DeadCodeRemover.removeDeadCode(code, rewriter, options);
removeUnneededLoadsAndStores();
allocateLocalRegisters();
// TODO(zerny): Compute debug info.
- return buildCfCode();
+ CfCode code = buildCfCode();
+ return code;
} catch (Unimplemented e) {
System.out.println("Incomplete CF construction: " + e.getMessage());
return method.getCode().asJarCode();
@@ -207,32 +160,6 @@
}
}
- 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()) {
- Instruction current = it.next();
- current.insertLoadAndStores(it, stack);
- }
- }
- }
-
private void removeUnneededLoadsAndStores() {
Iterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
@@ -258,6 +185,8 @@
// Remove the load.
it.next();
it.remove();
+ // Rewind to the instruction before the store so we can identify new patterns.
+ it.previous();
}
}
}
@@ -287,18 +216,8 @@
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() {
+ Stack stack = new Stack();
List<CfTryCatch> tryCatchRanges = new ArrayList<>();
labels = new HashMap<>(code.blocks.size());
instructions = new ArrayList<>();
@@ -328,39 +247,73 @@
tryCatchHandlers = handlers;
}
BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
- buildCfInstructions(block, nextBlock);
+ boolean fallthrough = block.exit().isGoto() && block.exit().asGoto().getTarget() == nextBlock;
+ buildCfInstructions(block, fallthrough, stack);
+ if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
+ assert stack.isEmpty();
+ instructions.add(getLabel(nextBlock));
+ if (nextBlock.getPredecessors().size() > 1) {
+ addFrame(Collections.emptyList(), types.keySet());
+ }
+ }
block = nextBlock;
} while (block != null);
- assert currentStack == 0;
- return new CfCode(maxStack, maxLocals, instructions, tryCatchRanges);
+ assert stack.isEmpty();
+ return new CfCode(stack.maxHeight, maxLocals, instructions, tryCatchRanges);
}
- private void buildCfInstructions(BasicBlock block, BasicBlock nextBlock) {
- boolean fallthrough = false;
+ private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
InstructionIterator it = block.iterator();
while (it.hasNext()) {
Instruction instruction = it.next();
- if (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock) {
- fallthrough = true;
- continue;
+ if (fallthrough && instruction.isGoto()) {
+ assert block.exit() == instruction;
+ return;
}
- for (Value inValue : instruction.inValues()) {
- if (inValue instanceof StackValue) {
- pop(inValue);
+ for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
+ if (instruction.inValues().get(i) instanceof StackValue) {
+ stack.pop(instruction.inValues().get(i));
}
}
if (instruction.outValue() != null) {
Value outValue = instruction.outValue();
if (outValue instanceof StackValue) {
- push(outValue);
+ stack.push(outValue);
}
}
instruction.buildCf(this);
}
- if (nextBlock == null || (fallthrough && nextBlock.getPredecessors().size() == 1)) {
- return;
+ }
+
+ private void addFrame(Collection<StackValue> stack, Collection<Value> locals) {
+ // TODO(zerny): Support having values on the stack on control-edges.
+ assert stack.isEmpty();
+ Int2ReferenceSortedMap<DexType> mapping = new Int2ReferenceAVLTreeMap<>();
+ for (Value local : locals) {
+ DexType type;
+ switch (local.outType()) {
+ case INT:
+ type = factory.intType;
+ break;
+ case FLOAT:
+ type = factory.floatType;
+ break;
+ case LONG:
+ type = factory.longType;
+ break;
+ case DOUBLE:
+ type = factory.doubleType;
+ break;
+ case OBJECT:
+ type = types.get(local);
+ break;
+ default:
+ throw new Unreachable(
+ "Unexpected local type: " + local.outType() + " for local: " + local);
+ }
+ mapping.put(getLocalRegister(local), type);
}
- instructions.add(getLabel(nextBlock));
+ instructions.add(new CfFrame(mapping));
}
// Callbacks
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index e353a70..eb055f2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -37,6 +37,7 @@
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.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -73,17 +74,28 @@
private final List<ValueType> argumentTypes;
private List<DexDebugEntry> debugEntries = null;
+ private Position callerPosition; // In case of inlining the position of the invoke in the caller.
+ private final DexMethod method;
- public DexSourceCode(DexCode code, DexEncodedMethod method) {
+ public DexSourceCode(DexCode code, DexEncodedMethod method, Position callerPosition) {
this.code = code;
this.proto = method.method.proto;
this.accessFlags = method.accessFlags;
argumentTypes = computeArgumentTypes();
DexDebugInfo info = code.getDebugInfo();
if (info != null) {
- debugEntries = info.computeEntries();
+ debugEntries = info.computeEntries(method.method);
canonicalPositions = new HashMap<>(debugEntries.size());
}
+ if (info != null && callerPosition != null) {
+ // Canonicalize callerPosition
+ this.callerPosition = callerPosition;
+ canonicalPositions.put(callerPosition, callerPosition);
+ } else {
+ this.callerPosition = null;
+ }
+
+ this.method = method.method;
}
@Override
@@ -217,15 +229,44 @@
if (current == null) {
currentPosition = Position.none();
} else {
- currentPosition = getCanonicalPosition(current);
+ currentPosition = getCanonicalPositionAppendCaller(current);
if (current.address == offset) {
builder.addDebugPosition(currentPosition);
}
}
}
- private Position getCanonicalPosition(DexDebugEntry entry) {
- return canonicalPositions.computeIfAbsent(new Position(entry.line, entry.sourceFile), p -> p);
+ private Position getCanonicalPosition(Position position) {
+ Position canonical = canonicalPositions.putIfAbsent(position, position);
+ return canonical != null ? canonical : position;
+ }
+
+ private Position canonicalizeCallerPosition(Position caller) {
+ if (caller == null) {
+ return callerPosition;
+ }
+ if (caller.callerPosition == null && callerPosition == null) {
+ return getCanonicalPosition(caller);
+ }
+ Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+ return getCanonicalPosition(
+ caller.isNone()
+ ? Position.noneWithMethod(caller.method, callerOfCaller)
+ : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+ }
+
+ private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
+ // If this instruction has already been inlined then this.method must be the outermost caller.
+ assert (entry.callerPosition == null && entry.method == method)
+ || (entry.callerPosition != null
+ && entry.callerPosition.getOutermostCaller().method == method);
+
+ return getCanonicalPosition(
+ new Position(
+ entry.line,
+ entry.sourceFile,
+ entry.method,
+ canonicalizeCallerPosition(entry.callerPosition)));
}
@Override
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 00787ca..6728afe 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
@@ -39,6 +39,7 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -417,7 +418,7 @@
DexType result;
int count = 0;
do {
- String name = options.outline.className + (count == 0 ? "" : Integer.toString(count));
+ String name = OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
count++;
result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
} while (appInfo.definitionFor(result) != null);
@@ -589,7 +590,7 @@
private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
assert method.getCode().isJarCode();
- CfBuilder builder = new CfBuilder(method, code);
+ CfBuilder builder = new CfBuilder(method, code, options.itemFactory);
// TODO(zerny): Change the return type of CfBuilder::build CfCode once complete.
Code result = builder.build(codeRewriter, options);
assert result.isCfCode() || result.isJarCode();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 04a2685..700d9da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -181,12 +181,22 @@
// Cooked position to indicate positions in synthesized code (ie, for synchronization).
private Position syntheticPosition = null;
- public JarSourceCode(DexType clazz, MethodNode node, JarApplicationReader application) {
+ private final DexMethod method;
+ private final Position callerPosition;
+
+ public JarSourceCode(
+ DexType clazz,
+ MethodNode node,
+ JarApplicationReader application,
+ DexMethod method,
+ Position callerPosition) {
assert node != null;
assert node.desc != null;
this.node = node;
this.application = application;
+ this.method = method;
this.clazz = clazz;
+ this.callerPosition = callerPosition;
parameterTypes = Arrays.asList(Type.getArgumentTypes(node.desc));
state = new JarState(node.maxLocals, node.localVariables, this, application);
AbstractInsnNode first = node.instructions.getFirst();
@@ -2840,7 +2850,8 @@
}
private Position getCanonicalPosition(int line) {
- return canonicalPositions.computeIfAbsent(line, l -> new Position(l, null));
+ return canonicalPositions.computeIfAbsent(
+ line, l -> new Position(l, null, method, callerPosition));
}
// If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
@@ -2862,8 +2873,8 @@
}
syntheticPosition =
(min == Integer.MAX_VALUE)
- ? Position.none()
- : Position.synthetic(min < max ? min - 1 : min);
+ ? Position.noneWithMethod(method, callerPosition)
+ : Position.synthetic(min < max ? min - 1 : min, method, callerPosition);
}
return syntheticPosition;
}
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 3c7fe40..5386163 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
@@ -1986,7 +1986,7 @@
InstructionListIterator iterator = block.listIterator();
// Attach some synthetic position to all inserted code.
- Position position = Position.synthetic(1);
+ Position position = Position.synthetic(1, method.method, null);
iterator.setInsertionPosition(position);
// Split arguments into their own block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index b15d254..cd51b25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.CallSiteInformation;
@@ -231,18 +232,23 @@
return reason != Reason.SIMPLE;
}
- public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
- GraphLense graphLense, InternalOptions options) throws ApiLevelException {
+ public IRCode buildIR(
+ ValueNumberGenerator generator,
+ AppInfoWithSubtyping appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Position callerPosition)
+ throws ApiLevelException {
if (target.isProcessed()) {
assert target.getCode().isDexCode();
- return target.buildIR(options, generator);
+ return target.buildIR(options, generator, callerPosition);
} else {
// Build the IR for a yet not processed method, and perform minimal IR processing.
IRCode code;
if (target.getCode().isJarCode()) {
- code = target.getCode().asJarCode().buildIR(target, options, generator);
+ code = target.getCode().asJarCode().buildIR(target, options, generator, callerPosition);
} else {
- code = target.getCode().asDexCode().buildIR(target, options, generator);
+ code = target.getCode().asDexCode().buildIR(target, options, generator, callerPosition);
}
new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
return code;
@@ -359,8 +365,16 @@
InlineAction result = invoke.computeInlining(oracle);
if (result != null) {
DexEncodedMethod target = result.target;
- IRCode inlinee = result
- .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
+ Position invokePosition = invoke.getPosition();
+ if (invokePosition.method == null) {
+ assert invokePosition.isNone();
+ invokePosition = Position.noneWithMethod(method.method, null);
+ }
+ assert invokePosition.getOutermostCaller().method == method.method;
+
+ IRCode inlinee =
+ result.buildIR(
+ code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
if (inlinee != null) {
// TODO(64432527): Get rid of this additional check by improved inlining.
if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 67e58cb..9709cf0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -48,6 +48,7 @@
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.Sets;
@@ -1062,7 +1063,7 @@
MethodAccessFlags methodAccess =
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
- DexString methodName = dexItemFactory.createString(options.outline.methodPrefix + count);
+ DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
DexMethod method = outline.buildMethod(type, methodName);
direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(), new OutlineCode(outline));
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c494c08..36ea1b3 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -17,6 +17,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.Type;
@@ -291,6 +292,12 @@
this.parameters = parameters;
}
+ public MethodSignature(String name, String type, Collection<String> parameters) {
+ super(name);
+ this.type = type;
+ this.parameters = parameters.toArray(new String[parameters.size()]);
+ }
+
public static MethodSignature fromDexMethod(DexMethod method) {
String[] paramNames = new String[method.getArity()];
DexType[] values = method.proto.parameters.values;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 67aa288..ddac51f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -608,7 +608,7 @@
* Set dead-code data.
*/
public Builder setDeadCode(byte[] content) {
- deadCode = content == null ? null : Resource.fromBytes(null, content);
+ deadCode = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
return this;
}
@@ -631,7 +631,7 @@
* Set proguard-map data.
*/
public Builder setProguardMapData(byte[] content) {
- proguardMap = content == null ? null : Resource.fromBytes(null, content);
+ proguardMap = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
return this;
}
@@ -639,7 +639,7 @@
* Set proguard-seeds data.
*/
public Builder setProguardSeedsData(byte[] content) {
- proguardSeeds = content == null ? null : Resource.fromBytes(null, content);
+ proguardSeeds = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
return this;
}
@@ -685,7 +685,7 @@
* Set the main-dex list output data.
*/
public Builder setMainDexListOutputData(byte[] content) {
- mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
+ mainDexListOutput = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
return this;
}
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 d7006af..62dac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -267,9 +267,10 @@
public static class OutlineOptions {
+ public static final String CLASS_NAME = "r8.GeneratedOutlineSupport";
+ public static final String METHOD_PREFIX = "outline";
+
public boolean enabled = true;
- public static final String className = "r8.GeneratedOutlineSupport";
- public String methodPrefix = "outline";
public int minSize = 3;
public int maxSize = 99;
public int threshold = 20;
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
new file mode 100644
index 0000000..859876d
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
@@ -0,0 +1,40 @@
+// 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.apiusagesample;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.utils.DirectoryClassFileProvider;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
+
+ private ConcurrentHashMap<String, Resource> resources = new ConcurrentHashMap<>();
+
+ private CachingArchiveClassFileProvider(Path archive) throws IOException {
+ super(archive);
+ }
+
+ @Override
+ public Resource getResource(String descriptor) {
+ return resources.computeIfAbsent(descriptor, desc -> super.getResource(desc));
+ }
+
+ public static ClassFileResourceProvider getProvider(Path entry)
+ throws IOException {
+ if (Files.isRegularFile(entry)) {
+ return new CachingArchiveClassFileProvider(entry);
+ } else if (Files.isDirectory(entry)) {
+ return DirectoryClassFileProvider.fromDirectory(entry);
+ } else {
+ throw new FileNotFoundException(entry.toString());
+ }
+ }
+}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
new file mode 100644
index 0000000..ff61567
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
@@ -0,0 +1,136 @@
+// 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.apiusagesample;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class D8Compiler {
+ private int minSdkVersion;
+ private Path bootclasspath;
+ private List<Path> classpath;
+ private static ExecutorService pool = Executors.newFixedThreadPool(4);
+
+ private D8Compiler(int minSdkVersion, Path bootclasspath, List<Path> classpath) {
+ this.minSdkVersion = minSdkVersion;
+ this.bootclasspath = bootclasspath;
+ this.classpath = classpath;
+ }
+
+ /**
+ * java ...Compiler output input minSdkVersion mainDexClasses bootclasspath [classpathEntries]+
+ */
+ public static void main(String[] args) throws Throwable {
+ try {
+ int argIndex = 0;
+ Path outputDir = Paths.get(args[argIndex++]);
+ Path input = Paths.get(args[argIndex++]);
+ int minSdkVersion = Integer.parseInt(args[argIndex++]);
+ Path mainDexClasses = Paths.get(args[argIndex++]);
+ Path bootclasspath = Paths.get(args[argIndex++]);
+
+ List<Path> classpath = new ArrayList<>(args.length - argIndex);
+ while (argIndex < args.length) {
+ classpath.add(Paths.get(args[argIndex++]));
+ }
+
+ D8Compiler compiler = new D8Compiler(minSdkVersion, bootclasspath, classpath);
+
+ List<Path> toMerge = new ArrayList<>(3);
+
+ int intermediateIndex = 0;
+ for (Path entry : classpath) {
+ Path output = outputDir.resolve(entry.getFileName() + "." + (intermediateIndex++));
+ Files.createDirectory(output);
+ toMerge.add(output);
+ compiler.compile(output, entry);
+ }
+
+ Path output = outputDir.resolve("main." + (intermediateIndex++));
+ Files.createDirectory(output);
+ toMerge.add(output);
+ compiler.compile(output, input);
+
+ compiler.merge(outputDir, mainDexClasses, toMerge);
+ } finally {
+ // Terminate pool threads to prevent the VM to wait on then before exiting.
+ pool.shutdown();
+ }
+ }
+
+ private void compile(Path output, Path input) throws Throwable {
+ D8Command.Builder builder =
+ D8Command.builder()
+ // Compile in debug and merge in release to assert access to both modes
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(minSdkVersion)
+ .setIntermediate(true)
+ .setEnableDesugaring(true)
+ .setOutputPath(output);
+
+ builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(bootclasspath));
+
+ for (Path entry : classpath) {
+ builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(entry));
+ }
+
+ if (Files.isRegularFile(input)) {
+ builder.setOutputMode(OutputMode.Indexed);
+ builder.addProgramFiles(input);
+ } else {
+ builder.setOutputMode(OutputMode.FilePerInputClass);
+ Files.walkFileTree(input, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+ throws IOException {
+ builder.addClassProgramData(Files.readAllBytes(path));
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ D8.run(builder.build(), pool);
+ }
+
+ private void merge(Path outputDir, Path mainDexClasses,
+ List<Path> toMerge) throws IOException, CompilationException {
+ D8Command.Builder merger = D8Command.builder();
+ merger.setEnableDesugaring(false);
+
+ for (Path mergeInput : toMerge) {
+ Files.walkFileTree(mergeInput, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+ throws IOException {
+ merger.addDexProgramData(Files.readAllBytes(path));
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ if (mainDexClasses != null) {
+ merger.addMainDexListFiles(mainDexClasses);
+ }
+ merger.setMinApiLevel(minSdkVersion)
+ .setMode(CompilationMode.RELEASE)
+ .setOutputPath(outputDir)
+ .setEnableDesugaring(false)
+ .setIntermediate(false);
+ D8.run(merger.build());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..5a3b0f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
@@ -0,0 +1,64 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class D8APiBinaryCompatibilityTests {
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testCompatibility() throws IOException {
+ Path compilerJar = Paths.get("tests", "api_usage_sample.jar");
+ String compiler = "com.android.tools.apiusagesample.D8Compiler";
+
+ String output = temp.newFolder().getAbsolutePath();
+ int minSdkVersion = AndroidApiLevel.K.getLevel();
+ String androidJar = ToolHelper.getAndroidJar(minSdkVersion);
+ Path lib1 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "desugaringwithmissingclasslib1" + JAR_EXTENSION);
+ Path lib2 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "desugaringwithmissingclasslib2" + JAR_EXTENSION);
+ Path input = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "classes", "desugaringwithmissingclasstest1");
+ File mainDexClasses = temp.newFile();
+ Files.asCharSink(mainDexClasses, StandardCharsets.UTF_8)
+ .write("desugaringwithmissingclasstest1/Main.class");
+
+ List<String> command = ImmutableList.of(
+ ToolHelper.getJavaExecutable(),
+ "-cp",
+ compilerJar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+ compiler,
+ // Compiler arguments.
+ output,
+ input.toString(),
+ Integer.toString(minSdkVersion),
+ mainDexClasses.getAbsolutePath(),
+ androidJar,
+ lib1.toString(),
+ lib2.toString());
+ ProcessBuilder builder = new ProcessBuilder(command);
+ ProcessResult result = ToolHelper.runProcess(builder);
+
+ Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2128663..60bcc5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -5,18 +5,20 @@
package com.android.tools.r8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
@@ -368,8 +370,34 @@
*/
protected void disassemble(AndroidApp app) throws Exception {
InternalOptions options = new InternalOptions();
- DexApplication dexApplication = new ApplicationReader(app, options, new Timing("XX")).read();
- System.out.println(SmaliWriter.smali(dexApplication, options));
+ System.out.println(SmaliWriter.smali(app, options));
+ }
+
+ protected DexEncodedMethod getMethod(
+ DexInspector inspector,
+ String className,
+ String returnType,
+ String methodName,
+ List<String> parameters) {
+ ClassSubject clazz = inspector.clazz(className);
+ assertTrue(clazz.isPresent());
+ MethodSubject method = clazz.method(returnType, methodName, parameters);
+ assertTrue(method.isPresent());
+ return method.getMethod();
+ }
+
+ protected DexEncodedMethod getMethod(
+ AndroidApp application,
+ String className,
+ String returnType,
+ String methodName,
+ List<String> parameters) {
+ try {
+ DexInspector inspector = new DexInspector(application);
+ return getMethod(inspector, className, returnType, methodName, parameters);
+ } catch (Exception e) {
+ return null;
+ }
}
public enum MinifyMode {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2c4bc98..34230de 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
import com.android.tools.r8.ToolHelper.DexVm.Kind;
import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.shaking.FilteredClassPath;
@@ -697,14 +696,6 @@
return compatSink.build();
}
- public static DexApplication optimizeWithR8(
- DexApplication application,
- InternalOptions options)
- throws CompilationException, ExecutionException, IOException {
- application = application.toDirect();
- return R8.optimize(application, new AppInfoWithSubtyping(application), options);
- }
-
public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
return runD8(app, null);
}
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index ba49e64..fc6e4c1 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -10,8 +10,8 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.smali.SmaliTestBase.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
index 66652bd..7a911be 100644
--- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -12,15 +12,13 @@
import com.android.tools.r8.debuginfo.DebugInfoInspector;
import com.android.tools.r8.graph.DexDebugEntry;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApp;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assume;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index b344c8c..8d93b31 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,9 +10,10 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -62,10 +63,11 @@
" goto :return"
);
- DexApplication application = buildApplication(builder);
- AppInfo appInfo = new AppInfo(application);
- DexEncodedMethod method =
- getMethod(application, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of());
+ AndroidApp application = buildApplication(builder);
+ AppInfo appInfo = getAppInfo(application);
+ DexInspector inspector = new DexInspector(appInfo.app);
+ DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
+ ImmutableList.of());
assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
assertNull(appInfo.lookupDirectTarget(method.method));
assertNotNull(appInfo.lookupStaticTarget(method.method));
@@ -74,12 +76,12 @@
// Dalvik rejects at verification time instead of producing the
// expected IncompatibleClassChangeError.
try {
- runArt(application, new InternalOptions());
+ runArt(application);
} catch (AssertionError e) {
assert e.toString().contains("VerifyError");
}
} else {
- assertEquals("OK", runArt(application, new InternalOptions()));
+ assertEquals("OK", runArt(application));
}
}
@@ -131,20 +133,21 @@
" goto :return"
);
- DexApplication application = buildApplication(builder);
- AppInfo appInfo = new AppInfo(application);
+ AndroidApp application = buildApplication(builder);
+ AppInfo appInfo = getAppInfo(application);
+ DexInspector inspector = new DexInspector(appInfo.app);
DexMethod methodXOnTestSuper =
- getMethod(application, "TestSuper", "int", "x", ImmutableList.of()).method;
+ getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
DexMethod methodYOnTest =
- getMethod(application, "Test", "int", "y", ImmutableList.of()).method;
+ getMethod(inspector, "Test", "int", "y", ImmutableList.of()).method;
DexType classTestSuper = methodXOnTestSuper.getHolder();
DexType classTest = methodYOnTest.getHolder();
DexProto methodXProto = methodXOnTestSuper.proto;
DexString methodXName = methodXOnTestSuper.name;
DexMethod methodXOnTest =
- application.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
+ appInfo.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
@@ -156,7 +159,7 @@
assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
- assertEquals("OK", runArt(application, new InternalOptions()));
+ assertEquals("OK", runArt(application));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index ad9a26a..2d73d1f 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,13 +4,15 @@
package com.android.tools.r8.ir;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -45,8 +47,7 @@
" return p0"
);
- InternalOptions options = new InternalOptions();
- DexApplication application = buildApplication(builder, options);
+ AndroidApp application = buildApplication(builder);
// Build the code, and split the code into three blocks.
ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 27a3ce9..b190933 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -13,7 +13,8 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@@ -22,7 +23,7 @@
import java.util.ListIterator;
import org.junit.Test;
-public class InlineTest extends SmaliTestBase {
+public class InlineTest extends IrInjectionTestBase {
TestApplication codeForMethodReplaceTest(int a, int b) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -84,7 +85,7 @@
DexEncodedMethod methodB = getMethod(application, signatureB);
IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
- return new SmaliTestBase.TestApplication(application, method, code,
+ return new TestApplication(application, method, code,
ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
}
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index a688ffe..5d3b0fb 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -4,14 +4,16 @@
package com.android.tools.r8.ir;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -46,8 +48,7 @@
" return p0"
);
- InternalOptions options = new InternalOptions();
- DexApplication application = buildApplication(builder, options);
+ AndroidApp application = buildApplication(builder);
// Build the code, and split the code into three blocks.
ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
new file mode 100644
index 0000000..9f3e0ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -0,0 +1,146 @@
+// 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.ir;
+
+import com.android.tools.r8.R8;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import org.antlr.runtime.RecognitionException;
+
+public class IrInjectionTestBase extends SmaliTestBase {
+
+ protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+ try {
+ return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
+ } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
+ try {
+ options.itemFactory.resetSortedIndices();
+ return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read();
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+ return getMethod(application,
+ signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
+ }
+
+ protected DexEncodedMethod getMethod(
+ DexApplication application,
+ String className,
+ String returnType,
+ String methodName,
+ List<String> parameters) {
+ DexInspector inspector = new DexInspector(application);
+ return getMethod(inspector, className, returnType, methodName, parameters);
+ }
+
+ public class TestApplication {
+
+ public final DexApplication application;
+ public final DexEncodedMethod method;
+ public final IRCode code;
+ public final List<IRCode> additionalCode;
+ public final ValueNumberGenerator valueNumberGenerator;
+ public final InternalOptions options;
+
+ public TestApplication(
+ DexApplication application,
+ DexEncodedMethod method,
+ IRCode code,
+ ValueNumberGenerator valueNumberGenerator,
+ InternalOptions options) {
+ this(application, method, code, null, valueNumberGenerator, options);
+ }
+
+ public TestApplication(
+ DexApplication application,
+ DexEncodedMethod method,
+ IRCode code,
+ List<IRCode> additionalCode,
+ ValueNumberGenerator valueNumberGenerator,
+ InternalOptions options) {
+ this.application = application;
+ this.method = method;
+ this.code = code;
+ this.additionalCode = additionalCode;
+ this.valueNumberGenerator = valueNumberGenerator;
+ this.options = options;
+ }
+
+ public int countArgumentInstructions() {
+ int count = 0;
+ ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
+ while (iterator.next().isArgument()) {
+ count++;
+ }
+ return count;
+ }
+
+ public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
+ InstructionListIterator iterator = block.listIterator();
+ for (int i = 0; i < index; i++) {
+ iterator.next();
+ }
+ return iterator;
+ }
+
+ private AndroidApp writeDex(DexApplication application, InternalOptions options)
+ throws DexOverflowException {
+ try {
+ AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
+ R8.writeApplication(
+ Executors.newSingleThreadExecutor(),
+ application,
+ compatSink,
+ null,
+ NamingLens.getIdentityLens(),
+ null,
+ options);
+ compatSink.close();
+ return compatSink.build();
+ } catch (ExecutionException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String run() throws DexOverflowException, IOException {
+ AppInfo appInfo = new AppInfo(application);
+ IRConverter converter = new IRConverter(appInfo, options);
+ converter.replaceCodeForTesting(method, code);
+ AndroidApp app = writeDex(application, options);
+ return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 5165432..25a237d 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -21,14 +21,15 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
-public class SplitBlockTest extends SmaliTestBase {
+public class SplitBlockTest extends IrInjectionTestBase {
TestApplication codeWithoutCatchHandlers() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index 1d17e31..74a91a8 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -3,13 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.regalloc;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.PriorityQueue;
@@ -43,7 +45,7 @@
ImmutableList.of(),
1,
" return-void");
- DexApplication application = buildApplication(builder, options);
+ AndroidApp application = buildApplication(builder);
// Build the code, and split the code into three blocks.
ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
DexEncodedMethod method = getMethod(application, signature);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 7421046..5579380 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -4,18 +4,15 @@
package com.android.tools.r8.jasmin;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.Resource;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.StringUtils;
@@ -33,13 +30,8 @@
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-public class JasminTestBase {
-
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+public class JasminTestBase extends TestBase {
public static String getPathFromDescriptor(String classDescriptor) {
assert classDescriptor.startsWith("L");
@@ -95,6 +87,10 @@
return runOnArt(compileWithD8(builder), main);
}
+ protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception {
+ return compileWithR8(builder, null);
+ }
+
protected AndroidApp compileWithR8(JasminBuilder builder,
Consumer<InternalOptions> optionsConsumer)
throws Exception {
@@ -169,20 +165,6 @@
protected DexEncodedMethod getMethod(AndroidApp application, String clazz,
MethodSignature signature) {
return getMethod(application,
- clazz, signature.type, signature.name, signature.parameters);
- }
-
- protected DexEncodedMethod getMethod(AndroidApp application, String className,
- String returnType, String methodName, String[] parameters) {
- try {
- DexInspector inspector = new DexInspector(application);
- ClassSubject clazz = inspector.clazz(className);
- assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(returnType, methodName, Arrays.asList(parameters));
- assertTrue(method.isPresent());
- return method.getMethod();
- } catch (Exception e) {
- return null;
- }
+ clazz, signature.type, signature.name, Arrays.asList(signature.parameters));
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
index acb0a51..de654b8 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.graph.DexDebugEvent.SetFile;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 4d1d619..51772fe 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.Sput;
import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -23,11 +22,11 @@
import com.android.tools.r8.graph.DexValue.DexValueLong;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
@@ -92,9 +91,8 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -147,7 +145,7 @@
assertTrue(value instanceof DexValueString);
assertEquals(("8"), ((DexValueString) value).getValue().toString());
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("true", "1", "2", "3", "4", "5.0", "6.0", "7", "8"), result);
}
@@ -177,16 +175,15 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
MethodSubject clinit = inspector.clazz("Test").clinit();
// Nothing changed in the class initializer.
assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("0", "1"), result);
}
@@ -219,16 +216,15 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("null", "null", "null"), result);
}
@@ -262,16 +258,15 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
assertTrue(
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("Value1", "Value2", "Value2"), result);
}
@@ -313,9 +308,8 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
// Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -333,7 +327,7 @@
assertTrue(value instanceof DexValueString);
assertEquals(("7"), ((DexValueString) value).getValue().toString());
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("3", "7") , result);
}
@@ -387,9 +381,8 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
assertTrue(inspector.clazz("Test").clinit().isPresent());
@@ -417,7 +410,7 @@
}
}
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("3", "7"), result);
}
@@ -475,9 +468,8 @@
"return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
@@ -485,7 +477,7 @@
assertTrue(
inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
- String result = runArt(processedApplication, options, className);
+ String result = runArt(processedApplication, className);
assertEquals(StringUtils.lines("Test", className, "Test", className, "Test", className),
result);
@@ -525,15 +517,14 @@
builder.addClass("org.example.Test2");
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
assertTrue(inspector.clazz(className).clinit().isPresent());
- String result = runArt(processedApplication, options, className);
+ String result = runArt(processedApplication, className);
assertEquals(StringUtils.lines("Test2", "org.example.Test2"), result);
}
@@ -559,16 +550,15 @@
builder.addClass("Other");
builder.addStaticField("field", "I", "1");
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexInspector inspector = new DexInspector(processedApplication);
MethodSubject clinit = inspector.clazz("Test").clinit();
// Nothing changed in the class initializer.
assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("2"), result);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index d1a2514..76c24f9 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
rename to src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
index f47025a..93b7c19 100644
--- a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
@@ -2,7 +2,7 @@
// 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.smali;
+package com.android.tools.r8.rewrite.switches;
import java.lang.reflect.Method;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
new file mode 100644
index 0000000..66cf986
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -0,0 +1,344 @@
+// 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.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class SwitchRewritingJarTest extends JasminTestBase {
+
+ private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ String switchCode;
+ if (packed) {
+ switchCode = StringUtils.join(
+ "\n",
+ " tableswitch " + key,
+ " case_0",
+ " default : case_default");
+ } else {
+ switchCode = StringUtils.join(
+ "\n",
+ " lookupswitch",
+ " " + key + " : case_0",
+ " default : case_default");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload 0",
+ switchCode,
+ " case_0:",
+ " iconst_3",
+ " goto return_",
+ " case_default:",
+ " ldc 5",
+ " return_:",
+ " ireturn");
+
+ clazz.addMainMethod(
+ " .limit stack 2",
+ " .limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 2",
+ " invokestatic Test/test(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ AndroidApp app = builder.build();
+ app = ToolHelper.runR8(app);
+
+ MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+ DexEncodedMethod method = getMethod(app, "Test", signature);
+ DexCode code = method.getCode().asDexCode();
+ if (key == 0) {
+ assertEquals(5, code.instructions.length);
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ assertEquals(6, code.instructions.length);
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
+ }
+
+ @Test
+ public void singleCaseJar() throws Exception {
+ for (boolean packed : new boolean[]{true, false}) {
+ runSingleCaseJarTest(packed, Integer.MIN_VALUE);
+ runSingleCaseJarTest(packed, -1);
+ runSingleCaseJarTest(packed, 0);
+ runSingleCaseJarTest(packed, 1);
+ runSingleCaseJarTest(packed, Integer.MAX_VALUE);
+ }
+ }
+
+ private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload 0",
+ " lookupswitch",
+ " " + key1 + " : case_1",
+ " " + key2 + " : case_2",
+ " default : case_default",
+ " case_1:",
+ " iconst_3",
+ " goto return_",
+ " case_2:",
+ " iconst_4",
+ " goto return_",
+ " case_default:",
+ " iconst_5",
+ " return_:",
+ " ireturn");
+
+ clazz.addMainMethod(
+ " .limit stack 2",
+ " .limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 2",
+ " invokestatic Test/test(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ AndroidApp app = compileWithR8(builder);
+
+ MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+ DexEncodedMethod method = getMethod(app, "Test", signature);
+ DexCode code = method.getCode().asDexCode();
+ if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) {
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
+ } else {
+ if (key1 == 0) {
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
+ }
+ }
+
+ @Test
+ public void twoCaseSparseToPackedJar() throws Exception {
+ for (int delta = 1; delta <= 3; delta++) {
+ runTwoCaseSparseToPackedJarTest(0, delta);
+ runTwoCaseSparseToPackedJarTest(-delta, 0);
+ runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+ runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+ }
+ runTwoCaseSparseToPackedJarTest(-1, 1);
+ runTwoCaseSparseToPackedJarTest(-2, 1);
+ runTwoCaseSparseToPackedJarTest(-1, 2);
+ runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+ }
+
+ private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
+ Integer additionalLastKey) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ StringBuilder switchSource = new StringBuilder();
+ StringBuilder targetCode = new StringBuilder();
+ for (int i = 0; i < totalKeys; i++) {
+ String caseLabel = "case_" + i;
+ switchSource.append(" " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
+ targetCode.append(" " + caseLabel + ":\n");
+ targetCode.append(" ldc " + i + "\n");
+ targetCode.append(" goto return_\n");
+ }
+ if (additionalLastKey != null) {
+ String caseLabel = "case_" + totalKeys;
+ switchSource.append(" " + additionalLastKey + " : " + caseLabel + "\n");
+ targetCode.append(" " + caseLabel + ":\n");
+ targetCode.append(" ldc " + totalKeys + "\n");
+ targetCode.append(" goto return_\n");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload 0",
+ " lookupswitch",
+ switchSource.toString(),
+ " default : case_default",
+ targetCode.toString(),
+ " case_default:",
+ " iconst_5",
+ " return_:",
+ " ireturn");
+
+ clazz.addMainMethod(
+ " .limit stack 2",
+ " .limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 2",
+ " invokestatic Test/test(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ AndroidApp app = compileWithR8(builder);
+
+ MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+ DexEncodedMethod method = getMethod(app, "Test", signature);
+ DexCode code = method.getCode().asDexCode();
+ int packedSwitchCount = 0;
+ int sparseSwitchCount = 0;
+ for (Instruction instruction : code.instructions) {
+ if (instruction instanceof PackedSwitch) {
+ packedSwitchCount++;
+ }
+ if (instruction instanceof SparseSwitch) {
+ sparseSwitchCount++;
+ }
+ }
+ if (keyStep <= 2) {
+ assertEquals(1, packedSwitchCount);
+ assertEquals(0, sparseSwitchCount);
+ } else {
+ assertEquals(0, packedSwitchCount);
+ assertEquals(1, sparseSwitchCount);
+ }
+ }
+
+ @Test
+ public void largerSwitchJar() throws Exception {
+ runLargerSwitchJarTest(0, 1, 100, null);
+ runLargerSwitchJarTest(0, 2, 100, null);
+ runLargerSwitchJarTest(0, 3, 100, null);
+ runLargerSwitchJarTest(100, 100, 100, null);
+ runLargerSwitchJarTest(-10000, 100, 100, null);
+ runLargerSwitchJarTest(-10000, 200, 100, 10000);
+ runLargerSwitchJarTest(
+ Integer.MIN_VALUE, (int) ((-(long) Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+ // This is the maximal value possible with Jasmin with the generated code above. It depends on
+ // the source, so making smaller source can raise this limit. However we never get close to the
+ // class file max.
+ runLargerSwitchJarTest(0, 1, 5503, null);
+ }
+
+ private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+ int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ StringBuilder x = new StringBuilder();
+ StringBuilder y = new StringBuilder();
+ for (Integer key : keys) {
+ x.append(key).append(" : case_").append(key).append("\n");
+ y.append("case_").append(key).append(":\n");
+ y.append(" ldc ").append(key).append("\n");
+ y.append(" goto return_\n");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload_0",
+ " lookupswitch",
+ x.toString(),
+ " default : case_default",
+ y.toString(),
+ " case_default:",
+ " ldc " + defaultValue,
+ " return_:",
+ " ireturn");
+
+ // Add the Jasmin class and a class from Java source with the main method.
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ appBuilder.addClassProgramData(builder.buildClasses());
+ appBuilder.addProgramFiles(FilteredClassPath
+ .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
+ AndroidApp app = compileWithR8(appBuilder.build());
+
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+ DexCode code = method.getMethod().getCode().asDexCode();
+
+ int packedSwitches = 0;
+ int sparseSwitches = 0;
+ int ifs = 0;
+ for (Instruction instruction : code.instructions) {
+ if (instruction instanceof PackedSwitch) {
+ packedSwitches++;
+ }
+ if (instruction instanceof SparseSwitch) {
+ sparseSwitches++;
+ }
+ if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+ ifs++;
+ }
+ }
+
+ assertEquals(expectedPackedSwitches, packedSwitches);
+ assertEquals(expectedSparceSwitches, sparseSwitches);
+ assertEquals(expectedIfs, ifs);
+
+ // Run the code
+ List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+ args.add(Integer.toString(defaultValue));
+ runOnArt(app, CheckSwitchInTestClass.class, args);
+ }
+
+ @Test
+ public void convertCasesToIf() throws Exception {
+ // Switches that are completely converted to ifs.
+ runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+ // Switches that are completely converted to ifs and one switch.
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+ // Switches that are completely converted to ifs and two switches.
+ runConvertCasesToIf(ImmutableList.of(
+ 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+ // Switches that are completely converted two switches (one sparse and one packed).
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, -900, -800, -700, -600, -500, -400, -300,
+ 1000, 1001, 1002, 1003, 1004,
+ 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
new file mode 100644
index 0000000..3d921b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -0,0 +1,250 @@
+// 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.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class SwitchRewritingTest extends SmaliTestBase {
+
+ static boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
+ assert key1 != key2;
+ return Math.abs((long) key1 - (long) key2) == 1;
+ }
+
+ private boolean some16BitConst(Instruction instruction) {
+ return instruction instanceof Const4
+ || instruction instanceof ConstHigh16
+ || instruction instanceof Const;
+ }
+ private void runSingleCaseDexTest(boolean packed, int key) {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+ String switchInstruction;
+ String switchData;
+ if (packed) {
+ switchInstruction = "packed-switch";
+ switchData = StringUtils.join(
+ "\n",
+ " :switch_data",
+ " .packed-switch " + key,
+ " :case_0",
+ " .end packed-switch");
+ } else {
+ switchInstruction = "sparse-switch";
+ switchData = StringUtils.join(
+ "\n",
+ " :switch_data",
+ " .sparse-switch",
+ " " + key + " -> :case_0",
+ " .end sparse-switch");
+ }
+ MethodSignature signature = builder.addStaticMethod(
+ "int",
+ DEFAULT_METHOD_NAME,
+ ImmutableList.of("int"),
+ 0,
+ " " + switchInstruction + " p0, :switch_data",
+ " const/4 p0, 0x5",
+ " goto :return",
+ " :case_0",
+ " const/4 p0, 0x3",
+ " :return",
+ " return p0",
+ switchData);
+
+ builder.addMainMethod(
+ 2,
+ " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ " const/4 v1, 0",
+ " invoke-static { v1 }, LTest;->method(I)I",
+ " move-result v1",
+ " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+ " return-void"
+ );
+
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
+ DexEncodedMethod method = getMethod(processedApplication, signature);
+ DexCode code = method.getCode().asDexCode();
+
+ if (key == 0) {
+ assertEquals(5, code.instructions.length);
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ assertEquals(6, code.instructions.length);
+ assertTrue(some16BitConst(code.instructions[0]));
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
+ }
+
+ @Test
+ public void singleCaseDex() {
+ for (boolean packed : new boolean[]{true, false}) {
+ runSingleCaseDexTest(packed, Integer.MIN_VALUE);
+ runSingleCaseDexTest(packed, -1);
+ runSingleCaseDexTest(packed, 0);
+ runSingleCaseDexTest(packed, 1);
+ runSingleCaseDexTest(packed, Integer.MAX_VALUE);
+ }
+ }
+
+ private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ MethodSignature signature = builder.addStaticMethod(
+ "int",
+ DEFAULT_METHOD_NAME,
+ ImmutableList.of("int"),
+ 0,
+ " sparse-switch p0, :sparse_switch_data",
+ " const/4 v0, 0x5",
+ " goto :return",
+ " :case_1",
+ " const/4 v0, 0x3",
+ " goto :return",
+ " :case_2",
+ " const/4 v0, 0x4",
+ " :return",
+ " return v0",
+ " :sparse_switch_data",
+ " .sparse-switch",
+ " " + key1 + " -> :case_1",
+ " " + key2 + " -> :case_2",
+ " .end sparse-switch");
+
+ builder.addMainMethod(
+ 2,
+ " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ " const/4 v1, 0",
+ " invoke-static { v1 }, LTest;->method(I)I",
+ " move-result v1",
+ " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+ " return-void"
+ );
+
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
+ DexEncodedMethod method = getMethod(processedApplication, signature);
+ DexCode code = method.getCode().asDexCode();
+ if (twoCaseWillUsePackedSwitch(key1, key2)) {
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
+ } else {
+ if (key1 == 0) {
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
+ }
+ }
+
+ @Test
+ public void twoCaseSparseToPackedOrIfsDex() {
+ for (int delta = 1; delta <= 3; delta++) {
+ runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+ }
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+ }
+
+ private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
+ Integer additionalLastKey) throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ StringBuilder switchSource = new StringBuilder();
+ StringBuilder targetCode = new StringBuilder();
+ for (int i = 0; i < totalKeys; i++) {
+ String caseLabel = "case_" + i;
+ switchSource.append(" " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
+ targetCode.append(" :" + caseLabel + "\n");
+ targetCode.append(" goto :return\n");
+ }
+ if (additionalLastKey != null) {
+ String caseLabel = "case_" + totalKeys;
+ switchSource.append(" " + additionalLastKey + " -> :" + caseLabel + "\n");
+ targetCode.append(" :" + caseLabel + "\n");
+ targetCode.append(" goto :return\n");
+ }
+
+ MethodSignature signature = builder.addStaticMethod(
+ "void",
+ DEFAULT_METHOD_NAME,
+ ImmutableList.of("int"),
+ 0,
+ " sparse-switch p0, :sparse_switch_data",
+ " goto :return",
+ targetCode.toString(),
+ " :return",
+ " return-void",
+ " :sparse_switch_data",
+ " .sparse-switch",
+ switchSource.toString(),
+ " .end sparse-switch");
+
+ builder.addMainMethod(
+ 2,
+ " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ " const/4 v1, 0",
+ " invoke-static { v1 }, LTest;->method(I)V",
+ " return-void"
+ );
+
+ Consumer<InternalOptions> optionsConsumer = options -> {
+ options.verbose = true;
+ options.printTimes = true;
+ };
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
+ DexEncodedMethod method = getMethod(processedApplication, signature);
+ DexCode code = method.getCode().asDexCode();
+ if (keyStep <= 2) {
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
+ } else {
+ assertTrue(code.instructions[0] instanceof SparseSwitch);
+ }
+ }
+
+ @Test
+ public void twoMonsterSparseToPackedDex() throws Exception {
+ runLargerSwitchDexTest(0, 1, 100, null);
+ runLargerSwitchDexTest(0, 2, 100, null);
+ runLargerSwitchDexTest(0, 3, 100, null);
+ runLargerSwitchDexTest(100, 100, 100, null);
+ runLargerSwitchDexTest(-10000, 100, 100, null);
+ runLargerSwitchDexTest(-10000, 200, 100, 10000);
+ runLargerSwitchDexTest(
+ Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+ // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
+ // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 909f574..d2391ff 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -6,13 +6,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
import java.util.Collections;
@@ -62,13 +63,11 @@
" invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V",
" return-void");
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
-
+ AndroidApp originalApplication = buildApplication(builder);
DexEncodedMethod method = getMethod(originalApplication, methodSig);
// Get the IR pre-optimization.
- IRCode code = method.buildIR(options);
+ IRCode code = method.buildIR(new InternalOptions());
// Find the exit block and assert that the value is a phi merging the exceptional edge
// with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
index 64aa946..de8b832 100644
--- a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.smali;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
@@ -48,9 +48,8 @@
" goto :in_try"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
DexEncodedMethod method = getMethod(processedApplication, methodSig);
assert method.getCode().asDexCode().tries.length > 0;
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 8d4b841..0395177 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -6,9 +6,8 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -56,10 +55,9 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(smaliBuilder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- String result = runArt(processedApplication, options);
+ AndroidApp originalApplication = buildApplication(smaliBuilder);
+ AndroidApp processedApplication = processApplication(originalApplication);
+ String result = runArt(processedApplication);
assertEquals(expectedBuilder.toString(), result);
}
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 1aa9be7..d7f16e8 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -21,26 +21,26 @@
import com.android.tools.r8.code.ReturnObject;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.ReturnWide;
-import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
-import org.antlr.runtime.RecognitionException;
import org.junit.Test;
public class OutlineTest extends SmaliTestBase {
@@ -52,6 +52,15 @@
return result;
}
+ private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) {
+ return options -> {
+ // Disable inlining to make sure that code looks as expected.
+ options.inlineAccessors = false;
+ // Also apply outline options.
+ optionsConsumer.accept(options.outline);
+ };
+ }
+
DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) {
DexInspector inspector = new DexInspector(application);
ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString());
@@ -62,31 +71,19 @@
invokedMethod.proto.returnType.toSourceString(),
invokedMethod.name.toString(),
Arrays.stream(invokedMethod.proto.parameters.values)
- .map(p -> p.toSourceString())
+ .map(DexType::toSourceString)
.collect(Collectors.toList()));
assertTrue(method.isPresent());
return method.getMethod();
}
- String firstOutlineMethodName(InternalOptions options) {
- StringBuilder builder = new StringBuilder(options.outline.className);
- builder.append('.');
- builder.append(options.outline.methodPrefix);
- builder.append("0");
- return builder.toString();
+ private String firstOutlineMethodName() {
+ return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
}
- MethodSignature firstOutlineMethodSignature(
- String returnType, List<String> parameterTypes, InternalOptions options) {
- return new MethodSignature(
- options.outline.className, options.outline.methodPrefix + "0", returnType, parameterTypes);
- }
-
- boolean isOutlineMethodName(InternalOptions options, String qualifiedName) {
- StringBuilder builder = new StringBuilder(options.outline.className);
- builder.append('.');
- builder.append(options.outline.methodPrefix);
- return qualifiedName.indexOf(builder.toString()) == 0;
+ private boolean isOutlineMethodName(String qualifiedName) {
+ String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
+ return qualifiedName.indexOf(qualifiedPrefix) == 0;
}
@Test
@@ -127,14 +124,16 @@
);
for (int i = 2; i < 6; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int j = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = j;
+ outline.maxSize = j;
+ });
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -143,10 +142,10 @@
assertTrue(code.instructions[0] instanceof ConstString);
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
}
@@ -192,14 +191,16 @@
);
for (int i = 2; i < 6; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int finalI = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = finalI;
+ outline.maxSize = finalI;
+ });
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -213,10 +214,10 @@
}
assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("Test1Test2Test3Test4", result);
}
}
@@ -258,11 +259,12 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ });
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -271,10 +273,10 @@
assertTrue(code.instructions[0] instanceof ConstString);
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("1", result);
}
@@ -322,11 +324,12 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ });
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -336,10 +339,10 @@
assertTrue(code.instructions[1] instanceof ConstString);
assertTrue(code.instructions[2] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[2];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("TestXTest1TestYTest2Test3", result);
}
@@ -381,14 +384,16 @@
);
for (int i = 2; i < 4; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int finalI = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = finalI;
+ outline.maxSize = finalI;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -398,17 +403,17 @@
if (i < 3) {
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assertTrue(code.instructions[1] instanceof InvokeVirtual);
assertTrue(code.instructions[2] instanceof InvokeVirtual);
assertTrue(code.instructions[3] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[3];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
StringBuilder resultBuilder = new StringBuilder();
for (int j = 0; j < 4; j++) {
resultBuilder.append(0x7fffffff00000000L);
@@ -455,14 +460,16 @@
);
for (int i = 2; i < 4; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int finalI = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = finalI;
+ outline.maxSize = finalI;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -472,17 +479,17 @@
if (i < 3) {
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assertTrue(code.instructions[1] instanceof InvokeVirtual);
assertTrue(code.instructions[2] instanceof InvokeVirtual);
assertTrue(code.instructions[3] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[3];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
StringBuilder resultBuilder = new StringBuilder();
for (int j = 0; j < 4; j++) {
resultBuilder.append(1.0d);
@@ -525,14 +532,16 @@
);
for (int i = 2; i < 6; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int finalI = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = finalI;
+ outline.maxSize = finalI;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed main method for inspection.
DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
@@ -548,18 +557,18 @@
}
if (i == 2) {
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else if (i == 3) {
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assert i == 4 || i == 5;
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("1122", result);
}
}
@@ -623,29 +632,30 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 7;
- options.outline.maxSize = 7;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 7;
+ outline.maxSize = 7;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
assertEquals(4, code1.instructions.length);
assertTrue(code1.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
assertEquals(5, code2.instructions.length);
assertTrue(code2.instructions[2] instanceof InvokeStatic);
InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
- assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("Test1Test1Test1Test1Test2Test2Test2Test2", result);
}
@@ -687,14 +697,16 @@
);
for (int i = 2; i < 8; i++) {
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = i;
- options.outline.maxSize = i;
+ final int finalI = i;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = finalI;
+ outline.maxSize = finalI;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
InvokeStatic invoke;
@@ -711,10 +723,10 @@
outlineInstructionIndex = 2;
}
invoke = (InvokeStatic) code.instructions[outlineInstructionIndex];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("Test2Test2", result);
}
}
@@ -744,23 +756,24 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3;
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code = getMethod(processedApplication, signature1).getCode().asDexCode();
InvokeStatic invoke;
assertTrue(code.instructions[0] instanceof InvokeStatic);
invoke = (InvokeStatic) code.instructions[0];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("", result);
}
@@ -817,18 +830,19 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3;
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that three outlining methods was created.
DexInspector inspector = new DexInspector(processedApplication);
- ClassSubject clazz = inspector.clazz(options.outline.className);
+ ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
assertEquals(3, clazz.getDexClass().directMethods().length);
// Collect the return types of the putlines for the body of method1 and method2.
@@ -842,12 +856,12 @@
assert r.size() == 2;
DexType r1 = r.get(0);
DexType r2 = r.get(1);
- DexItemFactory factory = processedApplication.dexItemFactory;
+ DexItemFactory factory = inspector.getFactory();
assertTrue(r1 == factory.voidType && r2 == factory.stringType ||
r1 == factory.stringType && r2 == factory.voidType);
// Run the code.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
@@ -888,37 +902,40 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3;
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
final int count = 10;
// Process the application several times. Each time will outline the previous outline.
for (int i = 0; i < count; i++) {
// Build a new application with the Outliner class.
- DexApplication.Builder appBuilder = processedApplication.builder();
- originalApplication = appBuilder.build();
+ originalApplication = processedApplication;
processedApplication = processApplication(originalApplication, options);
- assertEquals(i + 3, Iterables.size(processedApplication.classes()));
+ assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
}
// Process the application several times. No more outlining as threshold has been raised.
- options.outline.threshold = 2;
+ options = configureOptions(outline -> {
+ outline.threshold = 2;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
for (int i = 0; i < count; i++) {
// Build a new application with the Outliner class.
- DexApplication.Builder appBuilder = processedApplication.builder();
- originalApplication = appBuilder.build();
+ originalApplication = processedApplication;
processedApplication = processApplication(originalApplication, options);
- assertEquals(count - 1 + 3, Iterables.size(processedApplication.classes()));
+ assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
}
// Run the application with several levels of outlining.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
@@ -950,14 +967,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 5;
- options.outline.maxSize = 5;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 5;
+ outline.maxSize = 5;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -968,10 +986,10 @@
assertTrue(code.instructions[1] instanceof MoveResultWide);
assertTrue(code.instructions[2] instanceof ReturnWide);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
- assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+ assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run the code and expect a parsable long.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
Long.parseLong(result);
}
@@ -1010,14 +1028,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 4;
- options.outline.maxSize = 4;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 4;
+ outline.maxSize = 4;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1026,10 +1045,10 @@
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnObject);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
- assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+ assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("null", result);
}
@@ -1084,14 +1103,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 4;
- options.outline.maxSize = 4;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 4;
+ outline.maxSize = 4;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method1 = getMethod(processedApplication, signature1);
@@ -1101,7 +1121,7 @@
assertTrue(code1.instructions[1] instanceof MoveResult);
assertTrue(code1.instructions[2] instanceof Return);
InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
- assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
DexEncodedMethod method2 = getMethod(processedApplication, signature2);
DexCode code2 = method2.getCode().asDexCode();
@@ -1110,7 +1130,7 @@
assertEquals(invoke1.getMethod().qualifiedName(), invoke2.getMethod().qualifiedName());
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("44", result);
}
@@ -1159,14 +1179,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3; // Outline add, sub and mul.
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3; // Outline add, sub and mul.
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1180,10 +1201,10 @@
assertTrue(code.instructions[5] instanceof Const4);
assertTrue(code.instructions[6] instanceof Return);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
- assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+ assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("4", result);
}
@@ -1211,14 +1232,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3;
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1227,10 +1249,10 @@
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnVoid);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
- assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+ assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("", result);
}
@@ -1256,14 +1278,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 1;
- options.outline.minSize = 3;
- options.outline.maxSize = 3;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 1;
+ outline.minSize = 3;
+ outline.maxSize = 3;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1272,10 +1295,10 @@
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnVoid);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
- assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+ assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals("", result);
}
@@ -1462,14 +1485,15 @@
" return-void"
);
- InternalOptions options = createInternalOptions();
- options.outline.threshold = 2;
+ Consumer<InternalOptions> options = configureOptions(outline -> {
+ outline.threshold = 2;
+ });
- DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(2, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+ AndroidApp processedApplication = processApplication(originalApplication, options);
+ assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Verify the code.
- runDex2Oat(processedApplication, options);
+ runDex2Oat(processedApplication);
}
}
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
index bf89f7f..1f0cd78 100644
--- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java
+++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -5,9 +5,7 @@
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
@@ -48,10 +46,9 @@
"invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
"return-void");
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- String result = runArt(processedApplication, options);
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
+ String result = runArt(processedApplication);
// The art runtime changed the way exceptions are printed. Therefore, we only check
// for the type of the exception and that the message mentions null.
assertTrue(result.startsWith(StringUtils.joinLines("0", "java.lang.NumberFormatException:")));
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 214a3b4..4d13ae0 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -12,12 +12,11 @@
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.Iterables;
import org.junit.Test;
public class RunArtSmokeTest extends SmaliTestBase {
@@ -34,10 +33,9 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- assertEquals(1, Iterables.size(processedApplication.classes()));
+ AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp processedApplication = processApplication(originalApplication);
+ assertEquals(1, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod main = getMethod(processedApplication, mainSignature);
@@ -50,7 +48,7 @@
assertTrue(code.instructions[3] instanceof ReturnVoid);
// Run the generated code in Art.
- String result = runArt(processedApplication, options);
+ String result = runArt(processedApplication);
assertEquals(StringUtils.lines("Hello, world!"), result);
}
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index b3a4060..8e946b6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -7,20 +7,24 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class SmaliBuildTest extends SmaliTestBase {
- private void checkJavaLangString(DexApplication application, boolean present) {
- DexInspector inspector = new DexInspector(application);
- ClassSubject clazz = inspector.clazz("java.lang.String");
- assertEquals(present, clazz.isPresent());
+ private void checkJavaLangString(AndroidApp application, boolean present) {
+ try {
+ DexInspector inspector = new DexInspector(application);
+ ClassSubject clazz = inspector.clazz("java.lang.String");
+ assertEquals(present, clazz.isPresent());
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
}
@Test
@@ -35,12 +39,11 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
// No libraries added - java.lang.String is not present.
- DexApplication originalApplication = buildApplication(builder, options);
+ AndroidApp originalApplication = buildApplication(builder);
checkJavaLangString(originalApplication, false);
- DexApplication processedApplication = processApplication(originalApplication, options);
+ AndroidApp processedApplication = processApplication(originalApplication);
checkJavaLangString(processedApplication, false);
}
@@ -56,17 +59,17 @@
" return-void"
);
- InternalOptions options = new InternalOptions();
- AndroidApp app = AndroidApp.builder()
+ AndroidApp originalApp = AndroidApp.builder()
.addDexProgramData(builder.compile())
.addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
.build();
// Java standard library added - java.lang.String is present.
- DexApplication originalApplication = buildApplication(app, options);
- checkJavaLangString(originalApplication, true);
+ checkJavaLangString(originalApp, true);
- DexApplication processedApplication = processApplication(originalApplication, options);
- checkJavaLangString(processedApplication, true);
+ AndroidApp processedApplication = processApplication(originalApp);
+
+ // The library method is not part of the output.
+ checkJavaLangString(processedApplication, false);
}
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
new file mode 100644
index 0000000..ad1e097
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -0,0 +1,349 @@
+// 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.smali;
+
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Smali;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.antlr.runtime.RecognitionException;
+
+public class SmaliBuilder {
+
+ public static class MethodSignature {
+
+ public final String clazz;
+ public final String name;
+ public final String returnType;
+ public final List<String> parameterTypes;
+
+ public MethodSignature(String clazz, String name, String returnType,
+ List<String> parameterTypes) {
+ this.clazz = clazz;
+ this.name = name;
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ public static MethodSignature staticInitializer(String clazz) {
+ return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+ }
+
+ @Override
+ public String toString() {
+ return returnType + " " + clazz + "." + name
+ + "(" + StringUtils.join(parameterTypes, ",") + ")";
+ }
+ }
+
+ abstract class Builder {
+
+ String name;
+ String superName;
+ List<String> implementedInterfaces;
+ String sourceFile = null;
+ List<String> source = new ArrayList<>();
+
+ Builder(String name, String superName, List<String> implementedInterfaces) {
+ this.name = name;
+ this.superName = superName;
+ this.implementedInterfaces = implementedInterfaces;
+ }
+
+ protected void appendSuper(StringBuilder builder) {
+ builder.append(".super ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
+ builder.append("\n");
+ }
+
+ protected void appendImplementedInterfaces(StringBuilder builder) {
+ for (String implementedInterface : implementedInterfaces) {
+ builder.append(".implements ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
+ builder.append("\n");
+ }
+ }
+
+ protected void writeSource(StringBuilder builder) {
+ for (String sourceLine : source) {
+ builder.append(sourceLine);
+ builder.append("\n");
+ }
+ }
+ }
+
+ public class ClassBuilder extends Builder {
+
+ ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
+ super(name, superName, implementedInterfaces);
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".class public ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+ builder.append("\n");
+ appendSuper(builder);
+ appendImplementedInterfaces(builder);
+ builder.append("\n");
+ if (sourceFile != null) {
+ builder.append(".source \"").append(sourceFile).append("\"\n");
+ }
+ writeSource(builder);
+ return builder.toString();
+ }
+ }
+
+ public class InterfaceBuilder extends Builder {
+
+ InterfaceBuilder(String name, String superName) {
+ super(name, superName, ImmutableList.of());
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".class public interface abstract ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+ builder.append("\n");
+ appendSuper(builder);
+ appendImplementedInterfaces(builder);
+ builder.append("\n");
+ writeSource(builder);
+ return builder.toString();
+ }
+ }
+
+ private String currentClassName;
+ private final Map<String, Builder> classes = new HashMap<>();
+
+ public SmaliBuilder() {
+ // No default class.
+ }
+
+ public SmaliBuilder(String name) {
+ addClass(name);
+ }
+
+ public SmaliBuilder(String name, String superName) {
+ addClass(name, superName);
+ }
+
+ private List<String> getSource(String clazz) {
+ return classes.get(clazz).source;
+ }
+
+ public String getCurrentClassName() {
+ return currentClassName;
+ }
+
+ public String getCurrentClassDescriptor() {
+ return DescriptorUtils.javaTypeToDescriptor(currentClassName);
+ }
+
+ public void addClass(String name) {
+ addClass(name, "java.lang.Object");
+ }
+
+ public void addClass(String name, String superName) {
+ addClass(name, superName, ImmutableList.of());
+ }
+
+ public void addClass(String name, String superName, List<String> implementedInterfaces) {
+ assert !classes.containsKey(name);
+ currentClassName = name;
+ classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
+ }
+
+ public void addInterface(String name) {
+ addInterface(name, "java.lang.Object");
+ }
+
+ public void addInterface(String name, String superName) {
+ assert !classes.containsKey(name);
+ currentClassName = name;
+ classes.put(name, new InterfaceBuilder(name, superName));
+ }
+
+ public void setSourceFile(String file) {
+ classes.get(currentClassName).sourceFile = file;
+ }
+
+ public void addDefaultConstructor() {
+ String superDescriptor =
+ DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
+ addMethodRaw(
+ " .method public constructor <init>()V",
+ " .locals 0",
+ " invoke-direct {p0}, " + superDescriptor + "-><init>()V",
+ " return-void",
+ " .end method"
+ );
+ }
+
+ public void addStaticField(String name, String type, String defaultValue) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field static ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ if (defaultValue != null) {
+ builder.append(" = ");
+ if (type.equals("Ljava/lang/String;")) {
+ builder.append('"');
+ builder.append(defaultValue);
+ builder.append('"');
+ } else {
+ builder.append(defaultValue);
+ }
+ }
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ public void addStaticField(String name, String type) {
+ addStaticField(name, type, null);
+ }
+
+ public void addInstanceField(String name, String type) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ private MethodSignature addMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".method ");
+ if (flags != null && flags.length() > 0) {
+ builder.append(flags);
+ builder.append(" ");
+ }
+ builder.append(name);
+ builder.append("(");
+ for (String parameter : parameters) {
+ builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+ }
+ builder.append(")");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+ builder.append("\n");
+ if (locals >= 0) {
+ builder.append(".locals ");
+ builder.append(locals);
+ builder.append("\n\n");
+ assert code != null;
+ builder.append(code);
+ } else {
+ assert code == null;
+ }
+ builder.append(".end method");
+ getSource(currentClassName).add(builder.toString());
+ return new MethodSignature(currentClassName, name, returnType, parameters);
+ }
+
+ public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
+ int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addStaticMethod(returnType, name, parameters, locals, builder.toString());
+ }
+
+ public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
+ int locals, String code) {
+ return addStaticMethod("", returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addStaticInitializer(locals, builder.toString());
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String code) {
+ return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+ }
+
+ private MethodSignature addStaticMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
+ StringBuilder builder = new StringBuilder();
+ return addMethod("public static " + flags, returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addAbstractMethod(
+ String returnType, String name, List<String> parameters) {
+ return addMethod("public abstract", returnType, name, parameters, -1, null);
+ }
+
+ public MethodSignature addInstanceMethod(String returnType, String name,
+ List<String> parameters,
+ int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+ }
+
+ public MethodSignature addInstanceMethod(String returnType, String name,
+ List<String> parameters,
+ int locals, String code) {
+ return addMethod("public", returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addMainMethod(int locals, String... instructions) {
+ return addStaticMethod(
+ "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
+ }
+
+ public void addMethodRaw(String... source) {
+ StringBuilder builder = new StringBuilder();
+ for (String line : source) {
+ builder.append(line);
+ builder.append("\n");
+ }
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ public List<String> buildSource() {
+ List<String> result = new ArrayList<>(classes.size());
+ for (String clazz : classes.keySet()) {
+ Builder classBuilder = classes.get(clazz);
+ result.add(classBuilder.toString());
+ }
+ return result;
+ }
+
+ public byte[] compile()
+ throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+ return Smali.compile(buildSource());
+ }
+
+ public AndroidApp build()
+ throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+ return AndroidApp.fromDexProgramData(compile());
+ }
+
+
+ @Override
+ public String toString() {
+ return String.join("\n\n", buildSource());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index b35e768..3fe8da9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -6,16 +6,12 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.DexOverflowException;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
@@ -27,12 +23,7 @@
// Run the provided smali through R8 smali disassembler and expect the exact same output.
void roundTripRawSmali(String smali) {
try {
- DexApplication application =
- new ApplicationReader(
- AndroidApp.fromDexProgramData(Smali.compile(smali)),
- new InternalOptions(),
- new Timing("SmaliTest"))
- .read();
+ AndroidApp application = AndroidApp.fromDexProgramData(Smali.compile(smali));
assertEquals(smali, SmaliWriter.smali(application, new InternalOptions()));
} catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
throw new RuntimeException(e);
@@ -41,7 +32,7 @@
@Test
public void simpleSmokeTest() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int", Collections.singletonList("int"),
4,
" const/4 v0, 1 ",
@@ -81,7 +72,7 @@
@Test
public void sparseSwitchTest() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int", Collections.singletonList("int"),
0,
" sparse-switch v0, :sparse_switch_data",
@@ -137,7 +128,7 @@
@Test
public void packedSwitchTest() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int", Collections.singletonList("int"),
0,
" packed-switch v0, :packed_switch_data",
@@ -193,7 +184,7 @@
@Test
public void fillArrayDataTest8Bit() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int[]", ImmutableList.of(),
2,
" const/4 v1, 3",
@@ -236,7 +227,7 @@
@Test
public void fillArrayDataTest16Bit() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int[]", ImmutableList.of(),
2,
" const/4 v1, 3",
@@ -279,7 +270,7 @@
@Test
public void fillArrayDataTest32Bit() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int[]", ImmutableList.of(),
2,
" const/4 v1, 3",
@@ -322,7 +313,7 @@
@Test
public void fillArrayDataTest64Bit() {
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
"int[]", ImmutableList.of(),
2,
" const/4 v1, 3",
@@ -368,8 +359,7 @@
SmaliBuilder builder = new SmaliBuilder();
builder.addInterface("Test");
builder.addAbstractMethod("int", "test", ImmutableList.of());
- DexApplication application = buildApplication(builder);
- assertEquals(1, Iterables.size(application.classes()));
+ AndroidApp application = buildApplication(builder);
String expected =
".class public interface abstract LTest;\n" +
@@ -391,8 +381,7 @@
SmaliBuilder builder = new SmaliBuilder();
builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
builder.addAbstractMethod("int", "test", ImmutableList.of());
- DexApplication application = buildApplication(builder);
- assertEquals(1, Iterables.size(application.classes()));
+ AndroidApp application = buildApplication(builder);
String expected =
".class public LTest;\n" +
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 2d2f887..4949153 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,8 +8,8 @@
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.Resource;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
@@ -18,39 +18,21 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.Collection;
import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
import java.util.function.Consumer;
import org.antlr.runtime.RecognitionException;
@@ -60,414 +42,20 @@
public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
public static final String DEFAULT_METHOD_NAME = "method";
- public static class MethodSignature {
-
- public final String clazz;
- public final String name;
- public final String returnType;
- public final List<String> parameterTypes;
-
- public MethodSignature(String clazz, String name, String returnType,
- List<String> parameterTypes) {
- this.clazz = clazz;
- this.name = name;
- this.returnType = returnType;
- this.parameterTypes = parameterTypes;
- }
-
- public static MethodSignature staticInitializer(String clazz) {
- return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
- }
-
- @Override
- public String toString() {
- return returnType + " " + clazz + "." + name
- + "(" + StringUtils.join(parameterTypes, ",") + ")";
- }
- }
-
- public static class SmaliBuilder {
-
- abstract class Builder {
-
- String name;
- String superName;
- List<String> implementedInterfaces;
- String sourceFile = null;
- List<String> source = new ArrayList<>();
-
- Builder(String name, String superName, List<String> implementedInterfaces) {
- this.name = name;
- this.superName = superName;
- this.implementedInterfaces = implementedInterfaces;
- }
-
- protected void appendSuper(StringBuilder builder) {
- builder.append(".super ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
- builder.append("\n");
- }
-
- protected void appendImplementedInterfaces(StringBuilder builder) {
- for (String implementedInterface : implementedInterfaces) {
- builder.append(".implements ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
- builder.append("\n");
- }
- }
-
- protected void writeSource(StringBuilder builder) {
- for (String sourceLine : source) {
- builder.append(sourceLine);
- builder.append("\n");
- }
- }
- }
-
- public class ClassBuilder extends Builder {
-
- ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
- super(name, superName, implementedInterfaces);
- }
-
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(".class public ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(name));
- builder.append("\n");
- appendSuper(builder);
- appendImplementedInterfaces(builder);
- builder.append("\n");
- if (sourceFile != null) {
- builder.append(".source \"").append(sourceFile).append("\"\n");
- }
- writeSource(builder);
- return builder.toString();
- }
- }
-
- public class InterfaceBuilder extends Builder {
-
- InterfaceBuilder(String name, String superName) {
- super(name, superName, ImmutableList.of());
- }
-
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(".class public interface abstract ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(name));
- builder.append("\n");
- appendSuper(builder);
- appendImplementedInterfaces(builder);
- builder.append("\n");
- writeSource(builder);
- return builder.toString();
- }
- }
-
- private String currentClassName;
- private final Map<String, Builder> classes = new HashMap<>();
-
- public SmaliBuilder() {
- // No default class.
- }
-
- public SmaliBuilder(String name) {
- addClass(name);
- }
-
- public SmaliBuilder(String name, String superName) {
- addClass(name, superName);
- }
-
- private List<String> getSource(String clazz) {
- return classes.get(clazz).source;
- }
-
- public String getCurrentClassName() {
- return currentClassName;
- }
-
- public String getCurrentClassDescriptor() {
- return DescriptorUtils.javaTypeToDescriptor(currentClassName);
- }
-
- public void addClass(String name) {
- addClass(name, "java.lang.Object");
- }
-
- public void addClass(String name, String superName) {
- addClass(name, superName, ImmutableList.of());
- }
-
- public void addClass(String name, String superName, List<String> implementedInterfaces) {
- assert !classes.containsKey(name);
- currentClassName = name;
- classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
- }
-
- public void addInterface(String name) {
- addInterface(name, "java.lang.Object");
- }
-
- public void addInterface(String name, String superName) {
- assert !classes.containsKey(name);
- currentClassName = name;
- classes.put(name, new InterfaceBuilder(name, superName));
- }
-
- public void setSourceFile(String file) {
- classes.get(currentClassName).sourceFile = file;
- }
-
- public void addDefaultConstructor() {
- String superDescriptor =
- DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
- addMethodRaw(
- " .method public constructor <init>()V",
- " .locals 0",
- " invoke-direct {p0}, " + superDescriptor + "-><init>()V",
- " return-void",
- " .end method"
- );
- }
-
- public void addStaticField(String name, String type, String defaultValue) {
- StringBuilder builder = new StringBuilder();
- builder.append(".field static ");
- builder.append(name);
- builder.append(":");
- builder.append(type);
- if (defaultValue != null) {
- builder.append(" = ");
- if (type.equals("Ljava/lang/String;")) {
- builder.append('"');
- builder.append(defaultValue);
- builder.append('"');
- } else {
- builder.append(defaultValue);
- }
- }
- getSource(currentClassName).add(builder.toString());
- }
-
- public void addStaticField(String name, String type) {
- addStaticField(name, type, null);
- }
-
- public void addInstanceField(String name, String type) {
- StringBuilder builder = new StringBuilder();
- builder.append(".field ");
- builder.append(name);
- builder.append(":");
- builder.append(type);
- getSource(currentClassName).add(builder.toString());
- }
-
- private MethodSignature addMethod(String flags, String returnType, String name,
- List<String> parameters, int locals, String code) {
- StringBuilder builder = new StringBuilder();
- builder.append(".method ");
- if (flags != null && flags.length() > 0) {
- builder.append(flags);
- builder.append(" ");
- }
- builder.append(name);
- builder.append("(");
- for (String parameter : parameters) {
- builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
- }
- builder.append(")");
- builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
- builder.append("\n");
- if (locals >= 0) {
- builder.append(".locals ");
- builder.append(locals);
- builder.append("\n\n");
- assert code != null;
- builder.append(code);
- } else {
- assert code == null;
- }
- builder.append(".end method");
- getSource(currentClassName).add(builder.toString());
- return new MethodSignature(currentClassName, name, returnType, parameters);
- }
-
- public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
- int locals, String... instructions) {
- StringBuilder builder = new StringBuilder();
- for (String instruction : instructions) {
- builder.append(instruction);
- builder.append("\n");
- }
- return addStaticMethod(returnType, name, parameters, locals, builder.toString());
- }
-
- public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
- int locals, String code) {
- return addStaticMethod("", returnType, name, parameters, locals, code);
- }
-
- public MethodSignature addStaticInitializer(int locals, String... instructions) {
- StringBuilder builder = new StringBuilder();
- for (String instruction : instructions) {
- builder.append(instruction);
- builder.append("\n");
- }
- return addStaticInitializer(locals, builder.toString());
- }
-
- public MethodSignature addStaticInitializer(int locals, String code) {
- return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
- }
-
- private MethodSignature addStaticMethod(String flags, String returnType, String name,
- List<String> parameters, int locals, String code) {
- StringBuilder builder = new StringBuilder();
- return addMethod("public static " + flags, returnType, name, parameters, locals, code);
- }
-
- public MethodSignature addAbstractMethod(
- String returnType, String name, List<String> parameters) {
- return addMethod("public abstract", returnType, name, parameters, -1, null);
- }
-
- public MethodSignature addInstanceMethod(String returnType, String name,
- List<String> parameters,
- int locals, String... instructions) {
- StringBuilder builder = new StringBuilder();
- for (String instruction : instructions) {
- builder.append(instruction);
- builder.append("\n");
- }
- return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
- }
-
- public MethodSignature addInstanceMethod(String returnType, String name,
- List<String> parameters,
- int locals, String code) {
- return addMethod("public", returnType, name, parameters, locals, code);
- }
-
- public MethodSignature addMainMethod(int locals, String... instructions) {
- return addStaticMethod(
- "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
- }
-
- public void addMethodRaw(String... source) {
- StringBuilder builder = new StringBuilder();
- for (String line : source) {
- builder.append(line);
- builder.append("\n");
- }
- getSource(currentClassName).add(builder.toString());
- }
-
- public List<String> buildSource() {
- List<String> result = new ArrayList<>(classes.size());
- for (String clazz : classes.keySet()) {
- Builder classBuilder = classes.get(clazz);
- result.add(classBuilder.toString());
- }
- return result;
- }
-
- public byte[] compile()
- throws IOException, RecognitionException, DexOverflowException, ExecutionException {
- return Smali.compile(buildSource());
- }
-
- public AndroidApp build()
- throws IOException, RecognitionException, DexOverflowException, ExecutionException {
- return AndroidApp.fromDexProgramData(compile());
- }
-
-
- @Override
- public String toString() {
- return String.join("\n\n", buildSource());
- }
- }
-
- public class TestApplication {
-
- public final DexApplication application;
- public final DexEncodedMethod method;
- public final IRCode code;
- public final List<IRCode> additionalCode;
- public final ValueNumberGenerator valueNumberGenerator;
- public final InternalOptions options;
-
- public TestApplication(
- DexApplication application,
- DexEncodedMethod method,
- IRCode code,
- ValueNumberGenerator valueNumberGenerator,
- InternalOptions options) {
- this(application, method, code, null, valueNumberGenerator, options);
- }
-
- public TestApplication(
- DexApplication application,
- DexEncodedMethod method,
- IRCode code,
- List<IRCode> additionalCode,
- ValueNumberGenerator valueNumberGenerator,
- InternalOptions options) {
- this.application = application;
- this.method = method;
- this.code = code;
- this.additionalCode = additionalCode;
- this.valueNumberGenerator = valueNumberGenerator;
- this.options = options;
- }
-
- public int countArgumentInstructions() {
- int count = 0;
- ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
- while (iterator.next().isArgument()) {
- count++;
- }
- return count;
- }
-
- public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
- InstructionListIterator iterator = block.listIterator();
- for (int i = 0; i < index; i++) {
- iterator.next();
- }
- return iterator;
- }
-
- public String run() throws DexOverflowException {
- AppInfo appInfo = new AppInfo(application);
- IRConverter converter = new IRConverter(appInfo, options);
- converter.replaceCodeForTesting(method, code);
- return runArt(application, options);
- }
- }
-
- protected DexApplication buildApplication(SmaliBuilder builder) {
- return buildApplication(builder, new InternalOptions());
- }
-
- protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+ protected AndroidApp buildApplication(SmaliBuilder builder) {
try {
- return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
- } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+ return AndroidApp.fromDexProgramData(builder.compile());
+ } catch (IOException | RecognitionException | DexOverflowException | ExecutionException e) {
throw new RuntimeException(e);
}
}
- protected DexApplication buildApplicationWithAndroidJar(
- SmaliBuilder builder, InternalOptions options) {
+ protected AndroidApp buildApplicationWithAndroidJar(SmaliBuilder builder) {
try {
- AndroidApp input = AndroidApp.builder()
+ return AndroidApp.builder()
.addDexProgramData(builder.compile())
.addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
.build();
- return buildApplication(input, options);
} catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
throw new RuntimeException(e);
}
@@ -482,10 +70,15 @@
}
}
- protected DexApplication processApplication(DexApplication application, InternalOptions options) {
+ protected AndroidApp processApplication(AndroidApp application) {
+ return processApplication(application, null);
+ }
+
+ protected AndroidApp processApplication(AndroidApp application,
+ Consumer<InternalOptions> optionsConsumer) {
try {
- return ToolHelper.optimizeWithR8(application, options);
- } catch (IOException | CompilationException | ExecutionException e) {
+ return ToolHelper.runR8(application, optionsConsumer);
+ } catch (IOException | CompilationException e) {
throw new RuntimeException(e);
}
}
@@ -535,29 +128,6 @@
}
}
- protected DexEncodedMethod getMethod(
- DexInspector inspector,
- String className,
- String returnType,
- String methodName,
- List<String> parameters) {
- ClassSubject clazz = inspector.clazz(className);
- assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method(returnType, methodName, parameters);
- assertTrue(method.isPresent());
- return method.getMethod();
- }
-
- protected DexEncodedMethod getMethod(
- DexApplication application,
- String className,
- String returnType,
- String methodName,
- List<String> parameters) {
- DexInspector inspector = new DexInspector(application);
- return getMethod(inspector, className, returnType, methodName, parameters);
- }
-
protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
try {
DexInspector inspector = new DexInspector(appPath);
@@ -572,7 +142,7 @@
}
}
- protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+ protected DexEncodedMethod getMethod(AndroidApp application, MethodSignature signature) {
return getMethod(application,
signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
}
@@ -588,16 +158,45 @@
* @param instructions instructions for the method
* @return the processed method for inspection
*/
- public DexApplication singleMethodApplication(String returnType, List<String> parameters,
+ public AndroidApp singleMethodApplication(String returnType, List<String> parameters,
int locals, String... instructions) {
// Build a one class method.
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(returnType, DEFAULT_METHOD_NAME, parameters, locals, instructions);
// Read the one class method as an application.
- DexApplication application = buildApplication(builder);
- assertEquals(1, Iterables.size(application.classes()));
- return application;
+ return buildApplication(builder);
+ }
+
+ private int getNumberOfClassesForResources(Iterable<Resource> resources) {
+ int count = 0;
+ for (Resource resource : resources) {
+ Collection<String> descriptors = resource.getClassDescriptors();
+ if (descriptors == null) {
+ throw new IllegalStateException("Cannot count classes in application without descriptors.");
+ }
+ count += descriptors.size();
+ }
+ return count;
+ }
+
+ protected int getNumberOfProgramClasses(AndroidApp application) {
+ try {
+ return getNumberOfClassesForResources(application.getClassProgramResources())
+ + getNumberOfClassesForResources(application.getDexProgramResources());
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ protected AppInfo getAppInfo(AndroidApp application) {
+ try {
+ DexApplication dexApplication = new ApplicationReader(application, new InternalOptions(),
+ new Timing("SmaliTest.getAppInfo")).read();
+ return new AppInfo(dexApplication);
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -616,30 +215,29 @@
InternalOptions options = new InternalOptions();
// Build a one class application.
- DexApplication application = singleMethodApplication(
+ AndroidApp application = singleMethodApplication(
returnType, parameters, locals, instructions);
// Process the application with R8.
- DexApplication processdApplication = processApplication(application, options);
- assertEquals(1, Iterables.size(processdApplication.classes()));
+ AndroidApp processdApplication = processApplication(application);
+ assertEquals(1, getNumberOfProgramClasses(processdApplication));
// Return the processed method for inspection.
return getMethod(
processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
}
- public String runArt(DexApplication application, InternalOptions options)
- throws DexOverflowException {
- return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+ public String runArt(AndroidApp application) throws DexOverflowException {
+ return runArt(application, DEFAULT_MAIN_CLASS_NAME);
}
- public String runArt(DexApplication application, InternalOptions options, String mainClass)
+
+ public String runArt(AndroidApp application, String mainClass)
throws DexOverflowException {
try {
- AndroidApp app = writeDex(application, options);
Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
// TODO(sgjesse): Pass in a unique temp directory for each run.
- app.writeToZip(out, OutputMode.Indexed);
+ application.writeToZip(out, OutputMode.Indexed);
return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -654,36 +252,15 @@
}
}
- public void runDex2Oat(DexApplication application, InternalOptions options)
+ public void runDex2Oat(AndroidApp application)
throws DexOverflowException {
try {
- AndroidApp app = writeDex(application, options);
Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
Path oatFile = temp.getRoot().toPath().resolve("oat-file");
- app.writeToZip(dexOut, OutputMode.Indexed);
+ application.writeToZip(dexOut, OutputMode.Indexed);
ToolHelper.runDex2Oat(dexOut, oatFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
-
- public AndroidApp writeDex(DexApplication application, InternalOptions options)
- throws DexOverflowException {
- try {
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
- R8.writeApplication(
- Executors.newSingleThreadExecutor(),
- application,
- compatSink,
- null,
- NamingLens.getIdentityLens(),
- null,
- options);
- compatSink.close();
- return compatSink.build();
- } catch (ExecutionException | IOException e) {
- throw new RuntimeException(e);
- }
- }
-
}
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
deleted file mode 100644
index 3fc329c..0000000
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ /dev/null
@@ -1,570 +0,0 @@
-// Copyright (c) 2016, 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.smali;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Const;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstHigh16;
-import com.android.tools.r8.code.IfEq;
-import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.PackedSwitch;
-import com.android.tools.r8.code.SparseSwitch;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-
-public class SwitchRewritingTest extends SmaliTestBase {
-
- private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
- assert key1 != key2;
- return Math.abs((long) key1 - (long) key2) == 1;
- }
-
- private boolean some16BitConst(Instruction instruction) {
- return instruction instanceof Const4
- || instruction instanceof ConstHigh16
- || instruction instanceof Const;
- }
- private void runSingleCaseDexTest(boolean packed, int key) {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
- String switchInstruction;
- String switchData;
- if (packed) {
- switchInstruction = "packed-switch";
- switchData = StringUtils.join(
- "\n",
- " :switch_data",
- " .packed-switch " + key,
- " :case_0",
- " .end packed-switch");
- } else {
- switchInstruction = "sparse-switch";
- switchData = StringUtils.join(
- "\n",
- " :switch_data",
- " .sparse-switch",
- " " + key + " -> :case_0",
- " .end sparse-switch");
- }
- MethodSignature signature = builder.addStaticMethod(
- "int",
- DEFAULT_METHOD_NAME,
- ImmutableList.of("int"),
- 0,
- " " + switchInstruction + " p0, :switch_data",
- " const/4 p0, 0x5",
- " goto :return",
- " :case_0",
- " const/4 p0, 0x3",
- " :return",
- " return p0",
- switchData);
-
- builder.addMainMethod(
- 2,
- " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- " const/4 v1, 0",
- " invoke-static { v1 }, LTest;->method(I)I",
- " move-result v1",
- " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
- " return-void"
- );
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- DexEncodedMethod method = getMethod(processedApplication, signature);
- DexCode code = method.getCode().asDexCode();
-
- if (key == 0) {
- assertEquals(5, code.instructions.length);
- assertTrue(code.instructions[0] instanceof IfEqz);
- } else {
- assertEquals(6, code.instructions.length);
- assertTrue(some16BitConst(code.instructions[0]));
- assertTrue(code.instructions[1] instanceof IfEq);
- }
- }
-
- @Test
- public void singleCaseDex() {
- for (boolean packed : new boolean[]{true, false}) {
- runSingleCaseDexTest(packed, Integer.MIN_VALUE);
- runSingleCaseDexTest(packed, -1);
- runSingleCaseDexTest(packed, 0);
- runSingleCaseDexTest(packed, 1);
- runSingleCaseDexTest(packed, Integer.MAX_VALUE);
- }
- }
-
- private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- MethodSignature signature = builder.addStaticMethod(
- "int",
- DEFAULT_METHOD_NAME,
- ImmutableList.of("int"),
- 0,
- " sparse-switch p0, :sparse_switch_data",
- " const/4 v0, 0x5",
- " goto :return",
- " :case_1",
- " const/4 v0, 0x3",
- " goto :return",
- " :case_2",
- " const/4 v0, 0x4",
- " :return",
- " return v0",
- " :sparse_switch_data",
- " .sparse-switch",
- " " + key1 + " -> :case_1",
- " " + key2 + " -> :case_2",
- " .end sparse-switch");
-
- builder.addMainMethod(
- 2,
- " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- " const/4 v1, 0",
- " invoke-static { v1 }, LTest;->method(I)I",
- " move-result v1",
- " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
- " return-void"
- );
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- DexEncodedMethod method = getMethod(processedApplication, signature);
- DexCode code = method.getCode().asDexCode();
- if (twoCaseWillUsePackedSwitch(key1, key2)) {
- assertTrue(code.instructions[0] instanceof PackedSwitch);
- } else {
- if (key1 == 0) {
- assertTrue(code.instructions[0] instanceof IfEqz);
- } else {
- // Const instruction before if.
- assertTrue(code.instructions[1] instanceof IfEq);
- }
- }
- }
-
- @Test
- public void twoCaseSparseToPackedOrIfsDex() {
- for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
- runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
- }
- runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
- runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
- runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
- }
-
- private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
- Integer additionalLastKey) throws Exception {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- StringBuilder switchSource = new StringBuilder();
- StringBuilder targetCode = new StringBuilder();
- for (int i = 0; i < totalKeys; i++) {
- String caseLabel = "case_" + i;
- switchSource.append(" " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
- targetCode.append(" :" + caseLabel + "\n");
- targetCode.append(" goto :return\n");
- }
- if (additionalLastKey != null) {
- String caseLabel = "case_" + totalKeys;
- switchSource.append(" " + additionalLastKey + " -> :" + caseLabel + "\n");
- targetCode.append(" :" + caseLabel + "\n");
- targetCode.append(" goto :return\n");
- }
-
- MethodSignature signature = builder.addStaticMethod(
- "void",
- DEFAULT_METHOD_NAME,
- ImmutableList.of("int"),
- 0,
- " sparse-switch p0, :sparse_switch_data",
- " goto :return",
- targetCode.toString(),
- " :return",
- " return-void",
- " :sparse_switch_data",
- " .sparse-switch",
- switchSource.toString(),
- " .end sparse-switch");
-
- builder.addMainMethod(
- 2,
- " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- " const/4 v1, 0",
- " invoke-static { v1 }, LTest;->method(I)V",
- " return-void"
- );
-
- InternalOptions options = new InternalOptions();
- options.verbose = true;
- options.printTimes = true;
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
- DexEncodedMethod method = getMethod(processedApplication, signature);
- DexCode code = method.getCode().asDexCode();
- if (keyStep <= 2) {
- assertTrue(code.instructions[0] instanceof PackedSwitch);
- } else {
- assertTrue(code.instructions[0] instanceof SparseSwitch);
- }
- }
-
- @Test
- public void twoMonsterSparseToPackedDex() throws Exception {
- runLargerSwitchDexTest(0, 1, 100, null);
- runLargerSwitchDexTest(0, 2, 100, null);
- runLargerSwitchDexTest(0, 3, 100, null);
- runLargerSwitchDexTest(100, 100, 100, null);
- runLargerSwitchDexTest(-10000, 100, 100, null);
- runLargerSwitchDexTest(-10000, 200, 100, 10000);
- runLargerSwitchDexTest(
- Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
- // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
- // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
- }
-
- private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
- String switchCode;
- if (packed) {
- switchCode = StringUtils.join(
- "\n",
- " tableswitch " + key,
- " case_0",
- " default : case_default");
- } else {
- switchCode = StringUtils.join(
- "\n",
- " lookupswitch",
- " " + key + " : case_0",
- " default : case_default");
- }
-
- clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
- " .limit stack 1",
- " .limit locals 1",
- " iload 0",
- switchCode,
- " case_0:",
- " iconst_3",
- " goto return_",
- " case_default:",
- " ldc 5",
- " return_:",
- " ireturn");
-
- clazz.addMainMethod(
- " .limit stack 2",
- " .limit locals 1",
- " getstatic java/lang/System/out Ljava/io/PrintStream;",
- " ldc 2",
- " invokestatic Test/test(I)I",
- " invokevirtual java/io/PrintStream/print(I)V",
- " return");
-
- DexApplication app = builder.read();
- app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
- MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
- DexEncodedMethod method = getMethod(app, signature);
- DexCode code = method.getCode().asDexCode();
- if (key == 0) {
- assertEquals(5, code.instructions.length);
- assertTrue(code.instructions[0] instanceof IfEqz);
- } else {
- assertEquals(6, code.instructions.length);
- assertTrue(code.instructions[1] instanceof IfEq);
- }
- }
-
- @Test
- public void singleCaseJar() throws Exception {
- for (boolean packed : new boolean[]{true, false}) {
- runSingleCaseJarTest(packed, Integer.MIN_VALUE);
- runSingleCaseJarTest(packed, -1);
- runSingleCaseJarTest(packed, 0);
- runSingleCaseJarTest(packed, 1);
- runSingleCaseJarTest(packed, Integer.MAX_VALUE);
- }
- }
-
- private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
- clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
- " .limit stack 1",
- " .limit locals 1",
- " iload 0",
- " lookupswitch",
- " " + key1 + " : case_1",
- " " + key2 + " : case_2",
- " default : case_default",
- " case_1:",
- " iconst_3",
- " goto return_",
- " case_2:",
- " iconst_4",
- " goto return_",
- " case_default:",
- " iconst_5",
- " return_:",
- " ireturn");
-
- clazz.addMainMethod(
- " .limit stack 2",
- " .limit locals 1",
- " getstatic java/lang/System/out Ljava/io/PrintStream;",
- " ldc 2",
- " invokestatic Test/test(I)I",
- " invokevirtual java/io/PrintStream/print(I)V",
- " return");
-
- DexApplication app = builder.read();
- app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
- MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
- DexEncodedMethod method = getMethod(app, signature);
- DexCode code = method.getCode().asDexCode();
- if (twoCaseWillUsePackedSwitch(key1, key2)) {
- assertTrue(code.instructions[0] instanceof PackedSwitch);
- } else {
- if (key1 == 0) {
- assertTrue(code.instructions[0] instanceof IfEqz);
- } else {
- // Const instruction before if.
- assertTrue(code.instructions[1] instanceof IfEq);
- }
- }
- }
-
- @Test
- public void twoCaseSparseToPackedJar() throws Exception {
- for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedJarTest(0, delta);
- runTwoCaseSparseToPackedJarTest(-delta, 0);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
- }
- runTwoCaseSparseToPackedJarTest(-1, 1);
- runTwoCaseSparseToPackedJarTest(-2, 1);
- runTwoCaseSparseToPackedJarTest(-1, 2);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
- }
-
- private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
- Integer additionalLastKey) throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
- StringBuilder switchSource = new StringBuilder();
- StringBuilder targetCode = new StringBuilder();
- for (int i = 0; i < totalKeys; i++) {
- String caseLabel = "case_" + i;
- switchSource.append(" " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
- targetCode.append(" " + caseLabel + ":\n");
- targetCode.append(" ldc " + i + "\n");
- targetCode.append(" goto return_\n");
- }
- if (additionalLastKey != null) {
- String caseLabel = "case_" + totalKeys;
- switchSource.append(" " + additionalLastKey + " : " + caseLabel + "\n");
- targetCode.append(" " + caseLabel + ":\n");
- targetCode.append(" ldc " + totalKeys + "\n");
- targetCode.append(" goto return_\n");
- }
-
- clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
- " .limit stack 1",
- " .limit locals 1",
- " iload 0",
- " lookupswitch",
- switchSource.toString(),
- " default : case_default",
- targetCode.toString(),
- " case_default:",
- " iconst_5",
- " return_:",
- " ireturn");
-
- clazz.addMainMethod(
- " .limit stack 2",
- " .limit locals 1",
- " getstatic java/lang/System/out Ljava/io/PrintStream;",
- " ldc 2",
- " invokestatic Test/test(I)I",
- " invokevirtual java/io/PrintStream/print(I)V",
- " return");
-
- DexApplication app = builder.read();
- app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
- MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
- DexEncodedMethod method = getMethod(app, signature);
- DexCode code = method.getCode().asDexCode();
- int packedSwitchCount = 0;
- int sparseSwitchCount = 0;
- for (Instruction instruction : code.instructions) {
- if (instruction instanceof PackedSwitch) {
- packedSwitchCount++;
- }
- if (instruction instanceof SparseSwitch) {
- sparseSwitchCount++;
- }
- }
- if (keyStep <= 2) {
- assertEquals(1, packedSwitchCount);
- assertEquals(0, sparseSwitchCount);
- } else {
- assertEquals(0, packedSwitchCount);
- assertEquals(1, sparseSwitchCount);
- }
- }
-
- @Test
- public void largerSwitchJar() throws Exception {
- runLargerSwitchJarTest(0, 1, 100, null);
- runLargerSwitchJarTest(0, 2, 100, null);
- runLargerSwitchJarTest(0, 3, 100, null);
- runLargerSwitchJarTest(100, 100, 100, null);
- runLargerSwitchJarTest(-10000, 100, 100, null);
- runLargerSwitchJarTest(-10000, 200, 100, 10000);
- runLargerSwitchJarTest(
- Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
- // This is the maximal value possible with Jasmin with the generated code above. It depends on
- // the source, so making smaller source can raise this limit. However we never get close to the
- // class file max.
- runLargerSwitchJarTest(0, 1, 5503, null);
- }
-
- private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
- int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
- StringBuilder x = new StringBuilder();
- StringBuilder y = new StringBuilder();
- for (Integer key : keys) {
- x.append(key).append(" : case_").append(key).append("\n");
- y.append("case_").append(key).append(":\n");
- y.append(" ldc ").append(key).append("\n");
- y.append(" goto return_\n");
- }
-
- clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
- " .limit stack 1",
- " .limit locals 1",
- " iload_0",
- " lookupswitch",
- x.toString(),
- " default : case_default",
- y.toString(),
- " case_default:",
- " ldc " + defaultValue,
- " return_:",
- " ireturn");
-
- // Add the Jasmin class and a class from Java source with the main method.
- AndroidApp.Builder appBuilder = AndroidApp.builder();
- appBuilder.addClassProgramData(builder.buildClasses());
- appBuilder.addProgramFiles(FilteredClassPath
- .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
- AndroidApp app = compileWithR8(appBuilder.build());
-
- DexInspector inspector = new DexInspector(app);
- MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
- DexCode code = method.getMethod().getCode().asDexCode();
-
- int packedSwitches = 0;
- int sparseSwitches = 0;
- int ifs = 0;
- for (Instruction instruction : code.instructions) {
- if (instruction instanceof PackedSwitch) {
- packedSwitches++;
- }
- if (instruction instanceof SparseSwitch) {
- sparseSwitches++;
- }
- if (instruction instanceof IfEq || instruction instanceof IfEqz) {
- ifs++;
- }
- }
-
- assertEquals(expectedPackedSwitches, packedSwitches);
- assertEquals(expectedSparceSwitches, sparseSwitches);
- assertEquals(expectedIfs, ifs);
-
- // Run the code
- List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
- args.add(Integer.toString(defaultValue));
- runOnArt(app, CheckSwitchInTestClass.class, args);
- }
-
- @Test
- public void convertCasesToIf() throws Exception {
- // Switches that are completely converted to ifs.
- runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
- runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
- runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
-
- // Switches that are completely converted to ifs and one switch.
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
- runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
- runConvertCasesToIf(ImmutableList.of(
- Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
- runConvertCasesToIf(ImmutableList.of(
- 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
- runConvertCasesToIf(ImmutableList.of(
- Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
-
- // Switches that are completely converted to ifs and two switches.
- runConvertCasesToIf(ImmutableList.of(
- 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
- runConvertCasesToIf(ImmutableList.of(
- -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
- runConvertCasesToIf(ImmutableList.of(
- -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
-
- // Switches that are completely converted two switches (one sparse and one packed).
- runConvertCasesToIf(ImmutableList.of(
- -1000, -900, -800, -700, -600, -500, -400, -300,
- 1000, 1001, 1002, 1003, 1004,
- 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
- }
-}
\ No newline at end of file
diff --git a/tests/api_usage_sample.jar b/tests/api_usage_sample.jar
new file mode 100644
index 0000000..b8b9d66
--- /dev/null
+++ b/tests/api_usage_sample.jar
Binary files differ
diff --git a/tools/archive.py b/tools/archive.py
index b88eb8d..20d057c 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -4,6 +4,7 @@
# BSD-style license that can be found in the LICENSE file.
import gradle
+import create_maven_release
import d8
import os
import r8
@@ -42,11 +43,11 @@
if 'origin/master' in branches:
raise Exception('We are seeing origin/master in a commit that '
'don\'t have -dev in version')
- return False;
+ return False
if not 'origin/master' in branches:
raise Exception('We are not seeing origin/master '
'in a commit that have -dev in version')
- return True;
+ return True
def GetStorageDestination(storage_prefix, version, file_name, is_master):
# We archive master commits under raw/master instead of directly under raw
@@ -64,27 +65,33 @@
def Main():
if not 'BUILDBOT_BUILDERNAME' in os.environ:
raise Exception('You are not a bot, don\'t archive builds')
+ # Ensure all archived artifacts has been built before archiving.
+ gradle.RunGradle([utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD])
+ create_maven_release.main(['--jar', utils.R8_JAR, "--out", utils.LIBS])
+
version = GetVersion()
- is_master = True #IsMaster(version)
+ is_master = IsMaster(version)
if is_master:
# On master we use the git hash to archive with
print 'On master, using git hash for archiving'
version = GetGitHash()
- # Ensure all archived artifacts has been built before archiving.
- gradle.RunGradle([utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD])
-
with utils.TempDir() as temp:
version_file = os.path.join(temp, 'r8-version.properties')
with open(version_file,'w') as version_writer:
version_writer.write('version.sha=' + GetGitHash() + '\n')
- version_writer.write('releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
+ version_writer.write(
+ 'releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
version_writer.write('version-file.version.code=1\n')
- for jar in [utils.D8_JAR, utils.R8_JAR, utils.COMPATDX_JAR, utils.COMPATPROGUARD_JAR]:
- file_name = os.path.basename(jar)
+ for file in [utils.D8_JAR,
+ utils.R8_JAR,
+ utils.COMPATDX_JAR,
+ utils.COMPATPROGUARD_JAR,
+ utils.MAVEN_ZIP]:
+ file_name = os.path.basename(file)
tagged_jar = os.path.join(temp, file_name)
- shutil.copyfile(jar, tagged_jar)
+ shutil.copyfile(file, tagged_jar)
with zipfile.ZipFile(tagged_jar, 'a') as zip:
zip.write(version_file, os.path.basename(version_file))
destination = GetUploadDestination(version, file_name, is_master)
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
new file mode 100755
index 0000000..6789f18
--- /dev/null
+++ b/tools/update_prebuilds_in_android.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import gradle
+import os
+import sys
+import utils
+
+from shutil import copyfile
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(
+ description = 'Build and copy jars to an Android tree.')
+ parser.add_argument('android_root', nargs=1,
+ help='Android checkout root.')
+ return parser.parse_args()
+
+def Main():
+ args = parse_arguments()
+ targets = ['r8', 'd8', 'compatdx', 'compatproguard']
+ gradle.RunGradle(targets)
+ for target in targets:
+ src = os.path.join(utils.REPO_ROOT, 'build', 'libs', target + '.jar')
+ dest = os.path.join(
+ args.android_root[0], 'prebuilts', 'r8', target + '-master.jar')
+ print 'Copying: ' + src + ' -> ' + dest
+ copyfile(src, dest)
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index 7d198fa..40a62b8 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -19,6 +19,7 @@
'dexsegments.jar')
DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
LIBS = os.path.join(REPO_ROOT, 'build', 'libs')
+MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
D8 = 'd8'
R8 = 'r8'