| // Copyright (c) 2016, 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 static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_ANY; |
| import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_CLASS; |
| import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE; |
| import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS; |
| import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE; |
| |
| import com.android.tools.r8.cf.code.CfConstNull; |
| import com.android.tools.r8.cf.code.CfInstruction; |
| import com.android.tools.r8.cf.code.CfThrow; |
| import com.android.tools.r8.code.Const; |
| import com.android.tools.r8.code.ConstString; |
| import com.android.tools.r8.code.ConstStringJumbo; |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.code.InvokeDirect; |
| import com.android.tools.r8.code.InvokeStatic; |
| import com.android.tools.r8.code.NewInstance; |
| import com.android.tools.r8.code.Throw; |
| import com.android.tools.r8.dex.Constants; |
| import com.android.tools.r8.dex.IndexedItemCollection; |
| import com.android.tools.r8.dex.JumboStringRewriter; |
| import com.android.tools.r8.dex.MixedSectionCollection; |
| import com.android.tools.r8.graph.AppInfo.ResolutionResult; |
| import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Invoke; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.ValueNumberGenerator; |
| import com.android.tools.r8.ir.code.ValueType; |
| import com.android.tools.r8.ir.conversion.DexBuilder; |
| import com.android.tools.r8.ir.optimize.Inliner.Constraint; |
| import com.android.tools.r8.ir.optimize.Inliner.Reason; |
| import com.android.tools.r8.ir.regalloc.RegisterAllocator; |
| import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode; |
| import com.android.tools.r8.ir.synthetic.SynthesizedCode; |
| import com.android.tools.r8.logging.Log; |
| import com.android.tools.r8.naming.ClassNameMapper; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.naming.MemberNaming.Signature; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.InternalOptions; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.Consumer; |
| |
| public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult { |
| |
| /** |
| * Encodes the processing state of a method. |
| * <p> |
| * We also use this enum to encode under what constraints a method may be inlined. |
| */ |
| public enum CompilationState { |
| /** |
| * Has not been processed, yet. |
| */ |
| NOT_PROCESSED, |
| /** |
| * Has been processed but cannot be inlined due to instructions that are not supported. |
| */ |
| PROCESSED_NOT_INLINING_CANDIDATE, |
| /** |
| * Code only contains instructions that access public entities and can this be inlined into any |
| * context. |
| */ |
| PROCESSED_INLINING_CANDIDATE_ANY, |
| /** |
| * Code also contains instructions that access protected entities that reside in a differnt |
| * package and hence require subclass relationship to be visible. |
| */ |
| PROCESSED_INLINING_CANDIDATE_SUBCLASS, |
| /** |
| * Code contains instructions that reference package private entities or protected entities from |
| * the same package. |
| */ |
| PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE, |
| /** |
| * Code contains instructions that reference private entities. |
| */ |
| PROCESSED_INLINING_CANDIDATE_SAME_CLASS, |
| } |
| |
| public static final DexEncodedMethod[] EMPTY_ARRAY = new DexEncodedMethod[]{}; |
| public static final DexEncodedMethod SENTINEL = |
| new DexEncodedMethod(null, null, null, null, null); |
| |
| public final DexMethod method; |
| public final MethodAccessFlags accessFlags; |
| public DexAnnotationSet annotations; |
| public ParameterAnnotationsList parameterAnnotationsList; |
| private Code code; |
| private CompilationState compilationState = CompilationState.NOT_PROCESSED; |
| private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT; |
| private int classFileVersion = -1; |
| |
| public DexEncodedMethod( |
| DexMethod method, |
| MethodAccessFlags accessFlags, |
| DexAnnotationSet annotations, |
| ParameterAnnotationsList parameterAnnotationsList, |
| Code code) { |
| this.method = method; |
| this.accessFlags = accessFlags; |
| this.annotations = annotations; |
| this.parameterAnnotationsList = parameterAnnotationsList; |
| this.code = code; |
| assert code == null || !accessFlags.isAbstract(); |
| } |
| |
| public DexEncodedMethod( |
| DexMethod method, |
| MethodAccessFlags flags, |
| DexAnnotationSet annotationSet, |
| ParameterAnnotationsList annotationsList, |
| Code code, |
| int classFileVersion) { |
| this(method, flags, annotationSet, annotationsList, code); |
| this.classFileVersion = classFileVersion; |
| } |
| |
| public boolean isProcessed() { |
| return compilationState != CompilationState.NOT_PROCESSED; |
| } |
| |
| public boolean isInstanceInitializer() { |
| return accessFlags.isConstructor() && !accessFlags.isStatic(); |
| } |
| |
| public boolean isDefaultInitializer() { |
| return isInstanceInitializer() && method.proto.parameters.isEmpty(); |
| } |
| |
| public boolean isClassInitializer() { |
| return accessFlags.isConstructor() && accessFlags.isStatic(); |
| } |
| |
| /** |
| * Returns true if this method can be invoked via invoke-virtual, invoke-super or |
| * invoke-interface. |
| */ |
| public boolean isVirtualMethod() { |
| return !accessFlags.isStatic() && !accessFlags.isPrivate() && !accessFlags.isConstructor(); |
| } |
| |
| /** |
| * Returns true if this method can be invoked via invoke-virtual, invoke-super or invoke-interface |
| * and is non-abstract. |
| */ |
| public boolean isNonAbstractVirtualMethod() { |
| return isVirtualMethod() && !accessFlags.isAbstract(); |
| } |
| |
| public boolean isPublicized() { |
| return optimizationInfo.isPublicized(); |
| } |
| |
| public boolean isPublicMethod() { |
| return accessFlags.isPublic(); |
| } |
| |
| public boolean isPrivateMethod() { |
| return accessFlags.isPrivate(); |
| } |
| |
| /** |
| * Returns true if this method can be invoked via invoke-direct. |
| */ |
| public boolean isDirectMethod() { |
| return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic(); |
| } |
| |
| /** |
| * Returns true if this method can be invoked via invoke-static. |
| */ |
| public boolean isStaticMethod() { |
| return accessFlags.isStatic(); |
| } |
| |
| |
| /** |
| * Returns true if this method is synthetic. |
| */ |
| public boolean isSyntheticMethod() { |
| return accessFlags.isSynthetic(); |
| } |
| |
| |
| public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason, |
| AppInfoWithSubtyping appInfo) { |
| return isInliningCandidate(container.method.getHolder(), inliningReason, appInfo); |
| } |
| |
| public boolean isInliningCandidate(DexType containerType, Reason inliningReason, |
| AppInfoWithSubtyping appInfo) { |
| if (isClassInitializer()) { |
| // This will probably never happen but never inline a class initializer. |
| return false; |
| } |
| if (inliningReason == Reason.FORCE) { |
| // Make sure we would be able to inline this normally. |
| assert isInliningCandidate(containerType, Reason.SIMPLE, appInfo); |
| return true; |
| } |
| switch (compilationState) { |
| case PROCESSED_INLINING_CANDIDATE_ANY: |
| return true; |
| case PROCESSED_INLINING_CANDIDATE_SUBCLASS: |
| return containerType.isSubtypeOf(method.getHolder(), appInfo); |
| case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE: |
| return containerType.isSamePackage(method.getHolder()); |
| case PROCESSED_INLINING_CANDIDATE_SAME_CLASS: |
| return containerType == method.getHolder(); |
| default: |
| return false; |
| } |
| } |
| |
| public boolean markProcessed(Constraint state) { |
| CompilationState prevCompilationState = compilationState; |
| switch (state) { |
| case ALWAYS: |
| compilationState = PROCESSED_INLINING_CANDIDATE_ANY; |
| break; |
| case SUBCLASS: |
| compilationState = PROCESSED_INLINING_CANDIDATE_SUBCLASS; |
| break; |
| case PACKAGE: |
| compilationState = PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE; |
| break; |
| case SAMECLASS: |
| compilationState = PROCESSED_INLINING_CANDIDATE_SAME_CLASS; |
| break; |
| case NEVER: |
| compilationState = PROCESSED_NOT_INLINING_CANDIDATE; |
| break; |
| } |
| return prevCompilationState != compilationState; |
| } |
| |
| public void markNotProcessed() { |
| compilationState = CompilationState.NOT_PROCESSED; |
| } |
| |
| public IRCode buildIR(AppInfo appInfo, InternalOptions options, Origin origin) { |
| return code == null ? null : code.buildIR(this, appInfo, options, origin); |
| } |
| |
| public IRCode buildInliningIRForTesting( |
| InternalOptions options, ValueNumberGenerator valueNumberGenerator) { |
| return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown()); |
| } |
| |
| public IRCode buildInliningIR( |
| AppInfo appInfo, |
| InternalOptions options, |
| ValueNumberGenerator valueNumberGenerator, |
| Position callerPosition, |
| Origin origin) { |
| return code.buildInliningIR( |
| this, appInfo, options, valueNumberGenerator, callerPosition, origin); |
| } |
| |
| public void setCode(Code code) { |
| this.code = code; |
| } |
| |
| public void setCode( |
| IRCode ir, |
| RegisterAllocator registerAllocator, |
| InternalOptions options) { |
| final DexBuilder builder = new DexBuilder(ir, registerAllocator, options); |
| code = builder.build(method.getArity()); |
| } |
| |
| @Override |
| public String toString() { |
| return "Encoded method " + method; |
| } |
| |
| @Override |
| public void collectIndexedItems(IndexedItemCollection indexedItems, |
| DexMethod method, int instructionOffset) { |
| this.method.collectIndexedItems(indexedItems); |
| if (code != null) { |
| code.collectIndexedItems(indexedItems, this.method); |
| } |
| annotations.collectIndexedItems(indexedItems); |
| parameterAnnotationsList.collectIndexedItems(indexedItems); |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| if (code != null) { |
| code.collectMixedSectionItems(mixedItems); |
| } |
| annotations.collectMixedSectionItems(mixedItems); |
| parameterAnnotationsList.collectMixedSectionItems(mixedItems); |
| } |
| |
| public Code getCode() { |
| return code; |
| } |
| |
| public void setDexCode(DexCode code) { |
| this.code = code; |
| } |
| |
| public void removeCode() { |
| code = null; |
| } |
| |
| public boolean hasDebugPositions() { |
| assert code != null && code.isDexCode(); |
| return code.asDexCode().hasDebugPositions(); |
| } |
| |
| public int getClassFileVersion() { |
| assert classFileVersion >= 0; |
| return classFileVersion; |
| } |
| |
| public boolean hasClassFileVersion() { |
| return classFileVersion >= 0; |
| } |
| |
| public void upgradeClassFileVersion(int version) { |
| assert version >= 0; |
| assert !hasClassFileVersion() || version >= getClassFileVersion(); |
| classFileVersion = version; |
| } |
| |
| public String qualifiedName() { |
| return method.qualifiedName(); |
| } |
| |
| public String descriptor() { |
| return descriptor(NamingLens.getIdentityLens()); |
| } |
| |
| public String descriptor(NamingLens namingLens) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("("); |
| for (DexType type : method.proto.parameters.values) { |
| builder.append(namingLens.lookupDescriptor(type).toString()); |
| } |
| builder.append(")"); |
| builder.append(namingLens.lookupDescriptor(method.proto.returnType).toString()); |
| return builder.toString(); |
| } |
| |
| public String toSmaliString(ClassNameMapper naming) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(".method "); |
| builder.append(accessFlags.toSmaliString()); |
| builder.append(" "); |
| builder.append(method.name.toSmaliString()); |
| builder.append(method.proto.toSmaliString()); |
| builder.append("\n"); |
| if (code != null) { |
| DexCode dexCode = code.asDexCode(); |
| builder.append(" .registers "); |
| builder.append(dexCode.registerSize); |
| builder.append("\n\n"); |
| builder.append(dexCode.toSmaliString(naming)); |
| } |
| builder.append(".end method\n"); |
| return builder.toString(); |
| } |
| |
| @Override |
| public String toSourceString() { |
| return method.toSourceString(); |
| } |
| |
| public DexEncodedMethod toAbstractMethod() { |
| accessFlags.setAbstract(); |
| this.code = null; |
| return this; |
| } |
| |
| /** |
| * Generates a {@link DexCode} object for the given instructions. |
| * <p> |
| * As the code object is produced outside of the normal compilation cycle, it has to use {@link |
| * ConstStringJumbo} to reference string constants. Hence, code produced form these templates |
| * might incur a size overhead. |
| */ |
| private DexCode generateCodeFromTemplate( |
| int numberOfRegisters, int outRegisters, Instruction... instructions) { |
| int offset = 0; |
| for (Instruction instruction : instructions) { |
| assert !(instruction instanceof ConstString); |
| instruction.setOffset(offset); |
| offset += instruction.getSize(); |
| } |
| int requiredArgRegisters = accessFlags.isStatic() ? 0 : 1; |
| for (DexType type : method.proto.parameters.values) { |
| requiredArgRegisters += ValueType.fromDexType(type).requiredRegisters(); |
| } |
| // Passing null as highestSortingString is save, as ConstString instructions are not allowed. |
| return new DexCode(Math.max(numberOfRegisters, requiredArgRegisters), requiredArgRegisters, |
| outRegisters, instructions, new DexCode.Try[0], new DexCode.TryHandler[0], null, null); |
| } |
| |
| public DexEncodedMethod toEmptyThrowingMethodDex() { |
| assert !accessFlags.isAbstract() && !accessFlags.isNative(); |
| Builder builder = builder(this); |
| Instruction insn[] = {new Const(0, 0), new Throw(0)}; |
| DexCode emptyThrowingCode = generateCodeFromTemplate(1, 0, insn); |
| builder.setCode(emptyThrowingCode); |
| return builder.build(); |
| } |
| |
| public DexEncodedMethod toEmptyThrowingMethodCf() { |
| assert !accessFlags.isAbstract() && !accessFlags.isNative(); |
| Builder builder = builder(this); |
| CfInstruction insn[] = {new CfConstNull(), new CfThrow()}; |
| CfCode emptyThrowingCode = |
| new CfCode( |
| method, |
| 1, |
| method.proto.parameters.size() + 1, |
| Arrays.asList(insn), |
| Collections.emptyList(), |
| Collections.emptyList()); |
| builder.setCode(emptyThrowingCode); |
| return builder.build(); |
| } |
| |
| public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) { |
| Signature signature = MethodSignature.fromDexMethod(method); |
| // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors. |
| DexString message = itemFactory.createString( |
| "Shaking error: Missing method in " + method.holder.toSourceString() + ": " |
| + signature); |
| DexString tag = itemFactory.createString("TOIGHTNESS"); |
| DexType[] args = {itemFactory.stringType, itemFactory.stringType}; |
| DexProto proto = itemFactory.createProto(itemFactory.intType, args); |
| DexMethod logMethod = itemFactory |
| .createMethod(itemFactory.createType("Landroid/util/Log;"), proto, |
| itemFactory.createString("e")); |
| DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;"); |
| DexMethod exceptionInitMethod = itemFactory |
| .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType, |
| itemFactory.stringType), |
| itemFactory.constructorMethodName); |
| DexCode code; |
| if (isInstanceInitializer()) { |
| // The Java VM Spec requires that a constructor calls an initializer from the super class |
| // or another constructor from the current class. For simplicity we do the latter by just |
| // calling ourself. This is ok, as the constructor always throws before the recursive call. |
| code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag), |
| new ConstStringJumbo(1, message), |
| new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0), |
| new NewInstance(0, exceptionType), |
| new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0), |
| new Throw(0), |
| new InvokeDirect(1, method, 2, 0, 0, 0, 0)); |
| |
| } else { |
| // These methods might not get registered for jumbo string processing, therefore we always |
| // use the jumbo string encoding for the const string instruction. |
| code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag), |
| new ConstStringJumbo(1, message), |
| new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0), |
| new NewInstance(0, exceptionType), |
| new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0), |
| new Throw(0)); |
| } |
| Builder builder = builder(this); |
| builder.setCode(code); |
| return builder.build(); |
| } |
| |
| public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) { |
| if (this.method == method) { |
| return this; |
| } |
| Builder builder = builder(this); |
| builder.setMethod(method); |
| return builder.build(); |
| } |
| |
| public DexEncodedMethod toRenamedMethod(DexString name, DexItemFactory factory) { |
| if (method.name == name) { |
| return this; |
| } |
| DexMethod newMethod = factory.createMethod(method.holder, method.proto, name); |
| Builder builder = builder(this); |
| builder.setMethod(newMethod); |
| return builder.build(); |
| } |
| |
| public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) { |
| assert accessFlags.isPublic(); |
| // Clear the final flag, as this method is now overwritten. Do this before creating the builder |
| // for the forwarding method, as the forwarding method will copy the access flags from this, |
| // and if different forwarding methods are created in different subclasses the first could be |
| // final. |
| accessFlags.unsetFinal(); |
| DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name); |
| Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER; |
| Builder builder = builder(this); |
| builder.setMethod(newMethod); |
| if (accessFlags.isAbstract()) { |
| // If the forwarding target is abstract, we can just create an abstract method. While it |
| // will not actually forward, it will create the same exception when hit at runtime. |
| builder.accessFlags.setAbstract(); |
| } else { |
| // Create code that forwards the call to the target. |
| builder.setCode(new SynthesizedCode( |
| new ForwardMethodSourceCode( |
| accessFlags.isStatic() ? null : holder.type, |
| method.proto, |
| accessFlags.isStatic() ? null : method.holder, |
| method, |
| type), |
| registry -> { |
| if (accessFlags.isStatic()) { |
| registry.registerInvokeStatic(method); |
| } else { |
| registry.registerInvokeSuper(method); |
| } |
| })); |
| } |
| builder.accessFlags.setSynthetic(); |
| return builder.build(); |
| } |
| |
| /** |
| * Rewrites the code in this method to have JumboString bytecode if required by mapping. |
| * <p> |
| * Synchronized such that it can be called concurrently for different mappings. As a side-effect, |
| * this will also update the highestSortingString to the index of the strings up to which the code |
| * was rewritten to avoid rewriting again unless needed. |
| */ |
| public synchronized void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping, |
| DexApplication application, boolean force) { |
| assert code == null || code.isDexCode(); |
| if (code == null) { |
| return; |
| } |
| DexCode code = this.code.asDexCode(); |
| DexString firstJumboString = null; |
| if (force) { |
| firstJumboString = mapping.getFirstString(); |
| } else if (code.highestSortingString != null |
| && mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) { |
| firstJumboString = mapping.getFirstJumboString(); |
| } |
| if (firstJumboString != null) { |
| JumboStringRewriter rewriter = |
| new JumboStringRewriter(this, firstJumboString, application.dexItemFactory); |
| rewriter.rewrite(); |
| } |
| } |
| |
| public String codeToString() { |
| return code == null ? "<no code>" : code.toString(this, null); |
| } |
| |
| @Override |
| public DexMethod getKey() { |
| return method; |
| } |
| |
| public boolean hasAnnotation() { |
| return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty(); |
| } |
| |
| public void registerCodeReferences(UseRegistry registry) { |
| if (code != null) { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method); |
| } |
| code.registerCodeReferences(registry); |
| } |
| } |
| |
| public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) { |
| return m1.method.slowCompareTo(m2.method); |
| } |
| |
| public static class ClassInlinerEligibility { |
| public final boolean returnsReceiver; |
| |
| public ClassInlinerEligibility(boolean returnsReceiver) { |
| this.returnsReceiver = returnsReceiver; |
| } |
| } |
| |
| public static class TrivialInitializer { |
| private TrivialInitializer() { |
| } |
| |
| // Defines instance trivial initialized, see details in comments |
| // to CodeRewriter::computeInstanceInitializerInfo(...) |
| public static final class TrivialInstanceInitializer extends TrivialInitializer { |
| public static final TrivialInstanceInitializer INSTANCE = |
| new TrivialInstanceInitializer(); |
| } |
| |
| // Defines class trivial initialized, see details in comments |
| // to CodeRewriter::computeClassInitializerInfo(...) |
| public static final class TrivialClassInitializer extends TrivialInitializer { |
| public final DexField field; |
| |
| public TrivialClassInitializer(DexField field) { |
| this.field = field; |
| } |
| } |
| } |
| |
| public static class OptimizationInfo { |
| |
| private int returnedArgument = -1; |
| private boolean neverReturnsNull = false; |
| private boolean neverReturnsNormally = false; |
| private boolean returnsConstant = false; |
| private long returnedConstant = 0; |
| private boolean publicized = false; |
| private boolean forceInline = false; |
| private boolean useIdentifierNameString = false; |
| private boolean checksNullReceiverBeforeAnySideEffect = false; |
| private boolean triggersClassInitBeforeAnySideEffect = false; |
| // Stores information about instance methods and constructors for |
| // class inliner, null value indicates that the method is not eligible. |
| private ClassInlinerEligibility classInlinerEligibility = null; |
| private TrivialInitializer trivialInitializerInfo = null; |
| private ParameterUsagesInfo parametersUsages = null; |
| private BitSet kotlinNotNullParamHints = null; |
| |
| private OptimizationInfo() { |
| // Intentionally left empty. |
| } |
| |
| private OptimizationInfo(OptimizationInfo template) { |
| returnedArgument = template.returnedArgument; |
| neverReturnsNull = template.neverReturnsNull; |
| returnsConstant = template.returnsConstant; |
| returnedConstant = template.returnedConstant; |
| publicized = template.publicized; |
| forceInline = template.forceInline; |
| useIdentifierNameString = template.useIdentifierNameString; |
| checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect; |
| } |
| |
| public void setParameterUsages(ParameterUsagesInfo parametersUsages) { |
| this.parametersUsages = parametersUsages; |
| } |
| |
| public ParameterUsage getParameterUsages(int parameter) { |
| return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter); |
| } |
| |
| public BitSet getKotlinNotNullParamHints() { |
| return kotlinNotNullParamHints; |
| } |
| |
| public void setKotlinNotNullParamHints(BitSet hints) { |
| this.kotlinNotNullParamHints = hints; |
| } |
| |
| public boolean returnsArgument() { |
| return returnedArgument != -1; |
| } |
| |
| public int getReturnedArgument() { |
| assert returnsArgument(); |
| return returnedArgument; |
| } |
| |
| public boolean neverReturnsNull() { |
| return neverReturnsNull; |
| } |
| |
| public boolean neverReturnsNormally() { |
| return neverReturnsNormally; |
| } |
| |
| public boolean returnsConstant() { |
| return returnsConstant; |
| } |
| |
| private void setClassInlinerEligibility(ClassInlinerEligibility eligibility) { |
| this.classInlinerEligibility = eligibility; |
| } |
| |
| public ClassInlinerEligibility getClassInlinerEligibility() { |
| return this.classInlinerEligibility; |
| } |
| |
| private void setTrivialInitializer(TrivialInitializer info) { |
| this.trivialInitializerInfo = info; |
| } |
| |
| public TrivialInitializer getTrivialInitializerInfo() { |
| return this.trivialInitializerInfo; |
| } |
| |
| public long getReturnedConstant() { |
| assert returnsConstant(); |
| return returnedConstant; |
| } |
| |
| public boolean isPublicized() { |
| return publicized; |
| } |
| |
| public boolean forceInline() { |
| return forceInline; |
| } |
| |
| public boolean useIdentifierNameString() { |
| return useIdentifierNameString; |
| } |
| |
| public boolean checksNullReceiverBeforeAnySideEffect() { |
| return checksNullReceiverBeforeAnySideEffect; |
| } |
| |
| public boolean triggersClassInitBeforeAnySideEffect() { |
| return triggersClassInitBeforeAnySideEffect; |
| } |
| |
| private void markReturnsArgument(int argument) { |
| assert argument >= 0; |
| assert returnedArgument == -1 || returnedArgument == argument; |
| returnedArgument = argument; |
| } |
| |
| private void markNeverReturnsNull() { |
| neverReturnsNull = true; |
| } |
| |
| private void markNeverReturnsNormally() { |
| neverReturnsNormally = true; |
| } |
| |
| private void markReturnsConstant(long value) { |
| assert !returnsConstant || returnedConstant == value; |
| returnsConstant = true; |
| returnedConstant = value; |
| } |
| |
| private void markForceInline() { |
| forceInline = true; |
| } |
| |
| private void markPublicized() { |
| publicized = true; |
| } |
| |
| private void unsetPublicized() { |
| publicized = false; |
| } |
| |
| private void markUseIdentifierNameString() { |
| useIdentifierNameString = true; |
| } |
| |
| public OptimizationInfo copy() { |
| return new OptimizationInfo(this); |
| } |
| |
| private void markCheckNullReceiverBeforeAnySideEffect(boolean mark) { |
| checksNullReceiverBeforeAnySideEffect = mark; |
| } |
| |
| private void markTriggerClassInitBeforeAnySideEffect(boolean mark) { |
| triggersClassInitBeforeAnySideEffect = mark; |
| } |
| } |
| |
| private static class DefaultOptimizationInfo extends OptimizationInfo { |
| |
| static final OptimizationInfo DEFAULT = new DefaultOptimizationInfo(); |
| |
| private DefaultOptimizationInfo() { |
| } |
| |
| @Override |
| public OptimizationInfo copy() { |
| return this; |
| } |
| } |
| |
| synchronized private OptimizationInfo ensureMutableOI() { |
| if (optimizationInfo == DefaultOptimizationInfo.DEFAULT) { |
| optimizationInfo = new OptimizationInfo(); |
| } |
| return optimizationInfo; |
| } |
| |
| synchronized public void markReturnsArgument(int argument) { |
| ensureMutableOI().markReturnsArgument(argument); |
| } |
| |
| synchronized public void markNeverReturnsNull() { |
| ensureMutableOI().markNeverReturnsNull(); |
| } |
| |
| synchronized public void markNeverReturnsNormally() { |
| ensureMutableOI().markNeverReturnsNormally(); |
| } |
| |
| synchronized public void markReturnsConstant(long value) { |
| ensureMutableOI().markReturnsConstant(value); |
| } |
| |
| synchronized public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) { |
| ensureMutableOI().setClassInlinerEligibility(eligibility); |
| } |
| |
| synchronized public void setParameterUsages(ParameterUsagesInfo parametersUsages) { |
| ensureMutableOI().setParameterUsages(parametersUsages); |
| } |
| |
| synchronized public void setKotlinNotNullParamHints(BitSet hints) { |
| ensureMutableOI().setKotlinNotNullParamHints(hints); |
| } |
| |
| synchronized public void setTrivialInitializer(TrivialInitializer info) { |
| ensureMutableOI().setTrivialInitializer(info); |
| } |
| |
| synchronized public void markForceInline() { |
| ensureMutableOI().markForceInline(); |
| } |
| |
| synchronized public void markPublicized() { |
| ensureMutableOI().markPublicized(); |
| } |
| |
| synchronized public void unsetPublicized() { |
| ensureMutableOI().unsetPublicized(); |
| } |
| |
| synchronized public void markUseIdentifierNameString() { |
| ensureMutableOI().markUseIdentifierNameString(); |
| } |
| |
| synchronized public void markCheckNullReceiverBeforeAnySideEffect(boolean mark) { |
| ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect(mark); |
| } |
| |
| synchronized public void markTriggerClassInitBeforeAnySideEffect(boolean mark) { |
| ensureMutableOI().markTriggerClassInitBeforeAnySideEffect(mark); |
| } |
| |
| public OptimizationInfo getOptimizationInfo() { |
| return optimizationInfo; |
| } |
| |
| public void copyMetadataFromInlinee(DexEncodedMethod inlinee) { |
| // Record that the current method uses identifier name string if the inlinee did so. |
| if (inlinee.getOptimizationInfo().useIdentifierNameString()) { |
| markUseIdentifierNameString(); |
| } |
| if (inlinee.classFileVersion > classFileVersion) { |
| upgradeClassFileVersion(inlinee.getClassFileVersion()); |
| } |
| } |
| |
| private static Builder builder(DexEncodedMethod from) { |
| return new Builder(from); |
| } |
| |
| private static class Builder { |
| |
| private DexMethod method; |
| private final MethodAccessFlags accessFlags; |
| private final DexAnnotationSet annotations; |
| private final ParameterAnnotationsList parameterAnnotations; |
| private Code code; |
| private CompilationState compilationState = CompilationState.NOT_PROCESSED; |
| private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT; |
| private final int classFileVersion; |
| |
| private Builder(DexEncodedMethod from) { |
| // Copy all the mutable state of a DexEncodedMethod here. |
| method = from.method; |
| accessFlags = from.accessFlags.copy(); |
| annotations = from.annotations; |
| parameterAnnotations = from.parameterAnnotationsList; |
| code = from.code; |
| compilationState = from.compilationState; |
| optimizationInfo = from.optimizationInfo.copy(); |
| classFileVersion = from.classFileVersion; |
| } |
| |
| public void setMethod(DexMethod method) { |
| this.method = method; |
| } |
| |
| public void setCode(Code code) { |
| this.code = code; |
| } |
| |
| public DexEncodedMethod build() { |
| assert method != null; |
| assert accessFlags != null; |
| assert annotations != null; |
| assert parameterAnnotations != null; |
| DexEncodedMethod result = |
| new DexEncodedMethod( |
| method, accessFlags, annotations, parameterAnnotations, code, classFileVersion); |
| result.compilationState = compilationState; |
| result.optimizationInfo = optimizationInfo; |
| return result; |
| } |
| } |
| |
| @Override |
| public DexEncodedMethod asResultOfResolve() { |
| return this; |
| } |
| |
| @Override |
| public DexEncodedMethod asSingleTarget() { |
| return this; |
| } |
| |
| @Override |
| public boolean hasSingleTarget() { |
| return true; |
| } |
| |
| @Override |
| public List<DexEncodedMethod> asListOfTargets() { |
| return Collections.singletonList(this); |
| } |
| |
| @Override |
| public void forEachTarget(Consumer<DexEncodedMethod> consumer) { |
| consumer.accept(this); |
| } |
| |
| } |