| // Copyright (c) 2021, 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.graph; |
| |
| import com.android.tools.r8.cf.CfVersion; |
| import com.android.tools.r8.cf.code.CfInstruction; |
| import com.android.tools.r8.cf.code.CfInvoke; |
| import com.android.tools.r8.cf.code.CfLoad; |
| import com.android.tools.r8.cf.code.CfReturnVoid; |
| import com.android.tools.r8.dex.CodeToKeep; |
| import com.android.tools.r8.dex.IndexedItemCollection; |
| import com.android.tools.r8.dex.MixedSectionCollection; |
| import com.android.tools.r8.dex.code.DexInvokeDirect; |
| import com.android.tools.r8.dex.code.DexReturnVoid; |
| import com.android.tools.r8.graph.DexCode.Try; |
| import com.android.tools.r8.graph.DexCode.TryHandler; |
| import com.android.tools.r8.graph.GraphLens.MethodLookupResult; |
| import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.NumberGenerator; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.Position.SyntheticPosition; |
| import com.android.tools.r8.ir.code.ValueType; |
| import com.android.tools.r8.ir.conversion.IRBuilder; |
| import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; |
| import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; |
| import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions; |
| import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.IteratorUtils; |
| import com.android.tools.r8.utils.RetracerForCodePrinting; |
| import com.android.tools.r8.utils.structural.HashingVisitor; |
| import com.google.common.collect.ImmutableList; |
| import java.nio.ShortBuffer; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| /** |
| * A piece of code on the form: |
| * |
| * <pre> |
| * aload_0 |
| * invoke-special LSuperClass;-><init>()V |
| * return |
| * </pre> |
| * |
| * <p>Note that (i) {@code SuperClass} may be different from {@link java.lang.Object} and (ii) the |
| * method holding this code object may have a non-empty proto. |
| */ |
| public class DefaultInstanceInitializerCode extends Code |
| implements CfWritableCode, DexWritableCode { |
| |
| private static final DefaultInstanceInitializerCode INSTANCE = |
| new DefaultInstanceInitializerCode(); |
| |
| private DefaultInstanceInitializerCode() {} |
| |
| public static DefaultInstanceInitializerCode get() { |
| return INSTANCE; |
| } |
| |
| public static boolean canonicalizeCodeIfPossible(AppView<?> appView, ProgramMethod method) { |
| if (hasDefaultInstanceInitializerCode(method, appView)) { |
| method.setCode(get(), appView); |
| return true; |
| } |
| return false; |
| } |
| |
| public static void uncanonicalizeCode(AppView<?> appView, ProgramMethod method) { |
| uncanonicalizeCode(appView, method, method.getHolder().getSuperType()); |
| } |
| |
| public static void uncanonicalizeCode( |
| AppView<?> appView, ProgramMethod method, DexType superType) { |
| DexEncodedMethod definition = method.getDefinition(); |
| assert definition.getCode().isDefaultInstanceInitializerCode(); |
| method.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView); |
| } |
| |
| private static boolean hasDefaultInstanceInitializerCode( |
| ProgramMethod method, AppView<?> appView) { |
| if (!method.getDefinition().isInstanceInitializer()) { |
| return false; |
| } |
| Code code = method.getDefinition().getCode(); |
| if (!code.isCfCode()) { |
| return false; |
| } |
| CfCode cfCode = code.asCfCode(); |
| if (!method.getDefinition().isInstanceInitializer() |
| || !cfCode.getLocalVariables().isEmpty() |
| || !cfCode.getTryCatchRanges().isEmpty()) { |
| return false; |
| } |
| if (cfCode.getInstructions().size() > 6) { |
| // Default instance initializers typically have the following instruction sequence: |
| // [CfLabel, CfPosition, CfLoad, CfInvoke, CfReturnVoid, CfLabel]. |
| return false; |
| } |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| Iterator<CfInstruction> instructionIterator = cfCode.getInstructions().iterator(); |
| // Allow skipping CfPosition instructions in instance initializers that only call Object.<init>. |
| Predicate<CfInstruction> instructionOfInterest = |
| method.getHolder().getSuperType() == dexItemFactory.objectType |
| ? instruction -> !instruction.isLabel() && !instruction.isPosition() |
| : instruction -> !instruction.isLabel(); |
| CfLoad load = IteratorUtils.nextUntil(instructionIterator, instructionOfInterest).asLoad(); |
| if (load == null || load.getLocalIndex() != 0) { |
| return false; |
| } |
| CfInvoke invoke = instructionIterator.next().asInvoke(); |
| if (invoke == null |
| || !invoke.isInvokeConstructor(dexItemFactory) |
| || invoke.getMethod() != getParentConstructor(method, dexItemFactory)) { |
| return false; |
| } |
| return instructionIterator.next().isReturnVoid(); |
| } |
| |
| @Override |
| public Code asCode() { |
| return this; |
| } |
| |
| @Override |
| public void acceptHashing(HashingVisitor visitor) { |
| visitor.visitInt(getCfWritableCodeKind().hashCode()); |
| } |
| |
| @Override |
| public IRCode buildIR( |
| ProgramMethod method, |
| AppView<?> appView, |
| Origin origin, |
| MutableMethodConversionOptions conversionOptions) { |
| DexMethod originalMethod = |
| appView.graphLens().getOriginalMethodSignature(method.getReference()); |
| DefaultInstanceInitializerSourceCode source = |
| new DefaultInstanceInitializerSourceCode(originalMethod); |
| return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions); |
| } |
| |
| @Override |
| public IRCode buildInliningIR( |
| ProgramMethod context, |
| ProgramMethod method, |
| AppView<?> appView, |
| GraphLens codeLens, |
| NumberGenerator valueNumberGenerator, |
| Position callerPosition, |
| Origin origin, |
| RewrittenPrototypeDescription protoChanges) { |
| DexMethod originalMethod = |
| appView.graphLens().getOriginalMethodSignature(method.getReference()); |
| DefaultInstanceInitializerSourceCode source = |
| new DefaultInstanceInitializerSourceCode(originalMethod, callerPosition); |
| return IRBuilder.createForInlining( |
| method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges) |
| .build(context, new ThrowingMethodConversionOptions(appView.options())); |
| } |
| |
| @Override |
| public int codeSizeInBytes() { |
| return DexInvokeDirect.SIZE + DexReturnVoid.SIZE; |
| } |
| |
| @Override |
| public void collectIndexedItems( |
| AppView<?> appView, |
| IndexedItemCollection indexedItems, |
| ProgramMethod context, |
| LensCodeRewriterUtils rewriter) { |
| DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory()); |
| MethodLookupResult lookupResult = |
| appView.graphLens().lookupInvokeDirect(parentConstructor, context); |
| lookupResult.getReference().collectIndexedItems(appView, indexedItems); |
| } |
| |
| @Override |
| public void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| protected int computeHashCode() { |
| return System.identityHashCode(this); |
| } |
| |
| @Override |
| protected boolean computeEquals(Object other) { |
| return this == other; |
| } |
| |
| @Override |
| public int estimatedDexCodeSizeUpperBoundInBytes() { |
| return codeSizeInBytes(); |
| } |
| |
| @Override |
| public CfWritableCodeKind getCfWritableCodeKind() { |
| return CfWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER; |
| } |
| |
| @Override |
| public DexWritableCodeKind getDexWritableCodeKind() { |
| return DexWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER; |
| } |
| |
| @Override |
| public DexDebugInfoForWriting getDebugInfoForWriting() { |
| return null; |
| } |
| |
| @Override |
| public TryHandler[] getHandlers() { |
| return new TryHandler[0]; |
| } |
| |
| @Override |
| public DexString getHighestSortingString() { |
| return null; |
| } |
| |
| @Override |
| public int getIncomingRegisterSize(ProgramMethod method) { |
| return getMaxLocals(method); |
| } |
| |
| static DexMethod getParentConstructor(DexClassAndMethod method, DexItemFactory dexItemFactory) { |
| return dexItemFactory.createInstanceInitializer(method.getHolder().getSuperType()); |
| } |
| |
| private int getMaxLocals(ProgramMethod method) { |
| int maxLocals = method.getAccessFlags().isStatic() ? 0 : 1; |
| for (DexType parameter : method.getParameters()) { |
| maxLocals += parameter.getRequiredRegisters(); |
| } |
| return maxLocals; |
| } |
| |
| private int getMaxStack() { |
| return 1; |
| } |
| |
| @Override |
| public int getOutgoingRegisterSize() { |
| return 1; |
| } |
| |
| @Override |
| public int getRegisterSize(ProgramMethod method) { |
| return getIncomingRegisterSize(method); |
| } |
| |
| @Override |
| public Try[] getTries() { |
| return new Try[0]; |
| } |
| |
| @Override |
| public boolean isCfWritableCode() { |
| return true; |
| } |
| |
| @Override |
| public CfWritableCode asCfWritableCode() { |
| return this; |
| } |
| |
| @Override |
| public boolean isDexWritableCode() { |
| return true; |
| } |
| |
| @Override |
| public DexWritableCode asDexWritableCode() { |
| return this; |
| } |
| |
| @Override |
| public boolean isEmptyVoidMethod() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDefaultInstanceInitializerCode() { |
| return true; |
| } |
| |
| @Override |
| public DefaultInstanceInitializerCode asDefaultInstanceInitializerCode() { |
| return this; |
| } |
| |
| @Override |
| public boolean isSharedCodeObject() { |
| return true; |
| } |
| |
| @Override |
| public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { |
| internalRegisterCodeReferences(method, registry); |
| } |
| |
| @Override |
| public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { |
| internalRegisterCodeReferences(method, registry); |
| } |
| |
| private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry<?> registry) { |
| registry.registerInvokeDirect(getParentConstructor(method, registry.dexItemFactory())); |
| } |
| |
| @Override |
| public DexWritableCode rewriteCodeWithJumboStrings( |
| ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) { |
| // Intentionally empty. This piece of code does not have any const-string instructions. |
| return this; |
| } |
| |
| @Override |
| public void setCallSiteContexts(ProgramMethod method) { |
| // Intentionally empty. This piece of code does not have any call sites. |
| } |
| |
| public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory) { |
| return toCfCode(method, dexItemFactory, method.getHolder().getSuperType()); |
| } |
| |
| public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory, DexType supertype) { |
| List<CfInstruction> instructions = |
| Arrays.asList( |
| new CfLoad(ValueType.OBJECT, 0), |
| new CfInvoke( |
| Opcodes.INVOKESPECIAL, dexItemFactory.createInstanceInitializer(supertype), false), |
| new CfReturnVoid()); |
| return new CfCode(method.getHolderType(), getMaxStack(), getMaxLocals(method), instructions); |
| } |
| |
| @Override |
| public void writeCf( |
| ProgramMethod method, |
| CfVersion classFileVersion, |
| AppView<?> appView, |
| NamingLens namingLens, |
| LensCodeRewriterUtils rewriter, |
| MethodVisitor visitor) { |
| visitor.visitVarInsn(Opcodes.ALOAD, 0); |
| visitor.visitMethodInsn( |
| Opcodes.INVOKESPECIAL, |
| namingLens.lookupInternalName(method.getHolder().getSuperType()), |
| "<init>", |
| "()V", |
| false); |
| visitor.visitInsn(Opcodes.RETURN); |
| visitor.visitEnd(); |
| visitor.visitMaxs(getMaxStack(), getMaxLocals(method)); |
| } |
| |
| @Override |
| public void writeDex( |
| ShortBuffer shortBuffer, |
| ProgramMethod context, |
| GraphLens graphLens, |
| LensCodeRewriterUtils lensCodeRewriter, |
| ObjectToOffsetMapping mapping) { |
| DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory()); |
| MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context); |
| new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0) |
| .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter); |
| new DexReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter); |
| } |
| |
| @Override |
| public void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public String toString() { |
| return "DefaultInstanceInitializerCode"; |
| } |
| |
| @Override |
| public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) { |
| return toString(); |
| } |
| |
| static class DefaultInstanceInitializerSourceCode extends SyntheticStraightLineSourceCode { |
| |
| DefaultInstanceInitializerSourceCode(DexMethod method) { |
| this(method, null); |
| } |
| |
| DefaultInstanceInitializerSourceCode(DexMethod method, Position callerPosition) { |
| super( |
| getInstructionBuilders(), |
| SyntheticPosition.builder() |
| .setLine(0) |
| .setMethod(method) |
| .setCallerPosition(callerPosition) |
| .build()); |
| } |
| |
| private static List<Consumer<IRBuilder>> getInstructionBuilders() { |
| return ImmutableList.of( |
| builder -> |
| builder.add( |
| com.android.tools.r8.ir.code.InvokeDirect.builder() |
| .setMethod( |
| getParentConstructor( |
| builder.getProgramMethod(), builder.dexItemFactory())) |
| .setSingleArgument(builder.getReceiverValue()) |
| .build()), |
| IRBuilder::addReturn); |
| } |
| } |
| } |