| // Copyright (c) 2023, 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.api; |
| |
| import static com.android.tools.r8.utils.AndroidApiLevelUtils.isOutlinedAtSameOrLowerLevel; |
| |
| import com.android.tools.r8.androidapi.ComputedApiLevel; |
| import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProto; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.analysis.type.ClassTypeElement; |
| import com.android.tools.r8.ir.analysis.type.DynamicType; |
| import com.android.tools.r8.ir.analysis.type.Nullability; |
| import com.android.tools.r8.ir.analysis.type.TypeAnalysis; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.InvokeDirect; |
| import com.android.tools.r8.ir.code.InvokeStatic; |
| import com.android.tools.r8.ir.code.NewInstance; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.conversion.MethodProcessor; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; |
| import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder; |
| import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode; |
| import com.android.tools.r8.shaking.ComputeApiLevelUseRegistry; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * The InstanceInitializerOutliner will outline instance initializers and their NewInstance source. |
| * Unlike the ApiInvokeOutlinerDesugaring that works on CF, this works on IR to properly replace the |
| * users of the NewInstance call. |
| */ |
| public class InstanceInitializerOutliner { |
| |
| private final AppView<?> appView; |
| private final DexItemFactory factory; |
| |
| private final List<ProgramMethod> synthesizedMethods = new ArrayList<>(); |
| |
| public InstanceInitializerOutliner(AppView<?> appView) { |
| this.appView = appView; |
| this.factory = appView.dexItemFactory(); |
| } |
| |
| public List<ProgramMethod> getSynthesizedMethods() { |
| return synthesizedMethods; |
| } |
| |
| public void rewriteInstanceInitializers( |
| IRCode code, |
| ProgramMethod context, |
| MethodProcessor methodProcessor, |
| MethodProcessingContext methodProcessingContext) { |
| // Do not outline from already synthesized methods. |
| if (context.getDefinition().isD8R8Synthesized()) { |
| return; |
| } |
| Map<NewInstance, Value> rewrittenNewInstances = new IdentityHashMap<>(); |
| ComputedApiLevel minApiLevel = appView.computedMinApiLevel(); |
| InstructionListIterator iterator = code.instructionListIterator(); |
| // Scan over the code to find <init> calls that needs to be outlined. |
| while (iterator.hasNext()) { |
| Instruction instruction = iterator.next(); |
| InvokeDirect invokeDirect = instruction.asInvokeDirect(); |
| if (invokeDirect == null) { |
| continue; |
| } |
| DexMethod invokedConstructor = invokeDirect.getInvokedMethod(); |
| if (!invokedConstructor.isInstanceInitializer(factory)) { |
| continue; |
| } |
| Value firstOperand = invokeDirect.getFirstOperand(); |
| if (firstOperand.isPhi()) { |
| continue; |
| } |
| NewInstance newInstance = firstOperand.getDefinition().asNewInstance(); |
| if (newInstance == null) { |
| // We could not find a new instance call associated with the init, this is probably a |
| // constructor call to the super class. |
| continue; |
| } |
| ComputedApiLevel apiReferenceLevel = |
| appView |
| .apiLevelCompute() |
| .computeApiLevelForLibraryReference(invokedConstructor, minApiLevel); |
| if (minApiLevel.isGreaterThanOrEqualTo(apiReferenceLevel)) { |
| continue; |
| } |
| // Check if this is already outlined. |
| if (isOutlinedAtSameOrLowerLevel(context.getHolder(), apiReferenceLevel)) { |
| continue; |
| } |
| DexEncodedMethod synthesizedInstanceInitializer = |
| createSynthesizedInstanceInitializer( |
| invokeDirect.getInvokedMethod(), |
| apiReferenceLevel, |
| methodProcessor, |
| methodProcessingContext); |
| List<Value> arguments = instruction.inValues(); |
| InvokeStatic outlinedMethodInvoke = |
| InvokeStatic.builder() |
| .setMethod(synthesizedInstanceInitializer.getReference()) |
| .setPosition(instruction) |
| .setFreshOutValue(code, newInstance.getOutType()) |
| .setArguments(arguments.subList(1, arguments.size())) |
| .build(); |
| iterator.replaceCurrentInstruction(outlinedMethodInvoke); |
| rewrittenNewInstances.put(newInstance, outlinedMethodInvoke.outValue()); |
| } |
| if (rewrittenNewInstances.isEmpty()) { |
| return; |
| } |
| // Scan over NewInstance calls that needs to be outlined. We insert a call to a synthetic method |
| // with a NewInstance to preserve class-init semantics. |
| // TODO(b/244284945): If we know that arguments to an init cannot change class initializer |
| // semantics we can avoid inserting the NewInstance outline. |
| iterator = code.instructionListIterator(); |
| Set<Value> newOutValues = Sets.newIdentityHashSet(); |
| while (iterator.hasNext()) { |
| Instruction instruction = iterator.next(); |
| if (!instruction.isNewInstance()) { |
| continue; |
| } |
| NewInstance newInstance = instruction.asNewInstance(); |
| Value newOutlineInstanceValue = rewrittenNewInstances.get(newInstance); |
| if (newOutlineInstanceValue == null) { |
| continue; |
| } |
| newInstance.outValue().replaceUsers(newOutlineInstanceValue); |
| newOutValues.add(newOutlineInstanceValue); |
| if (canSkipClInit(iterator, newInstance, newOutlineInstanceValue)) { |
| iterator.removeOrReplaceByDebugLocalRead(); |
| continue; |
| } |
| ComputedApiLevel classApiLevel = |
| appView |
| .apiLevelCompute() |
| .computeApiLevelForLibraryReference(newInstance.getType(), minApiLevel); |
| assert classApiLevel.isKnownApiLevel(); |
| DexEncodedMethod synthesizedNewInstance = |
| createSynthesizedNewInstance( |
| newInstance.getType(), classApiLevel, methodProcessor, methodProcessingContext); |
| InvokeStatic outlinedStaticInit = |
| InvokeStatic.builder() |
| .setMethod(synthesizedNewInstance.getReference()) |
| .setPosition(instruction) |
| .build(); |
| iterator.replaceCurrentInstruction(outlinedStaticInit); |
| } |
| // We are changing a NewInstance to a method call where we loose that the type is not null. |
| assert !newOutValues.isEmpty(); |
| new TypeAnalysis(appView).widening(newOutValues); |
| |
| // Outlining of instance initializers will in most cases change the api level of the context |
| // since all other soft verification issues has been outlined. To ensure that we do not inline |
| // the outline again in R8 - but allow inlining of other calls to min api level methods, we have |
| // to recompute the api level. |
| if (appView.enableWholeProgramOptimizations()) { |
| recomputeApiLevel(context, code); |
| } |
| } |
| |
| private boolean canSkipClInit( |
| InstructionListIterator iterator, NewInstance newInstance, Value newInstanceOutValue) { |
| InvokeStatic definition = newInstanceOutValue.getDefinition().asInvokeStatic(); |
| assert definition != null; |
| Position currentPosition = newInstance.getPosition(); |
| // We can skip constant and debug local read instructions when searching for next instruction. |
| Instruction nextInstruction = |
| iterator.nextUntil( |
| instruction -> { |
| if (!instruction.isConstInstruction() && !instruction.isDebugLocalRead()) { |
| return true; |
| } |
| return isChangeInPosition(currentPosition, instruction.getPosition()); |
| }); |
| iterator.previousUntil(instruction -> instruction == newInstance); |
| Instruction newInstanceCopy = iterator.next(); |
| assert newInstanceCopy == newInstance; |
| // Check if there is an instruction between the new-instance and init call that is not constant. |
| return nextInstruction == definition |
| && !isChangeInPosition(currentPosition, nextInstruction.getPosition()); |
| } |
| |
| private boolean isChangeInPosition(Position currentPosition, Position nextPosition) { |
| return !nextPosition.isNone() && currentPosition.getLine() != nextPosition.getLine(); |
| } |
| |
| private void recomputeApiLevel(ProgramMethod context, IRCode code) { |
| DexEncodedMethod definition = context.getDefinition(); |
| if (!definition.getApiLevelForCode().isKnownApiLevel()) { |
| // This is either D8 or the api level is unknown. |
| return; |
| } |
| ComputeApiLevelUseRegistry registry = |
| new ComputeApiLevelUseRegistry(appView, context, appView.apiLevelCompute()); |
| code.registerCodeReferences(context, registry); |
| ComputedApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel(); |
| assert maxApiReferenceLevel.isKnownApiLevel(); |
| definition.setApiLevelForCode(maxApiReferenceLevel); |
| } |
| |
| private DexEncodedMethod createSynthesizedNewInstance( |
| DexType targetType, |
| ComputedApiLevel computedApiLevel, |
| MethodProcessor methodProcessor, |
| MethodProcessingContext methodProcessingContext) { |
| DexProto proto = appView.dexItemFactory().createProto(factory.voidType); |
| ProgramMethod method = |
| appView |
| .getSyntheticItems() |
| .createMethod( |
| kinds -> kinds.API_MODEL_OUTLINE, |
| methodProcessingContext.createUniqueContext(), |
| appView, |
| builder -> |
| builder |
| .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) |
| .setProto(proto) |
| .setApiLevelForDefinition(appView.computedMinApiLevel()) |
| .setApiLevelForCode(computedApiLevel) |
| .setCode( |
| m -> |
| NewInstanceSourceCode.create(appView, m.getHolderType(), targetType) |
| .generateCfCode())); |
| methodProcessor |
| .getEventConsumer() |
| .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext()); |
| synchronized (synthesizedMethods) { |
| synthesizedMethods.add(method); |
| } |
| return method.getDefinition(); |
| } |
| |
| private DexEncodedMethod createSynthesizedInstanceInitializer( |
| DexMethod targetMethod, |
| ComputedApiLevel computedApiLevel, |
| MethodProcessor methodProcessor, |
| MethodProcessingContext methodProcessingContext) { |
| DexProto proto = |
| appView |
| .dexItemFactory() |
| .createProto(targetMethod.getHolderType(), targetMethod.getParameters()); |
| ProgramMethod method = |
| appView |
| .getSyntheticItems() |
| .createMethod( |
| kinds -> kinds.API_MODEL_OUTLINE, |
| methodProcessingContext.createUniqueContext(), |
| appView, |
| builder -> |
| builder |
| .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) |
| .setProto(proto) |
| .setApiLevelForDefinition(appView.computedMinApiLevel()) |
| .setApiLevelForCode(computedApiLevel) |
| .setCode( |
| m -> |
| ForwardMethodBuilder.builder(appView.dexItemFactory()) |
| .setConstructorTargetWithNewInstance(targetMethod) |
| .setStaticSource(m) |
| .build())); |
| methodProcessor |
| .getEventConsumer() |
| .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext()); |
| synchronized (synthesizedMethods) { |
| synthesizedMethods.add(method); |
| ClassTypeElement exactType = |
| targetMethod |
| .getHolderType() |
| .toTypeElement(appView, Nullability.definitelyNotNull()) |
| .asClassType(); |
| OptimizationFeedback.getSimpleFeedback() |
| .setDynamicReturnType(method, appView, DynamicType.createExact(exactType)); |
| } |
| return method.getDefinition(); |
| } |
| } |