/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 | } |