| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.ir.optimize; |
| |
| 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.RESOURCE_CONST_NUMBER; |
| 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.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.ResourceConstNumber; |
| import com.android.tools.r8.ir.code.StaticGet; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.conversion.passes.BranchSimplifier; |
| import com.android.tools.r8.utils.AndroidApiLevelUtils; |
| 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 BranchSimplifier branchSimplifier; |
| private final ProgramMethod context; |
| private final IRCode code; |
| |
| private OptionalBool isAccessingVolatileField = OptionalBool.unknown(); |
| private Set<InstanceGet> ineligibleInstanceGetInstructions; |
| |
| public ConstantCanonicalizer(AppView<?> appView, ProgramMethod context, IRCode code) { |
| assert appView.options().isGeneratingDex(); |
| this.appView = appView; |
| this.branchSimplifier = new BranchSimplifier(appView); |
| this.context = context; |
| this.code = code; |
| } |
| |
| 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.hasBlock() |
| || (!object.isDefinedByInstructionSatisfying(Instruction::instructionTypeCanThrow) |
| && object.getBlock().hasCatchHandlers())) { |
| ineligibleInstanceGetInstructions.add(instanceGet); |
| } |
| } |
| } |
| return ineligibleInstanceGetInstructions; |
| } |
| |
| private Set<BasicBlock> computeDirectAndIndirectCatchHandlerBlocks() { |
| WorkList<BasicBlock> catchHandlerBlocks = WorkList.newIdentityWorkList(); |
| code.getBlocks() |
| .forEach( |
| block -> catchHandlerBlocks.addIfNotSeen(block.getCatchHandlers().getAllTargets())); |
| 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<>() { |
| |
| @Override |
| public int hashCode(Instruction candidate) { |
| assert candidate.instructionTypeCanBeCanonicalized(); |
| switch (candidate.opcode()) { |
| case CONST_CLASS: |
| return candidate.asConstClass().getType().hashCode(); |
| case CONST_NUMBER: |
| return Long.hashCode(candidate.asConstNumber().getRawValue()) |
| + 13 * candidate.outType().hashCode(); |
| case RESOURCE_CONST_NUMBER: |
| return Integer.hashCode(candidate.asResourceConstNumber().getValue()); |
| case CONST_STRING: |
| return candidate.asConstString().getValue().hashCode(); |
| case DEX_ITEM_BASED_CONST_STRING: |
| return candidate.asDexItemBasedConstString().getItem().hashCode(); |
| case INSTANCE_GET: |
| case STATIC_GET: |
| return candidate.asFieldGet().getField().hashCode(); |
| default: |
| throw new Unreachable(); |
| } |
| } |
| |
| @Override |
| public boolean equals(Instruction a, Instruction 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 instruction : code.instructions()) { |
| if (isConstantCanonicalizationCandidate(instruction)) { |
| valuesDefinedByConstant |
| .computeIfAbsent(instruction, ignoreKey(ArrayList::new)) |
| .add(instruction); |
| } |
| } |
| |
| if (valuesDefinedByConstant.isEmpty()) { |
| 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<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<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 clear(); |
| } |
| |
| boolean shouldSimplifyIfs = false; |
| |
| // Insert instructions in the entry block. |
| Map<InstructionOrPhi, List<Instruction>> pendingInsertions = new IdentityHashMap<>(); |
| do { |
| Object2ObjectMap.Entry<Instruction, List<Instruction>> entry = iterator.next(); |
| Instruction canonicalizedConstant = entry.getKey(); |
| assert canonicalizedConstant.instructionTypeCanBeCanonicalized(); |
| Instruction newInstruction; |
| if (canonicalizedConstant.getBlock().isEntry()) { |
| newInstruction = canonicalizedConstant; |
| } else { |
| 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); |
| } |
| } |
| // Remove the canonicalized instructions. |
| for (Instruction oldInstruction : entry.getValue()) { |
| if (oldInstruction != newInstruction) { |
| oldInstruction.outValue().replaceUsers(newInstruction.outValue()); |
| 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 |= 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(); |
| 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) { |
| branchSimplifier.simplifyIf(code); |
| } |
| |
| code.removeRedundantBlocks(); |
| assert code.isConsistentSSA(appView); |
| return clear(); |
| } |
| |
| 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 RESOURCE_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 RESOURCE_CONST_NUMBER: |
| return ResourceConstNumber.copyOf(code, canonicalizedConstant.asResourceConstNumber()); |
| 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) { |
| if (!isConstant(instruction)) { |
| 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; |
| } |
| |
| public boolean isConstant(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 RESOURCE_CONST_NUMBER: |
| 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 (!AndroidApiLevelUtils.isApiSafeForReference( |
| instanceGet.getField().getType(), appView)) { |
| return false; |
| } |
| NewInstance newInstance = null; |
| if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { |
| newInstance = instanceGet.object().getDefinition().asNewInstance(); |
| if (newInstance.getUniqueConstructorInvoke(appView.dexItemFactory()) == null) { |
| return false; |
| } |
| } |
| if (!isReadOfEffectivelyFinalFieldOutsideInitializer(instanceGet, newInstance)) { |
| 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 (!AndroidApiLevelUtils.isApiSafeForReference( |
| staticGet.getField().getType(), appView)) { |
| return false; |
| } |
| if (!isReadOfEffectivelyFinalFieldOutsideInitializer(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; |
| } |
| return true; |
| } |
| |
| private boolean isReadOfEffectivelyFinalFieldOutsideInitializer(StaticGet staticGet) { |
| return isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet, null); |
| } |
| |
| private boolean isReadOfEffectivelyFinalFieldOutsideInitializer( |
| FieldGet fieldGet, NewInstance newInstance) { |
| 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(); |
| if (!resolvedField.isFinalOrEffectivelyFinal(appViewWithClassHierarchy)) { |
| return false; |
| } |
| if (!resolvedField.getAccessFlags().isFinal() && newInstance != null) { |
| // The effectively final property captured in the enqueuer may be invalidated by constructor |
| // inlining (in particular, fields that used only to be written in instance initializers from |
| // the enclosing class may now be written outside such constructors). If we see an |
| // instance-get on a newly created instance, we therefore bail-out since the field may in |
| // principle not be effectively final in this method. |
| 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; |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| 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 |
| // that can throw exceptions (since the value could be used on the exceptional edge). |
| InstructionListIterator it = entryBlock.listIterator(); |
| while (it.hasNext()) { |
| Instruction next = it.next(); |
| if (!next.isArgument()) { |
| canonicalizedConstant.setPosition(code.getEntryPosition()); |
| it.previous(); |
| break; |
| } |
| } |
| 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()) { |
| // 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().add(newInstruction); |
| } else { |
| 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) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |