Merge commit '65ab16a350c417b2da9db0506ed95f3cc6156527' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index 34f4de6..3887ee7 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -337,31 +337,13 @@ timing.end(); } - timing.time("Finalize synthetics", () -> finalizeApplication(appView, executor, timing)); - - timing.time( - "Horizontal merger", - () -> - HorizontalClassMerger.createForD8ClassMerging(appView) - .runIfNecessary(executor, timing)); - - timing.time( - "Signature rewriter", - () -> - new GenericSignatureRewriter(appView) - .runForD8(appView.appInfo().classes(), executor)); - - timing.time( - "Kotlin metadata rewriter", () -> new KotlinMetadataRewriter(appView).runForD8(executor)); + finalizeApplication(appView, executor, timing); timing.end(); // post-converter if (options.isGeneratingClassFiles()) { new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp); } else { - if (options.apiModelingOptions().enableStubbingOfClasses) { - new ApiReferenceStubber(appView).run(executor); - } new ApplicationWriter(appView, marker == null ? null : ImmutableList.copyOf(markers)) .write(executor, inputApp); } @@ -397,7 +379,28 @@ private static void finalizeApplication( AppView<AppInfo> appView, ExecutorService executorService, Timing timing) throws ExecutionException { - SyntheticFinalization.finalize(appView, timing, executorService); + timing.time( + "Finalize synthetics", + () -> SyntheticFinalization.finalize(appView, timing, executorService)); + + timing.time( + "Horizontal merger", + () -> + HorizontalClassMerger.createForD8ClassMerging(appView) + .runIfNecessary(executorService, timing)); + + timing.time( + "Signature rewriter", + () -> + new GenericSignatureRewriter(appView) + .runForD8(appView.appInfo().classes(), executorService)); + + timing.time( + "Kotlin metadata rewriter", + () -> new KotlinMetadataRewriter(appView).runForD8(executorService)); + + timing.time( + "Api reference stubber", () -> new ApiReferenceStubber(appView).run(executorService)); } private static DexApplication rewriteNonDexInputs(
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 3e59e50..492e61a 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -163,10 +163,7 @@ } public ApplicationWriter(AppView<?> appView, List<Marker> markers) { - this( - appView, - markers, - null); + this(appView, markers, null); } public ApplicationWriter(
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 b785927..51a4a4a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -447,10 +447,6 @@ return isInstanceInitializer() || isClassInitializer(); } - public boolean isInitializer(boolean isStatic) { - return isStatic ? isClassInitializer() : isInstanceInitializer(); - } - public boolean isInstanceInitializer() { checkIfObsolete(); return accessFlags.isConstructor() && !accessFlags.isStatic();
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 c0aaa95..8fe1c7e 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2172,6 +2172,10 @@ assert false : "Unexpected invoke targeting `" + invokedMethod.toSourceString() + "`"; return false; } + + public boolean isAppendCharArrayMethod(DexMethod method) { + return method == appendCharArray || method == appendSubCharArray; + } } public class SupplierMembers extends LibraryMembers {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java index e037345..342a3fb 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
@@ -52,7 +52,7 @@ } else if (clazz.type == appView.dexItemFactory().enumType) { result = true; } else { - DexClass superClass = appView.definitionFor(clazz.superType); + DexClass superClass = clazz.hasSuperType() ? appView.definitionFor(clazz.superType) : null; result = superClass != null && isEnumSubtype(superClass); } cache.put(clazz, result);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java index 4e43590..13f8213 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -159,18 +159,24 @@ return true; } - // Check that all usages of the array are array stores. - for (Instruction user : array.uniqueUsers()) { - if (!user.isArrayPut() || user.asArrayPut().array() != array) { - return true; - } - } - - if (array.numberOfPhiUsers() > 0) { + if (array.hasPhiUsers()) { // The array could be used indirectly. return true; } + // Check that all usages of the array are array stores. + for (Instruction user : array.aliasedUsers()) { + if (user.isAssume()) { + if (user.outValue().hasPhiUsers()) { + return true; + } + continue; + } + if (!user.isArrayPut() || user.asArrayPut().array().getAliasedValue() != array) { + return true; + } + } + return false; }
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 432311b..550ea8b 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
@@ -22,6 +22,7 @@ import com.android.tools.r8.ir.conversion.IRBuilder; import com.android.tools.r8.utils.CfgPrinter; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.StringUtils.BraceType; @@ -53,6 +54,7 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; /** * Basic block abstraction. @@ -718,6 +720,10 @@ return instructions; } + public <T extends Instruction> Iterable<T> getInstructions(Predicate<Instruction> predicate) { + return IterableUtils.filter(getInstructions(), predicate); + } + public Iterable<Instruction> instructionsAfter(Instruction instruction) { return () -> iterator(instruction); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java index bdbe697..1d40f99 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -33,6 +33,7 @@ import java.util.ListIterator; import java.util.Set; import java.util.function.Consumer; +import java.util.function.UnaryOperator; public class BasicBlockInstructionListIterator implements InstructionListIterator { @@ -439,7 +440,7 @@ public void replaceCurrentInstructionWithThrow( AppView<?> appView, IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Value exceptionValue, Set<BasicBlock> blocksToRemove, Set<Value> affectedValues) { @@ -657,12 +658,18 @@ @Override public BasicBlock splitCopyCatchHandlers( - IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) { + IRCode code, + BasicBlockIterator blockIterator, + InternalOptions options, + UnaryOperator<BasicBlock> repositioningBlock) { BasicBlock splitBlock = split(code, blockIterator, false); assert !block.hasCatchHandlers(); if (splitBlock.hasCatchHandlers()) { block.copyCatchHandlers(code, blockIterator, splitBlock, options); } + if (repositioningBlock != null) { + blockIterator.positionAfterPreviousBlock(repositioningBlock.apply(splitBlock)); + } return splitBlock; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java index 4de1c80..50c15bb 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -64,6 +64,15 @@ return listIterator.previousIndex(); } + public BasicBlock positionAfterPreviousBlock(BasicBlock previousBlock) { + return positionAfterPreviousBlock(currentBlock -> currentBlock == previousBlock); + } + + public BasicBlock positionAfterPreviousBlock(Predicate<BasicBlock> predicate) { + previousUntil(predicate); + return next(); + } + public BasicBlock previousUntil(Predicate<BasicBlock> predicate) { return IteratorUtils.previousUntil(this, predicate); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java index ec663fb..4d4a55b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -13,6 +13,10 @@ TypeElement getOutType(); + boolean isInstanceGet(); + + boolean isStaticGet(); + boolean hasUsedOutValue(); Value outValue();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java index 4962981..ae400dc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -18,6 +18,7 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Consumer; +import java.util.function.UnaryOperator; public class IRCodeInstructionListIterator implements InstructionListIterator { @@ -105,7 +106,7 @@ public void replaceCurrentInstructionWithThrow( AppView<?> appView, IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Value exceptionValue, Set<BasicBlock> blocksToRemove, Set<Value> affectedValues) { @@ -135,7 +136,10 @@ @Override public BasicBlock splitCopyCatchHandlers( - IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) { + IRCode code, + BasicBlockIterator blockIterator, + InternalOptions options, + UnaryOperator<BasicBlock> repositioningBlock) { throw new Unimplemented(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java index b88555d..6f571d0 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -39,6 +39,25 @@ super(field, dest, object); } + public static Builder builder() { + return new Builder(); + } + + public static InstanceGet copyOf(IRCode code, InstanceGet original) { + Value newValue = + new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo()); + return copyOf(newValue, original); + } + + public static InstanceGet copyOf(Value newValue, InstanceGet original) { + assert newValue != original.outValue(); + return InstanceGet.builder() + .setField(original.getField()) + .setObject(original.object()) + .setOutValue(newValue) + .build(); + } + @Override public int opcode() { return Opcodes.INSTANCE_GET; @@ -234,4 +253,35 @@ public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) { return false; } + + @Override + public boolean instructionTypeCanBeCanonicalized() { + return true; + } + + public static class Builder extends BuilderBase<Builder, InstanceGet> { + + private DexField field; + private Value object; + + public Builder setField(DexField field) { + this.field = field; + return this; + } + + public Builder setObject(Value object) { + this.object = object; + return this; + } + + @Override + public InstanceGet build() { + return amend(new InstanceGet(outValue, object, field)); + } + + @Override + public Builder self() { + return this; + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java index 539f0d7..04ee188 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -19,6 +19,13 @@ Instruction previous(); + default void previous(int times) { + while (times > 0) { + previous(); + times--; + } + } + /** * Peek the next instruction. *
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java index e13fe25..0cb7228 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -19,6 +19,8 @@ import java.util.ListIterator; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; public interface InstructionListIterator extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> { @@ -29,12 +31,18 @@ Instruction instruction, InternalOptions options); - default void addBefore(Instruction instruction) { - previous(); + default void addAndPositionBeforeNewInstruction(Instruction instruction) { add(instruction); - next(); + Instruction previous = previous(); + assert previous == instruction; } + default void addBeforeAndPositionBeforeNewInstruction(Instruction instruction) { + previous(); + add(instruction); + Instruction previous = previous(); + assert previous == instruction; + } /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */ default void replaceCurrentInstruction(Instruction newInstruction) { replaceCurrentInstruction(newInstruction, null); @@ -107,6 +115,16 @@ Value value, Position position); + default Instruction positionAfterPreviousInstruction(Instruction previousInstruction) { + return positionAfterPreviousInstruction( + currentInstruction -> currentInstruction == previousInstruction); + } + + default Instruction positionAfterPreviousInstruction(Predicate<Instruction> predicate) { + previousUntil(predicate); + return next(); + } + boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context); default boolean removeOrReplaceCurrentInstructionByInitClassIfPossible( @@ -147,7 +165,7 @@ void replaceCurrentInstructionWithThrow( AppView<?> appView, IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Value exceptionValue, Set<BasicBlock> blocksToRemove, Set<Value> affectedValues); @@ -199,8 +217,16 @@ return split(code, null); } + default BasicBlock splitCopyCatchHandlers( + IRCode code, BasicBlockIterator blockIterator, InternalOptions options) { + return splitCopyCatchHandlers(code, blockIterator, options, null); + } + BasicBlock splitCopyCatchHandlers( - IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options); + IRCode code, + BasicBlockIterator blockIterator, + InternalOptions options, + UnaryOperator<BasicBlock> repositioningBlock); /** * Split the block into three blocks. The first split is at the point of the {@link ListIterator}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java index 91b51be..ed95689 100644 --- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -17,6 +17,7 @@ import java.util.ListIterator; import java.util.Set; import java.util.function.Consumer; +import java.util.function.UnaryOperator; public class LinearFlowInstructionListIterator implements InstructionListIterator { @@ -124,7 +125,7 @@ public void replaceCurrentInstructionWithThrow( AppView<?> appView, IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Value exceptionValue, Set<BasicBlock> blocksToRemove, Set<Value> affectedValues) { @@ -156,8 +157,12 @@ @Override public BasicBlock splitCopyCatchHandlers( - IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) { - return currentBlockIterator.splitCopyCatchHandlers(code, blockIterator, options); + IRCode code, + BasicBlockIterator blockIterator, + InternalOptions options, + UnaryOperator<BasicBlock> repositioningBlock) { + return currentBlockIterator.splitCopyCatchHandlers( + code, blockIterator, options, repositioningBlock); } @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 276a72c..52dc8b3 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
@@ -128,7 +128,6 @@ private final CfgPrinter printer; public final CodeRewriter codeRewriter; private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover(); - private final ConstantCanonicalizer constantCanonicalizer; public final MemberValuePropagation<?> memberValuePropagation; private final LensCodeRewriter lensCodeRewriter; private final Inliner inliner; @@ -176,7 +175,6 @@ this.options = appView.options(); this.printer = printer; this.codeRewriter = new CodeRewriter(appView); - this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter); this.classInitializerDefaultsOptimization = new ClassInitializerDefaultsOptimization(appView, this); this.stringOptimizer = new StringOptimizer(appView); @@ -1430,7 +1428,9 @@ // TODO(mkroghj) Test if shorten live ranges is worth it. if (!options.isGeneratingClassFiles()) { timing.begin("Canonicalize constants"); - constantCanonicalizer.canonicalize(appView, code); + ConstantCanonicalizer constantCanonicalizer = + new ConstantCanonicalizer(appView, codeRewriter, context, code); + constantCanonicalizer.canonicalize(); timing.end(); previous = printMethod(code, "IR after constant canonicalization (SSA)", previous); timing.begin("Create constants for literal instructions"); @@ -1438,7 +1438,7 @@ timing.end(); previous = printMethod(code, "IR after constant literals (SSA)", previous); timing.begin("Shorten live ranges"); - codeRewriter.shortenLiveRanges(code); + codeRewriter.shortenLiveRanges(code, constantCanonicalizer); timing.end(); previous = printMethod(code, "IR after shorten live ranges (SSA)", previous); }
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 1cf1e36..38afb5d 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
@@ -7,6 +7,12 @@ import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; +import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS; +import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER; +import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING; +import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING; +import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; +import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; import com.android.tools.r8.algorithms.scc.SCC; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; @@ -43,15 +49,18 @@ import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.Binop; import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.CheckCast; +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.DebugLocalWrite; import com.android.tools.r8.ir.code.DebugLocalsChange; +import com.android.tools.r8.ir.code.DexItemBasedConstString; import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; @@ -100,7 +109,6 @@ import com.android.tools.r8.utils.InternalOutputMode; import com.android.tools.r8.utils.LazyBox; import com.android.tools.r8.utils.LongInterval; -import com.android.tools.r8.utils.SetUtils; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ArrayListMultimap; @@ -137,13 +145,13 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; -import java.util.function.Consumer; import java.util.function.Predicate; public class CodeRewriter { @@ -1833,187 +1841,205 @@ return true; } - public void shortenLiveRanges(IRCode code) { + public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) { if (options.testing.disableShortenLiveRanges) { return; } - // Currently, we are only shortening the live range of ConstNumbers in the entry block - // and ConstStrings with one user. - // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently - // doing so seems to make things worse. LazyBox<DominatorTree> dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code)); - Map<BasicBlock, Map<Value, Instruction>> addConstantInBlock = new IdentityHashMap<>(); + Map<BasicBlock, LinkedHashMap<Value, Instruction>> addConstantInBlock = new IdentityHashMap<>(); LinkedList<BasicBlock> blocks = code.blocks; for (BasicBlock block : blocks) { - if (block == blocks.getFirst()) { - // For the first block process all ConstNumber as well as ConstString instructions. - shortenLiveRangesInsideBlock( - code, - block, - dominatorTreeMemoization, - addConstantInBlock, - insn -> - (insn.isConstNumber() && insn.outValue().hasAnyUsers()) - || (insn.isConstString() && insn.outValue().hasAnyUsers())); - } else { - // For all following blocks only process ConstString with just one use. - shortenLiveRangesInsideBlock( - code, - block, - dominatorTreeMemoization, - addConstantInBlock, - insn -> insn.isConstString() && insn.outValue().numberOfAllUsers() == 1); - } + shortenLiveRangesInsideBlock( + code, + block, + dominatorTreeMemoization, + addConstantInBlock, + canonicalizer::isConstantCanonicalizationCandidate); } // Heuristic to decide if constant instructions are shared in dominator block // of usages or moved to the usages. // Process all blocks in stable order to avoid non-determinism of hash map iterator. - for (BasicBlock block : blocks) { - Map<Value, Instruction> constants = addConstantInBlock.get(block); - if (constants == null) { + BasicBlockIterator blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + Map<Value, Instruction> movedInstructions = addConstantInBlock.get(block); + if (movedInstructions == null) { continue; } - - Set<Value> alreadyMoved = SetUtils.newIdentityHashSet(constants.size()); - if (block != blocks.getFirst() && constants.size() > STOP_SHARED_CONSTANT_THRESHOLD) { - // If there are too many constants in the same block, they are copied rather than shared - // except if they are used by phi instructions or they are a string constants. - assert constants instanceof LinkedHashMap; - for (Instruction constantInstruction : constants.values()) { - if (!constantInstruction.outValue().hasPhiUsers() - && !constantInstruction.isConstString()) { - assert constantInstruction.isConstNumber(); - ConstNumber constNumber = constantInstruction.asConstNumber(); - Value constantValue = constantInstruction.outValue(); - assert constantValue.hasUsers(); - assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers(); - for (Instruction user : constantValue.uniqueUsers()) { - ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); - newCstNum.setPosition(user.getPosition()); - InstructionListIterator iterator = user.getBlock().listIterator(code, user); - iterator.previous(); - iterator.add(newCstNum); - user.replaceValue(constantValue, newCstNum.outValue()); - } - constantValue.clearUsers(); - alreadyMoved.add(constantInstruction.outValue()); - } - } + assert !movedInstructions.isEmpty(); + if (!block.isEntry() && movedInstructions.size() > STOP_SHARED_CONSTANT_THRESHOLD) { + // If there are too many constant numbers in the same block they are copied rather than + // shared unless used by a phi. + movedInstructions + .values() + .removeIf( + movedInstruction -> { + if (movedInstruction.outValue().hasPhiUsers() + || !movedInstruction.isConstNumber()) { + return false; + } + ConstNumber constNumber = movedInstruction.asConstNumber(); + Value constantValue = movedInstruction.outValue(); + for (Instruction user : constantValue.uniqueUsers()) { + ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); + newCstNum.setPosition(user.getPosition()); + InstructionListIterator iterator = user.getBlock().listIterator(code, user); + iterator.previous(); + iterator.add(newCstNum); + user.replaceValue(constantValue, newCstNum.outValue()); + } + constantValue.clearUsers(); + return true; + }); } // Add constant into the dominator block of usages. boolean hasCatchHandlers = block.hasCatchHandlers(); - InstructionListIterator it = block.listIterator(code); - while (it.hasNext()) { - Instruction instruction = it.next(); - if (instruction.isJumpInstruction() - || (hasCatchHandlers && instruction.instructionTypeCanThrow()) - || (options.canHaveCmpIfFloatBug() && instruction.isCmp())) { + InstructionListIterator instructionIterator = block.listIterator(code); + while (instructionIterator.hasNext()) { + Instruction insertionPoint = instructionIterator.next(); + if (insertionPoint.isJumpInstruction() + || (hasCatchHandlers && insertionPoint.instructionTypeCanThrow()) + || (options.canHaveCmpIfFloatBug() && insertionPoint.isCmp())) { break; } - forEachUse( - instruction, - use -> { - Instruction constantInstruction = constants.get(use); - if (constantInstruction != null && !alreadyMoved.contains(use)) { - it.previous(); - constantInstruction.setPosition(instruction.getPosition()); - it.add(constantInstruction); - it.next(); - alreadyMoved.add(use); - } - }); - } - // Insert remaining constant instructions prior to the "exit". - Instruction next = it.previous(); - for (Instruction constantInstruction : constants.values()) { - if (!alreadyMoved.contains(constantInstruction.outValue())) { - constantInstruction.setPosition(next.getPosition()); - it.add(constantInstruction); + for (Value use : + Iterables.concat(insertionPoint.inValues(), insertionPoint.getDebugValues())) { + Instruction movedInstruction = movedInstructions.remove(use); + if (movedInstruction != null) { + instructionIterator = + insertInstructionWithShortenedLiveRange( + code, blockIterator, instructionIterator, movedInstruction, insertionPoint); + } } } + + // Insert remaining constant instructions prior to the "exit". + Instruction insertionPoint = instructionIterator.peekPrevious(); + for (Instruction movedInstruction : movedInstructions.values()) { + instructionIterator = + insertInstructionWithShortenedLiveRange( + code, blockIterator, instructionIterator, movedInstruction, insertionPoint); + } } assert code.isConsistentSSA(appView); } - private void forEachUse(Instruction instruction, Consumer<Value> fn) { - instruction.inValues().forEach(fn); - instruction.getDebugValues().forEach(fn); + private InstructionListIterator insertInstructionWithShortenedLiveRange( + IRCode code, + BasicBlockIterator blockIterator, + InstructionListIterator instructionIterator, + Instruction movedInstruction, + Instruction insertionPoint) { + Instruction previous = instructionIterator.previous(); + assert previous == insertionPoint; + movedInstruction.setPosition( + getPositionForMovedNonThrowingInstruction(movedInstruction, insertionPoint)); + if (movedInstruction.instructionTypeCanThrow() + && insertionPoint.getBlock().hasCatchHandlers()) { + // Split the block and reset the block iterator. + BasicBlock splitBlock = + instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options()); + BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock); + assert previousBlock == splitBlock; + blockIterator.next(); + + // Add the constant instruction before the exit instruction. + assert !instructionIterator.hasNext(); + instructionIterator.previous(); + instructionIterator.add(movedInstruction); + + // Continue insertion at the entry of the split block. + instructionIterator = splitBlock.listIterator(code); + } else { + instructionIterator.add(movedInstruction); + } + Instruction next = instructionIterator.next(); + assert next == insertionPoint; + return instructionIterator; + } + + private Position getPositionForMovedNonThrowingInstruction( + Instruction movedInstruction, Instruction insertionPoint) { + // If the type of the moved instruction is throwing and we don't have a position at the + // insertion point, we use the special synthetic-none position, which is OK as the moved + // instruction instance is known not to throw (or we would not be allowed the move it). + if (movedInstruction.instructionTypeCanThrow() && !insertionPoint.getPosition().isSome()) { + return Position.syntheticNone(); + } + return insertionPoint.getPosition(); } private void shortenLiveRangesInsideBlock( IRCode code, BasicBlock block, LazyBox<DominatorTree> dominatorTreeMemoization, - Map<BasicBlock, Map<Value, Instruction>> addConstantInBlock, - Predicate<ConstInstruction> selector) { + Map<BasicBlock, LinkedHashMap<Value, Instruction>> addConstantInBlock, + Predicate<Instruction> selector) { InstructionListIterator iterator = block.listIterator(code); while (iterator.hasNext()) { - Instruction next = iterator.next(); - if (!next.isConstInstruction()) { + Instruction instruction = iterator.next(); + if (instruction.hasUnusedOutValue() || instruction.outValue().hasLocalInfo()) { continue; } - // We don't want to push const string instructions down into code that has monitors since - // we may attach catch handlers that are not catch-all when inlining. This is symmetric in how - // we don't do const string canonicalization. - if ((next.isConstString() || next.isDexItemBasedConstString()) - && code.metadata().mayHaveMonitorInstruction()) { + if (!selector.test(instruction)) { continue; } - ConstInstruction instruction = next.asConstInstruction(); - if (!selector.test(instruction) || instruction.outValue().hasLocalInfo()) { - continue; - } - Set<Instruction> uniqueUsers = instruction.outValue().uniqueUsers(); - // Here we try to stop wasting time in the common case of large array of constants creation. - // We do not want to move a high number of constants up just to move them down because it - // takes multiple seconds in some cases (ZoneName clinit for instance). - // In array creation, the pattern is something like: + // Here we try to stop wasting time in the common case where constants are used immediately + // after their definition. + // + // This is especially important for the creation of large arrays, which has the following code + // pattern repeated many times, where the two loaded constants are only used by the ArrayPut + // instruction. + // // Const number (the array index) // Const (the array entry value) // ArrayPut - // And both constants are used only in the put. The heuristic is therefore to check for - // constants used only once if the use is within the next two instructions, and only swap - // them if that is the case (cannot shorten the live range anyway). - // This heuristic drops down the time spent in large array of constant creation in - // shortenLiveRanges from multiple seconds to multiple milliseconds. - if (uniqueUsers.size() == 1 && instruction.outValue().uniquePhiUsers().size() == 0) { - Instruction uniqueUse = uniqueUsers.iterator().next(); - if (iterator.hasNext()) { - Instruction nextNext = iterator.next(); - if (uniqueUse == nextNext && nextNext.isArrayPut()) { - assert !uniqueUse.isConstInstruction(); + // + // The heuristic is therefore to check for constants used only once if the use is within the + // next two instructions, and only swap them if that is the case (cannot shorten the live + // range anyway). + if (instruction.outValue().hasSingleUniqueUser() && !instruction.outValue().hasPhiUsers()) { + Instruction uniqueUse = instruction.outValue().singleUniqueUser(); + Instruction next = iterator.next(); + if (uniqueUse == next) { + continue; + } + if (next.hasOutValue() + && next.outValue().hasSingleUniqueUser() + && !next.outValue().hasPhiUsers() + && iterator.hasNext()) { + Instruction nextNext = iterator.peekNext(); + Instruction uniqueUseNext = next.outValue().singleUniqueUser(); + if (uniqueUse == nextNext && uniqueUseNext == nextNext) { continue; } - if (nextNext.isConstInstruction()) { - Set<Instruction> uniqueUsersNext = nextNext.outValue().uniqueUsers(); - if (uniqueUsersNext.size() == 1 - && nextNext.outValue().uniquePhiUsers().size() == 0 - && iterator.hasNext()) { - Instruction nextNextNext = iterator.peekNext(); - Instruction uniqueUseNext = uniqueUsersNext.iterator().next(); - if (uniqueUse == nextNextNext - && uniqueUseNext == nextNextNext - && nextNextNext.isArrayPut()) { - continue; - } - } - } - iterator.previous(); } + iterator.previous(); + // The call to removeOrReplaceByDebugLocalRead() at the end of this method will remove the + // last returned element of this iterator. Therefore, we re-read this element from the + // iterator. + iterator.previous(); + iterator.next(); } // Collect the blocks for all users of the constant. - List<BasicBlock> userBlocks = new LinkedList<>(); - for (Instruction user : uniqueUsers) { + Set<BasicBlock> userBlocks = new LinkedHashSet<>(); + for (Instruction user : instruction.outValue().uniqueUsers()) { userBlocks.add(user.getBlock()); } for (Phi phi : instruction.outValue().uniquePhiUsers()) { - userBlocks.add(phi.getBlock()); + int predecessorIndex = 0; + for (Value operand : phi.getOperands()) { + if (operand == instruction.outValue()) { + userBlocks.add(phi.getBlock().getPredecessors().get(predecessorIndex)); + } + predecessorIndex++; + } } // Locate the closest dominator block for all user blocks. DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent(); @@ -2044,14 +2070,35 @@ } } - Map<Value, Instruction> csts = - addConstantInBlock.computeIfAbsent(dominator, k -> new LinkedHashMap<>()); - - ConstInstruction copy = instruction.isConstNumber() - ? ConstNumber.copyOf(code, instruction.asConstNumber()) - : ConstString.copyOf(code, instruction.asConstString()); + Instruction copy; + switch (instruction.opcode()) { + case CONST_CLASS: + copy = ConstClass.copyOf(code, instruction.asConstClass()); + break; + case CONST_NUMBER: + copy = ConstNumber.copyOf(code, instruction.asConstNumber()); + break; + case CONST_STRING: + copy = ConstString.copyOf(code, instruction.asConstString()); + break; + case DEX_ITEM_BASED_CONST_STRING: + copy = DexItemBasedConstString.copyOf(code, instruction.asDexItemBasedConstString()); + break; + case INSTANCE_GET: + copy = InstanceGet.copyOf(code, instruction.asInstanceGet()); + break; + case STATIC_GET: + copy = StaticGet.copyOf(code, instruction.asStaticGet()); + break; + default: + throw new Unreachable(); + } instruction.outValue().replaceUsers(copy.outValue()); - csts.put(copy.outValue(), copy); + addConstantInBlock + .computeIfAbsent(dominator, k -> new LinkedHashMap<>()) + .put(copy.outValue(), copy); + assert iterator.peekPrevious() == instruction; + iterator.removeOrReplaceByDebugLocalRead(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 7082cc6..5275a80 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -7,50 +7,152 @@ import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER; import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING; import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING; +import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET; import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldAccessFlags; +import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult; +import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.SingleFieldValue; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DexItemBasedConstString; +import com.android.tools.r8.ir.code.FieldGet; +import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InstructionOrPhi; +import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.NewInstance; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.OptionalBool; +import com.android.tools.r8.utils.WorkList; +import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.Hash.Strategy; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap.FastSortedEntrySet; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Canonicalize constants. */ public class ConstantCanonicalizer { + // Threshold to limit the number of constant canonicalization. private static final int MAX_CANONICALIZED_CONSTANT = 22; + private final AppView<?> appView; private final CodeRewriter codeRewriter; + private final ProgramMethod context; + private final IRCode code; - public ConstantCanonicalizer(CodeRewriter codeRewriter) { + private OptionalBool isAccessingVolatileField = OptionalBool.unknown(); + private Set<InstanceGet> ineligibleInstanceGetInstructions; + + public ConstantCanonicalizer( + AppView<?> appView, CodeRewriter codeRewriter, ProgramMethod context, IRCode code) { + this.appView = appView; this.codeRewriter = codeRewriter; + this.context = context; + this.code = code; } - public void canonicalize(AppView<?> appView, IRCode code) { - ProgramMethod context = code.context(); - Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Value>> valuesDefinedByConstant = + private ConstantCanonicalizer clear() { + isAccessingVolatileField = OptionalBool.unknown(); + ineligibleInstanceGetInstructions = null; + return this; + } + + private boolean getOrComputeIsAccessingVolatileField() { + if (isAccessingVolatileField.isUnknown()) { + isAccessingVolatileField = OptionalBool.of(computeIsAccessingVolatileField()); + } + return isAccessingVolatileField.isTrue(); + } + + private boolean computeIsAccessingVolatileField() { + if (!appView.hasClassHierarchy()) { + // Conservatively return true. + return true; + } + AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy(); + for (FieldInstruction fieldGet : + code.<FieldInstruction>instructions(Instruction::isFieldInstruction)) { + SingleFieldResolutionResult<?> resolutionResult = + appInfo.resolveField(fieldGet.getField()).asSingleFieldResolutionResult(); + if (resolutionResult == null + || resolutionResult.getResolvedField().getAccessFlags().isVolatile()) { + return true; + } + } + return false; + } + + private Set<InstanceGet> getOrComputeIneligibleInstanceGetInstructions() { + if (ineligibleInstanceGetInstructions == null) { + ineligibleInstanceGetInstructions = computeIneligibleInstanceGetInstructions(); + } + return ineligibleInstanceGetInstructions; + } + + private Set<InstanceGet> computeIneligibleInstanceGetInstructions() { + Set<InstanceGet> ineligibleInstanceGetInstructions = Sets.newIdentityHashSet(); + for (BasicBlock catchHandlerBlock : computeDirectAndIndirectCatchHandlerBlocks()) { + for (InstanceGet instanceGet : + catchHandlerBlock.<InstanceGet>getInstructions(Instruction::isInstanceGet)) { + // If the receiver is defined in a block with catch handlers and the definition of the + // receiver is not throwing (typically defined by an assume instruction or a phi), then we + // cant split the block and copy the catch handlers, since the canonicalized constant would + // then not be defined on the exceptional edge. + Value object = instanceGet.object(); + if (!object.isDefinedByInstructionSatisfying(Instruction::instructionTypeCanThrow) + && object.getBlock().hasCatchHandlers()) { + ineligibleInstanceGetInstructions.add(instanceGet); + } + } + } + return ineligibleInstanceGetInstructions; + } + + private Set<BasicBlock> computeDirectAndIndirectCatchHandlerBlocks() { + WorkList<BasicBlock> catchHandlerBlocks = WorkList.newIdentityWorkList(); + for (BasicBlock block : code.getBlocks()) { + if (block.entry().isMoveException()) { + catchHandlerBlocks.addIfNotSeen(block); + } + } + while (catchHandlerBlocks.hasNext()) { + BasicBlock block = catchHandlerBlocks.next(); + catchHandlerBlocks.addIfNotSeen(block.getSuccessors()); + } + return catchHandlerBlocks.getSeenSet(); + } + + public ConstantCanonicalizer canonicalize() { + Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Instruction>> valuesDefinedByConstant = new Object2ObjectLinkedOpenCustomHashMap<>( new Strategy<Instruction>() { @@ -67,8 +169,9 @@ return candidate.asConstString().getValue().hashCode(); case DEX_ITEM_BASED_CONST_STRING: return candidate.asDexItemBasedConstString().getItem().hashCode(); + case INSTANCE_GET: case STATIC_GET: - return candidate.asStaticGet().getField().hashCode(); + return candidate.asFieldGet().getField().hashCode(); default: throw new Unreachable(); } @@ -76,133 +179,114 @@ @Override public boolean equals(Instruction a, Instruction b) { - // Constants with local info must not be canonicalized and must be filtered. - assert a == null || !a.outValue().hasLocalInfo(); - assert b == null || !b.outValue().hasLocalInfo(); - return a == b || (a != null && b != null && a.identicalNonValueNonPositionParts(b)); + if (a == b) { + return true; + } + if (a == null || b == null || a.getClass() != b.getClass()) { + return false; + } + if (a.isInstanceGet() && a.getFirstOperand() != b.getFirstOperand()) { + return false; + } + return a.identicalNonValueNonPositionParts(b); } }); // Collect usages of constants that can be canonicalized. - for (Instruction current : code.instructions()) { - // Interested only in instructions types that can be canonicalized, i.e., ConstClass, - // ConstNumber, (DexItemBased)?ConstString, and StaticGet. - if (!current.instructionTypeCanBeCanonicalized()) { - continue; + for (Instruction instruction : code.instructions()) { + if (isConstantCanonicalizationCandidate(instruction)) { + valuesDefinedByConstant + .computeIfAbsent(instruction, ignoreKey(ArrayList::new)) + .add(instruction); } - // Do not canonicalize ConstClass that may have side effects. Its original instructions - // will not be removed by dead code remover due to the side effects. - if (current.isConstClass() && current.instructionMayHaveSideEffects(appView, context)) { - continue; - } - // Do not canonicalize ConstString instructions if there are monitor operations in the code. - // That could lead to unbalanced locking and could lead to situations where OOM exceptions - // could leave a synchronized method without unlocking the monitor. - if ((current.isConstString() || current.isDexItemBasedConstString()) - && code.metadata().mayHaveMonitorInstruction()) { - continue; - } - // Canonicalize effectively final fields that are guaranteed to be written before they are - // read. This is only OK if the instruction cannot have side effects. - if (current.isStaticGet()) { - AbstractValue abstractValue = current.outValue().getAbstractValue(appView, context); - if (!abstractValue.isSingleFieldValue()) { - continue; - } - SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue(); - DexType fieldHolderType = singleFieldValue.getField().getHolderType(); - if (context.getDefinition().isClassInitializer() - && context.getHolderType() == fieldHolderType) { - // Avoid that canonicalization inserts a read before the unique write in the class - // initializer, as that would change the program behavior. - continue; - } - DexClass fieldHolder = appView.definitionFor(fieldHolderType); - DexEncodedField field = singleFieldValue.getField().lookupOnClass(fieldHolder); - if (field == null - || !field.isEnum() - || current.instructionMayHaveSideEffects(appView, context)) { - // Only allow canonicalization of enums. - continue; - } - } - // Constants with local info must not be canonicalized and must be filtered. - if (current.outValue().hasLocalInfo()) { - continue; - } - // Constants that are used by invoke range are not canonicalized to be compliant with the - // optimization splitRangeInvokeConstant that gives the register allocator more freedom in - // assigning register to ranged invokes which can greatly reduce the number of register - // needed (and thereby code size as well). Thus no need to do a transformation that should - // be removed later by another optimization. - if (constantUsedByInvokeRange(current)) { - continue; - } - List<Value> oldValuesDefinedByConstant = - valuesDefinedByConstant.computeIfAbsent(current, k -> new ArrayList<>()); - oldValuesDefinedByConstant.add(current.outValue()); } if (valuesDefinedByConstant.isEmpty()) { - return; + return clear(); } // Double-check the entry block does not have catch handlers. // Otherwise, we need to split it before moving canonicalized const-string, which may throw. assert !code.entryBlock().hasCatchHandlers(); - FastSortedEntrySet<Instruction, List<Value>> entries = + FastSortedEntrySet<Instruction, List<Instruction>> entries = valuesDefinedByConstant.object2ObjectEntrySet(); // Sort the most frequently used constant first and exclude constant use only one time, such // as the {@code MAX_CANONICALIZED_CONSTANT} will be canonicalized into the entry block. - Iterator<Object2ObjectMap.Entry<Instruction, List<Value>>> iterator = + Iterator<Object2ObjectMap.Entry<Instruction, List<Instruction>>> iterator = entries.stream() .filter(a -> a.getValue().size() > 1) .sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size())) .limit(MAX_CANONICALIZED_CONSTANT) .iterator(); - if (!iterator.hasNext()) { - return; + return clear(); } boolean shouldSimplifyIfs = false; + + // Insert instructions in the entry block. + Map<InstructionOrPhi, List<Instruction>> pendingInsertions = new IdentityHashMap<>(); do { - Object2ObjectMap.Entry<Instruction, List<Value>> entry = iterator.next(); + Object2ObjectMap.Entry<Instruction, List<Instruction>> entry = iterator.next(); Instruction canonicalizedConstant = entry.getKey(); assert canonicalizedConstant.instructionTypeCanBeCanonicalized(); - Instruction newConst; + Instruction newInstruction; if (canonicalizedConstant.getBlock().isEntry()) { - newConst = canonicalizedConstant; + newInstruction = canonicalizedConstant; } else { - switch (canonicalizedConstant.opcode()) { - case CONST_CLASS: - newConst = ConstClass.copyOf(code, canonicalizedConstant.asConstClass()); - break; - case CONST_NUMBER: - newConst = ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber()); - break; - case CONST_STRING: - newConst = ConstString.copyOf(code, canonicalizedConstant.asConstString()); - break; - case DEX_ITEM_BASED_CONST_STRING: - newConst = - DexItemBasedConstString.copyOf( - code, canonicalizedConstant.asDexItemBasedConstString()); - break; - case STATIC_GET: - newConst = StaticGet.copyOf(code, canonicalizedConstant.asStaticGet()); - break; - default: - throw new Unreachable(); + newInstruction = createMaterializingInstruction(canonicalizedConstant); + InstructionOrPhi insertionPoint = getInsertionPointForCanonicalizedConstant(newInstruction); + if (insertionPoint == null) { + insertCanonicalizedConstantInEntryBlock(newInstruction); + } else { + // Record that this instruction needs to be inserted at the insertion position. Note that + // the insertion position may later be moved if it is itself subject to canonicalization. + addPendingInsertion(insertionPoint, newInstruction, pendingInsertions); } - insertCanonicalizedConstant(code, newConst); } - for (Value outValue : entry.getValue()) { - outValue.replaceUsers(newConst.outValue()); + // Remove the canonicalized instructions. + for (Instruction oldInstruction : entry.getValue()) { + if (oldInstruction != newInstruction) { + oldInstruction.outValue().replaceUsers(newInstruction.outValue()); + oldInstruction + .getBlock() + .listIterator(code, oldInstruction) + .removeOrReplaceByDebugLocalRead(); + + // If the removed instruction is an insertion point for another constant, then record that + // the constant should instead be inserted at the point where the removed instruction has + // been moved to. + for (Instruction pendingInsertion : + removePendingInsertions(oldInstruction, pendingInsertions)) { + addPendingInsertion(newInstruction, pendingInsertion, pendingInsertions); + } + } } - shouldSimplifyIfs |= newConst.outValue().hasUserThatMatches(Instruction::isIf); + shouldSimplifyIfs |= newInstruction.outValue().hasUserThatMatches(Instruction::isIf); } while (iterator.hasNext()); + // Insert instructions that cannot be inserted in the entry block. + if (!pendingInsertions.isEmpty()) { + BasicBlockIterator blockIterator = code.listIterator(); + while (blockIterator.hasNext()) { + BasicBlock block = blockIterator.next(); + InstructionListIterator instructionIterator = block.listIterator(code); + for (Phi insertionPoint : block.getPhis()) { + instructionIterator = + insertPendingInsertions( + blockIterator, instructionIterator, insertionPoint, pendingInsertions); + } + while (instructionIterator.hasNext()) { + Instruction insertionPoint = instructionIterator.next(); + instructionIterator = + insertPendingInsertions( + blockIterator, instructionIterator, insertionPoint, pendingInsertions); + } + } + } + + assert pendingInsertions.isEmpty(); + shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis(); if (shouldSimplifyIfs) { @@ -210,9 +294,265 @@ } assert code.isConsistentSSA(appView); + return clear(); } - private static void insertCanonicalizedConstant(IRCode code, Instruction canonicalizedConstant) { + private void addPendingInsertion( + InstructionOrPhi insertionPoint, + Instruction newInstruction, + Map<InstructionOrPhi, List<Instruction>> pendingInsertions) { + pendingInsertions + .computeIfAbsent(insertionPoint, ignoreKey(ArrayList::new)) + .add(newInstruction); + } + + private InstructionListIterator insertPendingInsertions( + BasicBlockIterator blockIterator, + InstructionListIterator instructionIterator, + InstructionOrPhi insertionPoint, + Map<InstructionOrPhi, List<Instruction>> pendingInsertions) { + List<Instruction> pendingInsertionsAtInsertionPoint = + removePendingInsertions(insertionPoint, pendingInsertions); + if (pendingInsertionsAtInsertionPoint.isEmpty()) { + return instructionIterator; + } + WorkList<Instruction> worklist = + WorkList.newIdentityWorkList(pendingInsertionsAtInsertionPoint); + while (worklist.hasNext()) { + Instruction newInstruction = worklist.next(); + List<Instruction> pendingInsertionsAfterNewInstruction = + removePendingInsertions(newInstruction, pendingInsertions); + if (pendingInsertionsAfterNewInstruction.isEmpty()) { + instructionIterator = + insertCanonicalizedConstantAtInsertionPoint( + blockIterator, instructionIterator, insertionPoint, newInstruction); + } else { + // Process pending insertions before the current instruction to ensure the current + // instruction ends up first in the instruction stream. + worklist.addIfNotSeen(pendingInsertionsAfterNewInstruction); + worklist.addIgnoringSeenSet(newInstruction); + } + } + return instructionIterator; + } + + private List<Instruction> removePendingInsertions( + InstructionOrPhi insertionPoint, Map<InstructionOrPhi, List<Instruction>> pendingInsertions) { + List<Instruction> pendingInstructionsAtInsertionPosition = + pendingInsertions.remove(insertionPoint); + return pendingInstructionsAtInsertionPosition != null + ? pendingInstructionsAtInsertionPosition + : Collections.emptyList(); + } + + private InstructionOrPhi getInsertionPointForCanonicalizedConstant(Instruction newInstruction) { + switch (newInstruction.opcode()) { + case CONST_CLASS: + case CONST_NUMBER: + case CONST_STRING: + case DEX_ITEM_BASED_CONST_STRING: + case STATIC_GET: + // Insert in entry block. + return null; + case INSTANCE_GET: + { + InstanceGet instanceGet = newInstruction.asInstanceGet(); + Value object = instanceGet.object(); + if (object.isThis()) { + return null; + } + if (object.isPhi()) { + return object.asPhi(); + } + Instruction definition = object.getDefinition(); + if (definition.isArgument()) { + return code.getLastArgument(); + } + if (definition.isNewInstance()) { + InvokeDirect uniqueConstructorInvoke = + definition.asNewInstance().getUniqueConstructorInvoke(appView.dexItemFactory()); + // This is guaranteed to be non-null by isConstantCanonicalizationCandidate. + assert uniqueConstructorInvoke != null; + return uniqueConstructorInvoke; + } + return definition; + } + default: + throw new Unreachable(); + } + } + + private Instruction createMaterializingInstruction(Instruction canonicalizedConstant) { + switch (canonicalizedConstant.opcode()) { + case CONST_CLASS: + return ConstClass.copyOf(code, canonicalizedConstant.asConstClass()); + case CONST_NUMBER: + return ConstNumber.copyOf(code, canonicalizedConstant.asConstNumber()); + case CONST_STRING: + return ConstString.copyOf(code, canonicalizedConstant.asConstString()); + case DEX_ITEM_BASED_CONST_STRING: + return DexItemBasedConstString.copyOf( + code, canonicalizedConstant.asDexItemBasedConstString()); + case INSTANCE_GET: + return InstanceGet.copyOf(code, canonicalizedConstant.asInstanceGet()); + case STATIC_GET: + return StaticGet.copyOf(code, canonicalizedConstant.asStaticGet()); + default: + throw new Unreachable(); + } + } + + public boolean isConstantCanonicalizationCandidate(Instruction instruction) { + // Interested only in instructions types that can be canonicalized, i.e., ConstClass, + // ConstNumber, (DexItemBased)?ConstString, InstanceGet and StaticGet. + switch (instruction.opcode()) { + case CONST_CLASS: + // Do not canonicalize ConstClass that may have side effects. Its original instructions + // will not be removed by dead code remover due to the side effects. + if (instruction.instructionMayHaveSideEffects(appView, context)) { + return false; + } + break; + case CONST_NUMBER: + break; + case CONST_STRING: + case DEX_ITEM_BASED_CONST_STRING: + break; + case INSTANCE_GET: + { + InstanceGet instanceGet = instruction.asInstanceGet(); + if (instanceGet.instructionMayHaveSideEffects(appView, context)) { + return false; + } + if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { + NewInstance newInstance = instanceGet.object().getDefinition().asNewInstance(); + if (newInstance.getUniqueConstructorInvoke(appView.dexItemFactory()) == null) { + return false; + } + } + if (!isReadOfFinalFieldOutsideInitializer(instanceGet)) { + return false; + } + if (getOrComputeIneligibleInstanceGetInstructions().contains(instanceGet)) { + return false; + } + break; + } + case STATIC_GET: + { + // Canonicalize effectively final fields that are guaranteed to be written before they are + // read. This is only OK if the instruction cannot have side effects. + StaticGet staticGet = instruction.asStaticGet(); + if (staticGet.instructionMayHaveSideEffects(appView, context)) { + return false; + } + if (!isReadOfFinalFieldOutsideInitializer(staticGet) + && !isEffectivelyFinalField(staticGet)) { + return false; + } + break; + } + default: + assert !instruction.instructionTypeCanBeCanonicalized() : instruction.toString(); + return false; + } + // Constants with local info must not be canonicalized and must be filtered. + if (instruction.outValue().hasLocalInfo()) { + return false; + } + // Do not canonicalize throwing instructions if there are monitor operations in the code. + // That could lead to unbalanced locking and could lead to situations where OOM exceptions + // could leave a synchronized method without unlocking the monitor. + if (instruction.instructionTypeCanThrow() && code.metadata().mayHaveMonitorInstruction()) { + return false; + } + // Constants that are used by invoke range are not canonicalized to be compliant with the + // optimization splitRangeInvokeConstant that gives the register allocator more freedom in + // assigning register to ranged invokes which can greatly reduce the number of register + // needed (and thereby code size as well). Thus no need to do a transformation that should + // be removed later by another optimization. + if (constantUsedByInvokeRange(instruction)) { + return false; + } + return true; + } + + private boolean isReadOfFinalFieldOutsideInitializer(FieldGet fieldGet) { + if (getOrComputeIsAccessingVolatileField()) { + // A final field may be initialized concurrently. A requirement for this is that the field is + // volatile. However, the reading or writing of another volatile field also allows for + // concurrently initializing a non-volatile field. See also redundant field load elimination. + return false; + } + AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy = + appView.withClassHierarchy(); + SingleFieldResolutionResult<?> resolutionResult = + appViewWithClassHierarchy + .appInfo() + .resolveField(fieldGet.getField()) + .asSingleFieldResolutionResult(); + if (resolutionResult == null) { + // Not known to be final. + return false; + } + if (!resolutionResult.isSingleProgramFieldResolutionResult()) { + // We can't rely on the final flag of non-program fields. + return false; + } + ProgramField resolvedField = resolutionResult.getSingleProgramField(); + FieldAccessFlags accessFlags = resolvedField.getAccessFlags(); + assert !accessFlags.isVolatile(); + // TODO(b/236661949): Add support for effectively final fields so that this also works well + // without -allowaccessmodification. + if (!accessFlags.isFinal()) { + return false; + } + if (appView.getKeepInfo(resolvedField).isPinned(appView.options())) { + // The final flag could be unset using reflection. + return false; + } + if (context.getDefinition().isInitializer() + && context.getAccessFlags().isStatic() == fieldGet.isStaticGet()) { + if (context.getHolder() == resolvedField.getHolder()) { + // If this is an initializer on the field's holder, then bail out, since the field value is + // only known to be final after object/class creation. + return false; + } + if (fieldGet.isInstanceGet() + && appViewWithClassHierarchy + .appInfo() + .isSubtype(context.getHolder(), resolvedField.getHolder())) { + // If an instance initializer reads a final instance field declared in a super class, we + // cannot hoist the read above the parent constructor call. + return false; + } + } + if (!resolutionResult.getInitialResolutionHolder().isResolvable(appView)) { + // If this field read is guarded by an API level check, hoisting of this field could lead to + // a ClassNotFoundException on some API levels. + return false; + } + return true; + } + + private boolean isEffectivelyFinalField(StaticGet staticGet) { + AbstractValue abstractValue = staticGet.outValue().getAbstractValue(appView, context); + if (!abstractValue.isSingleFieldValue()) { + return false; + } + SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue(); + DexType fieldHolderType = singleFieldValue.getField().getHolderType(); + if (context.getDefinition().isClassInitializer() + && context.getHolderType() == fieldHolderType) { + // Avoid that canonicalization inserts a read before the unique write in the class + // initializer, as that would change the program behavior. + return false; + } + DexClass fieldHolder = appView.definitionFor(fieldHolderType); + return singleFieldValue.getField().lookupOnClass(fieldHolder) != null; + } + + private void insertCanonicalizedConstantInEntryBlock(Instruction canonicalizedConstant) { BasicBlock entryBlock = code.entryBlock(); // Insert the constant instruction at the start of the block right after the argument // instructions. It is important that the const instruction is put before any instruction @@ -229,6 +569,63 @@ it.add(canonicalizedConstant); } + private InstructionListIterator insertCanonicalizedConstantAtInsertionPoint( + BasicBlockIterator blockIterator, + InstructionListIterator instructionIterator, + InstructionOrPhi insertionPoint, + Instruction newInstruction) { + // If the insertion point is a phi, then we're inserting the new instruction before all other + // instructions in the block. + assert !insertionPoint.isPhi() || !instructionIterator.hasPrevious(); + // If the insertion point is not a phi, the iterator is positioned immediately after the + // insertion point. + assert insertionPoint.isPhi() || instructionIterator.peekPrevious() == insertionPoint; + newInstruction.setPosition( + getPositionForCanonicalizationConstantAtInsertionPoint(insertionPoint, newInstruction)); + if (newInstruction.instructionTypeCanThrow() + && insertionPoint.getBlock().hasCatchHandlers() + && insertionPoint.getBlock().canThrow()) { + // Split the block and rewind the block iterator to the insertion block. + BasicBlock splitBlock = + instructionIterator.splitCopyCatchHandlers( + code, blockIterator, appView.options(), ignore -> insertionPoint.getBlock()); + if (insertionPoint.isPhi()) { + // Add new instruction before the goto and position the instruction iterator before the + // first instruction (i.e., at the phi position). + assert insertionPoint.getBlock().getInstructions().size() == 1; + instructionIterator.addBeforeAndPositionBeforeNewInstruction(newInstruction); + assert !instructionIterator.hasPrevious(); + } else { + // Add the new instruction after the insertion point. If the block containing the insertion + // point can throw, we insert the new instruction in the beginning of the split block. + // Otherwise, we insert it in the end of the insertion block. + if (insertionPoint.getBlock().canThrow()) { + assert !splitBlock.canThrow(); + splitBlock.listIterator(code).add(newInstruction); + } else { + assert splitBlock.canThrow(); + instructionIterator.addBeforeAndPositionBeforeNewInstruction(newInstruction); + } + instructionIterator.positionAfterPreviousInstruction(insertionPoint.asInstruction()); + } + } else { + instructionIterator.addAndPositionBeforeNewInstruction(newInstruction); + } + return instructionIterator; + } + + private Position getPositionForCanonicalizationConstantAtInsertionPoint( + InstructionOrPhi insertionPoint, Instruction newInstruction) { + Position insertionPosition = + insertionPoint.isPhi() + ? insertionPoint.getBlock().getPosition() + : insertionPoint.asInstruction().getPosition(); + if (newInstruction.instructionTypeCanThrow() && insertionPosition.isNone()) { + return Position.syntheticNone(); + } + return insertionPosition; + } + private static boolean constantUsedByInvokeRange(Instruction constant) { for (Instruction user : constant.outValue().uniqueUsers()) { if (user.isInvoke() && user.asInvoke().requiredArgumentRegisters() > 5) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index 5930cd3..8a023db 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.DominatorTree; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; @@ -32,7 +33,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.IdentityHashMap; -import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -65,7 +65,7 @@ Map<Value, Map<DexType, Value>> castedReceiverCache = new IdentityHashMap<>(); Set<SafeCheckCast> newCheckCastInstructions = Sets.newIdentityHashSet(); - ListIterator<BasicBlock> blocks = code.listIterator(); + BasicBlockIterator blocks = code.listIterator(); while (blocks.hasNext()) { BasicBlock block = blocks.next(); InstructionListIterator it = block.listIterator(code);
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 f0980f3..9a38d14 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
@@ -1212,9 +1212,7 @@ } private void applyMemberValuePropagationToInlinee( - IRCode code, - ListIterator<BasicBlock> blockIterator, - Set<BasicBlock> inlineeBlocks) { + IRCode code, BasicBlockIterator blockIterator, Set<BasicBlock> inlineeBlocks) { Set<Value> affectedValues = Sets.newIdentityHashSet(); new R8MemberValuePropagation(appView) .run(code, blockIterator, affectedValues, inlineeBlocks::contains);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java index ed028ee..7327231 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.ArrayAccess; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.If; @@ -48,7 +49,6 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -124,7 +124,7 @@ ProgramMethod context = code.context(); Map<Instruction, DexType> convertedEnums = createInitialConvertedEnums(code, prototypeChanges); Set<Phi> affectedPhis = Sets.newIdentityHashSet(); - ListIterator<BasicBlock> blocks = code.listIterator(); + BasicBlockIterator blocks = code.listIterator(); Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet(); Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet(); Value zeroConstValue = null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java index 020d86e..b9ab26a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java
@@ -8,7 +8,7 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.ArrayGet; -import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstanceGet; import com.android.tools.r8.ir.code.InstancePut; @@ -18,7 +18,6 @@ import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; -import java.util.ListIterator; import java.util.Set; public class D8MemberValuePropagation extends MemberValuePropagation<AppInfo> { @@ -31,7 +30,7 @@ void rewriteArrayGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, ArrayGet arrayGet) { // Intentionally empty. @@ -41,7 +40,7 @@ void rewriteInstanceGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InstanceGet current) { // Intentionally empty. @@ -57,7 +56,7 @@ IRCode code, ProgramMethod context, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InvokeMethod invoke) { // Intentionally empty. @@ -67,7 +66,7 @@ void rewriteStaticGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, StaticGet current) { AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(current.getField());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java index 14a54b1..4d89225 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.ir.analysis.value.SingleValue; import com.android.tools.r8.ir.code.ArrayGet; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.IRMetadata; import com.android.tools.r8.ir.code.InstanceGet; @@ -38,7 +39,6 @@ import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; import com.android.tools.r8.utils.IteratorUtils; import com.google.common.collect.Sets; -import java.util.ListIterator; import java.util.Set; import java.util.function.Predicate; @@ -71,7 +71,7 @@ public void run( IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Set<Value> affectedValues, Predicate<BasicBlock> blockTester) { ProgramMethod context = code.context(); @@ -118,14 +118,14 @@ abstract void rewriteArrayGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, ArrayGet arrayGet); abstract void rewriteInstanceGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InstanceGet current); @@ -136,14 +136,14 @@ IRCode code, ProgramMethod context, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InvokeMethod invoke); abstract void rewriteStaticGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, StaticGet current); @@ -152,7 +152,7 @@ boolean applyAssumeInfo( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, Instruction current, AssumeInfo assumeInfo) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java index bc33a3a..587a78c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.ir.analysis.value.UnknownValue; import com.android.tools.r8.ir.code.ArrayGet; import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.FieldInstruction; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstanceGet; @@ -36,7 +37,6 @@ import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo; import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup; import com.android.tools.r8.shaking.AppInfoWithLiveness; -import java.util.ListIterator; import java.util.Set; public class R8MemberValuePropagation extends MemberValuePropagation<AppInfoWithLiveness> { @@ -51,7 +51,7 @@ void rewriteArrayGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, ArrayGet arrayGet) { TypeElement arrayType = arrayGet.array().getType(); @@ -123,7 +123,7 @@ IRCode code, ProgramMethod context, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InvokeMethod invoke) { if (invoke.hasUnusedOutValue()) { @@ -208,7 +208,7 @@ void rewriteInstanceGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, InstanceGet current) { rewriteFieldGet(code, affectedValues, blocks, iterator, current); @@ -218,7 +218,7 @@ void rewriteStaticGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, StaticGet current) { rewriteFieldGet(code, affectedValues, blocks, iterator, current); @@ -227,7 +227,7 @@ private void rewriteFieldGet( IRCode code, Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, + BasicBlockIterator blocks, InstructionListIterator iterator, FieldInstruction current) { DexField field = current.getField();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java index 2606826..ee85c13 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -35,6 +35,8 @@ import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitOrAppend; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.LoopNode; +import com.android.tools.r8.ir.optimize.string.StringBuilderNode.NewInstanceNode; +import com.android.tools.r8.ir.optimize.string.StringBuilderNode.SplitReferenceNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNodeMuncher.MunchingState; import com.android.tools.r8.ir.optimize.string.StringBuilderOracle.DefaultStringBuilderOracle; import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.DepthFirstSearchWorkList; @@ -42,6 +44,9 @@ import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; +import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; @@ -284,7 +289,7 @@ } else if (oracle.isAppend(instruction)) { AppendNode appendNode = createAppendNode(instruction.asInvokeVirtual()); appendNode.setConstantArgument(oracle.getConstantArgument(instruction)); - Value arg = invoke.getOperand(1).getAliasedValue(); + Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue(); if (oracle.hasStringBuilderType(arg)) { insertImplicitToStringNode( arg, instruction, appendNode, escapeState, nodeConsumer); @@ -416,10 +421,13 @@ DFSNodeWithState<BasicBlock, StringBuilderGraphState> node, List<DFSNodeWithState<BasicBlock, StringBuilderGraphState>> childStates) { StringBuilderGraphState state = node.getState(); + Reference2IntMap<Value> rootsInChildStateCounts = + new Reference2IntLinkedOpenHashMap<>(); for (DFSNodeWithState<BasicBlock, StringBuilderGraphState> childState : childStates) { StringBuilderGraphState childGraphState = childState.getState(); childGraphState.roots.forEach( (value, sbNode) -> { + rootsInChildStateCounts.put(value, rootsInChildStateCounts.getInt(value) + 1); StringBuilderNode currentRoot = state.roots.get(value); StringBuilderNode currentTail = state.tails.get(value); if (currentRoot == null) { @@ -446,6 +454,18 @@ childGraphState.isPartOfLoop = true; } } + // To ensure that we account for control flow correctly, we insert split reference nodes + // for all roots we've seen in only a subset of child states. + for (Entry<Value> valueEntry : rootsInChildStateCounts.reference2IntEntrySet()) { + assert valueEntry.getIntValue() <= childStates.size(); + if (valueEntry.getIntValue() < childStates.size()) { + SplitReferenceNode splitNode = StringBuilderNode.createSplitReferenceNode(); + StringBuilderNode tail = state.tails.get(valueEntry.getKey()); + assert tail != null; + splitNode.addPredecessor(tail); + tail.addSuccessor(splitNode); + } + } if (state.isPartOfLoop) { state.roots.replaceAll( (value, currentRoot) -> { @@ -475,6 +495,7 @@ Map<Value, StringBuilderNode> stringBuilderGraphs) { Map<Instruction, StringBuilderAction> actions = new IdentityHashMap<>(); // Build state to allow munching over the string builder graphs. + Map<StringBuilderNode, NewInstanceNode> newInstances = new IdentityHashMap<>(); Set<StringBuilderNode> inspectingCapacity = Sets.newIdentityHashSet(); Set<StringBuilderNode> looping = Sets.newIdentityHashSet(); Map<StringBuilderNode, Set<StringBuilderNode>> materializing = new IdentityHashMap<>(); @@ -492,6 +513,10 @@ while (workList.hasNext()) { StringBuilderNode next = workList.next(); nodeToRoots.put(next, root); + if (next.isNewInstanceNode()) { + StringBuilderNode existing = newInstances.put(root, next.asNewInstanceNode()); + assert existing == null; + } if (next.isInitOrAppend()) { ImplicitToStringNode dependency = next.asInitOrAppend().getImplicitToStringNode(); if (dependency != null) { @@ -508,7 +533,7 @@ escaping.add(root); } if (next.isToStringNode() || next.isImplicitToStringNode()) { - materializingInstructions.add(root); + materializingInstructions.add(next); } if (next.isInspectingNode()) { inspectingCapacity.add(root); @@ -518,7 +543,8 @@ }); MunchingState munchingState = - new MunchingState(actions, escaping, inspectingCapacity, looping, materializing, oracle); + new MunchingState( + actions, escaping, inspectingCapacity, looping, materializing, newInstances, oracle); boolean keepMunching = true; for (int i = 0; i < NUMBER_OF_MUNCHING_PASSES && keepMunching; i++) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java index 01be10c..0659a8a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitOrAppend; +import com.android.tools.r8.ir.optimize.string.StringBuilderNode.NewInstanceNode; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.StringBuilderInstruction; import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ToStringNode; import com.android.tools.r8.utils.WorkList; @@ -39,6 +40,7 @@ private final Set<StringBuilderNode> inspectingCapacity; private final Set<StringBuilderNode> looping; private final Map<StringBuilderNode, Set<StringBuilderNode>> materializingInstructions; + private final Map<StringBuilderNode, NewInstanceNode> newInstances; private final Map<Value, String> optimizedStrings = new IdentityHashMap<>(); MunchingState( @@ -47,14 +49,24 @@ Set<StringBuilderNode> inspectingCapacity, Set<StringBuilderNode> looping, Map<StringBuilderNode, Set<StringBuilderNode>> materializingInstructions, + Map<StringBuilderNode, NewInstanceNode> newInstances, StringBuilderOracle oracle) { this.actions = actions; this.escaping = escaping; this.inspectingCapacity = inspectingCapacity; this.looping = looping; this.materializingInstructions = materializingInstructions; + this.newInstances = newInstances; this.oracle = oracle; } + + public NewInstanceNode getNewInstanceNode(StringBuilderNode root) { + return newInstances.get(root); + } + + public boolean isLooping(StringBuilderNode root) { + return looping.contains(root); + } } private interface PeepholePattern { @@ -132,11 +144,11 @@ if (!currentNode.isToStringNode() && !currentNode.isImplicitToStringNode()) { return false; } - StringBuilderNode root = findFirstNonSentinelRoot(originalRoot); - if (!root.isNewInstanceNode() || !root.hasSingleSuccessor()) { + NewInstanceNode newInstanceNode = munchingState.getNewInstanceNode(originalRoot); + if (newInstanceNode == null || !newInstanceNode.hasSingleSuccessor()) { return false; } - StringBuilderNode init = root.getSingleSuccessor(); + StringBuilderNode init = newInstanceNode.getSingleSuccessor(); String rootConstantArgument = getConstantArgumentForNode(init, munchingState); if (rootConstantArgument == null || !init.isInitNode()) { return false; @@ -167,7 +179,6 @@ ToStringNode toStringNode = currentNode.asToStringNode(); munchingState.actions.put( toStringNode.getInstruction(), new ReplaceByConstantString(constantArgument)); - munchingState.materializingInstructions.get(originalRoot).remove(currentNode); String oldValue = munchingState.optimizedStrings.put( toStringNode.getInstruction().outValue(), constantArgument); @@ -180,29 +191,12 @@ munchingState.actions.put( initOrAppend.getInstruction(), new AppendWithNewConstantString(constantArgument)); } + munchingState.materializingInstructions.get(originalRoot).remove(currentNode); currentNode.removeNode(); return true; } } - /** - * Find the first non split reference node or loop-node, which are nodes inserted to track - * control-flow. - */ - private static StringBuilderNode findFirstNonSentinelRoot(StringBuilderNode root) { - WorkList<StringBuilderNode> workList = WorkList.newIdentityWorkList(root); - while (workList.hasNext()) { - StringBuilderNode node = workList.next(); - if (!node.isSplitReferenceNode() && !node.isLoopNode()) { - return node; - } - if (node.hasSingleSuccessor()) { - workList.addIfNotSeen(node.getSingleSuccessor()); - } - } - return root; - } - private static String getConstantArgumentForNode( StringBuilderNode node, MunchingState munchingState) { if (node.isAppendNode()) { @@ -249,7 +243,7 @@ // Remove appends if the string builder do not escape, is not inspected or materialized // and if it is not part of a loop. removeNode = false; - if (currentNode.isSplitReferenceNode()) { + if (currentNode.isSplitReferenceNode() && !munchingState.isLooping(root)) { removeNode = currentNode.getSuccessors().isEmpty() || currentNode.hasSinglePredecessor(); } else if (currentNode.isAppendNode() && !isEscaping) { AppendNode appendNode = currentNode.asAppendNode(); @@ -261,7 +255,7 @@ && currentNode.getSuccessors().isEmpty(); if (canRemoveIfNoInspectionOrMaterializing && canRemoveIfLastAndNoLoop - && munchingState.oracle.canObserveStringBuilderCall( + && !munchingState.oracle.canObserveStringBuilderCall( currentNode.asAppendNode().getInstruction())) { munchingState.actions.put( appendNode.getInstruction(), RemoveStringBuilderAction.getInstance());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java index d7d7f59..063f791 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InvokeDirect; +import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.Value; import java.util.List; @@ -109,7 +110,8 @@ if (!instruction.isInvokeMethodWithReceiver()) { return null; } - if (isAppend(instruction)) { + if (isAppend(instruction) + && !isAppendWithSubArray(instruction.asInvokeMethodWithReceiver())) { return getConstantStringForAppend(instruction.asInvokeVirtual()); } else if (isInit(instruction)) { return getConstantStringForInit(instruction.asInvokeDirect()); @@ -165,14 +167,16 @@ return false; } DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod(); - if (factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod) - || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod)) { - return false; - } return factory.stringBuilderMethods.isAppendMethod(invokedMethod) || factory.stringBufferMethods.isAppendMethod(invokedMethod); } + public boolean isAppendWithSubArray(InvokeMethodWithReceiver instruction) { + DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod(); + return factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod) + || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod); + } + @Override public boolean canObserveStringBuilderCall(Instruction instruction) { if (!instruction.isInvokeMethod()) { @@ -185,7 +189,11 @@ || factory.stringBuilderMethods.charSequenceConstructor == invokedMethod || factory.stringBufferMethods.charSequenceConstructor == invokedMethod) { assert instruction.inValues().size() == 2; - return instruction.inValues().get(1).getType().isStringType(factory); + return !instruction.inValues().get(1).getType().isStringType(factory); + } + if (factory.stringBuilderMethods.isAppendCharArrayMethod(invokedMethod) + || factory.stringBufferMethods.isAppendCharArrayMethod(invokedMethod)) { + return instruction.asInvokeVirtual().getFirstNonReceiverArgument().isMaybeNull(); } return false; }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index 707bf1c..6586750 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -261,9 +261,7 @@ String name = getNamingLens().lookupInternalName(clazz.type); String signature = clazz.getClassSignature().toRenamedString(getNamingLens(), isTypeMissing); String superName = - clazz.type == options.itemFactory.objectType - ? null - : getNamingLens().lookupInternalName(clazz.superType); + clazz.hasSuperType() ? getNamingLens().lookupInternalName(clazz.superType) : null; String[] interfaces = new String[clazz.interfaces.values.length]; for (int i = 0; i < clazz.interfaces.values.length; i++) { interfaces[i] = getNamingLens().lookupInternalName(clazz.interfaces.values[i]);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java index 7f4c47d..eb07f4b 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.kotlin; -import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo; import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour; @@ -19,7 +18,6 @@ import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueArray; import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour; -import com.android.tools.r8.utils.StringDiagnostic; import java.util.IdentityHashMap; import java.util.Map; import java.util.function.Consumer; @@ -36,7 +34,8 @@ private static final int SYNTHETIC_CLASS_KIND = 3; public static KotlinClassLevelInfo getKotlinInfo( - DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) { + DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) + throws KotlinMetadataException { DexAnnotation meta = clazz.annotations().getFirstMatching(appView.dexItemFactory().kotlinMetadataType); return meta != null ? getKotlinInfo(clazz, appView, keepByteCode, meta) : getNoKotlinInfo(); @@ -46,35 +45,18 @@ DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode, - DexAnnotation annotation) { - try { - Kotlin kotlin = appView.dexItemFactory().kotlin; - KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation); - return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode); - } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) { - appView - .reporter() - .info( - new StringDiagnostic( - "Class " - + clazz.type.toSourceString() - + " has malformed kotlin.Metadata: " - + e.getMessage())); - return getInvalidKotlinInfo(); - } catch (Throwable e) { - appView - .reporter() - .info( - new StringDiagnostic( - "Unexpected error while reading " - + clazz.type.toSourceString() - + "'s kotlin.Metadata: " - + e.getMessage())); - return getNoKotlinInfo(); + DexAnnotation annotation) + throws KotlinMetadataException { + Kotlin kotlin = appView.dexItemFactory().kotlin; + KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation); + if (kMetadata == null) { + throw new KotlinMetadataException(); } + return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode); } - public static boolean isLambda(AppView<?> appView, DexClass clazz) { + public static boolean isLambda(AppView<?> appView, DexClass clazz) + throws KotlinMetadataException { DexItemFactory dexItemFactory = appView.dexItemFactory(); Kotlin kotlin = dexItemFactory.kotlin; Flavour flavour = getFlavour(clazz, kotlin); @@ -108,7 +90,7 @@ } public static KotlinClassMetadata toKotlinClassMetadata( - Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) { + Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) throws KotlinMetadataException { return toKotlinClassMetadata(kotlin, toElementMap(metadataAnnotation)); } @@ -122,12 +104,11 @@ } private static KotlinClassMetadata toKotlinClassMetadata( - Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) { + Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) + throws KotlinMetadataException { int k = getKind(kotlin, elementMap); DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion); int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv"); - DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion); - int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv"); DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1); String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1"); DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2); @@ -139,8 +120,12 @@ DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt); Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue(); - KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi); - return KotlinClassMetadata.read(header); + try { + KotlinClassHeader header = new KotlinClassHeader(k, mv, d1, d2, xs, pn, xi); + return KotlinClassMetadata.read(header); + } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) { + throw new KotlinMetadataException(e); + } } private static int getKind(Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java index 83947d7..66dc1be 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -13,6 +13,9 @@ public class KotlinMetadataDiagnostic implements Diagnostic { + private static final String LINK_TO_PAGE = + "https://developer.android.com/studio/build/kotlin-d8-r8-versions"; + private final Origin origin; private final Position position; private final String message; @@ -73,4 +76,14 @@ + StringUtils.LINE_SEPARATOR + StringUtils.stacktraceAsString(t)); } + + static KotlinMetadataDiagnostic unknownMetadataVersion() { + return new KotlinMetadataDiagnostic( + Origin.unknown(), + Position.UNKNOWN, + "An error occurred when parsing kotlin metadata. This normally happens when using a newer" + + " version of kotlin than the kotlin version released when this version of R8 was" + + " created. To find compatible kotlin versions, please see: " + + LINK_TO_PAGE); + } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java index 2a3698b..6b5d589 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.kotlin; import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation; +import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo; import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo; import com.android.tools.r8.errors.Unreachable; @@ -26,6 +27,7 @@ import com.android.tools.r8.shaking.Enqueuer; import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier; import com.android.tools.r8.shaking.KeepClassInfo; +import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Sets; import java.util.Set; @@ -36,6 +38,7 @@ private final AppView<?> appView; private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier; private final Set<DexType> prunedTypes; + private boolean reportedUnknownMetadataVersion; public KotlinMetadataEnqueuerExtension( AppView<?> appView, @@ -66,26 +69,52 @@ enqueuer.forAllLiveClasses( clazz -> { assert clazz.getKotlinInfo().isNoKotlinInformation(); - if (enqueuer - .getKeepInfo(clazz) - .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { - if (KotlinClassMetadataReader.isLambda(appView, clazz) - && clazz.hasClassInitializer()) { - feedback.classInitializerMayBePostponed(clazz.getClassInitializer()); + try { + if (enqueuer + .getKeepInfo(clazz) + .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { + if (KotlinClassMetadataReader.isLambda(appView, clazz) + && clazz.hasClassInitializer()) { + feedback.classInitializerMayBePostponed(clazz.getClassInitializer()); + } + clazz.clearKotlinInfo(); + clazz.removeAnnotations( + annotation -> + annotation.getAnnotationType() + == appView.dexItemFactory().kotlinMetadataType); + } else { + clazz.setKotlinInfo( + KotlinClassMetadataReader.getKotlinInfo( + clazz, + appView, + method -> keepByteCodeFunctions.add(method.getReference()))); + if (clazz.getEnclosingMethodAttribute() != null + && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { + localOrAnonymousClasses.add(clazz); + } } - clazz.clearKotlinInfo(); - clazz.removeAnnotations( - annotation -> - annotation.getAnnotationType() - == appView.dexItemFactory().kotlinMetadataType); - } else { - clazz.setKotlinInfo( - KotlinClassMetadataReader.getKotlinInfo( - clazz, appView, method -> keepByteCodeFunctions.add(method.getReference()))); - if (clazz.getEnclosingMethodAttribute() != null - && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { - localOrAnonymousClasses.add(clazz); - } + } catch (KotlinMetadataException e) { + appView + .reporter() + .info( + new StringDiagnostic( + "Class " + + clazz.type.toSourceString() + + " has malformed kotlin.Metadata: " + + e.getMessage())); + clazz.setKotlinInfo(getInvalidKotlinInfo()); + reportUnknownMetadataVersion(); + } catch (Throwable e) { + appView + .reporter() + .info( + new StringDiagnostic( + "Unexpected error while reading " + + clazz.type.toSourceString() + + "'s kotlin.Metadata: " + + e.getMessage())); + clazz.setKotlinInfo(getNoKotlinInfo()); + reportUnknownMetadataVersion(); } }); for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) { @@ -138,6 +167,13 @@ }); } + private void reportUnknownMetadataVersion() { + if (!reportedUnknownMetadataVersion) { + reportedUnknownMetadataVersion = true; + appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion()); + } + } + public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier { private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java new file mode 100644 index 0000000..4ed669e --- /dev/null +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java
@@ -0,0 +1,15 @@ +// Copyright (c) 2022, 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.kotlin; + +/** Exception class to ensure that exceptions arising from kotlin metadata parsing are handled */ +public class KotlinMetadataException extends Exception { + + KotlinMetadataException() {} + + KotlinMetadataException(Throwable cause) { + super(cause); + } +}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java index 5353319..415995e 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -195,11 +195,15 @@ private boolean verifyRewrittenMetadataIsEquivalent( DexAnnotation original, DexAnnotation rewritten) { - String originalMetadata = - kotlinMetadataToString("", toKotlinClassMetadata(kotlin, original.annotation)); - String rewrittenMetadata = - kotlinMetadataToString("", toKotlinClassMetadata(kotlin, rewritten.annotation)); - assert originalMetadata.equals(rewrittenMetadata) : "The metadata should be equivalent"; + try { + String originalMetadata = + kotlinMetadataToString("", toKotlinClassMetadata(kotlin, original.annotation)); + String rewrittenMetadata = + kotlinMetadataToString("", toKotlinClassMetadata(kotlin, rewritten.annotation)); + assert originalMetadata.equals(rewrittenMetadata) : "The metadata should be equivalent"; + } catch (KotlinMetadataException ignored) { + + } return true; } @@ -228,11 +232,6 @@ new DexAnnotationElement( kotlin.metadata.metadataVersion, createIntArray(metadataVersion))); } - if (writeMetadataFieldInfo.writeByteCodeVersion) { - elements.add( - new DexAnnotationElement( - kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion()))); - } if (writeMetadataFieldInfo.writeKind) { elements.add( new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java index 3eb0ec1..bdd2b9b 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -18,8 +18,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -512,7 +512,7 @@ if (peekChar(0) == ')') { arguments = new String[0]; } else { - List<String> items = new LinkedList<>(); + List<String> items = new ArrayList<>(); items.add(parseType(true)); skipWhitespace(); while (peekChar(0) != ')') {
diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java index d8b5946..c1ee380 100644 --- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java +++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
@@ -148,7 +148,8 @@ && !accessInfo.isWrittenFromMethodHandle() && accessInfo.isWrittenOnlyInMethodSatisfying( method -> - method.getDefinition().isInitializer(flags.isStatic()) + method.getDefinition().isInitializer() + && method.getAccessFlags().isStatic() == flags.isStatic() && method.getHolder() == field.getHolder()) && !flags.isFinal() && !flags.isVolatile()) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java index 63f4483..8145a9b 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -105,7 +105,9 @@ new RepackagingUseRegistry(appView, this, clazz, libraryBoundaryNode); // Trace the references to the immediate super types. - registry.registerTypeReference(clazz.getSuperType(), appView.graphLens()); + if (clazz.superType != null) { + registry.registerTypeReference(clazz.getSuperType(), appView.graphLens()); + } clazz.interfaces.forEach(type -> registry.registerTypeReference(type, appView.graphLens())); // Trace the references from the class annotations. @@ -156,11 +158,13 @@ .forEachType(type -> registry.registerTypeReference(type, appView.graphLens())); // Check if this overrides a package-private method. - DexClass superClass = - appView.definitionFor(method.getHolder().getSuperType(), method.getHolder()); - if (superClass != null) { - registry.registerMemberAccess( - appView.appInfo().resolveMethodOnLegacy(superClass, method.getReference())); + if (method.getHolder().superType != null) { + DexClass superClass = + appView.definitionFor(method.getHolder().getSuperType(), method.getHolder()); + if (superClass != null) { + registry.registerMemberAccess( + appView.appInfo().resolveMethodOnLegacy(superClass, method.getReference())); + } } // Trace the references in the method and method parameter annotations.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 527cec3..6065527 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -35,6 +35,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.ListUtils; +import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; @@ -53,6 +54,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -592,14 +594,12 @@ List<EquivalenceGroup<T>> groups = groupEquivalent(appView, members, intermediate, classToFeatureSplitMap); for (EquivalenceGroup<T> group : groups) { - // If the group already has a representative, then this representative is pinned. - // Otherwise, we select a deterministic representative. - if (group.hasRepresentative()) { + // If the group has a pinned representative don't construct an external type. + if (group.isPinned(appView)) { EquivalenceGroup<T> previous = equivalences.put(group.getRepresentative().getHolder().getType(), group); assert previous == null; } else { - group.selectDeterministicRepresentative(); groupsPerPrefix .computeIfAbsent( group.getRepresentative().getPrefixForExternalSyntheticType(), @@ -666,52 +666,94 @@ List<T> potentialEquivalence, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) { - List<EquivalenceGroup<T>> groups = new ArrayList<>(); - // Each other member is in a shared group if it is actually equivalent to the first member. - for (T synthetic : potentialEquivalence) { - if (groups.size() > GROUP_COUNT_THRESHOLD) { - return ListUtils.map( - potentialEquivalence, m -> new EquivalenceGroup<>(m, isPinned(appView, m))); + // Fast path the singleton groups. + if (potentialEquivalence.size() == 1) { + return ImmutableList.of(EquivalenceGroup.singleton(potentialEquivalence.get(0))); + } + assert !potentialEquivalence.isEmpty(); + + // Sort the potential members and split them up into potential groups of members that are + // actually equal. + GraphLens graphLens = appView.graphLens(); + boolean includeContext = + intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled(); + List<T> sortedPotentialMembers = + ListUtils.sort( + potentialEquivalence, + (a, b) -> a.compareTo(b, includeContext, graphLens, classToFeatureSplitMap)); + List<List<T>> potentialGroups = new ArrayList<>(); + { + List<T> currentGroup = new ArrayList<>(); + T currentRepresentative = sortedPotentialMembers.get(0); + currentGroup.add(currentRepresentative); + for (int i = 1; i < sortedPotentialMembers.size(); i++) { + T member = sortedPotentialMembers.get(i); + if (!currentRepresentative.isEquivalentTo( + member, includeContext, graphLens, classToFeatureSplitMap)) { + potentialGroups.add(currentGroup); + currentGroup = new ArrayList<>(); + currentRepresentative = member; + } + currentGroup.add(member); } - boolean mustBeRepresentative = isPinned(appView, synthetic); - EquivalenceGroup<T> equivalenceGroup = null; - for (EquivalenceGroup<T> group : groups) { - boolean includeContext = - intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled(); - if (synthetic.isEquivalentTo( - group.hasRepresentative() - ? group.getRepresentative() - : group.getFirstNonRepresentativeMember(), - includeContext, - appView.graphLens(), - classToFeatureSplitMap)) { - if (mustBeRepresentative && group.hasRepresentative()) { - // Check if the current synthetic is smaller than the group's representative. If so, - // then replace the representative, to ensure deterministic groups, and create a new - // singleton group containing the old representative. Otherwise, just add a singleton - // group containing the new synthetic. - T representative = group.getRepresentative(); - if (representative - .toReference() - .getReference() - .compareTo(synthetic.toReference().getReference()) - > 0) { - group.replaceAndRemoveRepresentative(synthetic); - synthetic = representative; + potentialGroups.add(currentGroup); + } + + // Compute the actual groups by picking the group representatives. In cases of pinned members + // this may need to split out representatives into their own singleton groups. + List<EquivalenceGroup<T>> actualGroups = new ArrayList<>(); + for (List<T> potentialGroup : potentialGroups) { + assert !potentialGroup.isEmpty(); + if (potentialGroup.size() == 1) { + actualGroups.add(EquivalenceGroup.singleton(potentialGroup.get(0))); + continue; + } + List<T> forcedRepresentatives = null; + if (appView.enableWholeProgramOptimizations()) { + Iterator<T> it = potentialGroup.iterator(); + while (it.hasNext()) { + T member = it.next(); + boolean mustBeRepresentative = isPinned(appView, member); + if (mustBeRepresentative) { + if (forcedRepresentatives == null) { + forcedRepresentatives = new ArrayList<>(); } - } else { - equivalenceGroup = group; + forcedRepresentatives.add(member); + it.remove(); } - break; } } - if (equivalenceGroup != null) { - equivalenceGroup.add(synthetic, mustBeRepresentative); + if (forcedRepresentatives != null) { + assert appView.enableWholeProgramOptimizations(); + T representative = + findSmallestMember( + forcedRepresentatives, + other -> actualGroups.add(EquivalenceGroup.pinnedSingleton(other))); + actualGroups.add(EquivalenceGroup.pinnedGroup(representative, potentialGroup)); } else { - groups.add(new EquivalenceGroup<>(synthetic, mustBeRepresentative)); + List<T> members = new ArrayList<>(potentialGroup.size() - 1); + T representative = findSmallestMember(potentialGroup, members::add); + actualGroups.add(EquivalenceGroup.unpinnedGroup(representative, members)); } } - return groups; + return actualGroups; + } + + private static <T extends SyntheticDefinition<?, T, ?>> T findSmallestMember( + List<T> members, Consumer<T> notSmallestCallback) { + assert !members.isEmpty(); + T smallest = members.get(0); + for (int i = 1; i < members.size(); i++) { + T member = members.get(i); + if (member.toReference().getReference().compareTo(smallest.toReference().getReference()) + < 0) { + notSmallestCallback.accept(smallest); + smallest = member; + } else { + notSmallestCallback.accept(member); + } + } + return smallest; } /** @@ -846,28 +888,47 @@ private static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> { // The members of the equivalence group, *excluding* the representative. - private List<T> members = new ArrayList<>(); - private T representative; + private final List<T> members; + private final T representative; + private final OptionalBool pinned; - EquivalenceGroup(T member, boolean isRepresentative) { - add(member, isRepresentative); + static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> singleton(T member) { + return new EquivalenceGroup<>(member, ImmutableList.of(), OptionalBool.UNKNOWN); } - void add(T member, boolean isRepresentative) { - if (isRepresentative) { - assert !hasRepresentative(); - representative = member; - } else { - members.add(member); + static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> unpinnedGroup( + T representative, List<T> members) { + return new EquivalenceGroup<>( + representative, Collections.unmodifiableList(members), OptionalBool.FALSE); + } + + static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> pinnedSingleton(T member) { + return new EquivalenceGroup<>(member, ImmutableList.of(), OptionalBool.TRUE); + } + + static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> pinnedGroup( + T representative, List<T> members) { + return new EquivalenceGroup<>( + representative, Collections.unmodifiableList(members), OptionalBool.TRUE); + } + + private EquivalenceGroup(T representative, List<T> members, OptionalBool pinned) { + assert representative != null; + assert members != null; + assert pinned != null; + this.members = members; + this.representative = representative; + this.pinned = pinned; + } + + public boolean isPinned(AppView<?> appView) { + if (pinned.isTrue()) { + return true; } - } - - int compareToIncludingContext( - EquivalenceGroup<T> other, - GraphLens graphLens, - ClassToFeatureSplitMap classToFeatureSplitMap) { - return getRepresentative() - .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap); + if (pinned.isFalse()) { + return false; + } + return SyntheticFinalization.isPinned(appView, representative); } public void forEach(Consumer<T> consumer) { @@ -879,64 +940,23 @@ members.forEach(consumer); } - T getFirstNonRepresentativeMember() { - assert !members.isEmpty(); - return members.get(0); - } - T getRepresentative() { - assert hasRepresentative(); return representative; } - boolean hasRepresentative() { - return representative != null; - } - boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) { return getRepresentative().getContext().isDerivedFromMainDexList(mainDexInfo) || Iterables.any( members, member -> member.getContext().isDerivedFromMainDexList(mainDexInfo)); } - void replaceAndRemoveRepresentative(T representative) { - assert hasRepresentative(); - this.representative = representative; - } - - void selectDeterministicRepresentative() { - // Pick a deterministic member as representative. - assert !hasRepresentative(); - int representativeIndex = 0; - for (int i = 1; i < members.size(); i++) { - T next = members.get(i); - T representative = members.get(representativeIndex); - if (next.toReference().getReference().compareTo(representative.toReference().getReference()) - < 0) { - representativeIndex = i; - } - } - T representative = members.get(representativeIndex); - members.set(representativeIndex, ListUtils.last(members)); - ListUtils.removeLast(members); - setRepresentative(representative); - } - - void setRepresentative(T representative) { - assert !hasRepresentative(); - this.representative = representative; - } - @Override public String toString() { - if (hasRepresentative()) { - return "EquivalenceGroup{ size = " - + (members.size() + 1) - + ", repr = " - + getRepresentative() - + " }"; - } - return "EquivalenceGroup{ size = " + members.size() + " }"; + return "EquivalenceGroup{ size = " + + (members.size() + 1) + + ", repr = " + + getRepresentative() + + " }"; } } }
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java index 1c4aef5..cd6dcb6 100644 --- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java +++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -88,7 +88,8 @@ return -1; } - public static <T> Iterable<T> filter(Iterable<T> iterable, Predicate<? super T> predicate) { + public static <S, T extends S> Iterable<T> filter( + Iterable<S> iterable, Predicate<? super S> predicate) { return () -> IteratorUtils.filter(iterable.iterator(), predicate); }
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java index 9c6e2a7..406d482 100644 --- a/src/main/java/com/android/tools/r8/utils/ListUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -268,12 +268,26 @@ void accept(T item, int index); } + public static <T> List<T> sort(List<T> items, Comparator<T> comparator) { + List<T> sorted = new ArrayList<>(items); + sorted.sort(comparator); + return sorted; + } + public static <T> void destructiveSort(List<T> items, Comparator<T> comparator) { items.sort(comparator); } // Utility to add a slow verification of a comparator as part of sorting. Note that this // should not generally be used in asserts unless the quadratic behavior can be tolerated. + public static <T> List<T> sortAndVerify(List<T> items, Comparator<T> comparator) { + List<T> sorted = sort(items, comparator); + assert verifyComparatorOnSortedList(sorted, comparator); + return sorted; + } + + // Utility to add a slow verification of a comparator as part of sorting. Note that this + // should not generally be used in asserts unless the quadratic behavior can be tolerated. public static <T> void destructiveSortAndVerify(List<T> items, Comparator<T> comparator) { destructiveSort(items, comparator); assert verifyComparatorOnSortedList(items, comparator);
diff --git a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java index 3d3aecc..bdb4119 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
@@ -126,10 +126,9 @@ DexCode code = method.getMethod().getCode().asDexCode(); assertTrue(code.instructions[0] instanceof DexConstClass); - assertTrue(code.instructions[1] instanceof DexConstClass); - assertTrue(code.instructions[2] instanceof DexConstString); - DexConstString constString = (DexConstString) code.instructions[2]; - assertNotEquals("foo", constString.getString().toString()); + assertTrue(code.instructions[1] instanceof DexConstString); + assertNotEquals("foo", code.instructions[1].asConstString().getString().toString()); + assertTrue(code.instructions[2] instanceof DexConstClass); assertTrue(code.instructions[3] instanceof DexInvokeStatic); assertTrue(code.instructions[4] instanceof DexReturnVoid); }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java index 7a30afd..812eb63 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -93,15 +93,14 @@ assertTrue(method.isPresent()); DexCode code = method.getMethod().getCode().asDexCode(); - assertTrue(code.instructions[0] instanceof DexConstClass); - assertTrue(code.instructions[1] instanceof DexConst4); - assertTrue(code.instructions[2] instanceof DexNewArray); - assertTrue(code.instructions[3] instanceof DexConst4); + assertTrue(code.instructions[0] instanceof DexConst4); + assertTrue(code.instructions[1] instanceof DexNewArray); + assertTrue(code.instructions[2] instanceof DexConst4); + assertTrue(code.instructions[3] instanceof DexConstClass); assertTrue(code.instructions[4] instanceof DexAputObject); assertTrue(code.instructions[5] instanceof DexConstClass); assertTrue(code.instructions[6] instanceof DexConstString); - DexConstString constString = (DexConstString) code.instructions[6]; - assertNotEquals("foo", constString.getString().toString()); + assertNotEquals("foo", code.instructions[6].asConstString().getString().toString()); assertTrue(code.instructions[7] instanceof DexInvokeVirtual); assertTrue(code.instructions[8] instanceof DexReturnVoid); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java index 54c88bf..70d445d 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
@@ -4,10 +4,11 @@ package com.android.tools.r8.desugar.desugaredlibrary.jdk11; -import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS; +import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF; import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; @@ -17,6 +18,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -38,9 +40,9 @@ @Parameters(name = "{0}, spec: {1}, {2}") public static List<Object[]> data() { return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), + getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build(), ImmutableList.of(JDK11_PATH), - DEFAULT_SPECIFICATIONS); + SPECIFICATIONS_WITH_CF2CF); } public InputStreamTransferToTest( @@ -54,6 +56,11 @@ @Test public void test() throws Exception { + // The method is not present on JDK8 so if we don't desugar that won't work. + Assume.assumeFalse( + parameters.isCfRuntime(CfVm.JDK8) + && !libraryDesugaringSpecification.hasNioFileDesugaring(parameters) + && compilationSpecification.isCfToCf()); testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) .addProgramFiles(INPUT_JAR) .addKeepMainRule(MAIN_CLASS)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnNewInstanceCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnNewInstanceCanonicalizationTest.java new file mode 100644 index 0000000..d8950eb --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnNewInstanceCanonicalizationTest.java
@@ -0,0 +1,70 @@ +// Copyright (c) 2022, 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.canonicalization; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InstanceGetOnNewInstanceCanonicalizationTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertEquals( + parameters.isCfRuntime() ? 2 : 1, + mainMethodSubject + .streamInstructions() + .filter(InstructionSubject::isInstanceGet) + .count()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello, world!"); + } + + static class Main { + + final String field = System.currentTimeMillis() > 0 ? ", " : null; + + public static void main(String[] args) { + Main main = new Main(); + if (System.currentTimeMillis() > 0) { + System.out.print("Hello"); + System.out.print(main.field); + System.out.println("world!"); + } else { + System.out.println(main.field); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnPhiCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnPhiCanonicalizationTest.java new file mode 100644 index 0000000..8fafc8f --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/InstanceGetOnPhiCanonicalizationTest.java
@@ -0,0 +1,70 @@ +// Copyright (c) 2022, 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.canonicalization; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InstanceGetOnPhiCanonicalizationTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertEquals( + parameters.isCfRuntime() ? 2 : 1, + mainMethodSubject + .streamInstructions() + .filter(InstructionSubject::isInstanceGet) + .count()); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello, world!"); + } + + static class Main { + + final String field = System.currentTimeMillis() > 0 ? ", " : null; + + public static void main(String[] args) { + Main main = System.currentTimeMillis() > 0 ? new Main() : new Main(); + if (System.currentTimeMillis() > 0) { + System.out.print("Hello"); + System.out.print(main.field); + System.out.println("world!"); + } else { + System.out.println(main.field); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java index e7c5301..86fcb86 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
@@ -217,17 +217,21 @@ .apply(parameters) .inspect( inspector -> { - if (parameters.isCfRuntime()) { - // TODO(b/114002137): for now, string concatenation depends on rewriteMoveResult. - return; - } MethodSubject method = inspector.method(stringBuilderTest.method); assertThat(method, isPresent()); FoundMethodSubject foundMethodSubject = method.asFoundMethodSubject(); assertEquals( stringBuilderTest.stringBuilders, countStringBuilderInits(foundMethodSubject)); - assertEquals( - stringBuilderTest.appends, countStringBuilderAppends(foundMethodSubject)); + if (parameters.isCfRuntime() + && (stringBuilderTest.getMethodName().equals("diamondWithUseTest") + || stringBuilderTest.getMethodName().equals("intoPhiTest"))) { + // We are not doing block suffix optimization in CF. + assertEquals( + stringBuilderTest.appends + 1, countStringBuilderAppends(foundMethodSubject)); + } else { + assertEquals( + stringBuilderTest.appends, countStringBuilderAppends(foundMethodSubject)); + } assertEquals( stringBuilderTest.toStrings, countStringBuilderToStrings(foundMethodSubject)); })
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfInLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfInLoopTest.java new file mode 100644 index 0000000..67d3d66 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfInLoopTest.java
@@ -0,0 +1,58 @@ +// Copyright (c) 2022, 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.string; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.Sets; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** This is a regression test for b/237674850 */ +@RunWith(Parameterized.class) +public class StringBuilderWithConstantsBeforeAndInOpenEndedIfInLoopTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public StringBuilderWithConstantsBeforeAndInOpenEndedIfInLoopTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .addOptionsModification( + options -> + options.itemFactory.libraryMethodsReturningReceiver = Sets.newIdentityHashSet()) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class, "1", "3") + .assertSuccessWithOutputLines("0.1.2.3."); + } + + static class TestClass { + + public static void main(String[] args) { + StringBuilder builder = new StringBuilder(); + builder.append("0."); + for (int i = 0; i < args.length; ++i) { + builder.append(args[i]); + builder.append("."); + if (i != args.length - 1) { + builder.append("2."); + } + } + System.out.println(builder.toString()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfTest.java new file mode 100644 index 0000000..4219d7f --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithConstantsBeforeAndInOpenEndedIfTest.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2022, 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.string; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.Sets; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class StringBuilderWithConstantsBeforeAndInOpenEndedIfTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public StringBuilderWithConstantsBeforeAndInOpenEndedIfTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addKeepMainRule(TestClass.class) + .addOptionsModification( + options -> + options.itemFactory.libraryMethodsReturningReceiver = Sets.newIdentityHashSet()) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("foo"); + } + + static class TestClass { + + public static void main(String[] args) { + StringBuilder builder = new StringBuilder(); + builder.append("foo"); + if (System.currentTimeMillis() == 0) { + builder.append("bar"); + } + System.out.println(builder.toString()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java index 20a3077..8add503 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
@@ -5,7 +5,7 @@ import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -13,6 +13,8 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import java.util.Iterator; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,16 +48,13 @@ MethodSubject m = c.uniqueMethodWithName("invokeRangeTest"); assertThat(m, isPresent()); - long prev; - long curr = -1; Iterator<InstructionSubject> it = m.iterateInstructions(InstructionSubject::isConstNumber); + LongList numbers = new LongArrayList(); while (it.hasNext()) { - InstructionSubject instr = it.next(); - prev = curr; - curr = instr.getConstNumber(); - assertTrue(prev < curr); + numbers.add(it.next().getConstNumber()); } + assertEquals(new LongArrayList(new long[] {0, 1, 2, 3, 4, 5}), numbers); }); }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java index 90d7195..8983d39 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -35,6 +35,7 @@ import java.util.ListIterator; import java.util.Set; import java.util.function.Consumer; +import java.util.function.UnaryOperator; import org.junit.Test; public class RegisterMoveSchedulerTest { @@ -122,7 +123,7 @@ public void replaceCurrentInstructionWithThrow( AppView<?> appView, IRCode code, - ListIterator<BasicBlock> blockIterator, + BasicBlockIterator blockIterator, Value exceptionValue, Set<BasicBlock> blocksToRemove, Set<Value> affectedValues) { @@ -212,7 +213,10 @@ @Override public BasicBlock splitCopyCatchHandlers( - IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) { + IRCode code, + BasicBlockIterator blockIterator, + InternalOptions options, + UnaryOperator<BasicBlock> repositioningBlock) { throw new Unimplemented(); }
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java index 039ba9a..83ea780 100644 --- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java +++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -651,10 +651,10 @@ code, ImmutableList.of( DexInvokeDirect.class, - DexConstClass.class, DexConst4.class, DexNewArray.class, DexConst4.class, + DexConstClass.class, DexAputObject.class, DexConstString.class, DexInvokeStatic.class, @@ -712,10 +712,10 @@ code, ImmutableList.of( DexInvokeDirect.class, - DexConstClass.class, DexConst4.class, DexNewArray.class, DexConst4.class, + DexConstClass.class, DexAputObject.class, DexConstString.class, DexInvokeStatic.class,
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java index 1e0b59e..4c20192 100644 --- a/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java +++ b/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java
@@ -4,13 +4,10 @@ package com.android.tools.r8.repackage; -import static org.junit.Assert.assertThrows; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.V1_8; -import com.android.tools.r8.CompilationFailedException; -import com.android.tools.r8.DiagnosticsMatcher; import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -35,22 +32,17 @@ @Test public void testR8() throws Exception { - assertThrows( - CompilationFailedException.class, - () -> { - testForR8(parameters.getBackend()) - .addProgramClassFileData(dumpObject()) - .addProgramClasses(A.class, Main.class) - .setMinApi(parameters.getApiLevel()) - .enableInliningAnnotations() - .addKeepMainRule(Main.class) - .addDontWarn("*") - .compileWithExpectedDiagnostics( - diagnostics -> - // TODO(b/237124748): We should not throw an error. - diagnostics.assertErrorThatMatches( - DiagnosticsMatcher.diagnosticException(NullPointerException.class))); - }); + testForR8(parameters.getBackend()) + .addProgramClassFileData(dumpObject()) + .addProgramClasses(A.class, Main.class) + .setMinApi(parameters.getApiLevel()) + .enableInliningAnnotations() + .addKeepMainRule(Main.class) + .addDontWarn("*") + .addKeepClassRules(Object.class) + .allowDiagnosticWarningMessages() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A::foo"); } public static byte[] dumpObject() {
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java index 35dee6e..fd50cd6 100644 --- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java +++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -180,8 +180,8 @@ ":return", " return v0"); DexCode code = method.getCode().asDexCode(); - assertEquals(10, code.instructions.length); - assertTrue(code.instructions[9] instanceof DexReturn); + assertEquals(12, code.instructions.length); + assertTrue(code.instructions[11] instanceof DexReturn); } @Test @@ -452,6 +452,6 @@ // TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the // expectation might need changing with other optimizations. // TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return. - assertEquals(24, code.instructions.length); + assertEquals(26, code.instructions.length); } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java index cb65261..2c304ad 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -25,6 +25,7 @@ import com.android.tools.r8.graph.NestMemberClassAttribute; import com.android.tools.r8.graph.PermittedSubclassAttribute; import com.android.tools.r8.kotlin.KotlinClassMetadataReader; +import com.android.tools.r8.kotlin.KotlinMetadataException; import com.android.tools.r8.naming.ClassNamingForNameMapper; import com.android.tools.r8.naming.MemberNaming; import com.android.tools.r8.naming.MemberNaming.FieldSignature; @@ -544,9 +545,14 @@ if (!annotationSubject.isPresent()) { return new AbsentKmClassSubject(); } - KotlinClassMetadata metadata = - KotlinClassMetadataReader.toKotlinClassMetadata( - codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + KotlinClassMetadata metadata = null; + try { + metadata = + KotlinClassMetadataReader.toKotlinClassMetadata( + codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + } catch (KotlinMetadataException e) { + throw new RuntimeException(e); + } assertTrue(metadata instanceof KotlinClassMetadata.Class); KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) metadata; return new FoundKmClassSubject(codeInspector, getDexProgramClass(), kClass.toKmClass()); @@ -558,9 +564,14 @@ if (!annotationSubject.isPresent()) { return new AbsentKmPackageSubject(); } - KotlinClassMetadata metadata = - KotlinClassMetadataReader.toKotlinClassMetadata( - codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + KotlinClassMetadata metadata = null; + try { + metadata = + KotlinClassMetadataReader.toKotlinClassMetadata( + codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + } catch (KotlinMetadataException e) { + throw new RuntimeException(e); + } assertTrue(metadata instanceof KotlinClassMetadata.FileFacade || metadata instanceof KotlinClassMetadata.MultiFileClassPart); if (metadata instanceof KotlinClassMetadata.FileFacade) { @@ -579,8 +590,12 @@ if (!annotationSubject.isPresent()) { return null; } - return KotlinClassMetadataReader.toKotlinClassMetadata( - codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + try { + return KotlinClassMetadataReader.toKotlinClassMetadata( + codeInspector.getFactory().kotlin, annotationSubject.getAnnotation()); + } catch (KotlinMetadataException e) { + throw new RuntimeException(e); + } } @Override
diff --git a/tools/tag_versions.py b/tools/tag_versions.py index ebf14ba..28a1cae 100755 --- a/tools/tag_versions.py +++ b/tools/tag_versions.py
@@ -44,7 +44,10 @@ if args.branch: tag_r8_branch(args.branch, args) elif args.agp: - tag_agp_version(args.agp, args) + if (args.agp == 'all'): + tag_all_agp_versions(args) + else: + tag_agp_version(args.agp, args) else: print("Should use a top-level option, such as --branch or --agp.") return 1 @@ -71,6 +74,26 @@ return None return output +def tag_all_agp_versions(args): + with utils.TempDir() as temp: + url = "%s/maven-metadata.xml" % AGP_MAVEN + metadata = os.path.join(temp, "maven-metadata.xml") + try: + urllib.request.urlretrieve(url, metadata) + except urllib.error.HTTPError as e: + print('Could not find maven-metadata.xml for agp') + print(e) + return 1 + with open(metadata, 'r') as file: + data = file.read() + pattern = r'<version>(.+)</version>' + matches = re.findall(pattern, data) + matches.reverse() + for version in matches: + print('Tagging agp version ' + version) + tag_agp_version(version, args) + + def tag_agp_version(agp, args): tag = 'agp-%s' % agp result = get_tag_info_on_origin(tag)