blob: 164895898526d7917e7b2e5029c5a3d3974aabd2 [file] [log] [blame]
// Copyright (c) 2018, 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.ir.optimize.lambda;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstMethodHandle;
import com.android.tools.r8.ir.code.ConstMethodType;
import com.android.tools.r8.ir.code.DefaultInstructionVisitor;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger.ApplyStrategy;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ListIterator;
import java.util.function.Function;
// Performs processing of the method code (all methods) needed by lambda rewriter.
//
// Functionality can be modified by strategy (specific to lambda group) and lambda
// type visitor.
//
// In general it is used to
// (a) track the code for illegal lambda type usages inside the code, and
// (b) patching valid lambda type references to point to lambda group classes instead.
//
// This class is also used inside particular strategies as a context of the instruction
// being checked or patched, it provides access to code, block and instruction iterators.
public abstract class CodeProcessor extends DefaultInstructionVisitor<Void> {
// Strategy (specific to lambda group) for detecting valid references to the
// lambda classes (of this group) and patching them with group class references.
public interface Strategy {
LambdaGroup group();
boolean isValidStaticFieldWrite(CodeProcessor context, DexField field);
boolean isValidStaticFieldRead(CodeProcessor context, DexField field);
boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field);
boolean isValidInstanceFieldRead(CodeProcessor context, DexField field);
boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke);
boolean isValidNewInstance(CodeProcessor context, NewInstance invoke);
boolean isValidInitClass(CodeProcessor context, DexType clazz);
void patch(ApplyStrategy context, NewInstance newInstance);
void patch(ApplyStrategy context, InvokeMethod invoke);
void patch(ApplyStrategy context, InstanceGet instanceGet);
void patch(ApplyStrategy context, StaticGet staticGet);
void patch(ApplyStrategy context, InitClass initClass);
}
// No-op strategy.
static final Strategy NoOp =
new Strategy() {
@Override
public LambdaGroup group() {
return null;
}
@Override
public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
return false;
}
@Override
public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) {
return false;
}
@Override
public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) {
return false;
}
@Override
public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) {
return false;
}
@Override
public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) {
return false;
}
@Override
public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
return false;
}
@Override
public boolean isValidInitClass(CodeProcessor context, DexType clazz) {
return false;
}
@Override
public void patch(ApplyStrategy context, NewInstance newInstance) {
throw new Unreachable();
}
@Override
public void patch(ApplyStrategy context, InvokeMethod invoke) {
throw new Unreachable();
}
@Override
public void patch(ApplyStrategy context, InstanceGet instanceGet) {
throw new Unreachable();
}
@Override
public void patch(ApplyStrategy context, StaticGet staticGet) {
throw new Unreachable();
}
@Override
public void patch(ApplyStrategy context, InitClass initClass) {
throw new Unreachable();
}
};
public final AppView<AppInfoWithLiveness> appView;
public final DexItemFactory factory;
public final Kotlin kotlin;
// Defines a factory providing a strategy for a lambda type, returns
// NoOp strategy if the type is not a lambda.
private final Function<DexType, Strategy> strategyProvider;
// Visitor for lambda type references seen in unexpected places. Either
// invalidates the lambda or asserts depending on the processing phase.
private final LambdaTypeVisitor lambdaChecker;
// Specify the context of the current instruction: method/code/blocks/instructions.
public final DexEncodedMethod method;
public final IRCode code;
public final ListIterator<BasicBlock> blocks;
private InstructionListIterator instructions;
// The inlining context (caller), if any.
private final DexEncodedMethod context;
CodeProcessor(
AppView<AppInfoWithLiveness> appView,
Function<DexType, Strategy> strategyProvider,
LambdaTypeVisitor lambdaChecker,
DexEncodedMethod method,
IRCode code) {
this(appView, strategyProvider, lambdaChecker, method, code, null);
}
CodeProcessor(
AppView<AppInfoWithLiveness> appView,
Function<DexType, Strategy> strategyProvider,
LambdaTypeVisitor lambdaChecker,
DexEncodedMethod method,
IRCode code,
DexEncodedMethod context) {
this.appView = appView;
this.strategyProvider = strategyProvider;
this.factory = appView.dexItemFactory();
this.kotlin = factory.kotlin;
this.lambdaChecker = lambdaChecker;
this.method = method;
this.code = code;
this.blocks = code.listIterator();
this.context = context;
}
public final InstructionListIterator instructions() {
assert instructions != null;
return instructions;
}
void processCode() {
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
instructions = block.listIterator(code);
while (instructions.hasNext()) {
instructions.next().accept(this);
}
}
}
private boolean shouldRewrite(DexField field) {
return shouldRewrite(field.holder);
}
private boolean shouldRewrite(DexMethod method) {
return shouldRewrite(method.holder);
}
private boolean shouldRewrite(DexType type) {
// Rewrite references to lambda classes if we are outside the class.
return type != (context != null ? context : method).method.holder;
}
@Override
public Void handleInvoke(Invoke invoke) {
if (invoke.isInvokeNewArray()) {
lambdaChecker.accept(invoke.asInvokeNewArray().getReturnType());
return null;
}
if (invoke.isInvokeMultiNewArray()) {
lambdaChecker.accept(invoke.asInvokeMultiNewArray().getReturnType());
return null;
}
if (invoke.isInvokeCustom()) {
lambdaChecker.accept(invoke.asInvokeCustom().getCallSite());
return null;
}
InvokeMethod invokeMethod = invoke.asInvokeMethod();
Strategy strategy = strategyProvider.apply(invokeMethod.getInvokedMethod().holder);
if (strategy.isValidInvoke(this, invokeMethod)) {
// Invalidate signature, there still should not be lambda references.
lambdaChecker.accept(invokeMethod.getInvokedMethod().proto);
// Only rewrite references to lambda classes if we are outside the class.
if (shouldRewrite(invokeMethod.getInvokedMethod())) {
process(strategy, invokeMethod);
}
return null;
}
// For the rest invalidate any references.
if (invoke.isInvokePolymorphic()) {
lambdaChecker.accept(invoke.asInvokePolymorphic().getProto());
}
lambdaChecker.accept(invokeMethod.getInvokedMethod(), null);
return null;
}
@Override
public Void visit(NewInstance newInstance) {
Strategy strategy = strategyProvider.apply(newInstance.clazz);
if (strategy.isValidNewInstance(this, newInstance)) {
// Only rewrite references to lambda classes if we are outside the class.
if (shouldRewrite(newInstance.clazz)) {
process(strategy, newInstance);
}
}
return null;
}
@Override
public Void visit(CheckCast checkCast) {
lambdaChecker.accept(checkCast.getType());
return null;
}
@Override
public Void visit(NewArrayEmpty newArrayEmpty) {
lambdaChecker.accept(newArrayEmpty.type);
return null;
}
@Override
public Void visit(ConstClass constClass) {
lambdaChecker.accept(constClass.getValue());
return null;
}
@Override
public Void visit(ConstMethodType constMethodType) {
lambdaChecker.accept(constMethodType.getValue());
return null;
}
@Override
public Void visit(ConstMethodHandle constMethodHandle) {
lambdaChecker.accept(constMethodHandle.getValue());
return null;
}
@Override
public Void visit(InstanceGet instanceGet) {
DexField field = instanceGet.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidInstanceFieldRead(this, field)) {
if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, instanceGet);
}
} else {
lambdaChecker.accept(field.type);
}
// We avoid fields with type being lambda class, it is possible for
// a lambda to capture another lambda, but we don't support it for now.
lambdaChecker.accept(field.type);
return null;
}
@Override
public Void visit(InstancePut instancePut) {
DexField field = instancePut.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidInstanceFieldWrite(this, field)) {
if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, instancePut);
}
} else {
lambdaChecker.accept(field.type);
}
// We avoid fields with type being lambda class, it is possible for
// a lambda to capture another lambda, but we don't support it for now.
lambdaChecker.accept(field.type);
return null;
}
@Override
public Void visit(StaticGet staticGet) {
DexField field = staticGet.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidStaticFieldRead(this, field)) {
if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, staticGet);
}
} else {
lambdaChecker.accept(field.type);
lambdaChecker.accept(field.holder);
}
return null;
}
@Override
public Void visit(StaticPut staticPut) {
DexField field = staticPut.getField();
Strategy strategy = strategyProvider.apply(field.holder);
if (strategy.isValidStaticFieldWrite(this, field)) {
if (shouldRewrite(field)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, staticPut);
}
} else {
lambdaChecker.accept(field.type);
lambdaChecker.accept(field.holder);
}
return null;
}
@Override
public Void visit(InitClass initClass) {
DexType clazz = initClass.getClassValue();
Strategy strategy = strategyProvider.apply(clazz);
if (strategy.isValidInitClass(this, clazz)) {
if (shouldRewrite(clazz)) {
// Only rewrite references to lambda classes if we are outside the class.
process(strategy, initClass);
}
} else {
lambdaChecker.accept(clazz);
}
return null;
}
abstract void process(Strategy strategy, InvokeMethod invokeMethod);
abstract void process(Strategy strategy, NewInstance newInstance);
abstract void process(Strategy strategy, InstancePut instancePut);
abstract void process(Strategy strategy, InstanceGet instanceGet);
abstract void process(Strategy strategy, StaticPut staticPut);
abstract void process(Strategy strategy, StaticGet staticGet);
abstract void process(Strategy strategy, InitClass initClass);
}