Line data Source code
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_X64_LIFTOFF_ASSEMBLER_X64_H_
6 : #define V8_WASM_BASELINE_X64_LIFTOFF_ASSEMBLER_X64_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 : constexpr Register kScratchRegister2 = r11;
27 : static_assert(kScratchRegister != kScratchRegister2, "collision");
28 : static_assert((kLiftoffAssemblerGpCacheRegs &
29 : Register::ListOf<kScratchRegister, kScratchRegister2>()) == 0,
30 : "scratch registers must not be used as cache registers");
31 :
32 : constexpr DoubleRegister kScratchDoubleReg2 = xmm14;
33 : static_assert(kScratchDoubleReg != kScratchDoubleReg2, "collision");
34 : static_assert(
35 : (kLiftoffAssemblerFpCacheRegs &
36 : DoubleRegister::ListOf<kScratchDoubleReg, kScratchDoubleReg2>()) == 0,
37 : "scratch registers must not be used as cache registers");
38 :
39 : // rbp-8 holds the stack marker, rbp-16 is the instance parameter, first stack
40 : // slot is located at rbp-24.
41 : constexpr int32_t kConstantStackSpace = 16;
42 : constexpr int32_t kFirstStackSlotOffset =
43 : kConstantStackSpace + LiftoffAssembler::kStackSlotSize;
44 :
45 : inline Operand GetStackSlot(uint32_t index) {
46 143668 : int32_t offset = index * LiftoffAssembler::kStackSlotSize;
47 143668 : return Operand(rbp, -kFirstStackSlotOffset - offset);
48 : }
49 :
50 : // TODO(clemensh): Make this a constexpr variable once Operand is constexpr.
51 816759 : inline Operand GetInstanceOperand() { return Operand(rbp, -16); }
52 :
53 261228 : inline Operand GetMemOp(LiftoffAssembler* assm, Register addr, Register offset,
54 : uint32_t offset_imm) {
55 261228 : if (is_uint31(offset_imm)) {
56 261228 : if (offset == no_reg) return Operand(addr, offset_imm);
57 175002 : return Operand(addr, offset, times_1, offset_imm);
58 : }
59 : // Offset immediate does not fit in 31 bits.
60 : Register scratch = kScratchRegister;
61 0 : assm->movl(scratch, Immediate(offset_imm));
62 0 : if (offset != no_reg) {
63 : assm->addq(scratch, offset);
64 : }
65 0 : return Operand(addr, scratch, times_1, 0);
66 : }
67 :
68 19301 : inline void Load(LiftoffAssembler* assm, LiftoffRegister dst, Operand src,
69 : ValueType type) {
70 19301 : switch (type) {
71 : case kWasmI32:
72 5262 : assm->movl(dst.gp(), src);
73 : break;
74 : case kWasmI64:
75 3397 : assm->movq(dst.gp(), src);
76 : break;
77 : case kWasmF32:
78 : assm->Movss(dst.fp(), src);
79 : break;
80 : case kWasmF64:
81 : assm->Movsd(dst.fp(), src);
82 : break;
83 : default:
84 0 : UNREACHABLE();
85 : }
86 19301 : }
87 :
88 53864 : inline void Store(LiftoffAssembler* assm, Operand dst, LiftoffRegister src,
89 : ValueType type) {
90 53864 : switch (type) {
91 : case kWasmI32:
92 53864 : assm->movl(dst, src.gp());
93 : break;
94 : case kWasmI64:
95 0 : assm->movq(dst, src.gp());
96 : break;
97 : case kWasmF32:
98 : assm->Movss(dst, src.fp());
99 : break;
100 : case kWasmF64:
101 : assm->Movsd(dst, src.fp());
102 : break;
103 : default:
104 0 : UNREACHABLE();
105 : }
106 53864 : }
107 :
108 7560 : inline void push(LiftoffAssembler* assm, LiftoffRegister reg, ValueType type) {
109 7560 : switch (type) {
110 : case kWasmI32:
111 : case kWasmI64:
112 2492 : assm->pushq(reg.gp());
113 2493 : break;
114 : case kWasmF32:
115 2173 : assm->subq(rsp, Immediate(kSystemPointerSize));
116 4346 : assm->Movss(Operand(rsp, 0), reg.fp());
117 2173 : break;
118 : case kWasmF64:
119 2895 : assm->subq(rsp, Immediate(kSystemPointerSize));
120 5790 : assm->Movsd(Operand(rsp, 0), reg.fp());
121 2895 : break;
122 : default:
123 0 : UNREACHABLE();
124 : }
125 7561 : }
126 :
127 : template <typename... Regs>
128 58783 : inline void SpillRegisters(LiftoffAssembler* assm, Regs... regs) {
129 293909 : for (LiftoffRegister r : {LiftoffRegister(regs)...}) {
130 117564 : if (assm->cache_state()->is_used(r)) assm->SpillRegister(r);
131 : }
132 58782 : }
133 :
134 : } // namespace liftoff
135 :
136 : int LiftoffAssembler::PrepareStackFrame() {
137 : int offset = pc_offset();
138 455498 : sub_sp_32(0);
139 : return offset;
140 : }
141 :
142 441339 : void LiftoffAssembler::PatchPrepareStackFrame(int offset,
143 : uint32_t stack_slots) {
144 441339 : uint32_t bytes = liftoff::kConstantStackSpace + kStackSlotSize * stack_slots;
145 : DCHECK_LE(bytes, kMaxInt);
146 : // We can't run out of space, just pass anything big enough to not cause the
147 : // assembler to try to grow the buffer.
148 : constexpr int kAvailableSpace = 64;
149 : Assembler patching_assembler(
150 : AssemblerOptions{},
151 1767391 : ExternalAssemblerBuffer(buffer_start_ + offset, kAvailableSpace));
152 442040 : patching_assembler.sub_sp_32(bytes);
153 442223 : }
154 :
155 : void LiftoffAssembler::FinishCode() {}
156 :
157 : void LiftoffAssembler::AbortCompilation() {}
158 :
159 1102881 : void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value,
160 : RelocInfo::Mode rmode) {
161 1102881 : switch (value.type()) {
162 : case kWasmI32:
163 996277 : if (value.to_i32() == 0 && RelocInfo::IsNone(rmode)) {
164 75228 : xorl(reg.gp(), reg.gp());
165 : } else {
166 921049 : movl(reg.gp(), Immediate(value.to_i32(), rmode));
167 : }
168 : break;
169 : case kWasmI64:
170 16702 : if (RelocInfo::IsNone(rmode)) {
171 16702 : TurboAssembler::Set(reg.gp(), value.to_i64());
172 : } else {
173 0 : movq(reg.gp(), Immediate64(value.to_i64(), rmode));
174 : }
175 : break;
176 : case kWasmF32:
177 44401 : TurboAssembler::Move(reg.fp(), value.to_f32_boxed().get_bits());
178 44401 : break;
179 : case kWasmF64:
180 45501 : TurboAssembler::Move(reg.fp(), value.to_f64_boxed().get_bits());
181 45501 : break;
182 : default:
183 0 : UNREACHABLE();
184 : }
185 1102597 : }
186 :
187 313535 : void LiftoffAssembler::LoadFromInstance(Register dst, uint32_t offset,
188 : int size) {
189 : DCHECK_LE(offset, kMaxInt);
190 313806 : movq(dst, liftoff::GetInstanceOperand());
191 : DCHECK(size == 4 || size == 8);
192 313989 : if (size == 4) {
193 2864 : movl(dst, Operand(dst, offset));
194 : } else {
195 625380 : movq(dst, Operand(dst, offset));
196 : }
197 314255 : }
198 :
199 44173 : void LiftoffAssembler::LoadTaggedPointerFromInstance(Register dst,
200 : uint32_t offset) {
201 : DCHECK_LE(offset, kMaxInt);
202 44173 : movq(dst, liftoff::GetInstanceOperand());
203 44173 : LoadTaggedPointerField(dst, Operand(dst, offset));
204 44174 : }
205 :
206 455482 : void LiftoffAssembler::SpillInstance(Register instance) {
207 455732 : movq(liftoff::GetInstanceOperand(), instance);
208 455727 : }
209 :
210 3569 : void LiftoffAssembler::FillInstanceInto(Register dst) {
211 3573 : movq(dst, liftoff::GetInstanceOperand());
212 3577 : }
213 :
214 44154 : void LiftoffAssembler::LoadTaggedPointer(Register dst, Register src_addr,
215 : Register offset_reg,
216 : uint32_t offset_imm,
217 : LiftoffRegList pinned) {
218 44154 : if (emit_debug_code() && offset_reg != no_reg) {
219 0 : AssertZeroExtended(offset_reg);
220 : }
221 44154 : Operand src_op = liftoff::GetMemOp(this, src_addr, offset_reg, offset_imm);
222 44154 : LoadTaggedPointerField(dst, src_op);
223 44155 : }
224 :
225 120362 : void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr,
226 : Register offset_reg, uint32_t offset_imm,
227 : LoadType type, LiftoffRegList pinned,
228 : uint32_t* protected_load_pc, bool is_load_mem) {
229 120362 : if (emit_debug_code() && offset_reg != no_reg) {
230 0 : AssertZeroExtended(offset_reg);
231 : }
232 120362 : Operand src_op = liftoff::GetMemOp(this, src_addr, offset_reg, offset_imm);
233 194617 : if (protected_load_pc) *protected_load_pc = pc_offset();
234 120379 : switch (type.value()) {
235 : case LoadType::kI32Load8U:
236 : case LoadType::kI64Load8U:
237 269 : movzxbl(dst.gp(), src_op);
238 : break;
239 : case LoadType::kI32Load8S:
240 1054 : movsxbl(dst.gp(), src_op);
241 1054 : break;
242 : case LoadType::kI64Load8S:
243 44 : movsxbq(dst.gp(), src_op);
244 44 : break;
245 : case LoadType::kI32Load16U:
246 : case LoadType::kI64Load16U:
247 166 : movzxwl(dst.gp(), src_op);
248 : break;
249 : case LoadType::kI32Load16S:
250 58 : movsxwl(dst.gp(), src_op);
251 58 : break;
252 : case LoadType::kI64Load16S:
253 50 : movsxwq(dst.gp(), src_op);
254 50 : break;
255 : case LoadType::kI32Load:
256 : case LoadType::kI64Load32U:
257 36876 : movl(dst.gp(), src_op);
258 : break;
259 : case LoadType::kI64Load32S:
260 53 : movsxlq(dst.gp(), src_op);
261 53 : break;
262 : case LoadType::kI64Load:
263 79431 : movq(dst.gp(), src_op);
264 : break;
265 : case LoadType::kF32Load:
266 : Movss(dst.fp(), src_op);
267 : break;
268 : case LoadType::kF64Load:
269 : Movsd(dst.fp(), src_op);
270 : break;
271 : default:
272 0 : UNREACHABLE();
273 : }
274 120371 : }
275 :
276 96717 : void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
277 : uint32_t offset_imm, LiftoffRegister src,
278 : StoreType type, LiftoffRegList /* pinned */,
279 : uint32_t* protected_store_pc, bool is_store_mem) {
280 96717 : if (emit_debug_code() && offset_reg != no_reg) {
281 0 : AssertZeroExtended(offset_reg);
282 : }
283 96717 : Operand dst_op = liftoff::GetMemOp(this, dst_addr, offset_reg, offset_imm);
284 193455 : if (protected_store_pc) *protected_store_pc = pc_offset();
285 96860 : switch (type.value()) {
286 : case StoreType::kI32Store8:
287 : case StoreType::kI64Store8:
288 358 : movb(dst_op, src.gp());
289 358 : break;
290 : case StoreType::kI32Store16:
291 : case StoreType::kI64Store16:
292 174 : movw(dst_op, src.gp());
293 174 : break;
294 : case StoreType::kI32Store:
295 : case StoreType::kI64Store32:
296 55580 : movl(dst_op, src.gp());
297 : break;
298 : case StoreType::kI64Store:
299 40294 : movq(dst_op, src.gp());
300 : break;
301 : case StoreType::kF32Store:
302 : Movss(dst_op, src.fp());
303 : break;
304 : case StoreType::kF64Store:
305 : Movsd(dst_op, src.fp());
306 : break;
307 : default:
308 0 : UNREACHABLE();
309 : }
310 96849 : }
311 :
312 19301 : void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
313 : uint32_t caller_slot_idx,
314 : ValueType type) {
315 19301 : Operand src(rbp, kSystemPointerSize * (caller_slot_idx + 1));
316 19301 : liftoff::Load(this, dst, src, type);
317 19301 : }
318 :
319 6 : void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index,
320 : ValueType type) {
321 : DCHECK_NE(dst_index, src_index);
322 6 : Operand src = liftoff::GetStackSlot(src_index);
323 6 : Operand dst = liftoff::GetStackSlot(dst_index);
324 6 : if (ValueTypes::ElementSizeLog2Of(type) == 2) {
325 3 : movl(kScratchRegister, src);
326 : movl(dst, kScratchRegister);
327 : } else {
328 : DCHECK_EQ(3, ValueTypes::ElementSizeLog2Of(type));
329 3 : movq(kScratchRegister, src);
330 : movq(dst, kScratchRegister);
331 : }
332 6 : }
333 :
334 537655 : void LiftoffAssembler::Move(Register dst, Register src, ValueType type) {
335 : DCHECK_NE(dst, src);
336 537655 : if (type == kWasmI32) {
337 485430 : movl(dst, src);
338 : } else {
339 : DCHECK_EQ(kWasmI64, type);
340 52225 : movq(dst, src);
341 : }
342 537665 : }
343 :
344 74763 : void LiftoffAssembler::Move(DoubleRegister dst, DoubleRegister src,
345 : ValueType type) {
346 : DCHECK_NE(dst, src);
347 74763 : if (type == kWasmF32) {
348 : Movss(dst, src);
349 : } else {
350 : DCHECK_EQ(kWasmF64, type);
351 : Movsd(dst, src);
352 : }
353 74766 : }
354 :
355 75364 : void LiftoffAssembler::Spill(uint32_t index, LiftoffRegister reg,
356 : ValueType type) {
357 : RecordUsedSpillSlot(index);
358 75377 : Operand dst = liftoff::GetStackSlot(index);
359 75377 : switch (type) {
360 : case kWasmI32:
361 26838 : movl(dst, reg.gp());
362 : break;
363 : case kWasmI64:
364 21580 : movq(dst, reg.gp());
365 : break;
366 : case kWasmF32:
367 : Movss(dst, reg.fp());
368 : break;
369 : case kWasmF64:
370 : Movsd(dst, reg.fp());
371 : break;
372 : default:
373 0 : UNREACHABLE();
374 : }
375 75375 : }
376 :
377 264 : void LiftoffAssembler::Spill(uint32_t index, WasmValue value) {
378 : RecordUsedSpillSlot(index);
379 264 : Operand dst = liftoff::GetStackSlot(index);
380 264 : switch (value.type()) {
381 : case kWasmI32:
382 135 : movl(dst, Immediate(value.to_i32()));
383 135 : break;
384 : case kWasmI64: {
385 129 : if (is_int32(value.to_i64())) {
386 : // Sign extend low word.
387 129 : movq(dst, Immediate(static_cast<int32_t>(value.to_i64())));
388 0 : } else if (is_uint32(value.to_i64())) {
389 : // Zero extend low word.
390 0 : movl(kScratchRegister, Immediate(static_cast<int32_t>(value.to_i64())));
391 : movq(dst, kScratchRegister);
392 : } else {
393 0 : movq(kScratchRegister, value.to_i64());
394 : movq(dst, kScratchRegister);
395 : }
396 : break;
397 : }
398 : default:
399 : // We do not track f32 and f64 constants, hence they are unreachable.
400 0 : UNREACHABLE();
401 : }
402 264 : }
403 :
404 56797 : void LiftoffAssembler::Fill(LiftoffRegister reg, uint32_t index,
405 : ValueType type) {
406 56802 : Operand src = liftoff::GetStackSlot(index);
407 56802 : switch (type) {
408 : case kWasmI32:
409 21168 : movl(reg.gp(), src);
410 : break;
411 : case kWasmI64:
412 19634 : movq(reg.gp(), src);
413 : break;
414 : case kWasmF32:
415 : Movss(reg.fp(), src);
416 : break;
417 : case kWasmF64:
418 : Movsd(reg.fp(), src);
419 : break;
420 : default:
421 0 : UNREACHABLE();
422 : }
423 56804 : }
424 :
425 : void LiftoffAssembler::FillI64Half(Register, uint32_t index, RegPairHalf) {
426 0 : UNREACHABLE();
427 : }
428 :
429 5307 : void LiftoffAssembler::emit_i32_add(Register dst, Register lhs, Register rhs) {
430 5307 : if (lhs != dst) {
431 1384 : leal(dst, Operand(lhs, rhs, times_1, 0));
432 : } else {
433 4615 : addl(dst, rhs);
434 : }
435 5307 : }
436 :
437 112272 : void LiftoffAssembler::emit_i32_add(Register dst, Register lhs, int32_t imm) {
438 112272 : if (lhs != dst) {
439 487 : leal(dst, Operand(lhs, imm));
440 : } else {
441 112029 : addl(dst, Immediate(imm));
442 : }
443 112273 : }
444 :
445 18327 : void LiftoffAssembler::emit_i32_sub(Register dst, Register lhs, Register rhs) {
446 18327 : if (dst != rhs) {
447 : // Default path.
448 14211 : if (dst != lhs) movl(dst, lhs);
449 14211 : subl(dst, rhs);
450 4116 : } else if (lhs == rhs) {
451 : // Degenerate case.
452 3 : xorl(dst, dst);
453 : } else {
454 : // Emit {dst = lhs + -rhs} if dst == rhs.
455 4113 : negl(dst);
456 : addl(dst, lhs);
457 : }
458 18329 : }
459 :
460 : namespace liftoff {
461 : template <void (Assembler::*op)(Register, Register),
462 : void (Assembler::*mov)(Register, Register)>
463 63643 : void EmitCommutativeBinOp(LiftoffAssembler* assm, Register dst, Register lhs,
464 : Register rhs) {
465 63643 : if (dst == rhs) {
466 190 : (assm->*op)(dst, lhs);
467 : } else {
468 63453 : if (dst != lhs) (assm->*mov)(dst, lhs);
469 63453 : (assm->*op)(dst, rhs);
470 : }
471 63643 : }
472 : } // namespace liftoff
473 :
474 : void LiftoffAssembler::emit_i32_mul(Register dst, Register lhs, Register rhs) {
475 : liftoff::EmitCommutativeBinOp<&Assembler::imull, &Assembler::movl>(this, dst,
476 14194 : lhs, rhs);
477 : }
478 :
479 : namespace liftoff {
480 : enum class DivOrRem : uint8_t { kDiv, kRem };
481 : template <typename type, DivOrRem div_or_rem>
482 58782 : void EmitIntDivOrRem(LiftoffAssembler* assm, Register dst, Register lhs,
483 : Register rhs, Label* trap_div_by_zero,
484 : Label* trap_div_unrepresentable) {
485 : constexpr bool needs_unrepresentable_check =
486 : std::is_signed<type>::value && div_or_rem == DivOrRem::kDiv;
487 : constexpr bool special_case_minus_1 =
488 : std::is_signed<type>::value && div_or_rem == DivOrRem::kRem;
489 : DCHECK_EQ(needs_unrepresentable_check, trap_div_unrepresentable != nullptr);
490 :
491 : #define iop(name, ...) \
492 : do { \
493 : if (sizeof(type) == 4) { \
494 : assm->name##l(__VA_ARGS__); \
495 : } else { \
496 : assm->name##q(__VA_ARGS__); \
497 : } \
498 : } while (false)
499 :
500 : // For division, the lhs is always taken from {edx:eax}. Thus, make sure that
501 : // these registers are unused. If {rhs} is stored in one of them, move it to
502 : // another temporary register.
503 : // Do all this before any branch, such that the code is executed
504 : // unconditionally, as the cache state will also be modified unconditionally.
505 58782 : liftoff::SpillRegisters(assm, rdx, rax);
506 58782 : if (rhs == rax || rhs == rdx) {
507 55444 : iop(mov, kScratchRegister, rhs);
508 : rhs = kScratchRegister;
509 : }
510 :
511 : // Check for division by zero.
512 58782 : iop(test, rhs, rhs);
513 58782 : assm->j(zero, trap_div_by_zero);
514 :
515 14663 : Label done;
516 : if (needs_unrepresentable_check) {
517 : // Check for {kMinInt / -1}. This is unrepresentable.
518 14778 : Label do_div;
519 : iop(cmp, rhs, Immediate(-1));
520 14778 : assm->j(not_equal, &do_div);
521 : // {lhs} is min int if {lhs - 1} overflows.
522 : iop(cmp, lhs, Immediate(1));
523 14778 : assm->j(overflow, trap_div_unrepresentable);
524 14778 : assm->bind(&do_div);
525 : } else if (special_case_minus_1) {
526 : // {lhs % -1} is always 0 (needs to be special cased because {kMinInt / -1}
527 : // cannot be computed).
528 14663 : Label do_rem;
529 : iop(cmp, rhs, Immediate(-1));
530 14663 : assm->j(not_equal, &do_rem);
531 : // clang-format off
532 : // (conflicts with presubmit checks because it is confused about "xor")
533 : iop(xor, dst, dst);
534 : // clang-format on
535 14663 : assm->jmp(&done);
536 14663 : assm->bind(&do_rem);
537 : }
538 :
539 : // Now move {lhs} into {eax}, then zero-extend or sign-extend into {edx}, then
540 : // do the division.
541 58782 : if (lhs != rax) iop(mov, rax, lhs);
542 : if (std::is_same<int32_t, type>::value) { // i32
543 28185 : assm->cdq();
544 : assm->idivl(rhs);
545 : } else if (std::is_same<uint32_t, type>::value) { // u32
546 : assm->xorl(rdx, rdx);
547 : assm->divl(rhs);
548 : } else if (std::is_same<int64_t, type>::value) { // i64
549 1256 : assm->cqo();
550 : assm->idivq(rhs);
551 : } else { // u64
552 : assm->xorq(rdx, rdx);
553 : assm->divq(rhs);
554 : }
555 :
556 : // Move back the result (in {eax} or {edx}) into the {dst} register.
557 : constexpr Register kResultReg = div_or_rem == DivOrRem::kDiv ? rax : rdx;
558 58782 : if (dst != kResultReg) {
559 : iop(mov, dst, kResultReg);
560 : }
561 14663 : if (special_case_minus_1) assm->bind(&done);
562 58782 : }
563 : } // namespace liftoff
564 :
565 : void LiftoffAssembler::emit_i32_divs(Register dst, Register lhs, Register rhs,
566 : Label* trap_div_by_zero,
567 : Label* trap_div_unrepresentable) {
568 : liftoff::EmitIntDivOrRem<int32_t, liftoff::DivOrRem::kDiv>(
569 14122 : this, dst, lhs, rhs, trap_div_by_zero, trap_div_unrepresentable);
570 : }
571 :
572 : void LiftoffAssembler::emit_i32_divu(Register dst, Register lhs, Register rhs,
573 : Label* trap_div_by_zero) {
574 : liftoff::EmitIntDivOrRem<uint32_t, liftoff::DivOrRem::kDiv>(
575 14081 : this, dst, lhs, rhs, trap_div_by_zero, nullptr);
576 : }
577 :
578 : void LiftoffAssembler::emit_i32_rems(Register dst, Register lhs, Register rhs,
579 : Label* trap_div_by_zero) {
580 : liftoff::EmitIntDivOrRem<int32_t, liftoff::DivOrRem::kRem>(
581 14063 : this, dst, lhs, rhs, trap_div_by_zero, nullptr);
582 : }
583 :
584 : void LiftoffAssembler::emit_i32_remu(Register dst, Register lhs, Register rhs,
585 : Label* trap_div_by_zero) {
586 : liftoff::EmitIntDivOrRem<uint32_t, liftoff::DivOrRem::kRem>(
587 14054 : this, dst, lhs, rhs, trap_div_by_zero, nullptr);
588 : }
589 :
590 : void LiftoffAssembler::emit_i32_and(Register dst, Register lhs, Register rhs) {
591 : liftoff::EmitCommutativeBinOp<&Assembler::andl, &Assembler::movl>(this, dst,
592 16437 : lhs, rhs);
593 : }
594 :
595 : void LiftoffAssembler::emit_i32_or(Register dst, Register lhs, Register rhs) {
596 : liftoff::EmitCommutativeBinOp<&Assembler::orl, &Assembler::movl>(this, dst,
597 13517 : lhs, rhs);
598 : }
599 :
600 : void LiftoffAssembler::emit_i32_xor(Register dst, Register lhs, Register rhs) {
601 : liftoff::EmitCommutativeBinOp<&Assembler::xorl, &Assembler::movl>(this, dst,
602 13466 : lhs, rhs);
603 : }
604 :
605 : namespace liftoff {
606 : template <ValueType type>
607 46424 : inline void EmitShiftOperation(LiftoffAssembler* assm, Register dst,
608 : Register src, Register amount,
609 : void (Assembler::*emit_shift)(Register),
610 : LiftoffRegList pinned) {
611 : // If dst is rcx, compute into the scratch register first, then move to rcx.
612 46424 : if (dst == rcx) {
613 40622 : assm->Move(kScratchRegister, src, type);
614 40622 : if (amount != rcx) assm->Move(rcx, amount, type);
615 40623 : (assm->*emit_shift)(kScratchRegister);
616 40623 : assm->Move(rcx, kScratchRegister, type);
617 40623 : return;
618 : }
619 :
620 : // Move amount into rcx. If rcx is in use, move its content into the scratch
621 : // register. If src is rcx, src is now the scratch register.
622 : bool use_scratch = false;
623 5802 : if (amount != rcx) {
624 14546 : use_scratch = src == rcx ||
625 : assm->cache_state()->is_used(LiftoffRegister(rcx)) ||
626 : pinned.has(LiftoffRegister(rcx));
627 5654 : if (use_scratch) assm->movq(kScratchRegister, rcx);
628 5656 : if (src == rcx) src = kScratchRegister;
629 5656 : assm->Move(rcx, amount, type);
630 : }
631 :
632 : // Do the actual shift.
633 5809 : if (dst != src) assm->Move(dst, src, type);
634 5809 : (assm->*emit_shift)(dst);
635 :
636 : // Restore rcx if needed.
637 5810 : if (use_scratch) assm->movq(rcx, kScratchRegister);
638 : }
639 : } // namespace liftoff
640 :
641 : void LiftoffAssembler::emit_i32_shl(Register dst, Register src, Register amount,
642 : LiftoffRegList pinned) {
643 : liftoff::EmitShiftOperation<kWasmI32>(this, dst, src, amount,
644 14050 : &Assembler::shll_cl, pinned);
645 : }
646 :
647 : void LiftoffAssembler::emit_i32_sar(Register dst, Register src, Register amount,
648 : LiftoffRegList pinned) {
649 : liftoff::EmitShiftOperation<kWasmI32>(this, dst, src, amount,
650 14041 : &Assembler::sarl_cl, pinned);
651 : }
652 :
653 : void LiftoffAssembler::emit_i32_shr(Register dst, Register src, Register amount,
654 : LiftoffRegList pinned) {
655 : liftoff::EmitShiftOperation<kWasmI32>(this, dst, src, amount,
656 14041 : &Assembler::shrl_cl, pinned);
657 : }
658 :
659 : void LiftoffAssembler::emit_i32_shr(Register dst, Register src, int amount) {
660 : if (dst != src) movl(dst, src);
661 : DCHECK(is_uint5(amount));
662 : shrl(dst, Immediate(amount));
663 : }
664 :
665 594 : bool LiftoffAssembler::emit_i32_clz(Register dst, Register src) {
666 594 : Label nonzero_input;
667 594 : Label continuation;
668 594 : testl(src, src);
669 594 : j(not_zero, &nonzero_input, Label::kNear);
670 : movl(dst, Immediate(32));
671 594 : jmp(&continuation, Label::kNear);
672 :
673 594 : bind(&nonzero_input);
674 : // Get most significant bit set (MSBS).
675 594 : bsrl(dst, src);
676 : // CLZ = 31 - MSBS = MSBS ^ 31.
677 : xorl(dst, Immediate(31));
678 :
679 594 : bind(&continuation);
680 594 : return true;
681 : }
682 :
683 291 : bool LiftoffAssembler::emit_i32_ctz(Register dst, Register src) {
684 291 : Label nonzero_input;
685 291 : Label continuation;
686 291 : testl(src, src);
687 291 : j(not_zero, &nonzero_input, Label::kNear);
688 : movl(dst, Immediate(32));
689 291 : jmp(&continuation, Label::kNear);
690 :
691 291 : bind(&nonzero_input);
692 : // Get least significant bit set, which equals number of trailing zeros.
693 291 : bsfl(dst, src);
694 :
695 291 : bind(&continuation);
696 291 : return true;
697 : }
698 :
699 56 : bool LiftoffAssembler::emit_i32_popcnt(Register dst, Register src) {
700 56 : if (!CpuFeatures::IsSupported(POPCNT)) return false;
701 : CpuFeatureScope scope(this, POPCNT);
702 56 : popcntl(dst, src);
703 : return true;
704 : }
705 :
706 599 : void LiftoffAssembler::emit_i64_add(LiftoffRegister dst, LiftoffRegister lhs,
707 : LiftoffRegister rhs) {
708 599 : if (lhs.gp() != dst.gp()) {
709 990 : leaq(dst.gp(), Operand(lhs.gp(), rhs.gp(), times_1, 0));
710 : } else {
711 104 : addq(dst.gp(), rhs.gp());
712 : }
713 599 : }
714 :
715 21 : void LiftoffAssembler::emit_i64_add(LiftoffRegister dst, LiftoffRegister lhs,
716 : int32_t imm) {
717 21 : if (lhs.gp() != dst.gp()) {
718 24 : leaq(dst.gp(), Operand(lhs.gp(), imm));
719 : } else {
720 9 : addq(dst.gp(), Immediate(imm));
721 : }
722 21 : }
723 :
724 660 : void LiftoffAssembler::emit_i64_sub(LiftoffRegister dst, LiftoffRegister lhs,
725 : LiftoffRegister rhs) {
726 660 : if (dst.gp() == rhs.gp()) {
727 88 : negq(dst.gp());
728 : addq(dst.gp(), lhs.gp());
729 : } else {
730 572 : if (dst.gp() != lhs.gp()) movq(dst.gp(), lhs.gp());
731 572 : subq(dst.gp(), rhs.gp());
732 : }
733 660 : }
734 :
735 : void LiftoffAssembler::emit_i64_mul(LiftoffRegister dst, LiftoffRegister lhs,
736 : LiftoffRegister rhs) {
737 : liftoff::EmitCommutativeBinOp<&Assembler::imulq, &Assembler::movq>(
738 657 : this, dst.gp(), lhs.gp(), rhs.gp());
739 : }
740 :
741 : bool LiftoffAssembler::emit_i64_divs(LiftoffRegister dst, LiftoffRegister lhs,
742 : LiftoffRegister rhs,
743 : Label* trap_div_by_zero,
744 : Label* trap_div_unrepresentable) {
745 : liftoff::EmitIntDivOrRem<int64_t, liftoff::DivOrRem::kDiv>(
746 : this, dst.gp(), lhs.gp(), rhs.gp(), trap_div_by_zero,
747 656 : trap_div_unrepresentable);
748 : return true;
749 : }
750 :
751 : bool LiftoffAssembler::emit_i64_divu(LiftoffRegister dst, LiftoffRegister lhs,
752 : LiftoffRegister rhs,
753 : Label* trap_div_by_zero) {
754 : liftoff::EmitIntDivOrRem<uint64_t, liftoff::DivOrRem::kDiv>(
755 606 : this, dst.gp(), lhs.gp(), rhs.gp(), trap_div_by_zero, nullptr);
756 : return true;
757 : }
758 :
759 : bool LiftoffAssembler::emit_i64_rems(LiftoffRegister dst, LiftoffRegister lhs,
760 : LiftoffRegister rhs,
761 : Label* trap_div_by_zero) {
762 : liftoff::EmitIntDivOrRem<int64_t, liftoff::DivOrRem::kRem>(
763 600 : this, dst.gp(), lhs.gp(), rhs.gp(), trap_div_by_zero, nullptr);
764 : return true;
765 : }
766 :
767 : bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs,
768 : LiftoffRegister rhs,
769 : Label* trap_div_by_zero) {
770 : liftoff::EmitIntDivOrRem<uint64_t, liftoff::DivOrRem::kRem>(
771 600 : this, dst.gp(), lhs.gp(), rhs.gp(), trap_div_by_zero, nullptr);
772 : return true;
773 : }
774 :
775 : void LiftoffAssembler::emit_i64_and(LiftoffRegister dst, LiftoffRegister lhs,
776 : LiftoffRegister rhs) {
777 : liftoff::EmitCommutativeBinOp<&Assembler::andq, &Assembler::movq>(
778 2891 : this, dst.gp(), lhs.gp(), rhs.gp());
779 : }
780 :
781 : void LiftoffAssembler::emit_i64_or(LiftoffRegister dst, LiftoffRegister lhs,
782 : LiftoffRegister rhs) {
783 : liftoff::EmitCommutativeBinOp<&Assembler::orq, &Assembler::movq>(
784 2444 : this, dst.gp(), lhs.gp(), rhs.gp());
785 : }
786 :
787 : void LiftoffAssembler::emit_i64_xor(LiftoffRegister dst, LiftoffRegister lhs,
788 : LiftoffRegister rhs) {
789 : liftoff::EmitCommutativeBinOp<&Assembler::xorq, &Assembler::movq>(
790 42 : this, dst.gp(), lhs.gp(), rhs.gp());
791 : }
792 :
793 : void LiftoffAssembler::emit_i64_shl(LiftoffRegister dst, LiftoffRegister src,
794 : Register amount, LiftoffRegList pinned) {
795 : liftoff::EmitShiftOperation<kWasmI64>(this, dst.gp(), src.gp(), amount,
796 6070 : &Assembler::shlq_cl, pinned);
797 : }
798 :
799 : void LiftoffAssembler::emit_i64_sar(LiftoffRegister dst, LiftoffRegister src,
800 : Register amount, LiftoffRegList pinned) {
801 : liftoff::EmitShiftOperation<kWasmI64>(this, dst.gp(), src.gp(), amount,
802 1214 : &Assembler::sarq_cl, pinned);
803 : }
804 :
805 : void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src,
806 : Register amount, LiftoffRegList pinned) {
807 : liftoff::EmitShiftOperation<kWasmI64>(this, dst.gp(), src.gp(), amount,
808 1310 : &Assembler::shrq_cl, pinned);
809 : }
810 :
811 192 : void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src,
812 : int amount) {
813 192 : if (dst.gp() != src.gp()) movl(dst.gp(), src.gp());
814 : DCHECK(is_uint6(amount));
815 192 : shrq(dst.gp(), Immediate(amount));
816 192 : }
817 :
818 : void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) {
819 1 : movsxlq(dst, src);
820 : }
821 :
822 178 : void LiftoffAssembler::emit_f32_add(DoubleRegister dst, DoubleRegister lhs,
823 : DoubleRegister rhs) {
824 178 : if (CpuFeatures::IsSupported(AVX)) {
825 : CpuFeatureScope scope(this, AVX);
826 178 : vaddss(dst, lhs, rhs);
827 0 : } else if (dst == rhs) {
828 0 : addss(dst, lhs);
829 : } else {
830 0 : if (dst != lhs) movss(dst, lhs);
831 0 : addss(dst, rhs);
832 : }
833 178 : }
834 :
835 145 : void LiftoffAssembler::emit_f32_sub(DoubleRegister dst, DoubleRegister lhs,
836 : DoubleRegister rhs) {
837 145 : if (CpuFeatures::IsSupported(AVX)) {
838 : CpuFeatureScope scope(this, AVX);
839 145 : vsubss(dst, lhs, rhs);
840 0 : } else if (dst == rhs) {
841 0 : movss(kScratchDoubleReg, rhs);
842 0 : movss(dst, lhs);
843 0 : subss(dst, kScratchDoubleReg);
844 : } else {
845 0 : if (dst != lhs) movss(dst, lhs);
846 0 : subss(dst, rhs);
847 : }
848 145 : }
849 :
850 173 : void LiftoffAssembler::emit_f32_mul(DoubleRegister dst, DoubleRegister lhs,
851 : DoubleRegister rhs) {
852 173 : if (CpuFeatures::IsSupported(AVX)) {
853 : CpuFeatureScope scope(this, AVX);
854 173 : vmulss(dst, lhs, rhs);
855 0 : } else if (dst == rhs) {
856 0 : mulss(dst, lhs);
857 : } else {
858 0 : if (dst != lhs) movss(dst, lhs);
859 0 : mulss(dst, rhs);
860 : }
861 173 : }
862 :
863 150 : void LiftoffAssembler::emit_f32_div(DoubleRegister dst, DoubleRegister lhs,
864 : DoubleRegister rhs) {
865 150 : if (CpuFeatures::IsSupported(AVX)) {
866 : CpuFeatureScope scope(this, AVX);
867 150 : vdivss(dst, lhs, rhs);
868 0 : } else if (dst == rhs) {
869 0 : movss(kScratchDoubleReg, rhs);
870 0 : movss(dst, lhs);
871 0 : divss(dst, kScratchDoubleReg);
872 : } else {
873 0 : if (dst != lhs) movss(dst, lhs);
874 0 : divss(dst, rhs);
875 : }
876 150 : }
877 :
878 : namespace liftoff {
879 : enum class MinOrMax : uint8_t { kMin, kMax };
880 : template <typename type>
881 71 : inline void EmitFloatMinOrMax(LiftoffAssembler* assm, DoubleRegister dst,
882 : DoubleRegister lhs, DoubleRegister rhs,
883 : MinOrMax min_or_max) {
884 71 : Label is_nan;
885 71 : Label lhs_below_rhs;
886 71 : Label lhs_above_rhs;
887 71 : Label done;
888 :
889 : #define dop(name, ...) \
890 : do { \
891 : if (sizeof(type) == 4) { \
892 : assm->name##s(__VA_ARGS__); \
893 : } else { \
894 : assm->name##d(__VA_ARGS__); \
895 : } \
896 : } while (false)
897 :
898 : // Check the easy cases first: nan (e.g. unordered), smaller and greater.
899 : // NaN has to be checked first, because PF=1 implies CF=1.
900 : dop(Ucomis, lhs, rhs);
901 71 : assm->j(parity_even, &is_nan, Label::kNear); // PF=1
902 71 : assm->j(below, &lhs_below_rhs, Label::kNear); // CF=1
903 71 : assm->j(above, &lhs_above_rhs, Label::kNear); // CF=0 && ZF=0
904 :
905 : // If we get here, then either
906 : // a) {lhs == rhs},
907 : // b) {lhs == -0.0} and {rhs == 0.0}, or
908 : // c) {lhs == 0.0} and {rhs == -0.0}.
909 : // For a), it does not matter whether we return {lhs} or {rhs}. Check the sign
910 : // bit of {rhs} to differentiate b) and c).
911 : dop(Movmskp, kScratchRegister, rhs);
912 : assm->testl(kScratchRegister, Immediate(1));
913 71 : assm->j(zero, &lhs_below_rhs, Label::kNear);
914 71 : assm->jmp(&lhs_above_rhs, Label::kNear);
915 :
916 71 : assm->bind(&is_nan);
917 : // Create a NaN output.
918 : dop(Xorp, dst, dst);
919 : dop(Divs, dst, dst);
920 71 : assm->jmp(&done, Label::kNear);
921 :
922 71 : assm->bind(&lhs_below_rhs);
923 71 : DoubleRegister lhs_below_rhs_src = min_or_max == MinOrMax::kMin ? lhs : rhs;
924 71 : if (dst != lhs_below_rhs_src) dop(Movs, dst, lhs_below_rhs_src);
925 71 : assm->jmp(&done, Label::kNear);
926 :
927 71 : assm->bind(&lhs_above_rhs);
928 71 : DoubleRegister lhs_above_rhs_src = min_or_max == MinOrMax::kMin ? rhs : lhs;
929 71 : if (dst != lhs_above_rhs_src) dop(Movs, dst, lhs_above_rhs_src);
930 :
931 71 : assm->bind(&done);
932 71 : }
933 : } // namespace liftoff
934 :
935 : void LiftoffAssembler::emit_f32_min(DoubleRegister dst, DoubleRegister lhs,
936 : DoubleRegister rhs) {
937 : liftoff::EmitFloatMinOrMax<float>(this, dst, lhs, rhs,
938 17 : liftoff::MinOrMax::kMin);
939 : }
940 :
941 : void LiftoffAssembler::emit_f32_max(DoubleRegister dst, DoubleRegister lhs,
942 : DoubleRegister rhs) {
943 : liftoff::EmitFloatMinOrMax<float>(this, dst, lhs, rhs,
944 17 : liftoff::MinOrMax::kMax);
945 : }
946 :
947 13 : void LiftoffAssembler::emit_f32_copysign(DoubleRegister dst, DoubleRegister lhs,
948 : DoubleRegister rhs) {
949 : static constexpr int kF32SignBit = 1 << 31;
950 : Movd(kScratchRegister, lhs);
951 13 : andl(kScratchRegister, Immediate(~kF32SignBit));
952 : Movd(liftoff::kScratchRegister2, rhs);
953 : andl(liftoff::kScratchRegister2, Immediate(kF32SignBit));
954 : orl(kScratchRegister, liftoff::kScratchRegister2);
955 : Movd(dst, kScratchRegister);
956 13 : }
957 :
958 22 : void LiftoffAssembler::emit_f32_abs(DoubleRegister dst, DoubleRegister src) {
959 : static constexpr uint32_t kSignBit = uint32_t{1} << 31;
960 22 : if (dst == src) {
961 8 : TurboAssembler::Move(kScratchDoubleReg, kSignBit - 1);
962 : Andps(dst, kScratchDoubleReg);
963 : } else {
964 14 : TurboAssembler::Move(dst, kSignBit - 1);
965 : Andps(dst, src);
966 : }
967 22 : }
968 :
969 93 : void LiftoffAssembler::emit_f32_neg(DoubleRegister dst, DoubleRegister src) {
970 : static constexpr uint32_t kSignBit = uint32_t{1} << 31;
971 93 : if (dst == src) {
972 19 : TurboAssembler::Move(kScratchDoubleReg, kSignBit);
973 : Xorps(dst, kScratchDoubleReg);
974 : } else {
975 74 : TurboAssembler::Move(dst, kSignBit);
976 : Xorps(dst, src);
977 : }
978 93 : }
979 :
980 10 : bool LiftoffAssembler::emit_f32_ceil(DoubleRegister dst, DoubleRegister src) {
981 10 : if (CpuFeatures::IsSupported(SSE4_1)) {
982 : CpuFeatureScope feature(this, SSE4_1);
983 : Roundss(dst, src, kRoundUp);
984 : return true;
985 : }
986 : return false;
987 : }
988 :
989 10 : bool LiftoffAssembler::emit_f32_floor(DoubleRegister dst, DoubleRegister src) {
990 10 : if (CpuFeatures::IsSupported(SSE4_1)) {
991 : CpuFeatureScope feature(this, SSE4_1);
992 : Roundss(dst, src, kRoundDown);
993 : return true;
994 : }
995 : return false;
996 : }
997 :
998 10 : bool LiftoffAssembler::emit_f32_trunc(DoubleRegister dst, DoubleRegister src) {
999 10 : if (CpuFeatures::IsSupported(SSE4_1)) {
1000 : CpuFeatureScope feature(this, SSE4_1);
1001 : Roundss(dst, src, kRoundToZero);
1002 : return true;
1003 : }
1004 : return false;
1005 : }
1006 :
1007 10 : bool LiftoffAssembler::emit_f32_nearest_int(DoubleRegister dst,
1008 : DoubleRegister src) {
1009 10 : if (CpuFeatures::IsSupported(SSE4_1)) {
1010 : CpuFeatureScope feature(this, SSE4_1);
1011 : Roundss(dst, src, kRoundToNearest);
1012 : return true;
1013 : }
1014 : return false;
1015 : }
1016 :
1017 : void LiftoffAssembler::emit_f32_sqrt(DoubleRegister dst, DoubleRegister src) {
1018 : Sqrtss(dst, src);
1019 : }
1020 :
1021 322 : void LiftoffAssembler::emit_f64_add(DoubleRegister dst, DoubleRegister lhs,
1022 : DoubleRegister rhs) {
1023 322 : if (CpuFeatures::IsSupported(AVX)) {
1024 : CpuFeatureScope scope(this, AVX);
1025 322 : vaddsd(dst, lhs, rhs);
1026 0 : } else if (dst == rhs) {
1027 0 : addsd(dst, lhs);
1028 : } else {
1029 0 : if (dst != lhs) movsd(dst, lhs);
1030 0 : addsd(dst, rhs);
1031 : }
1032 322 : }
1033 :
1034 144 : void LiftoffAssembler::emit_f64_sub(DoubleRegister dst, DoubleRegister lhs,
1035 : DoubleRegister rhs) {
1036 144 : if (CpuFeatures::IsSupported(AVX)) {
1037 : CpuFeatureScope scope(this, AVX);
1038 144 : vsubsd(dst, lhs, rhs);
1039 0 : } else if (dst == rhs) {
1040 0 : movsd(kScratchDoubleReg, rhs);
1041 0 : movsd(dst, lhs);
1042 0 : subsd(dst, kScratchDoubleReg);
1043 : } else {
1044 0 : if (dst != lhs) movsd(dst, lhs);
1045 0 : subsd(dst, rhs);
1046 : }
1047 144 : }
1048 :
1049 219 : void LiftoffAssembler::emit_f64_mul(DoubleRegister dst, DoubleRegister lhs,
1050 : DoubleRegister rhs) {
1051 219 : if (CpuFeatures::IsSupported(AVX)) {
1052 : CpuFeatureScope scope(this, AVX);
1053 219 : vmulsd(dst, lhs, rhs);
1054 0 : } else if (dst == rhs) {
1055 0 : mulsd(dst, lhs);
1056 : } else {
1057 0 : if (dst != lhs) movsd(dst, lhs);
1058 0 : mulsd(dst, rhs);
1059 : }
1060 219 : }
1061 :
1062 174 : void LiftoffAssembler::emit_f64_div(DoubleRegister dst, DoubleRegister lhs,
1063 : DoubleRegister rhs) {
1064 174 : if (CpuFeatures::IsSupported(AVX)) {
1065 : CpuFeatureScope scope(this, AVX);
1066 174 : vdivsd(dst, lhs, rhs);
1067 0 : } else if (dst == rhs) {
1068 0 : movsd(kScratchDoubleReg, rhs);
1069 0 : movsd(dst, lhs);
1070 0 : divsd(dst, kScratchDoubleReg);
1071 : } else {
1072 0 : if (dst != lhs) movsd(dst, lhs);
1073 0 : divsd(dst, rhs);
1074 : }
1075 174 : }
1076 :
1077 : void LiftoffAssembler::emit_f64_min(DoubleRegister dst, DoubleRegister lhs,
1078 : DoubleRegister rhs) {
1079 : liftoff::EmitFloatMinOrMax<double>(this, dst, lhs, rhs,
1080 20 : liftoff::MinOrMax::kMin);
1081 : }
1082 :
1083 13 : void LiftoffAssembler::emit_f64_copysign(DoubleRegister dst, DoubleRegister lhs,
1084 : DoubleRegister rhs) {
1085 : // Extract sign bit from {rhs} into {kScratchRegister2}.
1086 : Movq(liftoff::kScratchRegister2, rhs);
1087 13 : shrq(liftoff::kScratchRegister2, Immediate(63));
1088 : shlq(liftoff::kScratchRegister2, Immediate(63));
1089 : // Reset sign bit of {lhs} (in {kScratchRegister}).
1090 : Movq(kScratchRegister, lhs);
1091 13 : btrq(kScratchRegister, Immediate(63));
1092 : // Combine both values into {kScratchRegister} and move into {dst}.
1093 : orq(kScratchRegister, liftoff::kScratchRegister2);
1094 : Movq(dst, kScratchRegister);
1095 13 : }
1096 :
1097 : void LiftoffAssembler::emit_f64_max(DoubleRegister dst, DoubleRegister lhs,
1098 : DoubleRegister rhs) {
1099 : liftoff::EmitFloatMinOrMax<double>(this, dst, lhs, rhs,
1100 17 : liftoff::MinOrMax::kMax);
1101 : }
1102 :
1103 22 : void LiftoffAssembler::emit_f64_abs(DoubleRegister dst, DoubleRegister src) {
1104 : static constexpr uint64_t kSignBit = uint64_t{1} << 63;
1105 22 : if (dst == src) {
1106 8 : TurboAssembler::Move(kScratchDoubleReg, kSignBit - 1);
1107 : Andpd(dst, kScratchDoubleReg);
1108 : } else {
1109 14 : TurboAssembler::Move(dst, kSignBit - 1);
1110 : Andpd(dst, src);
1111 : }
1112 22 : }
1113 :
1114 93 : void LiftoffAssembler::emit_f64_neg(DoubleRegister dst, DoubleRegister src) {
1115 : static constexpr uint64_t kSignBit = uint64_t{1} << 63;
1116 93 : if (dst == src) {
1117 22 : TurboAssembler::Move(kScratchDoubleReg, kSignBit);
1118 : Xorpd(dst, kScratchDoubleReg);
1119 : } else {
1120 71 : TurboAssembler::Move(dst, kSignBit);
1121 : Xorpd(dst, src);
1122 : }
1123 93 : }
1124 :
1125 10 : bool LiftoffAssembler::emit_f64_ceil(DoubleRegister dst, DoubleRegister src) {
1126 10 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1127 : Roundsd(dst, src, kRoundUp);
1128 : return true;
1129 : }
1130 :
1131 10 : bool LiftoffAssembler::emit_f64_floor(DoubleRegister dst, DoubleRegister src) {
1132 10 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1133 : Roundsd(dst, src, kRoundDown);
1134 : return true;
1135 : }
1136 :
1137 10 : bool LiftoffAssembler::emit_f64_trunc(DoubleRegister dst, DoubleRegister src) {
1138 10 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1139 : Roundsd(dst, src, kRoundToZero);
1140 : return true;
1141 : }
1142 :
1143 10 : bool LiftoffAssembler::emit_f64_nearest_int(DoubleRegister dst,
1144 : DoubleRegister src) {
1145 10 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1146 : Roundsd(dst, src, kRoundToNearest);
1147 : return true;
1148 : }
1149 :
1150 : void LiftoffAssembler::emit_f64_sqrt(DoubleRegister dst, DoubleRegister src) {
1151 : Sqrtsd(dst, src);
1152 : }
1153 :
1154 : namespace liftoff {
1155 : // Used for float to int conversions. If the value in {converted_back} equals
1156 : // {src} afterwards, the conversion succeeded.
1157 : template <typename dst_type, typename src_type>
1158 35 : inline void ConvertFloatToIntAndBack(LiftoffAssembler* assm, Register dst,
1159 : DoubleRegister src,
1160 : DoubleRegister converted_back) {
1161 : if (std::is_same<double, src_type>::value) { // f64
1162 : if (std::is_same<int32_t, dst_type>::value) { // f64 -> i32
1163 87 : assm->Cvttsd2si(dst, src);
1164 87 : assm->Cvtlsi2sd(converted_back, dst);
1165 : } else if (std::is_same<uint32_t, dst_type>::value) { // f64 -> u32
1166 16 : assm->Cvttsd2siq(dst, src);
1167 16 : assm->movl(dst, dst);
1168 16 : assm->Cvtqsi2sd(converted_back, dst);
1169 : } else if (std::is_same<int64_t, dst_type>::value) { // f64 -> i64
1170 301 : assm->Cvttsd2siq(dst, src);
1171 301 : assm->Cvtqsi2sd(converted_back, dst);
1172 : } else {
1173 : UNREACHABLE();
1174 : }
1175 : } else { // f32
1176 : if (std::is_same<int32_t, dst_type>::value) { // f32 -> i32
1177 87 : assm->Cvttss2si(dst, src);
1178 87 : assm->Cvtlsi2ss(converted_back, dst);
1179 : } else if (std::is_same<uint32_t, dst_type>::value) { // f32 -> u32
1180 19 : assm->Cvttss2siq(dst, src);
1181 19 : assm->movl(dst, dst);
1182 19 : assm->Cvtqsi2ss(converted_back, dst);
1183 : } else if (std::is_same<int64_t, dst_type>::value) { // f32 -> i64
1184 16 : assm->Cvttss2siq(dst, src);
1185 16 : assm->Cvtqsi2ss(converted_back, dst);
1186 : } else {
1187 : UNREACHABLE();
1188 : }
1189 : }
1190 35 : }
1191 :
1192 : template <typename dst_type, typename src_type>
1193 526 : inline bool EmitTruncateFloatToInt(LiftoffAssembler* assm, Register dst,
1194 : DoubleRegister src, Label* trap) {
1195 526 : if (!CpuFeatures::IsSupported(SSE4_1)) {
1196 : assm->bailout("no SSE4.1");
1197 : return true;
1198 : }
1199 : CpuFeatureScope feature(assm, SSE4_1);
1200 :
1201 : DoubleRegister rounded = kScratchDoubleReg;
1202 : DoubleRegister converted_back = kScratchDoubleReg2;
1203 :
1204 : if (std::is_same<double, src_type>::value) { // f64
1205 : assm->Roundsd(rounded, src, kRoundToZero);
1206 : } else { // f32
1207 : assm->Roundss(rounded, src, kRoundToZero);
1208 : }
1209 35 : ConvertFloatToIntAndBack<dst_type, src_type>(assm, dst, rounded,
1210 : converted_back);
1211 : if (std::is_same<double, src_type>::value) { // f64
1212 : assm->Ucomisd(converted_back, rounded);
1213 : } else { // f32
1214 : assm->Ucomiss(converted_back, rounded);
1215 : }
1216 :
1217 : // Jump to trap if PF is 0 (one of the operands was NaN) or they are not
1218 : // equal.
1219 526 : assm->j(parity_even, trap);
1220 526 : assm->j(not_equal, trap);
1221 : return true;
1222 : }
1223 : } // namespace liftoff
1224 :
1225 44538 : bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode,
1226 : LiftoffRegister dst,
1227 : LiftoffRegister src, Label* trap) {
1228 44538 : switch (opcode) {
1229 : case kExprI32ConvertI64:
1230 587 : movl(dst.gp(), src.gp());
1231 587 : return true;
1232 : case kExprI32SConvertF32:
1233 : return liftoff::EmitTruncateFloatToInt<int32_t, float>(this, dst.gp(),
1234 87 : src.fp(), trap);
1235 : case kExprI32UConvertF32:
1236 : return liftoff::EmitTruncateFloatToInt<uint32_t, float>(this, dst.gp(),
1237 19 : src.fp(), trap);
1238 : case kExprI32SConvertF64:
1239 : return liftoff::EmitTruncateFloatToInt<int32_t, double>(this, dst.gp(),
1240 87 : src.fp(), trap);
1241 : case kExprI32UConvertF64:
1242 : return liftoff::EmitTruncateFloatToInt<uint32_t, double>(this, dst.gp(),
1243 16 : src.fp(), trap);
1244 : case kExprI32ReinterpretF32:
1245 : Movd(dst.gp(), src.fp());
1246 19100 : return true;
1247 : case kExprI64SConvertI32:
1248 32 : movsxlq(dst.gp(), src.gp());
1249 32 : return true;
1250 : case kExprI64SConvertF32:
1251 : return liftoff::EmitTruncateFloatToInt<int64_t, float>(this, dst.gp(),
1252 16 : src.fp(), trap);
1253 : case kExprI64UConvertF32: {
1254 16 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1255 16 : Cvttss2uiq(dst.gp(), src.fp(), trap);
1256 : return true;
1257 : }
1258 : case kExprI64SConvertF64:
1259 : return liftoff::EmitTruncateFloatToInt<int64_t, double>(this, dst.gp(),
1260 301 : src.fp(), trap);
1261 : case kExprI64UConvertF64: {
1262 19 : REQUIRE_CPU_FEATURE(SSE4_1, true);
1263 19 : Cvttsd2uiq(dst.gp(), src.fp(), trap);
1264 : return true;
1265 : }
1266 : case kExprI64UConvertI32:
1267 4868 : AssertZeroExtended(src.gp());
1268 4867 : if (dst.gp() != src.gp()) movl(dst.gp(), src.gp());
1269 : return true;
1270 : case kExprI64ReinterpretF64:
1271 : Movq(dst.gp(), src.fp());
1272 18983 : return true;
1273 : case kExprF32SConvertI32:
1274 32 : Cvtlsi2ss(dst.fp(), src.gp());
1275 32 : return true;
1276 : case kExprF32UConvertI32:
1277 12 : movl(kScratchRegister, src.gp());
1278 12 : Cvtqsi2ss(dst.fp(), kScratchRegister);
1279 12 : return true;
1280 : case kExprF32SConvertI64:
1281 19 : Cvtqsi2ss(dst.fp(), src.gp());
1282 19 : return true;
1283 : case kExprF32UConvertI64:
1284 13 : Cvtqui2ss(dst.fp(), src.gp());
1285 13 : return true;
1286 : case kExprF32ConvertF64:
1287 30 : Cvtsd2ss(dst.fp(), src.fp());
1288 30 : return true;
1289 : case kExprF32ReinterpretI32:
1290 : Movd(dst.fp(), src.gp());
1291 52 : return true;
1292 : case kExprF64SConvertI32:
1293 25 : Cvtlsi2sd(dst.fp(), src.gp());
1294 25 : return true;
1295 : case kExprF64UConvertI32:
1296 27 : movl(kScratchRegister, src.gp());
1297 27 : Cvtqsi2sd(dst.fp(), kScratchRegister);
1298 27 : return true;
1299 : case kExprF64SConvertI64:
1300 26 : Cvtqsi2sd(dst.fp(), src.gp());
1301 26 : return true;
1302 : case kExprF64UConvertI64:
1303 51 : Cvtqui2sd(dst.fp(), src.gp());
1304 52 : return true;
1305 : case kExprF64ConvertF32:
1306 54 : Cvtss2sd(dst.fp(), src.fp());
1307 54 : return true;
1308 : case kExprF64ReinterpretI64:
1309 : Movq(dst.fp(), src.gp());
1310 67 : return true;
1311 : default:
1312 0 : UNREACHABLE();
1313 : }
1314 : }
1315 :
1316 : void LiftoffAssembler::emit_i32_signextend_i8(Register dst, Register src) {
1317 7 : movsxbl(dst, src);
1318 : }
1319 :
1320 : void LiftoffAssembler::emit_i32_signextend_i16(Register dst, Register src) {
1321 4 : movsxwl(dst, src);
1322 : }
1323 :
1324 : void LiftoffAssembler::emit_i64_signextend_i8(LiftoffRegister dst,
1325 : LiftoffRegister src) {
1326 7 : movsxbq(dst.gp(), src.gp());
1327 : }
1328 :
1329 : void LiftoffAssembler::emit_i64_signextend_i16(LiftoffRegister dst,
1330 : LiftoffRegister src) {
1331 4 : movsxwq(dst.gp(), src.gp());
1332 : }
1333 :
1334 : void LiftoffAssembler::emit_i64_signextend_i32(LiftoffRegister dst,
1335 : LiftoffRegister src) {
1336 4 : movsxlq(dst.gp(), src.gp());
1337 : }
1338 :
1339 124205 : void LiftoffAssembler::emit_jump(Label* label) { jmp(label); }
1340 :
1341 : void LiftoffAssembler::emit_jump(Register target) { jmp(target); }
1342 :
1343 131206 : void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label,
1344 : ValueType type, Register lhs,
1345 : Register rhs) {
1346 131206 : if (rhs != no_reg) {
1347 89710 : switch (type) {
1348 : case kWasmI32:
1349 88278 : cmpl(lhs, rhs);
1350 : break;
1351 : case kWasmI64:
1352 1432 : cmpq(lhs, rhs);
1353 : break;
1354 : default:
1355 0 : UNREACHABLE();
1356 : }
1357 : } else {
1358 : DCHECK_EQ(type, kWasmI32);
1359 41496 : testl(lhs, lhs);
1360 : }
1361 :
1362 131203 : j(cond, label);
1363 131200 : }
1364 :
1365 39371 : void LiftoffAssembler::emit_i32_eqz(Register dst, Register src) {
1366 39371 : testl(src, src);
1367 39371 : setcc(equal, dst);
1368 : movzxbl(dst, dst);
1369 39372 : }
1370 :
1371 161314 : void LiftoffAssembler::emit_i32_set_cond(Condition cond, Register dst,
1372 : Register lhs, Register rhs) {
1373 161314 : cmpl(lhs, rhs);
1374 161314 : setcc(cond, dst);
1375 : movzxbl(dst, dst);
1376 161314 : }
1377 :
1378 69 : void LiftoffAssembler::emit_i64_eqz(Register dst, LiftoffRegister src) {
1379 69 : testq(src.gp(), src.gp());
1380 69 : setcc(equal, dst);
1381 : movzxbl(dst, dst);
1382 69 : }
1383 :
1384 13219 : void LiftoffAssembler::emit_i64_set_cond(Condition cond, Register dst,
1385 : LiftoffRegister lhs,
1386 : LiftoffRegister rhs) {
1387 13219 : cmpq(lhs.gp(), rhs.gp());
1388 13219 : setcc(cond, dst);
1389 : movzxbl(dst, dst);
1390 13218 : }
1391 :
1392 : namespace liftoff {
1393 : template <void (TurboAssembler::*cmp_op)(DoubleRegister, DoubleRegister)>
1394 495 : void EmitFloatSetCond(LiftoffAssembler* assm, Condition cond, Register dst,
1395 : DoubleRegister lhs, DoubleRegister rhs) {
1396 495 : Label cont;
1397 495 : Label not_nan;
1398 :
1399 : (assm->*cmp_op)(lhs, rhs);
1400 : // If PF is one, one of the operands was NaN. This needs special handling.
1401 495 : assm->j(parity_odd, ¬_nan, Label::kNear);
1402 : // Return 1 for f32.ne, 0 for all other cases.
1403 495 : if (cond == not_equal) {
1404 : assm->movl(dst, Immediate(1));
1405 : } else {
1406 : assm->xorl(dst, dst);
1407 : }
1408 495 : assm->jmp(&cont, Label::kNear);
1409 495 : assm->bind(¬_nan);
1410 :
1411 495 : assm->setcc(cond, dst);
1412 : assm->movzxbl(dst, dst);
1413 496 : assm->bind(&cont);
1414 497 : }
1415 : } // namespace liftoff
1416 :
1417 : void LiftoffAssembler::emit_f32_set_cond(Condition cond, Register dst,
1418 : DoubleRegister lhs,
1419 : DoubleRegister rhs) {
1420 : liftoff::EmitFloatSetCond<&TurboAssembler::Ucomiss>(this, cond, dst, lhs,
1421 264 : rhs);
1422 : }
1423 :
1424 : void LiftoffAssembler::emit_f64_set_cond(Condition cond, Register dst,
1425 : DoubleRegister lhs,
1426 : DoubleRegister rhs) {
1427 : liftoff::EmitFloatSetCond<&TurboAssembler::Ucomisd>(this, cond, dst, lhs,
1428 234 : rhs);
1429 : }
1430 :
1431 94797 : void LiftoffAssembler::StackCheck(Label* ool_code, Register limit_address) {
1432 94797 : cmpq(rsp, Operand(limit_address, 0));
1433 95233 : j(below_equal, ool_code);
1434 94719 : }
1435 :
1436 217656 : void LiftoffAssembler::CallTrapCallbackForTesting() {
1437 217656 : PrepareCallCFunction(0);
1438 217656 : CallCFunction(ExternalReference::wasm_call_trap_callback_for_testing(), 0);
1439 217656 : }
1440 :
1441 : void LiftoffAssembler::AssertUnreachable(AbortReason reason) {
1442 92133 : TurboAssembler::AssertUnreachable(reason);
1443 : }
1444 :
1445 15242 : void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {
1446 : LiftoffRegList gp_regs = regs & kGpCacheRegList;
1447 49868 : while (!gp_regs.is_empty()) {
1448 : LiftoffRegister reg = gp_regs.GetFirstRegSet();
1449 17310 : pushq(reg.gp());
1450 : gp_regs.clear(reg);
1451 : }
1452 : LiftoffRegList fp_regs = regs & kFpCacheRegList;
1453 : unsigned num_fp_regs = fp_regs.GetNumRegsSet();
1454 15245 : if (num_fp_regs) {
1455 2306 : subq(rsp, Immediate(num_fp_regs * kStackSlotSize));
1456 : unsigned offset = 0;
1457 14981 : while (!fp_regs.is_empty()) {
1458 : LiftoffRegister reg = fp_regs.GetFirstRegSet();
1459 12675 : Movsd(Operand(rsp, offset), reg.fp());
1460 : fp_regs.clear(reg);
1461 6339 : offset += sizeof(double);
1462 : }
1463 : DCHECK_EQ(offset, num_fp_regs * sizeof(double));
1464 : }
1465 15245 : }
1466 :
1467 15282 : void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {
1468 : LiftoffRegList fp_regs = regs & kFpCacheRegList;
1469 : unsigned fp_offset = 0;
1470 27938 : while (!fp_regs.is_empty()) {
1471 : LiftoffRegister reg = fp_regs.GetFirstRegSet();
1472 12660 : Movsd(reg.fp(), Operand(rsp, fp_offset));
1473 : fp_regs.clear(reg);
1474 6328 : fp_offset += sizeof(double);
1475 : }
1476 15278 : if (fp_offset) addq(rsp, Immediate(fp_offset));
1477 : LiftoffRegList gp_regs = regs & kGpCacheRegList;
1478 49997 : while (!gp_regs.is_empty()) {
1479 : LiftoffRegister reg = gp_regs.GetLastRegSet();
1480 17349 : popq(reg.gp());
1481 : gp_regs.clear(reg);
1482 : }
1483 15292 : }
1484 :
1485 : void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {
1486 : DCHECK_LT(num_stack_slots,
1487 : (1 << 16) / kSystemPointerSize); // 16 bit immediate
1488 665482 : ret(static_cast<int>(num_stack_slots * kSystemPointerSize));
1489 : }
1490 :
1491 26932 : void LiftoffAssembler::CallC(wasm::FunctionSig* sig,
1492 : const LiftoffRegister* args,
1493 : const LiftoffRegister* rets,
1494 : ValueType out_argument_type, int stack_bytes,
1495 : ExternalReference ext_ref) {
1496 26932 : subq(rsp, Immediate(stack_bytes));
1497 :
1498 : int arg_bytes = 0;
1499 134660 : for (ValueType param_type : sig->parameters()) {
1500 53864 : liftoff::Store(this, Operand(rsp, arg_bytes), *args++, param_type);
1501 53864 : arg_bytes += ValueTypes::MemSize(param_type);
1502 : }
1503 : DCHECK_LE(arg_bytes, stack_bytes);
1504 :
1505 : // Pass a pointer to the buffer with the arguments to the C function.
1506 : movq(arg_reg_1, rsp);
1507 :
1508 : constexpr int kNumCCallArgs = 1;
1509 :
1510 : // Now call the C function.
1511 26932 : PrepareCallCFunction(kNumCCallArgs);
1512 26932 : CallCFunction(ext_ref, kNumCCallArgs);
1513 :
1514 : // Move return value to the right register.
1515 : const LiftoffRegister* next_result_reg = rets;
1516 26932 : if (sig->return_count() > 0) {
1517 : DCHECK_EQ(1, sig->return_count());
1518 : constexpr Register kReturnReg = rax;
1519 26932 : if (kReturnReg != next_result_reg->gp()) {
1520 53858 : Move(*next_result_reg, LiftoffRegister(kReturnReg), sig->GetReturn(0));
1521 : }
1522 26932 : ++next_result_reg;
1523 : }
1524 :
1525 : // Load potential output value from the buffer on the stack.
1526 26932 : if (out_argument_type != kWasmStmt) {
1527 0 : liftoff::Load(this, *next_result_reg, Operand(rsp, 0), out_argument_type);
1528 : }
1529 :
1530 : addq(rsp, Immediate(stack_bytes));
1531 26932 : }
1532 :
1533 : void LiftoffAssembler::CallNativeWasmCode(Address addr) {
1534 3590 : near_call(addr, RelocInfo::WASM_CALL);
1535 : }
1536 :
1537 44153 : void LiftoffAssembler::CallIndirect(wasm::FunctionSig* sig,
1538 : compiler::CallDescriptor* call_descriptor,
1539 : Register target) {
1540 44153 : if (target == no_reg) {
1541 0 : popq(kScratchRegister);
1542 : target = kScratchRegister;
1543 : }
1544 44153 : if (FLAG_untrusted_code_mitigations) {
1545 0 : RetpolineCall(target);
1546 : } else {
1547 44153 : call(target);
1548 : }
1549 44154 : }
1550 :
1551 : void LiftoffAssembler::CallRuntimeStub(WasmCode::RuntimeStubId sid) {
1552 : // A direct call to a wasm runtime stub defined in this module.
1553 : // Just encode the stub index. This will be patched at relocation.
1554 135736 : near_call(static_cast<Address>(sid), RelocInfo::WASM_STUB_CALL);
1555 : }
1556 :
1557 18 : void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) {
1558 18 : subq(rsp, Immediate(size));
1559 : movq(addr, rsp);
1560 19 : }
1561 :
1562 : void LiftoffAssembler::DeallocateStackSlot(uint32_t size) {
1563 20 : addq(rsp, Immediate(size));
1564 : }
1565 :
1566 47747 : void LiftoffStackSlots::Construct() {
1567 85865 : for (auto& slot : slots_) {
1568 : const LiftoffAssembler::VarState& src = slot.src_;
1569 19059 : switch (src.loc()) {
1570 : case LiftoffAssembler::VarState::kStack:
1571 11231 : if (src.type() == kWasmI32) {
1572 : // Load i32 values to a register first to ensure they are zero
1573 : // extended.
1574 7182 : asm_->movl(kScratchRegister, liftoff::GetStackSlot(slot.src_index_));
1575 3591 : asm_->pushq(kScratchRegister);
1576 : } else {
1577 : // For all other types, just push the whole (8-byte) stack slot.
1578 : // This is also ok for f32 values (even though we copy 4 uninitialized
1579 : // bytes), because f32 and f64 values are clearly distinguished in
1580 : // Turbofan, so the uninitialized bytes are never accessed.
1581 15280 : asm_->pushq(liftoff::GetStackSlot(slot.src_index_));
1582 : }
1583 : break;
1584 : case LiftoffAssembler::VarState::kRegister:
1585 7561 : liftoff::push(asm_, src.reg(), src.type());
1586 7561 : break;
1587 : case LiftoffAssembler::VarState::kIntConst:
1588 267 : asm_->pushq(Immediate(src.i32_const()));
1589 267 : break;
1590 : }
1591 : }
1592 47747 : }
1593 :
1594 : #undef REQUIRE_CPU_FEATURE
1595 :
1596 : } // namespace wasm
1597 : } // namespace internal
1598 : } // namespace v8
1599 :
1600 : #endif // V8_WASM_BASELINE_X64_LIFTOFF_ASSEMBLER_X64_H_
|