5 #include "src/wasm/baseline/liftoff-assembler.h" 9 #include "src/assembler-inl.h" 10 #include "src/compiler/linkage.h" 11 #include "src/compiler/wasm-compiler.h" 12 #include "src/macro-assembler-inl.h" 13 #include "src/wasm/function-body-decoder-impl.h" 14 #include "src/wasm/wasm-opcodes.h" 20 using VarState = LiftoffAssembler::VarState;
28 if (FLAG_trace_liftoff) PrintF("[liftoff] " __VA_ARGS__); \ 31 class StackTransferRecipe {
36 constexpr RegisterMove(LiftoffRegister dst, LiftoffRegister src,
38 : dst(dst), src(src), type(type) {}
41 enum LoadKind : uint8_t {
53 static RegisterLoad Const(LiftoffRegister dst, WasmValue constant) {
54 if (constant.type() == kWasmI32) {
55 return {dst, kConstant, kWasmI32, constant.to_i32()};
57 DCHECK_EQ(kWasmI64, constant.type());
58 DCHECK_EQ(constant.to_i32_unchecked(), constant.to_i64_unchecked());
59 return {dst, kConstant, kWasmI64, constant.to_i32_unchecked()};
61 static RegisterLoad Stack(LiftoffRegister dst, int32_t stack_index,
63 return {dst, kStack, type, stack_index};
65 static RegisterLoad HalfStack(LiftoffRegister dst,
66 int32_t half_stack_index) {
67 return {dst, kHalfStack, kWasmI32, half_stack_index};
71 RegisterLoad(LiftoffRegister dst, LoadKind kind, ValueType type,
73 : dst(dst), kind(kind), type(type), value(value) {}
77 explicit StackTransferRecipe(LiftoffAssembler* wasm_asm) : asm_(wasm_asm) {}
78 ~StackTransferRecipe() { Execute(); }
84 if ((move_dst_regs_ & move_src_regs_).is_empty()) {
87 for (RegisterMove& rm : register_moves_) {
88 asm_->Move(rm.dst, rm.src, rm.type);
90 register_moves_.clear();
93 uint32_t src_reg_use_count[kAfterMaxLiftoffRegCode] = {0};
94 for (RegisterMove& rm : register_moves_) {
95 ++src_reg_use_count[rm.src.liftoff_code()];
103 uint32_t next_spill_slot = asm_->cache_state()->stack_height();
104 while (!register_moves_.empty()) {
105 int executed_moves = 0;
106 for (
auto& rm : register_moves_) {
107 if (src_reg_use_count[rm.dst.liftoff_code()] == 0) {
108 asm_->Move(rm.dst, rm.src, rm.type);
110 DCHECK_LT(0, src_reg_use_count[rm.src.liftoff_code()]);
111 --src_reg_use_count[rm.src.liftoff_code()];
112 }
else if (executed_moves) {
114 (&rm)[-executed_moves] = rm;
117 if (executed_moves == 0) {
120 RegisterMove& rm = register_moves_.back();
121 LiftoffRegister spill_reg = rm.src;
122 asm_->Spill(next_spill_slot, spill_reg, rm.type);
124 LoadStackSlot(register_moves_.back().dst, next_spill_slot, rm.type);
125 DCHECK_EQ(1, src_reg_use_count[spill_reg.liftoff_code()]);
126 src_reg_use_count[spill_reg.liftoff_code()] = 0;
130 register_moves_.erase(register_moves_.end() - executed_moves,
131 register_moves_.end());
135 for (RegisterLoad& rl : register_loads_) {
137 case RegisterLoad::kConstant:
138 asm_->LoadConstant(rl.dst, rl.type == kWasmI64
140 : WasmValue(int32_t{rl.value}));
142 case RegisterLoad::kStack:
143 asm_->Fill(rl.dst, rl.value, rl.type);
145 case RegisterLoad::kHalfStack:
147 asm_->FillI64Half(rl.dst.gp(), rl.value);
151 register_loads_.clear();
154 void TransferStackSlot(
const LiftoffAssembler::CacheState& dst_state,
156 const VarState& dst = dst_state.stack_state[dst_index];
157 const VarState& src = __ cache_state()->stack_state[src_index];
158 DCHECK_EQ(dst.type(), src.type());
160 case VarState::kStack:
162 case VarState::kStack:
163 if (src_index == dst_index)
break;
164 asm_->MoveStackValue(dst_index, src_index, src.type());
166 case VarState::kRegister:
167 asm_->Spill(dst_index, src.reg(), src.type());
169 case VarState::KIntConst:
170 asm_->Spill(dst_index, src.constant());
174 case VarState::kRegister:
175 LoadIntoRegister(dst.reg(), src, src_index);
177 case VarState::KIntConst:
183 void LoadIntoRegister(LiftoffRegister dst,
184 const LiftoffAssembler::VarState& src,
187 case VarState::kStack:
188 LoadStackSlot(dst, src_index, src.type());
190 case VarState::kRegister:
191 DCHECK_EQ(dst.reg_class(), src.reg_class());
192 if (dst != src.reg()) MoveRegister(dst, src.reg(), src.type());
194 case VarState::KIntConst:
195 LoadConstant(dst, src.constant());
200 void LoadI64HalfIntoRegister(LiftoffRegister dst,
201 const LiftoffAssembler::VarState& src,
205 CHECK(kNeedI64RegPair);
206 DCHECK_EQ(kWasmI64, src.type());
208 case VarState::kStack:
209 LoadI64HalfStackSlot(dst, 2 * index - (half == kLowWord ? 0 : 1));
211 case VarState::kRegister: {
212 LiftoffRegister src_half =
213 half == kLowWord ? src.reg().low() : src.reg().high();
214 if (dst != src_half) MoveRegister(dst, src_half, kWasmI32);
217 case VarState::KIntConst:
218 int32_t value = src.i32_const();
220 if (half == kHighWord) value = value >> 31;
221 LoadConstant(dst, WasmValue(value));
226 void MoveRegister(LiftoffRegister dst, LiftoffRegister src, ValueType type) {
228 DCHECK_EQ(dst.reg_class(), src.reg_class());
229 DCHECK_EQ(reg_class_for(type), src.reg_class());
231 DCHECK_EQ(kWasmI64, type);
232 if (dst.low() != src.low()) MoveRegister(dst.low(), src.low(), kWasmI32);
233 if (dst.high() != src.high())
234 MoveRegister(dst.high(), src.high(), kWasmI32);
237 DCHECK(!move_dst_regs_.has(dst));
238 move_dst_regs_.set(dst);
239 move_src_regs_.set(src);
240 register_moves_.emplace_back(dst, src, type);
243 void LoadConstant(LiftoffRegister dst, WasmValue value) {
244 register_loads_.push_back(RegisterLoad::Const(dst, value));
247 void LoadStackSlot(LiftoffRegister dst,
uint32_t stack_index,
249 register_loads_.push_back(RegisterLoad::Stack(dst, stack_index, type));
252 void LoadI64HalfStackSlot(LiftoffRegister dst,
uint32_t half_stack_index) {
253 register_loads_.push_back(RegisterLoad::HalfStack(dst, half_stack_index));
258 std::vector<RegisterMove> register_moves_;
259 std::vector<RegisterLoad> register_loads_;
260 LiftoffRegList move_dst_regs_;
261 LiftoffRegList move_src_regs_;
262 LiftoffAssembler*
const asm_;
268 void LiftoffAssembler::CacheState::InitMerge(
const CacheState& source,
271 DCHECK(stack_state.empty());
272 DCHECK_GE(source.stack_height(), stack_base);
273 stack_state.resize(stack_base + arity, VarState(kWasmStmt));
281 for (
int range = 0; range < 2; ++range) {
282 auto src_idx = range ? 0 : source.stack_state.size() - arity;
283 auto src_end = range ? num_locals : source.stack_state.size();
284 auto dst_idx = range ? 0 : stack_state.size() - arity;
285 for (; src_idx < src_end; ++src_idx, ++dst_idx) {
286 auto& dst = stack_state[dst_idx];
287 auto& src = source.stack_state[src_idx];
289 LiftoffRegister reg = kGpCacheRegList.GetFirstRegSet();
290 RegClass rc = src.is_reg() ? src.reg_class() : reg_class_for(src.type());
291 if (src.is_reg() && is_free(src.reg())) {
293 }
else if (has_unused_register(rc)) {
294 reg = unused_register(rc);
297 dst = VarState(src.type());
300 dst = VarState(src.type(), reg);
307 for (
uint32_t i = num_locals;
i < stack_base; ++
i) {
308 auto& dst = stack_state[
i];
309 auto& src = source.stack_state[
i];
311 if (is_used(src.reg())) {
313 dst = VarState(src.type());
315 dst = VarState(src.type(), src.reg());
318 }
else if (src.is_const()) {
321 DCHECK(src.is_stack());
323 dst = VarState(src.type());
326 last_spilled_regs = source.last_spilled_regs;
329 void LiftoffAssembler::CacheState::Steal(CacheState& source) {
331 *
this = std::move(source);
334 void LiftoffAssembler::CacheState::Split(
const CacheState& source) {
341 constexpr AssemblerOptions DefaultLiftoffOptions() {
342 return AssemblerOptions{};
349 LiftoffAssembler::LiftoffAssembler()
350 : TurboAssembler(nullptr, DefaultLiftoffOptions(), nullptr, 0,
351 CodeObjectRequired::kNo) {
352 set_abort_hard(
true);
355 LiftoffAssembler::~LiftoffAssembler() {
356 if (num_locals_ > kInlineLocalTypes) {
357 free(more_local_types_);
361 LiftoffRegister LiftoffAssembler::PopToRegister(LiftoffRegList pinned) {
362 DCHECK(!cache_state_.stack_state.empty());
363 VarState slot = cache_state_.stack_state.back();
364 cache_state_.stack_state.pop_back();
365 switch (slot.loc()) {
366 case VarState::kStack: {
367 LiftoffRegister reg =
368 GetUnusedRegister(reg_class_for(slot.type()), pinned);
369 Fill(reg, cache_state_.stack_height(), slot.type());
372 case VarState::kRegister:
373 cache_state_.dec_used(slot.reg());
375 case VarState::KIntConst: {
377 kNeedI64RegPair && slot.type() == kWasmI64 ? kGpRegPair : kGpReg;
378 LiftoffRegister reg = GetUnusedRegister(rc, pinned);
379 LoadConstant(reg, slot.constant());
386 void LiftoffAssembler::MergeFullStackWith(CacheState& target) {
387 DCHECK_EQ(cache_state_.stack_height(), target.stack_height());
390 StackTransferRecipe transfers(
this);
391 for (
uint32_t i = 0, e = cache_state_.stack_height();
i < e; ++
i) {
392 transfers.TransferStackSlot(target,
i,
i);
396 void LiftoffAssembler::MergeStackWith(CacheState& target,
uint32_t arity) {
402 uint32_t stack_height = cache_state_.stack_height();
403 uint32_t target_stack_height = target.stack_height();
404 DCHECK_LE(target_stack_height, stack_height);
405 DCHECK_LE(arity, target_stack_height);
406 uint32_t stack_base = stack_height - arity;
407 uint32_t target_stack_base = target_stack_height - arity;
408 StackTransferRecipe transfers(
this);
410 transfers.TransferStackSlot(target,
i,
i);
413 transfers.TransferStackSlot(target, target_stack_base +
i, stack_base +
i);
417 void LiftoffAssembler::Spill(
uint32_t index) {
418 auto& slot = cache_state_.stack_state[index];
419 switch (slot.loc()) {
420 case VarState::kStack:
422 case VarState::kRegister:
423 Spill(index, slot.reg(), slot.type());
424 cache_state_.dec_used(slot.reg());
426 case VarState::KIntConst:
427 Spill(index, slot.constant());
433 void LiftoffAssembler::SpillLocals() {
439 void LiftoffAssembler::SpillAllRegisters() {
440 for (
uint32_t i = 0, e = cache_state_.stack_height();
i < e; ++
i) {
441 auto& slot = cache_state_.stack_state[
i];
442 if (!slot.is_reg())
continue;
443 Spill(
i, slot.reg(), slot.type());
446 cache_state_.reset_used_registers();
449 void LiftoffAssembler::PrepareCall(FunctionSig* sig,
450 compiler::CallDescriptor* call_descriptor,
452 Register* target_instance) {
455 constexpr
size_t kInputShift = 1;
459 for (
uint32_t idx = 0, end = cache_state_.stack_height() - num_params;
461 VarState& slot = cache_state_.stack_state[idx];
462 if (!slot.is_reg())
continue;
463 Spill(idx, slot.reg(), slot.type());
467 LiftoffStackSlots stack_slots(
this);
468 StackTransferRecipe stack_transfers(
this);
469 LiftoffRegList param_regs;
472 compiler::LinkageLocation instance_loc =
473 call_descriptor->GetInputLocation(kInputShift);
474 DCHECK(instance_loc.IsRegister() && !instance_loc.IsAnyRegister());
475 Register instance_reg = Register::from_code(instance_loc.AsRegister());
476 param_regs.set(instance_reg);
477 if (target_instance && *target_instance != instance_reg) {
478 stack_transfers.MoveRegister(LiftoffRegister(instance_reg),
479 LiftoffRegister(*target_instance),
488 uint32_t param_base = cache_state_.stack_height() - num_params;
490 static_cast<uint32_t>(call_descriptor->InputCount());
493 ValueType type = sig->GetParam(param);
494 const bool is_pair = kNeedI64RegPair && type == kWasmI64;
495 const int num_lowered_params = is_pair ? 2 : 1;
496 const uint32_t stack_idx = param_base + param;
497 const VarState& slot = cache_state_.stack_state[stack_idx];
500 for (
int lowered_idx = 0; lowered_idx < num_lowered_params; ++lowered_idx) {
501 const RegPairHalf half =
502 is_pair && lowered_idx == 0 ? kHighWord : kLowWord;
503 --call_desc_input_idx;
504 compiler::LinkageLocation loc =
505 call_descriptor->GetInputLocation(call_desc_input_idx);
506 if (loc.IsRegister()) {
507 DCHECK(!loc.IsAnyRegister());
508 RegClass rc = is_pair ? kGpReg : reg_class_for(type);
509 int reg_code = loc.AsRegister();
510 #if V8_TARGET_ARCH_ARM 515 DCHECK_IMPLIES(type == kWasmF32, (reg_code % 2) == 0);
516 LiftoffRegister reg = LiftoffRegister::from_code(
517 rc, (type == kWasmF32) ? (reg_code / 2) : reg_code);
519 LiftoffRegister reg = LiftoffRegister::from_code(rc, reg_code);
523 stack_transfers.LoadI64HalfIntoRegister(reg, slot, stack_idx, half);
525 stack_transfers.LoadIntoRegister(reg, slot, stack_idx);
528 DCHECK(loc.IsCallerFrameSlot());
529 stack_slots.Add(slot, stack_idx, half);
534 DCHECK_EQ(call_desc_input_idx, kInputShift + 1);
538 if (target && param_regs.has(LiftoffRegister(*target))) {
540 LiftoffRegList free_regs = kGpCacheRegList.MaskOut(param_regs);
541 if (!free_regs.is_empty()) {
542 LiftoffRegister new_target = free_regs.GetFirstRegSet();
543 stack_transfers.MoveRegister(new_target, LiftoffRegister(*target),
545 *target = new_target.gp();
547 stack_slots.Add(LiftoffAssembler::VarState(LiftoffAssembler::kWasmIntPtr,
548 LiftoffRegister(*target)));
554 stack_slots.Construct();
556 stack_transfers.Execute();
559 auto stack_end = cache_state_.stack_state.end();
560 cache_state_.stack_state.erase(stack_end - num_params, stack_end);
563 cache_state_.reset_used_registers();
566 if (!target_instance) {
567 FillInstanceInto(instance_reg);
571 void LiftoffAssembler::FinishCall(FunctionSig* sig,
572 compiler::CallDescriptor* call_descriptor) {
573 const size_t return_count = sig->return_count();
574 if (return_count != 0) {
575 DCHECK_EQ(1, return_count);
576 ValueType return_type = sig->GetReturn(0);
577 const bool need_pair = kNeedI64RegPair && return_type == kWasmI64;
578 DCHECK_EQ(need_pair ? 2 : 1, call_descriptor->ReturnCount());
579 RegClass rc = need_pair ? kGpReg : reg_class_for(return_type);
580 #if V8_TARGET_ARCH_ARM 583 DCHECK_EQ(call_descriptor->GetReturnLocation(0).AsRegister(), 0);
585 LiftoffRegister return_reg = LiftoffRegister::from_code(
586 rc, call_descriptor->GetReturnLocation(0).AsRegister());
587 DCHECK(GetCacheRegList(rc).has(return_reg));
589 LiftoffRegister high_reg = LiftoffRegister::from_code(
590 rc, call_descriptor->GetReturnLocation(1).AsRegister());
591 DCHECK(GetCacheRegList(rc).has(high_reg));
592 return_reg = LiftoffRegister::ForPair(return_reg.gp(), high_reg.gp());
594 DCHECK(!cache_state_.is_used(return_reg));
595 PushRegister(return_type, return_reg);
599 void LiftoffAssembler::Move(LiftoffRegister dst, LiftoffRegister src,
601 DCHECK_EQ(dst.reg_class(), src.reg_class());
603 if (kNeedI64RegPair && dst.is_pair()) {
606 StackTransferRecipe(
this).MoveRegister(dst, src, type);
607 }
else if (dst.is_gp()) {
608 Move(dst.gp(), src.gp(), type);
610 Move(dst.fp(), src.fp(), type);
614 void LiftoffAssembler::ParallelRegisterMove(
615 Vector<ParallelRegisterMoveTuple> tuples) {
616 StackTransferRecipe stack_transfers(
this);
617 for (
auto tuple : tuples) {
618 if (tuple.dst == tuple.src)
continue;
619 stack_transfers.MoveRegister(tuple.dst, tuple.src, tuple.type);
623 #ifdef ENABLE_SLOW_DCHECKS 624 bool LiftoffAssembler::ValidateCacheState()
const {
625 uint32_t register_use_count[kAfterMaxLiftoffRegCode] = {0};
626 LiftoffRegList used_regs;
627 for (
const VarState& var : cache_state_.stack_state) {
628 if (!var.is_reg())
continue;
629 LiftoffRegister reg = var.reg();
630 if (kNeedI64RegPair && reg.is_pair()) {
631 ++register_use_count[reg.low().liftoff_code()];
632 ++register_use_count[reg.high().liftoff_code()];
634 ++register_use_count[reg.liftoff_code()];
638 bool valid = memcmp(register_use_count, cache_state_.register_use_count,
639 sizeof(register_use_count)) == 0 &&
640 used_regs == cache_state_.used_registers;
641 if (valid)
return true;
642 std::ostringstream os;
643 os <<
"Error in LiftoffAssembler::ValidateCacheState().\n";
644 os <<
"expected: used_regs " << used_regs <<
", counts " 645 << PrintCollection(register_use_count) <<
"\n";
646 os <<
"found: used_regs " << cache_state_.used_registers <<
", counts " 647 << PrintCollection(cache_state_.register_use_count) <<
"\n";
648 os <<
"Use --trace-liftoff to debug.";
649 FATAL(
"%s", os.str().c_str());
653 LiftoffRegister LiftoffAssembler::SpillOneRegister(LiftoffRegList candidates,
654 LiftoffRegList pinned) {
656 LiftoffRegister spill_reg = cache_state_.GetNextSpillReg(candidates, pinned);
657 SpillRegister(spill_reg);
661 void LiftoffAssembler::SpillRegister(LiftoffRegister reg) {
662 int remaining_uses = cache_state_.get_use_count(reg);
663 DCHECK_LT(0, remaining_uses);
664 for (
uint32_t idx = cache_state_.stack_height() - 1;; --idx) {
665 DCHECK_GT(cache_state_.stack_height(), idx);
666 auto* slot = &cache_state_.stack_state[idx];
667 if (!slot->is_reg() || !slot->reg().overlaps(reg))
continue;
668 if (slot->reg().is_pair()) {
671 cache_state_.dec_used(slot->reg().low());
672 cache_state_.dec_used(slot->reg().high());
674 Spill(idx, slot->reg(), slot->type());
676 if (--remaining_uses == 0)
break;
678 cache_state_.clear_used(reg);
681 void LiftoffAssembler::set_num_locals(
uint32_t num_locals) {
682 DCHECK_EQ(0, num_locals_);
683 num_locals_ = num_locals;
684 if (num_locals > kInlineLocalTypes) {
686 reinterpret_cast<ValueType*
>(malloc(num_locals *
sizeof(ValueType)));
687 DCHECK_NOT_NULL(more_local_types_);
691 std::ostream& operator<<(std::ostream& os, VarState slot) {
692 os << ValueTypes::TypeName(slot.type()) <<
":";
693 switch (slot.loc()) {
694 case VarState::kStack:
696 case VarState::kRegister:
697 return os << slot.reg();
698 case VarState::KIntConst:
699 return os <<
"c" << slot.i32_const();