Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/src/jit/MoveResolver.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
 * vim: set ts=8 sts=4 et sw=4 tw=99:
3
 * This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "jit/MoveResolver.h"
8
9
#include "mozilla/Attributes.h"
10
#include "mozilla/ScopeExit.h"
11
12
#include "jit/MacroAssembler.h"
13
#include "jit/RegisterSets.h"
14
15
using namespace js;
16
using namespace js::jit;
17
18
MoveOperand::MoveOperand(MacroAssembler& masm, const ABIArg& arg)
19
  : disp_(0)
20
2.78k
{
21
2.78k
    switch (arg.kind()) {
22
2.78k
      case ABIArg::GPR:
23
2.76k
        kind_ = REG;
24
2.76k
        code_ = arg.gpr().code();
25
2.76k
        break;
26
#ifdef JS_CODEGEN_REGISTER_PAIR
27
      case ABIArg::GPR_PAIR:
28
        kind_ = REG_PAIR;
29
        code_ = arg.evenGpr().code();
30
        MOZ_ASSERT(code_ % 2 == 0);
31
        MOZ_ASSERT(code_ + 1 == arg.oddGpr().code());
32
        break;
33
#endif
34
3
      case ABIArg::FPU:
35
3
        kind_ = FLOAT_REG;
36
3
        code_ = arg.fpu().code();
37
3
        break;
38
2.78k
      case ABIArg::Stack:
39
15
        kind_ = MEMORY;
40
15
        if (IsHiddenSP(masm.getStackPointer())) {
41
0
            MOZ_CRASH("Hidden SP cannot be represented as register code on this platform");
42
15
        } else {
43
15
            code_ = AsRegister(masm.getStackPointer()).code();
44
15
        }
45
15
        disp_ = arg.offsetFromArgBase();
46
15
        break;
47
15
      case ABIArg::Uninitialized:
48
0
        MOZ_CRASH("Uninitialized ABIArg kind");
49
2.78k
    }
50
2.78k
}
51
52
MoveResolver::MoveResolver()
53
  : numCycles_(0), curCycles_(0)
54
141
{
55
141
}
56
57
void
58
MoveResolver::resetState()
59
957
{
60
957
    numCycles_ = 0;
61
957
    curCycles_ = 0;
62
957
}
63
64
bool
65
MoveResolver::addMove(const MoveOperand& from, const MoveOperand& to, MoveOp::Type type)
66
2.23k
{
67
2.23k
    // Assert that we're not doing no-op moves.
68
2.23k
    MOZ_ASSERT(!(from == to));
69
2.23k
    PendingMove* pm = movePool_.allocate(from, to, type);
70
2.23k
    if (!pm) {
71
0
        return false;
72
0
    }
73
2.23k
    pending_.pushBack(pm);
74
2.23k
    return true;
75
2.23k
}
76
77
// Given move (A -> B), this function attempts to find any move (B -> *) in the
78
// pending move list, and returns the first one.
79
MoveResolver::PendingMove*
80
MoveResolver::findBlockingMove(const PendingMove* last)
81
2.23k
{
82
4.61k
    for (PendingMoveIterator iter = pending_.begin(); iter != pending_.end(); iter++) {
83
2.37k
        PendingMove* other = *iter;
84
2.37k
85
2.37k
        if (other->from().aliases(last->to())) {
86
7
            // We now have pairs in the form (A -> X) (X -> y). The second pair
87
7
            // blocks the move in the first pair, so return it.
88
7
            return other;
89
7
        }
90
2.37k
    }
91
2.23k
92
2.23k
    // No blocking moves found.
93
2.23k
    return nullptr;
94
2.23k
}
95
96
// Given move (A -> B), this function attempts to find any move (B -> *) in the
97
// move list iterator, and returns the first one.
98
// N.B. It is unclear if a single move can complete more than one cycle, so to be
99
// conservative, this function operates on iterators, so the caller can process all
100
// instructions that start a cycle.
101
MoveResolver::PendingMove*
102
MoveResolver::findCycledMove(PendingMoveIterator* iter, PendingMoveIterator end, const PendingMove* last)
103
7
{
104
14
    for (; *iter != end; (*iter)++) {
105
7
        PendingMove* other = **iter;
106
7
        if (other->from().aliases(last->to())) {
107
0
            // We now have pairs in the form (A -> X) (X -> y). The second pair
108
0
            // blocks the move in the first pair, so return it.
109
0
            (*iter)++;
110
0
            return other;
111
0
        }
112
7
    }
113
7
    // No blocking moves found.
114
7
    return nullptr;
115
7
}
116
117
#ifdef JS_CODEGEN_ARM
118
static inline bool
119
MoveIsDouble(const MoveOperand& move)
120
{
121
    if (!move.isFloatReg()) {
122
        return false;
123
    }
124
    return move.floatReg().isDouble();
125
}
126
#endif
127
128
#ifdef JS_CODEGEN_ARM
129
static inline bool
130
MoveIsSingle(const MoveOperand& move)
131
{
132
    if (!move.isFloatReg()) {
133
        return false;
134
    }
135
    return move.floatReg().isSingle();
136
}
137
#endif
138
139
#ifdef JS_CODEGEN_ARM
140
bool
141
MoveResolver::isDoubleAliasedAsSingle(const MoveOperand& move)
142
{
143
    if (!MoveIsDouble(move)) {
144
        return false;
145
    }
146
147
    for (auto iter = pending_.begin(); iter != pending_.end(); ++iter) {
148
        PendingMove* other = *iter;
149
        if (other->from().aliases(move) && MoveIsSingle(other->from())) {
150
            return true;
151
        }
152
        if (other->to().aliases(move) && MoveIsSingle(other->to())) {
153
            return true;
154
        }
155
    }
156
    return false;
157
}
158
#endif
159
160
#ifdef JS_CODEGEN_ARM
161
static MoveOperand
162
SplitIntoLowerHalf(const MoveOperand& move)
163
{
164
    if (MoveIsDouble(move)) {
165
        FloatRegister lowerSingle = move.floatReg().asSingle();
166
        return MoveOperand(lowerSingle);
167
    }
168
169
    MOZ_ASSERT(move.isMemoryOrEffectiveAddress());
170
    return move;
171
}
172
#endif
173
174
#ifdef JS_CODEGEN_ARM
175
static MoveOperand
176
SplitIntoUpperHalf(const MoveOperand& move)
177
{
178
    if (MoveIsDouble(move)) {
179
        FloatRegister lowerSingle = move.floatReg().asSingle();
180
        FloatRegister upperSingle = VFPRegister(lowerSingle.code() + 1, VFPRegister::Single);
181
        return MoveOperand(upperSingle);
182
    }
183
184
    MOZ_ASSERT(move.isMemoryOrEffectiveAddress());
185
    return MoveOperand(move.base(), move.disp() + sizeof(float));
186
}
187
#endif
188
189
// Resolves the pending_ list to a list in orderedMoves_.
190
bool
191
MoveResolver::resolve()
192
957
{
193
957
    resetState();
194
957
    orderedMoves_.clear();
195
957
196
957
    // Upon return from this function, the pending_ list must be cleared.
197
957
    auto clearPending = mozilla::MakeScopeExit([this]() {
198
957
        pending_.clear();
199
957
    });
200
957
201
#ifdef JS_CODEGEN_ARM
202
    // Some of ARM's double registers alias two of its single registers,
203
    // but the algorithm below assumes that every register can participate
204
    // in at most one cycle. To satisfy the algorithm, any double registers
205
    // that may conflict are split into their single-register halves.
206
    //
207
    // This logic is only applicable because ARM only uses registers d0-d15,
208
    // all of which alias s0-s31. Double registers d16-d31 are unused.
209
    // Therefore there is never a double move that cannot be split.
210
    // If this changes in the future, the algorithm will have to be fixed.
211
    for (auto iter = pending_.begin(); iter != pending_.end(); ++iter) {
212
        PendingMove* pm = *iter;
213
214
        if (isDoubleAliasedAsSingle(pm->from()) || isDoubleAliasedAsSingle(pm->to())) {
215
            MoveOperand fromLower = SplitIntoLowerHalf(pm->from());
216
            MoveOperand toLower = SplitIntoLowerHalf(pm->to());
217
218
            PendingMove* lower = movePool_.allocate(fromLower, toLower, MoveOp::FLOAT32);
219
            if (!lower) {
220
                return false;
221
            }
222
223
            // Insert the new node before the current position to not affect iteration.
224
            pending_.insertBefore(pm, lower);
225
226
            // Overwrite pm in place for the upper move. Iteration proceeds as normal.
227
            MoveOperand fromUpper = SplitIntoUpperHalf(pm->from());
228
            MoveOperand toUpper = SplitIntoUpperHalf(pm->to());
229
            pm->overwrite(fromUpper, toUpper, MoveOp::FLOAT32);
230
        }
231
    }
232
#endif
233
234
957
    InlineList<PendingMove> stack;
235
957
236
957
    // This is a depth-first-search without recursion, which tries to find
237
957
    // cycles in a list of moves.
238
957
    //
239
957
    // Algorithm.
240
957
    //
241
957
    // S = Traversal stack.
242
957
    // P = Pending move list.
243
957
    // O = Ordered list of moves.
244
957
    //
245
957
    // As long as there are pending moves in P:
246
957
    //      Let |root| be any pending move removed from P
247
957
    //      Add |root| to the traversal stack.
248
957
    //      As long as S is not empty:
249
957
    //          Let |L| be the most recent move added to S.
250
957
    //
251
957
    //          Find any pending move M whose source is L's destination, thus
252
957
    //          preventing L's move until M has completed.
253
957
    //
254
957
    //          If a move M was found,
255
957
    //              Remove M from the pending list.
256
957
    //              If M's destination is |root|,
257
957
    //                  Annotate M and |root| as cycles.
258
957
    //                  Add M to S.
259
957
    //                  do not Add M to O, since M may have other conflictors in P that have not yet been processed.
260
957
    //              Otherwise,
261
957
    //                  Add M to S.
262
957
    //         Otherwise,
263
957
    //              Remove L from S.
264
957
    //              Add L to O.
265
957
    //
266
3.18k
    while (!pending_.empty()) {
267
2.22k
        PendingMove* pm = pending_.popBack();
268
2.22k
269
2.22k
        // Add this pending move to the cycle detection stack.
270
2.22k
        stack.pushBack(pm);
271
2.22k
272
4.46k
        while (!stack.empty()) {
273
2.23k
            PendingMove* blocking = findBlockingMove(stack.peekBack());
274
2.23k
275
2.23k
            if (blocking) {
276
7
                PendingMoveIterator stackiter = stack.begin();
277
7
                PendingMove* cycled = findCycledMove(&stackiter, stack.end(), blocking);
278
7
                if (cycled) {
279
0
                    // Find the cycle's start.
280
0
                    // We annotate cycles at each move in the cycle, and
281
0
                    // assert that we do not find two cycles in one move chain
282
0
                    // traversal (which would indicate two moves to the same
283
0
                    // destination).
284
0
                    // Since there can be more than one cycle, find them all.
285
0
                    do {
286
0
                        cycled->setCycleEnd(curCycles_);
287
0
                        cycled = findCycledMove(&stackiter, stack.end(), blocking);
288
0
                    } while (cycled);
289
0
290
0
                    blocking->setCycleBegin(pm->type(), curCycles_);
291
0
                    curCycles_++;
292
0
                    pending_.remove(blocking);
293
0
                    stack.pushBack(blocking);
294
7
                } else {
295
7
                    // This is a new link in the move chain, so keep
296
7
                    // searching for a cycle.
297
7
                    pending_.remove(blocking);
298
7
                    stack.pushBack(blocking);
299
7
                }
300
2.23k
            } else {
301
2.23k
                // Otherwise, pop the last move on the search stack because it's
302
2.23k
                // complete and not participating in a cycle. The resulting
303
2.23k
                // move can safely be added to the ordered move list.
304
2.23k
                PendingMove* done = stack.popBack();
305
2.23k
                if (!addOrderedMove(*done)) {
306
0
                    return false;
307
0
                }
308
2.23k
                movePool_.free(done);
309
2.23k
            }
310
2.23k
        }
311
2.22k
        // If the current queue is empty, it is certain that there are
312
2.22k
        // all previous cycles cannot conflict with future cycles,
313
2.22k
        // so re-set the counter of pending cycles, while keeping a high-water mark.
314
2.22k
        if (numCycles_ < curCycles_) {
315
0
            numCycles_ = curCycles_;
316
0
        }
317
2.22k
        curCycles_ = 0;
318
2.22k
    }
319
957
320
957
    return true;
321
957
}
322
323
bool
324
MoveResolver::addOrderedMove(const MoveOp& move)
325
2.23k
{
326
2.23k
    // Sometimes the register allocator generates move groups where multiple
327
2.23k
    // moves have the same source. Try to optimize these cases when the source
328
2.23k
    // is in memory and the target of one of the moves is in a register.
329
2.23k
    MOZ_ASSERT(!move.from().aliases(move.to()));
330
2.23k
331
2.23k
    if (!move.from().isMemory() || move.isCycleBegin() || move.isCycleEnd()) {
332
1.56k
        return orderedMoves_.append(move);
333
1.56k
    }
334
669
335
669
    // Look for an earlier move with the same source, where no intervening move
336
669
    // touches either the source or destination of the new move.
337
1.58k
    for (int i = orderedMoves_.length() - 1; i >= 0; i--) {
338
917
        const MoveOp& existing = orderedMoves_[i];
339
917
340
917
        if (existing.from() == move.from() &&
341
917
            !existing.to().aliases(move.to()) &&
342
917
            existing.type() == move.type() &&
343
917
            !existing.isCycleBegin() &&
344
917
            !existing.isCycleEnd())
345
0
        {
346
0
            MoveOp* after = orderedMoves_.begin() + i + 1;
347
0
            if (existing.to().isGeneralReg() || existing.to().isFloatReg()) {
348
0
                MoveOp nmove(existing.to(), move.to(), move.type());
349
0
                return orderedMoves_.insert(after, nmove);
350
0
            } else if (move.to().isGeneralReg() || move.to().isFloatReg()) {
351
0
                MoveOp nmove(move.to(), existing.to(), move.type());
352
0
                orderedMoves_[i] = move;
353
0
                return orderedMoves_.insert(after, nmove);
354
0
            }
355
917
        }
356
917
357
917
        if (existing.aliases(move)) {
358
0
            break;
359
0
        }
360
917
    }
361
669
362
669
    return orderedMoves_.append(move);
363
669
}
364
365
void
366
MoveResolver::reorderMove(size_t from, size_t to)
367
0
{
368
0
    MOZ_ASSERT(from != to);
369
0
370
0
    MoveOp op = orderedMoves_[from];
371
0
    if (from < to) {
372
0
        for (size_t i = from; i < to; i++) {
373
0
            orderedMoves_[i] = orderedMoves_[i + 1];
374
0
        }
375
0
    } else {
376
0
        for (size_t i = from; i > to; i--) {
377
0
            orderedMoves_[i] = orderedMoves_[i - 1];
378
0
        }
379
0
    }
380
0
    orderedMoves_[to] = op;
381
0
}
382
383
void
384
MoveResolver::sortMemoryToMemoryMoves()
385
0
{
386
0
    // Try to reorder memory->memory moves so that they are executed right
387
0
    // before a move that clobbers some register. This will allow the move
388
0
    // emitter to use that clobbered register as a scratch register for the
389
0
    // memory->memory move, if necessary.
390
0
    for (size_t i = 0; i < orderedMoves_.length(); i++) {
391
0
        const MoveOp& base = orderedMoves_[i];
392
0
        if (!base.from().isMemory() || !base.to().isMemory()) {
393
0
            continue;
394
0
        }
395
0
        if (base.type() != MoveOp::GENERAL && base.type() != MoveOp::INT32) {
396
0
            continue;
397
0
        }
398
0
399
0
        // Look for an earlier move clobbering a register.
400
0
        bool found = false;
401
0
        for (int j = i - 1; j >= 0; j--) {
402
0
            const MoveOp& previous = orderedMoves_[j];
403
0
            if (previous.aliases(base) || previous.isCycleBegin() || previous.isCycleEnd()) {
404
0
                break;
405
0
            }
406
0
407
0
            if (previous.to().isGeneralReg()) {
408
0
                reorderMove(i, j);
409
0
                found = true;
410
0
                break;
411
0
            }
412
0
        }
413
0
        if (found) {
414
0
            continue;
415
0
        }
416
0
417
0
        // Look for a later move clobbering a register.
418
0
        if (i + 1 < orderedMoves_.length()) {
419
0
            bool found = false, skippedRegisterUse = false;
420
0
            for (size_t j = i + 1; j < orderedMoves_.length(); j++) {
421
0
                const MoveOp& later = orderedMoves_[j];
422
0
                if (later.aliases(base) || later.isCycleBegin() || later.isCycleEnd()) {
423
0
                    break;
424
0
                }
425
0
426
0
                if (later.to().isGeneralReg()) {
427
0
                    if (skippedRegisterUse) {
428
0
                        reorderMove(i, j);
429
0
                        found = true;
430
0
                    } else {
431
0
                        // There is no move that uses a register between the
432
0
                        // original memory->memory move and this move that
433
0
                        // clobbers a register. The move should already be able
434
0
                        // to use a scratch register, so don't shift anything
435
0
                        // around.
436
0
                    }
437
0
                    break;
438
0
                }
439
0
440
0
                if (later.from().isGeneralReg()) {
441
0
                    skippedRegisterUse = true;
442
0
                }
443
0
            }
444
0
445
0
            if (found) {
446
0
                // Redo the search for memory->memory moves at the current
447
0
                // index, so we don't skip the move just shifted back.
448
0
                i--;
449
0
            }
450
0
        }
451
0
    }
452
0
}