1 // Copyright 2017 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
5 #include "src/wasm/baseline/liftoff-assembler.h"
6
7 #include <sstream>
8
9 #include "src/base/optional.h"
10 #include "src/base/platform/wrappers.h"
11 #include "src/codegen/assembler-inl.h"
12 #include "src/codegen/macro-assembler-inl.h"
13 #include "src/compiler/linkage.h"
14 #include "src/compiler/wasm-compiler.h"
15 #include "src/utils/ostreams.h"
16 #include "src/wasm/baseline/liftoff-register.h"
17 #include "src/wasm/function-body-decoder-impl.h"
18 #include "src/wasm/object-access.h"
19 #include "src/wasm/wasm-linkage.h"
20 #include "src/wasm/wasm-opcodes.h"
21
22 namespace v8 {
23 namespace internal {
24 namespace wasm {
25
26 using VarState = LiftoffAssembler::VarState;
27 using ValueKindSig = LiftoffAssembler::ValueKindSig;
28
29 constexpr ValueKind LiftoffAssembler::kPointerKind;
30 constexpr ValueKind LiftoffAssembler::kTaggedKind;
31 constexpr ValueKind LiftoffAssembler::kSmiKind;
32
33 namespace {
34
35 class StackTransferRecipe {
36 struct RegisterMove {
37 LiftoffRegister src;
38 ValueKind kind;
RegisterMovev8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterMove39 constexpr RegisterMove(LiftoffRegister src, ValueKind kind)
40 : src(src), kind(kind) {}
41 };
42
43 struct RegisterLoad {
44 enum LoadKind : uint8_t {
45 kNop, // no-op, used for high fp of a fp pair.
46 kConstant, // load a constant value into a register.
47 kStack, // fill a register from a stack slot.
48 kLowHalfStack, // fill a register from the low half of a stack slot.
49 kHighHalfStack // fill a register from the high half of a stack slot.
50 };
51
52 LoadKind load_kind;
53 ValueKind kind;
54 int32_t value; // i32 constant value or stack offset, depending on kind.
55
56 // Named constructors.
Constv8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterLoad57 static RegisterLoad Const(WasmValue constant) {
58 if (constant.type().kind() == kI32) {
59 return {kConstant, kI32, constant.to_i32()};
60 }
61 DCHECK_EQ(kI64, constant.type().kind());
62 int32_t i32_const = static_cast<int32_t>(constant.to_i64());
63 DCHECK_EQ(constant.to_i64(), i32_const);
64 return {kConstant, kI64, i32_const};
65 }
Stackv8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterLoad66 static RegisterLoad Stack(int32_t offset, ValueKind kind) {
67 return {kStack, kind, offset};
68 }
HalfStackv8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterLoad69 static RegisterLoad HalfStack(int32_t offset, RegPairHalf half) {
70 return {half == kLowWord ? kLowHalfStack : kHighHalfStack, kI32, offset};
71 }
Nopv8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterLoad72 static RegisterLoad Nop() {
73 // ValueKind does not matter.
74 return {kNop, kI32, 0};
75 }
76
77 private:
RegisterLoadv8::internal::wasm::__anonf890b2d00111::StackTransferRecipe::RegisterLoad78 RegisterLoad(LoadKind load_kind, ValueKind kind, int32_t value)
79 : load_kind(load_kind), kind(kind), value(value) {}
80 };
81
82 public:
StackTransferRecipe(LiftoffAssembler * wasm_asm)83 explicit StackTransferRecipe(LiftoffAssembler* wasm_asm) : asm_(wasm_asm) {}
84 StackTransferRecipe(const StackTransferRecipe&) = delete;
85 StackTransferRecipe& operator=(const StackTransferRecipe&) = delete;
~StackTransferRecipe()86 ~StackTransferRecipe() { Execute(); }
87
Execute()88 void Execute() {
89 // First, execute register moves. Then load constants and stack values into
90 // registers.
91 ExecuteMoves();
92 DCHECK(move_dst_regs_.is_empty());
93 ExecuteLoads();
94 DCHECK(load_dst_regs_.is_empty());
95 }
96
TransferStackSlot(const VarState & dst,const VarState & src)97 V8_INLINE void TransferStackSlot(const VarState& dst, const VarState& src) {
98 DCHECK(CheckCompatibleStackSlotTypes(dst.kind(), src.kind()));
99 if (dst.is_reg()) {
100 LoadIntoRegister(dst.reg(), src, src.offset());
101 return;
102 }
103 if (dst.is_const()) {
104 DCHECK_EQ(dst.i32_const(), src.i32_const());
105 return;
106 }
107 DCHECK(dst.is_stack());
108 switch (src.loc()) {
109 case VarState::kStack:
110 if (src.offset() != dst.offset()) {
111 asm_->MoveStackValue(dst.offset(), src.offset(), src.kind());
112 }
113 break;
114 case VarState::kRegister:
115 asm_->Spill(dst.offset(), src.reg(), src.kind());
116 break;
117 case VarState::kIntConst:
118 asm_->Spill(dst.offset(), src.constant());
119 break;
120 }
121 }
122
LoadIntoRegister(LiftoffRegister dst,const LiftoffAssembler::VarState & src,uint32_t src_offset)123 V8_INLINE void LoadIntoRegister(LiftoffRegister dst,
124 const LiftoffAssembler::VarState& src,
125 uint32_t src_offset) {
126 switch (src.loc()) {
127 case VarState::kStack:
128 LoadStackSlot(dst, src_offset, src.kind());
129 break;
130 case VarState::kRegister:
131 DCHECK_EQ(dst.reg_class(), src.reg_class());
132 if (dst != src.reg()) MoveRegister(dst, src.reg(), src.kind());
133 break;
134 case VarState::kIntConst:
135 LoadConstant(dst, src.constant());
136 break;
137 }
138 }
139
LoadI64HalfIntoRegister(LiftoffRegister dst,const LiftoffAssembler::VarState & src,int offset,RegPairHalf half)140 void LoadI64HalfIntoRegister(LiftoffRegister dst,
141 const LiftoffAssembler::VarState& src,
142 int offset, RegPairHalf half) {
143 // Use CHECK such that the remaining code is statically dead if
144 // {kNeedI64RegPair} is false.
145 CHECK(kNeedI64RegPair);
146 DCHECK_EQ(kI64, src.kind());
147 switch (src.loc()) {
148 case VarState::kStack:
149 LoadI64HalfStackSlot(dst, offset, half);
150 break;
151 case VarState::kRegister: {
152 LiftoffRegister src_half =
153 half == kLowWord ? src.reg().low() : src.reg().high();
154 if (dst != src_half) MoveRegister(dst, src_half, kI32);
155 break;
156 }
157 case VarState::kIntConst:
158 int32_t value = src.i32_const();
159 // The high word is the sign extension of the low word.
160 if (half == kHighWord) value = value >> 31;
161 LoadConstant(dst, WasmValue(value));
162 break;
163 }
164 }
165
MoveRegister(LiftoffRegister dst,LiftoffRegister src,ValueKind kind)166 void MoveRegister(LiftoffRegister dst, LiftoffRegister src, ValueKind kind) {
167 DCHECK_NE(dst, src);
168 DCHECK_EQ(dst.reg_class(), src.reg_class());
169 DCHECK_EQ(reg_class_for(kind), src.reg_class());
170 if (src.is_gp_pair()) {
171 DCHECK_EQ(kI64, kind);
172 if (dst.low() != src.low()) MoveRegister(dst.low(), src.low(), kI32);
173 if (dst.high() != src.high()) MoveRegister(dst.high(), src.high(), kI32);
174 return;
175 }
176 if (src.is_fp_pair()) {
177 DCHECK_EQ(kS128, kind);
178 if (dst.low() != src.low()) {
179 MoveRegister(dst.low(), src.low(), kF64);
180 MoveRegister(dst.high(), src.high(), kF64);
181 }
182 return;
183 }
184 if (move_dst_regs_.has(dst)) {
185 DCHECK_EQ(register_move(dst)->src, src);
186 // Non-fp registers can only occur with the exact same type.
187 DCHECK_IMPLIES(!dst.is_fp(), register_move(dst)->kind == kind);
188 // It can happen that one fp register holds both the f32 zero and the f64
189 // zero, as the initial value for local variables. Move the value as f64
190 // in that case.
191 if (kind == kF64) register_move(dst)->kind = kF64;
192 return;
193 }
194 move_dst_regs_.set(dst);
195 ++*src_reg_use_count(src);
196 *register_move(dst) = {src, kind};
197 }
198
LoadConstant(LiftoffRegister dst,WasmValue value)199 void LoadConstant(LiftoffRegister dst, WasmValue value) {
200 DCHECK(!load_dst_regs_.has(dst));
201 load_dst_regs_.set(dst);
202 if (dst.is_gp_pair()) {
203 DCHECK_EQ(kI64, value.type().kind());
204 int64_t i64 = value.to_i64();
205 *register_load(dst.low()) =
206 RegisterLoad::Const(WasmValue(static_cast<int32_t>(i64)));
207 *register_load(dst.high()) =
208 RegisterLoad::Const(WasmValue(static_cast<int32_t>(i64 >> 32)));
209 } else {
210 *register_load(dst) = RegisterLoad::Const(value);
211 }
212 }
213
LoadStackSlot(LiftoffRegister dst,uint32_t stack_offset,ValueKind kind)214 void LoadStackSlot(LiftoffRegister dst, uint32_t stack_offset,
215 ValueKind kind) {
216 if (load_dst_regs_.has(dst)) {
217 // It can happen that we spilled the same register to different stack
218 // slots, and then we reload them later into the same dst register.
219 // In that case, it is enough to load one of the stack slots.
220 return;
221 }
222 load_dst_regs_.set(dst);
223 if (dst.is_gp_pair()) {
224 DCHECK_EQ(kI64, kind);
225 *register_load(dst.low()) =
226 RegisterLoad::HalfStack(stack_offset, kLowWord);
227 *register_load(dst.high()) =
228 RegisterLoad::HalfStack(stack_offset, kHighWord);
229 } else if (dst.is_fp_pair()) {
230 DCHECK_EQ(kS128, kind);
231 // Only need register_load for low_gp since we load 128 bits at one go.
232 // Both low and high need to be set in load_dst_regs_ but when iterating
233 // over it, both low and high will be cleared, so we won't load twice.
234 *register_load(dst.low()) = RegisterLoad::Stack(stack_offset, kind);
235 *register_load(dst.high()) = RegisterLoad::Nop();
236 } else {
237 *register_load(dst) = RegisterLoad::Stack(stack_offset, kind);
238 }
239 }
240
LoadI64HalfStackSlot(LiftoffRegister dst,int offset,RegPairHalf half)241 void LoadI64HalfStackSlot(LiftoffRegister dst, int offset, RegPairHalf half) {
242 if (load_dst_regs_.has(dst)) {
243 // It can happen that we spilled the same register to different stack
244 // slots, and then we reload them later into the same dst register.
245 // In that case, it is enough to load one of the stack slots.
246 return;
247 }
248 load_dst_regs_.set(dst);
249 *register_load(dst) = RegisterLoad::HalfStack(offset, half);
250 }
251
252 private:
253 using MovesStorage =
254 std::aligned_storage<kAfterMaxLiftoffRegCode * sizeof(RegisterMove),
255 alignof(RegisterMove)>::type;
256 using LoadsStorage =
257 std::aligned_storage<kAfterMaxLiftoffRegCode * sizeof(RegisterLoad),
258 alignof(RegisterLoad)>::type;
259
260 ASSERT_TRIVIALLY_COPYABLE(RegisterMove);
261 ASSERT_TRIVIALLY_COPYABLE(RegisterLoad);
262
263 MovesStorage register_moves_; // uninitialized
264 LoadsStorage register_loads_; // uninitialized
265 int src_reg_use_count_[kAfterMaxLiftoffRegCode] = {0};
266 LiftoffRegList move_dst_regs_;
267 LiftoffRegList load_dst_regs_;
268 LiftoffAssembler* const asm_;
269
register_move(LiftoffRegister reg)270 RegisterMove* register_move(LiftoffRegister reg) {
271 return reinterpret_cast<RegisterMove*>(®ister_moves_) +
272 reg.liftoff_code();
273 }
register_load(LiftoffRegister reg)274 RegisterLoad* register_load(LiftoffRegister reg) {
275 return reinterpret_cast<RegisterLoad*>(®ister_loads_) +
276 reg.liftoff_code();
277 }
src_reg_use_count(LiftoffRegister reg)278 int* src_reg_use_count(LiftoffRegister reg) {
279 return src_reg_use_count_ + reg.liftoff_code();
280 }
281
ExecuteMove(LiftoffRegister dst)282 void ExecuteMove(LiftoffRegister dst) {
283 RegisterMove* move = register_move(dst);
284 DCHECK_EQ(0, *src_reg_use_count(dst));
285 asm_->Move(dst, move->src, move->kind);
286 ClearExecutedMove(dst);
287 }
288
ClearExecutedMove(LiftoffRegister dst)289 void ClearExecutedMove(LiftoffRegister dst) {
290 DCHECK(move_dst_regs_.has(dst));
291 move_dst_regs_.clear(dst);
292 RegisterMove* move = register_move(dst);
293 DCHECK_LT(0, *src_reg_use_count(move->src));
294 if (--*src_reg_use_count(move->src)) return;
295 // src count dropped to zero. If this is a destination register, execute
296 // that move now.
297 if (!move_dst_regs_.has(move->src)) return;
298 ExecuteMove(move->src);
299 }
300
ExecuteMoves()301 void ExecuteMoves() {
302 // Execute all moves whose {dst} is not being used as src in another move.
303 // If any src count drops to zero, also (transitively) execute the
304 // corresponding move to that register.
305 for (LiftoffRegister dst : move_dst_regs_) {
306 // Check if already handled via transitivity in {ClearExecutedMove}.
307 if (!move_dst_regs_.has(dst)) continue;
308 if (*src_reg_use_count(dst)) continue;
309 ExecuteMove(dst);
310 }
311
312 // All remaining moves are parts of a cycle. Just spill the first one, then
313 // process all remaining moves in that cycle. Repeat for all cycles.
314 int last_spill_offset = asm_->TopSpillOffset();
315 while (!move_dst_regs_.is_empty()) {
316 // TODO(clemensb): Use an unused register if available.
317 LiftoffRegister dst = move_dst_regs_.GetFirstRegSet();
318 RegisterMove* move = register_move(dst);
319 last_spill_offset += LiftoffAssembler::SlotSizeForType(move->kind);
320 LiftoffRegister spill_reg = move->src;
321 asm_->Spill(last_spill_offset, spill_reg, move->kind);
322 // Remember to reload into the destination register later.
323 LoadStackSlot(dst, last_spill_offset, move->kind);
324 ClearExecutedMove(dst);
325 }
326 }
327
ExecuteLoads()328 void ExecuteLoads() {
329 for (LiftoffRegister dst : load_dst_regs_) {
330 RegisterLoad* load = register_load(dst);
331 switch (load->load_kind) {
332 case RegisterLoad::kNop:
333 break;
334 case RegisterLoad::kConstant:
335 asm_->LoadConstant(dst, load->kind == kI64
336 ? WasmValue(int64_t{load->value})
337 : WasmValue(int32_t{load->value}));
338 break;
339 case RegisterLoad::kStack:
340 if (kNeedS128RegPair && load->kind == kS128) {
341 asm_->Fill(LiftoffRegister::ForFpPair(dst.fp()), load->value,
342 load->kind);
343 } else {
344 asm_->Fill(dst, load->value, load->kind);
345 }
346 break;
347 case RegisterLoad::kLowHalfStack:
348 // Half of a register pair, {dst} must be a gp register.
349 asm_->FillI64Half(dst.gp(), load->value, kLowWord);
350 break;
351 case RegisterLoad::kHighHalfStack:
352 // Half of a register pair, {dst} must be a gp register.
353 asm_->FillI64Half(dst.gp(), load->value, kHighWord);
354 break;
355 }
356 }
357 load_dst_regs_ = {};
358 }
359 };
360
361 class RegisterReuseMap {
362 public:
Add(LiftoffRegister src,LiftoffRegister dst)363 void Add(LiftoffRegister src, LiftoffRegister dst) {
364 if (auto previous = Lookup(src)) {
365 DCHECK_EQ(previous, dst);
366 return;
367 }
368 map_.emplace_back(src);
369 map_.emplace_back(dst);
370 }
371
Lookup(LiftoffRegister src)372 base::Optional<LiftoffRegister> Lookup(LiftoffRegister src) {
373 for (auto it = map_.begin(), end = map_.end(); it != end; it += 2) {
374 if (it->is_gp_pair() == src.is_gp_pair() &&
375 it->is_fp_pair() == src.is_fp_pair() && *it == src)
376 return *(it + 1);
377 }
378 return {};
379 }
380
381 private:
382 // {map_} holds pairs of <src, dst>.
383 base::SmallVector<LiftoffRegister, 8> map_;
384 };
385
386 enum MergeKeepStackSlots : bool {
387 kKeepStackSlots = true,
388 kTurnStackSlotsIntoRegisters = false
389 };
390 enum MergeAllowConstants : bool {
391 kConstantsAllowed = true,
392 kConstantsNotAllowed = false
393 };
394 enum ReuseRegisters : bool {
395 kReuseRegisters = true,
396 kNoReuseRegisters = false
397 };
InitMergeRegion(LiftoffAssembler::CacheState * state,const VarState * source,VarState * target,uint32_t count,MergeKeepStackSlots keep_stack_slots,MergeAllowConstants allow_constants,ReuseRegisters reuse_registers,LiftoffRegList used_regs)398 void InitMergeRegion(LiftoffAssembler::CacheState* state,
399 const VarState* source, VarState* target, uint32_t count,
400 MergeKeepStackSlots keep_stack_slots,
401 MergeAllowConstants allow_constants,
402 ReuseRegisters reuse_registers, LiftoffRegList used_regs) {
403 RegisterReuseMap register_reuse_map;
404 for (const VarState* source_end = source + count; source < source_end;
405 ++source, ++target) {
406 if ((source->is_stack() && keep_stack_slots) ||
407 (source->is_const() && allow_constants)) {
408 *target = *source;
409 continue;
410 }
411 base::Optional<LiftoffRegister> reg;
412 // First try: Keep the same register, if it's free.
413 if (source->is_reg() && state->is_free(source->reg())) {
414 reg = source->reg();
415 }
416 // Second try: Use the same register we used before (if we reuse registers).
417 if (!reg && reuse_registers) {
418 reg = register_reuse_map.Lookup(source->reg());
419 }
420 // Third try: Use any free register.
421 RegClass rc = reg_class_for(source->kind());
422 if (!reg && state->has_unused_register(rc, used_regs)) {
423 reg = state->unused_register(rc, used_regs);
424 }
425 if (!reg) {
426 // No free register; make this a stack slot.
427 *target = VarState(source->kind(), source->offset());
428 continue;
429 }
430 if (reuse_registers) register_reuse_map.Add(source->reg(), *reg);
431 state->inc_used(*reg);
432 *target = VarState(source->kind(), *reg, source->offset());
433 }
434 }
435
436 } // namespace
437
438 // TODO(clemensb): Don't copy the full parent state (this makes us N^2).
InitMerge(const CacheState & source,uint32_t num_locals,uint32_t arity,uint32_t stack_depth)439 void LiftoffAssembler::CacheState::InitMerge(const CacheState& source,
440 uint32_t num_locals,
441 uint32_t arity,
442 uint32_t stack_depth) {
443 // |------locals------|---(in between)----|--(discarded)--|----merge----|
444 // <-- num_locals --> <-- stack_depth -->^stack_base <-- arity -->
445
446 if (source.cached_instance != no_reg) {
447 SetInstanceCacheRegister(source.cached_instance);
448 }
449
450 if (source.cached_mem_start != no_reg) {
451 SetMemStartCacheRegister(source.cached_mem_start);
452 }
453
454 uint32_t stack_base = stack_depth + num_locals;
455 uint32_t target_height = stack_base + arity;
456 uint32_t discarded = source.stack_height() - target_height;
457 DCHECK(stack_state.empty());
458
459 DCHECK_GE(source.stack_height(), stack_base);
460 stack_state.resize_no_init(target_height);
461
462 const VarState* source_begin = source.stack_state.data();
463 VarState* target_begin = stack_state.data();
464
465 // Try to keep locals and the merge region in their registers. Register used
466 // multiple times need to be copied to another free register. Compute the list
467 // of used registers.
468 LiftoffRegList used_regs;
469 for (auto& src : base::VectorOf(source_begin, num_locals)) {
470 if (src.is_reg()) used_regs.set(src.reg());
471 }
472 for (auto& src :
473 base::VectorOf(source_begin + stack_base + discarded, arity)) {
474 if (src.is_reg()) used_regs.set(src.reg());
475 }
476
477 // Initialize the merge region. If this region moves, try to turn stack slots
478 // into registers since we need to load the value anyways.
479 MergeKeepStackSlots keep_merge_stack_slots =
480 discarded == 0 ? kKeepStackSlots : kTurnStackSlotsIntoRegisters;
481 InitMergeRegion(this, source_begin + stack_base + discarded,
482 target_begin + stack_base, arity, keep_merge_stack_slots,
483 kConstantsNotAllowed, kNoReuseRegisters, used_regs);
484
485 // Initialize the locals region. Here, stack slots stay stack slots (because
486 // they do not move). Try to keep register in registers, but avoid duplicates.
487 InitMergeRegion(this, source_begin, target_begin, num_locals, kKeepStackSlots,
488 kConstantsNotAllowed, kNoReuseRegisters, used_regs);
489 // Consistency check: All the {used_regs} are really in use now.
490 DCHECK_EQ(used_regs, used_registers & used_regs);
491
492 // Last, initialize the section in between. Here, constants are allowed, but
493 // registers which are already used for the merge region or locals must be
494 // moved to other registers or spilled. If a register appears twice in the
495 // source region, ensure to use the same register twice in the target region.
496 InitMergeRegion(this, source_begin + num_locals, target_begin + num_locals,
497 stack_depth, kKeepStackSlots, kConstantsAllowed,
498 kReuseRegisters, used_regs);
499 }
500
Steal(const CacheState & source)501 void LiftoffAssembler::CacheState::Steal(const CacheState& source) {
502 // Just use the move assignment operator.
503 *this = std::move(source);
504 }
505
Split(const CacheState & source)506 void LiftoffAssembler::CacheState::Split(const CacheState& source) {
507 // Call the private copy assignment operator.
508 *this = source;
509 }
510
511 namespace {
GetSafepointIndexForStackSlot(const VarState & slot)512 int GetSafepointIndexForStackSlot(const VarState& slot) {
513 // index = 0 is for the stack slot at 'fp + kFixedFrameSizeAboveFp -
514 // kSystemPointerSize', the location of the current stack slot is 'fp -
515 // slot.offset()'. The index we need is therefore '(fp +
516 // kFixedFrameSizeAboveFp - kSystemPointerSize) - (fp - slot.offset())' =
517 // 'slot.offset() + kFixedFrameSizeAboveFp - kSystemPointerSize'.
518 // Concretely, the index of the first stack slot is '4'.
519 return (slot.offset() + StandardFrameConstants::kFixedFrameSizeAboveFp -
520 kSystemPointerSize) /
521 kSystemPointerSize;
522 }
523 } // namespace
524
GetTaggedSlotsForOOLCode(ZoneVector<int> * slots,LiftoffRegList * spills,SpillLocation spill_location)525 void LiftoffAssembler::CacheState::GetTaggedSlotsForOOLCode(
526 ZoneVector<int>* slots, LiftoffRegList* spills,
527 SpillLocation spill_location) {
528 for (const auto& slot : stack_state) {
529 if (!is_reference(slot.kind())) continue;
530
531 if (spill_location == SpillLocation::kTopOfStack && slot.is_reg()) {
532 // Registers get spilled just before the call to the runtime. In {spills}
533 // we store which of the spilled registers contain references, so that we
534 // can add the spill slots to the safepoint.
535 spills->set(slot.reg());
536 continue;
537 }
538 DCHECK_IMPLIES(slot.is_reg(), spill_location == SpillLocation::kStackSlots);
539
540 slots->push_back(GetSafepointIndexForStackSlot(slot));
541 }
542 }
543
DefineSafepoint(Safepoint & safepoint)544 void LiftoffAssembler::CacheState::DefineSafepoint(Safepoint& safepoint) {
545 for (const auto& slot : stack_state) {
546 if (is_reference(slot.kind())) {
547 DCHECK(slot.is_stack());
548 safepoint.DefinePointerSlot(GetSafepointIndexForStackSlot(slot));
549 }
550 }
551 }
552
DefineSafepointWithCalleeSavedRegisters(Safepoint & safepoint)553 void LiftoffAssembler::CacheState::DefineSafepointWithCalleeSavedRegisters(
554 Safepoint& safepoint) {
555 for (const auto& slot : stack_state) {
556 if (!is_reference(slot.kind())) continue;
557 if (slot.is_stack()) {
558 safepoint.DefinePointerSlot(GetSafepointIndexForStackSlot(slot));
559 } else {
560 DCHECK(slot.is_reg());
561 safepoint.DefineRegister(slot.reg().gp().code());
562 }
563 }
564 if (cached_instance != no_reg) {
565 safepoint.DefineRegister(cached_instance.code());
566 }
567 }
568
GetTotalFrameSlotCountForGC() const569 int LiftoffAssembler::GetTotalFrameSlotCountForGC() const {
570 // The GC does not care about the actual number of spill slots, just about
571 // the number of references that could be there in the spilling area. Note
572 // that the offset of the first spill slot is kSystemPointerSize and not
573 // '0'. Therefore we don't have to add '+1' here.
574 return (max_used_spill_offset_ +
575 StandardFrameConstants::kFixedFrameSizeAboveFp +
576 ool_spill_space_size_) /
577 kSystemPointerSize;
578 }
579
580 namespace {
581
DefaultLiftoffOptions()582 AssemblerOptions DefaultLiftoffOptions() { return AssemblerOptions{}; }
583
584 } // namespace
585
LiftoffAssembler(std::unique_ptr<AssemblerBuffer> buffer)586 LiftoffAssembler::LiftoffAssembler(std::unique_ptr<AssemblerBuffer> buffer)
587 : TurboAssembler(nullptr, DefaultLiftoffOptions(), CodeObjectRequired::kNo,
588 std::move(buffer)) {
589 set_abort_hard(true); // Avoid calls to Abort.
590 }
591
~LiftoffAssembler()592 LiftoffAssembler::~LiftoffAssembler() {
593 if (num_locals_ > kInlineLocalKinds) {
594 base::Free(more_local_kinds_);
595 }
596 }
597
LoadToRegister(VarState slot,LiftoffRegList pinned)598 LiftoffRegister LiftoffAssembler::LoadToRegister(VarState slot,
599 LiftoffRegList pinned) {
600 if (slot.is_reg()) return slot.reg();
601 LiftoffRegister reg = GetUnusedRegister(reg_class_for(slot.kind()), pinned);
602 if (slot.is_const()) {
603 LoadConstant(reg, slot.constant());
604 } else {
605 DCHECK(slot.is_stack());
606 Fill(reg, slot.offset(), slot.kind());
607 }
608 return reg;
609 }
610
LoadI64HalfIntoRegister(VarState slot,RegPairHalf half)611 LiftoffRegister LiftoffAssembler::LoadI64HalfIntoRegister(VarState slot,
612 RegPairHalf half) {
613 if (slot.is_reg()) {
614 return half == kLowWord ? slot.reg().low() : slot.reg().high();
615 }
616 LiftoffRegister dst = GetUnusedRegister(kGpReg, {});
617 if (slot.is_stack()) {
618 FillI64Half(dst.gp(), slot.offset(), half);
619 return dst;
620 }
621 DCHECK(slot.is_const());
622 int32_t half_word =
623 static_cast<int32_t>(half == kLowWord ? slot.constant().to_i64()
624 : slot.constant().to_i64() >> 32);
625 LoadConstant(dst, WasmValue(half_word));
626 return dst;
627 }
628
PeekToRegister(int index,LiftoffRegList pinned)629 LiftoffRegister LiftoffAssembler::PeekToRegister(int index,
630 LiftoffRegList pinned) {
631 DCHECK_LT(index, cache_state_.stack_state.size());
632 VarState& slot = cache_state_.stack_state.end()[-1 - index];
633 if (slot.is_reg()) {
634 return slot.reg();
635 }
636 LiftoffRegister reg = LoadToRegister(slot, pinned);
637 cache_state_.inc_used(reg);
638 slot.MakeRegister(reg);
639 return reg;
640 }
641
DropValues(int count)642 void LiftoffAssembler::DropValues(int count) {
643 for (int i = 0; i < count; ++i) {
644 DCHECK(!cache_state_.stack_state.empty());
645 VarState slot = cache_state_.stack_state.back();
646 cache_state_.stack_state.pop_back();
647 if (slot.is_reg()) {
648 cache_state_.dec_used(slot.reg());
649 }
650 }
651 }
652
DropValue(int depth)653 void LiftoffAssembler::DropValue(int depth) {
654 auto* dropped = cache_state_.stack_state.begin() + depth;
655 if (dropped->is_reg()) {
656 cache_state_.dec_used(dropped->reg());
657 }
658 std::copy(dropped + 1, cache_state_.stack_state.end(), dropped);
659 cache_state_.stack_state.pop_back();
660 }
661
PrepareLoopArgs(int num)662 void LiftoffAssembler::PrepareLoopArgs(int num) {
663 for (int i = 0; i < num; ++i) {
664 VarState& slot = cache_state_.stack_state.end()[-1 - i];
665 if (slot.is_stack()) continue;
666 RegClass rc = reg_class_for(slot.kind());
667 if (slot.is_reg()) {
668 if (cache_state_.get_use_count(slot.reg()) > 1) {
669 // If the register is used more than once, we cannot use it for the
670 // merge. Move it to an unused register instead.
671 LiftoffRegList pinned;
672 pinned.set(slot.reg());
673 LiftoffRegister dst_reg = GetUnusedRegister(rc, pinned);
674 Move(dst_reg, slot.reg(), slot.kind());
675 cache_state_.dec_used(slot.reg());
676 cache_state_.inc_used(dst_reg);
677 slot.MakeRegister(dst_reg);
678 }
679 continue;
680 }
681 LiftoffRegister reg = GetUnusedRegister(rc, {});
682 LoadConstant(reg, slot.constant());
683 slot.MakeRegister(reg);
684 cache_state_.inc_used(reg);
685 }
686 }
687
MaterializeMergedConstants(uint32_t arity)688 void LiftoffAssembler::MaterializeMergedConstants(uint32_t arity) {
689 // Materialize constants on top of the stack ({arity} many), and locals.
690 VarState* stack_base = cache_state_.stack_state.data();
691 for (auto slots :
692 {base::VectorOf(stack_base + cache_state_.stack_state.size() - arity,
693 arity),
694 base::VectorOf(stack_base, num_locals())}) {
695 for (VarState& slot : slots) {
696 if (!slot.is_const()) continue;
697 RegClass rc = reg_class_for(slot.kind());
698 if (cache_state_.has_unused_register(rc)) {
699 LiftoffRegister reg = cache_state_.unused_register(rc);
700 LoadConstant(reg, slot.constant());
701 cache_state_.inc_used(reg);
702 slot.MakeRegister(reg);
703 } else {
704 Spill(slot.offset(), slot.constant());
705 slot.MakeStack();
706 }
707 }
708 }
709 }
710
MergeFullStackWith(CacheState & target,const CacheState & source)711 void LiftoffAssembler::MergeFullStackWith(CacheState& target,
712 const CacheState& source) {
713 DCHECK_EQ(source.stack_height(), target.stack_height());
714 // TODO(clemensb): Reuse the same StackTransferRecipe object to save some
715 // allocations.
716 StackTransferRecipe transfers(this);
717 for (uint32_t i = 0, e = source.stack_height(); i < e; ++i) {
718 transfers.TransferStackSlot(target.stack_state[i], source.stack_state[i]);
719 }
720
721 // Full stack merging is only done for forward jumps, so we can just clear the
722 // cache registers at the target in case of mismatch.
723 if (source.cached_instance != target.cached_instance) {
724 target.ClearCachedInstanceRegister();
725 }
726 if (source.cached_mem_start != target.cached_mem_start) {
727 target.ClearCachedMemStartRegister();
728 }
729 }
730
MergeStackWith(CacheState & target,uint32_t arity,JumpDirection jump_direction)731 void LiftoffAssembler::MergeStackWith(CacheState& target, uint32_t arity,
732 JumpDirection jump_direction) {
733 // Before: ----------------|----- (discarded) ----|--- arity ---|
734 // ^target_stack_height ^stack_base ^stack_height
735 // After: ----|-- arity --|
736 // ^ ^target_stack_height
737 // ^target_stack_base
738 uint32_t stack_height = cache_state_.stack_height();
739 uint32_t target_stack_height = target.stack_height();
740 DCHECK_LE(target_stack_height, stack_height);
741 DCHECK_LE(arity, target_stack_height);
742 uint32_t stack_base = stack_height - arity;
743 uint32_t target_stack_base = target_stack_height - arity;
744 StackTransferRecipe transfers(this);
745 for (uint32_t i = 0; i < target_stack_base; ++i) {
746 transfers.TransferStackSlot(target.stack_state[i],
747 cache_state_.stack_state[i]);
748 }
749 for (uint32_t i = 0; i < arity; ++i) {
750 transfers.TransferStackSlot(target.stack_state[target_stack_base + i],
751 cache_state_.stack_state[stack_base + i]);
752 }
753
754 // Check whether the cached instance and/or memory start need to be moved to
755 // another register. Register moves are executed as part of the
756 // {StackTransferRecipe}. Remember whether the register content has to be
757 // reloaded after executing the stack transfers.
758 bool reload_instance = false;
759 bool reload_mem_start = false;
760 for (auto tuple :
761 {std::make_tuple(&reload_instance, cache_state_.cached_instance,
762 &target.cached_instance),
763 std::make_tuple(&reload_mem_start, cache_state_.cached_mem_start,
764 &target.cached_mem_start)}) {
765 bool* reload = std::get<0>(tuple);
766 Register src_reg = std::get<1>(tuple);
767 Register* dst_reg = std::get<2>(tuple);
768 // If the registers match, or the destination has no cache register, nothing
769 // needs to be done.
770 if (src_reg == *dst_reg || *dst_reg == no_reg) continue;
771 // On forward jumps, just reset the cached register in the target state.
772 if (jump_direction == kForwardJump) {
773 target.ClearCacheRegister(dst_reg);
774 } else if (src_reg != no_reg) {
775 // If the source has the content but in the wrong register, execute a
776 // register move as part of the stack transfer.
777 transfers.MoveRegister(LiftoffRegister{*dst_reg},
778 LiftoffRegister{src_reg}, kPointerKind);
779 } else {
780 // Otherwise (the source state has no cached content), we reload later.
781 *reload = true;
782 }
783 }
784
785 // Now execute stack transfers and register moves/loads.
786 transfers.Execute();
787
788 if (reload_instance) {
789 LoadInstanceFromFrame(target.cached_instance);
790 }
791 if (reload_mem_start) {
792 // {target.cached_instance} already got restored above, so we can use it
793 // if it exists.
794 Register instance = target.cached_instance;
795 if (instance == no_reg) {
796 // We don't have the instance available yet. Store it into the target
797 // mem_start, so that we can load the mem_start from there.
798 instance = target.cached_mem_start;
799 LoadInstanceFromFrame(instance);
800 }
801 LoadFromInstance(
802 target.cached_mem_start, instance,
803 ObjectAccess::ToTagged(WasmInstanceObject::kMemoryStartOffset),
804 sizeof(size_t));
805 }
806 }
807
Spill(VarState * slot)808 void LiftoffAssembler::Spill(VarState* slot) {
809 switch (slot->loc()) {
810 case VarState::kStack:
811 return;
812 case VarState::kRegister:
813 Spill(slot->offset(), slot->reg(), slot->kind());
814 cache_state_.dec_used(slot->reg());
815 break;
816 case VarState::kIntConst:
817 Spill(slot->offset(), slot->constant());
818 break;
819 }
820 slot->MakeStack();
821 }
822
SpillLocals()823 void LiftoffAssembler::SpillLocals() {
824 for (uint32_t i = 0; i < num_locals_; ++i) {
825 Spill(&cache_state_.stack_state[i]);
826 }
827 }
828
SpillAllRegisters()829 void LiftoffAssembler::SpillAllRegisters() {
830 for (uint32_t i = 0, e = cache_state_.stack_height(); i < e; ++i) {
831 auto& slot = cache_state_.stack_state[i];
832 if (!slot.is_reg()) continue;
833 Spill(slot.offset(), slot.reg(), slot.kind());
834 slot.MakeStack();
835 }
836 cache_state_.ClearAllCacheRegisters();
837 cache_state_.reset_used_registers();
838 }
839
ClearRegister(Register reg,std::initializer_list<Register * > possible_uses,LiftoffRegList pinned)840 void LiftoffAssembler::ClearRegister(
841 Register reg, std::initializer_list<Register*> possible_uses,
842 LiftoffRegList pinned) {
843 if (reg == cache_state()->cached_instance) {
844 cache_state()->ClearCachedInstanceRegister();
845 // We can return immediately. The instance is only used to load information
846 // at the beginning of an instruction when values don't have to be in
847 // specific registers yet. Therefore the instance should never be one of the
848 // {possible_uses}.
849 for (Register* use : possible_uses) {
850 USE(use);
851 DCHECK_NE(reg, *use);
852 }
853 return;
854 } else if (reg == cache_state()->cached_mem_start) {
855 cache_state()->ClearCachedMemStartRegister();
856 // The memory start may be among the {possible_uses}, e.g. for an atomic
857 // compare exchange. Therefore it is necessary to iterate over the
858 // {possible_uses} below, and we cannot return early.
859 } else if (cache_state()->is_used(LiftoffRegister(reg))) {
860 SpillRegister(LiftoffRegister(reg));
861 }
862 Register replacement = no_reg;
863 for (Register* use : possible_uses) {
864 if (reg != *use) continue;
865 if (replacement == no_reg) {
866 replacement = GetUnusedRegister(kGpReg, pinned).gp();
867 Move(replacement, reg, kPointerKind);
868 }
869 // We cannot leave this loop early. There may be multiple uses of {reg}.
870 *use = replacement;
871 }
872 }
873
874 namespace {
PrepareStackTransfers(const ValueKindSig * sig,compiler::CallDescriptor * call_descriptor,const VarState * slots,LiftoffStackSlots * stack_slots,StackTransferRecipe * stack_transfers,LiftoffRegList * param_regs)875 void PrepareStackTransfers(const ValueKindSig* sig,
876 compiler::CallDescriptor* call_descriptor,
877 const VarState* slots,
878 LiftoffStackSlots* stack_slots,
879 StackTransferRecipe* stack_transfers,
880 LiftoffRegList* param_regs) {
881 // Process parameters backwards, to reduce the amount of Slot sorting for
882 // the most common case - a normal Wasm Call. Slots will be mostly unsorted
883 // in the Builtin call case.
884 uint32_t call_desc_input_idx =
885 static_cast<uint32_t>(call_descriptor->InputCount());
886 uint32_t num_params = static_cast<uint32_t>(sig->parameter_count());
887 for (uint32_t i = num_params; i > 0; --i) {
888 const uint32_t param = i - 1;
889 ValueKind kind = sig->GetParam(param);
890 const bool is_gp_pair = kNeedI64RegPair && kind == kI64;
891 const int num_lowered_params = is_gp_pair ? 2 : 1;
892 const VarState& slot = slots[param];
893 const uint32_t stack_offset = slot.offset();
894 // Process both halfs of a register pair separately, because they are passed
895 // as separate parameters. One or both of them could end up on the stack.
896 for (int lowered_idx = 0; lowered_idx < num_lowered_params; ++lowered_idx) {
897 const RegPairHalf half =
898 is_gp_pair && lowered_idx == 0 ? kHighWord : kLowWord;
899 --call_desc_input_idx;
900 compiler::LinkageLocation loc =
901 call_descriptor->GetInputLocation(call_desc_input_idx);
902 if (loc.IsRegister()) {
903 DCHECK(!loc.IsAnyRegister());
904 RegClass rc = is_gp_pair ? kGpReg : reg_class_for(kind);
905 int reg_code = loc.AsRegister();
906 LiftoffRegister reg =
907 LiftoffRegister::from_external_code(rc, kind, reg_code);
908 param_regs->set(reg);
909 if (is_gp_pair) {
910 stack_transfers->LoadI64HalfIntoRegister(reg, slot, stack_offset,
911 half);
912 } else {
913 stack_transfers->LoadIntoRegister(reg, slot, stack_offset);
914 }
915 } else {
916 DCHECK(loc.IsCallerFrameSlot());
917 int param_offset = -loc.GetLocation() - 1;
918 stack_slots->Add(slot, stack_offset, half, param_offset);
919 }
920 }
921 }
922 }
923
924 } // namespace
925
PrepareBuiltinCall(const ValueKindSig * sig,compiler::CallDescriptor * call_descriptor,std::initializer_list<VarState> params)926 void LiftoffAssembler::PrepareBuiltinCall(
927 const ValueKindSig* sig, compiler::CallDescriptor* call_descriptor,
928 std::initializer_list<VarState> params) {
929 LiftoffStackSlots stack_slots(this);
930 StackTransferRecipe stack_transfers(this);
931 LiftoffRegList param_regs;
932 PrepareStackTransfers(sig, call_descriptor, params.begin(), &stack_slots,
933 &stack_transfers, ¶m_regs);
934 SpillAllRegisters();
935 int param_slots = static_cast<int>(call_descriptor->ParameterSlotCount());
936 if (param_slots > 0) {
937 stack_slots.Construct(param_slots);
938 }
939 // Execute the stack transfers before filling the instance register.
940 stack_transfers.Execute();
941
942 // Reset register use counters.
943 cache_state_.reset_used_registers();
944 }
945
PrepareCall(const ValueKindSig * sig,compiler::CallDescriptor * call_descriptor,Register * target,Register * target_instance)946 void LiftoffAssembler::PrepareCall(const ValueKindSig* sig,
947 compiler::CallDescriptor* call_descriptor,
948 Register* target,
949 Register* target_instance) {
950 uint32_t num_params = static_cast<uint32_t>(sig->parameter_count());
951 // Input 0 is the call target.
952 constexpr size_t kInputShift = 1;
953
954 // Spill all cache slots which are not being used as parameters.
955 cache_state_.ClearAllCacheRegisters();
956 for (VarState* it = cache_state_.stack_state.end() - 1 - num_params;
957 it >= cache_state_.stack_state.begin() &&
958 !cache_state_.used_registers.is_empty();
959 --it) {
960 if (!it->is_reg()) continue;
961 Spill(it->offset(), it->reg(), it->kind());
962 cache_state_.dec_used(it->reg());
963 it->MakeStack();
964 }
965
966 LiftoffStackSlots stack_slots(this);
967 StackTransferRecipe stack_transfers(this);
968 LiftoffRegList param_regs;
969
970 // Move the target instance (if supplied) into the correct instance register.
971 compiler::LinkageLocation instance_loc =
972 call_descriptor->GetInputLocation(kInputShift);
973 DCHECK(instance_loc.IsRegister() && !instance_loc.IsAnyRegister());
974 Register instance_reg = Register::from_code(instance_loc.AsRegister());
975 param_regs.set(instance_reg);
976 if (target_instance && *target_instance != instance_reg) {
977 stack_transfers.MoveRegister(LiftoffRegister(instance_reg),
978 LiftoffRegister(*target_instance),
979 kPointerKind);
980 }
981
982 int param_slots = static_cast<int>(call_descriptor->ParameterSlotCount());
983 if (num_params) {
984 uint32_t param_base = cache_state_.stack_height() - num_params;
985 PrepareStackTransfers(sig, call_descriptor,
986 &cache_state_.stack_state[param_base], &stack_slots,
987 &stack_transfers, ¶m_regs);
988 }
989
990 // If the target register overlaps with a parameter register, then move the
991 // target to another free register, or spill to the stack.
992 if (target && param_regs.has(LiftoffRegister(*target))) {
993 // Try to find another free register.
994 LiftoffRegList free_regs = kGpCacheRegList.MaskOut(param_regs);
995 if (!free_regs.is_empty()) {
996 LiftoffRegister new_target = free_regs.GetFirstRegSet();
997 stack_transfers.MoveRegister(new_target, LiftoffRegister(*target),
998 kPointerKind);
999 *target = new_target.gp();
1000 } else {
1001 stack_slots.Add(VarState(kPointerKind, LiftoffRegister(*target), 0),
1002 param_slots);
1003 param_slots++;
1004 *target = no_reg;
1005 }
1006 }
1007
1008 if (param_slots > 0) {
1009 stack_slots.Construct(param_slots);
1010 }
1011 // Execute the stack transfers before filling the instance register.
1012 stack_transfers.Execute();
1013 // Pop parameters from the value stack.
1014 cache_state_.stack_state.pop_back(num_params);
1015
1016 // Reset register use counters.
1017 cache_state_.reset_used_registers();
1018
1019 // Reload the instance from the stack.
1020 if (!target_instance) {
1021 FillInstanceInto(instance_reg);
1022 }
1023 }
1024
FinishCall(const ValueKindSig * sig,compiler::CallDescriptor * call_descriptor)1025 void LiftoffAssembler::FinishCall(const ValueKindSig* sig,
1026 compiler::CallDescriptor* call_descriptor) {
1027 int call_desc_return_idx = 0;
1028 for (ValueKind return_kind : sig->returns()) {
1029 DCHECK_LT(call_desc_return_idx, call_descriptor->ReturnCount());
1030 const bool needs_gp_pair = needs_gp_reg_pair(return_kind);
1031 const int num_lowered_params = 1 + needs_gp_pair;
1032 const ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
1033 const RegClass rc = reg_class_for(lowered_kind);
1034 // Initialize to anything, will be set in the loop and used afterwards.
1035 LiftoffRegister reg_pair[2] = {kGpCacheRegList.GetFirstRegSet(),
1036 kGpCacheRegList.GetFirstRegSet()};
1037 LiftoffRegList pinned;
1038 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
1039 compiler::LinkageLocation loc =
1040 call_descriptor->GetReturnLocation(call_desc_return_idx++);
1041 if (loc.IsRegister()) {
1042 DCHECK(!loc.IsAnyRegister());
1043 reg_pair[pair_idx] = LiftoffRegister::from_external_code(
1044 rc, lowered_kind, loc.AsRegister());
1045 } else {
1046 DCHECK(loc.IsCallerFrameSlot());
1047 reg_pair[pair_idx] = GetUnusedRegister(rc, pinned);
1048 // Get slot offset relative to the stack pointer.
1049 int offset = call_descriptor->GetOffsetToReturns();
1050 int return_slot = -loc.GetLocation() - offset - 1;
1051 LoadReturnStackSlot(reg_pair[pair_idx],
1052 return_slot * kSystemPointerSize, lowered_kind);
1053 }
1054 if (pair_idx == 0) {
1055 pinned.set(reg_pair[0]);
1056 }
1057 }
1058 if (num_lowered_params == 1) {
1059 PushRegister(return_kind, reg_pair[0]);
1060 } else {
1061 PushRegister(return_kind, LiftoffRegister::ForPair(reg_pair[0].gp(),
1062 reg_pair[1].gp()));
1063 }
1064 }
1065 int return_slots = static_cast<int>(call_descriptor->ReturnSlotCount());
1066 RecordUsedSpillOffset(TopSpillOffset() + return_slots * kSystemPointerSize);
1067 }
1068
Move(LiftoffRegister dst,LiftoffRegister src,ValueKind kind)1069 void LiftoffAssembler::Move(LiftoffRegister dst, LiftoffRegister src,
1070 ValueKind kind) {
1071 DCHECK_EQ(dst.reg_class(), src.reg_class());
1072 DCHECK_NE(dst, src);
1073 if (kNeedI64RegPair && dst.is_gp_pair()) {
1074 // Use the {StackTransferRecipe} to move pairs, as the registers in the
1075 // pairs might overlap.
1076 StackTransferRecipe(this).MoveRegister(dst, src, kind);
1077 } else if (kNeedS128RegPair && dst.is_fp_pair()) {
1078 // Calling low_fp is fine, Move will automatically check the kind and
1079 // convert this FP to its SIMD register, and use a SIMD move.
1080 Move(dst.low_fp(), src.low_fp(), kind);
1081 } else if (dst.is_gp()) {
1082 Move(dst.gp(), src.gp(), kind);
1083 } else {
1084 Move(dst.fp(), src.fp(), kind);
1085 }
1086 }
1087
ParallelRegisterMove(base::Vector<const ParallelRegisterMoveTuple> tuples)1088 void LiftoffAssembler::ParallelRegisterMove(
1089 base::Vector<const ParallelRegisterMoveTuple> tuples) {
1090 StackTransferRecipe stack_transfers(this);
1091 for (auto tuple : tuples) {
1092 if (tuple.dst == tuple.src) continue;
1093 stack_transfers.MoveRegister(tuple.dst, tuple.src, tuple.kind);
1094 }
1095 }
1096
MoveToReturnLocations(const FunctionSig * sig,compiler::CallDescriptor * descriptor)1097 void LiftoffAssembler::MoveToReturnLocations(
1098 const FunctionSig* sig, compiler::CallDescriptor* descriptor) {
1099 StackTransferRecipe stack_transfers(this);
1100 if (sig->return_count() == 1) {
1101 ValueKind return_kind = sig->GetReturn(0).kind();
1102 // Defaults to a gp reg, will be set below if return kind is not gp.
1103 LiftoffRegister return_reg = LiftoffRegister(kGpReturnRegisters[0]);
1104
1105 if (needs_gp_reg_pair(return_kind)) {
1106 return_reg = LiftoffRegister::ForPair(kGpReturnRegisters[0],
1107 kGpReturnRegisters[1]);
1108 } else if (needs_fp_reg_pair(return_kind)) {
1109 return_reg = LiftoffRegister::ForFpPair(kFpReturnRegisters[0]);
1110 } else if (reg_class_for(return_kind) == kFpReg) {
1111 return_reg = LiftoffRegister(kFpReturnRegisters[0]);
1112 } else {
1113 DCHECK_EQ(kGpReg, reg_class_for(return_kind));
1114 }
1115 stack_transfers.LoadIntoRegister(return_reg,
1116 cache_state_.stack_state.back(),
1117 cache_state_.stack_state.back().offset());
1118 return;
1119 }
1120
1121 // Slow path for multi-return.
1122 int call_desc_return_idx = 0;
1123 DCHECK_LE(sig->return_count(), cache_state_.stack_height());
1124 VarState* slots = cache_state_.stack_state.end() - sig->return_count();
1125 // Fill return frame slots first to ensure that all potential spills happen
1126 // before we prepare the stack transfers.
1127 for (size_t i = 0; i < sig->return_count(); ++i) {
1128 ValueKind return_kind = sig->GetReturn(i).kind();
1129 bool needs_gp_pair = needs_gp_reg_pair(return_kind);
1130 int num_lowered_params = 1 + needs_gp_pair;
1131 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
1132 compiler::LinkageLocation loc =
1133 descriptor->GetReturnLocation(call_desc_return_idx++);
1134 if (loc.IsCallerFrameSlot()) {
1135 RegPairHalf half = pair_idx == 0 ? kLowWord : kHighWord;
1136 VarState& slot = slots[i];
1137 LiftoffRegister reg = needs_gp_pair
1138 ? LoadI64HalfIntoRegister(slot, half)
1139 : LoadToRegister(slot, {});
1140 ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
1141 StoreCallerFrameSlot(reg, -loc.AsCallerFrameSlot(), lowered_kind);
1142 }
1143 }
1144 }
1145 // Prepare and execute stack transfers.
1146 call_desc_return_idx = 0;
1147 for (size_t i = 0; i < sig->return_count(); ++i) {
1148 ValueKind return_kind = sig->GetReturn(i).kind();
1149 bool needs_gp_pair = needs_gp_reg_pair(return_kind);
1150 int num_lowered_params = 1 + needs_gp_pair;
1151 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
1152 RegPairHalf half = pair_idx == 0 ? kLowWord : kHighWord;
1153 compiler::LinkageLocation loc =
1154 descriptor->GetReturnLocation(call_desc_return_idx++);
1155 if (loc.IsRegister()) {
1156 DCHECK(!loc.IsAnyRegister());
1157 int reg_code = loc.AsRegister();
1158 ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
1159 RegClass rc = reg_class_for(lowered_kind);
1160 LiftoffRegister reg =
1161 LiftoffRegister::from_external_code(rc, return_kind, reg_code);
1162 VarState& slot = slots[i];
1163 if (needs_gp_pair) {
1164 stack_transfers.LoadI64HalfIntoRegister(reg, slot, slot.offset(),
1165 half);
1166 } else {
1167 stack_transfers.LoadIntoRegister(reg, slot, slot.offset());
1168 }
1169 }
1170 }
1171 }
1172 }
1173
1174 #ifdef ENABLE_SLOW_DCHECKS
ValidateCacheState() const1175 bool LiftoffAssembler::ValidateCacheState() const {
1176 uint32_t register_use_count[kAfterMaxLiftoffRegCode] = {0};
1177 LiftoffRegList used_regs;
1178 for (const VarState& var : cache_state_.stack_state) {
1179 if (!var.is_reg()) continue;
1180 LiftoffRegister reg = var.reg();
1181 if ((kNeedI64RegPair || kNeedS128RegPair) && reg.is_pair()) {
1182 ++register_use_count[reg.low().liftoff_code()];
1183 ++register_use_count[reg.high().liftoff_code()];
1184 } else {
1185 ++register_use_count[reg.liftoff_code()];
1186 }
1187 used_regs.set(reg);
1188 }
1189 for (Register cache_reg :
1190 {cache_state_.cached_instance, cache_state_.cached_mem_start}) {
1191 if (cache_reg != no_reg) {
1192 DCHECK(!used_regs.has(cache_reg));
1193 int liftoff_code = LiftoffRegister{cache_reg}.liftoff_code();
1194 used_regs.set(cache_reg);
1195 DCHECK_EQ(0, register_use_count[liftoff_code]);
1196 register_use_count[liftoff_code] = 1;
1197 }
1198 }
1199 bool valid = memcmp(register_use_count, cache_state_.register_use_count,
1200 sizeof(register_use_count)) == 0 &&
1201 used_regs == cache_state_.used_registers;
1202 if (valid) return true;
1203 std::ostringstream os;
1204 os << "Error in LiftoffAssembler::ValidateCacheState().\n";
1205 os << "expected: used_regs " << used_regs << ", counts "
1206 << PrintCollection(register_use_count) << "\n";
1207 os << "found: used_regs " << cache_state_.used_registers << ", counts "
1208 << PrintCollection(cache_state_.register_use_count) << "\n";
1209 os << "Use --trace-wasm-decoder and --trace-liftoff to debug.";
1210 FATAL("%s", os.str().c_str());
1211 }
1212 #endif
1213
SpillOneRegister(LiftoffRegList candidates)1214 LiftoffRegister LiftoffAssembler::SpillOneRegister(LiftoffRegList candidates) {
1215 // Spill one cached value to free a register.
1216 LiftoffRegister spill_reg = cache_state_.GetNextSpillReg(candidates);
1217 SpillRegister(spill_reg);
1218 return spill_reg;
1219 }
1220
SpillAdjacentFpRegisters(LiftoffRegList pinned)1221 LiftoffRegister LiftoffAssembler::SpillAdjacentFpRegisters(
1222 LiftoffRegList pinned) {
1223 // We end up in this call only when:
1224 // [1] kNeedS128RegPair, and
1225 // [2] there are no pair of adjacent FP registers that are free
1226 CHECK(kNeedS128RegPair);
1227 DCHECK(!kFpCacheRegList.MaskOut(pinned)
1228 .MaskOut(cache_state_.used_registers)
1229 .HasAdjacentFpRegsSet());
1230
1231 // Special logic, if the top fp register is even, we might hit a case of an
1232 // invalid register in case 2.
1233 LiftoffRegister last_fp = kFpCacheRegList.GetLastRegSet();
1234 if (last_fp.fp().code() % 2 == 0) {
1235 pinned.set(last_fp);
1236 }
1237
1238 // We can try to optimize the spilling here:
1239 // 1. Try to get a free fp register, either:
1240 // a. This register is already free, or
1241 // b. it had to be spilled.
1242 // 2. If 1a, the adjacent register is used (invariant [2]), spill it.
1243 // 3. If 1b, check the adjacent register:
1244 // a. If free, done!
1245 // b. If used, spill it.
1246 // We spill one register in 2 and 3a, and two registers in 3b.
1247
1248 LiftoffRegister first_reg = GetUnusedRegister(kFpReg, pinned);
1249 LiftoffRegister second_reg = first_reg, low_reg = first_reg;
1250
1251 if (first_reg.fp().code() % 2 == 0) {
1252 second_reg =
1253 LiftoffRegister::from_liftoff_code(first_reg.liftoff_code() + 1);
1254 } else {
1255 second_reg =
1256 LiftoffRegister::from_liftoff_code(first_reg.liftoff_code() - 1);
1257 low_reg = second_reg;
1258 }
1259
1260 if (cache_state_.is_used(second_reg)) {
1261 SpillRegister(second_reg);
1262 }
1263
1264 return low_reg;
1265 }
1266
SpillRegister(LiftoffRegister reg)1267 void LiftoffAssembler::SpillRegister(LiftoffRegister reg) {
1268 int remaining_uses = cache_state_.get_use_count(reg);
1269 DCHECK_LT(0, remaining_uses);
1270 for (uint32_t idx = cache_state_.stack_height() - 1;; --idx) {
1271 DCHECK_GT(cache_state_.stack_height(), idx);
1272 auto* slot = &cache_state_.stack_state[idx];
1273 if (!slot->is_reg() || !slot->reg().overlaps(reg)) continue;
1274 if (slot->reg().is_pair()) {
1275 // Make sure to decrement *both* registers in a pair, because the
1276 // {clear_used} call below only clears one of them.
1277 cache_state_.dec_used(slot->reg().low());
1278 cache_state_.dec_used(slot->reg().high());
1279 cache_state_.last_spilled_regs.set(slot->reg().low());
1280 cache_state_.last_spilled_regs.set(slot->reg().high());
1281 }
1282 Spill(slot->offset(), slot->reg(), slot->kind());
1283 slot->MakeStack();
1284 if (--remaining_uses == 0) break;
1285 }
1286 cache_state_.clear_used(reg);
1287 cache_state_.last_spilled_regs.set(reg);
1288 }
1289
set_num_locals(uint32_t num_locals)1290 void LiftoffAssembler::set_num_locals(uint32_t num_locals) {
1291 DCHECK_EQ(0, num_locals_); // only call this once.
1292 num_locals_ = num_locals;
1293 if (num_locals > kInlineLocalKinds) {
1294 more_local_kinds_ = reinterpret_cast<ValueKind*>(
1295 base::Malloc(num_locals * sizeof(ValueKind)));
1296 DCHECK_NOT_NULL(more_local_kinds_);
1297 }
1298 }
1299
operator <<(std::ostream & os,VarState slot)1300 std::ostream& operator<<(std::ostream& os, VarState slot) {
1301 os << name(slot.kind()) << ":";
1302 switch (slot.loc()) {
1303 case VarState::kStack:
1304 return os << "s";
1305 case VarState::kRegister:
1306 return os << slot.reg();
1307 case VarState::kIntConst:
1308 return os << "c" << slot.i32_const();
1309 }
1310 UNREACHABLE();
1311 }
1312
1313 #if DEBUG
CheckCompatibleStackSlotTypes(ValueKind a,ValueKind b)1314 bool CheckCompatibleStackSlotTypes(ValueKind a, ValueKind b) {
1315 if (is_object_reference(a)) {
1316 // Since Liftoff doesn't do accurate type tracking (e.g. on loop back
1317 // edges), we only care that pointer types stay amongst pointer types.
1318 // It's fine if ref/optref overwrite each other.
1319 DCHECK(is_object_reference(b));
1320 } else if (is_rtt(a)) {
1321 // Same for rtt/rtt_with_depth.
1322 DCHECK(is_rtt(b));
1323 } else {
1324 // All other types (primitive numbers, bottom/stmt) must be equal.
1325 DCHECK_EQ(a, b);
1326 }
1327 return true; // Dummy so this can be called via DCHECK.
1328 }
1329 #endif
1330
1331 } // namespace wasm
1332 } // namespace internal
1333 } // namespace v8
1334