1 /* 2 * Copyright (c) 2014, 2019, 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 24 25 package org.graalvm.compiler.lir.stackslotalloc; 26 27 import static org.graalvm.compiler.debug.DebugContext.BASIC_LEVEL; 28 import static org.graalvm.compiler.lir.LIRValueUtil.asVirtualStackSlot; 29 import static org.graalvm.compiler.lir.LIRValueUtil.isVirtualStackSlot; 30 import static org.graalvm.compiler.lir.phases.LIRPhase.Options.LIROptimization; 31 32 import java.util.ArrayDeque; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Deque; 36 import java.util.EnumSet; 37 import java.util.PriorityQueue; 38 import java.util.function.Predicate; 39 40 import jdk.internal.vm.compiler.collections.EconomicSet; 41 import org.graalvm.compiler.core.common.LIRKind; 42 import org.graalvm.compiler.core.common.cfg.AbstractBlockBase; 43 import org.graalvm.compiler.debug.DebugCloseable; 44 import org.graalvm.compiler.debug.DebugContext; 45 import org.graalvm.compiler.debug.Indent; 46 import org.graalvm.compiler.debug.TimerKey; 47 import org.graalvm.compiler.lir.LIR; 48 import org.graalvm.compiler.lir.LIRInstruction; 49 import org.graalvm.compiler.lir.LIRInstruction.OperandFlag; 50 import org.graalvm.compiler.lir.LIRInstruction.OperandMode; 51 import org.graalvm.compiler.lir.ValueProcedure; 52 import org.graalvm.compiler.lir.VirtualStackSlot; 53 import org.graalvm.compiler.lir.framemap.FrameMap; 54 import org.graalvm.compiler.lir.framemap.FrameMapBuilderTool; 55 import org.graalvm.compiler.lir.framemap.SimpleVirtualStackSlot; 56 import org.graalvm.compiler.lir.framemap.VirtualStackSlotRange; 57 import org.graalvm.compiler.lir.gen.LIRGenerationResult; 58 import org.graalvm.compiler.lir.phases.AllocationPhase; 59 import org.graalvm.compiler.options.NestedBooleanOptionKey; 60 import org.graalvm.compiler.options.Option; 61 import org.graalvm.compiler.options.OptionType; 62 63 import jdk.vm.ci.code.CodeUtil; 64 import jdk.vm.ci.code.StackSlot; 65 import jdk.vm.ci.code.TargetDescription; 66 import jdk.vm.ci.meta.Value; 67 import jdk.vm.ci.meta.ValueKind; 68 69 /** 70 * Linear Scan {@link StackSlotAllocatorUtil stack slot allocator}. 71 * <p> 72 * <b>Remark:</b> The analysis works under the assumption that a stack slot is no longer live after 73 * its last usage. If an {@link LIRInstruction instruction} transfers the raw address of the stack 74 * slot to another location, e.g. a registers, and this location is referenced later on, the 75 * {@link org.graalvm.compiler.lir.LIRInstruction.Use usage} of the stack slot must be marked with 76 * the {@link OperandFlag#UNINITIALIZED}. Otherwise the stack slot might be reused and its content 77 * destroyed. 78 */ 79 public final class LSStackSlotAllocator extends AllocationPhase { 80 81 public static class Options { 82 // @formatter:off 83 @Option(help = "Use linear scan stack slot allocation.", type = OptionType.Debug) 84 public static final NestedBooleanOptionKey LIROptLSStackSlotAllocator = new NestedBooleanOptionKey(LIROptimization, true); 85 // @formatter:on 86 } 87 88 private static final TimerKey MainTimer = DebugContext.timer("LSStackSlotAllocator"); 89 private static final TimerKey NumInstTimer = DebugContext.timer("LSStackSlotAllocator[NumberInstruction]"); 90 private static final TimerKey BuildIntervalsTimer = DebugContext.timer("LSStackSlotAllocator[BuildIntervals]"); 91 private static final TimerKey VerifyIntervalsTimer = DebugContext.timer("LSStackSlotAllocator[VerifyIntervals]"); 92 private static final TimerKey AllocateSlotsTimer = DebugContext.timer("LSStackSlotAllocator[AllocateSlots]"); 93 private static final TimerKey AssignSlotsTimer = DebugContext.timer("LSStackSlotAllocator[AssignSlots]"); 94 95 @Override 96 protected void run(TargetDescription target, LIRGenerationResult lirGenRes, AllocationContext context) { 97 allocateStackSlots((FrameMapBuilderTool) lirGenRes.getFrameMapBuilder(), lirGenRes); 98 lirGenRes.buildFrameMap(); 99 } 100 101 @SuppressWarnings("try") 102 public static void allocateStackSlots(FrameMapBuilderTool builder, LIRGenerationResult res) { 103 if (builder.getNumberOfStackSlots() > 0) { 104 try (DebugCloseable t = MainTimer.start(res.getLIR().getDebug())) { 105 new Allocator(res.getLIR(), builder).allocate(); 106 } 107 } 108 } 109 110 private static final class Allocator { 111 112 private final LIR lir; 113 private final DebugContext debug; 114 private final FrameMapBuilderTool frameMapBuilder; 115 private final StackInterval[] stackSlotMap; 116 private final PriorityQueue<StackInterval> unhandled; 117 private final PriorityQueue<StackInterval> active; 118 private final AbstractBlockBase<?>[] sortedBlocks; 119 private final int maxOpId; 120 121 @SuppressWarnings("try") 122 private Allocator(LIR lir, FrameMapBuilderTool frameMapBuilder) { 123 this.lir = lir; 124 this.debug = lir.getDebug(); 125 this.frameMapBuilder = frameMapBuilder; 126 this.stackSlotMap = new StackInterval[frameMapBuilder.getNumberOfStackSlots()]; 127 this.sortedBlocks = lir.getControlFlowGraph().getBlocks(); 128 129 // insert by from 130 this.unhandled = new PriorityQueue<>((a, b) -> a.from() - b.from()); 131 // insert by to 132 this.active = new PriorityQueue<>((a, b) -> a.to() - b.to()); 133 134 try (DebugCloseable t = NumInstTimer.start(debug)) { 135 // step 1: number instructions 136 this.maxOpId = numberInstructions(lir, sortedBlocks); 137 } 138 } 139 140 @SuppressWarnings("try") 141 private void allocate() { 142 debug.dump(DebugContext.VERBOSE_LEVEL, lir, "After StackSlot numbering"); 143 144 boolean allocationFramesizeEnabled = StackSlotAllocatorUtil.allocatedFramesize.isEnabled(debug); 145 long currentFrameSize = allocationFramesizeEnabled ? frameMapBuilder.getFrameMap().currentFrameSize() : 0; 146 EconomicSet<LIRInstruction> usePos; 147 // step 2: build intervals 148 try (DebugContext.Scope s = debug.scope("StackSlotAllocationBuildIntervals"); Indent indent = debug.logAndIndent("BuildIntervals"); DebugCloseable t = BuildIntervalsTimer.start(debug)) { 149 usePos = buildIntervals(); 150 } 151 // step 3: verify intervals 152 if (debug.areScopesEnabled()) { 153 try (DebugCloseable t = VerifyIntervalsTimer.start(debug)) { 154 assert verifyIntervals(); 155 } 156 } 157 if (debug.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) { 158 dumpIntervals("Before stack slot allocation"); 159 } 160 // step 4: allocate stack slots 161 try (DebugCloseable t = AllocateSlotsTimer.start(debug)) { 162 /* 163 * Allocate primitive spill slots before reference spill slots. This ensures a 164 * ReferenceMap will be as compact as possible and only exceed the encoding limit of 165 * a stack offset if there are really too many objects live on the stack at an 166 * instruction with a ReferenceMap (as opposed to the method simply having a very 167 * large frame). 168 */ 169 allocateStackSlots(IS_PRIMITIVE_INTERVAL); 170 allocateStackSlots(IS_REFERENCE_INTERVAL); 171 } 172 if (debug.isDumpEnabled(DebugContext.VERBOSE_LEVEL)) { 173 dumpIntervals("After stack slot allocation"); 174 } 175 176 // step 5: assign stack slots 177 try (DebugCloseable t = AssignSlotsTimer.start(debug)) { 178 assignStackSlots(usePos); 179 } 180 if (allocationFramesizeEnabled) { 181 StackSlotAllocatorUtil.allocatedFramesize.add(debug, frameMapBuilder.getFrameMap().currentFrameSize() - currentFrameSize); 182 } 183 } 184 185 // ==================== 186 // step 1: number instructions 187 // ==================== 188 189 /** 190 * Numbers all instructions in all blocks. 191 * 192 * @return The id of the last operation. 193 */ 194 private static int numberInstructions(LIR lir, AbstractBlockBase<?>[] sortedBlocks) { 195 int opId = 0; 196 int index = 0; 197 for (AbstractBlockBase<?> block : sortedBlocks) { 198 199 ArrayList<LIRInstruction> instructions = lir.getLIRforBlock(block); 200 201 int numInst = instructions.size(); 202 for (int j = 0; j < numInst; j++) { 203 LIRInstruction op = instructions.get(j); 204 op.setId(opId); 205 206 index++; 207 opId += 2; // numbering of lirOps by two 208 } 209 } 210 assert (index << 1) == opId : "must match: " + (index << 1); 211 return opId - 2; 212 } 213 214 // ==================== 215 // step 2: build intervals 216 // ==================== 217 218 private EconomicSet<LIRInstruction> buildIntervals() { 219 return new FixPointIntervalBuilder(lir, stackSlotMap, maxOpId()).build(); 220 } 221 222 // ==================== 223 // step 3: verify intervals 224 // ==================== 225 226 private boolean verifyIntervals() { 227 for (StackInterval interval : stackSlotMap) { 228 if (interval != null) { 229 assert interval.verify(maxOpId()); 230 } 231 } 232 return true; 233 } 234 235 // ==================== 236 // step 4: allocate stack slots 237 // ==================== 238 239 @SuppressWarnings("try") 240 private void allocateStackSlots(Predicate<StackInterval> predicate) { 241 for (StackInterval interval : stackSlotMap) { 242 if (interval != null && (predicate == null || predicate.test(interval))) { 243 unhandled.add(interval); 244 } 245 } 246 for (StackInterval current = activateNext(); current != null; current = activateNext()) { 247 try (Indent indent = debug.logAndIndent("allocate %s", current)) { 248 allocateSlot(current); 249 } 250 } 251 252 // Cannot re-use free slots between rounds of slot allocation 253 freeSlots = null; 254 active.clear(); 255 } 256 257 private static final Predicate<StackInterval> IS_REFERENCE_INTERVAL = new Predicate<StackInterval>() { 258 @Override 259 public boolean test(StackInterval interval) { 260 return !((LIRKind) interval.kind()).isValue(); 261 } 262 }; 263 264 private static final Predicate<StackInterval> IS_PRIMITIVE_INTERVAL = new Predicate<StackInterval>() { 265 @Override 266 public boolean test(StackInterval interval) { 267 return ((LIRKind) interval.kind()).isValue(); 268 } 269 }; 270 271 private void allocateSlot(StackInterval current) { 272 VirtualStackSlot virtualSlot = current.getOperand(); 273 final StackSlot location; 274 if (virtualSlot instanceof VirtualStackSlotRange) { 275 // No reuse of ranges (yet). 276 VirtualStackSlotRange slotRange = (VirtualStackSlotRange) virtualSlot; 277 location = frameMapBuilder.getFrameMap().allocateStackSlots(slotRange.getSlots()); 278 StackSlotAllocatorUtil.virtualFramesize.add(debug, frameMapBuilder.getFrameMap().spillSlotRangeSize(slotRange.getSlots())); 279 StackSlotAllocatorUtil.allocatedSlots.increment(debug); 280 } else { 281 assert virtualSlot instanceof SimpleVirtualStackSlot : "Unexpected VirtualStackSlot type: " + virtualSlot; 282 StackSlot slot = findFreeSlot((SimpleVirtualStackSlot) virtualSlot); 283 if (slot != null) { 284 /* 285 * Free stack slot available. Note that we create a new one because the kind 286 * might not match. 287 */ 288 location = StackSlot.get(current.kind(), slot.getRawOffset(), slot.getRawAddFrameSize()); 289 StackSlotAllocatorUtil.reusedSlots.increment(debug); 290 debug.log(BASIC_LEVEL, "Reuse stack slot %s (reallocated from %s) for virtual stack slot %s", location, slot, virtualSlot); 291 } else { 292 // Allocate new stack slot. 293 location = frameMapBuilder.getFrameMap().allocateSpillSlot(virtualSlot.getValueKind()); 294 StackSlotAllocatorUtil.virtualFramesize.add(debug, frameMapBuilder.getFrameMap().spillSlotSize(virtualSlot.getValueKind())); 295 StackSlotAllocatorUtil.allocatedSlots.increment(debug); 296 debug.log(BASIC_LEVEL, "New stack slot %s for virtual stack slot %s", location, virtualSlot); 297 } 298 } 299 debug.log("Allocate location %s for interval %s", location, current); 300 current.setLocation(location); 301 } 302 303 /** 304 * Map from log2 of {@link FrameMap#spillSlotSize(ValueKind) a spill slot size} to a list of 305 * free stack slots. 306 */ 307 private ArrayList<Deque<StackSlot>> freeSlots; 308 309 /** 310 * @return The list of free stack slots for {@code index} or {@code null} if there is none. 311 */ 312 private Deque<StackSlot> getNullOrFreeSlots(int index) { 313 if (freeSlots == null) { 314 return null; 315 } 316 if (index < freeSlots.size()) { 317 return freeSlots.get(index); 318 } 319 return null; 320 } 321 322 /** 323 * @return the list of free stack slots for {@code index}. If there is none a list is 324 * created. 325 */ 326 private Deque<StackSlot> getOrInitFreeSlots(int index) { 327 Deque<StackSlot> freeList = null; 328 if (freeSlots == null) { 329 freeSlots = new ArrayList<>(6); 330 } else if (index < freeSlots.size()) { 331 freeList = freeSlots.get(index); 332 } 333 if (freeList == null) { 334 int requiredSize = index + 1; 335 for (int i = freeSlots.size(); i < requiredSize; i++) { 336 freeSlots.add(null); 337 } 338 freeList = new ArrayDeque<>(); 339 freeSlots.set(index, freeList); 340 } 341 return freeList; 342 } 343 344 /** 345 * Gets a free stack slot for {@code slot} or {@code null} if there is none. 346 */ 347 private StackSlot findFreeSlot(SimpleVirtualStackSlot slot) { 348 assert slot != null; 349 int size = log2SpillSlotSize(slot.getValueKind()); 350 Deque<StackSlot> freeList = getNullOrFreeSlots(size); 351 if (freeList == null) { 352 return null; 353 } 354 return freeList.pollLast(); 355 } 356 357 /** 358 * Adds a stack slot to the list of free slots. 359 */ 360 private void freeSlot(StackSlot slot) { 361 int size = log2SpillSlotSize(slot.getValueKind()); 362 getOrInitFreeSlots(size).addLast(slot); 363 } 364 365 private int log2SpillSlotSize(ValueKind<?> kind) { 366 int size = frameMapBuilder.getFrameMap().spillSlotSize(kind); 367 assert CodeUtil.isPowerOf2(size); 368 return CodeUtil.log2(size); 369 } 370 371 /** 372 * Gets the next unhandled interval and finishes handled intervals. 373 */ 374 private StackInterval activateNext() { 375 if (unhandled.isEmpty()) { 376 return null; 377 } 378 StackInterval next = unhandled.poll(); 379 // finish handled intervals 380 for (int id = next.from(); activePeekId() < id;) { 381 finished(active.poll()); 382 } 383 debug.log("active %s", next); 384 active.add(next); 385 return next; 386 } 387 388 /** 389 * Gets the lowest {@link StackInterval#to() end position} of all active intervals. If there 390 * is none {@link Integer#MAX_VALUE} is returned. 391 */ 392 private int activePeekId() { 393 StackInterval first = active.peek(); 394 if (first == null) { 395 return Integer.MAX_VALUE; 396 } 397 return first.to(); 398 } 399 400 /** 401 * Finishes {@code interval} by adding its location to the list of free stack slots. 402 */ 403 private void finished(StackInterval interval) { 404 StackSlot location = interval.location(); 405 debug.log("finished %s (freeing %s)", interval, location); 406 freeSlot(location); 407 } 408 409 // ==================== 410 // step 5: assign stack slots 411 // ==================== 412 413 private void assignStackSlots(EconomicSet<LIRInstruction> usePos) { 414 for (LIRInstruction op : usePos) { 415 op.forEachInput(assignSlot); 416 op.forEachAlive(assignSlot); 417 op.forEachState(assignSlot); 418 419 op.forEachTemp(assignSlot); 420 op.forEachOutput(assignSlot); 421 } 422 } 423 424 ValueProcedure assignSlot = new ValueProcedure() { 425 @Override 426 public Value doValue(Value value, OperandMode mode, EnumSet<OperandFlag> flags) { 427 if (isVirtualStackSlot(value)) { 428 VirtualStackSlot slot = asVirtualStackSlot(value); 429 StackInterval interval = get(slot); 430 assert interval != null; 431 return interval.location(); 432 } 433 return value; 434 } 435 }; 436 437 // ==================== 438 // 439 // ==================== 440 441 /** 442 * Gets the highest instruction id. 443 */ 444 private int maxOpId() { 445 return maxOpId; 446 } 447 448 private StackInterval get(VirtualStackSlot stackSlot) { 449 return stackSlotMap[stackSlot.getId()]; 450 } 451 452 private void dumpIntervals(String label) { 453 debug.dump(DebugContext.VERBOSE_LEVEL, new StackIntervalDumper(Arrays.copyOf(stackSlotMap, stackSlotMap.length)), label); 454 } 455 456 } 457 }