blob: 44bd9d41b4e7cb44fe5a5c8632cf7b2ba876a644 [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.ir.code;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class Value {
/**
* Immutable view of the debug info associated with an SSA value.
*
* Used during IR building and to construct replacement values.
*/
public static class DebugInfo {
private final DebugLocalInfo local;
private final Value previousLocalValue;
public DebugInfo(DebugLocalInfo local, Value previousLocalValue) {
assert local != null;
this.local = local;
this.previousLocalValue = previousLocalValue;
}
}
// Actual internal data for the debug information of locals.
// This is wrapped in a class to avoid multiple pointers in the value structure.
private static class DebugData {
final DebugLocalInfo local;
Value previousLocalValue;
Set<Instruction> debugUsers = new HashSet<>();
List<Instruction> localStarts = new ArrayList<>();
List<Instruction> localEnds = new ArrayList<>();
DebugData(DebugInfo info) {
this(info.local, info.previousLocalValue);
}
DebugData(DebugLocalInfo local, Value previousLocalValue) {
assert previousLocalValue == null || !previousLocalValue.isUninitializedLocal();
this.local = local;
this.previousLocalValue = previousLocalValue;
}
}
public static final Value UNDEFINED = new Value(-1, MoveType.OBJECT, null);
protected final int number;
protected final MoveType type;
public Instruction definition = null;
private LinkedList<Instruction> users = new LinkedList<>();
private Set<Instruction> uniqueUsers = null;
private LinkedList<Phi> phiUsers = new LinkedList<>();
private Set<Phi> uniquePhiUsers = null;
private Value nextConsecutive = null;
private Value previousConsecutive = null;
private LiveIntervals liveIntervals;
private int needsRegister = -1;
private boolean neverNull = false;
private boolean isThis = false;
private boolean isArgument = false;
private LongInterval valueRange;
private final DebugData debugData;
public Value(int number, MoveType type, DebugInfo debugInfo) {
this.number = number;
this.type = type;
this.debugData = debugInfo == null ? null : new DebugData(debugInfo);
}
public boolean isFixedRegisterValue() {
return false;
}
public FixedRegisterValue asFixedRegisterValue() {
return null;
}
public int getNumber() {
return number;
}
public int requiredRegisters() {
return type.requiredRegisters();
}
public DebugInfo getDebugInfo() {
return debugData == null ? null : new DebugInfo(debugData.local, debugData.previousLocalValue);
}
public DebugLocalInfo getLocalInfo() {
return debugData == null ? null : debugData.local;
}
public Value getPreviousLocalValue() {
return debugData == null ? null : debugData.previousLocalValue;
}
public void replacePreviousLocalValue(Value value) {
if (value == null || value.isUninitializedLocal()) {
debugData.previousLocalValue = null;
} else {
debugData.previousLocalValue = value;
}
}
public List<Instruction> getDebugLocalStarts() {
return debugData.localStarts;
}
public List<Instruction> getDebugLocalEnds() {
return debugData.localEnds;
}
public void addDebugLocalStart(Instruction start) {
assert start != null;
debugData.localStarts.add(start);
}
public void addDebugLocalEnd(Instruction end) {
assert end != null;
debugData.localEnds.add(end);
}
public void linkTo(Value other) {
assert nextConsecutive == null || nextConsecutive == other;
assert other.previousConsecutive == null || other.previousConsecutive == this;
other.previousConsecutive = this;
nextConsecutive = other;
}
public void replaceLink(Value newArgument) {
assert isLinked();
if (previousConsecutive != null) {
previousConsecutive.nextConsecutive = newArgument;
newArgument.previousConsecutive = previousConsecutive;
previousConsecutive = null;
}
if (nextConsecutive != null) {
nextConsecutive.previousConsecutive = newArgument;
newArgument.nextConsecutive = nextConsecutive;
nextConsecutive = null;
}
}
public boolean isLinked() {
return nextConsecutive != null || previousConsecutive != null;
}
public Value getStartOfConsecutive() {
Value current = this;
while (current.getPreviousConsecutive() != null) {
current = current.getPreviousConsecutive();
}
return current;
}
public Value getNextConsecutive() {
return nextConsecutive;
}
public Value getPreviousConsecutive() {
return previousConsecutive;
}
public Set<Instruction> uniqueUsers() {
if (uniqueUsers != null) {
return uniqueUsers;
}
return uniqueUsers = ImmutableSet.copyOf(users);
}
public Set<Phi> uniquePhiUsers() {
if (uniquePhiUsers != null) {
return uniquePhiUsers;
}
return uniquePhiUsers = ImmutableSet.copyOf(phiUsers);
}
public Set<Instruction> debugUsers() {
if (debugData == null) {
return null;
}
return Collections.unmodifiableSet(debugData.debugUsers);
}
public int numberOfUsers() {
int size = users.size();
if (size <= 1) {
return size;
}
return uniqueUsers().size();
}
public int numberOfPhiUsers() {
int size = phiUsers.size();
if (size <= 1) {
return size;
}
return uniquePhiUsers().size();
}
public int numberOfDebugUsers() {
return debugData == null ? 0 : debugData.debugUsers.size();
}
public int numberOfAllUsers() {
return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
}
public boolean isUsed() {
return !users.isEmpty()
|| !phiUsers.isEmpty()
|| ((debugData != null) && !debugData.debugUsers.isEmpty());
}
public boolean usedInMonitorOperation() {
for (Instruction instruction : uniqueUsers()) {
if (instruction.isMonitor()) {
return true;
}
}
return false;
}
public void addUser(Instruction user) {
users.add(user);
uniqueUsers = null;
}
public void removeUser(Instruction user) {
users.remove(user);
uniqueUsers = null;
}
public void clearUsers() {
users.clear();
uniqueUsers = null;
phiUsers.clear();
uniquePhiUsers = null;
if (debugData != null) {
debugData.debugUsers.clear();
}
}
public void addPhiUser(Phi user) {
phiUsers.add(user);
uniquePhiUsers = null;
}
public void removePhiUser(Phi user) {
phiUsers.remove(user);
uniquePhiUsers = null;
}
public void addDebugUser(Instruction user) {
if (isUninitializedLocal()) {
return;
}
debugData.debugUsers.add(user);
}
public boolean isUninitializedLocal() {
return definition != null && definition.isDebugLocalUninitialized();
}
public boolean isInitializedLocal() {
return !isUninitializedLocal();
}
public void removeDebugUser(Instruction user) {
debugData.debugUsers.remove(user);
}
public boolean hasUsersInfo() {
return users != null;
}
public void clearUsersInfo() {
users = null;
uniqueUsers = null;
phiUsers = null;
uniquePhiUsers = null;
if (debugData != null) {
debugData.debugUsers = null;
}
}
public void replaceUsers(Value newValue) {
if (this == newValue) {
return;
}
for (Instruction user : uniqueUsers()) {
user.inValues.replaceAll(v -> {
if (v == this) {
newValue.addUser(user);
return newValue;
}
return v;
});
}
for (Phi user : uniquePhiUsers()) {
user.getOperands().replaceAll(v -> {
if (v == this) {
newValue.addPhiUser(user);
return newValue;
}
return v;
});
}
if (debugData != null) {
for (Instruction user : debugUsers()) {
user.getDebugValues().replaceAll(v -> {
if (v == this) {
newValue.addDebugUser(user);
return newValue;
}
return v;
});
if (user.getPreviousLocalValue() == this) {
newValue.addDebugUser(user);
user.replacePreviousLocalValue(newValue);
}
}
}
clearUsers();
}
public void setLiveIntervals(LiveIntervals intervals) {
assert liveIntervals == null;
liveIntervals = intervals;
}
public LiveIntervals getLiveIntervals() {
return liveIntervals;
}
public boolean needsRegister() {
assert needsRegister >= 0;
assert !hasUsersInfo() || (needsRegister > 0) == internalComputeNeedsRegister();
return needsRegister > 0;
}
public void setNeedsRegister(boolean value) {
assert needsRegister == -1 || (needsRegister > 0) == value;
needsRegister = value ? 1 : 0;
}
public void computeNeedsRegister() {
assert needsRegister < 0;
setNeedsRegister(internalComputeNeedsRegister());
}
public boolean internalComputeNeedsRegister() {
if (!isConstNumber()) {
return true;
}
if (numberOfPhiUsers() > 0) {
return true;
}
for (Instruction user : uniqueUsers()) {
if (user.needsValueInRegister(this)) {
return true;
}
}
return false;
}
public boolean hasRegisterConstraint() {
for (Instruction instruction : uniqueUsers()) {
if (instruction.maxInValueRegister() != Constants.U16BIT_MAX) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return number;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("v");
builder.append(number);
boolean isConstant = definition != null && definition.isConstNumber();
boolean hasLocalInfo = getLocalInfo() != null;
if (isConstant || hasLocalInfo) {
builder.append("(");
if (isConstant) {
ConstNumber constNumber = getConstInstruction().asConstNumber();
if (constNumber.outType() == MoveType.SINGLE) {
builder.append((int) constNumber.getRawValue());
} else {
builder.append(constNumber.getRawValue());
}
}
if (isConstant && hasLocalInfo) {
builder.append(", ");
}
if (hasLocalInfo) {
builder.append(getLocalInfo());
}
builder.append(")");
}
if (valueRange != null) {
builder.append(valueRange);
}
return builder.toString();
}
public MoveType outType() {
return type;
}
public ConstInstruction getConstInstruction() {
assert isConstant();
return definition.getOutConstantConstInstruction();
}
public boolean isConstNumber() {
return isConstant() && getConstInstruction().isConstNumber();
}
public boolean isConstString() {
return isConstant() && getConstInstruction().isConstString();
}
public boolean isConstant() {
return definition.isOutConstant() && getLocalInfo() == null;
}
public boolean isPhi() {
return false;
}
public Phi asPhi() {
return null;
}
public void markNeverNull() {
assert !neverNull;
neverNull = true;
}
/**
* Returns whether this value is know to never be <code>null</code>.
*/
public boolean isNeverNull() {
return neverNull;
}
public boolean canBeNull() {
return !neverNull;
}
public void markAsArgument() {
assert !isArgument;
assert !isThis;
isArgument = true;
}
public boolean isArgument() {
return isArgument;
}
public void markAsThis() {
assert isArgument;
assert !isThis;
isThis = true;
markNeverNull();
}
/**
* Returns whether this value is known to be the receiver (this argument) in a method body.
* <p>
* For a receiver value {@link #isNeverNull()} is guarenteed to be <code>true</code> as well.
*/
public boolean isThis() {
return isThis;
}
public void setValueRange(LongInterval range) {
valueRange = range;
}
public boolean hasValueRange() {
return valueRange != null || isConstNumber();
}
public boolean isValueInRange(int value) {
if (isConstNumber()) {
return value == getConstInstruction().asConstNumber().getIntValue();
} else {
return valueRange != null && valueRange.containsValue(value);
}
}
public LongInterval getValueRange() {
if (isConstNumber()) {
if (type == MoveType.SINGLE) {
int value = getConstInstruction().asConstNumber().getIntValue();
return new LongInterval(value, value);
} else {
assert type == MoveType.WIDE;
long value = getConstInstruction().asConstNumber().getLongValue();
return new LongInterval(value, value);
}
} else {
return valueRange;
}
}
public boolean isDead(InternalOptions options) {
// Totally unused values are trivially dead.
return !isUsed() || isDead(new HashSet<>(), options);
}
protected boolean isDead(Set<Value> active, InternalOptions options) {
// If the value has debug users we cannot eliminate it since it represents a value in a local
// variable that should be visible in the debugger.
if (numberOfDebugUsers() != 0) {
return false;
}
// This is a candidate for a dead value. Guard against looping by adding it to the set of
// currently active values.
active.add(this);
for (Instruction instruction : uniqueUsers()) {
if (!instruction.canBeDeadCode(null, options)) {
return false;
}
Value outValue = instruction.outValue();
// Instructions with no out value cannot be dead code by the current definition
// (unused out value). They typically side-effect input values or deals with control-flow.
assert outValue != null;
if (!active.contains(outValue) && !outValue.isDead(active, options)) {
return false;
}
}
for (Phi phi : uniquePhiUsers()) {
if (!active.contains(phi) && !phi.isDead(active, options)) {
return false;
}
}
return true;
}
}