blob: fafd904b796f81f4ad7690eb6e48b0af30aa8198 [file] [log] [blame]
// 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.ApiLevelException;
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.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.utils.InternalOptions;
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 DexAnnotationSetRefList parameterAnnotations;
private Code code;
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
public DexEncodedMethod(
DexMethod method,
MethodAccessFlags accessFlags,
DexAnnotationSet annotations,
DexAnnotationSetRefList parameterAnnotations,
Code code) {
this.method = method;
this.accessFlags = accessFlags;
this.annotations = annotations;
this.parameterAnnotations = parameterAnnotations;
this.code = code;
assert code == null || !accessFlags.isAbstract();
}
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 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) {
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(container, Reason.SIMPLE, appInfo);
return true;
}
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_ANY:
return true;
case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
return container.method.getHolder().isSubtypeOf(method.getHolder(), appInfo);
case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
return container.method.getHolder().isSamePackage(method.getHolder());
case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
return container.method.getHolder() == 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) throws ApiLevelException {
return code == null ? null : code.buildIR(this, appInfo, options);
}
public IRCode buildInliningIRForTesting(
InternalOptions options, ValueNumberGenerator valueNumberGenerator)
throws ApiLevelException {
return buildInliningIR(null, options, valueNumberGenerator, null);
}
public IRCode buildInliningIR(
AppInfo appInfo,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition)
throws ApiLevelException {
return code.buildInliningIR(
this, appInfo, options, valueNumberGenerator, callerPosition);
}
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) {
method.collectIndexedItems(indexedItems);
if (code != null) {
code.collectIndexedItems(indexedItems);
}
annotations.collectIndexedItems(indexedItems);
parameterAnnotations.collectIndexedItems(indexedItems);
}
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
if (code != null) {
code.collectMixedSectionItems(mixedItems);
}
annotations.collectMixedSectionItems(mixedItems);
parameterAnnotations.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 String qualifiedName() {
return method.qualifiedName();
}
public String descriptor() {
StringBuilder builder = new StringBuilder();
builder.append("(");
for (DexType type : method.proto.parameters.values) {
builder.append(type.descriptor.toString());
}
builder.append(")");
builder.append(method.proto.returnType.descriptor.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 toEmptyThrowingMethod() {
Instruction insn[] = {new Const(0, 0), new Throw(0)};
DexCode code = generateCodeFromTemplate(1, 0, insn);
assert !accessFlags.isAbstract();
Builder builder = builder(this);
builder.setCode(code);
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) {
assert code == null || code.isDexCode();
if (code == null) {
return;
}
DexCode code = this.code.asDexCode();
if (code.highestSortingString != null) {
if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
JumboStringRewriter rewriter =
new JumboStringRewriter(this, mapping.getFirstJumboString(),
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() || !parameterAnnotations.isEmpty();
}
public void registerInstructionsReferences(UseRegistry registry) {
if (code != null) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
}
code.registerInstructionsReferences(registry);
}
}
public void registerCatchedTypes(Consumer<DexType> dexTypeConsumer) {
if (code != null) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Visiting catched types `%s`.", method);
}
code.registerCaughtTypes(dexTypeConsumer);
}
}
public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) {
return m1.method.slowCompareTo(m2.method);
}
public static class OptimizationInfo {
private int returnedArgument = -1;
private boolean neverReturnsNull = false;
private boolean returnsConstant = false;
private long returnedConstant = 0;
private boolean forceInline = false;
private boolean useIdentifierNameString = false;
private OptimizationInfo() {
// Intentionally left empty.
}
private OptimizationInfo(OptimizationInfo template) {
returnedArgument = template.returnedArgument;
neverReturnsNull = template.neverReturnsNull;
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
forceInline = template.forceInline;
useIdentifierNameString = template.useIdentifierNameString;
}
public boolean returnsArgument() {
return returnedArgument != -1;
}
public int getReturnedArgument() {
assert returnsArgument();
return returnedArgument;
}
public boolean neverReturnsNull() {
return neverReturnsNull;
}
public boolean returnsConstant() {
return returnsConstant;
}
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
}
public boolean forceInline() {
return forceInline;
}
public boolean useIdentifierNameString() {
return useIdentifierNameString;
}
private void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
private void markNeverReturnsNull() {
neverReturnsNull = true;
}
private void markReturnsConstant(long value) {
assert !returnsConstant || returnedConstant == value;
returnsConstant = true;
returnedConstant = value;
}
private void markForceInline() {
forceInline = true;
}
private void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
public OptimizationInfo copy() {
return new OptimizationInfo(this);
}
}
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 markReturnsConstant(long value) {
ensureMutableOI().markReturnsConstant(value);
}
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
synchronized public void markUseIdentifierNameString() {
ensureMutableOI().markUseIdentifierNameString();
}
public OptimizationInfo getOptimizationInfo() {
return optimizationInfo;
}
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 DexAnnotationSetRefList parameterAnnotations;
private Code code;
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
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.parameterAnnotations;
code = from.code;
compilationState = from.compilationState;
optimizationInfo = from.optimizationInfo.copy();
}
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);
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);
}
}