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