V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
liftoff-assembler-arm.h
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_ARM_LIFTOFF_ASSEMBLER_ARM_H_
6 #define V8_WASM_BASELINE_ARM_LIFTOFF_ASSEMBLER_ARM_H_
7 
8 #include "src/wasm/baseline/liftoff-assembler.h"
9 
10 #define BAILOUT(reason) bailout("arm " reason)
11 
12 namespace v8 {
13 namespace internal {
14 namespace wasm {
15 
16 namespace liftoff {
17 
18 // half
19 // slot Frame
20 // -----+--------------------+---------------------------
21 // n+3 | parameter n |
22 // ... | ... |
23 // 4 | parameter 1 | or parameter 2
24 // 3 | parameter 0 | or parameter 1
25 // 2 | (result address) | or parameter 0
26 // -----+--------------------+---------------------------
27 // 1 | return addr (lr) |
28 // 0 | previous frame (fp)|
29 // -----+--------------------+ <-- frame ptr (fp)
30 // -1 | 0xa: WASM_COMPILED |
31 // -2 | instance |
32 // -----+--------------------+---------------------------
33 // -3 | slot 0 (high) | ^
34 // -4 | slot 0 (low) | |
35 // -5 | slot 1 (high) | Frame slots
36 // -6 | slot 1 (low) | |
37 // | | v
38 // -----+--------------------+ <-- stack ptr (sp)
39 //
40 static_assert(2 * kPointerSize == LiftoffAssembler::kStackSlotSize,
41  "Slot size should be twice the size of the 32 bit pointer.");
42 constexpr int32_t kInstanceOffset = 2 * kPointerSize;
43 constexpr int32_t kFirstStackSlotOffset = kInstanceOffset + 2 * kPointerSize;
44 constexpr int32_t kConstantStackSpace = kPointerSize;
45 // kPatchInstructionsRequired sets a maximum limit of how many instructions that
46 // PatchPrepareStackFrame will use in order to increase the stack appropriately.
47 // Three instructions are required to sub a large constant, movw + movt + sub.
48 constexpr int32_t kPatchInstructionsRequired = 3;
49 
50 inline MemOperand GetStackSlot(uint32_t index) {
51  int32_t offset =
52  kFirstStackSlotOffset + index * LiftoffAssembler::kStackSlotSize;
53  return MemOperand(fp, -offset);
54 }
55 
56 inline MemOperand GetHalfStackSlot(uint32_t half_index) {
57  int32_t offset = kFirstStackSlotOffset +
58  half_index * (LiftoffAssembler::kStackSlotSize / 2);
59  return MemOperand(fp, -offset);
60 }
61 
62 inline MemOperand GetHalfStackSlot(uint32_t index, RegPairHalf half) {
63  if (half == kLowWord) {
64  return GetHalfStackSlot(2 * index);
65  } else {
66  return GetHalfStackSlot(2 * index - 1);
67  }
68 }
69 
70 inline MemOperand GetInstanceOperand() {
71  return MemOperand(fp, -kInstanceOffset);
72 }
73 
74 inline MemOperand GetMemOp(LiftoffAssembler* assm,
75  UseScratchRegisterScope* temps, Register addr,
76  Register offset, int32_t offset_imm) {
77  if (offset != no_reg) {
78  if (offset_imm == 0) return MemOperand(addr, offset);
79  Register tmp = temps->Acquire();
80  assm->add(tmp, offset, Operand(offset_imm));
81  return MemOperand(addr, tmp);
82  }
83  return MemOperand(addr, offset_imm);
84 }
85 
86 inline Register CalculateActualAddress(LiftoffAssembler* assm,
87  UseScratchRegisterScope* temps,
88  Register addr_reg, Register offset_reg,
89  int32_t offset_imm) {
90  if (offset_reg == no_reg && offset_imm == 0) {
91  return addr_reg;
92  }
93  Register actual_addr_reg = temps->Acquire();
94  if (offset_reg == no_reg) {
95  assm->add(actual_addr_reg, addr_reg, Operand(offset_imm));
96  } else {
97  assm->add(actual_addr_reg, addr_reg, Operand(offset_reg));
98  if (offset_imm != 0) {
99  assm->add(actual_addr_reg, actual_addr_reg, Operand(offset_imm));
100  }
101  }
102  return actual_addr_reg;
103 }
104 
105 inline Condition MakeUnsigned(Condition cond) {
106  switch (cond) {
107  case kSignedLessThan:
108  return kUnsignedLessThan;
109  case kSignedLessEqual:
110  return kUnsignedLessEqual;
111  case kSignedGreaterThan:
112  return kUnsignedGreaterThan;
113  case kSignedGreaterEqual:
114  return kUnsignedGreaterEqual;
115  case kEqual:
116  case kUnequal:
117  case kUnsignedLessThan:
118  case kUnsignedLessEqual:
119  case kUnsignedGreaterThan:
120  case kUnsignedGreaterEqual:
121  return cond;
122  default:
123  UNREACHABLE();
124  }
125 }
126 
127 template <void (Assembler::*op)(Register, Register, Register, SBit, Condition),
128  void (Assembler::*op_with_carry)(Register, Register, const Operand&,
129  SBit, Condition)>
130 inline void I64Binop(LiftoffAssembler* assm, LiftoffRegister dst,
131  LiftoffRegister lhs, LiftoffRegister rhs) {
132  UseScratchRegisterScope temps(assm);
133  Register scratch = dst.low_gp();
134  bool can_use_dst =
135  dst.low_gp() != lhs.high_gp() && dst.low_gp() != rhs.high_gp();
136  if (!can_use_dst) {
137  scratch = temps.Acquire();
138  }
139  (assm->*op)(scratch, lhs.low_gp(), rhs.low_gp(), SetCC, al);
140  (assm->*op_with_carry)(dst.high_gp(), lhs.high_gp(), Operand(rhs.high_gp()),
141  LeaveCC, al);
142  if (!can_use_dst) {
143  assm->mov(dst.low_gp(), scratch);
144  }
145 }
146 
147 // safe_amount_reg is the register in which the register holding the shift
148 // amount can be held without being clobbered, thus the original register
149 // holding the shift amount can be moved into it if required.
150 template <void (TurboAssembler::*op)(Register, Register, Register, Register,
151  Register)>
152 inline void I64Shiftop(LiftoffAssembler* assm, LiftoffRegister dst,
153  LiftoffRegister src, Register amount,
154  Register safe_amount_reg, LiftoffRegList pinned) {
155  DCHECK(safe_amount_reg == dst.low_gp() || safe_amount_reg == dst.high_gp());
156  Register other_reg =
157  (safe_amount_reg == dst.low_gp()) ? dst.high_gp() : dst.low_gp();
158  pinned.set(other_reg);
159  pinned.set(src.low_gp());
160  pinned.set(src.high_gp());
161  Register scratch = assm->GetUnusedRegister(kGpReg, pinned).gp();
162  assm->and_(scratch, amount, Operand(0x3F));
163  (assm->*op)(dst.low_gp(), dst.high_gp(), src.low_gp(), src.high_gp(),
164  scratch);
165 }
166 
167 enum class MinOrMax : uint8_t { kMin, kMax };
168 inline void EmitFloatMinOrMax(LiftoffAssembler* assm, DoubleRegister dst,
169  DoubleRegister lhs, DoubleRegister rhs,
170  MinOrMax min_or_max) {
171  Label is_nan;
172  Label lhs_below_rhs;
173  Label lhs_above_rhs;
174  Label done;
175  // Check the easy cases first: nan (e.g. unordered), smaller and greater.
176  assm->VFPCompareAndSetFlags(lhs, rhs);
177  assm->b(&is_nan, vs);
178 
179  if (CpuFeatures::IsSupported(ARMv8)) {
180  CpuFeatureScope scope(assm, ARMv8);
181  if (min_or_max == MinOrMax::kMin) {
182  assm->vminnm(dst, lhs, rhs);
183  } else {
184  assm->vmaxnm(dst, lhs, rhs);
185  }
186  assm->b(&done);
187  assm->bind(&is_nan);
188  // Create a NaN output.
189  assm->vadd(dst, lhs, rhs);
190  } else {
191  assm->b(&lhs_below_rhs, lt);
192  assm->b(&lhs_above_rhs, gt);
193 
194  UseScratchRegisterScope temps(assm);
195  Register scratch = temps.Acquire();
196 
197  // If we get here, then either
198  // a) {lhs == rhs},
199  // b) {lhs == -0.0} and {rhs == 0.0}, or
200  // c) {lhs == 0.0} and {rhs == -0.0}.
201  // For a), it does not matter whether we return {lhs} or {rhs}. Check the
202  // sign bit of {rhs} to differentiate b) and c).
203  assm->VmovHigh(scratch, lhs);
204  assm->cmp(scratch, Operand(0));
205  assm->b(&lhs_below_rhs, mi);
206  assm->b(&lhs_above_rhs);
207  assm->bind(&is_nan);
208  // Create a NaN output.
209  assm->vadd(dst, lhs, rhs);
210 
211  assm->b(&done);
212  assm->bind(&lhs_below_rhs);
213  DoubleRegister lhs_below_rhs_src =
214  (min_or_max == MinOrMax::kMin) ? lhs : rhs;
215  if (dst != lhs_below_rhs_src) {
216  assm->vmov(dst, lhs_below_rhs_src);
217  }
218  assm->b(&done);
219 
220  assm->bind(&lhs_above_rhs);
221  DoubleRegister lhs_above_rhs_src =
222  (min_or_max == MinOrMax::kMin) ? rhs : lhs;
223  if (dst != lhs_above_rhs_src) {
224  assm->vmov(dst, lhs_above_rhs_src);
225  }
226  }
227  assm->bind(&done);
228 }
229 } // namespace liftoff
230 
231 int LiftoffAssembler::PrepareStackFrame() {
232  if (!CpuFeatures::IsSupported(ARMv7)) {
233  BAILOUT("Armv6 not supported");
234  return 0;
235  }
236  uint32_t offset = static_cast<uint32_t>(pc_offset());
237  // PatchPrepareStackFrame will patch this in order to increase the stack
238  // appropriately. Additional nops are required as the bytes operand might
239  // require extra moves to encode.
240  for (int i = 0; i < liftoff::kPatchInstructionsRequired; i++) {
241  nop();
242  }
243  DCHECK_EQ(offset + liftoff::kPatchInstructionsRequired * kInstrSize,
244  pc_offset());
245  return offset;
246 }
247 
248 void LiftoffAssembler::PatchPrepareStackFrame(int offset,
249  uint32_t stack_slots) {
250  // Allocate space for instance plus what is needed for the frame slots.
251  uint32_t bytes = liftoff::kConstantStackSpace + kStackSlotSize * stack_slots;
252 #ifdef USE_SIMULATOR
253  // When using the simulator, deal with Liftoff which allocates the stack
254  // before checking it.
255  // TODO(arm): Remove this when the stack check mechanism will be updated.
256  if (bytes > KB / 2) {
257  BAILOUT("Stack limited to 512 bytes to avoid a bug in StackCheck");
258  return;
259  }
260 #endif
261  PatchingAssembler patching_assembler(AssemblerOptions{}, buffer_ + offset,
262  liftoff::kPatchInstructionsRequired);
263  patching_assembler.sub(sp, sp, Operand(bytes));
264  patching_assembler.PadWithNops();
265 }
266 
267 void LiftoffAssembler::FinishCode() { CheckConstPool(true, false); }
268 
269 void LiftoffAssembler::AbortCompilation() { AbortedCodeGeneration(); }
270 
271 void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value,
272  RelocInfo::Mode rmode) {
273  switch (value.type()) {
274  case kWasmI32:
275  TurboAssembler::Move(reg.gp(), Operand(value.to_i32(), rmode));
276  break;
277  case kWasmI64: {
278  DCHECK(RelocInfo::IsNone(rmode));
279  int32_t low_word = value.to_i64();
280  int32_t high_word = value.to_i64() >> 32;
281  TurboAssembler::Move(reg.low_gp(), Operand(low_word));
282  TurboAssembler::Move(reg.high_gp(), Operand(high_word));
283  break;
284  }
285  case kWasmF32:
286  BAILOUT("Load f32 Constant");
287  break;
288  case kWasmF64: {
289  Register extra_scratch = GetUnusedRegister(kGpReg).gp();
290  vmov(reg.fp(), Double(value.to_f64_boxed().get_scalar()), extra_scratch);
291  break;
292  }
293  default:
294  UNREACHABLE();
295  }
296 }
297 
298 void LiftoffAssembler::LoadFromInstance(Register dst, uint32_t offset,
299  int size) {
300  DCHECK_LE(offset, kMaxInt);
301  DCHECK_EQ(4, size);
302  ldr(dst, liftoff::GetInstanceOperand());
303  ldr(dst, MemOperand(dst, offset));
304 }
305 
306 void LiftoffAssembler::SpillInstance(Register instance) {
307  str(instance, liftoff::GetInstanceOperand());
308 }
309 
310 void LiftoffAssembler::FillInstanceInto(Register dst) {
311  ldr(dst, liftoff::GetInstanceOperand());
312 }
313 
314 void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr,
315  Register offset_reg, uint32_t offset_imm,
316  LoadType type, LiftoffRegList pinned,
317  uint32_t* protected_load_pc, bool is_load_mem) {
318  DCHECK_IMPLIES(type.value_type() == kWasmI64, dst.is_pair());
319  // If offset_imm cannot be converted to int32 safely, we abort as a separate
320  // check should cause this code to never be executed.
321  // TODO(7881): Support when >2GB is required.
322  if (!is_uint31(offset_imm)) {
323  TurboAssembler::Abort(AbortReason::kOffsetOutOfRange);
324  return;
325  }
326  UseScratchRegisterScope temps(this);
327  if (type.value() == LoadType::kF64Load) {
328  // Armv6 is not supported so Neon can be used to avoid alignment issues.
329  CpuFeatureScope scope(this, NEON);
330  Register actual_src_addr = liftoff::CalculateActualAddress(
331  this, &temps, src_addr, offset_reg, offset_imm);
332  vld1(Neon64, NeonListOperand(dst.fp()), NeonMemOperand(actual_src_addr));
333  } else {
334  MemOperand src_op =
335  liftoff::GetMemOp(this, &temps, src_addr, offset_reg, offset_imm);
336  if (protected_load_pc) *protected_load_pc = pc_offset();
337  switch (type.value()) {
338  case LoadType::kI32Load8U:
339  ldrb(dst.gp(), src_op);
340  break;
341  case LoadType::kI64Load8U:
342  ldrb(dst.low_gp(), src_op);
343  mov(dst.high_gp(), Operand(0));
344  break;
345  case LoadType::kI32Load8S:
346  ldrsb(dst.gp(), src_op);
347  break;
348  case LoadType::kI64Load8S:
349  ldrsb(dst.low_gp(), src_op);
350  asr(dst.high_gp(), dst.low_gp(), Operand(31));
351  break;
352  case LoadType::kI32Load16U:
353  ldrh(dst.gp(), src_op);
354  break;
355  case LoadType::kI64Load16U:
356  ldrh(dst.low_gp(), src_op);
357  mov(dst.high_gp(), Operand(0));
358  break;
359  case LoadType::kI32Load16S:
360  ldrsh(dst.gp(), src_op);
361  break;
362  case LoadType::kI32Load:
363  ldr(dst.gp(), src_op);
364  break;
365  case LoadType::kI64Load16S:
366  ldrsh(dst.low_gp(), src_op);
367  asr(dst.high_gp(), dst.low_gp(), Operand(31));
368  break;
369  case LoadType::kI64Load32U:
370  ldr(dst.low_gp(), src_op);
371  mov(dst.high_gp(), Operand(0));
372  break;
373  case LoadType::kI64Load32S:
374  ldr(dst.low_gp(), src_op);
375  asr(dst.high_gp(), dst.low_gp(), Operand(31));
376  break;
377  case LoadType::kI64Load:
378  ldr(dst.low_gp(), src_op);
379  // GetMemOp may use a scratch register as the offset register, in which
380  // case, calling GetMemOp again will fail due to the assembler having
381  // ran out of scratch registers.
382  if (temps.CanAcquire()) {
383  src_op = liftoff::GetMemOp(this, &temps, src_addr, offset_reg,
384  offset_imm + kRegisterSize);
385  } else {
386  add(src_op.rm(), src_op.rm(), Operand(kRegisterSize));
387  }
388  ldr(dst.high_gp(), src_op);
389  break;
390  case LoadType::kF32Load:
391  BAILOUT("Load f32");
392  break;
393  default:
394  UNREACHABLE();
395  }
396  }
397 }
398 
399 void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
400  uint32_t offset_imm, LiftoffRegister src,
401  StoreType type, LiftoffRegList pinned,
402  uint32_t* protected_store_pc, bool is_store_mem) {
403  // If offset_imm cannot be converted to int32 safely, we abort as a separate
404  // check should cause this code to never be executed.
405  // TODO(7881): Support when >2GB is required.
406  if (!is_uint31(offset_imm)) {
407  TurboAssembler::Abort(AbortReason::kOffsetOutOfRange);
408  return;
409  }
410  UseScratchRegisterScope temps(this);
411  if (type.value() == StoreType::kF64Store) {
412  // Armv6 is not supported so Neon can be used to avoid alignment issues.
413  CpuFeatureScope scope(this, NEON);
414  Register actual_dst_addr = liftoff::CalculateActualAddress(
415  this, &temps, dst_addr, offset_reg, offset_imm);
416  vst1(Neon64, NeonListOperand(src.fp()), NeonMemOperand(actual_dst_addr));
417  } else {
418  MemOperand dst_op =
419  liftoff::GetMemOp(this, &temps, dst_addr, offset_reg, offset_imm);
420  if (protected_store_pc) *protected_store_pc = pc_offset();
421  switch (type.value()) {
422  case StoreType::kI64Store8:
423  src = src.low();
424  V8_FALLTHROUGH;
425  case StoreType::kI32Store8:
426  strb(src.gp(), dst_op);
427  break;
428  case StoreType::kI64Store16:
429  src = src.low();
430  V8_FALLTHROUGH;
431  case StoreType::kI32Store16:
432  strh(src.gp(), dst_op);
433  break;
434  case StoreType::kI64Store32:
435  src = src.low();
436  V8_FALLTHROUGH;
437  case StoreType::kI32Store:
438  str(src.gp(), dst_op);
439  break;
440  case StoreType::kI64Store:
441  str(src.low_gp(), dst_op);
442  // GetMemOp may use a scratch register as the offset register, in which
443  // case, calling GetMemOp again will fail due to the assembler having
444  // ran out of scratch registers.
445  if (temps.CanAcquire()) {
446  dst_op = liftoff::GetMemOp(this, &temps, dst_addr, offset_reg,
447  offset_imm + kRegisterSize);
448  } else {
449  add(dst_op.rm(), dst_op.rm(), Operand(kRegisterSize));
450  }
451  str(src.high_gp(), dst_op);
452  break;
453  case StoreType::kF32Store:
454  BAILOUT("Store f32");
455  break;
456  default:
457  UNREACHABLE();
458  }
459  }
460 }
461 
462 void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
463  uint32_t caller_slot_idx,
464  ValueType type) {
465  int32_t offset = (caller_slot_idx + 1) * kRegisterSize;
466  MemOperand src(fp, offset);
467  switch (type) {
468  case kWasmI32:
469  ldr(dst.gp(), src);
470  break;
471  case kWasmI64:
472  ldr(dst.low_gp(), src);
473  ldr(dst.high_gp(), MemOperand(fp, offset + kRegisterSize));
474  break;
475  case kWasmF32:
476  BAILOUT("Load Caller Frame Slot for f32");
477  break;
478  case kWasmF64:
479  vldr(dst.fp(), src);
480  break;
481  default:
482  UNREACHABLE();
483  }
484 }
485 
486 void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index,
487  ValueType type) {
488  DCHECK_NE(dst_index, src_index);
489  LiftoffRegister reg = GetUnusedRegister(kGpReg);
490  Fill(reg, src_index, type);
491  Spill(dst_index, reg, type);
492 }
493 
494 void LiftoffAssembler::Move(Register dst, Register src, ValueType type) {
495  DCHECK_NE(dst, src);
496  DCHECK_EQ(type, kWasmI32);
497  TurboAssembler::Move(dst, src);
498 }
499 
500 void LiftoffAssembler::Move(DoubleRegister dst, DoubleRegister src,
501  ValueType type) {
502  DCHECK_NE(dst, src);
503  if (type == kWasmF32) {
504  BAILOUT("Move DoubleRegister");
505  } else {
506  DCHECK_EQ(kWasmF64, type);
507  vmov(dst, src);
508  }
509 }
510 
511 void LiftoffAssembler::Spill(uint32_t index, LiftoffRegister reg,
512  ValueType type) {
513  RecordUsedSpillSlot(index);
514  MemOperand dst = liftoff::GetStackSlot(index);
515  switch (type) {
516  case kWasmI32:
517  str(reg.gp(), dst);
518  break;
519  case kWasmI64:
520  str(reg.low_gp(), liftoff::GetHalfStackSlot(index, kLowWord));
521  str(reg.high_gp(), liftoff::GetHalfStackSlot(index, kHighWord));
522  break;
523  case kWasmF32:
524  BAILOUT("Spill Register f32");
525  break;
526  case kWasmF64:
527  vstr(reg.fp(), dst);
528  break;
529  default:
530  UNREACHABLE();
531  }
532 }
533 
534 void LiftoffAssembler::Spill(uint32_t index, WasmValue value) {
535  RecordUsedSpillSlot(index);
536  MemOperand dst = liftoff::GetStackSlot(index);
537  UseScratchRegisterScope temps(this);
538  Register src = no_reg;
539  // The scratch register will be required by str if multiple instructions
540  // are required to encode the offset, and so we cannot use it in that case.
541  if (!ImmediateFitsAddrMode2Instruction(dst.offset())) {
542  src = GetUnusedRegister(kGpReg).gp();
543  } else {
544  src = temps.Acquire();
545  }
546  switch (value.type()) {
547  case kWasmI32:
548  mov(src, Operand(value.to_i32()));
549  str(src, dst);
550  break;
551  case kWasmI64: {
552  int32_t low_word = value.to_i64();
553  mov(src, Operand(low_word));
554  str(src, liftoff::GetHalfStackSlot(index, kLowWord));
555  int32_t high_word = value.to_i64() >> 32;
556  mov(src, Operand(high_word));
557  str(src, liftoff::GetHalfStackSlot(index, kHighWord));
558  break;
559  }
560  default:
561  // We do not track f32 and f64 constants, hence they are unreachable.
562  UNREACHABLE();
563  }
564 }
565 
566 void LiftoffAssembler::Fill(LiftoffRegister reg, uint32_t index,
567  ValueType type) {
568  switch (type) {
569  case kWasmI32:
570  ldr(reg.gp(), liftoff::GetStackSlot(index));
571  break;
572  case kWasmI64:
573  ldr(reg.low_gp(), liftoff::GetHalfStackSlot(index, kLowWord));
574  ldr(reg.high_gp(), liftoff::GetHalfStackSlot(index, kHighWord));
575  break;
576  case kWasmF32:
577  BAILOUT("Fill Register");
578  break;
579  case kWasmF64:
580  vldr(reg.fp(), liftoff::GetStackSlot(index));
581  break;
582  default:
583  UNREACHABLE();
584  }
585 }
586 
587 void LiftoffAssembler::FillI64Half(Register reg, uint32_t half_index) {
588  ldr(reg, liftoff::GetHalfStackSlot(half_index));
589 }
590 
591 #define I32_BINOP(name, instruction) \
592  void LiftoffAssembler::emit_##name(Register dst, Register lhs, \
593  Register rhs) { \
594  instruction(dst, lhs, rhs); \
595  }
596 #define I32_SHIFTOP(name, instruction) \
597  void LiftoffAssembler::emit_##name(Register dst, Register src, \
598  Register amount, LiftoffRegList pinned) { \
599  UseScratchRegisterScope temps(this); \
600  Register scratch = temps.Acquire(); \
601  and_(scratch, amount, Operand(0x1f)); \
602  instruction(dst, src, Operand(scratch)); \
603  }
604 #define FP64_UNOP(name, instruction) \
605  void LiftoffAssembler::emit_##name(DoubleRegister dst, DoubleRegister src) { \
606  instruction(dst, src); \
607  }
608 #define FP64_BINOP(name, instruction) \
609  void LiftoffAssembler::emit_##name(DoubleRegister dst, DoubleRegister lhs, \
610  DoubleRegister rhs) { \
611  instruction(dst, lhs, rhs); \
612  }
613 #define UNIMPLEMENTED_FP_BINOP(name) \
614  void LiftoffAssembler::emit_##name(DoubleRegister dst, DoubleRegister lhs, \
615  DoubleRegister rhs) { \
616  BAILOUT("fp binop: " #name); \
617  }
618 #define UNIMPLEMENTED_FP_UNOP(name) \
619  void LiftoffAssembler::emit_##name(DoubleRegister dst, DoubleRegister src) { \
620  BAILOUT("fp unop: " #name); \
621  }
622 
623 I32_BINOP(i32_add, add)
624 I32_BINOP(i32_sub, sub)
625 I32_BINOP(i32_mul, mul)
626 I32_BINOP(i32_and, and_)
627 I32_BINOP(i32_or, orr)
628 I32_BINOP(i32_xor, eor)
629 I32_SHIFTOP(i32_shl, lsl)
630 I32_SHIFTOP(i32_sar, asr)
631 I32_SHIFTOP(i32_shr, lsr)
632 UNIMPLEMENTED_FP_BINOP(f32_add)
633 UNIMPLEMENTED_FP_BINOP(f32_sub)
634 UNIMPLEMENTED_FP_BINOP(f32_mul)
635 UNIMPLEMENTED_FP_BINOP(f32_div)
636 UNIMPLEMENTED_FP_BINOP(f32_min)
637 UNIMPLEMENTED_FP_BINOP(f32_max)
638 UNIMPLEMENTED_FP_BINOP(f32_copysign)
639 UNIMPLEMENTED_FP_UNOP(f32_abs)
640 UNIMPLEMENTED_FP_UNOP(f32_neg)
641 UNIMPLEMENTED_FP_UNOP(f32_sqrt)
642 FP64_BINOP(f64_add, vadd)
643 FP64_BINOP(f64_sub, vsub)
644 FP64_BINOP(f64_mul, vmul)
645 FP64_BINOP(f64_div, vdiv)
646 FP64_UNOP(f64_abs, vabs)
647 FP64_UNOP(f64_neg, vneg)
648 FP64_UNOP(f64_sqrt, vsqrt)
649 
650 #undef I32_BINOP
651 #undef I32_SHIFTOP
652 #undef UNIMPLEMENTED_FP_BINOP
653 #undef UNIMPLEMENTED_FP_UNOP
654 
655 bool LiftoffAssembler::emit_i32_clz(Register dst, Register src) {
656  clz(dst, src);
657  return true;
658 }
659 
660 bool LiftoffAssembler::emit_i32_ctz(Register dst, Register src) {
661  rbit(dst, src);
662  clz(dst, dst);
663  return true;
664 }
665 
666 bool LiftoffAssembler::emit_i32_popcnt(Register dst, Register src) {
667  {
668  UseScratchRegisterScope temps(this);
669  Register scratch = temps.Acquire();
670  // x = x - ((x & (0x55555555 << 1)) >> 1)
671  and_(scratch, src, Operand(0xaaaaaaaa));
672  sub(dst, src, Operand(scratch, LSR, 1));
673  LiftoffRegList pinned;
674  pinned.set(dst);
675  Register scratch_2 = GetUnusedRegister(kGpReg, pinned).gp();
676  // x = (x & 0x33333333) + ((x & (0x33333333 << 2)) >> 2)
677  mov(scratch, Operand(0x33333333));
678  and_(scratch_2, dst, Operand(scratch, LSL, 2));
679  and_(scratch, dst, scratch);
680  add(dst, scratch, Operand(scratch_2, LSR, 2));
681  }
682  // x = (x + (x >> 4)) & 0x0F0F0F0F
683  add(dst, dst, Operand(dst, LSR, 4));
684  and_(dst, dst, Operand(0x0f0f0f0f));
685  // x = x + (x >> 8)
686  add(dst, dst, Operand(dst, LSR, 8));
687  // x = x + (x >> 16)
688  add(dst, dst, Operand(dst, LSR, 16));
689  // x = x & 0x3F
690  and_(dst, dst, Operand(0x3f));
691  return true;
692 }
693 
694 void LiftoffAssembler::emit_i32_divs(Register dst, Register lhs, Register rhs,
695  Label* trap_div_by_zero,
696  Label* trap_div_unrepresentable) {
697  if (!CpuFeatures::IsSupported(SUDIV)) {
698  BAILOUT("i32_divs");
699  return;
700  }
701  CpuFeatureScope scope(this, SUDIV);
702  // Issue division early so we can perform the trapping checks whilst it
703  // completes.
704  bool speculative_sdiv = dst != lhs && dst != rhs;
705  if (speculative_sdiv) {
706  sdiv(dst, lhs, rhs);
707  }
708  Label noTrap;
709  // Check for division by zero.
710  cmp(rhs, Operand(0));
711  b(trap_div_by_zero, eq);
712  // Check for kMinInt / -1. This is unrepresentable.
713  cmp(rhs, Operand(-1));
714  b(&noTrap, ne);
715  cmp(lhs, Operand(kMinInt));
716  b(trap_div_unrepresentable, eq);
717  bind(&noTrap);
718  if (!speculative_sdiv) {
719  sdiv(dst, lhs, rhs);
720  }
721 }
722 
723 void LiftoffAssembler::emit_i32_divu(Register dst, Register lhs, Register rhs,
724  Label* trap_div_by_zero) {
725  if (!CpuFeatures::IsSupported(SUDIV)) {
726  BAILOUT("i32_divu");
727  return;
728  }
729  CpuFeatureScope scope(this, SUDIV);
730  // Check for division by zero.
731  cmp(rhs, Operand(0));
732  b(trap_div_by_zero, eq);
733  udiv(dst, lhs, rhs);
734 }
735 
736 void LiftoffAssembler::emit_i32_rems(Register dst, Register lhs, Register rhs,
737  Label* trap_div_by_zero) {
738  if (!CpuFeatures::IsSupported(SUDIV)) {
739  // When this case is handled, a check for ARMv7 is required to use mls.
740  // Mls support is implied with SUDIV support.
741  BAILOUT("i32_rems");
742  return;
743  }
744  CpuFeatureScope scope(this, SUDIV);
745  // No need to check kMinInt / -1 because the result is kMinInt and then
746  // kMinInt * -1 -> kMinInt. In this case, the Msub result is therefore 0.
747  UseScratchRegisterScope temps(this);
748  Register scratch = temps.Acquire();
749  sdiv(scratch, lhs, rhs);
750  // Check for division by zero.
751  cmp(rhs, Operand(0));
752  b(trap_div_by_zero, eq);
753  // Compute remainder.
754  mls(dst, scratch, rhs, lhs);
755 }
756 
757 void LiftoffAssembler::emit_i32_remu(Register dst, Register lhs, Register rhs,
758  Label* trap_div_by_zero) {
759  if (!CpuFeatures::IsSupported(SUDIV)) {
760  // When this case is handled, a check for ARMv7 is required to use mls.
761  // Mls support is implied with SUDIV support.
762  BAILOUT("i32_remu");
763  return;
764  }
765  CpuFeatureScope scope(this, SUDIV);
766  // No need to check kMinInt / -1 because the result is kMinInt and then
767  // kMinInt * -1 -> kMinInt. In this case, the Msub result is therefore 0.
768  UseScratchRegisterScope temps(this);
769  Register scratch = temps.Acquire();
770  udiv(scratch, lhs, rhs);
771  // Check for division by zero.
772  cmp(rhs, Operand(0));
773  b(trap_div_by_zero, eq);
774  // Compute remainder.
775  mls(dst, scratch, rhs, lhs);
776 }
777 
778 void LiftoffAssembler::emit_i32_shr(Register dst, Register src, int amount) {
779  DCHECK(is_uint5(amount));
780  lsr(dst, src, Operand(amount));
781 }
782 
783 void LiftoffAssembler::emit_i64_add(LiftoffRegister dst, LiftoffRegister lhs,
784  LiftoffRegister rhs) {
785  liftoff::I64Binop<&Assembler::add, &Assembler::adc>(this, dst, lhs, rhs);
786 }
787 
788 void LiftoffAssembler::emit_i64_sub(LiftoffRegister dst, LiftoffRegister lhs,
789  LiftoffRegister rhs) {
790  liftoff::I64Binop<&Assembler::sub, &Assembler::sbc>(this, dst, lhs, rhs);
791 }
792 
793 void LiftoffAssembler::emit_i64_mul(LiftoffRegister dst, LiftoffRegister lhs,
794  LiftoffRegister rhs) {
795  // Idea:
796  // [ lhs_hi | lhs_lo ] * [ rhs_hi | rhs_lo ]
797  // = [ lhs_hi * rhs_lo | ] (32 bit mul, shift 32)
798  // + [ lhs_lo * rhs_hi | ] (32 bit mul, shift 32)
799  // + [ lhs_lo * rhs_lo ] (32x32->64 mul, shift 0)
800  UseScratchRegisterScope temps(this);
801  Register scratch = temps.Acquire();
802  // scratch = lhs_hi * rhs_lo
803  mul(scratch, lhs.high_gp(), rhs.low_gp());
804  // scratch += lhs_lo * rhs_hi
805  mla(scratch, lhs.low_gp(), rhs.high_gp(), scratch);
806  // TODO(arm): use umlal once implemented correctly in the simulator.
807  // [dst_hi|dst_lo] = lhs_lo * rhs_lo
808  umull(dst.low_gp(), dst.high_gp(), lhs.low_gp(), rhs.low_gp());
809  // dst_hi += scratch
810  add(dst.high_gp(), dst.high_gp(), scratch);
811 }
812 
813 bool LiftoffAssembler::emit_i64_divs(LiftoffRegister dst, LiftoffRegister lhs,
814  LiftoffRegister rhs,
815  Label* trap_div_by_zero,
816  Label* trap_div_unrepresentable) {
817  return false;
818 }
819 
820 bool LiftoffAssembler::emit_i64_divu(LiftoffRegister dst, LiftoffRegister lhs,
821  LiftoffRegister rhs,
822  Label* trap_div_by_zero) {
823  return false;
824 }
825 
826 bool LiftoffAssembler::emit_i64_rems(LiftoffRegister dst, LiftoffRegister lhs,
827  LiftoffRegister rhs,
828  Label* trap_div_by_zero) {
829  return false;
830 }
831 
832 bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs,
833  LiftoffRegister rhs,
834  Label* trap_div_by_zero) {
835  return false;
836 }
837 
838 void LiftoffAssembler::emit_i64_shl(LiftoffRegister dst, LiftoffRegister src,
839  Register amount, LiftoffRegList pinned) {
840  liftoff::I64Shiftop<&TurboAssembler::LslPair>(this, dst, src, amount,
841  dst.low_gp(), pinned);
842 }
843 
844 void LiftoffAssembler::emit_i64_sar(LiftoffRegister dst, LiftoffRegister src,
845  Register amount, LiftoffRegList pinned) {
846  liftoff::I64Shiftop<&TurboAssembler::AsrPair>(this, dst, src, amount,
847  dst.high_gp(), pinned);
848 }
849 
850 void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src,
851  Register amount, LiftoffRegList pinned) {
852  liftoff::I64Shiftop<&TurboAssembler::LsrPair>(this, dst, src, amount,
853  dst.high_gp(), pinned);
854 }
855 
856 void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src,
857  int amount) {
858  DCHECK(is_uint6(amount));
859  LsrPair(dst.low_gp(), dst.high_gp(), src.low_gp(), src.high_gp(), amount);
860 }
861 
862 bool LiftoffAssembler::emit_f32_ceil(DoubleRegister dst, DoubleRegister src) {
863  return false;
864 }
865 
866 bool LiftoffAssembler::emit_f32_floor(DoubleRegister dst, DoubleRegister src) {
867  return false;
868 }
869 
870 bool LiftoffAssembler::emit_f32_trunc(DoubleRegister dst, DoubleRegister src) {
871  return false;
872 }
873 
874 bool LiftoffAssembler::emit_f32_nearest_int(DoubleRegister dst,
875  DoubleRegister src) {
876  return false;
877 }
878 
879 bool LiftoffAssembler::emit_f64_ceil(DoubleRegister dst, DoubleRegister src) {
880  if (CpuFeatures::IsSupported(ARMv8)) {
881  CpuFeatureScope scope(this, ARMv8);
882  vrintp(dst, src);
883  return true;
884  }
885  return false;
886 }
887 
888 bool LiftoffAssembler::emit_f64_floor(DoubleRegister dst, DoubleRegister src) {
889  if (CpuFeatures::IsSupported(ARMv8)) {
890  CpuFeatureScope scope(this, ARMv8);
891  vrintm(dst, src);
892  return true;
893  }
894  return false;
895 }
896 
897 bool LiftoffAssembler::emit_f64_trunc(DoubleRegister dst, DoubleRegister src) {
898  if (CpuFeatures::IsSupported(ARMv8)) {
899  CpuFeatureScope scope(this, ARMv8);
900  vrintz(dst, src);
901  return true;
902  }
903  return false;
904 }
905 
906 bool LiftoffAssembler::emit_f64_nearest_int(DoubleRegister dst,
907  DoubleRegister src) {
908  if (CpuFeatures::IsSupported(ARMv8)) {
909  CpuFeatureScope scope(this, ARMv8);
910  vrintn(dst, src);
911  return true;
912  }
913  return false;
914 }
915 
916 void LiftoffAssembler::emit_f64_min(DoubleRegister dst, DoubleRegister lhs,
917  DoubleRegister rhs) {
918  liftoff::EmitFloatMinOrMax(this, dst, lhs, rhs, liftoff::MinOrMax::kMin);
919 }
920 
921 void LiftoffAssembler::emit_f64_max(DoubleRegister dst, DoubleRegister lhs,
922  DoubleRegister rhs) {
923  liftoff::EmitFloatMinOrMax(this, dst, lhs, rhs, liftoff::MinOrMax::kMax);
924 }
925 
926 void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) {
927  // This is a nop on arm.
928 }
929 
930 void LiftoffAssembler::emit_f64_copysign(DoubleRegister dst, DoubleRegister lhs,
931  DoubleRegister rhs) {
932  constexpr uint32_t kF64SignBitHighWord = uint32_t{1} << 31;
933  // On arm, we cannot hold the whole f64 value in a gp register, so we just
934  // operate on the upper half (UH).
935  UseScratchRegisterScope temps(this);
936  Register scratch = temps.Acquire();
937  Register scratch2 = GetUnusedRegister(kGpReg).gp();
938  VmovHigh(scratch, lhs);
939  // Clear sign bit in {scratch}.
940  bic(scratch, scratch, Operand(kF64SignBitHighWord));
941  VmovHigh(scratch2, rhs);
942  // Isolate sign bit in {scratch2}.
943  and_(scratch2, scratch2, Operand(kF64SignBitHighWord));
944  // Combine {scratch2} into {scratch}.
945  orr(scratch, scratch, scratch2);
946  vmov(dst, lhs);
947  VmovHigh(dst, scratch);
948 }
949 
950 bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode,
951  LiftoffRegister dst,
952  LiftoffRegister src, Label* trap) {
953  switch (opcode) {
954  case kExprI32ConvertI64:
955  TurboAssembler::Move(dst.gp(), src.low_gp());
956  return true;
957  case kExprI32SConvertF32: {
958  BAILOUT("kExprI32SConvertF32");
959  return true;
960  }
961  case kExprI32UConvertF32: {
962  BAILOUT("kExprI32UConvertF32");
963  return true;
964  }
965  case kExprI32SConvertF64: {
966  UseScratchRegisterScope temps(this);
967  SwVfpRegister scratch_f = temps.AcquireS();
968  vcvt_s32_f64(scratch_f, src.fp()); // f64 -> i32 round to zero.
969  vmov(dst.gp(), scratch_f);
970  // Check underflow and NaN.
971  DwVfpRegister scratch_d = temps.AcquireD();
972  vmov(scratch_d, Double(static_cast<double>(INT32_MIN - 1.0)));
973  VFPCompareAndSetFlags(src.fp(), scratch_d);
974  b(trap, le);
975  // Check overflow.
976  vmov(scratch_d, Double(static_cast<double>(INT32_MAX + 1.0)));
977  VFPCompareAndSetFlags(src.fp(), scratch_d);
978  b(trap, ge);
979  return true;
980  }
981  case kExprI32UConvertF64: {
982  UseScratchRegisterScope temps(this);
983  SwVfpRegister scratch_f = temps.AcquireS();
984  vcvt_u32_f64(scratch_f, src.fp()); // f64 -> i32 round to zero.
985  vmov(dst.gp(), scratch_f);
986  // Check underflow and NaN.
987  DwVfpRegister scratch_d = temps.AcquireD();
988  vmov(scratch_d, Double(static_cast<double>(-1.0)));
989  VFPCompareAndSetFlags(src.fp(), scratch_d);
990  b(trap, le);
991  // Check overflow.
992  vmov(scratch_d, Double(static_cast<double>(UINT32_MAX + 1.0)));
993  VFPCompareAndSetFlags(src.fp(), scratch_d);
994  b(trap, ge);
995  return true;
996  }
997  case kExprI32ReinterpretF32:
998  BAILOUT("kExprI32ReinterpretF32");
999  return true;
1000  case kExprI64SConvertI32:
1001  if (dst.low_gp() != src.gp()) mov(dst.low_gp(), src.gp());
1002  mov(dst.high_gp(), Operand(src.gp(), ASR, 31));
1003  return true;
1004  case kExprI64UConvertI32:
1005  if (dst.low_gp() != src.gp()) mov(dst.low_gp(), src.gp());
1006  mov(dst.high_gp(), Operand(0));
1007  return true;
1008  case kExprI64ReinterpretF64:
1009  vmov(dst.low_gp(), dst.high_gp(), src.fp());
1010  return true;
1011  case kExprF32SConvertI32:
1012  BAILOUT("kExprF32SConvertI32");
1013  return true;
1014  case kExprF32UConvertI32:
1015  BAILOUT("kExprF32UConvertI32");
1016  return true;
1017  case kExprF32ConvertF64:
1018  BAILOUT("kExprF32ConvertF64");
1019  return true;
1020  case kExprF32ReinterpretI32:
1021  BAILOUT("kExprF32ReinterpretI32");
1022  return true;
1023  case kExprF64SConvertI32: {
1024  UseScratchRegisterScope temps(this);
1025  SwVfpRegister scratch = temps.AcquireS();
1026  vmov(scratch, src.gp());
1027  vcvt_f64_s32(dst.fp(), scratch);
1028  return true;
1029  }
1030  case kExprF64UConvertI32: {
1031  UseScratchRegisterScope temps(this);
1032  SwVfpRegister scratch = temps.AcquireS();
1033  vmov(scratch, src.gp());
1034  vcvt_f64_u32(dst.fp(), scratch);
1035  return true;
1036  }
1037  case kExprF64ConvertF32:
1038  BAILOUT("kExprF64ConvertF32");
1039  return true;
1040  case kExprF64ReinterpretI64:
1041  vmov(dst.fp(), src.low_gp(), src.high_gp());
1042  return true;
1043  case kExprF64SConvertI64:
1044  case kExprF64UConvertI64:
1045  case kExprI64SConvertF32:
1046  case kExprI64UConvertF32:
1047  case kExprF32SConvertI64:
1048  case kExprF32UConvertI64:
1049  case kExprI64SConvertF64:
1050  case kExprI64UConvertF64:
1051  // These cases can be handled by the C fallback function.
1052  return false;
1053  default:
1054  UNREACHABLE();
1055  }
1056 }
1057 
1058 void LiftoffAssembler::emit_i32_signextend_i8(Register dst, Register src) {
1059  sxtb(dst, src);
1060 }
1061 
1062 void LiftoffAssembler::emit_i32_signextend_i16(Register dst, Register src) {
1063  sxth(dst, src);
1064 }
1065 
1066 void LiftoffAssembler::emit_i64_signextend_i8(LiftoffRegister dst,
1067  LiftoffRegister src) {
1068  emit_i32_signextend_i8(dst.low_gp(), src.low_gp());
1069  mov(dst.high_gp(), Operand(dst.low_gp(), ASR, 31));
1070 }
1071 
1072 void LiftoffAssembler::emit_i64_signextend_i16(LiftoffRegister dst,
1073  LiftoffRegister src) {
1074  emit_i32_signextend_i16(dst.low_gp(), src.low_gp());
1075  mov(dst.high_gp(), Operand(dst.low_gp(), ASR, 31));
1076 }
1077 
1078 void LiftoffAssembler::emit_i64_signextend_i32(LiftoffRegister dst,
1079  LiftoffRegister src) {
1080  TurboAssembler::Move(dst.low_gp(), src.low_gp());
1081  mov(dst.high_gp(), Operand(src.low_gp(), ASR, 31));
1082 }
1083 
1084 void LiftoffAssembler::emit_jump(Label* label) { b(label); }
1085 
1086 void LiftoffAssembler::emit_jump(Register target) { bx(target); }
1087 
1088 void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label,
1089  ValueType type, Register lhs,
1090  Register rhs) {
1091  DCHECK_EQ(type, kWasmI32);
1092  if (rhs == no_reg) {
1093  cmp(lhs, Operand(0));
1094  } else {
1095  cmp(lhs, rhs);
1096  }
1097  b(label, cond);
1098 }
1099 
1100 void LiftoffAssembler::emit_i32_eqz(Register dst, Register src) {
1101  clz(dst, src);
1102  mov(dst, Operand(dst, LSR, kRegSizeInBitsLog2));
1103 }
1104 
1105 void LiftoffAssembler::emit_i32_set_cond(Condition cond, Register dst,
1106  Register lhs, Register rhs) {
1107  cmp(lhs, rhs);
1108  mov(dst, Operand(0), LeaveCC);
1109  mov(dst, Operand(1), LeaveCC, cond);
1110 }
1111 
1112 void LiftoffAssembler::emit_i64_eqz(Register dst, LiftoffRegister src) {
1113  orr(dst, src.low_gp(), src.high_gp());
1114  clz(dst, dst);
1115  mov(dst, Operand(dst, LSR, 5));
1116 }
1117 
1118 void LiftoffAssembler::emit_i64_set_cond(Condition cond, Register dst,
1119  LiftoffRegister lhs,
1120  LiftoffRegister rhs) {
1121  // For signed i64 comparisons, we still need to use unsigned comparison for
1122  // the low word (the only bit carrying signedness information is the MSB in
1123  // the high word).
1124  Condition unsigned_cond = liftoff::MakeUnsigned(cond);
1125  Label set_cond;
1126  Label cont;
1127  LiftoffRegister dest = LiftoffRegister(dst);
1128  bool speculative_move = !dest.overlaps(lhs) && !dest.overlaps(rhs);
1129  if (speculative_move) {
1130  mov(dst, Operand(0));
1131  }
1132  // Compare high word first. If it differs, use it for the set_cond. If it's
1133  // equal, compare the low word and use that for set_cond.
1134  cmp(lhs.high_gp(), rhs.high_gp());
1135  if (unsigned_cond == cond) {
1136  cmp(lhs.low_gp(), rhs.low_gp(), kEqual);
1137  if (!speculative_move) {
1138  mov(dst, Operand(0));
1139  }
1140  mov(dst, Operand(1), LeaveCC, cond);
1141  } else {
1142  // If the condition predicate for the low differs from that for the high
1143  // word, the conditional move instructions must be separated.
1144  b(ne, &set_cond);
1145  cmp(lhs.low_gp(), rhs.low_gp());
1146  if (!speculative_move) {
1147  mov(dst, Operand(0));
1148  }
1149  mov(dst, Operand(1), LeaveCC, unsigned_cond);
1150  b(&cont);
1151  bind(&set_cond);
1152  if (!speculative_move) {
1153  mov(dst, Operand(0));
1154  }
1155  mov(dst, Operand(1), LeaveCC, cond);
1156  bind(&cont);
1157  }
1158 }
1159 
1160 void LiftoffAssembler::emit_f32_set_cond(Condition cond, Register dst,
1161  DoubleRegister lhs,
1162  DoubleRegister rhs) {
1163  BAILOUT("emit_f32_set_cond");
1164 }
1165 
1166 void LiftoffAssembler::emit_f64_set_cond(Condition cond, Register dst,
1167  DoubleRegister lhs,
1168  DoubleRegister rhs) {
1169  VFPCompareAndSetFlags(lhs, rhs);
1170  mov(dst, Operand(0), LeaveCC);
1171  mov(dst, Operand(1), LeaveCC, cond);
1172  if (cond != ne) {
1173  // If V flag set, at least one of the arguments was a Nan -> false.
1174  mov(dst, Operand(0), LeaveCC, vs);
1175  }
1176 }
1177 
1178 void LiftoffAssembler::StackCheck(Label* ool_code, Register limit_address) {
1179  ldr(limit_address, MemOperand(limit_address));
1180  cmp(sp, limit_address);
1181  b(ool_code, ls);
1182 }
1183 
1184 void LiftoffAssembler::CallTrapCallbackForTesting() {
1185  PrepareCallCFunction(0, 0);
1186  CallCFunction(ExternalReference::wasm_call_trap_callback_for_testing(), 0);
1187 }
1188 
1189 void LiftoffAssembler::AssertUnreachable(AbortReason reason) {
1190  // Asserts unreachable within the wasm code.
1191  TurboAssembler::AssertUnreachable(reason);
1192 }
1193 
1194 void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {
1195  RegList core_regs = regs.GetGpList();
1196  if (core_regs != 0) {
1197  stm(db_w, sp, core_regs);
1198  }
1199  LiftoffRegList fp_regs = regs & kFpCacheRegList;
1200  while (!fp_regs.is_empty()) {
1201  LiftoffRegister reg = fp_regs.GetFirstRegSet();
1202  DoubleRegister first = reg.fp();
1203  DoubleRegister last = first;
1204  fp_regs.clear(reg);
1205  while (!fp_regs.is_empty()) {
1206  LiftoffRegister reg = fp_regs.GetFirstRegSet();
1207  int code = reg.fp().code();
1208  // vstm can not push more than 16 registers. We have to make sure the
1209  // condition is met.
1210  if ((code != last.code() + 1) || ((code - first.code() + 1) > 16)) break;
1211  last = reg.fp();
1212  fp_regs.clear(reg);
1213  }
1214  vstm(db_w, sp, first, last);
1215  }
1216 }
1217 
1218 void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {
1219  LiftoffRegList fp_regs = regs & kFpCacheRegList;
1220  while (!fp_regs.is_empty()) {
1221  LiftoffRegister reg = fp_regs.GetLastRegSet();
1222  DoubleRegister last = reg.fp();
1223  DoubleRegister first = last;
1224  fp_regs.clear(reg);
1225  while (!fp_regs.is_empty()) {
1226  LiftoffRegister reg = fp_regs.GetLastRegSet();
1227  int code = reg.fp().code();
1228  if ((code != first.code() - 1) || ((last.code() - code + 1) > 16)) break;
1229  first = reg.fp();
1230  fp_regs.clear(reg);
1231  }
1232  vldm(ia_w, sp, first, last);
1233  }
1234  RegList core_regs = regs.GetGpList();
1235  if (core_regs != 0) {
1236  ldm(ia_w, sp, core_regs);
1237  }
1238 }
1239 
1240 void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {
1241  Drop(num_stack_slots);
1242  Ret();
1243 }
1244 
1245 void LiftoffAssembler::CallC(wasm::FunctionSig* sig,
1246  const LiftoffRegister* args,
1247  const LiftoffRegister* rets,
1248  ValueType out_argument_type, int stack_bytes,
1249  ExternalReference ext_ref) {
1250  // Arguments are passed by pushing them all to the stack and then passing
1251  // a pointer to them.
1252  DCHECK_EQ(stack_bytes % kPointerSize, 0);
1253  // Reserve space in the stack.
1254  sub(sp, sp, Operand(stack_bytes));
1255 
1256  int arg_bytes = 0;
1257  for (ValueType param_type : sig->parameters()) {
1258  switch (param_type) {
1259  case kWasmI32:
1260  str(args->gp(), MemOperand(sp, arg_bytes));
1261  break;
1262  case kWasmI64:
1263  str(args->low_gp(), MemOperand(sp, arg_bytes));
1264  str(args->high_gp(), MemOperand(sp, arg_bytes + kRegisterSize));
1265  break;
1266  case kWasmF32:
1267  BAILOUT("Call C for f32 parameter");
1268  break;
1269  case kWasmF64:
1270  vstr(args->fp(), MemOperand(sp, arg_bytes));
1271  break;
1272  default:
1273  UNREACHABLE();
1274  }
1275  args++;
1276  arg_bytes += ValueTypes::MemSize(param_type);
1277  }
1278  DCHECK_LE(arg_bytes, stack_bytes);
1279 
1280  // Pass a pointer to the buffer with the arguments to the C function.
1281  mov(r0, sp);
1282 
1283  // Now call the C function.
1284  constexpr int kNumCCallArgs = 1;
1285  PrepareCallCFunction(kNumCCallArgs);
1286  CallCFunction(ext_ref, kNumCCallArgs);
1287 
1288  // Move return value to the right register.
1289  const LiftoffRegister* result_reg = rets;
1290  if (sig->return_count() > 0) {
1291  DCHECK_EQ(1, sig->return_count());
1292  constexpr Register kReturnReg = r0;
1293  if (kReturnReg != rets->gp()) {
1294  Move(*rets, LiftoffRegister(kReturnReg), sig->GetReturn(0));
1295  }
1296  result_reg++;
1297  }
1298 
1299  // Load potential output value from the buffer on the stack.
1300  if (out_argument_type != kWasmStmt) {
1301  switch (out_argument_type) {
1302  case kWasmI32:
1303  ldr(result_reg->gp(), MemOperand(sp));
1304  break;
1305  case kWasmI64:
1306  ldr(result_reg->low_gp(), MemOperand(sp));
1307  ldr(result_reg->high_gp(), MemOperand(sp, kPointerSize));
1308  break;
1309  case kWasmF32:
1310  BAILOUT("Call C for f32 parameter");
1311  break;
1312  case kWasmF64:
1313  vldr(result_reg->fp(), MemOperand(sp));
1314  break;
1315  default:
1316  UNREACHABLE();
1317  }
1318  }
1319  add(sp, sp, Operand(stack_bytes));
1320 }
1321 
1322 void LiftoffAssembler::CallNativeWasmCode(Address addr) {
1323  Call(addr, RelocInfo::WASM_CALL);
1324 }
1325 
1326 void LiftoffAssembler::CallIndirect(wasm::FunctionSig* sig,
1327  compiler::CallDescriptor* call_descriptor,
1328  Register target) {
1329  DCHECK(target != no_reg);
1330  Call(target);
1331 }
1332 
1333 void LiftoffAssembler::CallRuntimeStub(WasmCode::RuntimeStubId sid) {
1334  // A direct call to a wasm runtime stub defined in this module.
1335  // Just encode the stub index. This will be patched at relocation.
1336  Call(static_cast<Address>(sid), RelocInfo::WASM_STUB_CALL);
1337 }
1338 
1339 void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) {
1340  sub(sp, sp, Operand(size));
1341  mov(addr, sp);
1342 }
1343 
1344 void LiftoffAssembler::DeallocateStackSlot(uint32_t size) {
1345  add(sp, sp, Operand(size));
1346 }
1347 
1348 void LiftoffStackSlots::Construct() {
1349  for (auto& slot : slots_) {
1350  const LiftoffAssembler::VarState& src = slot.src_;
1351  switch (src.loc()) {
1352  case LiftoffAssembler::VarState::kStack: {
1353  switch (src.type()) {
1354  // i32 and i64 can be treated as similar cases, i64 being previously
1355  // split into two i32 registers
1356  case kWasmI32:
1357  case kWasmI64: {
1358  UseScratchRegisterScope temps(asm_);
1359  Register scratch = temps.Acquire();
1360  asm_->ldr(scratch,
1361  liftoff::GetHalfStackSlot(slot.src_index_, slot.half_));
1362  asm_->Push(scratch);
1363  } break;
1364  case kWasmF32:
1365  asm_->BAILOUT("Construct f32 from kStack");
1366  break;
1367  case kWasmF64: {
1368  UseScratchRegisterScope temps(asm_);
1369  DwVfpRegister scratch = temps.AcquireD();
1370  asm_->vldr(scratch, liftoff::GetStackSlot(slot.src_index_));
1371  asm_->vpush(scratch);
1372  } break;
1373  default:
1374  UNREACHABLE();
1375  }
1376  break;
1377  }
1378  case LiftoffAssembler::VarState::kRegister:
1379  switch (src.type()) {
1380  case kWasmI64: {
1381  LiftoffRegister reg =
1382  slot.half_ == kLowWord ? src.reg().low() : src.reg().high();
1383  asm_->push(reg.gp());
1384  } break;
1385  case kWasmI32:
1386  asm_->push(src.reg().gp());
1387  break;
1388  case kWasmF32:
1389  asm_->BAILOUT("Construct f32 from kRegister");
1390  break;
1391  case kWasmF64:
1392  asm_->vpush(src.reg().fp());
1393  break;
1394  default:
1395  UNREACHABLE();
1396  }
1397  break;
1398  case LiftoffAssembler::VarState::KIntConst: {
1399  DCHECK(src.type() == kWasmI32 || src.type() == kWasmI64);
1400  UseScratchRegisterScope temps(asm_);
1401  Register scratch = temps.Acquire();
1402  // The high word is the sign extension of the low word.
1403  asm_->mov(scratch,
1404  Operand(slot.half_ == kLowWord ? src.i32_const()
1405  : src.i32_const() >> 31));
1406  asm_->push(scratch);
1407  break;
1408  }
1409  }
1410  }
1411 }
1412 
1413 } // namespace wasm
1414 } // namespace internal
1415 } // namespace v8
1416 
1417 #undef BAILOUT
1418 
1419 #endif // V8_WASM_BASELINE_ARM_LIFTOFF_ASSEMBLER_ARM_H_
Definition: libplatform.h:13