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 #ifndef V8_WASM_BASELINE_IA32_LIFTOFF_ASSEMBLER_IA32_H_
6 #define V8_WASM_BASELINE_IA32_LIFTOFF_ASSEMBLER_IA32_H_
7
8 #include "src/wasm/baseline/liftoff-assembler.h"
9
10 #include "src/assembler.h"
11 #include "src/wasm/value-type.h"
12
13 namespace v8 {
14 namespace internal {
15 namespace wasm {
16
17 #define REQUIRE_CPU_FEATURE(name, ...) \
18 if (!CpuFeatures::IsSupported(name)) { \
19 bailout("no " #name); \
20 return __VA_ARGS__; \
21 } \
22 CpuFeatureScope feature(this, name);
23
24 namespace liftoff {
25
26 // ebp-4 holds the stack marker, ebp-8 is the instance parameter, first stack
27 // slot is located at ebp-16.
28 constexpr int32_t kConstantStackSpace = 8;
29 constexpr int32_t kFirstStackSlotOffset =
30 kConstantStackSpace + LiftoffAssembler::kStackSlotSize;
31
GetStackSlot(uint32_t index)32 inline Operand GetStackSlot(uint32_t index) {
33 int32_t offset = index * LiftoffAssembler::kStackSlotSize;
34 return Operand(ebp, -kFirstStackSlotOffset - offset);
35 }
36
GetHalfStackSlot(uint32_t half_index)37 inline Operand GetHalfStackSlot(uint32_t half_index) {
38 int32_t offset = half_index * (LiftoffAssembler::kStackSlotSize / 2);
39 return Operand(ebp, -kFirstStackSlotOffset - offset);
40 }
41
42 // TODO(clemensh): Make this a constexpr variable once Operand is constexpr.
GetInstanceOperand()43 inline Operand GetInstanceOperand() { return Operand(ebp, -8); }
44
45 static constexpr LiftoffRegList kByteRegs =
46 LiftoffRegList::FromBits<Register::ListOf<eax, ecx, edx, ebx>()>();
47 static_assert(kByteRegs.GetNumRegsSet() == 4, "should have four byte regs");
48 static_assert((kByteRegs & kGpCacheRegList) == kByteRegs,
49 "kByteRegs only contains gp cache registers");
50
Load(LiftoffAssembler * assm,LiftoffRegister dst,Register base,int32_t offset,ValueType type)51 inline void Load(LiftoffAssembler* assm, LiftoffRegister dst, Register base,
52 int32_t offset, ValueType type) {
53 Operand src(base, offset);
54 switch (type) {
55 case kWasmI32:
56 assm->mov(dst.gp(), src);
57 break;
58 case kWasmI64:
59 assm->mov(dst.low_gp(), src);
60 assm->mov(dst.high_gp(), Operand(base, offset + 4));
61 break;
62 case kWasmF32:
63 assm->movss(dst.fp(), src);
64 break;
65 case kWasmF64:
66 assm->movsd(dst.fp(), src);
67 break;
68 default:
69 UNREACHABLE();
70 }
71 }
72
Store(LiftoffAssembler * assm,Register base,int32_t offset,LiftoffRegister src,ValueType type)73 inline void Store(LiftoffAssembler* assm, Register base, int32_t offset,
74 LiftoffRegister src, ValueType type) {
75 Operand dst(base, offset);
76 switch (type) {
77 case kWasmI32:
78 assm->mov(dst, src.gp());
79 break;
80 case kWasmI64:
81 assm->mov(dst, src.low_gp());
82 assm->mov(Operand(base, offset + 4), src.high_gp());
83 break;
84 case kWasmF32:
85 assm->movss(dst, src.fp());
86 break;
87 case kWasmF64:
88 assm->movsd(dst, src.fp());
89 break;
90 default:
91 UNREACHABLE();
92 }
93 }
94
push(LiftoffAssembler * assm,LiftoffRegister reg,ValueType type)95 inline void push(LiftoffAssembler* assm, LiftoffRegister reg, ValueType type) {
96 switch (type) {
97 case kWasmI32:
98 assm->push(reg.gp());
99 break;
100 case kWasmI64:
101 assm->push(reg.high_gp());
102 assm->push(reg.low_gp());
103 break;
104 case kWasmF32:
105 assm->sub(esp, Immediate(sizeof(float)));
106 assm->movss(Operand(esp, 0), reg.fp());
107 break;
108 case kWasmF64:
109 assm->sub(esp, Immediate(sizeof(double)));
110 assm->movsd(Operand(esp, 0), reg.fp());
111 break;
112 default:
113 UNREACHABLE();
114 }
115 }
116
117 template <typename... Regs>
SpillRegisters(LiftoffAssembler * assm,Regs...regs)118 inline void SpillRegisters(LiftoffAssembler* assm, Regs... regs) {
119 for (LiftoffRegister r : {LiftoffRegister(regs)...}) {
120 if (assm->cache_state()->is_used(r)) assm->SpillRegister(r);
121 }
122 }
123
124 constexpr DoubleRegister kScratchDoubleReg = xmm7;
125
126 constexpr int kSubSpSize = 6; // 6 bytes for "sub esp, <imm32>"
127
128 } // namespace liftoff
129
PrepareStackFrame()130 int LiftoffAssembler::PrepareStackFrame() {
131 int offset = pc_offset();
132 sub_sp_32(0);
133 DCHECK_EQ(liftoff::kSubSpSize, pc_offset() - offset);
134 return offset;
135 }
136
PatchPrepareStackFrame(int offset,uint32_t stack_slots)137 void LiftoffAssembler::PatchPrepareStackFrame(int offset,
138 uint32_t stack_slots) {
139 uint32_t bytes = liftoff::kConstantStackSpace + kStackSlotSize * stack_slots;
140 DCHECK_LE(bytes, kMaxInt);
141 // We can't run out of space, just pass anything big enough to not cause the
142 // assembler to try to grow the buffer.
143 constexpr int kAvailableSpace = 64;
144 Assembler patching_assembler(AssemblerOptions{}, buffer_ + offset,
145 kAvailableSpace);
146 #if V8_OS_WIN
147 constexpr int kPageSize = 4 * 1024;
148 if (bytes > kPageSize) {
149 // Generate OOL code (at the end of the function, where the current
150 // assembler is pointing) to do the explicit stack limit check (see
151 // https://docs.microsoft.com/en-us/previous-versions/visualstudio/
152 // visual-studio-6.0/aa227153(v=vs.60)).
153 // At the function start, emit a jump to that OOL code (from {offset} to
154 // {pc_offset()}).
155 int ool_offset = pc_offset() - offset;
156 patching_assembler.jmp_rel(ool_offset);
157 DCHECK_GE(liftoff::kSubSpSize, patching_assembler.pc_offset());
158 patching_assembler.Nop(liftoff::kSubSpSize -
159 patching_assembler.pc_offset());
160
161 // Now generate the OOL code.
162 // Use {edi} as scratch register; it is not being used as parameter
163 // register (see wasm-linkage.h).
164 mov(edi, bytes);
165 AllocateStackFrame(edi);
166 // Jump back to the start of the function (from {pc_offset()} to {offset +
167 // kSubSpSize}).
168 int func_start_offset = offset + liftoff::kSubSpSize - pc_offset();
169 jmp_rel(func_start_offset);
170 return;
171 }
172 #endif
173 patching_assembler.sub_sp_32(bytes);
174 DCHECK_EQ(liftoff::kSubSpSize, patching_assembler.pc_offset());
175 }
176
FinishCode()177 void LiftoffAssembler::FinishCode() {}
178
AbortCompilation()179 void LiftoffAssembler::AbortCompilation() {}
180
LoadConstant(LiftoffRegister reg,WasmValue value,RelocInfo::Mode rmode)181 void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value,
182 RelocInfo::Mode rmode) {
183 switch (value.type()) {
184 case kWasmI32:
185 TurboAssembler::Move(reg.gp(), Immediate(value.to_i32(), rmode));
186 break;
187 case kWasmI64: {
188 DCHECK(RelocInfo::IsNone(rmode));
189 int32_t low_word = value.to_i64();
190 int32_t high_word = value.to_i64() >> 32;
191 TurboAssembler::Move(reg.low_gp(), Immediate(low_word));
192 TurboAssembler::Move(reg.high_gp(), Immediate(high_word));
193 break;
194 }
195 case kWasmF32:
196 TurboAssembler::Move(reg.fp(), value.to_f32_boxed().get_bits());
197 break;
198 case kWasmF64:
199 TurboAssembler::Move(reg.fp(), value.to_f64_boxed().get_bits());
200 break;
201 default:
202 UNREACHABLE();
203 }
204 }
205
LoadFromInstance(Register dst,uint32_t offset,int size)206 void LiftoffAssembler::LoadFromInstance(Register dst, uint32_t offset,
207 int size) {
208 DCHECK_LE(offset, kMaxInt);
209 mov(dst, liftoff::GetInstanceOperand());
210 DCHECK_EQ(4, size);
211 mov(dst, Operand(dst, offset));
212 }
213
SpillInstance(Register instance)214 void LiftoffAssembler::SpillInstance(Register instance) {
215 mov(liftoff::GetInstanceOperand(), instance);
216 }
217
FillInstanceInto(Register dst)218 void LiftoffAssembler::FillInstanceInto(Register dst) {
219 mov(dst, liftoff::GetInstanceOperand());
220 }
221
Load(LiftoffRegister dst,Register src_addr,Register offset_reg,uint32_t offset_imm,LoadType type,LiftoffRegList pinned,uint32_t * protected_load_pc,bool is_load_mem)222 void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr,
223 Register offset_reg, uint32_t offset_imm,
224 LoadType type, LiftoffRegList pinned,
225 uint32_t* protected_load_pc, bool is_load_mem) {
226 DCHECK_EQ(type.value_type() == kWasmI64, dst.is_pair());
227 // Wasm memory is limited to a size <2GB, so all offsets can be encoded as
228 // immediate value (in 31 bits, interpreted as signed value).
229 // If the offset is bigger, we always trap and this code is not reached.
230 // Note: We shouldn't have memories larger than 2GiB on 32-bit, but if we
231 // did, we encode {offset_im} as signed, and it will simply wrap around.
232 Operand src_op = offset_reg == no_reg
233 ? Operand(src_addr, bit_cast<int32_t>(offset_imm))
234 : Operand(src_addr, offset_reg, times_1, offset_imm);
235 if (protected_load_pc) *protected_load_pc = pc_offset();
236
237 switch (type.value()) {
238 case LoadType::kI32Load8U:
239 movzx_b(dst.gp(), src_op);
240 break;
241 case LoadType::kI32Load8S:
242 movsx_b(dst.gp(), src_op);
243 break;
244 case LoadType::kI64Load8U:
245 movzx_b(dst.low_gp(), src_op);
246 xor_(dst.high_gp(), dst.high_gp());
247 break;
248 case LoadType::kI64Load8S:
249 movsx_b(dst.low_gp(), src_op);
250 mov(dst.high_gp(), dst.low_gp());
251 sar(dst.high_gp(), 31);
252 break;
253 case LoadType::kI32Load16U:
254 movzx_w(dst.gp(), src_op);
255 break;
256 case LoadType::kI32Load16S:
257 movsx_w(dst.gp(), src_op);
258 break;
259 case LoadType::kI64Load16U:
260 movzx_w(dst.low_gp(), src_op);
261 xor_(dst.high_gp(), dst.high_gp());
262 break;
263 case LoadType::kI64Load16S:
264 movsx_w(dst.low_gp(), src_op);
265 mov(dst.high_gp(), dst.low_gp());
266 sar(dst.high_gp(), 31);
267 break;
268 case LoadType::kI32Load:
269 mov(dst.gp(), src_op);
270 break;
271 case LoadType::kI64Load32U:
272 mov(dst.low_gp(), src_op);
273 xor_(dst.high_gp(), dst.high_gp());
274 break;
275 case LoadType::kI64Load32S:
276 mov(dst.low_gp(), src_op);
277 mov(dst.high_gp(), dst.low_gp());
278 sar(dst.high_gp(), 31);
279 break;
280 case LoadType::kI64Load: {
281 // Compute the operand for the load of the upper half.
282 Operand upper_src_op =
283 offset_reg == no_reg
284 ? Operand(src_addr, bit_cast<int32_t>(offset_imm + 4))
285 : Operand(src_addr, offset_reg, times_1, offset_imm + 4);
286 // The high word has to be mov'ed first, such that this is the protected
287 // instruction. The mov of the low word cannot segfault.
288 mov(dst.high_gp(), upper_src_op);
289 mov(dst.low_gp(), src_op);
290 break;
291 }
292 case LoadType::kF32Load:
293 movss(dst.fp(), src_op);
294 break;
295 case LoadType::kF64Load:
296 movsd(dst.fp(), src_op);
297 break;
298 default:
299 UNREACHABLE();
300 }
301 }
302
Store(Register dst_addr,Register offset_reg,uint32_t offset_imm,LiftoffRegister src,StoreType type,LiftoffRegList pinned,uint32_t * protected_store_pc,bool is_store_mem)303 void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
304 uint32_t offset_imm, LiftoffRegister src,
305 StoreType type, LiftoffRegList pinned,
306 uint32_t* protected_store_pc, bool is_store_mem) {
307 DCHECK_EQ(type.value_type() == kWasmI64, src.is_pair());
308 // Wasm memory is limited to a size <2GB, so all offsets can be encoded as
309 // immediate value (in 31 bits, interpreted as signed value).
310 // If the offset is bigger, we always trap and this code is not reached.
311 Operand dst_op = offset_reg == no_reg
312 ? Operand(dst_addr, bit_cast<int32_t>(offset_imm))
313 : Operand(dst_addr, offset_reg, times_1, offset_imm);
314 if (protected_store_pc) *protected_store_pc = pc_offset();
315
316 switch (type.value()) {
317 case StoreType::kI64Store8:
318 src = src.low();
319 V8_FALLTHROUGH;
320 case StoreType::kI32Store8:
321 // Only the lower 4 registers can be addressed as 8-bit registers.
322 if (src.gp().is_byte_register()) {
323 mov_b(dst_op, src.gp());
324 } else {
325 Register byte_src = GetUnusedRegister(liftoff::kByteRegs, pinned).gp();
326 mov(byte_src, src.gp());
327 mov_b(dst_op, byte_src);
328 }
329 break;
330 case StoreType::kI64Store16:
331 src = src.low();
332 V8_FALLTHROUGH;
333 case StoreType::kI32Store16:
334 mov_w(dst_op, src.gp());
335 break;
336 case StoreType::kI64Store32:
337 src = src.low();
338 V8_FALLTHROUGH;
339 case StoreType::kI32Store:
340 mov(dst_op, src.gp());
341 break;
342 case StoreType::kI64Store: {
343 // Compute the operand for the store of the upper half.
344 Operand upper_dst_op =
345 offset_reg == no_reg
346 ? Operand(dst_addr, bit_cast<int32_t>(offset_imm + 4))
347 : Operand(dst_addr, offset_reg, times_1, offset_imm + 4);
348 // The high word has to be mov'ed first, such that this is the protected
349 // instruction. The mov of the low word cannot segfault.
350 mov(upper_dst_op, src.high_gp());
351 mov(dst_op, src.low_gp());
352 break;
353 }
354 case StoreType::kF32Store:
355 movss(dst_op, src.fp());
356 break;
357 case StoreType::kF64Store:
358 movsd(dst_op, src.fp());
359 break;
360 default:
361 UNREACHABLE();
362 }
363 }
364
LoadCallerFrameSlot(LiftoffRegister dst,uint32_t caller_slot_idx,ValueType type)365 void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
366 uint32_t caller_slot_idx,
367 ValueType type) {
368 liftoff::Load(this, dst, ebp, kPointerSize * (caller_slot_idx + 1), type);
369 }
370
MoveStackValue(uint32_t dst_index,uint32_t src_index,ValueType type)371 void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index,
372 ValueType type) {
373 DCHECK_NE(dst_index, src_index);
374 if (cache_state_.has_unused_register(kGpReg)) {
375 LiftoffRegister reg = GetUnusedRegister(kGpReg);
376 Fill(reg, src_index, type);
377 Spill(dst_index, reg, type);
378 } else {
379 push(liftoff::GetStackSlot(src_index));
380 pop(liftoff::GetStackSlot(dst_index));
381 }
382 }
383
Move(Register dst,Register src,ValueType type)384 void LiftoffAssembler::Move(Register dst, Register src, ValueType type) {
385 DCHECK_NE(dst, src);
386 DCHECK_EQ(kWasmI32, type);
387 mov(dst, src);
388 }
389
Move(DoubleRegister dst,DoubleRegister src,ValueType type)390 void LiftoffAssembler::Move(DoubleRegister dst, DoubleRegister src,
391 ValueType type) {
392 DCHECK_NE(dst, src);
393 if (type == kWasmF32) {
394 movss(dst, src);
395 } else {
396 DCHECK_EQ(kWasmF64, type);
397 movsd(dst, src);
398 }
399 }
400
Spill(uint32_t index,LiftoffRegister reg,ValueType type)401 void LiftoffAssembler::Spill(uint32_t index, LiftoffRegister reg,
402 ValueType type) {
403 RecordUsedSpillSlot(index);
404 Operand dst = liftoff::GetStackSlot(index);
405 switch (type) {
406 case kWasmI32:
407 mov(dst, reg.gp());
408 break;
409 case kWasmI64:
410 mov(dst, reg.low_gp());
411 mov(liftoff::GetHalfStackSlot(2 * index - 1), reg.high_gp());
412 break;
413 case kWasmF32:
414 movss(dst, reg.fp());
415 break;
416 case kWasmF64:
417 movsd(dst, reg.fp());
418 break;
419 default:
420 UNREACHABLE();
421 }
422 }
423
Spill(uint32_t index,WasmValue value)424 void LiftoffAssembler::Spill(uint32_t index, WasmValue value) {
425 RecordUsedSpillSlot(index);
426 Operand dst = liftoff::GetStackSlot(index);
427 switch (value.type()) {
428 case kWasmI32:
429 mov(dst, Immediate(value.to_i32()));
430 break;
431 case kWasmI64: {
432 int32_t low_word = value.to_i64();
433 int32_t high_word = value.to_i64() >> 32;
434 mov(dst, Immediate(low_word));
435 mov(liftoff::GetHalfStackSlot(2 * index - 1), Immediate(high_word));
436 break;
437 }
438 default:
439 // We do not track f32 and f64 constants, hence they are unreachable.
440 UNREACHABLE();
441 }
442 }
443
Fill(LiftoffRegister reg,uint32_t index,ValueType type)444 void LiftoffAssembler::Fill(LiftoffRegister reg, uint32_t index,
445 ValueType type) {
446 Operand src = liftoff::GetStackSlot(index);
447 switch (type) {
448 case kWasmI32:
449 mov(reg.gp(), src);
450 break;
451 case kWasmI64:
452 mov(reg.low_gp(), src);
453 mov(reg.high_gp(), liftoff::GetHalfStackSlot(2 * index - 1));
454 break;
455 case kWasmF32:
456 movss(reg.fp(), src);
457 break;
458 case kWasmF64:
459 movsd(reg.fp(), src);
460 break;
461 default:
462 UNREACHABLE();
463 }
464 }
465
FillI64Half(Register reg,uint32_t half_index)466 void LiftoffAssembler::FillI64Half(Register reg, uint32_t half_index) {
467 mov(reg, liftoff::GetHalfStackSlot(half_index));
468 }
469
emit_i32_add(Register dst,Register lhs,Register rhs)470 void LiftoffAssembler::emit_i32_add(Register dst, Register lhs, Register rhs) {
471 if (lhs != dst) {
472 lea(dst, Operand(lhs, rhs, times_1, 0));
473 } else {
474 add(dst, rhs);
475 }
476 }
477
emit_i32_sub(Register dst,Register lhs,Register rhs)478 void LiftoffAssembler::emit_i32_sub(Register dst, Register lhs, Register rhs) {
479 if (dst == rhs) {
480 neg(dst);
481 add(dst, lhs);
482 } else {
483 if (dst != lhs) mov(dst, lhs);
484 sub(dst, rhs);
485 }
486 }
487
488 namespace liftoff {
489 template <void (Assembler::*op)(Register, Register)>
EmitCommutativeBinOp(LiftoffAssembler * assm,Register dst,Register lhs,Register rhs)490 void EmitCommutativeBinOp(LiftoffAssembler* assm, Register dst, Register lhs,
491 Register rhs) {
492 if (dst == rhs) {
493 (assm->*op)(dst, lhs);
494 } else {
495 if (dst != lhs) assm->mov(dst, lhs);
496 (assm->*op)(dst, rhs);
497 }
498 }
499 } // namespace liftoff
500
emit_i32_mul(Register dst,Register lhs,Register rhs)501 void LiftoffAssembler::emit_i32_mul(Register dst, Register lhs, Register rhs) {
502 liftoff::EmitCommutativeBinOp<&Assembler::imul>(this, dst, lhs, rhs);
503 }
504
505 namespace liftoff {
506 enum class DivOrRem : uint8_t { kDiv, kRem };
507 template <bool is_signed, DivOrRem div_or_rem>
EmitInt32DivOrRem(LiftoffAssembler * assm,Register dst,Register lhs,Register rhs,Label * trap_div_by_zero,Label * trap_div_unrepresentable)508 void EmitInt32DivOrRem(LiftoffAssembler* assm, Register dst, Register lhs,
509 Register rhs, Label* trap_div_by_zero,
510 Label* trap_div_unrepresentable) {
511 constexpr bool needs_unrepresentable_check =
512 is_signed && div_or_rem == DivOrRem::kDiv;
513 constexpr bool special_case_minus_1 =
514 is_signed && div_or_rem == DivOrRem::kRem;
515 DCHECK_EQ(needs_unrepresentable_check, trap_div_unrepresentable != nullptr);
516
517 // For division, the lhs is always taken from {edx:eax}. Thus, make sure that
518 // these registers are unused. If {rhs} is stored in one of them, move it to
519 // another temporary register.
520 // Do all this before any branch, such that the code is executed
521 // unconditionally, as the cache state will also be modified unconditionally.
522 liftoff::SpillRegisters(assm, eax, edx);
523 if (rhs == eax || rhs == edx) {
524 LiftoffRegList unavailable = LiftoffRegList::ForRegs(eax, edx, lhs);
525 Register tmp = assm->GetUnusedRegister(kGpReg, unavailable).gp();
526 assm->mov(tmp, rhs);
527 rhs = tmp;
528 }
529
530 // Check for division by zero.
531 assm->test(rhs, rhs);
532 assm->j(zero, trap_div_by_zero);
533
534 Label done;
535 if (needs_unrepresentable_check) {
536 // Check for {kMinInt / -1}. This is unrepresentable.
537 Label do_div;
538 assm->cmp(rhs, -1);
539 assm->j(not_equal, &do_div);
540 assm->cmp(lhs, kMinInt);
541 assm->j(equal, trap_div_unrepresentable);
542 assm->bind(&do_div);
543 } else if (special_case_minus_1) {
544 // {lhs % -1} is always 0 (needs to be special cased because {kMinInt / -1}
545 // cannot be computed).
546 Label do_rem;
547 assm->cmp(rhs, -1);
548 assm->j(not_equal, &do_rem);
549 assm->xor_(dst, dst);
550 assm->jmp(&done);
551 assm->bind(&do_rem);
552 }
553
554 // Now move {lhs} into {eax}, then zero-extend or sign-extend into {edx}, then
555 // do the division.
556 if (lhs != eax) assm->mov(eax, lhs);
557 if (is_signed) {
558 assm->cdq();
559 assm->idiv(rhs);
560 } else {
561 assm->xor_(edx, edx);
562 assm->div(rhs);
563 }
564
565 // Move back the result (in {eax} or {edx}) into the {dst} register.
566 constexpr Register kResultReg = div_or_rem == DivOrRem::kDiv ? eax : edx;
567 if (dst != kResultReg) assm->mov(dst, kResultReg);
568 if (special_case_minus_1) assm->bind(&done);
569 }
570 } // namespace liftoff
571
emit_i32_divs(Register dst,Register lhs,Register rhs,Label * trap_div_by_zero,Label * trap_div_unrepresentable)572 void LiftoffAssembler::emit_i32_divs(Register dst, Register lhs, Register rhs,
573 Label* trap_div_by_zero,
574 Label* trap_div_unrepresentable) {
575 liftoff::EmitInt32DivOrRem<true, liftoff::DivOrRem::kDiv>(
576 this, dst, lhs, rhs, trap_div_by_zero, trap_div_unrepresentable);
577 }
578
emit_i32_divu(Register dst,Register lhs,Register rhs,Label * trap_div_by_zero)579 void LiftoffAssembler::emit_i32_divu(Register dst, Register lhs, Register rhs,
580 Label* trap_div_by_zero) {
581 liftoff::EmitInt32DivOrRem<false, liftoff::DivOrRem::kDiv>(
582 this, dst, lhs, rhs, trap_div_by_zero, nullptr);
583 }
584
emit_i32_rems(Register dst,Register lhs,Register rhs,Label * trap_div_by_zero)585 void LiftoffAssembler::emit_i32_rems(Register dst, Register lhs, Register rhs,
586 Label* trap_div_by_zero) {
587 liftoff::EmitInt32DivOrRem<true, liftoff::DivOrRem::kRem>(
588 this, dst, lhs, rhs, trap_div_by_zero, nullptr);
589 }
590
emit_i32_remu(Register dst,Register lhs,Register rhs,Label * trap_div_by_zero)591 void LiftoffAssembler::emit_i32_remu(Register dst, Register lhs, Register rhs,
592 Label* trap_div_by_zero) {
593 liftoff::EmitInt32DivOrRem<false, liftoff::DivOrRem::kRem>(
594 this, dst, lhs, rhs, trap_div_by_zero, nullptr);
595 }
596
emit_i32_and(Register dst,Register lhs,Register rhs)597 void LiftoffAssembler::emit_i32_and(Register dst, Register lhs, Register rhs) {
598 liftoff::EmitCommutativeBinOp<&Assembler::and_>(this, dst, lhs, rhs);
599 }
600
emit_i32_or(Register dst,Register lhs,Register rhs)601 void LiftoffAssembler::emit_i32_or(Register dst, Register lhs, Register rhs) {
602 liftoff::EmitCommutativeBinOp<&Assembler::or_>(this, dst, lhs, rhs);
603 }
604
emit_i32_xor(Register dst,Register lhs,Register rhs)605 void LiftoffAssembler::emit_i32_xor(Register dst, Register lhs, Register rhs) {
606 liftoff::EmitCommutativeBinOp<&Assembler::xor_>(this, dst, lhs, rhs);
607 }
608
609 namespace liftoff {
EmitShiftOperation(LiftoffAssembler * assm,Register dst,Register src,Register amount,void (Assembler::* emit_shift)(Register),LiftoffRegList pinned)610 inline void EmitShiftOperation(LiftoffAssembler* assm, Register dst,
611 Register src, Register amount,
612 void (Assembler::*emit_shift)(Register),
613 LiftoffRegList pinned) {
614 pinned.set(dst);
615 pinned.set(src);
616 pinned.set(amount);
617 // If dst is ecx, compute into a tmp register first, then move to ecx.
618 if (dst == ecx) {
619 Register tmp = assm->GetUnusedRegister(kGpReg, pinned).gp();
620 assm->mov(tmp, src);
621 if (amount != ecx) assm->mov(ecx, amount);
622 (assm->*emit_shift)(tmp);
623 assm->mov(ecx, tmp);
624 return;
625 }
626
627 // Move amount into ecx. If ecx is in use, move its content to a tmp register
628 // first. If src is ecx, src is now the tmp register.
629 Register tmp_reg = no_reg;
630 if (amount != ecx) {
631 if (assm->cache_state()->is_used(LiftoffRegister(ecx)) ||
632 pinned.has(LiftoffRegister(ecx))) {
633 tmp_reg = assm->GetUnusedRegister(kGpReg, pinned).gp();
634 assm->mov(tmp_reg, ecx);
635 if (src == ecx) src = tmp_reg;
636 }
637 assm->mov(ecx, amount);
638 }
639
640 // Do the actual shift.
641 if (dst != src) assm->mov(dst, src);
642 (assm->*emit_shift)(dst);
643
644 // Restore ecx if needed.
645 if (tmp_reg.is_valid()) assm->mov(ecx, tmp_reg);
646 }
647 } // namespace liftoff
648
emit_i32_shl(Register dst,Register src,Register amount,LiftoffRegList pinned)649 void LiftoffAssembler::emit_i32_shl(Register dst, Register src, Register amount,
650 LiftoffRegList pinned) {
651 liftoff::EmitShiftOperation(this, dst, src, amount, &Assembler::shl_cl,
652 pinned);
653 }
654
emit_i32_sar(Register dst,Register src,Register amount,LiftoffRegList pinned)655 void LiftoffAssembler::emit_i32_sar(Register dst, Register src, Register amount,
656 LiftoffRegList pinned) {
657 liftoff::EmitShiftOperation(this, dst, src, amount, &Assembler::sar_cl,
658 pinned);
659 }
660
emit_i32_shr(Register dst,Register src,Register amount,LiftoffRegList pinned)661 void LiftoffAssembler::emit_i32_shr(Register dst, Register src, Register amount,
662 LiftoffRegList pinned) {
663 liftoff::EmitShiftOperation(this, dst, src, amount, &Assembler::shr_cl,
664 pinned);
665 }
666
emit_i32_clz(Register dst,Register src)667 bool LiftoffAssembler::emit_i32_clz(Register dst, Register src) {
668 Label nonzero_input;
669 Label continuation;
670 test(src, src);
671 j(not_zero, &nonzero_input, Label::kNear);
672 mov(dst, Immediate(32));
673 jmp(&continuation, Label::kNear);
674
675 bind(&nonzero_input);
676 // Get most significant bit set (MSBS).
677 bsr(dst, src);
678 // CLZ = 31 - MSBS = MSBS ^ 31.
679 xor_(dst, 31);
680
681 bind(&continuation);
682 return true;
683 }
684
emit_i32_ctz(Register dst,Register src)685 bool LiftoffAssembler::emit_i32_ctz(Register dst, Register src) {
686 Label nonzero_input;
687 Label continuation;
688 test(src, src);
689 j(not_zero, &nonzero_input, Label::kNear);
690 mov(dst, Immediate(32));
691 jmp(&continuation, Label::kNear);
692
693 bind(&nonzero_input);
694 // Get least significant bit set, which equals number of trailing zeros.
695 bsf(dst, src);
696
697 bind(&continuation);
698 return true;
699 }
700
emit_i32_popcnt(Register dst,Register src)701 bool LiftoffAssembler::emit_i32_popcnt(Register dst, Register src) {
702 if (!CpuFeatures::IsSupported(POPCNT)) return false;
703 CpuFeatureScope scope(this, POPCNT);
704 popcnt(dst, src);
705 return true;
706 }
707
708 namespace liftoff {
709 template <void (Assembler::*op)(Register, Register),
710 void (Assembler::*op_with_carry)(Register, Register)>
OpWithCarry(LiftoffAssembler * assm,LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs)711 inline void OpWithCarry(LiftoffAssembler* assm, LiftoffRegister dst,
712 LiftoffRegister lhs, LiftoffRegister rhs) {
713 // First, compute the low half of the result, potentially into a temporary dst
714 // register if {dst.low_gp()} equals {rhs.low_gp()} or any register we need to
715 // keep alive for computing the upper half.
716 LiftoffRegList keep_alive = LiftoffRegList::ForRegs(lhs.high_gp(), rhs);
717 Register dst_low = keep_alive.has(dst.low_gp())
718 ? assm->GetUnusedRegister(kGpReg, keep_alive).gp()
719 : dst.low_gp();
720
721 if (dst_low != lhs.low_gp()) assm->mov(dst_low, lhs.low_gp());
722 (assm->*op)(dst_low, rhs.low_gp());
723
724 // Now compute the upper half, while keeping alive the previous result.
725 keep_alive = LiftoffRegList::ForRegs(dst_low, rhs.high_gp());
726 Register dst_high = keep_alive.has(dst.high_gp())
727 ? assm->GetUnusedRegister(kGpReg, keep_alive).gp()
728 : dst.high_gp();
729
730 if (dst_high != lhs.high_gp()) assm->mov(dst_high, lhs.high_gp());
731 (assm->*op_with_carry)(dst_high, rhs.high_gp());
732
733 // If necessary, move result into the right registers.
734 LiftoffRegister tmp_result = LiftoffRegister::ForPair(dst_low, dst_high);
735 if (tmp_result != dst) assm->Move(dst, tmp_result, kWasmI64);
736 }
737 } // namespace liftoff
738
emit_i64_add(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs)739 void LiftoffAssembler::emit_i64_add(LiftoffRegister dst, LiftoffRegister lhs,
740 LiftoffRegister rhs) {
741 liftoff::OpWithCarry<&Assembler::add, &Assembler::adc>(this, dst, lhs, rhs);
742 }
743
emit_i64_sub(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs)744 void LiftoffAssembler::emit_i64_sub(LiftoffRegister dst, LiftoffRegister lhs,
745 LiftoffRegister rhs) {
746 liftoff::OpWithCarry<&Assembler::sub, &Assembler::sbb>(this, dst, lhs, rhs);
747 }
748
emit_i64_mul(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs)749 void LiftoffAssembler::emit_i64_mul(LiftoffRegister dst, LiftoffRegister lhs,
750 LiftoffRegister rhs) {
751 // Idea:
752 // [ lhs_hi | lhs_lo ] * [ rhs_hi | rhs_lo ]
753 // = [ lhs_hi * rhs_lo | ] (32 bit mul, shift 32)
754 // + [ lhs_lo * rhs_hi | ] (32 bit mul, shift 32)
755 // + [ lhs_lo * rhs_lo ] (32x32->64 mul, shift 0)
756
757 // For simplicity, we move lhs and rhs into fixed registers.
758 Register dst_hi = edx;
759 Register dst_lo = eax;
760 Register lhs_hi = ecx;
761 Register lhs_lo = dst_lo;
762 Register rhs_hi = dst_hi;
763 Register rhs_lo = ebx;
764
765 // Spill all these registers if they are still holding other values.
766 liftoff::SpillRegisters(this, dst_hi, dst_lo, lhs_hi, rhs_lo);
767
768 // Move lhs and rhs into the respective registers.
769 ParallelRegisterMove(
770 {{LiftoffRegister::ForPair(lhs_lo, lhs_hi), lhs, kWasmI64},
771 {LiftoffRegister::ForPair(rhs_lo, rhs_hi), rhs, kWasmI64}});
772
773 // First mul: lhs_hi' = lhs_hi * rhs_lo.
774 imul(lhs_hi, rhs_lo);
775 // Second mul: rhi_hi' = rhs_hi * lhs_lo.
776 imul(rhs_hi, lhs_lo);
777 // Add them: lhs_hi'' = lhs_hi' + rhs_hi' = lhs_hi * rhs_lo + rhs_hi * lhs_lo.
778 add(lhs_hi, rhs_hi);
779 // Third mul: edx:eax (dst_hi:dst_lo) = eax * ebx (lhs_lo * rhs_lo).
780 mul(rhs_lo);
781 // Add lhs_hi'' to dst_hi.
782 add(dst_hi, lhs_hi);
783
784 // Finally, move back the temporary result to the actual dst register pair.
785 LiftoffRegister dst_tmp = LiftoffRegister::ForPair(dst_lo, dst_hi);
786 if (dst != dst_tmp) Move(dst, dst_tmp, kWasmI64);
787 }
788
emit_i64_divs(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs,Label * trap_div_by_zero,Label * trap_div_unrepresentable)789 bool LiftoffAssembler::emit_i64_divs(LiftoffRegister dst, LiftoffRegister lhs,
790 LiftoffRegister rhs,
791 Label* trap_div_by_zero,
792 Label* trap_div_unrepresentable) {
793 return false;
794 }
795
emit_i64_divu(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs,Label * trap_div_by_zero)796 bool LiftoffAssembler::emit_i64_divu(LiftoffRegister dst, LiftoffRegister lhs,
797 LiftoffRegister rhs,
798 Label* trap_div_by_zero) {
799 return false;
800 }
801
emit_i64_rems(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs,Label * trap_div_by_zero)802 bool LiftoffAssembler::emit_i64_rems(LiftoffRegister dst, LiftoffRegister lhs,
803 LiftoffRegister rhs,
804 Label* trap_div_by_zero) {
805 return false;
806 }
807
emit_i64_remu(LiftoffRegister dst,LiftoffRegister lhs,LiftoffRegister rhs,Label * trap_div_by_zero)808 bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs,
809 LiftoffRegister rhs,
810 Label* trap_div_by_zero) {
811 return false;
812 }
813
814 namespace liftoff {
PairContains(LiftoffRegister pair,Register reg)815 inline bool PairContains(LiftoffRegister pair, Register reg) {
816 return pair.low_gp() == reg || pair.high_gp() == reg;
817 }
818
ReplaceInPair(LiftoffRegister pair,Register old_reg,Register new_reg)819 inline LiftoffRegister ReplaceInPair(LiftoffRegister pair, Register old_reg,
820 Register new_reg) {
821 if (pair.low_gp() == old_reg) {
822 return LiftoffRegister::ForPair(new_reg, pair.high_gp());
823 }
824 if (pair.high_gp() == old_reg) {
825 return LiftoffRegister::ForPair(pair.low_gp(), new_reg);
826 }
827 return pair;
828 }
829
Emit64BitShiftOperation(LiftoffAssembler * assm,LiftoffRegister dst,LiftoffRegister src,Register amount,void (TurboAssembler::* emit_shift)(Register,Register),LiftoffRegList pinned)830 inline void Emit64BitShiftOperation(
831 LiftoffAssembler* assm, LiftoffRegister dst, LiftoffRegister src,
832 Register amount, void (TurboAssembler::*emit_shift)(Register, Register),
833 LiftoffRegList pinned) {
834 pinned.set(dst);
835 pinned.set(src);
836 pinned.set(amount);
837 // If {dst} contains {ecx}, replace it by an unused register, which is then
838 // moved to {ecx} in the end.
839 Register ecx_replace = no_reg;
840 if (PairContains(dst, ecx)) {
841 ecx_replace = pinned.set(assm->GetUnusedRegister(kGpReg, pinned)).gp();
842 dst = ReplaceInPair(dst, ecx, ecx_replace);
843 // If {amount} needs to be moved to {ecx}, but {ecx} is in use (and not part
844 // of {dst}, hence overwritten anyway), move {ecx} to a tmp register and
845 // restore it at the end.
846 } else if (amount != ecx &&
847 assm->cache_state()->is_used(LiftoffRegister(ecx))) {
848 ecx_replace = assm->GetUnusedRegister(kGpReg, pinned).gp();
849 assm->mov(ecx_replace, ecx);
850 }
851
852 assm->ParallelRegisterMove(
853 {{dst, src, kWasmI64},
854 {LiftoffRegister{ecx}, LiftoffRegister{amount}, kWasmI32}});
855
856 // Do the actual shift.
857 (assm->*emit_shift)(dst.high_gp(), dst.low_gp());
858
859 // Restore {ecx} if needed.
860 if (ecx_replace != no_reg) assm->mov(ecx, ecx_replace);
861 }
862 } // namespace liftoff
863
emit_i64_shl(LiftoffRegister dst,LiftoffRegister src,Register amount,LiftoffRegList pinned)864 void LiftoffAssembler::emit_i64_shl(LiftoffRegister dst, LiftoffRegister src,
865 Register amount, LiftoffRegList pinned) {
866 liftoff::Emit64BitShiftOperation(this, dst, src, amount,
867 &TurboAssembler::ShlPair_cl, pinned);
868 }
869
emit_i64_sar(LiftoffRegister dst,LiftoffRegister src,Register amount,LiftoffRegList pinned)870 void LiftoffAssembler::emit_i64_sar(LiftoffRegister dst, LiftoffRegister src,
871 Register amount, LiftoffRegList pinned) {
872 liftoff::Emit64BitShiftOperation(this, dst, src, amount,
873 &TurboAssembler::SarPair_cl, pinned);
874 }
875
emit_i64_shr(LiftoffRegister dst,LiftoffRegister src,Register amount,LiftoffRegList pinned)876 void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src,
877 Register amount, LiftoffRegList pinned) {
878 liftoff::Emit64BitShiftOperation(this, dst, src, amount,
879 &TurboAssembler::ShrPair_cl, pinned);
880 }
881
emit_i32_to_intptr(Register dst,Register src)882 void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) {
883 // This is a nop on ia32.
884 }
885
emit_f32_add(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)886 void LiftoffAssembler::emit_f32_add(DoubleRegister dst, DoubleRegister lhs,
887 DoubleRegister rhs) {
888 if (CpuFeatures::IsSupported(AVX)) {
889 CpuFeatureScope scope(this, AVX);
890 vaddss(dst, lhs, rhs);
891 } else if (dst == rhs) {
892 addss(dst, lhs);
893 } else {
894 if (dst != lhs) movss(dst, lhs);
895 addss(dst, rhs);
896 }
897 }
898
emit_f32_sub(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)899 void LiftoffAssembler::emit_f32_sub(DoubleRegister dst, DoubleRegister lhs,
900 DoubleRegister rhs) {
901 if (CpuFeatures::IsSupported(AVX)) {
902 CpuFeatureScope scope(this, AVX);
903 vsubss(dst, lhs, rhs);
904 } else if (dst == rhs) {
905 movss(liftoff::kScratchDoubleReg, rhs);
906 movss(dst, lhs);
907 subss(dst, liftoff::kScratchDoubleReg);
908 } else {
909 if (dst != lhs) movss(dst, lhs);
910 subss(dst, rhs);
911 }
912 }
913
emit_f32_mul(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)914 void LiftoffAssembler::emit_f32_mul(DoubleRegister dst, DoubleRegister lhs,
915 DoubleRegister rhs) {
916 if (CpuFeatures::IsSupported(AVX)) {
917 CpuFeatureScope scope(this, AVX);
918 vmulss(dst, lhs, rhs);
919 } else if (dst == rhs) {
920 mulss(dst, lhs);
921 } else {
922 if (dst != lhs) movss(dst, lhs);
923 mulss(dst, rhs);
924 }
925 }
926
emit_f32_div(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)927 void LiftoffAssembler::emit_f32_div(DoubleRegister dst, DoubleRegister lhs,
928 DoubleRegister rhs) {
929 if (CpuFeatures::IsSupported(AVX)) {
930 CpuFeatureScope scope(this, AVX);
931 vdivss(dst, lhs, rhs);
932 } else if (dst == rhs) {
933 movss(liftoff::kScratchDoubleReg, rhs);
934 movss(dst, lhs);
935 divss(dst, liftoff::kScratchDoubleReg);
936 } else {
937 if (dst != lhs) movss(dst, lhs);
938 divss(dst, rhs);
939 }
940 }
941
942 namespace liftoff {
943 enum class MinOrMax : uint8_t { kMin, kMax };
944 template <typename type>
EmitFloatMinOrMax(LiftoffAssembler * assm,DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs,MinOrMax min_or_max)945 inline void EmitFloatMinOrMax(LiftoffAssembler* assm, DoubleRegister dst,
946 DoubleRegister lhs, DoubleRegister rhs,
947 MinOrMax min_or_max) {
948 Label is_nan;
949 Label lhs_below_rhs;
950 Label lhs_above_rhs;
951 Label done;
952
953 // We need one tmp register to extract the sign bit. Get it right at the
954 // beginning, such that the spilling code is not accidentially jumped over.
955 Register tmp = assm->GetUnusedRegister(kGpReg).gp();
956
957 #define dop(name, ...) \
958 do { \
959 if (sizeof(type) == 4) { \
960 assm->name##s(__VA_ARGS__); \
961 } else { \
962 assm->name##d(__VA_ARGS__); \
963 } \
964 } while (false)
965
966 // Check the easy cases first: nan (e.g. unordered), smaller and greater.
967 // NaN has to be checked first, because PF=1 implies CF=1.
968 dop(ucomis, lhs, rhs);
969 assm->j(parity_even, &is_nan, Label::kNear); // PF=1
970 assm->j(below, &lhs_below_rhs, Label::kNear); // CF=1
971 assm->j(above, &lhs_above_rhs, Label::kNear); // CF=0 && ZF=0
972
973 // If we get here, then either
974 // a) {lhs == rhs},
975 // b) {lhs == -0.0} and {rhs == 0.0}, or
976 // c) {lhs == 0.0} and {rhs == -0.0}.
977 // For a), it does not matter whether we return {lhs} or {rhs}. Check the sign
978 // bit of {rhs} to differentiate b) and c).
979 dop(movmskp, tmp, rhs);
980 assm->test(tmp, Immediate(1));
981 assm->j(zero, &lhs_below_rhs, Label::kNear);
982 assm->jmp(&lhs_above_rhs, Label::kNear);
983
984 assm->bind(&is_nan);
985 // Create a NaN output.
986 dop(xorp, dst, dst);
987 dop(divs, dst, dst);
988 assm->jmp(&done, Label::kNear);
989
990 assm->bind(&lhs_below_rhs);
991 DoubleRegister lhs_below_rhs_src = min_or_max == MinOrMax::kMin ? lhs : rhs;
992 if (dst != lhs_below_rhs_src) dop(movs, dst, lhs_below_rhs_src);
993 assm->jmp(&done, Label::kNear);
994
995 assm->bind(&lhs_above_rhs);
996 DoubleRegister lhs_above_rhs_src = min_or_max == MinOrMax::kMin ? rhs : lhs;
997 if (dst != lhs_above_rhs_src) dop(movs, dst, lhs_above_rhs_src);
998
999 assm->bind(&done);
1000 }
1001 } // namespace liftoff
1002
emit_f32_min(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1003 void LiftoffAssembler::emit_f32_min(DoubleRegister dst, DoubleRegister lhs,
1004 DoubleRegister rhs) {
1005 liftoff::EmitFloatMinOrMax<float>(this, dst, lhs, rhs,
1006 liftoff::MinOrMax::kMin);
1007 }
1008
emit_f32_max(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1009 void LiftoffAssembler::emit_f32_max(DoubleRegister dst, DoubleRegister lhs,
1010 DoubleRegister rhs) {
1011 liftoff::EmitFloatMinOrMax<float>(this, dst, lhs, rhs,
1012 liftoff::MinOrMax::kMax);
1013 }
1014
emit_f32_abs(DoubleRegister dst,DoubleRegister src)1015 void LiftoffAssembler::emit_f32_abs(DoubleRegister dst, DoubleRegister src) {
1016 static constexpr uint32_t kSignBit = uint32_t{1} << 31;
1017 if (dst == src) {
1018 TurboAssembler::Move(liftoff::kScratchDoubleReg, kSignBit - 1);
1019 Andps(dst, liftoff::kScratchDoubleReg);
1020 } else {
1021 TurboAssembler::Move(dst, kSignBit - 1);
1022 Andps(dst, src);
1023 }
1024 }
1025
emit_f32_neg(DoubleRegister dst,DoubleRegister src)1026 void LiftoffAssembler::emit_f32_neg(DoubleRegister dst, DoubleRegister src) {
1027 static constexpr uint32_t kSignBit = uint32_t{1} << 31;
1028 if (dst == src) {
1029 TurboAssembler::Move(liftoff::kScratchDoubleReg, kSignBit);
1030 Xorps(dst, liftoff::kScratchDoubleReg);
1031 } else {
1032 TurboAssembler::Move(dst, kSignBit);
1033 Xorps(dst, src);
1034 }
1035 }
1036
emit_f32_ceil(DoubleRegister dst,DoubleRegister src)1037 void LiftoffAssembler::emit_f32_ceil(DoubleRegister dst, DoubleRegister src) {
1038 REQUIRE_CPU_FEATURE(SSE4_1);
1039 roundss(dst, src, kRoundUp);
1040 }
1041
emit_f32_floor(DoubleRegister dst,DoubleRegister src)1042 void LiftoffAssembler::emit_f32_floor(DoubleRegister dst, DoubleRegister src) {
1043 REQUIRE_CPU_FEATURE(SSE4_1);
1044 roundss(dst, src, kRoundDown);
1045 }
1046
emit_f32_trunc(DoubleRegister dst,DoubleRegister src)1047 void LiftoffAssembler::emit_f32_trunc(DoubleRegister dst, DoubleRegister src) {
1048 REQUIRE_CPU_FEATURE(SSE4_1);
1049 roundss(dst, src, kRoundToZero);
1050 }
1051
emit_f32_nearest_int(DoubleRegister dst,DoubleRegister src)1052 void LiftoffAssembler::emit_f32_nearest_int(DoubleRegister dst,
1053 DoubleRegister src) {
1054 REQUIRE_CPU_FEATURE(SSE4_1);
1055 roundss(dst, src, kRoundToNearest);
1056 }
1057
emit_f32_sqrt(DoubleRegister dst,DoubleRegister src)1058 void LiftoffAssembler::emit_f32_sqrt(DoubleRegister dst, DoubleRegister src) {
1059 Sqrtss(dst, src);
1060 }
1061
emit_f64_add(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1062 void LiftoffAssembler::emit_f64_add(DoubleRegister dst, DoubleRegister lhs,
1063 DoubleRegister rhs) {
1064 if (CpuFeatures::IsSupported(AVX)) {
1065 CpuFeatureScope scope(this, AVX);
1066 vaddsd(dst, lhs, rhs);
1067 } else if (dst == rhs) {
1068 addsd(dst, lhs);
1069 } else {
1070 if (dst != lhs) movsd(dst, lhs);
1071 addsd(dst, rhs);
1072 }
1073 }
1074
emit_f64_sub(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1075 void LiftoffAssembler::emit_f64_sub(DoubleRegister dst, DoubleRegister lhs,
1076 DoubleRegister rhs) {
1077 if (CpuFeatures::IsSupported(AVX)) {
1078 CpuFeatureScope scope(this, AVX);
1079 vsubsd(dst, lhs, rhs);
1080 } else if (dst == rhs) {
1081 movsd(liftoff::kScratchDoubleReg, rhs);
1082 movsd(dst, lhs);
1083 subsd(dst, liftoff::kScratchDoubleReg);
1084 } else {
1085 if (dst != lhs) movsd(dst, lhs);
1086 subsd(dst, rhs);
1087 }
1088 }
1089
emit_f64_mul(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1090 void LiftoffAssembler::emit_f64_mul(DoubleRegister dst, DoubleRegister lhs,
1091 DoubleRegister rhs) {
1092 if (CpuFeatures::IsSupported(AVX)) {
1093 CpuFeatureScope scope(this, AVX);
1094 vmulsd(dst, lhs, rhs);
1095 } else if (dst == rhs) {
1096 mulsd(dst, lhs);
1097 } else {
1098 if (dst != lhs) movsd(dst, lhs);
1099 mulsd(dst, rhs);
1100 }
1101 }
1102
emit_f64_div(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1103 void LiftoffAssembler::emit_f64_div(DoubleRegister dst, DoubleRegister lhs,
1104 DoubleRegister rhs) {
1105 if (CpuFeatures::IsSupported(AVX)) {
1106 CpuFeatureScope scope(this, AVX);
1107 vdivsd(dst, lhs, rhs);
1108 } else if (dst == rhs) {
1109 movsd(liftoff::kScratchDoubleReg, rhs);
1110 movsd(dst, lhs);
1111 divsd(dst, liftoff::kScratchDoubleReg);
1112 } else {
1113 if (dst != lhs) movsd(dst, lhs);
1114 divsd(dst, rhs);
1115 }
1116 }
1117
emit_f64_min(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1118 void LiftoffAssembler::emit_f64_min(DoubleRegister dst, DoubleRegister lhs,
1119 DoubleRegister rhs) {
1120 liftoff::EmitFloatMinOrMax<double>(this, dst, lhs, rhs,
1121 liftoff::MinOrMax::kMin);
1122 }
1123
emit_f64_max(DoubleRegister dst,DoubleRegister lhs,DoubleRegister rhs)1124 void LiftoffAssembler::emit_f64_max(DoubleRegister dst, DoubleRegister lhs,
1125 DoubleRegister rhs) {
1126 liftoff::EmitFloatMinOrMax<double>(this, dst, lhs, rhs,
1127 liftoff::MinOrMax::kMax);
1128 }
1129
emit_f64_abs(DoubleRegister dst,DoubleRegister src)1130 void LiftoffAssembler::emit_f64_abs(DoubleRegister dst, DoubleRegister src) {
1131 static constexpr uint64_t kSignBit = uint64_t{1} << 63;
1132 if (dst == src) {
1133 TurboAssembler::Move(liftoff::kScratchDoubleReg, kSignBit - 1);
1134 Andpd(dst, liftoff::kScratchDoubleReg);
1135 } else {
1136 TurboAssembler::Move(dst, kSignBit - 1);
1137 Andpd(dst, src);
1138 }
1139 }
1140
emit_f64_neg(DoubleRegister dst,DoubleRegister src)1141 void LiftoffAssembler::emit_f64_neg(DoubleRegister dst, DoubleRegister src) {
1142 static constexpr uint64_t kSignBit = uint64_t{1} << 63;
1143 if (dst == src) {
1144 TurboAssembler::Move(liftoff::kScratchDoubleReg, kSignBit);
1145 Xorpd(dst, liftoff::kScratchDoubleReg);
1146 } else {
1147 TurboAssembler::Move(dst, kSignBit);
1148 Xorpd(dst, src);
1149 }
1150 }
1151
emit_f64_ceil(DoubleRegister dst,DoubleRegister src)1152 bool LiftoffAssembler::emit_f64_ceil(DoubleRegister dst, DoubleRegister src) {
1153 REQUIRE_CPU_FEATURE(SSE4_1, true);
1154 roundsd(dst, src, kRoundUp);
1155 return true;
1156 }
1157
emit_f64_floor(DoubleRegister dst,DoubleRegister src)1158 bool LiftoffAssembler::emit_f64_floor(DoubleRegister dst, DoubleRegister src) {
1159 REQUIRE_CPU_FEATURE(SSE4_1, true);
1160 roundsd(dst, src, kRoundDown);
1161 return true;
1162 }
1163
emit_f64_trunc(DoubleRegister dst,DoubleRegister src)1164 bool LiftoffAssembler::emit_f64_trunc(DoubleRegister dst, DoubleRegister src) {
1165 REQUIRE_CPU_FEATURE(SSE4_1, true);
1166 roundsd(dst, src, kRoundToZero);
1167 return true;
1168 }
1169
emit_f64_nearest_int(DoubleRegister dst,DoubleRegister src)1170 bool LiftoffAssembler::emit_f64_nearest_int(DoubleRegister dst,
1171 DoubleRegister src) {
1172 REQUIRE_CPU_FEATURE(SSE4_1, true);
1173 roundsd(dst, src, kRoundToNearest);
1174 return true;
1175 }
1176
emit_f64_sqrt(DoubleRegister dst,DoubleRegister src)1177 void LiftoffAssembler::emit_f64_sqrt(DoubleRegister dst, DoubleRegister src) {
1178 Sqrtsd(dst, src);
1179 }
1180
1181 namespace liftoff {
1182 // Used for float to int conversions. If the value in {converted_back} equals
1183 // {src} afterwards, the conversion succeeded.
1184 template <typename dst_type, typename src_type>
ConvertFloatToIntAndBack(LiftoffAssembler * assm,Register dst,DoubleRegister src,DoubleRegister converted_back,LiftoffRegList pinned)1185 inline void ConvertFloatToIntAndBack(LiftoffAssembler* assm, Register dst,
1186 DoubleRegister src,
1187 DoubleRegister converted_back,
1188 LiftoffRegList pinned) {
1189 if (std::is_same<double, src_type>::value) { // f64
1190 if (std::is_signed<dst_type>::value) { // f64 -> i32
1191 assm->cvttsd2si(dst, src);
1192 assm->Cvtsi2sd(converted_back, dst);
1193 } else { // f64 -> u32
1194 assm->Cvttsd2ui(dst, src, liftoff::kScratchDoubleReg);
1195 assm->Cvtui2sd(converted_back, dst);
1196 }
1197 } else { // f32
1198 if (std::is_signed<dst_type>::value) { // f32 -> i32
1199 assm->cvttss2si(dst, src);
1200 assm->Cvtsi2ss(converted_back, dst);
1201 } else { // f32 -> u32
1202 assm->Cvttss2ui(dst, src, liftoff::kScratchDoubleReg);
1203 assm->Cvtui2ss(converted_back, dst,
1204 assm->GetUnusedRegister(kGpReg, pinned).gp());
1205 }
1206 }
1207 }
1208
1209 template <typename dst_type, typename src_type>
EmitTruncateFloatToInt(LiftoffAssembler * assm,Register dst,DoubleRegister src,Label * trap)1210 inline bool EmitTruncateFloatToInt(LiftoffAssembler* assm, Register dst,
1211 DoubleRegister src, Label* trap) {
1212 if (!CpuFeatures::IsSupported(SSE4_1)) {
1213 assm->bailout("no SSE4.1");
1214 return true;
1215 }
1216 CpuFeatureScope feature(assm, SSE4_1);
1217
1218 LiftoffRegList pinned = LiftoffRegList::ForRegs(src, dst);
1219 DoubleRegister rounded =
1220 pinned.set(assm->GetUnusedRegister(kFpReg, pinned)).fp();
1221 DoubleRegister converted_back =
1222 pinned.set(assm->GetUnusedRegister(kFpReg, pinned)).fp();
1223
1224 if (std::is_same<double, src_type>::value) { // f64
1225 assm->roundsd(rounded, src, kRoundToZero);
1226 } else { // f32
1227 assm->roundss(rounded, src, kRoundToZero);
1228 }
1229 ConvertFloatToIntAndBack<dst_type, src_type>(assm, dst, rounded,
1230 converted_back, pinned);
1231 if (std::is_same<double, src_type>::value) { // f64
1232 assm->ucomisd(converted_back, rounded);
1233 } else { // f32
1234 assm->ucomiss(converted_back, rounded);
1235 }
1236
1237 // Jump to trap if PF is 0 (one of the operands was NaN) or they are not
1238 // equal.
1239 assm->j(parity_even, trap);
1240 assm->j(not_equal, trap);
1241 return true;
1242 }
1243 } // namespace liftoff
1244
emit_type_conversion(WasmOpcode opcode,LiftoffRegister dst,LiftoffRegister src,Label * trap)1245 bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode,
1246 LiftoffRegister dst,
1247 LiftoffRegister src, Label* trap) {
1248 switch (opcode) {
1249 case kExprI32ConvertI64:
1250 if (dst.gp() != src.low_gp()) mov(dst.gp(), src.low_gp());
1251 return true;
1252 case kExprI32SConvertF32:
1253 return liftoff::EmitTruncateFloatToInt<int32_t, float>(this, dst.gp(),
1254 src.fp(), trap);
1255 case kExprI32UConvertF32:
1256 return liftoff::EmitTruncateFloatToInt<uint32_t, float>(this, dst.gp(),
1257 src.fp(), trap);
1258 case kExprI32SConvertF64:
1259 return liftoff::EmitTruncateFloatToInt<int32_t, double>(this, dst.gp(),
1260 src.fp(), trap);
1261 case kExprI32UConvertF64:
1262 return liftoff::EmitTruncateFloatToInt<uint32_t, double>(this, dst.gp(),
1263 src.fp(), trap);
1264 case kExprI32ReinterpretF32:
1265 Movd(dst.gp(), src.fp());
1266 return true;
1267 case kExprI64SConvertI32:
1268 if (dst.low_gp() != src.gp()) mov(dst.low_gp(), src.gp());
1269 mov(dst.high_gp(), src.gp());
1270 sar(dst.high_gp(), 31);
1271 return true;
1272 case kExprI64UConvertI32:
1273 if (dst.low_gp() != src.gp()) mov(dst.low_gp(), src.gp());
1274 xor_(dst.high_gp(), dst.high_gp());
1275 return true;
1276 case kExprI64ReinterpretF64:
1277 // Push src to the stack.
1278 sub(esp, Immediate(8));
1279 movsd(Operand(esp, 0), src.fp());
1280 // Pop to dst.
1281 pop(dst.low_gp());
1282 pop(dst.high_gp());
1283 return true;
1284 case kExprF32SConvertI32:
1285 cvtsi2ss(dst.fp(), src.gp());
1286 return true;
1287 case kExprF32UConvertI32: {
1288 LiftoffRegList pinned = LiftoffRegList::ForRegs(dst, src);
1289 Register scratch = GetUnusedRegister(kGpReg, pinned).gp();
1290 Cvtui2ss(dst.fp(), src.gp(), scratch);
1291 return true;
1292 }
1293 case kExprF32ConvertF64:
1294 cvtsd2ss(dst.fp(), src.fp());
1295 return true;
1296 case kExprF32ReinterpretI32:
1297 Movd(dst.fp(), src.gp());
1298 return true;
1299 case kExprF64SConvertI32:
1300 Cvtsi2sd(dst.fp(), src.gp());
1301 return true;
1302 case kExprF64UConvertI32:
1303 Cvtui2sd(dst.fp(), src.gp());
1304 return true;
1305 case kExprF64ConvertF32:
1306 cvtss2sd(dst.fp(), src.fp());
1307 return true;
1308 case kExprF64ReinterpretI64:
1309 // Push src to the stack.
1310 push(src.high_gp());
1311 push(src.low_gp());
1312 // Pop to dst.
1313 movsd(dst.fp(), Operand(esp, 0));
1314 add(esp, Immediate(8));
1315 return true;
1316 default:
1317 return false;
1318 }
1319 }
1320
emit_jump(Label * label)1321 void LiftoffAssembler::emit_jump(Label* label) { jmp(label); }
1322
emit_jump(Register target)1323 void LiftoffAssembler::emit_jump(Register target) { jmp(target); }
1324
emit_cond_jump(Condition cond,Label * label,ValueType type,Register lhs,Register rhs)1325 void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label,
1326 ValueType type, Register lhs,
1327 Register rhs) {
1328 if (rhs != no_reg) {
1329 switch (type) {
1330 case kWasmI32:
1331 cmp(lhs, rhs);
1332 break;
1333 default:
1334 UNREACHABLE();
1335 }
1336 } else {
1337 DCHECK_EQ(type, kWasmI32);
1338 test(lhs, lhs);
1339 }
1340
1341 j(cond, label);
1342 }
1343
1344 namespace liftoff {
1345
1346 // Get a temporary byte register, using {candidate} if possible.
1347 // Might spill, but always keeps status flags intact.
GetTmpByteRegister(LiftoffAssembler * assm,Register candidate)1348 inline Register GetTmpByteRegister(LiftoffAssembler* assm, Register candidate) {
1349 if (candidate.is_byte_register()) return candidate;
1350 LiftoffRegList pinned = LiftoffRegList::ForRegs(candidate);
1351 // {GetUnusedRegister()} may insert move instructions to spill registers to
1352 // the stack. This is OK because {mov} does not change the status flags.
1353 return assm->GetUnusedRegister(liftoff::kByteRegs, pinned).gp();
1354 }
1355
1356 // Setcc into dst register, given a scratch byte register (might be the same as
1357 // dst). Never spills.
setcc_32_no_spill(LiftoffAssembler * assm,Condition cond,Register dst,Register tmp_byte_reg)1358 inline void setcc_32_no_spill(LiftoffAssembler* assm, Condition cond,
1359 Register dst, Register tmp_byte_reg) {
1360 assm->setcc(cond, tmp_byte_reg);
1361 assm->movzx_b(dst, tmp_byte_reg);
1362 }
1363
1364 // Setcc into dst register (no contraints). Might spill.
setcc_32(LiftoffAssembler * assm,Condition cond,Register dst)1365 inline void setcc_32(LiftoffAssembler* assm, Condition cond, Register dst) {
1366 Register tmp_byte_reg = GetTmpByteRegister(assm, dst);
1367 setcc_32_no_spill(assm, cond, dst, tmp_byte_reg);
1368 }
1369
1370 } // namespace liftoff
1371
emit_i32_eqz(Register dst,Register src)1372 void LiftoffAssembler::emit_i32_eqz(Register dst, Register src) {
1373 test(src, src);
1374 liftoff::setcc_32(this, equal, dst);
1375 }
1376
emit_i32_set_cond(Condition cond,Register dst,Register lhs,Register rhs)1377 void LiftoffAssembler::emit_i32_set_cond(Condition cond, Register dst,
1378 Register lhs, Register rhs) {
1379 cmp(lhs, rhs);
1380 liftoff::setcc_32(this, cond, dst);
1381 }
1382
emit_i64_eqz(Register dst,LiftoffRegister src)1383 void LiftoffAssembler::emit_i64_eqz(Register dst, LiftoffRegister src) {
1384 // Compute the OR of both registers in the src pair, using dst as scratch
1385 // register. Then check whether the result is equal to zero.
1386 if (src.low_gp() == dst) {
1387 or_(dst, src.high_gp());
1388 } else {
1389 if (src.high_gp() != dst) mov(dst, src.high_gp());
1390 or_(dst, src.low_gp());
1391 }
1392 liftoff::setcc_32(this, equal, dst);
1393 }
1394
1395 namespace liftoff {
cond_make_unsigned(Condition cond)1396 inline Condition cond_make_unsigned(Condition cond) {
1397 switch (cond) {
1398 case kSignedLessThan:
1399 return kUnsignedLessThan;
1400 case kSignedLessEqual:
1401 return kUnsignedLessEqual;
1402 case kSignedGreaterThan:
1403 return kUnsignedGreaterThan;
1404 case kSignedGreaterEqual:
1405 return kUnsignedGreaterEqual;
1406 default:
1407 return cond;
1408 }
1409 }
1410 } // namespace liftoff
1411
emit_i64_set_cond(Condition cond,Register dst,LiftoffRegister lhs,LiftoffRegister rhs)1412 void LiftoffAssembler::emit_i64_set_cond(Condition cond, Register dst,
1413 LiftoffRegister lhs,
1414 LiftoffRegister rhs) {
1415 // Get the tmp byte register out here, such that we don't conditionally spill
1416 // (this cannot be reflected in the cache state).
1417 Register tmp_byte_reg = liftoff::GetTmpByteRegister(this, dst);
1418
1419 // For signed i64 comparisons, we still need to use unsigned comparison for
1420 // the low word (the only bit carrying signedness information is the MSB in
1421 // the high word).
1422 Condition unsigned_cond = liftoff::cond_make_unsigned(cond);
1423 Label setcc;
1424 Label cont;
1425 // Compare high word first. If it differs, use if for the setcc. If it's
1426 // equal, compare the low word and use that for setcc.
1427 cmp(lhs.high_gp(), rhs.high_gp());
1428 j(not_equal, &setcc, Label::kNear);
1429 cmp(lhs.low_gp(), rhs.low_gp());
1430 if (unsigned_cond != cond) {
1431 // If the condition predicate for the low differs from that for the high
1432 // word, emit a separete setcc sequence for the low word.
1433 liftoff::setcc_32_no_spill(this, unsigned_cond, dst, tmp_byte_reg);
1434 jmp(&cont);
1435 }
1436 bind(&setcc);
1437 liftoff::setcc_32_no_spill(this, cond, dst, tmp_byte_reg);
1438 bind(&cont);
1439 }
1440
1441 namespace liftoff {
1442 template <void (Assembler::*cmp_op)(DoubleRegister, DoubleRegister)>
EmitFloatSetCond(LiftoffAssembler * assm,Condition cond,Register dst,DoubleRegister lhs,DoubleRegister rhs)1443 void EmitFloatSetCond(LiftoffAssembler* assm, Condition cond, Register dst,
1444 DoubleRegister lhs, DoubleRegister rhs) {
1445 Label cont;
1446 Label not_nan;
1447
1448 // Get the tmp byte register out here, such that we don't conditionally spill
1449 // (this cannot be reflected in the cache state).
1450 Register tmp_byte_reg = GetTmpByteRegister(assm, dst);
1451
1452 (assm->*cmp_op)(lhs, rhs);
1453 // If PF is one, one of the operands was Nan. This needs special handling.
1454 assm->j(parity_odd, ¬_nan, Label::kNear);
1455 // Return 1 for f32.ne, 0 for all other cases.
1456 if (cond == not_equal) {
1457 assm->mov(dst, Immediate(1));
1458 } else {
1459 assm->xor_(dst, dst);
1460 }
1461 assm->jmp(&cont, Label::kNear);
1462 assm->bind(¬_nan);
1463
1464 setcc_32_no_spill(assm, cond, dst, tmp_byte_reg);
1465 assm->bind(&cont);
1466 }
1467 } // namespace liftoff
1468
emit_f32_set_cond(Condition cond,Register dst,DoubleRegister lhs,DoubleRegister rhs)1469 void LiftoffAssembler::emit_f32_set_cond(Condition cond, Register dst,
1470 DoubleRegister lhs,
1471 DoubleRegister rhs) {
1472 liftoff::EmitFloatSetCond<&Assembler::ucomiss>(this, cond, dst, lhs, rhs);
1473 }
1474
emit_f64_set_cond(Condition cond,Register dst,DoubleRegister lhs,DoubleRegister rhs)1475 void LiftoffAssembler::emit_f64_set_cond(Condition cond, Register dst,
1476 DoubleRegister lhs,
1477 DoubleRegister rhs) {
1478 liftoff::EmitFloatSetCond<&Assembler::ucomisd>(this, cond, dst, lhs, rhs);
1479 }
1480
StackCheck(Label * ool_code,Register limit_address)1481 void LiftoffAssembler::StackCheck(Label* ool_code, Register limit_address) {
1482 cmp(esp, Operand(limit_address, 0));
1483 j(below_equal, ool_code);
1484 }
1485
CallTrapCallbackForTesting()1486 void LiftoffAssembler::CallTrapCallbackForTesting() {
1487 PrepareCallCFunction(0, GetUnusedRegister(kGpReg).gp());
1488 CallCFunction(ExternalReference::wasm_call_trap_callback_for_testing(), 0);
1489 }
1490
AssertUnreachable(AbortReason reason)1491 void LiftoffAssembler::AssertUnreachable(AbortReason reason) {
1492 TurboAssembler::AssertUnreachable(reason);
1493 }
1494
PushRegisters(LiftoffRegList regs)1495 void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {
1496 LiftoffRegList gp_regs = regs & kGpCacheRegList;
1497 while (!gp_regs.is_empty()) {
1498 LiftoffRegister reg = gp_regs.GetFirstRegSet();
1499 push(reg.gp());
1500 gp_regs.clear(reg);
1501 }
1502 LiftoffRegList fp_regs = regs & kFpCacheRegList;
1503 unsigned num_fp_regs = fp_regs.GetNumRegsSet();
1504 if (num_fp_regs) {
1505 sub(esp, Immediate(num_fp_regs * kStackSlotSize));
1506 unsigned offset = 0;
1507 while (!fp_regs.is_empty()) {
1508 LiftoffRegister reg = fp_regs.GetFirstRegSet();
1509 movsd(Operand(esp, offset), reg.fp());
1510 fp_regs.clear(reg);
1511 offset += sizeof(double);
1512 }
1513 DCHECK_EQ(offset, num_fp_regs * sizeof(double));
1514 }
1515 }
1516
PopRegisters(LiftoffRegList regs)1517 void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {
1518 LiftoffRegList fp_regs = regs & kFpCacheRegList;
1519 unsigned fp_offset = 0;
1520 while (!fp_regs.is_empty()) {
1521 LiftoffRegister reg = fp_regs.GetFirstRegSet();
1522 movsd(reg.fp(), Operand(esp, fp_offset));
1523 fp_regs.clear(reg);
1524 fp_offset += sizeof(double);
1525 }
1526 if (fp_offset) add(esp, Immediate(fp_offset));
1527 LiftoffRegList gp_regs = regs & kGpCacheRegList;
1528 while (!gp_regs.is_empty()) {
1529 LiftoffRegister reg = gp_regs.GetLastRegSet();
1530 pop(reg.gp());
1531 gp_regs.clear(reg);
1532 }
1533 }
1534
DropStackSlotsAndRet(uint32_t num_stack_slots)1535 void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {
1536 DCHECK_LT(num_stack_slots, (1 << 16) / kPointerSize); // 16 bit immediate
1537 ret(static_cast<int>(num_stack_slots * kPointerSize));
1538 }
1539
CallC(wasm::FunctionSig * sig,const LiftoffRegister * args,const LiftoffRegister * rets,ValueType out_argument_type,int stack_bytes,ExternalReference ext_ref)1540 void LiftoffAssembler::CallC(wasm::FunctionSig* sig,
1541 const LiftoffRegister* args,
1542 const LiftoffRegister* rets,
1543 ValueType out_argument_type, int stack_bytes,
1544 ExternalReference ext_ref) {
1545 sub(esp, Immediate(stack_bytes));
1546
1547 int arg_bytes = 0;
1548 for (ValueType param_type : sig->parameters()) {
1549 liftoff::Store(this, esp, arg_bytes, *args++, param_type);
1550 arg_bytes += ValueTypes::MemSize(param_type);
1551 }
1552 DCHECK_LE(arg_bytes, stack_bytes);
1553
1554 constexpr Register kScratch = eax;
1555 constexpr Register kArgumentBuffer = ecx;
1556 constexpr int kNumCCallArgs = 1;
1557 mov(kArgumentBuffer, esp);
1558 PrepareCallCFunction(kNumCCallArgs, kScratch);
1559
1560 // Pass a pointer to the buffer with the arguments to the C function. ia32
1561 // does not use registers here, so push to the stack.
1562 mov(Operand(esp, 0), kArgumentBuffer);
1563
1564 // Now call the C function.
1565 CallCFunction(ext_ref, kNumCCallArgs);
1566
1567 // Move return value to the right register.
1568 const LiftoffRegister* next_result_reg = rets;
1569 if (sig->return_count() > 0) {
1570 DCHECK_EQ(1, sig->return_count());
1571 constexpr Register kReturnReg = eax;
1572 if (kReturnReg != next_result_reg->gp()) {
1573 Move(*next_result_reg, LiftoffRegister(kReturnReg), sig->GetReturn(0));
1574 }
1575 ++next_result_reg;
1576 }
1577
1578 // Load potential output value from the buffer on the stack.
1579 if (out_argument_type != kWasmStmt) {
1580 liftoff::Load(this, *next_result_reg, esp, 0, out_argument_type);
1581 }
1582
1583 add(esp, Immediate(stack_bytes));
1584 }
1585
CallNativeWasmCode(Address addr)1586 void LiftoffAssembler::CallNativeWasmCode(Address addr) {
1587 wasm_call(addr, RelocInfo::WASM_CALL);
1588 }
1589
CallIndirect(wasm::FunctionSig * sig,compiler::CallDescriptor * call_descriptor,Register target)1590 void LiftoffAssembler::CallIndirect(wasm::FunctionSig* sig,
1591 compiler::CallDescriptor* call_descriptor,
1592 Register target) {
1593 // Since we have more cache registers than parameter registers, the
1594 // {LiftoffCompiler} should always be able to place {target} in a register.
1595 DCHECK(target.is_valid());
1596 if (FLAG_untrusted_code_mitigations) {
1597 RetpolineCall(target);
1598 } else {
1599 call(target);
1600 }
1601 }
1602
CallRuntimeStub(WasmCode::RuntimeStubId sid)1603 void LiftoffAssembler::CallRuntimeStub(WasmCode::RuntimeStubId sid) {
1604 // A direct call to a wasm runtime stub defined in this module.
1605 // Just encode the stub index. This will be patched at relocation.
1606 wasm_call(static_cast<Address>(sid), RelocInfo::WASM_STUB_CALL);
1607 }
1608
AllocateStackSlot(Register addr,uint32_t size)1609 void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) {
1610 sub(esp, Immediate(size));
1611 mov(addr, esp);
1612 }
1613
DeallocateStackSlot(uint32_t size)1614 void LiftoffAssembler::DeallocateStackSlot(uint32_t size) {
1615 add(esp, Immediate(size));
1616 }
1617
Construct()1618 void LiftoffStackSlots::Construct() {
1619 for (auto& slot : slots_) {
1620 const LiftoffAssembler::VarState& src = slot.src_;
1621 switch (src.loc()) {
1622 case LiftoffAssembler::VarState::kStack:
1623 if (src.type() == kWasmF64) {
1624 DCHECK_EQ(kLowWord, slot.half_);
1625 asm_->push(liftoff::GetHalfStackSlot(2 * slot.src_index_ - 1));
1626 }
1627 asm_->push(liftoff::GetHalfStackSlot(2 * slot.src_index_ -
1628 (slot.half_ == kLowWord ? 0 : 1)));
1629 break;
1630 case LiftoffAssembler::VarState::kRegister:
1631 if (src.type() == kWasmI64) {
1632 liftoff::push(
1633 asm_, slot.half_ == kLowWord ? src.reg().low() : src.reg().high(),
1634 kWasmI32);
1635 } else {
1636 liftoff::push(asm_, src.reg(), src.type());
1637 }
1638 break;
1639 case LiftoffAssembler::VarState::KIntConst:
1640 // The high word is the sign extension of the low word.
1641 asm_->push(Immediate(slot.half_ == kLowWord ? src.i32_const()
1642 : src.i32_const() >> 31));
1643 break;
1644 }
1645 }
1646 }
1647
1648 #undef REQUIRE_CPU_FEATURE
1649
1650 } // namespace wasm
1651 } // namespace internal
1652 } // namespace v8
1653
1654 #endif // V8_WASM_BASELINE_IA32_LIFTOFF_ASSEMBLER_IA32_H_
1655