package org.graalvm.compiler.lir.stackslotalloc;
import static org.graalvm.compiler.debug.DebugContext.BASIC_LEVEL;
import static org.graalvm.compiler.lir.LIRValueUtil.asVirtualStackSlot;
import static org.graalvm.compiler.lir.LIRValueUtil.isVirtualStackSlot;
import static org.graalvm.compiler.lir.phases.LIRPhase.Options.LIROptimization;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.PriorityQueue;
import jdk.internal.vm.compiler.collections.EconomicSet;
import org.graalvm.compiler.core.common.cfg.AbstractBlockBase;
import org.graalvm.compiler.debug.DebugCloseable;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.compiler.debug.TimerKey;
import org.graalvm.compiler.lir.LIR;
import org.graalvm.compiler.lir.LIRInstruction;
import org.graalvm.compiler.lir.LIRInstruction.OperandFlag;
import org.graalvm.compiler.lir.LIRInstruction.OperandMode;
import org.graalvm.compiler.lir.ValueProcedure;
import org.graalvm.compiler.lir.VirtualStackSlot;
import org.graalvm.compiler.lir.framemap.FrameMapBuilderTool;
import org.graalvm.compiler.lir.framemap.SimpleVirtualStackSlot;
import org.graalvm.compiler.lir.framemap.VirtualStackSlotRange;
import org.graalvm.compiler.lir.gen.LIRGenerationResult;
import org.graalvm.compiler.lir.phases.AllocationPhase;
import org.graalvm.compiler.options.NestedBooleanOptionKey;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionType;
import jdk.vm.ci.code.StackSlot;
import jdk.vm.ci.code.TargetDescription;
import jdk.vm.ci.meta.Value;
import jdk.vm.ci.meta.ValueKind;
public final class LSStackSlotAllocator extends AllocationPhase {
public static class Options {
@Option(help = "Use linear scan stack slot allocation.", type = OptionType.Debug)
public static final NestedBooleanOptionKey LIROptLSStackSlotAllocator = new NestedBooleanOptionKey(LIROptimization, true);
}
private static final TimerKey MainTimer = DebugContext.timer("LSStackSlotAllocator");
private static final TimerKey NumInstTimer = DebugContext.timer("LSStackSlotAllocator[NumberInstruction]");
private static final TimerKey BuildIntervalsTimer = DebugContext.timer("LSStackSlotAllocator[BuildIntervals]");
private static final TimerKey VerifyIntervalsTimer = DebugContext.timer("LSStackSlotAllocator[VerifyIntervals]");
private static final TimerKey AllocateSlotsTimer = DebugContext.timer("LSStackSlotAllocator[AllocateSlots]");
private static final TimerKey AssignSlotsTimer = DebugContext.timer("LSStackSlotAllocator[AssignSlots]");
@Override
protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) {
allocateStackSlots((FrameMapBuilderTool) lirGenRes.getFrameMapBuilder(), lirGenRes);
lirGenRes.buildFrameMap();
}
@SuppressWarnings("try")
public static void allocateStackSlots(FrameMapBuilderTool builder, LIRGenerationResult res) {
if (builder.getNumberOfStackSlots() > 0) {
try (DebugCloseable t = MainTimer.start(res.getLIR().getDebug())) {
new Allocator(res.getLIR(), builder).allocate();
}
}
}
private static final class Allocator {
private final LIR lir;
private final DebugContext debug;
private final FrameMapBuilderTool frameMapBuilder;
private final StackInterval[] stackSlotMap;
private final PriorityQueue<StackInterval> unhandled;
private final PriorityQueue<StackInterval> active;
private final AbstractBlockBase<?>[] sortedBlocks;
private final int maxOpId;
@SuppressWarnings("try")
private Allocator(LIR lir, FrameMapBuilderTool frameMapBuilder) {
this.lir = lir;
this.debug = lir.getDebug();
this.frameMapBuilder = frameMapBuilder;
this.stackSlotMap = new StackInterval[frameMapBuilder.getNumberOfStackSlots()];
this.sortedBlocks = lir.getControlFlowGraph().getBlocks();
this.unhandled = new PriorityQueue<>((a, b) -> a.from() - b.from());
this.active = new PriorityQueue<>((a, b) -> a.to() - b.to());
try (DebugCloseable t = NumInstTimer.start(debug)) {
this.maxOpId = numberInstructions(lir, sortedBlocks);
}
}
@SuppressWarnings("try")
private void allocate() {
debug.dump(DebugContext.VERBOSE_LEVEL, lir, "After StackSlot numbering");
boolean allocationFramesizeEnabled = StackSlotAllocatorUtil.allocatedFramesize.isEnabled(debug);
long currentFrameSize = allocationFramesizeEnabled ? frameMapBuilder.getFrameMap().currentFrameSize() : 0;
EconomicSet<LIRInstruction> usePos;
try (DebugContext.Scope s = debug.scope("StackSlotAllocationBuildIntervals"); Indent indent = debug.logAndIndent("BuildIntervals"); DebugCloseable t = BuildIntervalsTimer.start(debug)) {
usePos = buildIntervals();
}
if (debug.areScopesEnabled()) {
try (DebugCloseable t = VerifyIntervalsTimer.start(debug)) {
assert verifyIntervals();
}
}
if (debug.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) {
dumpIntervals("Before stack slot allocation");
}
try (DebugCloseable t = AllocateSlotsTimer.start(debug)) {
allocateStackSlots();
}
if (debug.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) {
dumpIntervals("After stack slot allocation");
}
try (DebugCloseable t = AssignSlotsTimer.start(debug)) {
assignStackSlots(usePos);
}
if (allocationFramesizeEnabled) {
StackSlotAllocatorUtil.allocatedFramesize.add(debug, frameMapBuilder.getFrameMap().currentFrameSize() - currentFrameSize);
}
}
private static int numberInstructions(LIR lir, AbstractBlockBase<?>[] sortedBlocks) {
int opId = 0;
int index = 0;
for (AbstractBlockBase<?> block : sortedBlocks) {
ArrayList<LIRInstruction> instructions = lir.getLIRforBlock(block);
int numInst = instructions.size();
for (int j = 0; j < numInst; j++) {
LIRInstruction op = instructions.get(j);
op.setId(opId);
index++;
opId += 2;
}
}
assert (index << 1) == opId : "must match: " + (index << 1);
return opId - 2;
}
private EconomicSet<LIRInstruction> buildIntervals() {
return new FixPointIntervalBuilder(lir, stackSlotMap, maxOpId()).build();
}
private boolean verifyIntervals() {
for (StackInterval interval : stackSlotMap) {
if (interval != null) {
assert interval.verify(maxOpId());
}
}
return true;
}
@SuppressWarnings("try")
private void allocateStackSlots() {
for (StackInterval interval : stackSlotMap) {
if (interval != null) {
unhandled.add(interval);
}
}
for (StackInterval current = activateNext(); current != null; current = activateNext()) {
try (Indent indent = debug.logAndIndent("allocate %s", current)) {
allocateSlot(current);
}
}
}
private void allocateSlot(StackInterval current) {
VirtualStackSlot virtualSlot = current.getOperand();
final StackSlot location;
if (virtualSlot instanceof VirtualStackSlotRange) {
VirtualStackSlotRange slotRange = (VirtualStackSlotRange) virtualSlot;
location = frameMapBuilder.getFrameMap().allocateStackSlots(slotRange.getSlots(), slotRange.getObjects());
StackSlotAllocatorUtil.virtualFramesize.add(debug, frameMapBuilder.getFrameMap().spillSlotRangeSize(slotRange.getSlots()));
StackSlotAllocatorUtil.allocatedSlots.increment(debug);
} else {
assert virtualSlot instanceof SimpleVirtualStackSlot : "Unexpected VirtualStackSlot type: " + virtualSlot;
StackSlot slot = findFreeSlot((SimpleVirtualStackSlot) virtualSlot);
if (slot != null) {
location = StackSlot.get(current.kind(), slot.getRawOffset(), slot.getRawAddFrameSize());
StackSlotAllocatorUtil.reusedSlots.increment(debug);
debug.log(BASIC_LEVEL, "Reuse stack slot %s (reallocated from %s) for virtual stack slot %s", location, slot, virtualSlot);
} else {
location = frameMapBuilder.getFrameMap().allocateSpillSlot(virtualSlot.getValueKind());
StackSlotAllocatorUtil.virtualFramesize.add(debug, frameMapBuilder.getFrameMap().spillSlotSize(virtualSlot.getValueKind()));
StackSlotAllocatorUtil.allocatedSlots.increment(debug);
debug.log(BASIC_LEVEL, "New stack slot %s for virtual stack slot %s", location, virtualSlot);
}
}
debug.log("Allocate location %s for interval %s", location, current);
current.setLocation(location);
}
private enum SlotSize {
Size1,
Size2,
Size4,
Size8,
Illegal;
}
private SlotSize forKind(ValueKind<?> kind) {
switch (frameMapBuilder.getFrameMap().spillSlotSize(kind)) {
case 1:
return SlotSize.Size1;
case 2:
return SlotSize.Size2;
case 4:
return SlotSize.Size4;
case 8:
return SlotSize.Size8;
default:
return SlotSize.Illegal;
}
}
private EnumMap<SlotSize, Deque<StackSlot>> freeSlots;
private Deque<StackSlot> getOrNullFreeSlots(SlotSize size) {
if (freeSlots == null) {
return null;
}
return freeSlots.get(size);
}
private Deque<StackSlot> getOrInitFreeSlots(SlotSize size) {
assert size != SlotSize.Illegal;
Deque<StackSlot> freeList;
if (freeSlots != null) {
freeList = freeSlots.get(size);
} else {
freeSlots = new EnumMap<>(SlotSize.class);
freeList = null;
}
if (freeList == null) {
freeList = new ArrayDeque<>();
freeSlots.put(size, freeList);
}
assert freeList != null;
return freeList;
}
private StackSlot findFreeSlot(SimpleVirtualStackSlot slot) {
assert slot != null;
SlotSize size = forKind(slot.getValueKind());
if (size == SlotSize.Illegal) {
return null;
}
Deque<StackSlot> freeList = getOrNullFreeSlots(size);
if (freeList == null) {
return null;
}
return freeList.pollLast();
}
private void freeSlot(StackSlot slot) {
SlotSize size = forKind(slot.getValueKind());
if (size == SlotSize.Illegal) {
return;
}
getOrInitFreeSlots(size).addLast(slot);
}
private StackInterval activateNext() {
if (unhandled.isEmpty()) {
return null;
}
StackInterval next = unhandled.poll();
for (int id = next.from(); activePeekId() < id;) {
finished(active.poll());
}
debug.log("active %s", next);
active.add(next);
return next;
}
private int activePeekId() {
StackInterval first = active.peek();
if (first == null) {
return Integer.MAX_VALUE;
}
return first.to();
}
private void finished(StackInterval interval) {
StackSlot location = interval.location();
debug.log("finished %s (freeing %s)", interval, location);
freeSlot(location);
}
private void assignStackSlots(EconomicSet<LIRInstruction> usePos) {
for (LIRInstruction op : usePos) {
op.forEachInput(assignSlot);
op.forEachAlive(assignSlot);
op.forEachState(assignSlot);
op.forEachTemp(assignSlot);
op.forEachOutput(assignSlot);
}
}
ValueProcedure assignSlot = new ValueProcedure() {
@Override
public Value doValue(Value value, OperandMode mode, EnumSet<OperandFlag> flags) {
if (isVirtualStackSlot(value)) {
VirtualStackSlot slot = asVirtualStackSlot(value);
StackInterval interval = get(slot);
assert interval != null;
return interval.location();
}
return value;
}
};
private int maxOpId() {
return maxOpId;
}
private StackInterval get(VirtualStackSlot stackSlot) {
return stackSlotMap[stackSlot.getId()];
}
private void dumpIntervals(String label) {
debug.dump(DebugContext.VERBOSE_LEVEL, new StackIntervalDumper(Arrays.copyOf(stackSlotMap, stackSlotMap.length)), label);
}
}
}