v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
gap-resolver.cc
Go to the documentation of this file.
1// Copyright 2014 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <algorithm>
8#include <set>
9
10#include "src/base/enum-set.h"
12
13namespace v8 {
14namespace internal {
15namespace compiler {
16
17namespace {
18
19enum MoveOperandKind : uint8_t { kConstant, kGpReg, kFpReg, kStack };
20
21MoveOperandKind GetKind(const InstructionOperand& move) {
22 if (move.IsConstant()) return kConstant;
23 LocationOperand loc_op = LocationOperand::cast(move);
24 if (loc_op.location_kind() != LocationOperand::REGISTER) return kStack;
25 return IsFloatingPoint(loc_op.representation()) ? kFpReg : kGpReg;
26}
27
28} // namespace
29
33
34 // Remove redundant moves, collect source kinds and destination kinds to
35 // detect simple non-overlapping moves, and collect FP move representations if
36 // aliasing is non-simple.
37 size_t nmoves = moves->size();
38 for (size_t i = 0; i < nmoves;) {
39 MoveOperands* move = (*moves)[i];
40 if (move->IsRedundant()) {
41 nmoves--;
42 if (i < nmoves) (*moves)[i] = (*moves)[nmoves];
43 continue;
44 }
45 i++;
46 source_kinds.Add(GetKind(move->source()));
47 destination_kinds.Add(GetKind(move->destination()));
48 }
49 if (nmoves != moves->size()) moves->resize(nmoves);
50
51 if ((source_kinds & destination_kinds).empty() || moves->size() < 2) {
52 // Fast path for non-conflicting parallel moves.
53 for (MoveOperands* move : *moves) {
54 assembler_->AssembleMove(&move->source(), &move->destination());
55 }
56 return;
57 }
58
59 for (size_t i = 0; i < moves->size(); ++i) {
60 auto move = (*moves)[i];
61 if (!move->IsEliminated()) PerformMove(moves, move);
62 }
64}
65
66// Check if a 2-move cycle is a swap. This is not always the case, for instance:
67//
68// [fp_stack:-3|s128] = [xmm5|R|s128]
69// [xmm5|R|s128] = [fp_stack:-4|s128]
70//
71// The two stack operands conflict but start at a different stack offset, so a
72// swap would be incorrect.
73// In general, swapping is allowed if the conflicting operands:
74// - Have the same representation, and
75// - Are the same register, or are stack slots with the same index
76bool IsSwap(MoveOperands* move1, MoveOperands* move2) {
77 return move1->source() == move2->destination() &&
78 move2->source() == move1->destination();
79}
80
81void GapResolver::PerformCycle(const std::vector<MoveOperands*>& cycle) {
82 DCHECK(!cycle.empty());
83 MoveOperands* move1 = cycle.back();
84 if (cycle.size() == 2 && IsSwap(cycle.front(), cycle.back())) {
85 // Call {AssembleSwap} which can generate better code than the generic
86 // algorithm below in some cases.
87 MoveOperands* move2 = cycle.front();
88 InstructionOperand* source = &move1->source();
90 // Ensure source is a register or both are stack slots, to limit swap
91 // cases.
92 if (source->IsAnyStackSlot()) {
93 std::swap(source, destination);
94 }
96 move1->Eliminate();
97 move2->Eliminate();
98 return;
99 }
100 // Generic move-cycle algorithm. The cycle of size n is ordered such that the
101 // move at index i % n blocks the move at index (i + 1) % n.
102 // - Move the source of the last move to a platform-specific temporary
103 // location.
104 // - Assemble the remaining moves from left to right. The first move was
105 // unblocked by the temporary location, and each move unblocks the next one.
106 // - Move the temporary location to the last move's destination, thereby
107 // completing the cycle.
108 // To ensure that the temporary location does not conflict with any scratch
109 // register used during the move cycle, the platform implements
110 // {SetPendingMove}, which marks the registers needed for the given moves.
111 // {MoveToTempLocation} will then choose the location accordingly.
114 for (size_t i = 0; i < cycle.size() - 1; ++i) {
115 assembler_->SetPendingMove(cycle[i]);
116 }
117 assembler_->MoveToTempLocation(&move1->source(), rep);
119 move1->Eliminate();
120 for (size_t i = 0; i < cycle.size() - 1; ++i) {
121 assembler_->AssembleMove(&cycle[i]->source(), &cycle[i]->destination());
122 cycle[i]->Eliminate();
123 }
125 // We do not need to update the sources of the remaining moves in the parallel
126 // move. If any of the remaining moves had the same source as one of the moves
127 // in the cycle, it would block the cycle and would have already been
128 // assembled by {PerformMoveHelper}.
129}
130
132 // Try to perform the move and its dependencies with {PerformMoveHelper}.
133 // This helper function will be able to solve most cases, including cycles.
134 // But for some rare cases, it will bail out and return one of the
135 // problematic moves. In this case, push the source to the stack to
136 // break the cycles that it belongs to, and try again.
137 std::vector<MoveOperands*> cycle;
138 while (MoveOperands* blocking_move = PerformMoveHelper(moves, move, &cycle)) {
139 // Push an arbitrary operand of the cycle to break it.
140 AllocatedOperand scratch = assembler_->Push(&blocking_move->source());
141 InstructionOperand source = blocking_move->source();
142 for (auto m : *moves) {
143 if (m->source() == source) {
144 m->set_source(scratch);
145 }
146 }
147 cycle.clear();
148 }
149}
150
152 ParallelMove* moves, MoveOperands* move,
153 std::vector<MoveOperands*>* cycle) {
154 // We interpret moves as nodes in a graph. x is a successor of y (x blocks y)
155 // if x.source() conflicts with y.destination(). We recursively assemble the
156 // moves in this graph in post-order using a DFS traversal, such that all
157 // blocking moves are assembled first.
158 // We also mark moves in the current DFS branch as pending. If a move is
159 // blocked by a pending move, this is a cycle. In this case we just
160 // reconstruct the cycle on the way back, and assemble it using {PerformCycle}
161 // when we reach the first move.
162 // This algorithm can only process one cycle at a time. If another cycle is
163 // found while the first one is still being processed, we bail out.
164 // The caller breaks the cycle using a temporary stack slot, and we try
165 // again.
166
167 DCHECK(!move->IsPending());
168 DCHECK(!move->IsRedundant());
169
170 // Clear this move's destination to indicate a pending move. The actual
171 // destination is saved on the side.
172 InstructionOperand source = move->source();
173 DCHECK(!source.IsInvalid()); // Or else it will look eliminated.
174 InstructionOperand destination = move->destination();
175 move->SetPending();
176 MoveOperands* blocking_move = nullptr;
177
178 for (size_t i = 0; i < moves->size(); ++i) {
179 auto other = (*moves)[i];
180 if (other->IsEliminated()) continue;
181 if (other == move) continue;
182 if (other->source().InterferesWith(destination)) {
183 if (other->IsPending()) {
184 // The conflicting move is pending, we found a cycle. Build the list of
185 // moves that belong to the cycle on the way back.
186 // If this move already belongs to a cycle, bail out.
187 if (!cycle->empty()) {
188 blocking_move = cycle->front();
189 break;
190 }
191 // Initialize the cycle with {other} and reconstruct the rest of the
192 // cycle on the way back.
193 cycle->push_back(other);
194 } else {
195 std::vector<MoveOperands*> cycle_rec;
196 blocking_move = PerformMoveHelper(moves, other, &cycle_rec);
197 if (blocking_move) break;
198 if (!cycle->empty() && !cycle_rec.empty()) {
199 blocking_move = cycle_rec.front();
200 break;
201 }
202 if (cycle->empty() && !cycle_rec.empty()) {
203 *cycle = std::move(cycle_rec);
204 }
205 }
206 }
207 }
208
209 // We finished processing all the blocking moves and don't need this one
210 // marked as pending anymore, restore its destination.
212
213 if (blocking_move != nullptr) return blocking_move;
214
215 if (!cycle->empty()) {
216 if (cycle->front() == move) {
217 // We returned to the topmost move in the cycle and assembled all the
218 // other dependencies. Assemble the cycle.
219 PerformCycle(*cycle);
220 cycle->clear();
221 } else {
222 cycle->push_back(move);
223 }
224 } else {
226 move->Eliminate();
227 }
228 return nullptr;
229}
230
231} // namespace compiler
232} // namespace internal
233} // namespace v8
constexpr void Add(E element)
Definition enum-set.h:50
virtual void SetPendingMove(MoveOperands *move)=0
virtual void MoveToTempLocation(InstructionOperand *src, MachineRepresentation rep)=0
virtual void AssembleMove(InstructionOperand *source, InstructionOperand *destination)=0
virtual void AssembleSwap(InstructionOperand *source, InstructionOperand *destination)=0
virtual AllocatedOperand Push(InstructionOperand *src)=0
virtual void MoveTempLocationTo(InstructionOperand *dst, MachineRepresentation rep)=0
MoveOperands * PerformMoveHelper(ParallelMove *moves, MoveOperands *move, std::vector< MoveOperands * > *cycle)
void PerformCycle(const std::vector< MoveOperands * > &cycle)
V8_EXPORT_PRIVATE void Resolve(ParallelMove *parallel_move)
void PerformMove(ParallelMove *moves, MoveOperands *move)
MachineRepresentation representation() const
static LocationOperand * cast(InstructionOperand *op)
const InstructionOperand & source() const
const InstructionOperand & destination() const
void set_destination(const InstructionOperand &operand)
InstructionOperand source
InstructionOperand destination
int m
Definition mul-fft.cc:294
bool IsSwap(MoveOperands *move1, MoveOperands *move2)
constexpr bool IsFloatingPoint(MachineRepresentation rep)
#define DCHECK(condition)
Definition logging.h:482