|  | // Copyright (c) 2020, 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 com.android.tools.r8.graph.DexField; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.ir.code.Invoke.Type; | 
|  | import com.android.tools.r8.ir.code.Position; | 
|  | import com.android.tools.r8.ir.code.Value; | 
|  | import com.android.tools.r8.ir.code.ValueType; | 
|  | import com.android.tools.r8.ir.conversion.IRBuilder; | 
|  | import com.android.tools.r8.ir.synthetic.SyntheticSourceCode; | 
|  | import com.android.tools.r8.utils.IntBox; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  |  | 
|  | /** | 
|  | * Assuming a method signature <code> | 
|  | *   void method([args]); | 
|  | * </code>. This class generates code depending on which of the following cases it matches. | 
|  | * | 
|  | * <p>If the method does not override a method and is implemented by many (e.g. 2) classes: | 
|  | * | 
|  | * <pre> | 
|  | *   void method([args]) { | 
|  | *     switch (classId) { | 
|  | *       case 0: | 
|  | *         return method$1([args]); | 
|  | *       default: | 
|  | *         return method$2([args]); | 
|  | *     } | 
|  | *   } | 
|  | * </pre> | 
|  | * | 
|  | * <p>If the method overrides a method and is implemented by any number of classes: | 
|  | * | 
|  | * <pre> | 
|  | *   void method([args]) { | 
|  | *     switch (classId) { | 
|  | *       case 0: | 
|  | *         return method$1([args]); | 
|  | *       // ... further cases ... | 
|  | *       default: | 
|  | *         return super.method$1([args]); | 
|  | *     } | 
|  | *   } | 
|  | * </pre> | 
|  | */ | 
|  | public class VirtualMethodEntryPoint extends SyntheticSourceCode { | 
|  | private final Int2ReferenceSortedMap<DexMethod> mappedMethods; | 
|  | private final DexField classIdField; | 
|  | private final DexMethod superMethod; | 
|  |  | 
|  | public VirtualMethodEntryPoint( | 
|  | Int2ReferenceSortedMap<DexMethod> mappedMethods, | 
|  | DexField classIdField, | 
|  | DexMethod superMethod, | 
|  | DexMethod newMethod, | 
|  | Position callerPosition, | 
|  | DexMethod originalMethod) { | 
|  | super(newMethod.holder, newMethod, callerPosition, originalMethod); | 
|  |  | 
|  | assert classIdField != null; | 
|  |  | 
|  | this.mappedMethods = mappedMethods; | 
|  | this.classIdField = classIdField; | 
|  | this.superMethod = superMethod; | 
|  | } | 
|  |  | 
|  | void addInvokeDirect(DexMethod method) { | 
|  | add( | 
|  | builder -> { | 
|  | List<Value> arguments = new ArrayList<>(method.getArity() + 1); | 
|  | arguments.add(builder.getReceiverValue()); | 
|  | if (builder.getArgumentValues() != null) { | 
|  | arguments.addAll(builder.getArgumentValues()); | 
|  | } | 
|  | builder.addInvoke(Type.DIRECT, method, method.proto, arguments, false); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void addInvokeSuper() { | 
|  | assert superMethod != null; | 
|  |  | 
|  | add( | 
|  | builder -> { | 
|  | List<Value> arguments = new ArrayList<>(method.getArity() + 1); | 
|  | arguments.add(builder.getReceiverValue()); | 
|  | if (builder.getArgumentValues() != null) { | 
|  | arguments.addAll(builder.getArgumentValues()); | 
|  | } | 
|  | builder.addInvoke(Type.SUPER, superMethod, superMethod.proto, arguments, false); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void handleReturn(int retRegister) { | 
|  | if (proto.returnType.isVoidType()) { | 
|  | add(IRBuilder::addReturn, endsBlock); | 
|  | } else { | 
|  | add(builder -> builder.addMoveResult(retRegister)); | 
|  | add(builder -> builder.addReturn(retRegister), endsBlock); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void prepareInstructions() { | 
|  | int casesCount = mappedMethods.size(); | 
|  |  | 
|  | // If there is no super method, use one of the cases as a fallthrough case. | 
|  | if (superMethod == null) { | 
|  | casesCount--; | 
|  | } | 
|  |  | 
|  | assert casesCount > 0; | 
|  |  | 
|  | // Return value register if needed. | 
|  | int returnRegister = | 
|  | !proto.returnType.isVoidType() ? nextRegister(ValueType.fromDexType(proto.returnType)) : -1; | 
|  |  | 
|  | int[] keys = new int[casesCount]; | 
|  | int[] offsets = new int[casesCount]; | 
|  | IntBox fallthrough = new IntBox(); | 
|  |  | 
|  | // Fetch the class id from the class id field. | 
|  | int idRegister = nextRegister(ValueType.INT); | 
|  | add(builder -> builder.addInstanceGet(idRegister, getReceiverRegister(), classIdField)); | 
|  |  | 
|  | int switchIndex = lastInstructionIndex(); | 
|  | add( | 
|  | builder -> builder.addSwitch(idRegister, keys, fallthrough.get(), offsets), | 
|  | builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets)); | 
|  |  | 
|  | int index = 0; | 
|  | for (Entry<DexMethod> entry : mappedMethods.int2ReferenceEntrySet()) { | 
|  | int classId = entry.getIntKey(); | 
|  | DexMethod mappedMethod = entry.getValue(); | 
|  |  | 
|  | // If there is no super method, then use the last case as the default case. | 
|  | if (index >= casesCount) { | 
|  | fallthrough.set(nextInstructionIndex()); | 
|  | } else { | 
|  | keys[index] = classId; | 
|  | offsets[index] = nextInstructionIndex(); | 
|  | } | 
|  |  | 
|  | addInvokeDirect(mappedMethod); | 
|  | handleReturn(returnRegister); | 
|  |  | 
|  | index++; | 
|  | } | 
|  |  | 
|  | // If the super class implements this method, then the fallthrough case should execute it. | 
|  | if (superMethod != null) { | 
|  | fallthrough.set(nextInstructionIndex()); | 
|  | addInvokeSuper(); | 
|  | handleReturn(returnRegister); | 
|  | } | 
|  | } | 
|  | } |