| // 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; |
| } |
| } |