1 /*
   2  * Copyright (c) 2009, 2015, 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.alloc.trace.lsra;
  24 
  25 import static org.graalvm.compiler.lir.LIRValueUtil.asVirtualStackSlot;
  26 import static org.graalvm.compiler.lir.LIRValueUtil.isStackSlotValue;
  27 import static org.graalvm.compiler.lir.LIRValueUtil.isVirtualStackSlot;
  28 import static jdk.vm.ci.code.ValueUtil.asRegister;
  29 import static jdk.vm.ci.code.ValueUtil.asStackSlot;
  30 import static jdk.vm.ci.code.ValueUtil.isIllegal;
  31 import static jdk.vm.ci.code.ValueUtil.isRegister;
  32 import static jdk.vm.ci.code.ValueUtil.isStackSlot;
  33 
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.HashSet;
  37 import java.util.List;
  38 
  39 import org.graalvm.compiler.core.common.LIRKind;
  40 import org.graalvm.compiler.debug.Debug;
  41 import org.graalvm.compiler.debug.DebugCounter;
  42 import org.graalvm.compiler.debug.GraalError;
  43 import org.graalvm.compiler.debug.Indent;
  44 import org.graalvm.compiler.lir.LIRInsertionBuffer;
  45 import org.graalvm.compiler.lir.LIRInstruction;
  46 import org.graalvm.compiler.lir.VirtualStackSlot;
  47 import org.graalvm.compiler.lir.alloc.trace.lsra.TraceLinearScanPhase.TraceLinearScan;
  48 import org.graalvm.compiler.lir.framemap.FrameMap;
  49 import org.graalvm.compiler.lir.framemap.FrameMapBuilderTool;
  50 
  51 import jdk.vm.ci.code.StackSlot;
  52 import jdk.vm.ci.meta.AllocatableValue;
  53 import jdk.vm.ci.meta.Constant;
  54 import jdk.vm.ci.meta.JavaConstant;
  55 import jdk.vm.ci.meta.Value;
  56 
  57 /**
  58  */
  59 final class TraceLocalMoveResolver {
  60 
  61     private static final DebugCounter cycleBreakingSlotsAllocated = Debug.counter("TraceRA[cycleBreakingSlotsAllocated(local)]");
  62 
  63     private static final int STACK_SLOT_IN_CALLER_FRAME_IDX = -1;
  64     private final TraceLinearScan allocator;
  65 
  66     private int insertIdx;
  67     private LIRInsertionBuffer insertionBuffer; // buffer where moves are inserted
  68 
  69     private final List<TraceInterval> mappingFrom;
  70     private final List<Constant> mappingFromOpr;
  71     private final List<TraceInterval> mappingTo;
  72     private final int[] registerBlocked;
  73 
  74     private int[] stackBlocked;
  75     private final int firstVirtualStackIndex;
  76 
  77     private int getStackArrayIndex(Value stackSlotValue) {
  78         if (isStackSlot(stackSlotValue)) {
  79             return getStackArrayIndex(asStackSlot(stackSlotValue));
  80         }
  81         if (isVirtualStackSlot(stackSlotValue)) {
  82             return getStackArrayIndex(asVirtualStackSlot(stackSlotValue));
  83         }
  84         throw GraalError.shouldNotReachHere("value is not a stack slot: " + stackSlotValue);
  85     }
  86 
  87     private int getStackArrayIndex(StackSlot stackSlot) {
  88         int stackIdx;
  89         if (stackSlot.isInCallerFrame()) {
  90             // incoming stack arguments can be ignored
  91             stackIdx = STACK_SLOT_IN_CALLER_FRAME_IDX;
  92         } else {
  93             assert stackSlot.getRawAddFrameSize() : "Unexpected stack slot: " + stackSlot;
  94             int offset = -stackSlot.getRawOffset();
  95             assert 0 <= offset && offset < firstVirtualStackIndex : String.format("Wrong stack slot offset: %d (first virtual stack slot index: %d", offset, firstVirtualStackIndex);
  96             stackIdx = offset;
  97         }
  98         return stackIdx;
  99     }
 100 
 101     private int getStackArrayIndex(VirtualStackSlot virtualStackSlot) {
 102         return firstVirtualStackIndex + virtualStackSlot.getId();
 103     }
 104 
 105     protected void setValueBlocked(Value location, int direction) {
 106         assert direction == 1 || direction == -1 : "out of bounds";
 107         if (isStackSlotValue(location)) {
 108             int stackIdx = getStackArrayIndex(location);
 109             if (stackIdx == STACK_SLOT_IN_CALLER_FRAME_IDX) {
 110                 // incoming stack arguments can be ignored
 111                 return;
 112             }
 113             if (stackIdx >= stackBlocked.length) {
 114                 stackBlocked = Arrays.copyOf(stackBlocked, stackIdx + 1);
 115             }
 116             stackBlocked[stackIdx] += direction;
 117         } else {
 118             assert direction == 1 || direction == -1 : "out of bounds";
 119             if (isRegister(location)) {
 120                 registerBlocked[asRegister(location).number] += direction;
 121             } else {
 122                 throw GraalError.shouldNotReachHere("unhandled value " + location);
 123             }
 124         }
 125     }
 126 
 127     protected TraceInterval getMappingFrom(int i) {
 128         return mappingFrom.get(i);
 129     }
 130 
 131     protected int mappingFromSize() {
 132         return mappingFrom.size();
 133     }
 134 
 135     protected int valueBlocked(Value location) {
 136         if (isStackSlotValue(location)) {
 137             int stackIdx = getStackArrayIndex(location);
 138             if (stackIdx == STACK_SLOT_IN_CALLER_FRAME_IDX) {
 139                 // incoming stack arguments are always blocked (aka they can not be written)
 140                 return 1;
 141             }
 142             if (stackIdx >= stackBlocked.length) {
 143                 return 0;
 144             }
 145             return stackBlocked[stackIdx];
 146         }
 147         if (isRegister(location)) {
 148             return registerBlocked[asRegister(location).number];
 149         }
 150         throw GraalError.shouldNotReachHere("unhandled value " + location);
 151     }
 152 
 153     /*
 154      * TODO (je) remove?
 155      */
 156     protected static boolean areMultipleReadsAllowed() {
 157         return true;
 158     }
 159 
 160     boolean hasMappings() {
 161         return mappingFrom.size() > 0;
 162     }
 163 
 164     protected TraceLinearScan getAllocator() {
 165         return allocator;
 166     }
 167 
 168     protected TraceLocalMoveResolver(TraceLinearScan allocator) {
 169 
 170         this.allocator = allocator;
 171         this.mappingFrom = new ArrayList<>(8);
 172         this.mappingFromOpr = new ArrayList<>(8);
 173         this.mappingTo = new ArrayList<>(8);
 174         this.insertIdx = -1;
 175         this.insertionBuffer = new LIRInsertionBuffer();
 176         this.registerBlocked = new int[allocator.getRegisters().size()];
 177         FrameMapBuilderTool frameMapBuilderTool = (FrameMapBuilderTool) allocator.getFrameMapBuilder();
 178         FrameMap frameMap = frameMapBuilderTool.getFrameMap();
 179         this.stackBlocked = new int[frameMapBuilderTool.getNumberOfStackSlots()];
 180         this.firstVirtualStackIndex = !frameMap.frameNeedsAllocating() ? 0 : frameMap.currentFrameSize() + 1;
 181     }
 182 
 183     protected boolean checkEmpty() {
 184         assert mappingFrom.size() == 0 && mappingFromOpr.size() == 0 && mappingTo.size() == 0 : "list must be empty before and after processing";
 185         for (int i = 0; i < stackBlocked.length; i++) {
 186             assert stackBlocked[i] == 0 : "stack map must be empty before and after processing";
 187         }
 188         for (int i = 0; i < getAllocator().getRegisters().size(); i++) {
 189             assert registerBlocked[i] == 0 : "register map must be empty before and after processing";
 190         }
 191         checkMultipleReads();
 192         return true;
 193     }
 194 
 195     protected void checkMultipleReads() {
 196         // multiple reads are allowed in SSA LSRA
 197     }
 198 
 199     private boolean verifyBeforeResolve() {
 200         assert mappingFrom.size() == mappingFromOpr.size() : "length must be equal";
 201         assert mappingFrom.size() == mappingTo.size() : "length must be equal";
 202         assert insertIdx != -1 : "insert position not set";
 203 
 204         int i;
 205         int j;
 206         if (!areMultipleReadsAllowed()) {
 207             for (i = 0; i < mappingFrom.size(); i++) {
 208                 for (j = i + 1; j < mappingFrom.size(); j++) {
 209                     assert mappingFrom.get(i) == null || mappingFrom.get(i) != mappingFrom.get(j) : "cannot read from same interval twice";
 210                 }
 211             }
 212         }
 213 
 214         for (i = 0; i < mappingTo.size(); i++) {
 215             for (j = i + 1; j < mappingTo.size(); j++) {
 216                 assert mappingTo.get(i) != mappingTo.get(j) : "cannot write to same interval twice";
 217             }
 218         }
 219 
 220         HashSet<Value> usedRegs = new HashSet<>();
 221         if (!areMultipleReadsAllowed()) {
 222             for (i = 0; i < mappingFrom.size(); i++) {
 223                 TraceInterval interval = mappingFrom.get(i);
 224                 if (interval != null && !isIllegal(interval.location())) {
 225                     boolean unique = usedRegs.add(interval.location());
 226                     assert unique : "cannot read from same register twice";
 227                 }
 228             }
 229         }
 230 
 231         usedRegs.clear();
 232         for (i = 0; i < mappingTo.size(); i++) {
 233             TraceInterval interval = mappingTo.get(i);
 234             if (isIllegal(interval.location())) {
 235                 // After insertion the location may become illegal, so don't check it since multiple
 236                 // intervals might be illegal.
 237                 continue;
 238             }
 239             boolean unique = usedRegs.add(interval.location());
 240             assert unique : "cannot write to same register twice";
 241         }
 242 
 243         verifyStackSlotMapping();
 244 
 245         return true;
 246     }
 247 
 248     protected void verifyStackSlotMapping() {
 249         // relax disjoint stack maps invariant
 250     }
 251 
 252     // mark assignedReg and assignedRegHi of the interval as blocked
 253     private void blockRegisters(TraceInterval interval) {
 254         Value location = interval.location();
 255         if (mightBeBlocked(location)) {
 256             assert areMultipleReadsAllowed() || valueBlocked(location) == 0 : "location already marked as used: " + location;
 257             int direction = 1;
 258             setValueBlocked(location, direction);
 259             Debug.log("block %s", location);
 260         }
 261     }
 262 
 263     // mark assignedReg and assignedRegHi of the interval as unblocked
 264     private void unblockRegisters(TraceInterval interval) {
 265         Value location = interval.location();
 266         if (mightBeBlocked(location)) {
 267             assert valueBlocked(location) > 0 : "location already marked as unused: " + location;
 268             setValueBlocked(location, -1);
 269             Debug.log("unblock %s", location);
 270         }
 271     }
 272 
 273     /**
 274      * Checks if the {@linkplain TraceInterval#location() location} of {@code to} is not blocked or
 275      * is only blocked by {@code from}.
 276      */
 277     private boolean safeToProcessMove(TraceInterval from, TraceInterval to) {
 278         Value fromReg = from != null ? from.location() : null;
 279 
 280         Value location = to.location();
 281         if (mightBeBlocked(location)) {
 282             if ((valueBlocked(location) > 1 || (valueBlocked(location) == 1 && !isMoveToSelf(fromReg, location)))) {
 283                 return false;
 284             }
 285         }
 286 
 287         return true;
 288     }
 289 
 290     protected static boolean isMoveToSelf(Value from, Value to) {
 291         assert to != null;
 292         if (to.equals(from)) {
 293             return true;
 294         }
 295         if (from != null && isRegister(from) && isRegister(to) && asRegister(from).equals(asRegister(to))) {
 296             assert LIRKind.verifyMoveKinds(to.getValueKind(), from.getValueKind()) : String.format("Same register but Kind mismatch %s <- %s", to, from);
 297             return true;
 298         }
 299         return false;
 300     }
 301 
 302     protected static boolean mightBeBlocked(Value location) {
 303         if (isRegister(location)) {
 304             return true;
 305         }
 306         if (isStackSlotValue(location)) {
 307             return true;
 308         }
 309         return false;
 310     }
 311 
 312     private void createInsertionBuffer(List<LIRInstruction> list) {
 313         assert !insertionBuffer.initialized() : "overwriting existing buffer";
 314         insertionBuffer.init(list);
 315     }
 316 
 317     private void appendInsertionBuffer() {
 318         if (insertionBuffer.initialized()) {
 319             insertionBuffer.finish();
 320         }
 321         assert !insertionBuffer.initialized() : "must be uninitialized now";
 322 
 323         insertIdx = -1;
 324     }
 325 
 326     private void insertMove(TraceInterval fromInterval, TraceInterval toInterval) {
 327         assert !fromInterval.operand.equals(toInterval.operand) : "from and to interval equal: " + fromInterval;
 328         assert LIRKind.verifyMoveKinds(toInterval.kind(), fromInterval.kind()) : "move between different types";
 329         assert insertIdx != -1 : "must setup insert position first";
 330 
 331         insertionBuffer.append(insertIdx, createMove(fromInterval.operand, toInterval.operand, fromInterval.location(), toInterval.location()));
 332 
 333         if (Debug.isLogEnabled()) {
 334             Debug.log("insert move from %s to %s at %d", fromInterval, toInterval, insertIdx);
 335         }
 336     }
 337 
 338     /**
 339      * @param fromOpr {@link TraceInterval#operand operand} of the {@code from} interval
 340      * @param toOpr {@link TraceInterval#operand operand} of the {@code to} interval
 341      * @param fromLocation {@link TraceInterval#location() location} of the {@code to} interval
 342      * @param toLocation {@link TraceInterval#location() location} of the {@code to} interval
 343      */
 344     protected LIRInstruction createMove(AllocatableValue fromOpr, AllocatableValue toOpr, AllocatableValue fromLocation, AllocatableValue toLocation) {
 345         if (isStackSlotValue(toLocation) && isStackSlotValue(fromLocation)) {
 346             return getAllocator().getSpillMoveFactory().createStackMove(toOpr, fromOpr);
 347         }
 348         return getAllocator().getSpillMoveFactory().createMove(toOpr, fromOpr);
 349     }
 350 
 351     private void insertMove(Constant fromOpr, TraceInterval toInterval) {
 352         assert insertIdx != -1 : "must setup insert position first";
 353 
 354         AllocatableValue toOpr = toInterval.operand;
 355         LIRInstruction move = getAllocator().getSpillMoveFactory().createLoad(toOpr, fromOpr);
 356         insertionBuffer.append(insertIdx, move);
 357 
 358         if (Debug.isLogEnabled()) {
 359             Debug.log("insert move from value %s to %s at %d", fromOpr, toInterval, insertIdx);
 360         }
 361     }
 362 
 363     @SuppressWarnings("try")
 364     private void resolveMappings() {
 365         try (Indent indent = Debug.logAndIndent("resolveMapping")) {
 366             assert verifyBeforeResolve();
 367             if (Debug.isLogEnabled()) {
 368                 printMapping();
 369             }
 370 
 371             // Block all registers that are used as input operands of a move.
 372             // When a register is blocked, no move to this register is emitted.
 373             // This is necessary for detecting cycles in moves.
 374             int i;
 375             for (i = mappingFrom.size() - 1; i >= 0; i--) {
 376                 TraceInterval fromInterval = mappingFrom.get(i);
 377                 if (fromInterval != null) {
 378                     blockRegisters(fromInterval);
 379                 }
 380             }
 381 
 382             ArrayList<AllocatableValue> busySpillSlots = null;
 383             while (mappingFrom.size() > 0) {
 384                 boolean processedInterval = false;
 385 
 386                 int spillCandidate = -1;
 387                 for (i = mappingFrom.size() - 1; i >= 0; i--) {
 388                     TraceInterval fromInterval = mappingFrom.get(i);
 389                     TraceInterval toInterval = mappingTo.get(i);
 390 
 391                     if (safeToProcessMove(fromInterval, toInterval)) {
 392                         // this interval can be processed because target is free
 393                         if (fromInterval != null) {
 394                             insertMove(fromInterval, toInterval);
 395                             unblockRegisters(fromInterval);
 396                         } else {
 397                             insertMove(mappingFromOpr.get(i), toInterval);
 398                         }
 399                         if (isStackSlotValue(toInterval.location())) {
 400                             if (busySpillSlots == null) {
 401                                 busySpillSlots = new ArrayList<>(2);
 402                             }
 403                             busySpillSlots.add(toInterval.location());
 404                         }
 405                         mappingFrom.remove(i);
 406                         mappingFromOpr.remove(i);
 407                         mappingTo.remove(i);
 408 
 409                         processedInterval = true;
 410                     } else if (fromInterval != null && isRegister(fromInterval.location()) && (busySpillSlots == null || !busySpillSlots.contains(fromInterval.spillSlot()))) {
 411                         // this interval cannot be processed now because target is not free
 412                         // it starts in a register, so it is a possible candidate for spilling
 413                         spillCandidate = i;
 414                     }
 415                 }
 416 
 417                 if (!processedInterval) {
 418                     breakCycle(spillCandidate);
 419                 }
 420             }
 421         }
 422 
 423         // check that all intervals have been processed
 424         assert checkEmpty();
 425     }
 426 
 427     protected void breakCycle(int spillCandidate) {
 428         if (spillCandidate != -1) {
 429             // no move could be processed because there is a cycle in the move list
 430             // (e.g. r1 . r2, r2 . r1), so one interval must be spilled to memory
 431             assert spillCandidate != -1 : "no interval in register for spilling found";
 432 
 433             // create a new spill interval and assign a stack slot to it
 434             TraceInterval fromInterval1 = mappingFrom.get(spillCandidate);
 435             // do not allocate a new spill slot for temporary interval, but
 436             // use spill slot assigned to fromInterval. Otherwise moves from
 437             // one stack slot to another can happen (not allowed by LIRAssembler
 438             AllocatableValue spillSlot1 = fromInterval1.spillSlot();
 439             if (spillSlot1 == null) {
 440                 spillSlot1 = getAllocator().getFrameMapBuilder().allocateSpillSlot(fromInterval1.kind());
 441                 fromInterval1.setSpillSlot(spillSlot1);
 442                 cycleBreakingSlotsAllocated.increment();
 443             }
 444             spillInterval(spillCandidate, fromInterval1, spillSlot1);
 445             return;
 446         }
 447         assert mappingFromSize() > 1;
 448         // Arbitrarily select the first entry for spilling.
 449         int stackSpillCandidate = 0;
 450         TraceInterval fromInterval = getMappingFrom(stackSpillCandidate);
 451         // allocate new stack slot
 452         VirtualStackSlot spillSlot = getAllocator().getFrameMapBuilder().allocateSpillSlot(fromInterval.kind());
 453         spillInterval(stackSpillCandidate, fromInterval, spillSlot);
 454     }
 455 
 456     protected void spillInterval(int spillCandidate, TraceInterval fromInterval, AllocatableValue spillSlot) {
 457         assert mappingFrom.get(spillCandidate).equals(fromInterval);
 458         TraceInterval spillInterval = getAllocator().createDerivedInterval(fromInterval);
 459 
 460         // add a dummy range because real position is difficult to calculate
 461         // Note: this range is a special case when the integrity of the allocation is
 462         // checked
 463         spillInterval.addRange(1, 2);
 464 
 465         spillInterval.assignLocation(spillSlot);
 466 
 467         if (Debug.isLogEnabled()) {
 468             Debug.log("created new Interval for spilling: %s", spillInterval);
 469         }
 470         blockRegisters(spillInterval);
 471 
 472         // insert a move from register to stack and update the mapping
 473         insertMove(fromInterval, spillInterval);
 474         mappingFrom.set(spillCandidate, spillInterval);
 475         unblockRegisters(fromInterval);
 476     }
 477 
 478     @SuppressWarnings("try")
 479     private void printMapping() {
 480         try (Indent indent = Debug.logAndIndent("Mapping")) {
 481             for (int i = mappingFrom.size() - 1; i >= 0; i--) {
 482                 TraceInterval fromInterval = mappingFrom.get(i);
 483                 TraceInterval toInterval = mappingTo.get(i);
 484                 String from;
 485                 Value to = toInterval.location();
 486                 if (fromInterval == null) {
 487                     from = mappingFromOpr.get(i).toString();
 488                 } else {
 489                     from = fromInterval.location().toString();
 490                 }
 491                 Debug.log("move %s <- %s", from, to);
 492             }
 493         }
 494     }
 495 
 496     void setInsertPosition(List<LIRInstruction> insertList, int insertIdx) {
 497         assert this.insertIdx == -1 : "use moveInsertPosition instead of setInsertPosition when data already set";
 498 
 499         createInsertionBuffer(insertList);
 500         this.insertIdx = insertIdx;
 501     }
 502 
 503     void moveInsertPosition(List<LIRInstruction> newInsertList, int newInsertIdx) {
 504         if (insertionBuffer.lirList() != null && (insertionBuffer.lirList() != newInsertList || this.insertIdx != newInsertIdx)) {
 505             // insert position changed . resolve current mappings
 506             resolveMappings();
 507         }
 508 
 509         assert insertionBuffer.lirList() != newInsertList || newInsertIdx >= insertIdx : String.format("Decreasing insert index: old=%d new=%d", insertIdx, newInsertIdx);
 510 
 511         if (insertionBuffer.lirList() != newInsertList) {
 512             // block changed . append insertionBuffer because it is
 513             // bound to a specific block and create a new insertionBuffer
 514             appendInsertionBuffer();
 515             createInsertionBuffer(newInsertList);
 516         }
 517 
 518         this.insertIdx = newInsertIdx;
 519     }
 520 
 521     public void addMapping(TraceInterval fromInterval, TraceInterval toInterval) {
 522 
 523         if (isIllegal(toInterval.location()) && toInterval.canMaterialize()) {
 524             if (Debug.isLogEnabled()) {
 525                 Debug.log("no store to rematerializable interval %s needed", toInterval);
 526             }
 527             return;
 528         }
 529         if (isIllegal(fromInterval.location()) && fromInterval.canMaterialize()) {
 530             // Instead of a reload, re-materialize the value
 531             JavaConstant rematValue = fromInterval.getMaterializedValue();
 532             addMapping(rematValue, toInterval);
 533             return;
 534         }
 535         if (Debug.isLogEnabled()) {
 536             Debug.log("add move mapping from %s to %s", fromInterval, toInterval);
 537         }
 538 
 539         assert !fromInterval.operand.equals(toInterval.operand) : "from and to interval equal: " + fromInterval;
 540         assert LIRKind.verifyMoveKinds(toInterval.kind(), fromInterval.kind()) : String.format("Kind mismatch: %s vs. %s, from=%s, to=%s", fromInterval.kind(), toInterval.kind(), fromInterval,
 541                         toInterval);
 542         mappingFrom.add(fromInterval);
 543         mappingFromOpr.add(null);
 544         mappingTo.add(toInterval);
 545     }
 546 
 547     public void addMapping(Constant fromOpr, TraceInterval toInterval) {
 548         if (Debug.isLogEnabled()) {
 549             Debug.log("add move mapping from %s to %s", fromOpr, toInterval);
 550         }
 551 
 552         mappingFrom.add(null);
 553         mappingFromOpr.add(fromOpr);
 554         mappingTo.add(toInterval);
 555     }
 556 
 557     void resolveAndAppendMoves() {
 558         if (hasMappings()) {
 559             resolveMappings();
 560         }
 561         appendInsertionBuffer();
 562     }
 563 }