| // Copyright (c) 2024, 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.horizontalclassmerging; |
| |
| 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.utils.MapUtils.ignoreKey; |
| |
| import com.android.tools.r8.cf.CfVersion; |
| import com.android.tools.r8.classmerging.ClassMergerSharedData; |
| 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.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| 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.NewInstance; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.conversion.IRToLirFinalizer; |
| import com.android.tools.r8.lightir.ByteArrayWriter; |
| import com.android.tools.r8.lightir.ByteUtils; |
| import com.android.tools.r8.lightir.LirBuilder; |
| import com.android.tools.r8.lightir.LirCode; |
| import com.android.tools.r8.lightir.LirConstant; |
| import com.android.tools.r8.lightir.LirEncodingStrategy; |
| import com.android.tools.r8.lightir.LirInstructionView; |
| import com.android.tools.r8.lightir.LirOpcodes; |
| import com.android.tools.r8.lightir.LirStrategy; |
| import com.android.tools.r8.lightir.LirWriter; |
| import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph; |
| import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions; |
| import com.android.tools.r8.utils.ArrayUtils; |
| import com.android.tools.r8.utils.ObjectUtils; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.Timing; |
| import com.android.tools.r8.utils.WorkList; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; |
| import it.unimi.dsi.fastutil.objects.Reference2IntMap; |
| import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| public class UndoConstructorInlining { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final ClassMergerSharedData classMergerSharedData; |
| private final ImmediateProgramSubtypingInfo immediateSubtypingInfo; |
| |
| public UndoConstructorInlining( |
| AppView<?> appView, |
| ClassMergerSharedData classMergerSharedData, |
| ImmediateProgramSubtypingInfo immediateSubtypingInfo) { |
| this.appView = appView.enableWholeProgramOptimizations() ? appView.withClassHierarchy() : null; |
| this.classMergerSharedData = classMergerSharedData; |
| this.immediateSubtypingInfo = immediateSubtypingInfo; |
| } |
| |
| public void runIfNecessary( |
| Collection<HorizontalMergeGroup> groups, |
| ExecutorService executorService, |
| Timing timing) |
| throws ExecutionException { |
| if (shouldRun()) { |
| timing.begin("Undo constructor inlining"); |
| run(groups, executorService); |
| timing.end(); |
| } |
| } |
| |
| private boolean shouldRun() { |
| // Only run when constructor inlining is enabled. |
| return appView != null && appView.options().canInitNewInstanceUsingSuperclassConstructor(); |
| } |
| |
| private void run(Collection<HorizontalMergeGroup> groups, ExecutorService executorService) |
| throws ExecutionException { |
| // Find all classes in horizontal non-interface merge groups that have a class id. All |
| // instantiations of these classes *must* use a constructor on the class itself, since the |
| // constructor will be responsible for assigning the class id field. This property may not hold |
| // as a result of constructor inlining, so we need to restore it. |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses = |
| groups.stream() |
| .filter(group -> group.isClassGroup() && group.hasClassIdField()) |
| .flatMap(HorizontalMergeGroup::stream) |
| .collect(Collectors.toMap(DexClass::getType, Function.identity())); |
| if (ensureConstructorsOnClasses.isEmpty()) { |
| return; |
| } |
| |
| // Extend the mapping to include subclasses. |
| ensureConstructorsOnSubclasses(ensureConstructorsOnClasses); |
| |
| // Create a mapping from program classes to their strongly connected program component. When we |
| // need to synthesize a constructor on a class C we lock on the strongly connected component of |
| // C to ensure thread safety. |
| Map<DexProgramClass, StronglyConnectedComponent> stronglyConnectedComponents = |
| computeStronglyConnectedComponents(); |
| new LirRewriter(appView, ensureConstructorsOnClasses, stronglyConnectedComponents) |
| .run(executorService); |
| appView.dexItemFactory().clearTypeElementsCache(); |
| } |
| |
| private void ensureConstructorsOnSubclasses( |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses) { |
| // Perform a top-down traversal from each merge class and record that instantiations of |
| // subclasses of the merge class must not skip any constructors on the merge class. |
| Map<DexType, DexProgramClass> ensureConstructorsOnSubclasses = new IdentityHashMap<>(); |
| for (DexProgramClass mergeClass : ensureConstructorsOnClasses.values()) { |
| WorkList.newIdentityWorkList(immediateSubtypingInfo.getSubclasses(mergeClass)) |
| .process( |
| (current, worklist) -> { |
| if (ensureConstructorsOnClasses.containsKey(current.getType())) { |
| return; |
| } |
| ensureConstructorsOnSubclasses.put(current.getType(), mergeClass); |
| worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current)); |
| }); |
| } |
| ensureConstructorsOnClasses.putAll(ensureConstructorsOnSubclasses); |
| } |
| |
| private Map<DexProgramClass, StronglyConnectedComponent> computeStronglyConnectedComponents() { |
| List<Set<DexProgramClass>> stronglyConnectedComponents = |
| new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo) |
| .computeStronglyConnectedComponents(); |
| Map<DexProgramClass, StronglyConnectedComponent> stronglyConnectedComponentMap = |
| new IdentityHashMap<>(); |
| for (Set<DexProgramClass> classes : stronglyConnectedComponents) { |
| StronglyConnectedComponent stronglyConnectedComponent = new StronglyConnectedComponent(); |
| for (DexProgramClass clazz : classes) { |
| stronglyConnectedComponentMap.put(clazz, stronglyConnectedComponent); |
| } |
| } |
| return stronglyConnectedComponentMap; |
| } |
| |
| private static class LirRewriter { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final Map<DexType, DexProgramClass> ensureConstructorsOnClasses; |
| private final ProfileCollectionAdditions profileCollectionAdditions; |
| private final Map<DexProgramClass, StronglyConnectedComponent> stronglyConnectedComponents; |
| |
| LirRewriter( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses, |
| Map<DexProgramClass, StronglyConnectedComponent> stronglyConnectedComponents) { |
| this.appView = appView; |
| this.ensureConstructorsOnClasses = ensureConstructorsOnClasses; |
| this.profileCollectionAdditions = ProfileCollectionAdditions.create(appView); |
| this.stronglyConnectedComponents = stronglyConnectedComponents; |
| } |
| |
| public void run(ExecutorService executorService) throws ExecutionException { |
| ThreadUtils.processItems( |
| appView.appInfo().classes(), |
| this::processClass, |
| appView.options().getThreadingModule(), |
| executorService); |
| profileCollectionAdditions.commit(appView); |
| } |
| |
| private void processClass(DexProgramClass clazz) { |
| clazz.forEachProgramMethodMatching( |
| method -> filterMethod(clazz, method), this::processMethod); |
| } |
| |
| private boolean filterMethod(DexProgramClass clazz, DexEncodedMethod method) { |
| return method.hasCode() |
| && method.getCode().isLirCode() |
| && mayInstantiateClassOfInterest( |
| clazz, method, method.getCode().asLirCode(), ensureConstructorsOnClasses); |
| } |
| |
| private void processMethod(ProgramMethod method) { |
| LirCode<Integer> code = method.getDefinition().getCode().asLirCode(); |
| LirCode<Integer> rewritten = rewriteLir(method, code); |
| if (ObjectUtils.notIdentical(code, rewritten)) { |
| method.setCode(rewritten, appView); |
| } |
| } |
| |
| private boolean mayInstantiateClassOfInterest( |
| DexProgramClass clazz, |
| DexEncodedMethod method, |
| LirCode<Integer> code, |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses) { |
| // Treat constructors as allocations of the super class. |
| if (method.isInstanceInitializer() |
| && ensureConstructorsOnClasses.containsKey(clazz.getSuperType())) { |
| return true; |
| } |
| return ArrayUtils.any( |
| code.getConstantPool(), |
| constant -> |
| constant instanceof DexType && ensureConstructorsOnClasses.containsKey(constant)); |
| } |
| |
| private LirCode<Integer> rewriteLir(ProgramMethod method, LirCode<Integer> code) { |
| // Create a mapping from new-instance value index -> new-instance type (limited to the types |
| // of interest). |
| Int2ReferenceMap<DexType> allocationsOfInterest = getAllocationsOfInterest(method, code); |
| if (allocationsOfInterest.isEmpty()) { |
| return code; |
| } |
| ByteArrayWriter byteWriter = new ByteArrayWriter(); |
| LirWriter lirWriter = new LirWriter(byteWriter); |
| List<LirConstant> methodsToAppend = new ArrayList<>(); |
| Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>(); |
| for (LirInstructionView view : code) { |
| InvokeDirectInfo info = |
| getAllocationOfInterest(method, code, lirWriter, view, allocationsOfInterest); |
| if (info == null) { |
| continue; |
| } |
| ProgramMethod newInvokedMethod = |
| getStronglyConnectedComponent(info.getProgramClass()) |
| .getOrCreateConstructor( |
| info.getProgramClass(), |
| info.getInvokedMethod(), |
| ensureConstructorsOnClasses, |
| newConstructor -> |
| profileCollectionAdditions.addMethodIfContextIsInProfile( |
| newConstructor, method)); |
| if (newInvokedMethod.getArity() != info.getInvokedMethod().getArity()) { |
| assert newInvokedMethod.getArity() > info.getInvokedMethod().getArity(); |
| return rewriteIR(method, code); |
| } |
| int constantIndex = |
| methodIndices.computeIfAbsent( |
| newInvokedMethod.getReference(), |
| ref -> { |
| methodsToAppend.add(ref); |
| return code.getConstantPool().length + methodsToAppend.size() - 1; |
| }); |
| int constantIndexSize = ByteUtils.intEncodingSize(constantIndex); |
| int firstValueSize = ByteUtils.intEncodingSize(info.getFirstValue()); |
| int remainingSize = view.getRemainingOperandSizeInBytes(); |
| lirWriter.writeInstruction( |
| LirOpcodes.INVOKEDIRECT, constantIndexSize + firstValueSize + remainingSize); |
| ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand); |
| ByteUtils.writeEncodedInt(info.getFirstValue(), lirWriter::writeOperand); |
| while (remainingSize-- > 0) { |
| lirWriter.writeOperand(view.getNextU1()); |
| } |
| } |
| return methodsToAppend.isEmpty() |
| ? code |
| : code.copyWithNewConstantsAndInstructions( |
| ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend), |
| byteWriter.toByteArray()); |
| } |
| |
| private LirCode<Integer> rewriteIR(ProgramMethod method, LirCode<Integer> code) { |
| IRCode irCode = code.buildIR(method, appView); |
| InstructionListIterator instructionIterator = irCode.instructionListIterator(); |
| InvokeDirect invoke; |
| while ((invoke = instructionIterator.nextUntil(Instruction::isInvokeDirect)) != null) { |
| DexType newType; |
| if (invoke |
| .getReceiver() |
| .getAliasedValue() |
| .isDefinedByInstructionSatisfying(Instruction::isNewInstance)) { |
| NewInstance newInstance = |
| invoke.getReceiver().getAliasedValue().getDefinition().asNewInstance(); |
| newType = newInstance.getType(); |
| } else if (invoke.getReceiver().isThis() |
| && method.getDefinition().isInstanceInitializer()) { |
| newType = method.getHolder().getSuperType(); |
| } else { |
| continue; |
| } |
| DexMethod invokedMethod = invoke.getInvokedMethod(); |
| if (ensureConstructorsOnClasses.containsKey(newType) |
| && isConstructorInlined(newType, invokedMethod) |
| && !isForwardingConstructorCall(method, invokedMethod, invoke.getReceiver().isThis()) |
| && isSkippingRequiredConstructor(newType, invokedMethod)) { |
| DexProgramClass noSkipClass = ensureConstructorsOnClasses.get(newType); |
| ProgramMethod newInvokedMethod = |
| getStronglyConnectedComponent(noSkipClass) |
| .getOrCreateConstructor( |
| noSkipClass, |
| invokedMethod, |
| ensureConstructorsOnClasses, |
| newConstructor -> |
| profileCollectionAdditions.addMethodIfContextIsInProfile( |
| newConstructor, method)); |
| InvokeDirect.Builder invokeDirectBuilder = |
| InvokeDirect.builder() |
| .setArguments(invoke.arguments()) |
| .setMethod(newInvokedMethod.getReference()); |
| if (newInvokedMethod.getArity() > invokedMethod.getArity()) { |
| instructionIterator.previous(); |
| Value zeroValue = |
| instructionIterator.insertConstIntInstruction(irCode, appView.options(), 0); |
| List<Value> newArguments = |
| new ArrayList<>(newInvokedMethod.getDefinition().getNumberOfArguments()); |
| newArguments.addAll(invoke.arguments()); |
| while (newArguments.size() < newInvokedMethod.getDefinition().getNumberOfArguments()) { |
| newArguments.add(zeroValue); |
| } |
| Instruction next = instructionIterator.next(); |
| assert next == invoke; |
| invokeDirectBuilder.setArguments(newArguments); |
| } |
| instructionIterator.replaceCurrentInstruction(invokeDirectBuilder.build()); |
| } |
| } |
| return new IRToLirFinalizer(appView) |
| .finalizeCode(irCode, BytecodeMetadataProvider.empty(), Timing.empty()); |
| } |
| |
| private Int2ReferenceMap<DexType> getAllocationsOfInterest( |
| ProgramMethod method, LirCode<Integer> code) { |
| Int2ReferenceMap<DexType> allocationsOfInterest = new Int2ReferenceOpenHashMap<>(); |
| if (method.getDefinition().isInstanceInitializer()) { |
| if (ensureConstructorsOnClasses.containsKey(method.getHolder().getSuperType())) { |
| allocationsOfInterest.put(0, method.getHolder().getSuperType()); |
| } |
| } |
| for (LirInstructionView view : code) { |
| if (view.getOpcode() == LirOpcodes.NEW) { |
| DexType type = (DexType) code.getConstantItem(view.getNextConstantOperand()); |
| if (ensureConstructorsOnClasses.containsKey(type)) { |
| allocationsOfInterest.put(view.getValueIndex(code), type); |
| } |
| } |
| } |
| return allocationsOfInterest; |
| } |
| |
| /** |
| * Returns non-null for constructor calls that need to be rewritten to a constructor call on the |
| * new-instance type. When this returns null, the current instruction is written to the {@param |
| * lirWriter}. |
| */ |
| // TODO(b/225838009): Look into making it easier to "peek" data in the LirInstructionView to |
| // avoid needing to keep track of how many operands have been consumed. |
| private InvokeDirectInfo getAllocationOfInterest( |
| ProgramMethod method, |
| LirCode<Integer> code, |
| LirWriter lirWriter, |
| LirInstructionView view, |
| Int2ReferenceMap<DexType> allocationsOfInterest) { |
| int opcode = view.getOpcode(); |
| if (LirOpcodes.isOneByteInstruction(opcode)) { |
| lirWriter.writeOneByteInstruction(opcode); |
| return null; |
| } |
| int operandSizeInBytes = view.getRemainingOperandSizeInBytes(); |
| int numReadOperands = 0; |
| int constantIndex = -1; |
| int firstValue = -1; |
| if (opcode == LirOpcodes.INVOKEDIRECT) { |
| constantIndex = view.getNextConstantOperand(); |
| numReadOperands++; |
| DexMethod invokedMethod = (DexMethod) code.getConstantItem(constantIndex); |
| if (invokedMethod.isInstanceInitializer(appView.dexItemFactory())) { |
| firstValue = view.getNextValueOperand(); |
| numReadOperands++; |
| int receiver = code.decodeValueIndex(firstValue, view.getValueIndex(code)); |
| DexType newType = allocationsOfInterest.get(receiver); |
| if (newType != null |
| && isConstructorInlined(newType, invokedMethod) |
| && !isForwardingConstructorCall(method, invokedMethod, receiver == 0) |
| && isSkippingRequiredConstructor(newType, invokedMethod)) { |
| return new InvokeDirectInfo( |
| invokedMethod, firstValue, ensureConstructorsOnClasses.get(newType)); |
| } |
| } |
| } |
| lirWriter.writeInstruction(opcode, operandSizeInBytes); |
| assert numReadOperands <= 2; |
| if (numReadOperands > 0) { |
| ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand); |
| if (numReadOperands == 2) { |
| ByteUtils.writeEncodedInt(firstValue, lirWriter::writeOperand); |
| } |
| } |
| int size = view.getRemainingOperandSizeInBytes(); |
| while (size-- > 0) { |
| lirWriter.writeOperand(view.getNextU1()); |
| } |
| return null; |
| } |
| |
| private boolean isConstructorInlined(DexType newType, DexMethod invokedMethod) { |
| return newType.isNotIdenticalTo(invokedMethod.getHolderType()); |
| } |
| |
| private boolean isForwardingConstructorCall( |
| ProgramMethod method, DexMethod invokedMethod, boolean isThisReceiver) { |
| assert invokedMethod.isInstanceInitializer(appView.dexItemFactory()); |
| return method.getDefinition().isInstanceInitializer() |
| && invokedMethod.getHolderType().isIdenticalTo(method.getHolderType()) |
| && isThisReceiver; |
| } |
| |
| private boolean isSkippingRequiredConstructor(DexType newType, DexMethod invokedMethod) { |
| DexProgramClass requiredConstructorClass = ensureConstructorsOnClasses.get(newType); |
| assert requiredConstructorClass != null; |
| return !appView |
| .appInfo() |
| .isSubtype(invokedMethod.getHolderType(), requiredConstructorClass.getType()); |
| } |
| |
| private StronglyConnectedComponent getStronglyConnectedComponent(DexProgramClass clazz) { |
| return stronglyConnectedComponents.get(clazz); |
| } |
| } |
| |
| private static class InvokeDirectInfo { |
| |
| private final DexMethod invokedMethod; |
| private final int firstValue; |
| private final DexProgramClass programClass; |
| |
| InvokeDirectInfo(DexMethod invokedMethod, int firstValue, DexProgramClass newType) { |
| this.invokedMethod = invokedMethod; |
| this.firstValue = firstValue; |
| this.programClass = newType; |
| } |
| |
| DexMethod getInvokedMethod() { |
| return invokedMethod; |
| } |
| |
| public int getFirstValue() { |
| return firstValue; |
| } |
| |
| public DexProgramClass getProgramClass() { |
| return programClass; |
| } |
| } |
| |
| private class StronglyConnectedComponent { |
| |
| private final Map<DexProgramClass, Map<DexMethod, ProgramMethod>> constructorCache = |
| new IdentityHashMap<>(); |
| |
| // Get or create a constructor on the given class that calls target, which is a constructor on |
| // a parent class. Note that the returned constructor may not actually call the given target |
| // constructor directly, as constructors may also need to be synthesized between the current |
| // class and the target holder. |
| // |
| // Synchronized to ensure thread safety. |
| public synchronized ProgramMethod getOrCreateConstructor( |
| DexProgramClass clazz, |
| DexMethod target, |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses, |
| Consumer<ProgramMethod> creationConsumer) { |
| return constructorCache |
| .computeIfAbsent(clazz, ignoreKey(IdentityHashMap::new)) |
| .computeIfAbsent( |
| target, |
| k -> createConstructor(clazz, target, ensureConstructorsOnClasses, creationConsumer)); |
| } |
| |
| private ProgramMethod createConstructor( |
| DexProgramClass clazz, |
| DexMethod target, |
| Map<DexType, DexProgramClass> ensureConstructorsOnClasses, |
| Consumer<ProgramMethod> creationConsumer) { |
| // Create a fresh constructor on the given class that calls target. If there is a class in the |
| // hierarchy inbetween `clazz` and `target.holder`, which is also subject to class merging, |
| // then we must create a constructor that calls a constructor on that intermediate class, |
| // which then calls target. |
| DexType currentType = clazz.getSuperType(); |
| while (currentType.isNotIdenticalTo(target.getHolderType())) { |
| DexProgramClass currentClass = asProgramClassOrNull(appView.definitionFor(currentType)); |
| if (currentClass == null) { |
| break; |
| } |
| if (ensureConstructorsOnClasses.containsKey(currentType)) { |
| target = |
| getOrCreateConstructor( |
| currentClass, target, ensureConstructorsOnClasses, creationConsumer) |
| .getReference(); |
| break; |
| } |
| currentType = currentClass.getSuperType(); |
| } |
| |
| // Create a constructor that calls target. |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| DexMethod candidateMethodReference = target.withHolder(clazz, dexItemFactory); |
| DexMethod methodReference = |
| dexItemFactory.createInstanceInitializerWithFreshProto( |
| candidateMethodReference, |
| classMergerSharedData.getExtraUnusedArgumentTypes(), |
| test -> clazz.lookupDirectMethod(test) == null); |
| DexEncodedMethod method = |
| DexEncodedMethod.syntheticBuilder() |
| .setMethod(methodReference) |
| .setAccessFlags( |
| MethodAccessFlags.builder().setConstructor().setPublic().setSynthetic().build()) |
| .setCode(createConstructorCode(methodReference, target)) |
| .setClassFileVersion(CfVersion.V1_6) |
| // TODO(b/325199754): Compute api level here? |
| .setApiLevelForCode( |
| appView.apiLevelCompute().computeInitialMinApiLevel(appView.options())) |
| .setApiLevelForDefinition( |
| appView.apiLevelCompute().computeInitialMinApiLevel(appView.options())) |
| .build(); |
| clazz.addDirectMethod(method); |
| ProgramMethod programMethod = method.asProgramMethod(clazz); |
| creationConsumer.accept(programMethod); |
| return programMethod; |
| } |
| |
| private LirCode<Integer> createConstructorCode(DexMethod methodReference, DexMethod target) { |
| LirEncodingStrategy<Value, Integer> strategy = |
| LirStrategy.getDefaultStrategy().getEncodingStrategy(); |
| LirBuilder<Value, Integer> lirBuilder = |
| LirCode.builder(methodReference, true, strategy, appView.options()); |
| |
| int instructionIndex = 0; |
| List<Value> argumentValues = new ArrayList<>(); |
| |
| // Add receiver argument. |
| DexType receiverType = methodReference.getHolderType(); |
| TypeElement receiverTypeElement = receiverType.toTypeElement(appView, definitelyNotNull()); |
| Value receiverValue = Value.createNoDebugLocal(instructionIndex, receiverTypeElement); |
| argumentValues.add(receiverValue); |
| strategy.defineValue(receiverValue, receiverValue.getNumber()); |
| lirBuilder.addArgument(receiverValue.getNumber(), false); |
| instructionIndex++; |
| |
| // Add non-receiver arguments. |
| for (; |
| instructionIndex < target.getNumberOfArgumentsForNonStaticMethod(); |
| instructionIndex++) { |
| DexType argumentType = target.getArgumentTypeForNonStaticMethod(instructionIndex); |
| TypeElement argumentTypeElement = argumentType.toTypeElement(appView); |
| Value argumentValue = Value.createNoDebugLocal(instructionIndex, argumentTypeElement); |
| argumentValues.add(argumentValue); |
| strategy.defineValue(argumentValue, argumentValue.getNumber()); |
| lirBuilder.addArgument(argumentValue.getNumber(), argumentType.isBooleanType()); |
| } |
| |
| // Add remaining unused non-receiver arguments. |
| for (; |
| instructionIndex < methodReference.getNumberOfArgumentsForNonStaticMethod(); |
| instructionIndex++) { |
| DexType argumentType = methodReference.getArgumentTypeForNonStaticMethod(instructionIndex); |
| lirBuilder.addArgument(instructionIndex, argumentType.isBooleanType()); |
| } |
| |
| // Invoke parent constructor. |
| lirBuilder.addInvokeDirect(target, argumentValues, false); |
| instructionIndex++; |
| |
| // Return. |
| lirBuilder.addReturnVoid(); |
| instructionIndex++; |
| |
| return lirBuilder.build(); |
| } |
| } |
| } |