blob: 188440dfb6054ccfea83f9ec2004895c2c38a710 [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_PACKAGE_PRIVATE;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PRIVATE;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PUBLIC;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
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.InvokeStatic;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
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;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
public enum CompilationState
{
NOT_PROCESSED,
PROCESSED_NOT_INLINING_CANDIDATE,
// Code only contains instructions that access public entities.
PROCESSED_INLINING_CANDIDATE_PUBLIC,
// Code only contains instructions that access public and package private entities.
PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE,
// Code also contains instructions that access public entities.
PROCESSED_INLINING_CANDIDATE_PRIVATE,
}
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 DexAccessFlags 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, DexAccessFlags 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 isInliningCandidate(DexEncodedMethod container, boolean alwaysInline) {
if (container.accessFlags.isStatic() && container.accessFlags.isConstructor()) {
// This will probably never happen but never inline a class initializer.
return false;
}
if (alwaysInline) {
// Only inline constructor iff holder classes are equal.
if (!accessFlags.isStatic() && accessFlags.isConstructor()) {
return container.method.getHolder() == method.getHolder();
}
return true;
}
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_PUBLIC:
return true;
case PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE:
return container.method.getHolder().isSamePackage(method.getHolder());
// TODO(bak): Expand check for package private access:
case PROCESSED_INLINING_CANDIDATE_PRIVATE:
return container.method.getHolder() == method.getHolder();
default:
return false;
}
}
public boolean isPublicInlining() {
return compilationState == PROCESSED_INLINING_CANDIDATE_PUBLIC;
}
public void markProcessed(InliningConstraint state) {
switch (state) {
case ALWAYS:
compilationState = PROCESSED_INLINING_CANDIDATE_PUBLIC;
break;
case PACKAGE:
compilationState = PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE;
break;
case PRIVATE:
compilationState = PROCESSED_INLINING_CANDIDATE_PRIVATE;
break;
case NEVER:
compilationState = PROCESSED_NOT_INLINING_CANDIDATE;
break;
}
}
public void markNotProcessed() {
compilationState = CompilationState.NOT_PROCESSED;
}
public IRCode buildIR(InternalOptions options) {
return code == null ? null : code.buildIR(this, options);
}
public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) {
return code == null
? null
: code.asDexCode().buildIR(this, valueNumberGenerator, options);
}
public void setCode(
IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory) {
final DexBuilder builder = new DexBuilder(ir, registerAllocator, dexItemFactory);
code = builder.build(method.proto.parameters.values.length);
}
// Replaces the dex code in the method by setting code to result of compiling the IR.
public void setCode(IRCode ir, RegisterAllocator registerAllocator,
DexItemFactory dexItemFactory, DexString firstJumboString) {
final DexBuilder builder =
new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
code = builder.build(method.proto.parameters.values.length);
}
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 removeCode() {
code = null;
}
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 += MoveType.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;");
DexType[] exceptionArgs = {exceptionType, itemFactory.stringType};
DexMethod initMethod = itemFactory
.createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType, exceptionArgs),
itemFactory.constructorMethodName);
// These methods might not get registered for jumbo string processing, therefore we always
// use the jumbo string encoding for the const string instruction.
Instruction insn[] = {
new ConstStringJumbo(0, tag),
new ConstStringJumbo(1, message),
new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
new NewInstance(0, exceptionType),
new InvokeStatic(2, initMethod, 0, 1, 0, 0, 0),
new Throw(0)
};
DexCode code = generateCodeFromTemplate(2, 2, insn);
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();
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();
accessFlags.unsetFinal();
return builder.build();
}
public String codeToString() {
return code == null ? "<no code>" : code.toString();
}
@Override
public DexMethod getKey() {
return method;
}
public boolean hasAnnotation() {
return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
}
public void registerReachableDefinitions(UseRegistry registry) {
if (code != null) {
if (Log.ENABLED) {
Log.verbose((Class) getClass(), "Registering definitions reachable from `%s`.", method);
}
code.registerReachableDefinitions(registry);
}
}
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 OptimizationInfo() {
// Intentionally left empty.
}
private OptimizationInfo(OptimizationInfo template) {
returnedArgument = template.returnedArgument;
neverReturnsNull = template.neverReturnsNull;
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
forceInline = template.forceInline;
}
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;
}
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;
}
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();
}
public OptimizationInfo getOptimizationInfo() {
return optimizationInfo;
}
private static Builder builder(DexEncodedMethod from) {
return new Builder(from);
}
private static class Builder {
private DexMethod method;
private DexAccessFlags accessFlags;
private DexAnnotationSet annotations;
private 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 = new DexAccessFlags(from.accessFlags.get());
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 setAccessFlags(DexAccessFlags accessFlags) {
this.accessFlags = accessFlags;
}
public void setAnnotations(DexAnnotationSet annotations) {
this.annotations = annotations;
}
public void setParameterAnnotations(DexAnnotationSetRefList parameterAnnotations) {
this.parameterAnnotations = parameterAnnotations;
}
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;
}
}
}