Merge "Fix incorrect/flaky settings in lambda-related access relaxation tests."
diff --git a/build.gradle b/build.gradle
index b733b88..dd401d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -517,6 +517,7 @@
task repackageDeps(type: ShadowJar) {
configurations = [project.configurations.compile]
configureRelocations(it)
+ exclude { it.getRelativePath().getPathString() == "module-info.class" }
baseName 'deps'
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 63c1173..b7fc6b3 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.3.5-dev";
+ public static final String LABEL = "1.3.7-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 7508ea0..644f2c2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -35,6 +35,10 @@
return position;
}
+ public CfLabel getLabel() {
+ return label;
+ }
+
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
state.setPosition(position);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 5bed061..8ab2c1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
import com.android.tools.r8.graph.DexItemFactory;
@@ -467,7 +468,7 @@
return instruction;
}
}
- return null;
+ throw new Unreachable();
}
public void clearUserInfo() {
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 00a94a9..f276cb2 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
@@ -87,6 +87,8 @@
private List<InvokeDirect> thisInitializers;
private Map<NewInstance, CfLabel> newInstanceLabels;
+ private InternalOptions options;
+
// Internal abstraction of the stack values and height.
private static class Stack {
int maxHeight = 0;
@@ -123,6 +125,7 @@
GraphLense graphLense,
InternalOptions options,
AppInfoWithSubtyping appInfo) {
+ this.options = options;
computeInitializers();
types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
splitExceptionalBlocks();
@@ -379,7 +382,10 @@
private void updatePositionAndLocals(Instruction instruction) {
Position position = instruction.getPosition();
boolean didLocalsChange = localsChanged();
- boolean didPositionChange = position.isSome() && position != currentPosition;
+ boolean didPositionChange =
+ position.isSome()
+ && position != currentPosition
+ && (options.debug || instruction.instructionTypeCanThrow());
if (!didLocalsChange && !didPositionChange) {
return;
}
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 fffbc44..c6ba97d 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
@@ -46,7 +46,6 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
-import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -696,7 +695,6 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
- new RedundantFieldLoadElimination(code).run();
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
deleted file mode 100644
index 24012b1..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) 2018, 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.optimize;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.FieldInstruction;
-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.Phi;
-import com.android.tools.r8.ir.code.Value;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Eliminate redundant field loads.
- *
- * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
- * active field sets across control-flow edges where the target has only one predecessor.
- */
-// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
-public class RedundantFieldLoadElimination {
-
- private final IRCode code;
- private final DominatorTree dominatorTree;
-
- // Maps keeping track of fields that have an already loaded value at basic block entry.
- private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
- activeInstanceFieldsAtEntry = new HashMap<>();
- private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
- new HashMap<>();
-
- // Maps keeping track of fields with already loaded values for the current block during
- // elimination.
- private HashMap<FieldAndObject, Instruction> activeInstanceFields;
- private HashMap<DexField, Instruction> activeStaticFields;
-
- public RedundantFieldLoadElimination(IRCode code) {
- this.code = code;
- dominatorTree = new DominatorTree(code);
- }
-
- private static class FieldAndObject {
- private final DexField field;
- private final Value object;
-
- private FieldAndObject(DexField field, Value receiver) {
- this.field = field;
- this.object = receiver;
- }
-
- @Override
- public int hashCode() {
- return field.hashCode() * 7 + object.hashCode();
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof FieldAndObject)) {
- return false;
- }
- FieldAndObject o = (FieldAndObject) other;
- return o.object == object && o.field == field;
- }
- }
-
- public void run() {
- for (BasicBlock block : dominatorTree.getSortedBlocks()) {
- activeInstanceFields =
- activeInstanceFieldsAtEntry.containsKey(block)
- ? activeInstanceFieldsAtEntry.get(block)
- : new HashMap<>();
- activeStaticFields =
- activeStaticFieldsAtEntry.containsKey(block)
- ? activeStaticFieldsAtEntry.get(block)
- : new HashMap<>();
- InstructionListIterator it = block.listIterator();
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isFieldInstruction()) {
- DexField field = instruction.asFieldInstruction().getField();
- if (instruction.isInstancePut() || instruction.isStaticPut()) {
- killActiveFields(instruction.asFieldInstruction());
- } else if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
- Value object = instruction.asInstanceGet().object();
- FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- if (activeInstanceFields.containsKey(fieldAndObject)) {
- Instruction active = activeInstanceFields.get(fieldAndObject);
- eliminateRedundantRead(it, instruction, active);
- } else {
- activeInstanceFields.put(fieldAndObject, instruction);
- }
- } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
- if (activeStaticFields.containsKey(field)) {
- Instruction active = activeStaticFields.get(field);
- eliminateRedundantRead(it, instruction, active);
- } else {
- // A field get on a different class can cause <clinit> to run and change static
- // field values.
- killActiveFields(instruction.asFieldInstruction());
- activeStaticFields.put(field, instruction);
- }
- }
- }
- if (instruction.isMonitor() || instruction.isInvokeMethod()) {
- activeInstanceFields.clear();
- activeStaticFields.clear();
- }
- }
- propagateActiveFieldsFrom(block);
- }
- assert code.isConsistentSSA();
- }
-
- private void propagateActiveFieldsFrom(BasicBlock block) {
- for (BasicBlock successor : block.getSuccessors()) {
- // Allow propagation across exceptional edges, just be careful not to propagate if the
- // throwing instruction is a field instruction.
- if (successor.getPredecessors().size() == 1) {
- if (block.hasCatchSuccessor(successor)) {
- Instruction exceptionalExit = block.exceptionalExit();
- if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
- killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
- }
- }
- assert !activeInstanceFieldsAtEntry.containsKey(successor);
- activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
- assert !activeStaticFieldsAtEntry.containsKey(successor);
- activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
- }
- }
- }
-
- private void killActiveFields(FieldInstruction instruction) {
- DexField field = instruction.getField();
- if (instruction.isInstancePut()) {
- // Remove all the field/object pairs that refer to this field to make sure
- // that we are conservative.
- List<FieldAndObject> keysToRemove = new ArrayList<>();
- for (FieldAndObject key : activeInstanceFields.keySet()) {
- if (key.field == field) {
- keysToRemove.add(key);
- }
- }
- keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
- } else if (instruction.isInstanceGet()) {
- Value object = instruction.asInstanceGet().object();
- FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- activeInstanceFields.remove(fieldAndObject);
- } else if (instruction.isStaticPut()) {
- if (field.clazz != code.method.method.holder) {
- // Accessing a static field on a different object could cause <clinit> to run which
- // could modify any static field on any other object.
- activeStaticFields.clear();
- } else {
- activeStaticFields.remove(field);
- }
- } else if (instruction.isStaticGet()) {
- if (field.clazz != code.method.method.holder) {
- // Accessing a static field on a different object could cause <clinit> to run which
- // could modify any static field on any other object.
- activeStaticFields.clear();
- }
- }
- }
-
- // If a field get instruction throws an exception it did not have an effect on the
- // value of the field. Therefore, when propagating across exceptional edges for a
- // field get instruction we have to exclude that field from the set of known
- // field values.
- private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
- DexField field = instruction.getField();
- if (instruction.isInstanceGet()) {
- Value object = instruction.asInstanceGet().object();
- FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- activeInstanceFields.remove(fieldAndObject);
- } else if (instruction.isStaticGet()) {
- activeStaticFields.remove(field);
- }
- }
-
- private void eliminateRedundantRead(
- InstructionListIterator it, Instruction redundant, Instruction active) {
- redundant.outValue().replaceUsers(active.outValue());
- it.removeOrReplaceByDebugLocalRead();
- active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f10ff69..7d7baf8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
@@ -119,17 +122,15 @@
// PositionRemapper is a stateful function which takes a position (represented by a
// DexDebugPositionState) and returns a remapped Position.
private interface PositionRemapper {
- Position createRemappedPosition(DexDebugPositionState positionState);
+ Position createRemappedPosition(
+ int line, DexString file, DexMethod method, Position callerPosition);
}
private static class IdentityPositionRemapper implements PositionRemapper {
@Override
- public Position createRemappedPosition(DexDebugPositionState positionState) {
- return new Position(
- positionState.getCurrentLine(),
- positionState.getCurrentFile(),
- positionState.getCurrentMethod(),
- positionState.getCurrentCallerPosition());
+ public Position createRemappedPosition(
+ int line, DexString file, DexMethod method, Position callerPosition) {
+ return new Position(line, file, method, callerPosition);
}
}
@@ -137,13 +138,9 @@
private int nextLineNumber = 1;
@Override
- public Position createRemappedPosition(DexDebugPositionState positionState) {
- Position newPosition =
- new Position(
- nextLineNumber,
- positionState.getCurrentFile(),
- positionState.getCurrentMethod(),
- null);
+ public Position createRemappedPosition(
+ int line, DexString file, DexMethod method, Position callerPosition) {
+ Position newPosition = new Position(nextLineNumber, file, method, null);
++nextLineNumber;
return newPosition;
}
@@ -252,57 +249,14 @@
for (DexEncodedMethod method : methods) {
List<MappedPosition> mappedPositions = new ArrayList<>();
- if (doesContainPositions(method)) {
- // Do the actual processing for each method.
- DexCode dexCode = method.getCode().asDexCode();
- DexDebugInfo debugInfo = dexCode.getDebugInfo();
- List<DexDebugEvent> processedEvents = new ArrayList<>();
-
- // Our pipeline will be:
- // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
- // [processedEvents]
- PositionEventEmitter positionEventEmitter =
- new PositionEventEmitter(
- application.dexItemFactory, method.method, processedEvents);
-
- EventFilter eventFilter =
- new EventFilter(
- debugInfo.startLine,
- method.method,
- processedEvents::add,
- positionState -> {
- int currentLine = positionState.getCurrentLine();
- assert currentLine >= 0;
- Position position = positionRemapper.createRemappedPosition(positionState);
- mappedPositions.add(
- new MappedPosition(
- positionState.getCurrentMethod(),
- currentLine,
- positionState.getCurrentCallerPosition(),
- position.line));
- positionEventEmitter.emitPositionEvents(
- positionState.getCurrentPc(), position);
- });
- for (DexDebugEvent event : debugInfo.events) {
- event.accept(eventFilter);
+ Code code = method.getCode();
+ if (code != null) {
+ if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
+ optimizeDexCodePositions(
+ method, application, positionRemapper, mappedPositions, identityMapping);
+ } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
+ optimizeCfCodePositions(method, positionRemapper, mappedPositions);
}
-
- DexDebugInfo optimizedDebugInfo =
- new DexDebugInfo(
- positionEventEmitter.getStartLine(),
- debugInfo.parameters,
- processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
-
- // TODO(tamaskenez) Remove this as soon as we have external tests testing not only the
- // remapping but whether the non-positional debug events remain intact.
- if (identityMapping) {
- assert optimizedDebugInfo.startLine == debugInfo.startLine;
- assert optimizedDebugInfo.events.length == debugInfo.events.length;
- for (int i = 0; i < debugInfo.events.length; ++i) {
- assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
- }
- }
- dexCode.setDebugInfo(optimizedDebugInfo);
}
MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
@@ -382,21 +336,33 @@
return classNameMapperBuilder.build();
}
+ private static int getMethodStartLine(DexEncodedMethod method) {
+ Code code = method.getCode();
+ if (code == null) {
+ return 0;
+ }
+ if (code.isDexCode()) {
+ DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+ return dexDebugInfo == null ? 0 : dexDebugInfo.startLine;
+ } else if (code.isCfCode()) {
+ List<CfInstruction> instructions = code.asCfCode().getInstructions();
+ for (CfInstruction instruction : instructions) {
+ if (!(instruction instanceof CfPosition)) {
+ continue;
+ }
+ return ((CfPosition) instruction).getPosition().line;
+ }
+ }
+ return 0;
+ }
+
// Sort by startline, then DexEncodedMethod.slowCompare.
// Use startLine = 0 if no debuginfo.
private static void sortMethods(List<DexEncodedMethod> methods) {
methods.sort(
(lhs, rhs) -> {
- Code lhsCode = lhs.getCode();
- Code rhsCode = rhs.getCode();
- DexCode lhsDexCode =
- lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
- DexCode rhsDexCode =
- rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
- DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
- DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
- int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
- int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+ int lhsStartLine = getMethodStartLine(lhs);
+ int rhsStartLine = getMethodStartLine(rhs);
int startLineDiff = lhsStartLine - rhsStartLine;
if (startLineDiff != 0) return startLineDiff;
return DexEncodedMethod.slowCompare(lhs, rhs);
@@ -453,10 +419,19 @@
private static boolean doesContainPositions(DexEncodedMethod method) {
Code code = method.getCode();
- if (code == null || !code.isDexCode()) {
+ if (code == null) {
return false;
}
- DexDebugInfo debugInfo = code.asDexCode().getDebugInfo();
+ if (code.isDexCode()) {
+ return doesContainPositions(code.asDexCode());
+ } else if (code.isCfCode()) {
+ return doesContainPositions(code.asCfCode());
+ }
+ return false;
+ }
+
+ private static boolean doesContainPositions(DexCode dexCode) {
+ DexDebugInfo debugInfo = dexCode.getDebugInfo();
if (debugInfo == null) {
return false;
}
@@ -467,4 +442,114 @@
}
return false;
}
+
+ private static boolean doesContainPositions(CfCode cfCode) {
+ List<CfInstruction> instructions = cfCode.getInstructions();
+ for (CfInstruction instruction : instructions) {
+ if (instruction instanceof CfPosition) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void optimizeDexCodePositions(
+ DexEncodedMethod method,
+ DexApplication application,
+ PositionRemapper positionRemapper,
+ List<MappedPosition> mappedPositions,
+ boolean identityMapping) {
+ // Do the actual processing for each method.
+ DexCode dexCode = method.getCode().asDexCode();
+ DexDebugInfo debugInfo = dexCode.getDebugInfo();
+ List<DexDebugEvent> processedEvents = new ArrayList<>();
+
+ // Our pipeline will be:
+ // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
+ // [processedEvents]
+ PositionEventEmitter positionEventEmitter =
+ new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+
+ EventFilter eventFilter =
+ new EventFilter(
+ debugInfo.startLine,
+ method.method,
+ processedEvents::add,
+ positionState -> {
+ int currentLine = positionState.getCurrentLine();
+ assert currentLine >= 0;
+ Position position =
+ positionRemapper.createRemappedPosition(
+ positionState.getCurrentLine(),
+ positionState.getCurrentFile(),
+ positionState.getCurrentMethod(),
+ positionState.getCurrentCallerPosition());
+ mappedPositions.add(
+ new MappedPosition(
+ positionState.getCurrentMethod(),
+ currentLine,
+ positionState.getCurrentCallerPosition(),
+ position.line));
+ positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
+ });
+ for (DexDebugEvent event : debugInfo.events) {
+ event.accept(eventFilter);
+ }
+
+ DexDebugInfo optimizedDebugInfo =
+ new DexDebugInfo(
+ positionEventEmitter.getStartLine(),
+ debugInfo.parameters,
+ processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
+
+ // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
+ // remapping but whether the non-positional debug events remain intact.
+ if (identityMapping) {
+ assert optimizedDebugInfo.startLine == debugInfo.startLine;
+ assert optimizedDebugInfo.events.length == debugInfo.events.length;
+ for (int i = 0; i < debugInfo.events.length; ++i) {
+ assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
+ }
+ }
+ dexCode.setDebugInfo(optimizedDebugInfo);
+ }
+
+ private static void optimizeCfCodePositions(
+ DexEncodedMethod method,
+ PositionRemapper positionRemapper,
+ List<MappedPosition> mappedPositions) {
+ // Do the actual processing for each method.
+ CfCode oldCode = method.getCode().asCfCode();
+ List<CfInstruction> oldInstructions = oldCode.getInstructions();
+ List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
+ for (int i = 0; i < oldInstructions.size(); ++i) {
+ CfInstruction oldInstruction = oldInstructions.get(i);
+ CfInstruction newInstruction;
+ if (oldInstruction instanceof CfPosition) {
+ CfPosition cfPosition = (CfPosition) oldInstruction;
+ Position oldPosition = cfPosition.getPosition();
+ Position newPosition =
+ positionRemapper.createRemappedPosition(
+ oldPosition.line, oldPosition.file, oldPosition.method, oldPosition.callerPosition);
+ mappedPositions.add(
+ new MappedPosition(
+ oldPosition.method,
+ oldPosition.line,
+ oldPosition.callerPosition,
+ newPosition.line));
+ newInstruction = new CfPosition(cfPosition.getLabel(), newPosition);
+ } else {
+ newInstruction = oldInstruction;
+ }
+ newInstructions.add(newInstruction);
+ }
+ method.setCode(
+ new CfCode(
+ oldCode.getMethod(),
+ oldCode.getMaxStack(),
+ oldCode.getMaxLocals(),
+ newInstructions,
+ oldCode.getTryCatchRanges(),
+ oldCode.getLocalVariables()));
+ }
}
diff --git a/src/test/examples/shaking19/Shaking.java b/src/test/examples/shaking19/Shaking.java
new file mode 100644
index 0000000..da8de41
--- /dev/null
+++ b/src/test/examples/shaking19/Shaking.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2018, 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 shaking19;
+
+public class Shaking {
+
+ public static void main(String[] args) {
+ A obj = new B();
+ obj.m();
+ }
+
+ public static class A {
+
+ // Since A is never instantiated and B overrides method m(), this is dead code.
+ public void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ public static class B extends A {
+
+ @Override
+ public void m() {
+ System.out.println("In B.m()");
+ }
+ }
+}
diff --git a/src/test/examples/shaking19/keep-rules.txt b/src/test/examples/shaking19/keep-rules.txt
new file mode 100644
index 0000000..cbc332e
--- /dev/null
+++ b/src/test/examples/shaking19/keep-rules.txt
@@ -0,0 +1,9 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking19.Shaking {
+ public static void main(...);
+}
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
deleted file mode 100644
index 781d2b4..0000000
--- a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2018 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 uninitializedfinal;
-
-// Test that leaks an instance before its final field has been initialized to a thread that
-// reads that field. This tests that redundant field load elimination does not eliminate
-// field reads (even of final fields) that cross a monitor operation.
-public class UninitializedFinalFieldLeak {
-
- public static class PollingThread extends Thread {
- public int result = 0;
- UninitializedFinalFieldLeak f;
-
- PollingThread(UninitializedFinalFieldLeak f) {
- this.f = f;
- }
-
- // Read the field a number of times. Then lock on the object to await field initialization.
- public void run() {
- result += f.i;
- result += f.i;
- result += f.i;
- f.threadReadsDone = true;
- synchronized (f) {
- result += f.i;
- }
- // The right result is 42. Reading the uninitialized 0 three times and then
- // reading the initialized value. It is safe to remove the two redundant loads
- // before the monitor operation.
- System.out.println(result);
- }
- }
-
- public final int i;
- public volatile boolean threadReadsDone = false;
-
- public UninitializedFinalFieldLeak() throws InterruptedException {
- // Leak the object to a thread and start the thread with the lock on the object taken.
- // Then allow the other thread to run and read the uninitialized field.
- // Finally, initialize the field and release the lock.
- PollingThread t = new PollingThread(this);
- synchronized (this) {
- t.start();
- while (!threadReadsDone) {
- Thread.yield();
- }
- i = 42;
- }
- t.join();
- }
-
- public static void main(String[] args) throws InterruptedException {
- new UninitializedFinalFieldLeak();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 221d427..36f7594 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,11 +45,9 @@
"instancevariable.InstanceVariable",
"instanceofstring.InstanceofString",
"invoke.Invoke",
- "invokeempty.InvokeEmpty",
"jumbostring.JumboString",
"loadconst.LoadConst",
"loop.UdpServer",
- "nestedtrycatches.NestedTryCatches",
"newarray.NewArray",
"regalloc.RegAlloc",
"returns.Returns",
@@ -60,7 +58,9 @@
"throwing.Throwing",
"trivial.Trivial",
"trycatch.TryCatch",
+ "nestedtrycatches.NestedTryCatches",
"trycatchmany.TryCatchMany",
+ "invokeempty.InvokeEmpty",
"regress.Regress",
"regress2.Regress2",
"regress_37726195.Regress",
@@ -82,7 +82,6 @@
"enclosingmethod_proguarded.Main",
"interfaceinlining.Main",
"switchmaps.Switches",
- "uninitializedfinal.UninitializedFinalFieldLeak",
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d944a75..6e1c05f 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -7,12 +7,18 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
/** Tests source file and line numbers on inlined methods. */
+@RunWith(Parameterized.class)
public class LineNumberOptimizationTest extends DebugTestBase {
private static final int[] ORIGINAL_LINE_NUMBERS = {20, 7, 8, 28, 8, 20, 21, 12, 21, 22, 16, 22};
@@ -26,25 +32,53 @@
private static final String FILE2 = CLASS2 + ".java";
private static final String MAIN_SIGNATURE = "([Ljava/lang/String;)V";
+ private RuntimeKind runtimeKind;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ return ImmutableList.of(
+ new Object[] {"CF", RuntimeKind.CF}, new Object[] {"DEX", RuntimeKind.DEX});
+ }
+
+ public LineNumberOptimizationTest(String name, RuntimeKind runtimeKind) {
+ this.runtimeKind = runtimeKind;
+ }
+
private static DebugTestConfig makeConfig(
LineNumberOptimization lineNumberOptimization,
boolean writeProguardMap,
- boolean dontOptimizeByEnablingDebug)
+ boolean dontOptimizeByEnablingDebug,
+ RuntimeKind runtimeKind)
throws Exception {
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path outdir = temp.newFolder().toPath();
Path outjar = outdir.resolve("r8_compiled.jar");
- Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
- .setOutput(outjar, OutputMode.DexIndexed);
- if (proguardMapPath != null) {
- builder.setProguardMapOutputPath(proguardMapPath);
+ .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+ DebugTestConfig config = null;
+
+ if (runtimeKind == RuntimeKind.CF) {
+ builder.setOutput(outjar, OutputMode.ClassFile);
+ config = new CfDebugTestConfig(outjar);
+ } else {
+ assert (runtimeKind == RuntimeKind.DEX);
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ builder
+ .setMinApiLevel(minSdk.getLevel())
+ .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+ .setOutput(outjar, OutputMode.DexIndexed);
+ config = new D8DebugTestConfig();
}
+
+ config.addPaths(outjar);
+ if (writeProguardMap) {
+ Path proguardMapPath = outdir.resolve("proguard.map");
+ builder.setProguardMapOutputPath(proguardMapPath);
+ config.setProguardMap(proguardMapPath);
+ }
+
ToolHelper.runR8(
builder.build(),
options -> {
@@ -53,46 +87,52 @@
}
options.enableInlining = false;
});
- DebugTestConfig config = new D8DebugTestConfig();
- config.addPaths(outjar);
- config.setProguardMap(proguardMapPath);
+
return config;
}
@Test
public void testIdentityCompilation() throws Throwable {
// Compilation will fail if the identity translation does.
- makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false);
+ makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
}
@Test
public void testNotOptimized() throws Throwable {
- testRelease(makeConfig(LineNumberOptimization.OFF, false, false), ORIGINAL_LINE_NUMBERS);
+ testRelease(
+ makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
}
@Test
public void testNotOptimizedWithMap() throws Throwable {
- testRelease(makeConfig(LineNumberOptimization.OFF, true, false), ORIGINAL_LINE_NUMBERS);
+ testRelease(
+ makeConfig(LineNumberOptimization.OFF, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
}
@Test
public void testNotOptimizedByEnablingDebug() throws Throwable {
- testDebug(makeConfig(LineNumberOptimization.OFF, false, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+ testDebug(
+ makeConfig(LineNumberOptimization.OFF, false, true, runtimeKind),
+ ORIGINAL_LINE_NUMBERS_DEBUG);
}
@Test
public void testNotOptimizedByEnablingDebugWithMap() throws Throwable {
- testDebug(makeConfig(LineNumberOptimization.OFF, true, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+ testDebug(
+ makeConfig(LineNumberOptimization.OFF, true, true, runtimeKind),
+ ORIGINAL_LINE_NUMBERS_DEBUG);
}
@Test
public void testOptimized() throws Throwable {
- testRelease(makeConfig(LineNumberOptimization.ON, false, false), OPTIMIZED_LINE_NUMBERS);
+ testRelease(
+ makeConfig(LineNumberOptimization.ON, false, false, runtimeKind), OPTIMIZED_LINE_NUMBERS);
}
@Test
public void testOptimizedWithMap() throws Throwable {
- testRelease(makeConfig(LineNumberOptimization.ON, true, false), ORIGINAL_LINE_NUMBERS);
+ testRelease(
+ makeConfig(LineNumberOptimization.ON, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
}
private void testDebug(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
new file mode 100644
index 0000000..e5be477
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2018, 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.shaking.examples;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+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.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TreeShaking19Test extends TreeShakingTest {
+
+ @Parameters(name = "mode:{0}-{1} minify:{2}")
+ public static Collection<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ for (MinifyMode minify : MinifyMode.values()) {
+ parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+ parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+ parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+ }
+ return parameters;
+ }
+
+ public TreeShaking19Test(Frontend frontend, Backend backend, MinifyMode minify) {
+ super("examples/shaking19", "shaking19.Shaking", frontend, backend, minify);
+ }
+
+ @Ignore("b/111199171")
+ @Test
+ public void test() throws Exception {
+ runTest(
+ TreeShaking19Test::unusedRemoved,
+ null,
+ null,
+ ImmutableList.of("src/test/examples/shaking19/keep-rules.txt"),
+ // Disable vertical class merging to prevent A from being merged into B.
+ opt -> opt.enableClassMerging = false);
+ }
+
+ private static void unusedRemoved(DexInspector inspector) {
+ ClassSubject clazz = inspector.clazz("shaking19.Shaking$A");
+ assertThat(clazz, isPresent());
+
+ MethodSubject method = clazz.method("void", "m", ImmutableList.of());
+ assertThat(method, not(isPresent()));
+ }
+}