1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.lir.stackslotalloc; 24 25 import static org.graalvm.compiler.lir.LIRValueUtil.asVirtualStackSlot; 26 import static org.graalvm.compiler.lir.LIRValueUtil.isVirtualStackSlot; 27 import static org.graalvm.compiler.lir.phases.LIRPhase.Options.LIROptimization; 28 29 import java.util.ArrayDeque; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Deque; 33 import java.util.EnumMap; 34 import java.util.EnumSet; 35 import java.util.PriorityQueue; 36 37 import org.graalvm.compiler.core.common.cfg.AbstractBlockBase; 38 import org.graalvm.compiler.debug.Debug; 39 import org.graalvm.compiler.debug.Debug.Scope; 40 import org.graalvm.compiler.debug.DebugCloseable; 41 import org.graalvm.compiler.debug.DebugTimer; 42 import org.graalvm.compiler.debug.Indent; 43 import org.graalvm.compiler.lir.LIR; 44 import org.graalvm.compiler.lir.LIRInstruction; 45 import org.graalvm.compiler.lir.LIRInstruction.OperandFlag; 46 import org.graalvm.compiler.lir.LIRInstruction.OperandMode; 47 import org.graalvm.compiler.lir.ValueProcedure; 48 import org.graalvm.compiler.lir.VirtualStackSlot; 49 import org.graalvm.compiler.lir.framemap.FrameMapBuilderTool; 50 import org.graalvm.compiler.lir.framemap.SimpleVirtualStackSlot; 51 import org.graalvm.compiler.lir.framemap.VirtualStackSlotRange; 52 import org.graalvm.compiler.lir.gen.LIRGenerationResult; 53 import org.graalvm.compiler.lir.phases.AllocationPhase; 54 import org.graalvm.compiler.options.NestedBooleanOptionKey; 55 import org.graalvm.compiler.options.Option; 56 import org.graalvm.compiler.options.OptionType; 57 import org.graalvm.util.EconomicSet; 58 59 import jdk.vm.ci.code.StackSlot; 60 import jdk.vm.ci.code.TargetDescription; 61 import jdk.vm.ci.meta.Value; 62 import jdk.vm.ci.meta.ValueKind; 63 64 /** 65 * Linear Scan {@link StackSlotAllocatorUtil stack slot allocator}. 66 * <p> 67 * <b>Remark:</b> The analysis works under the assumption that a stack slot is no longer live after 68 * its last usage. If an {@link LIRInstruction instruction} transfers the raw address of the stack 69 * slot to another location, e.g. a registers, and this location is referenced later on, the 70 * {@link org.graalvm.compiler.lir.LIRInstruction.Use usage} of the stack slot must be marked with 71 * the {@link OperandFlag#UNINITIALIZED}. Otherwise the stack slot might be reused and its content 72 * destroyed. 73 */ 74 public final class LSStackSlotAllocator extends AllocationPhase { 75 76 public static class Options { 77 // @formatter:off 78 @Option(help = "Use linear scan stack slot allocation.", type = OptionType.Debug) 79 public static final NestedBooleanOptionKey LIROptLSStackSlotAllocator = new NestedBooleanOptionKey(LIROptimization, true); 80 // @formatter:on 81 } 82 83 private static final DebugTimer MainTimer = Debug.timer("LSStackSlotAllocator"); 84 private static final DebugTimer NumInstTimer = Debug.timer("LSStackSlotAllocator[NumberInstruction]"); 85 private static final DebugTimer BuildIntervalsTimer = Debug.timer("LSStackSlotAllocator[BuildIntervals]"); 86 private static final DebugTimer VerifyIntervalsTimer = Debug.timer("LSStackSlotAllocator[VerifyIntervals]"); 87 private static final DebugTimer AllocateSlotsTimer = Debug.timer("LSStackSlotAllocator[AllocateSlots]"); 88 private static final DebugTimer AssignSlotsTimer = Debug.timer("LSStackSlotAllocator[AssignSlots]"); 89 90 @Override 91 protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) { 92 allocateStackSlots((FrameMapBuilderTool) lirGenRes.getFrameMapBuilder(), lirGenRes); 93 lirGenRes.buildFrameMap(); 94 } 95 96 @SuppressWarnings("try") 97 public static void allocateStackSlots(FrameMapBuilderTool builder, LIRGenerationResult res) { 98 if (builder.getNumberOfStackSlots() > 0) { 99 try (DebugCloseable t = MainTimer.start()) { 100 new Allocator(res.getLIR(), builder).allocate(); 101 } 102 } 103 } 104 105 private static final class Allocator { 106 private final LIR lir; 107 private final FrameMapBuilderTool frameMapBuilder; 108 private final StackInterval[] stackSlotMap; 109 private final PriorityQueue<StackInterval> unhandled; 110 private final PriorityQueue<StackInterval> active; 111 private final AbstractBlockBase<?>[] sortedBlocks; 112 private final int maxOpId; 113 114 @SuppressWarnings("try") 115 private Allocator(LIR lir, FrameMapBuilderTool frameMapBuilder) { 116 this.lir = lir; 117 this.frameMapBuilder = frameMapBuilder; 118 this.stackSlotMap = new StackInterval[frameMapBuilder.getNumberOfStackSlots()]; 119 this.sortedBlocks = lir.getControlFlowGraph().getBlocks(); 120 121 // insert by from 122 this.unhandled = new PriorityQueue<>((a, b) -> a.from() - b.from()); 123 // insert by to 124 this.active = new PriorityQueue<>((a, b) -> a.to() - b.to()); 125 126 try (DebugCloseable t = NumInstTimer.start()) { 127 // step 1: number instructions 128 this.maxOpId = numberInstructions(lir, sortedBlocks); 129 } 130 } 131 132 @SuppressWarnings("try") 133 private void allocate() { 134 Debug.dump(Debug.INFO_LOG_LEVEL, lir, "After StackSlot numbering"); 135 136 long currentFrameSize = StackSlotAllocatorUtil.allocatedFramesize.isEnabled() ? frameMapBuilder.getFrameMap().currentFrameSize() : 0; 137 EconomicSet<LIRInstruction> usePos; 138 // step 2: build intervals 139 try (Scope s = Debug.scope("StackSlotAllocationBuildIntervals"); Indent indent = Debug.logAndIndent("BuildIntervals"); DebugCloseable t = BuildIntervalsTimer.start()) { 140 usePos = buildIntervals(); 141 } 142 // step 3: verify intervals 143 if (Debug.isEnabled()) { 144 try (DebugCloseable t = VerifyIntervalsTimer.start()) { 145 assert verifyIntervals(); 146 } 147 } 148 if (Debug.isDumpEnabled(Debug.INFO_LOG_LEVEL)) { 149 dumpIntervals("Before stack slot allocation"); 150 } 151 // step 4: allocate stack slots 152 try (DebugCloseable t = AllocateSlotsTimer.start()) { 153 allocateStackSlots(); 154 } 155 if (Debug.isDumpEnabled(Debug.INFO_LOG_LEVEL)) { 156 dumpIntervals("After stack slot allocation"); 157 } 158 159 // step 5: assign stack slots 160 try (DebugCloseable t = AssignSlotsTimer.start()) { 161 assignStackSlots(usePos); 162 } 163 Debug.dump(Debug.INFO_LOG_LEVEL, lir, "After StackSlot assignment"); 164 if (StackSlotAllocatorUtil.allocatedFramesize.isEnabled()) { 165 StackSlotAllocatorUtil.allocatedFramesize.add(frameMapBuilder.getFrameMap().currentFrameSize() - currentFrameSize); 166 } 167 } 168 169 // ==================== 170 // step 1: number instructions 171 // ==================== 172 173 /** 174 * Numbers all instructions in all blocks. 175 * 176 * @return The id of the last operation. 177 */ 178 private static int numberInstructions(LIR lir, AbstractBlockBase<?>[] sortedBlocks) { 179 int opId = 0; 180 int index = 0; 181 for (AbstractBlockBase<?> block : sortedBlocks) { 182 183 ArrayList<LIRInstruction> instructions = lir.getLIRforBlock(block); 184 185 int numInst = instructions.size(); 186 for (int j = 0; j < numInst; j++) { 187 LIRInstruction op = instructions.get(j); 188 op.setId(opId); 189 190 index++; 191 opId += 2; // numbering of lirOps by two 192 } 193 } 194 assert (index << 1) == opId : "must match: " + (index << 1); 195 return opId - 2; 196 } 197 198 // ==================== 199 // step 2: build intervals 200 // ==================== 201 202 private EconomicSet<LIRInstruction> buildIntervals() { 203 return new FixPointIntervalBuilder(lir, stackSlotMap, maxOpId()).build(); 204 } 205 206 // ==================== 207 // step 3: verify intervals 208 // ==================== 209 210 private boolean verifyIntervals() { 211 for (StackInterval interval : stackSlotMap) { 212 if (interval != null) { 213 assert interval.verify(maxOpId()); 214 } 215 } 216 return true; 217 } 218 219 // ==================== 220 // step 4: allocate stack slots 221 // ==================== 222 223 @SuppressWarnings("try") 224 private void allocateStackSlots() { 225 // create unhandled lists 226 for (StackInterval interval : stackSlotMap) { 227 if (interval != null) { 228 unhandled.add(interval); 229 } 230 } 231 232 for (StackInterval current = activateNext(); current != null; current = activateNext()) { 233 try (Indent indent = Debug.logAndIndent("allocate %s", current)) { 234 allocateSlot(current); 235 } 236 } 237 238 } 239 240 private void allocateSlot(StackInterval current) { 241 VirtualStackSlot virtualSlot = current.getOperand(); 242 final StackSlot location; 243 if (virtualSlot instanceof VirtualStackSlotRange) { 244 // No reuse of ranges (yet). 245 VirtualStackSlotRange slotRange = (VirtualStackSlotRange) virtualSlot; 246 location = frameMapBuilder.getFrameMap().allocateStackSlots(slotRange.getSlots(), slotRange.getObjects()); 247 StackSlotAllocatorUtil.virtualFramesize.add(frameMapBuilder.getFrameMap().spillSlotRangeSize(slotRange.getSlots())); 248 StackSlotAllocatorUtil.allocatedSlots.increment(); 249 } else { 250 assert virtualSlot instanceof SimpleVirtualStackSlot : "Unexpected VirtualStackSlot type: " + virtualSlot; 251 StackSlot slot = findFreeSlot((SimpleVirtualStackSlot) virtualSlot); 252 if (slot != null) { 253 /* 254 * Free stack slot available. Note that we create a new one because the kind 255 * might not match. 256 */ 257 location = StackSlot.get(current.kind(), slot.getRawOffset(), slot.getRawAddFrameSize()); 258 StackSlotAllocatorUtil.reusedSlots.increment(); 259 Debug.log(Debug.BASIC_LOG_LEVEL, "Reuse stack slot %s (reallocated from %s) for virtual stack slot %s", location, slot, virtualSlot); 260 } else { 261 // Allocate new stack slot. 262 location = frameMapBuilder.getFrameMap().allocateSpillSlot(virtualSlot.getValueKind()); 263 StackSlotAllocatorUtil.virtualFramesize.add(frameMapBuilder.getFrameMap().spillSlotSize(virtualSlot.getValueKind())); 264 StackSlotAllocatorUtil.allocatedSlots.increment(); 265 Debug.log(Debug.BASIC_LOG_LEVEL, "New stack slot %s for virtual stack slot %s", location, virtualSlot); 266 } 267 } 268 Debug.log("Allocate location %s for interval %s", location, current); 269 current.setLocation(location); 270 } 271 272 private enum SlotSize { 273 Size1, 274 Size2, 275 Size4, 276 Size8, 277 Illegal; 278 } 279 280 private SlotSize forKind(ValueKind<?> kind) { 281 switch (frameMapBuilder.getFrameMap().spillSlotSize(kind)) { 282 case 1: 283 return SlotSize.Size1; 284 case 2: 285 return SlotSize.Size2; 286 case 4: 287 return SlotSize.Size4; 288 case 8: 289 return SlotSize.Size8; 290 default: 291 return SlotSize.Illegal; 292 } 293 } 294 295 private EnumMap<SlotSize, Deque<StackSlot>> freeSlots; 296 297 /** 298 * @return The list of free stack slots for {@code size} or {@code null} if there is none. 299 */ 300 private Deque<StackSlot> getOrNullFreeSlots(SlotSize size) { 301 if (freeSlots == null) { 302 return null; 303 } 304 return freeSlots.get(size); 305 } 306 307 /** 308 * @return the list of free stack slots for {@code size}. If there is none a list is 309 * created. 310 */ 311 private Deque<StackSlot> getOrInitFreeSlots(SlotSize size) { 312 assert size != SlotSize.Illegal; 313 Deque<StackSlot> freeList; 314 if (freeSlots != null) { 315 freeList = freeSlots.get(size); 316 } else { 317 freeSlots = new EnumMap<>(SlotSize.class); 318 freeList = null; 319 } 320 if (freeList == null) { 321 freeList = new ArrayDeque<>(); 322 freeSlots.put(size, freeList); 323 } 324 assert freeList != null; 325 return freeList; 326 } 327 328 /** 329 * Gets a free stack slot for {@code slot} or {@code null} if there is none. 330 */ 331 private StackSlot findFreeSlot(SimpleVirtualStackSlot slot) { 332 assert slot != null; 333 SlotSize size = forKind(slot.getValueKind()); 334 if (size == SlotSize.Illegal) { 335 return null; 336 } 337 Deque<StackSlot> freeList = getOrNullFreeSlots(size); 338 if (freeList == null) { 339 return null; 340 } 341 return freeList.pollLast(); 342 } 343 344 /** 345 * Adds a stack slot to the list of free slots. 346 */ 347 private void freeSlot(StackSlot slot) { 348 SlotSize size = forKind(slot.getValueKind()); 349 if (size == SlotSize.Illegal) { 350 return; 351 } 352 getOrInitFreeSlots(size).addLast(slot); 353 } 354 355 /** 356 * Gets the next unhandled interval and finishes handled intervals. 357 */ 358 private StackInterval activateNext() { 359 if (unhandled.isEmpty()) { 360 return null; 361 } 362 StackInterval next = unhandled.poll(); 363 // finish handled intervals 364 for (int id = next.from(); activePeekId() < id;) { 365 finished(active.poll()); 366 } 367 Debug.log("active %s", next); 368 active.add(next); 369 return next; 370 } 371 372 /** 373 * Gets the lowest {@link StackInterval#to() end position} of all active intervals. If there 374 * is none {@link Integer#MAX_VALUE} is returned. 375 */ 376 private int activePeekId() { 377 StackInterval first = active.peek(); 378 if (first == null) { 379 return Integer.MAX_VALUE; 380 } 381 return first.to(); 382 } 383 384 /** 385 * Finishes {@code interval} by adding its location to the list of free stack slots. 386 */ 387 private void finished(StackInterval interval) { 388 StackSlot location = interval.location(); 389 Debug.log("finished %s (freeing %s)", interval, location); 390 freeSlot(location); 391 } 392 393 // ==================== 394 // step 5: assign stack slots 395 // ==================== 396 397 private void assignStackSlots(EconomicSet<LIRInstruction> usePos) { 398 for (LIRInstruction op : usePos) { 399 op.forEachInput(assignSlot); 400 op.forEachAlive(assignSlot); 401 op.forEachState(assignSlot); 402 403 op.forEachTemp(assignSlot); 404 op.forEachOutput(assignSlot); 405 } 406 } 407 408 ValueProcedure assignSlot = new ValueProcedure() { 409 @Override 410 public Value doValue(Value value, OperandMode mode, EnumSet<OperandFlag> flags) { 411 if (isVirtualStackSlot(value)) { 412 VirtualStackSlot slot = asVirtualStackSlot(value); 413 StackInterval interval = get(slot); 414 assert interval != null; 415 return interval.location(); 416 } 417 return value; 418 } 419 }; 420 421 // ==================== 422 // 423 // ==================== 424 425 /** 426 * Gets the highest instruction id. 427 */ 428 private int maxOpId() { 429 return maxOpId; 430 } 431 432 private StackInterval get(VirtualStackSlot stackSlot) { 433 return stackSlotMap[stackSlot.getId()]; 434 } 435 436 private void dumpIntervals(String label) { 437 Debug.dump(Debug.INFO_LOG_LEVEL, new StackIntervalDumper(Arrays.copyOf(stackSlotMap, stackSlotMap.length)), label); 438 } 439 440 } 441 }