Flutter Engine
The Flutter Engine
parallel_move_resolver.cc
Go to the documentation of this file.
1// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
6
7namespace dart {
8
9// Simple dynamically allocated array of fixed length.
10template <typename Subclass, typename Element>
12 public:
13 static Subclass& Allocate(intptr_t length) {
14 static_assert(Utils::IsAligned(alignof(Subclass), alignof(Element)));
15 auto result =
16 reinterpret_cast<void*>(Thread::Current()->zone()->AllocUnsafe(
17 sizeof(Subclass) + length * sizeof(Element)));
18 return *new (result) Subclass(length);
19 }
20
21 intptr_t length() const { return length_; }
22
23 Element& operator[](intptr_t i) {
24 ASSERT(0 <= i && i < length_);
25 return data()[i];
26 }
27
28 const Element& operator[](intptr_t i) const {
29 ASSERT(0 <= i && i < length_);
30 return data()[i];
31 }
32
35
36 Element* begin() { return data(); }
37 const Element* begin() const { return data(); }
38
39 Element* end() { return data() + length_; }
40 const Element* end() const { return data() + length_; }
41
42 protected:
43 explicit FixedArray(intptr_t length) : length_(length) {}
44
45 private:
46 intptr_t length_;
47
48 DISALLOW_COPY_AND_ASSIGN(FixedArray);
49};
50
51class MoveSchedule : public FixedArray<MoveSchedule, ParallelMoveResolver::Op> {
52 public:
53 // Converts the given list of |ParallelMoveResolver::Op| operations
54 // into a |MoveSchedule| and filters out all |kNop| operations.
55 static const MoveSchedule& From(
57 intptr_t count = 0;
58 for (const auto& op : ops) {
59 if (op.kind != ParallelMoveResolver::OpKind::kNop) count++;
60 }
61
63 intptr_t i = 0;
64 for (const auto& op : ops) {
65 if (op.kind != ParallelMoveResolver::OpKind::kNop) {
66 result[i++] = op;
67 }
68 }
69 return result;
70 }
71
72 private:
74
75 explicit MoveSchedule(intptr_t length) : FixedArray(length) {}
76
77 DISALLOW_COPY_AND_ASSIGN(MoveSchedule);
78};
79
81 return ((reg) != kNoRegister) ? (1 << (reg)) : 0;
82}
83
85
87 ASSERT(moves_.is_empty());
88
89 // Build up a worklist of moves.
90 BuildInitialMoveList(parallel_move);
91
92 const InstructionSource& move_source = InstructionSource(
93 TokenPosition::kParallelMove, parallel_move->inlining_id());
94 for (intptr_t i = 0; i < moves_.length(); ++i) {
95 const MoveOperands& move = moves_[i];
96 // Skip constants to perform them last. They don't block other moves
97 // and skipping such moves with register destinations keeps those
98 // registers free for the whole algorithm.
99 if (!move.IsEliminated() && !move.src().IsConstant()) {
100 PerformMove(move_source, i);
101 }
102 }
103
104 // Perform the moves with constant sources.
105 for (const auto& move : moves_) {
106 if (!move.IsEliminated()) {
107 ASSERT(move.src().IsConstant());
108 scheduled_ops_.Add({OpKind::kMove, move});
109 }
110 }
111 moves_.Clear();
112
113 // Schedule is ready. Update parallel move itself.
114 parallel_move->set_move_schedule(MoveSchedule::From(scheduled_ops_));
115 scheduled_ops_.Clear();
116}
117
118void ParallelMoveResolver::BuildInitialMoveList(
119 ParallelMoveInstr* parallel_move) {
120 // Perform a linear sweep of the moves to add them to the initial list of
121 // moves to perform, ignoring any move that is redundant (the source is
122 // the same as the destination, the destination is ignored and
123 // unallocated, or the move was already eliminated).
124 for (int i = 0; i < parallel_move->NumMoves(); i++) {
125 MoveOperands* move = parallel_move->MoveOperandsAt(i);
126 if (!move->IsRedundant()) moves_.Add(*move);
127 }
128}
129
130void ParallelMoveResolver::PerformMove(const InstructionSource& source,
131 int index) {
132 // Each call to this function performs a move and deletes it from the move
133 // graph. We first recursively perform any move blocking this one. We
134 // mark a move as "pending" on entry to PerformMove in order to detect
135 // cycles in the move graph. We use operand swaps to resolve cycles,
136 // which means that a call to PerformMove could change any source operand
137 // in the move graph.
138
139 ASSERT(!moves_[index].IsPending());
140 ASSERT(!moves_[index].IsRedundant());
141
142 // Clear this move's destination to indicate a pending move. The actual
143 // destination is saved in a stack-allocated local. Recursion may allow
144 // multiple moves to be pending.
145 ASSERT(!moves_[index].src().IsInvalid());
146 Location destination = moves_[index].MarkPending();
147
148 // Perform a depth-first traversal of the move graph to resolve
149 // dependencies. Any unperformed, unpending move with a source the same
150 // as this one's destination blocks this one so recursively perform all
151 // such moves.
152 for (int i = 0; i < moves_.length(); ++i) {
153 const MoveOperands& other_move = moves_[i];
154 if (other_move.Blocks(destination) && !other_move.IsPending()) {
155 // Though PerformMove can change any source operand in the move graph,
156 // this call cannot create a blocking move via a swap (this loop does
157 // not miss any). Assume there is a non-blocking move with source A
158 // and this move is blocked on source B and there is a swap of A and
159 // B. Then A and B must be involved in the same cycle (or they would
160 // not be swapped). Since this move's destination is B and there is
161 // only a single incoming edge to an operand, this move must also be
162 // involved in the same cycle. In that case, the blocking move will
163 // be created but will be "pending" when we return from PerformMove.
164 PerformMove(source, i);
165 }
166 }
167
168 // We are about to resolve this move and don't need it marked as
169 // pending, so restore its destination.
170 moves_[index].ClearPending(destination);
171
172 // This move's source may have changed due to swaps to resolve cycles and
173 // so it may now be the last move in the cycle. If so remove it.
174 if (moves_[index].src().Equals(destination)) {
175 moves_[index].Eliminate();
176 return;
177 }
178
179 // The move may be blocked on a (at most one) pending move, in which case
180 // we have a cycle. Search for such a blocking move and perform a swap to
181 // resolve it.
182 for (auto& other_move : moves_) {
183 if (other_move.Blocks(destination)) {
184 ASSERT(other_move.IsPending());
185 AddSwapToSchedule(index);
186 return;
187 }
188 }
189
190 // This move is not blocked.
191 AddMoveToSchedule(index);
192}
193
194void ParallelMoveResolver::AddMoveToSchedule(int index) {
195 auto& move = moves_[index];
196 scheduled_ops_.Add({OpKind::kMove, move});
197 move.Eliminate();
198}
199
200void ParallelMoveResolver::AddSwapToSchedule(int index) {
201 auto& move = moves_[index];
202 const auto source = move.src();
203 const auto destination = move.dest();
204
205 scheduled_ops_.Add({OpKind::kSwap, move});
206
207 // The swap of source and destination has executed a move from source to
208 // destination.
209 move.Eliminate();
210
211 // Any unperformed (including pending) move with a source of either
212 // this move's source or destination needs to have their source
213 // changed to reflect the state of affairs after the swap.
214 for (auto& other_move : moves_) {
215 if (other_move.Blocks(source)) {
216 other_move.set_src(destination);
217 } else if (other_move.Blocks(destination)) {
218 other_move.set_src(source);
219 }
220 }
221}
222
224 const auto& move_schedule = parallel_move_->move_schedule();
225 for (intptr_t i = 0; i < move_schedule.length(); i++) {
226 current_move_ = i;
227 const auto& op = move_schedule[i];
228 switch (op.kind) {
229 case ParallelMoveResolver::OpKind::kNop:
230 // |MoveSchedule::From| is expected to filter nops.
231 UNREACHABLE();
232 break;
233 case ParallelMoveResolver::OpKind::kMove:
234 EmitMove(op.operands);
235 break;
236 case ParallelMoveResolver::OpKind::kSwap:
237 EmitSwap(op.operands);
238 break;
239 }
240 }
241}
242
243void ParallelMoveEmitter::EmitMove(const MoveOperands& move) {
244 Location src = move.src();
245 Location dst = move.dest();
246#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
247 dst = compiler_->RebaseIfImprovesAddressing(dst);
248 src = compiler_->RebaseIfImprovesAddressing(src);
249#endif
250 ParallelMoveEmitter::TemporaryAllocator temp(this, /*blocked=*/kNoRegister);
251 compiler_->EmitMove(dst, src, &temp);
252#if defined(DEBUG)
253 // Allocating a scratch register here may cause stack spilling. Neither the
254 // source nor destination register should be SP-relative in that case.
255 for (const Location& loc : {dst, src}) {
256 ASSERT(!temp.DidAllocateTemporary() || !loc.HasStackIndex() ||
257 loc.base_reg() != SPREG);
258 }
259#endif
260}
261
262bool ParallelMoveEmitter::IsScratchLocation(Location loc) {
263 const auto& move_schedule = parallel_move_->move_schedule();
264 for (intptr_t i = current_move_; i < move_schedule.length(); i++) {
265 const auto& op = move_schedule[i];
266 if (op.operands.src().Equals(loc) ||
267 (op.kind == ParallelMoveResolver::OpKind::kSwap &&
268 op.operands.dest().Equals(loc))) {
269 return false;
270 }
271 }
272
273 for (intptr_t i = current_move_ + 1; i < move_schedule.length(); i++) {
274 const auto& op = move_schedule[i];
275 if (op.kind == ParallelMoveResolver::OpKind::kMove &&
276 op.operands.dest().Equals(loc)) {
277 return true;
278 }
279 }
280
281 return false;
282}
283
284intptr_t ParallelMoveEmitter::AllocateScratchRegister(
285 Location::Kind kind,
286 uword blocked_mask,
287 intptr_t first_free_register,
288 intptr_t last_free_register,
289 bool* spilled) {
290 COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >=
292 COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >=
294 intptr_t scratch = -1;
295 for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) {
296 if ((((1 << reg) & blocked_mask) == 0) &&
297 IsScratchLocation(Location::MachineRegisterLocation(kind, reg))) {
298 scratch = reg;
299 break;
300 }
301 }
302
303 if (scratch == -1) {
304 *spilled = true;
305 for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) {
306 if (((1 << reg) & blocked_mask) == 0) {
307 scratch = reg;
308 break;
309 }
310 }
311 } else {
312 *spilled = false;
313 }
314
315 return scratch;
316}
317
318ParallelMoveEmitter::ScratchFpuRegisterScope::ScratchFpuRegisterScope(
319 ParallelMoveEmitter* emitter,
320 FpuRegister blocked)
321 : emitter_(emitter), reg_(kNoFpuRegister), spilled_(false) {
323 uword blocked_mask =
324 ((blocked != kNoFpuRegister) ? 1 << blocked : 0) | 1 << FpuTMP;
325 reg_ = static_cast<FpuRegister>(
326 emitter_->AllocateScratchRegister(Location::kFpuRegister, blocked_mask, 0,
327 kNumberOfFpuRegisters - 1, &spilled_));
328
329 if (spilled_) {
330 emitter->SpillFpuScratch(reg_);
331 }
332}
333
334ParallelMoveEmitter::ScratchFpuRegisterScope::~ScratchFpuRegisterScope() {
335 if (spilled_) {
336 emitter_->RestoreFpuScratch(reg_);
337 }
338}
339
340ParallelMoveEmitter::TemporaryAllocator::TemporaryAllocator(
341 ParallelMoveEmitter* emitter,
342 Register blocked)
343 : emitter_(emitter),
344 blocked_(blocked),
345 reg_(kNoRegister),
346 spilled_(false) {}
347
348Register ParallelMoveEmitter::TemporaryAllocator::AllocateTemporary() {
349 ASSERT(reg_ == kNoRegister);
350
351 uword blocked_mask = RegMaskBit(blocked_) | kReservedCpuRegisters;
352 if (emitter_->compiler_->intrinsic_mode()) {
353 // Block additional registers that must be preserved for intrinsics.
354 blocked_mask |= RegMaskBit(ARGS_DESC_REG);
355#if !defined(TARGET_ARCH_IA32)
356 // Need to preserve CODE_REG to be able to store the PC marker
357 // and load the pool pointer.
358 blocked_mask |= RegMaskBit(CODE_REG);
359#endif
360 }
361 reg_ = static_cast<Register>(
362 emitter_->AllocateScratchRegister(Location::kRegister, blocked_mask, 0,
363 kNumberOfCpuRegisters - 1, &spilled_));
364
365 if (spilled_) {
366 emitter_->SpillScratch(reg_);
367 }
368
369 DEBUG_ONLY(allocated_ = true;)
370 return reg_;
371}
372
373void ParallelMoveEmitter::TemporaryAllocator::ReleaseTemporary() {
374 if (spilled_) {
375 emitter_->RestoreScratch(reg_);
376 }
377 reg_ = kNoRegister;
378}
379
380ParallelMoveEmitter::ScratchRegisterScope::ScratchRegisterScope(
381 ParallelMoveEmitter* emitter,
382 Register blocked)
383 : allocator_(emitter, blocked) {
384 reg_ = allocator_.AllocateTemporary();
385}
386
387ParallelMoveEmitter::ScratchRegisterScope::~ScratchRegisterScope() {
388 allocator_.ReleaseTemporary();
389}
390
391template <>
394 const MoveSchedule* schedule) {
395 ASSERT(schedule != nullptr);
396 const intptr_t len = schedule->length();
397 s->Write<intptr_t>(len);
398 for (intptr_t i = 0; i < len; ++i) {
399 const auto& op = (*schedule)[i];
400 s->Write<uint8_t>(static_cast<uint8_t>(op.kind));
401 op.operands.Write(s);
402 }
403}
404
405template <>
408 const intptr_t len = d->Read<intptr_t>();
410 for (intptr_t i = 0; i < len; ++i) {
411 schedule[i].kind =
412 static_cast<ParallelMoveResolver::OpKind>(d->Read<uint8_t>());
413 schedule[i].operands = MoveOperands(d);
414 }
415 return &schedule;
416}
417
418} // namespace dart
int count
Definition: FontMgrTest.cpp:50
SkPathOp ops[]
#define UNREACHABLE()
Definition: assert.h:248
void Add(const T &value)
static Subclass & Allocate(intptr_t length)
const Element * data() const
Element & operator[](intptr_t i)
const Element * end() const
FixedArray(intptr_t length)
intptr_t length() const
const Element * begin() const
const Element & operator[](intptr_t i) const
void EmitMove(Location dst, Location src, TemporaryRegisterAllocator *temp)
virtual intptr_t inlining_id() const
Definition: il.h:1311
static Location MachineRegisterLocation(Kind kind, intptr_t reg)
Definition: locations.h:425
bool IsConstant() const
Definition: locations.h:292
bool IsEliminated() const
Definition: il.h:1581
Location src() const
Definition: il.h:1540
void Eliminate()
Definition: il.h:1580
Location dest() const
Definition: il.h:1541
bool IsRedundant() const
Definition: il.h:1575
static const MoveSchedule & From(const GrowableArray< ParallelMoveResolver::Op > &ops)
intptr_t NumMoves() const
Definition: il.h:1617
const MoveSchedule & move_schedule() const
Definition: il.h:1625
MoveOperands * MoveOperandsAt(intptr_t index) const
Definition: il.h:1615
void set_move_schedule(const MoveSchedule &schedule)
Definition: il.h:1630
void Resolve(ParallelMoveInstr *parallel_move)
Zone * zone() const
Definition: thread_state.h:37
static Thread * Current()
Definition: thread.h:362
static constexpr bool IsAligned(T x, uintptr_t alignment, uintptr_t offset=0)
Definition: utils.h:92
void * AllocUnsafe(intptr_t size)
#define ASSERT(E)
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
SkBitmap source
Definition: examples.cpp:28
struct MyStruct s
GAsyncResult * result
Definition: dart.idl:42
Definition: dart_vm.cc:33
static bool Equals(const Object &expected, const Object &actual)
const FpuRegister kNoFpuRegister
static uword RegMaskBit(Register reg)
const RegList kReservedCpuRegisters
constexpr intptr_t kBitsPerByte
Definition: globals.h:463
const FpuRegister FpuTMP
uintptr_t uword
Definition: globals.h:501
const Register CODE_REG
const Register ARGS_DESC_REG
@ kNumberOfCpuRegisters
Definition: constants_arm.h:98
@ kNoRegister
Definition: constants_arm.h:99
const int kNumberOfFpuRegisters
QRegister FpuRegister
const Register SPREG
COMPILE_ASSERT(kUnreachableReference==WeakTable::kNoValue)
dst
Definition: cp.py:12
#define DEBUG_ONLY(code)
Definition: globals.h:141
const AbstractType * Read(FlowGraphDeserializer *d)
void Write(FlowGraphSerializer *s, const AbstractType *x)
#define OPEN_ARRAY_START(type, align)
Definition: globals.h:154