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