blob: ce8eb609642e05fb7df5acff933b1c90f26985b2 [file] [log] [blame]
/*
* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.lir.alloc.trace.lsra;
import static org.graalvm.compiler.lir.LIRValueUtil.asVirtualStackSlot;
import static org.graalvm.compiler.lir.LIRValueUtil.isStackSlotValue;
import static org.graalvm.compiler.lir.LIRValueUtil.isVirtualStackSlot;
import static jdk.vm.ci.code.ValueUtil.asRegister;
import static jdk.vm.ci.code.ValueUtil.asStackSlot;
import static jdk.vm.ci.code.ValueUtil.isIllegal;
import static jdk.vm.ci.code.ValueUtil.isRegister;
import static jdk.vm.ci.code.ValueUtil.isStackSlot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.graalvm.compiler.core.common.LIRKind;
import org.graalvm.compiler.debug.Debug;
import org.graalvm.compiler.debug.DebugCounter;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.compiler.lir.LIRInsertionBuffer;
import org.graalvm.compiler.lir.LIRInstruction;
import org.graalvm.compiler.lir.VirtualStackSlot;
import org.graalvm.compiler.lir.alloc.trace.lsra.TraceLinearScanPhase.TraceLinearScan;
import org.graalvm.compiler.lir.framemap.FrameMap;
import org.graalvm.compiler.lir.framemap.FrameMapBuilderTool;
import jdk.vm.ci.code.StackSlot;
import jdk.vm.ci.meta.AllocatableValue;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.Value;
/**
*/
final class TraceLocalMoveResolver {
private static final DebugCounter cycleBreakingSlotsAllocated = Debug.counter("TraceRA[cycleBreakingSlotsAllocated(local)]");
private static final int STACK_SLOT_IN_CALLER_FRAME_IDX = -1;
private final TraceLinearScan allocator;
private int insertIdx;
private LIRInsertionBuffer insertionBuffer; // buffer where moves are inserted
private final List<TraceInterval> mappingFrom;
private final List<Constant> mappingFromOpr;
private final List<TraceInterval> mappingTo;
private final int[] registerBlocked;
private int[] stackBlocked;
private final int firstVirtualStackIndex;
private int getStackArrayIndex(Value stackSlotValue) {
if (isStackSlot(stackSlotValue)) {
return getStackArrayIndex(asStackSlot(stackSlotValue));
}
if (isVirtualStackSlot(stackSlotValue)) {
return getStackArrayIndex(asVirtualStackSlot(stackSlotValue));
}
throw GraalError.shouldNotReachHere("value is not a stack slot: " + stackSlotValue);
}
private int getStackArrayIndex(StackSlot stackSlot) {
int stackIdx;
if (stackSlot.isInCallerFrame()) {
// incoming stack arguments can be ignored
stackIdx = STACK_SLOT_IN_CALLER_FRAME_IDX;
} else {
assert stackSlot.getRawAddFrameSize() : "Unexpected stack slot: " + stackSlot;
int offset = -stackSlot.getRawOffset();
assert 0 <= offset && offset < firstVirtualStackIndex : String.format("Wrong stack slot offset: %d (first virtual stack slot index: %d", offset, firstVirtualStackIndex);
stackIdx = offset;
}
return stackIdx;
}
private int getStackArrayIndex(VirtualStackSlot virtualStackSlot) {
return firstVirtualStackIndex + virtualStackSlot.getId();
}
protected void setValueBlocked(Value location, int direction) {
assert direction == 1 || direction == -1 : "out of bounds";
if (isStackSlotValue(location)) {
int stackIdx = getStackArrayIndex(location);
if (stackIdx == STACK_SLOT_IN_CALLER_FRAME_IDX) {
// incoming stack arguments can be ignored
return;
}
if (stackIdx >= stackBlocked.length) {
stackBlocked = Arrays.copyOf(stackBlocked, stackIdx + 1);
}
stackBlocked[stackIdx] += direction;
} else {
assert direction == 1 || direction == -1 : "out of bounds";
if (isRegister(location)) {
registerBlocked[asRegister(location).number] += direction;
} else {
throw GraalError.shouldNotReachHere("unhandled value " + location);
}
}
}
protected TraceInterval getMappingFrom(int i) {
return mappingFrom.get(i);
}
protected int mappingFromSize() {
return mappingFrom.size();
}
protected int valueBlocked(Value location) {
if (isStackSlotValue(location)) {
int stackIdx = getStackArrayIndex(location);
if (stackIdx == STACK_SLOT_IN_CALLER_FRAME_IDX) {
// incoming stack arguments are always blocked (aka they can not be written)
return 1;
}
if (stackIdx >= stackBlocked.length) {
return 0;
}
return stackBlocked[stackIdx];
}
if (isRegister(location)) {
return registerBlocked[asRegister(location).number];
}
throw GraalError.shouldNotReachHere("unhandled value " + location);
}
/*
* TODO (je) remove?
*/
protected static boolean areMultipleReadsAllowed() {
return true;
}
boolean hasMappings() {
return mappingFrom.size() > 0;
}
protected TraceLinearScan getAllocator() {
return allocator;
}
protected TraceLocalMoveResolver(TraceLinearScan allocator) {
this.allocator = allocator;
this.mappingFrom = new ArrayList<>(8);
this.mappingFromOpr = new ArrayList<>(8);
this.mappingTo = new ArrayList<>(8);
this.insertIdx = -1;
this.insertionBuffer = new LIRInsertionBuffer();
this.registerBlocked = new int[allocator.getRegisters().size()];
FrameMapBuilderTool frameMapBuilderTool = (FrameMapBuilderTool) allocator.getFrameMapBuilder();
FrameMap frameMap = frameMapBuilderTool.getFrameMap();
this.stackBlocked = new int[frameMapBuilderTool.getNumberOfStackSlots()];
this.firstVirtualStackIndex = !frameMap.frameNeedsAllocating() ? 0 : frameMap.currentFrameSize() + 1;
}
protected boolean checkEmpty() {
assert mappingFrom.size() == 0 && mappingFromOpr.size() == 0 && mappingTo.size() == 0 : "list must be empty before and after processing";
for (int i = 0; i < stackBlocked.length; i++) {
assert stackBlocked[i] == 0 : "stack map must be empty before and after processing";
}
for (int i = 0; i < getAllocator().getRegisters().size(); i++) {
assert registerBlocked[i] == 0 : "register map must be empty before and after processing";
}
checkMultipleReads();
return true;
}
protected void checkMultipleReads() {
// multiple reads are allowed in SSA LSRA
}
private boolean verifyBeforeResolve() {
assert mappingFrom.size() == mappingFromOpr.size() : "length must be equal";
assert mappingFrom.size() == mappingTo.size() : "length must be equal";
assert insertIdx != -1 : "insert position not set";
int i;
int j;
if (!areMultipleReadsAllowed()) {
for (i = 0; i < mappingFrom.size(); i++) {
for (j = i + 1; j < mappingFrom.size(); j++) {
assert mappingFrom.get(i) == null || mappingFrom.get(i) != mappingFrom.get(j) : "cannot read from same interval twice";
}
}
}
for (i = 0; i < mappingTo.size(); i++) {
for (j = i + 1; j < mappingTo.size(); j++) {
assert mappingTo.get(i) != mappingTo.get(j) : "cannot write to same interval twice";
}
}
HashSet<Value> usedRegs = new HashSet<>();
if (!areMultipleReadsAllowed()) {
for (i = 0; i < mappingFrom.size(); i++) {
TraceInterval interval = mappingFrom.get(i);
if (interval != null && !isIllegal(interval.location())) {
boolean unique = usedRegs.add(interval.location());
assert unique : "cannot read from same register twice";
}
}
}
usedRegs.clear();
for (i = 0; i < mappingTo.size(); i++) {
TraceInterval interval = mappingTo.get(i);
if (isIllegal(interval.location())) {
// After insertion the location may become illegal, so don't check it since multiple
// intervals might be illegal.
continue;
}
boolean unique = usedRegs.add(interval.location());
assert unique : "cannot write to same register twice";
}
verifyStackSlotMapping();
return true;
}
protected void verifyStackSlotMapping() {
// relax disjoint stack maps invariant
}
// mark assignedReg and assignedRegHi of the interval as blocked
private void blockRegisters(TraceInterval interval) {
Value location = interval.location();
if (mightBeBlocked(location)) {
assert areMultipleReadsAllowed() || valueBlocked(location) == 0 : "location already marked as used: " + location;
int direction = 1;
setValueBlocked(location, direction);
Debug.log("block %s", location);
}
}
// mark assignedReg and assignedRegHi of the interval as unblocked
private void unblockRegisters(TraceInterval interval) {
Value location = interval.location();
if (mightBeBlocked(location)) {
assert valueBlocked(location) > 0 : "location already marked as unused: " + location;
setValueBlocked(location, -1);
Debug.log("unblock %s", location);
}
}
/**
* Checks if the {@linkplain TraceInterval#location() location} of {@code to} is not blocked or
* is only blocked by {@code from}.
*/
private boolean safeToProcessMove(TraceInterval from, TraceInterval to) {
Value fromReg = from != null ? from.location() : null;
Value location = to.location();
if (mightBeBlocked(location)) {
if ((valueBlocked(location) > 1 || (valueBlocked(location) == 1 && !isMoveToSelf(fromReg, location)))) {
return false;
}
}
return true;
}
protected static boolean isMoveToSelf(Value from, Value to) {
assert to != null;
if (to.equals(from)) {
return true;
}
if (from != null && isRegister(from) && isRegister(to) && asRegister(from).equals(asRegister(to))) {
assert LIRKind.verifyMoveKinds(to.getValueKind(), from.getValueKind()) : String.format("Same register but Kind mismatch %s <- %s", to, from);
return true;
}
return false;
}
protected static boolean mightBeBlocked(Value location) {
if (isRegister(location)) {
return true;
}
if (isStackSlotValue(location)) {
return true;
}
return false;
}
private void createInsertionBuffer(List<LIRInstruction> list) {
assert !insertionBuffer.initialized() : "overwriting existing buffer";
insertionBuffer.init(list);
}
private void appendInsertionBuffer() {
if (insertionBuffer.initialized()) {
insertionBuffer.finish();
}
assert !insertionBuffer.initialized() : "must be uninitialized now";
insertIdx = -1;
}
private void insertMove(TraceInterval fromInterval, TraceInterval toInterval) {
assert !fromInterval.operand.equals(toInterval.operand) : "from and to interval equal: " + fromInterval;
assert LIRKind.verifyMoveKinds(toInterval.kind(), fromInterval.kind()) : "move between different types";
assert insertIdx != -1 : "must setup insert position first";
insertionBuffer.append(insertIdx, createMove(fromInterval.operand, toInterval.operand, fromInterval.location(), toInterval.location()));
if (Debug.isLogEnabled()) {
Debug.log("insert move from %s to %s at %d", fromInterval, toInterval, insertIdx);
}
}
/**
* @param fromOpr {@link TraceInterval#operand operand} of the {@code from} interval
* @param toOpr {@link TraceInterval#operand operand} of the {@code to} interval
* @param fromLocation {@link TraceInterval#location() location} of the {@code to} interval
* @param toLocation {@link TraceInterval#location() location} of the {@code to} interval
*/
protected LIRInstruction createMove(AllocatableValue fromOpr, AllocatableValue toOpr, AllocatableValue fromLocation, AllocatableValue toLocation) {
if (isStackSlotValue(toLocation) && isStackSlotValue(fromLocation)) {
return getAllocator().getSpillMoveFactory().createStackMove(toOpr, fromOpr);
}
return getAllocator().getSpillMoveFactory().createMove(toOpr, fromOpr);
}
private void insertMove(Constant fromOpr, TraceInterval toInterval) {
assert insertIdx != -1 : "must setup insert position first";
AllocatableValue toOpr = toInterval.operand;
LIRInstruction move = getAllocator().getSpillMoveFactory().createLoad(toOpr, fromOpr);
insertionBuffer.append(insertIdx, move);
if (Debug.isLogEnabled()) {
Debug.log("insert move from value %s to %s at %d", fromOpr, toInterval, insertIdx);
}
}
@SuppressWarnings("try")
private void resolveMappings() {
try (Indent indent = Debug.logAndIndent("resolveMapping")) {
assert verifyBeforeResolve();
if (Debug.isLogEnabled()) {
printMapping();
}
// Block all registers that are used as input operands of a move.
// When a register is blocked, no move to this register is emitted.
// This is necessary for detecting cycles in moves.
int i;
for (i = mappingFrom.size() - 1; i >= 0; i--) {
TraceInterval fromInterval = mappingFrom.get(i);
if (fromInterval != null) {
blockRegisters(fromInterval);
}
}
ArrayList<AllocatableValue> busySpillSlots = null;
while (mappingFrom.size() > 0) {
boolean processedInterval = false;
int spillCandidate = -1;
for (i = mappingFrom.size() - 1; i >= 0; i--) {
TraceInterval fromInterval = mappingFrom.get(i);
TraceInterval toInterval = mappingTo.get(i);
if (safeToProcessMove(fromInterval, toInterval)) {
// this interval can be processed because target is free
if (fromInterval != null) {
insertMove(fromInterval, toInterval);
unblockRegisters(fromInterval);
} else {
insertMove(mappingFromOpr.get(i), toInterval);
}
if (isStackSlotValue(toInterval.location())) {
if (busySpillSlots == null) {
busySpillSlots = new ArrayList<>(2);
}
busySpillSlots.add(toInterval.location());
}
mappingFrom.remove(i);
mappingFromOpr.remove(i);
mappingTo.remove(i);
processedInterval = true;
} else if (fromInterval != null && isRegister(fromInterval.location()) && (busySpillSlots == null || !busySpillSlots.contains(fromInterval.spillSlot()))) {
// this interval cannot be processed now because target is not free
// it starts in a register, so it is a possible candidate for spilling
spillCandidate = i;
}
}
if (!processedInterval) {
breakCycle(spillCandidate);
}
}
}
// check that all intervals have been processed
assert checkEmpty();
}
protected void breakCycle(int spillCandidate) {
if (spillCandidate != -1) {
// no move could be processed because there is a cycle in the move list
// (e.g. r1 . r2, r2 . r1), so one interval must be spilled to memory
assert spillCandidate != -1 : "no interval in register for spilling found";
// create a new spill interval and assign a stack slot to it
TraceInterval fromInterval1 = mappingFrom.get(spillCandidate);
// do not allocate a new spill slot for temporary interval, but
// use spill slot assigned to fromInterval. Otherwise moves from
// one stack slot to another can happen (not allowed by LIRAssembler
AllocatableValue spillSlot1 = fromInterval1.spillSlot();
if (spillSlot1 == null) {
spillSlot1 = getAllocator().getFrameMapBuilder().allocateSpillSlot(fromInterval1.kind());
fromInterval1.setSpillSlot(spillSlot1);
cycleBreakingSlotsAllocated.increment();
}
spillInterval(spillCandidate, fromInterval1, spillSlot1);
return;
}
assert mappingFromSize() > 1;
// Arbitrarily select the first entry for spilling.
int stackSpillCandidate = 0;
TraceInterval fromInterval = getMappingFrom(stackSpillCandidate);
// allocate new stack slot
VirtualStackSlot spillSlot = getAllocator().getFrameMapBuilder().allocateSpillSlot(fromInterval.kind());
spillInterval(stackSpillCandidate, fromInterval, spillSlot);
}
protected void spillInterval(int spillCandidate, TraceInterval fromInterval, AllocatableValue spillSlot) {
assert mappingFrom.get(spillCandidate).equals(fromInterval);
TraceInterval spillInterval = getAllocator().createDerivedInterval(fromInterval);
// add a dummy range because real position is difficult to calculate
// Note: this range is a special case when the integrity of the allocation is
// checked
spillInterval.addRange(1, 2);
spillInterval.assignLocation(spillSlot);
if (Debug.isLogEnabled()) {
Debug.log("created new Interval for spilling: %s", spillInterval);
}
blockRegisters(spillInterval);
// insert a move from register to stack and update the mapping
insertMove(fromInterval, spillInterval);
mappingFrom.set(spillCandidate, spillInterval);
unblockRegisters(fromInterval);
}
@SuppressWarnings("try")
private void printMapping() {
try (Indent indent = Debug.logAndIndent("Mapping")) {
for (int i = mappingFrom.size() - 1; i >= 0; i--) {
TraceInterval fromInterval = mappingFrom.get(i);
TraceInterval toInterval = mappingTo.get(i);
String from;
Value to = toInterval.location();
if (fromInterval == null) {
from = mappingFromOpr.get(i).toString();
} else {
from = fromInterval.location().toString();
}
Debug.log("move %s <- %s", from, to);
}
}
}
void setInsertPosition(List<LIRInstruction> insertList, int insertIdx) {
assert this.insertIdx == -1 : "use moveInsertPosition instead of setInsertPosition when data already set";
createInsertionBuffer(insertList);
this.insertIdx = insertIdx;
}
void moveInsertPosition(List<LIRInstruction> newInsertList, int newInsertIdx) {
if (insertionBuffer.lirList() != null && (insertionBuffer.lirList() != newInsertList || this.insertIdx != newInsertIdx)) {
// insert position changed . resolve current mappings
resolveMappings();
}
assert insertionBuffer.lirList() != newInsertList || newInsertIdx >= insertIdx : String.format("Decreasing insert index: old=%d new=%d", insertIdx, newInsertIdx);
if (insertionBuffer.lirList() != newInsertList) {
// block changed . append insertionBuffer because it is
// bound to a specific block and create a new insertionBuffer
appendInsertionBuffer();
createInsertionBuffer(newInsertList);
}
this.insertIdx = newInsertIdx;
}
public void addMapping(TraceInterval fromInterval, TraceInterval toInterval) {
if (isIllegal(toInterval.location()) && toInterval.canMaterialize()) {
if (Debug.isLogEnabled()) {
Debug.log("no store to rematerializable interval %s needed", toInterval);
}
return;
}
if (isIllegal(fromInterval.location()) && fromInterval.canMaterialize()) {
// Instead of a reload, re-materialize the value
JavaConstant rematValue = fromInterval.getMaterializedValue();
addMapping(rematValue, toInterval);
return;
}
if (Debug.isLogEnabled()) {
Debug.log("add move mapping from %s to %s", fromInterval, toInterval);
}
assert !fromInterval.operand.equals(toInterval.operand) : "from and to interval equal: " + fromInterval;
assert LIRKind.verifyMoveKinds(toInterval.kind(), fromInterval.kind()) : String.format("Kind mismatch: %s vs. %s, from=%s, to=%s", fromInterval.kind(), toInterval.kind(), fromInterval,
toInterval);
mappingFrom.add(fromInterval);
mappingFromOpr.add(null);
mappingTo.add(toInterval);
}
public void addMapping(Constant fromOpr, TraceInterval toInterval) {
if (Debug.isLogEnabled()) {
Debug.log("add move mapping from %s to %s", fromOpr, toInterval);
}
mappingFrom.add(null);
mappingFromOpr.add(fromOpr);
mappingTo.add(toInterval);
}
void resolveAndAppendMoves() {
if (hasMappings()) {
resolveMappings();
}
appendInsertionBuffer();
}
}