blob: c24f548570647b09508ca14d951cff0ceea3a43b [file]
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WASM_BASELINE_S390_LIFTOFF_ASSEMBLER_S390_H_
#define V8_WASM_BASELINE_S390_LIFTOFF_ASSEMBLER_S390_H_
#include "src/base/v8-fallthrough.h"
#include "src/codegen/assembler.h"
#include "src/heap/memory-chunk.h"
#include "src/wasm/baseline/liftoff-assembler.h"
#include "src/wasm/simd-shuffle.h"
#include "src/wasm/wasm-objects.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace liftoff {
// half
// slot Frame
// -----+--------------------+---------------------------
// n+3 | parameter n |
// ... | ... |
// 4 | parameter 1 | or parameter 2
// 3 | parameter 0 | or parameter 1
// 2 | (result address) | or parameter 0
// -----+--------------------+---------------------------
// 1 | return addr (lr) |
// 0 | previous frame (fp)|
// -----+--------------------+ <-- frame ptr (fp)
// -1 | StackFrame::WASM |
// -2 | instance |
// -3 | feedback vector |
// -4 | tiering budget |
// -----+--------------------+---------------------------
// -5 | slot 0 (high) | ^
// -6 | slot 0 (low) | |
// -7 | slot 1 (high) | Frame slots
// -8 | slot 1 (low) | |
// | | v
// -----+--------------------+ <-- stack ptr (sp)
//
constexpr int32_t kInstanceOffset = 2 * kSystemPointerSize;
constexpr int kFeedbackVectorOffset = 3 * kSystemPointerSize;
inline MemOperand GetStackSlot(uint32_t offset) {
return MemOperand(fp, -offset);
}
inline MemOperand GetInstanceOperand() { return GetStackSlot(kInstanceOffset); }
} // namespace liftoff
int LiftoffAssembler::PrepareStackFrame() {
int offset = pc_offset();
lay(sp, MemOperand(sp));
return offset;
}
void LiftoffAssembler::CallFrameSetupStub(int declared_function_index) {
// TODO(jkummerow): Enable this check when we have C++20.
// static_assert(std::find(std::begin(wasm::kGpParamRegisters),
// std::end(wasm::kGpParamRegisters),
// kLiftoffFrameSetupFunctionReg) ==
// std::end(wasm::kGpParamRegisters));
// On ARM, we must push at least {lr} before calling the stub, otherwise
// it would get clobbered with no possibility to recover it.
Register scratch = ip;
mov(scratch, Operand(StackFrame::TypeToMarker(StackFrame::WASM)));
PushCommonFrame(scratch);
LoadConstant(LiftoffRegister(kLiftoffFrameSetupFunctionReg),
WasmValue(declared_function_index));
CallRuntimeStub(WasmCode::kWasmLiftoffFrameSetup);
}
void LiftoffAssembler::PrepareTailCall(int num_callee_stack_params,
int stack_param_delta) {
Register scratch = r1;
// Push the return address and frame pointer to complete the stack frame.
lay(sp, MemOperand(sp, -2 * kSystemPointerSize));
LoadU64(scratch, MemOperand(fp, kSystemPointerSize));
StoreU64(scratch, MemOperand(sp, kSystemPointerSize));
LoadU64(scratch, MemOperand(fp));
StoreU64(scratch, MemOperand(sp));
// Shift the whole frame upwards.
int slot_count = num_callee_stack_params + 2;
for (int i = slot_count - 1; i >= 0; --i) {
LoadU64(scratch, MemOperand(sp, i * kSystemPointerSize));
StoreU64(scratch,
MemOperand(fp, (i - stack_param_delta) * kSystemPointerSize));
}
// Set the new stack and frame pointer.
lay(sp, MemOperand(fp, -stack_param_delta * kSystemPointerSize));
Pop(r14, fp);
}
void LiftoffAssembler::AlignFrameSize() {}
void LiftoffAssembler::PatchPrepareStackFrame(
int offset, SafepointTableBuilder* safepoint_table_builder,
bool feedback_vector_slot) {
int frame_size = GetTotalFrameSize() - 2 * kSystemPointerSize;
// The frame setup builtin also pushes the feedback vector.
if (feedback_vector_slot) {
frame_size -= kSystemPointerSize;
}
constexpr int LayInstrSize = 6;
Assembler patching_assembler(
AssemblerOptions{},
ExternalAssemblerBuffer(buffer_start_ + offset, LayInstrSize + kGap));
if (V8_LIKELY(frame_size < 4 * KB)) {
patching_assembler.lay(sp, MemOperand(sp, -frame_size));
return;
}
// The frame size is bigger than 4KB, so we might overflow the available stack
// space if we first allocate the frame and then do the stack check (we will
// need some remaining stack space for throwing the exception). That's why we
// check the available stack space before we allocate the frame. To do this we
// replace the {__ sub(sp, sp, framesize)} with a jump to OOL code that does
// this "extended stack check".
//
// The OOL code can simply be generated here with the normal assembler,
// because all other code generation, including OOL code, has already finished
// when {PatchPrepareStackFrame} is called. The function prologue then jumps
// to the current {pc_offset()} to execute the OOL code for allocating the
// large frame.
// Emit the unconditional branch in the function prologue (from {offset} to
// {pc_offset()}).
int jump_offset = pc_offset() - offset;
patching_assembler.branchOnCond(al, jump_offset, true, true);
// If the frame is bigger than the stack, we throw the stack overflow
// exception unconditionally. Thereby we can avoid the integer overflow
// check in the condition code.
RecordComment("OOL: stack check for large frame");
Label continuation;
if (frame_size < v8_flags.stack_size * 1024) {
Register stack_limit = ip;
LoadU64(stack_limit,
FieldMemOperand(kWasmInstanceRegister,
WasmInstanceObject::kRealStackLimitAddressOffset),
r0);
LoadU64(stack_limit, MemOperand(stack_limit), r0);
AddU64(stack_limit, Operand(frame_size));
CmpU64(sp, stack_limit);
bge(&continuation);
}
Call(wasm::WasmCode::kWasmStackOverflow, RelocInfo::WASM_STUB_CALL);
// The call will not return; just define an empty safepoint.
safepoint_table_builder->DefineSafepoint(this);
if (v8_flags.debug_code) stop();
bind(&continuation);
// Now allocate the stack space. Note that this might do more than just
// decrementing the SP; consult {MacroAssembler::AllocateStackSpace}.
lay(sp, MemOperand(sp, -frame_size));
// Jump back to the start of the function, from {pc_offset()} to
// right after the reserved space for the {__ sub(sp, sp, framesize)} (which
// is a branch now).
jump_offset = offset - pc_offset() + 6;
branchOnCond(al, jump_offset, true);
}
void LiftoffAssembler::FinishCode() {}
void LiftoffAssembler::AbortCompilation() { AbortedCodeGeneration(); }
// static
constexpr int LiftoffAssembler::StaticStackFrameSize() {
return liftoff::kFeedbackVectorOffset;
}
int LiftoffAssembler::SlotSizeForType(ValueKind kind) {
switch (kind) {
case kS128:
return value_kind_size(kind);
default:
return kStackSlotSize;
}
}
bool LiftoffAssembler::NeedsAlignment(ValueKind kind) {
return (kind == kS128 || is_reference(kind));
}
void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value) {
switch (value.type().kind()) {
case kI32:
mov(reg.gp(), Operand(value.to_i32()));
break;
case kI64:
mov(reg.gp(), Operand(value.to_i64()));
break;
case kF32: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
LoadF32(reg.fp(), value.to_f32(), scratch);
break;
}
case kF64: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
LoadF64(reg.fp(), value.to_f64(), scratch);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::LoadInstanceFromFrame(Register dst) {
LoadU64(dst, liftoff::GetInstanceOperand());
}
void LiftoffAssembler::LoadFromInstance(Register dst, Register instance,
int offset, int size) {
DCHECK_LE(0, offset);
switch (size) {
case 1:
LoadU8(dst, MemOperand(instance, offset));
break;
case 4:
LoadU32(dst, MemOperand(instance, offset));
break;
case 8:
LoadU64(dst, MemOperand(instance, offset));
break;
default:
UNIMPLEMENTED();
}
}
void LiftoffAssembler::LoadTaggedPointerFromInstance(Register dst,
Register instance,
int offset) {
DCHECK_LE(0, offset);
LoadTaggedField(dst, MemOperand(instance, offset));
}
void LiftoffAssembler::SpillInstance(Register instance) {
StoreU64(instance, liftoff::GetInstanceOperand());
}
void LiftoffAssembler::ResetOSRTarget() {}
void LiftoffAssembler::LoadTaggedPointer(Register dst, Register src_addr,
Register offset_reg,
int32_t offset_imm, bool needs_shift) {
CHECK(is_int20(offset_imm));
unsigned shift_amount = !needs_shift ? 0 : COMPRESS_POINTERS_BOOL ? 2 : 3;
if (offset_reg != no_reg && shift_amount != 0) {
ShiftLeftU64(ip, offset_reg, Operand(shift_amount));
offset_reg = ip;
}
LoadTaggedField(
dst,
MemOperand(src_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
}
void LiftoffAssembler::LoadFullPointer(Register dst, Register src_addr,
int32_t offset_imm) {
UseScratchRegisterScope temps(this);
LoadU64(dst, MemOperand(src_addr, offset_imm), r1);
}
void LiftoffAssembler::StoreTaggedPointer(Register dst_addr,
Register offset_reg,
int32_t offset_imm,
LiftoffRegister src,
LiftoffRegList /* pinned */,
SkipWriteBarrier skip_write_barrier) {
MemOperand dst_op =
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm);
StoreTaggedField(src.gp(), dst_op);
if (skip_write_barrier || v8_flags.disable_write_barriers) return;
Label exit;
CheckPageFlag(dst_addr, r1, MemoryChunk::kPointersFromHereAreInterestingMask,
to_condition(kZero), &exit);
JumpIfSmi(src.gp(), &exit);
CheckPageFlag(src.gp(), r1, MemoryChunk::kPointersToHereAreInterestingMask,
eq, &exit);
lay(r1, dst_op);
CallRecordWriteStubSaveRegisters(dst_addr, r1, SaveFPRegsMode::kSave,
StubCallMode::kCallWasmRuntimeStub);
bind(&exit);
}
void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr,
Register offset_reg, uintptr_t offset_imm,
LoadType type, uint32_t* protected_load_pc,
bool is_load_mem, bool i64_offset,
bool needs_shift) {
UseScratchRegisterScope temps(this);
if (offset_reg != no_reg && !i64_offset) {
// Clear the upper 32 bits of the 64 bit offset register.
llgfr(ip, offset_reg);
offset_reg = ip;
}
unsigned shift_amount = needs_shift ? type.size_log_2() : 0;
if (offset_reg != no_reg && shift_amount != 0) {
ShiftLeftU64(ip, offset_reg, Operand(shift_amount));
offset_reg = ip;
}
if (!is_int20(offset_imm)) {
if (offset_reg != no_reg) {
mov(r0, Operand(offset_imm));
AddS64(r0, offset_reg);
mov(ip, r0);
} else {
mov(ip, Operand(offset_imm));
}
offset_reg = ip;
offset_imm = 0;
}
MemOperand src_op =
MemOperand(src_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm);
if (protected_load_pc) *protected_load_pc = pc_offset();
switch (type.value()) {
case LoadType::kI32Load8U:
case LoadType::kI64Load8U:
LoadU8(dst.gp(), src_op);
break;
case LoadType::kI32Load8S:
case LoadType::kI64Load8S:
LoadS8(dst.gp(), src_op);
break;
case LoadType::kI32Load16U:
case LoadType::kI64Load16U:
if (is_load_mem) {
LoadU16LE(dst.gp(), src_op);
} else {
LoadU16(dst.gp(), src_op);
}
break;
case LoadType::kI32Load16S:
case LoadType::kI64Load16S:
if (is_load_mem) {
LoadS16LE(dst.gp(), src_op);
} else {
LoadS16(dst.gp(), src_op);
}
break;
case LoadType::kI64Load32U:
if (is_load_mem) {
LoadU32LE(dst.gp(), src_op);
} else {
LoadU32(dst.gp(), src_op);
}
break;
case LoadType::kI32Load:
case LoadType::kI64Load32S:
if (is_load_mem) {
LoadS32LE(dst.gp(), src_op);
} else {
LoadS32(dst.gp(), src_op);
}
break;
case LoadType::kI64Load:
if (is_load_mem) {
LoadU64LE(dst.gp(), src_op);
} else {
LoadU64(dst.gp(), src_op);
}
break;
case LoadType::kF32Load:
if (is_load_mem) {
LoadF32LE(dst.fp(), src_op, r0);
} else {
LoadF32(dst.fp(), src_op);
}
break;
case LoadType::kF64Load:
if (is_load_mem) {
LoadF64LE(dst.fp(), src_op, r0);
} else {
LoadF64(dst.fp(), src_op);
}
break;
case LoadType::kS128Load:
if (is_load_mem) {
LoadV128LE(dst.fp(), src_op, r1, r0);
} else {
LoadV128(dst.fp(), src_op, r1);
}
break;
default:
UNREACHABLE();
}
}
#define PREP_MEM_OPERAND(offset_reg, offset_imm, scratch) \
if (offset_reg != no_reg && !i64_offset) { \
/* Clear the upper 32 bits of the 64 bit offset register.*/ \
llgfr(scratch, offset_reg); \
offset_reg = scratch; \
} \
if (!is_int20(offset_imm)) { \
if (offset_reg != no_reg) { \
mov(r0, Operand(offset_imm)); \
AddS64(r0, offset_reg); \
mov(scratch, r0); \
} else { \
mov(scratch, Operand(offset_imm)); \
} \
offset_reg = scratch; \
offset_imm = 0; \
}
void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister src,
StoreType type, LiftoffRegList /* pinned */,
uint32_t* protected_store_pc, bool is_store_mem,
bool i64_offset) {
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
MemOperand dst_op =
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm);
if (protected_store_pc) *protected_store_pc = pc_offset();
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8:
StoreU8(src.gp(), dst_op);
break;
case StoreType::kI32Store16:
case StoreType::kI64Store16:
if (is_store_mem) {
StoreU16LE(src.gp(), dst_op, r1);
} else {
StoreU16(src.gp(), dst_op, r1);
}
break;
case StoreType::kI32Store:
case StoreType::kI64Store32:
if (is_store_mem) {
StoreU32LE(src.gp(), dst_op, r1);
} else {
StoreU32(src.gp(), dst_op, r1);
}
break;
case StoreType::kI64Store:
if (is_store_mem) {
StoreU64LE(src.gp(), dst_op, r1);
} else {
StoreU64(src.gp(), dst_op, r1);
}
break;
case StoreType::kF32Store:
if (is_store_mem) {
StoreF32LE(src.fp(), dst_op, r1);
} else {
StoreF32(src.fp(), dst_op);
}
break;
case StoreType::kF64Store:
if (is_store_mem) {
StoreF64LE(src.fp(), dst_op, r1);
} else {
StoreF64(src.fp(), dst_op);
}
break;
case StoreType::kS128Store: {
if (is_store_mem) {
StoreV128LE(src.fp(), dst_op, r1, r0);
} else {
StoreV128(src.fp(), dst_op, r1);
}
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicLoad(LiftoffRegister dst, Register src_addr,
Register offset_reg, uintptr_t offset_imm,
LoadType type, LiftoffRegList /* pinned */,
bool i64_offset) {
Load(dst, src_addr, offset_reg, offset_imm, type, nullptr, true, i64_offset);
}
void LiftoffAssembler::AtomicStore(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister src,
StoreType type, LiftoffRegList /* pinned */,
bool i64_offset) {
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
AtomicExchangeU8(ip, src.gp(), r1, r0);
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r1, src.gp());
ShiftRightU32(r1, r1, Operand(16));
#else
LoadU16(r1, src.gp());
#endif
Push(r2);
AtomicExchangeU16(ip, r1, r2, r0);
Pop(r2);
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r1, src.gp());
#else
LoadU32(r1, src.gp());
#endif
Label do_cs;
bind(&do_cs);
cs(r0, r1, MemOperand(ip));
bne(&do_cs, Label::kNear);
break;
}
case StoreType::kI64Store: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(r1, src.gp());
#else
mov(r1, src.gp());
#endif
Label do_cs;
bind(&do_cs);
csg(r0, r1, MemOperand(ip));
bne(&do_cs, Label::kNear);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicAdd(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
Register tmp1 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result})
.gp();
Register tmp2 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result, tmp1})
.gp();
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
Label doadd;
bind(&doadd);
LoadU8(tmp1, MemOperand(ip));
AddS32(tmp2, tmp1, value.gp());
AtomicCmpExchangeU8(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &doadd);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Label doadd;
bind(&doadd);
LoadU16(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
ShiftRightU32(tmp2, tmp2, Operand(16));
AddS32(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
ShiftRightU32(tmp2, tmp2, Operand(16));
#else
AddS32(tmp2, tmp1, value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &doadd);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Label doadd;
bind(&doadd);
LoadU32(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
AddS32(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
#else
AddS32(tmp2, tmp1, value.gp());
#endif
CmpAndSwap(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &doadd);
LoadU32(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI64Store: {
Label doadd;
bind(&doadd);
LoadU64(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(tmp2, tmp1);
AddS64(tmp2, tmp2, value.gp());
lrvgr(tmp2, tmp2);
#else
AddS64(tmp2, tmp1, value.gp());
#endif
CmpAndSwap64(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &doadd);
mov(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicSub(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
Register tmp1 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result})
.gp();
Register tmp2 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result, tmp1})
.gp();
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
Label do_again;
bind(&do_again);
LoadU8(tmp1, MemOperand(ip));
SubS32(tmp2, tmp1, value.gp());
AtomicCmpExchangeU8(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Label do_again;
bind(&do_again);
LoadU16(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
ShiftRightU32(tmp2, tmp2, Operand(16));
SubS32(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
ShiftRightU32(tmp2, tmp2, Operand(16));
#else
SubS32(tmp2, tmp1, value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Label do_again;
bind(&do_again);
LoadU32(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
SubS32(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
#else
SubS32(tmp2, tmp1, value.gp());
#endif
CmpAndSwap(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
LoadU32(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI64Store: {
Label do_again;
bind(&do_again);
LoadU64(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(tmp2, tmp1);
SubS64(tmp2, tmp2, value.gp());
lrvgr(tmp2, tmp2);
#else
SubS64(tmp2, tmp1, value.gp());
#endif
CmpAndSwap64(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
mov(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicAnd(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
Register tmp1 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result})
.gp();
Register tmp2 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result, tmp1})
.gp();
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
Label do_again;
bind(&do_again);
LoadU8(tmp1, MemOperand(ip));
AndP(tmp2, tmp1, value.gp());
AtomicCmpExchangeU8(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Label do_again;
bind(&do_again);
LoadU16(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
ShiftRightU32(tmp2, tmp2, Operand(16));
AndP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
ShiftRightU32(tmp2, tmp2, Operand(16));
#else
AndP(tmp2, tmp1, value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Label do_again;
bind(&do_again);
LoadU32(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
AndP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
#else
AndP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
LoadU32(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI64Store: {
Label do_again;
bind(&do_again);
LoadU64(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(tmp2, tmp1);
AndP(tmp2, tmp2, value.gp());
lrvgr(tmp2, tmp2);
#else
AndP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap64(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
mov(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicOr(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
Register tmp1 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result})
.gp();
Register tmp2 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result, tmp1})
.gp();
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
Label do_again;
bind(&do_again);
LoadU8(tmp1, MemOperand(ip));
OrP(tmp2, tmp1, value.gp());
AtomicCmpExchangeU8(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Label do_again;
bind(&do_again);
LoadU16(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
ShiftRightU32(tmp2, tmp2, Operand(16));
OrP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
ShiftRightU32(tmp2, tmp2, Operand(16));
#else
OrP(tmp2, tmp1, value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Label do_again;
bind(&do_again);
LoadU32(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
OrP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
#else
OrP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
LoadU32(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI64Store: {
Label do_again;
bind(&do_again);
LoadU64(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(tmp2, tmp1);
OrP(tmp2, tmp2, value.gp());
lrvgr(tmp2, tmp2);
#else
OrP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap64(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
mov(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicXor(Register dst_addr, Register offset_reg,
uintptr_t offset_imm, LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
Register tmp1 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result})
.gp();
Register tmp2 = GetUnusedRegister(kGpReg, LiftoffRegList{dst_addr, offset_reg,
value, result, tmp1})
.gp();
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
Label do_again;
bind(&do_again);
LoadU8(tmp1, MemOperand(ip));
XorP(tmp2, tmp1, value.gp());
AtomicCmpExchangeU8(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Label do_again;
bind(&do_again);
LoadU16(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
ShiftRightU32(tmp2, tmp2, Operand(16));
XorP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
ShiftRightU32(tmp2, tmp2, Operand(16));
#else
XorP(tmp2, tmp1, value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), tmp1, tmp2, r0, r1);
b(Condition(4), &do_again);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Label do_again;
bind(&do_again);
LoadU32(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(tmp2, tmp1);
XorP(tmp2, tmp2, value.gp());
lrvr(tmp2, tmp2);
#else
XorP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
LoadU32(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI64Store: {
Label do_again;
bind(&do_again);
LoadU64(tmp1, MemOperand(ip));
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(tmp2, tmp1);
XorP(tmp2, tmp2, value.gp());
lrvgr(tmp2, tmp2);
#else
XorP(tmp2, tmp1, value.gp());
#endif
CmpAndSwap64(tmp1, tmp2, MemOperand(ip));
b(Condition(4), &do_again);
mov(result.gp(), tmp1);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicExchange(Register dst_addr, Register offset_reg,
uintptr_t offset_imm,
LiftoffRegister value,
LiftoffRegister result, StoreType type,
bool i64_offset) {
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
AtomicExchangeU8(ip, value.gp(), result.gp(), r0);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r1, value.gp());
ShiftRightU32(r1, r1, Operand(16));
#else
LoadU16(r1, value.gp());
#endif
AtomicExchangeU16(ip, r1, result.gp(), r0);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#else
LoadU16(result.gp(), result.gp());
#endif
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r1, value.gp());
#else
LoadU32(r1, value.gp());
#endif
Label do_cs;
bind(&do_cs);
cs(result.gp(), r1, MemOperand(ip));
bne(&do_cs, Label::kNear);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
LoadU32(result.gp(), result.gp());
break;
}
case StoreType::kI64Store: {
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(r1, value.gp());
#else
mov(r1, value.gp());
#endif
Label do_cs;
bind(&do_cs);
csg(result.gp(), r1, MemOperand(ip));
bne(&do_cs, Label::kNear);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicCompareExchange(
Register dst_addr, Register offset_reg, uintptr_t offset_imm,
LiftoffRegister expected, LiftoffRegister new_value, LiftoffRegister result,
StoreType type, bool i64_offset) {
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
lay(ip,
MemOperand(dst_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm));
switch (type.value()) {
case StoreType::kI32Store8:
case StoreType::kI64Store8: {
AtomicCmpExchangeU8(ip, result.gp(), expected.gp(), new_value.gp(), r0,
r1);
LoadU8(result.gp(), result.gp());
break;
}
case StoreType::kI32Store16:
case StoreType::kI64Store16: {
Push(r2, r3);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r2, expected.gp());
lrvr(r3, new_value.gp());
ShiftRightU32(r2, r2, Operand(16));
ShiftRightU32(r3, r3, Operand(16));
#else
LoadU16(r2, expected.gp());
LoadU16(r3, new_value.gp());
#endif
AtomicCmpExchangeU16(ip, result.gp(), r2, r3, r0, r1);
LoadU16(result.gp(), result.gp());
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
ShiftRightU32(result.gp(), result.gp(), Operand(16));
#endif
Pop(r2, r3);
break;
}
case StoreType::kI32Store:
case StoreType::kI64Store32: {
Push(r2, r3);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(r2, expected.gp());
lrvr(r3, new_value.gp());
#else
LoadU32(r2, expected.gp());
LoadU32(r3, new_value.gp());
#endif
CmpAndSwap(r2, r3, MemOperand(ip));
LoadU32(result.gp(), r2);
#ifdef V8_TARGET_BIG_ENDIAN
lrvr(result.gp(), result.gp());
#endif
Pop(r2, r3);
break;
}
case StoreType::kI64Store: {
Push(r2, r3);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(r2, expected.gp());
lrvgr(r3, new_value.gp());
#else
mov(r2, expected.gp());
mov(r3, new_value.gp());
#endif
CmpAndSwap64(r2, r3, MemOperand(ip));
mov(result.gp(), r2);
#ifdef V8_TARGET_BIG_ENDIAN
lrvgr(result.gp(), result.gp());
#endif
Pop(r2, r3);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::AtomicFence() { bailout(kAtomics, "AtomicFence"); }
void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
uint32_t caller_slot_idx,
ValueKind kind) {
int32_t offset = (caller_slot_idx + 1) * 8;
switch (kind) {
case kI32: {
#if defined(V8_TARGET_BIG_ENDIAN)
LoadS32(dst.gp(), MemOperand(fp, offset + 4));
break;
#else
LoadS32(dst.gp(), MemOperand(fp, offset));
break;
#endif
}
case kRef:
case kRtt:
case kRefNull:
case kI64: {
LoadU64(dst.gp(), MemOperand(fp, offset));
break;
}
case kF32: {
LoadF32(dst.fp(), MemOperand(fp, offset));
break;
}
case kF64: {
LoadF64(dst.fp(), MemOperand(fp, offset));
break;
}
case kS128: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
LoadV128(dst.fp(), MemOperand(fp, offset), scratch);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::StoreCallerFrameSlot(LiftoffRegister src,
uint32_t caller_slot_idx,
ValueKind kind) {
int32_t offset = (caller_slot_idx + 1) * 8;
switch (kind) {
case kI32: {
#if defined(V8_TARGET_BIG_ENDIAN)
StoreU32(src.gp(), MemOperand(fp, offset + 4));
break;
#else
StoreU32(src.gp(), MemOperand(fp, offset));
break;
#endif
}
case kRef:
case kRtt:
case kRefNull:
case kI64: {
StoreU64(src.gp(), MemOperand(fp, offset));
break;
}
case kF32: {
StoreF32(src.fp(), MemOperand(fp, offset));
break;
}
case kF64: {
StoreF64(src.fp(), MemOperand(fp, offset));
break;
}
case kS128: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
StoreV128(src.fp(), MemOperand(fp, offset), scratch);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::LoadReturnStackSlot(LiftoffRegister dst, int offset,
ValueKind kind) {
switch (kind) {
case kI32: {
#if defined(V8_TARGET_BIG_ENDIAN)
LoadS32(dst.gp(), MemOperand(sp, offset + 4));
break;
#else
LoadS32(dst.gp(), MemOperand(sp, offset));
break;
#endif
}
case kRef:
case kRtt:
case kRefNull:
case kI64: {
LoadU64(dst.gp(), MemOperand(sp, offset));
break;
}
case kF32: {
LoadF32(dst.fp(), MemOperand(sp, offset));
break;
}
case kF64: {
LoadF64(dst.fp(), MemOperand(sp, offset));
break;
}
case kS128: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
LoadV128(dst.fp(), MemOperand(sp, offset), scratch);
break;
}
default:
UNREACHABLE();
}
}
#ifdef V8_TARGET_BIG_ENDIAN
constexpr int stack_bias = -4;
#else
constexpr int stack_bias = 0;
#endif
void LiftoffAssembler::MoveStackValue(uint32_t dst_offset, uint32_t src_offset,
ValueKind kind) {
DCHECK_NE(dst_offset, src_offset);
int length = 0;
switch (kind) {
case kI32:
case kF32:
length = 4;
break;
case kI64:
case kRefNull:
case kRef:
case kRtt:
case kF64:
length = 8;
break;
case kS128:
length = 16;
break;
default:
UNREACHABLE();
}
dst_offset += (length == 4 ? stack_bias : 0);
src_offset += (length == 4 ? stack_bias : 0);
if (is_int20(dst_offset)) {
lay(ip, liftoff::GetStackSlot(dst_offset));
} else {
mov(ip, Operand(-dst_offset));
lay(ip, MemOperand(fp, ip));
}
if (is_int20(src_offset)) {
lay(r1, liftoff::GetStackSlot(src_offset));
} else {
mov(r1, Operand(-src_offset));
lay(r1, MemOperand(fp, r1));
}
MoveChar(MemOperand(ip), MemOperand(r1), Operand(length));
}
void LiftoffAssembler::Move(Register dst, Register src, ValueKind kind) {
mov(dst, src);
}
void LiftoffAssembler::Move(DoubleRegister dst, DoubleRegister src,
ValueKind kind) {
DCHECK_NE(dst, src);
if (kind == kF32) {
ler(dst, src);
} else if (kind == kF64) {
ldr(dst, src);
} else {
DCHECK_EQ(kS128, kind);
vlr(dst, src, Condition(0), Condition(0), Condition(0));
}
}
void LiftoffAssembler::Spill(int offset, LiftoffRegister reg, ValueKind kind) {
DCHECK_LT(0, offset);
RecordUsedSpillOffset(offset);
switch (kind) {
case kI32:
StoreU32(reg.gp(), liftoff::GetStackSlot(offset + stack_bias));
break;
case kI64:
case kRefNull:
case kRef:
case kRtt:
StoreU64(reg.gp(), liftoff::GetStackSlot(offset));
break;
case kF32:
StoreF32(reg.fp(), liftoff::GetStackSlot(offset + stack_bias));
break;
case kF64:
StoreF64(reg.fp(), liftoff::GetStackSlot(offset));
break;
case kS128: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
StoreV128(reg.fp(), liftoff::GetStackSlot(offset), scratch);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::Spill(int offset, WasmValue value) {
RecordUsedSpillOffset(offset);
UseScratchRegisterScope temps(this);
Register src = no_reg;
src = ip;
switch (value.type().kind()) {
case kI32: {
mov(src, Operand(value.to_i32()));
StoreU32(src, liftoff::GetStackSlot(offset + stack_bias));
break;
}
case kI64: {
mov(src, Operand(value.to_i64()));
StoreU64(src, liftoff::GetStackSlot(offset));
break;
}
default:
// We do not track f32 and f64 constants, hence they are unreachable.
UNREACHABLE();
}
}
void LiftoffAssembler::Fill(LiftoffRegister reg, int offset, ValueKind kind) {
switch (kind) {
case kI32:
LoadS32(reg.gp(), liftoff::GetStackSlot(offset + stack_bias));
break;
case kI64:
case kRef:
case kRefNull:
case kRtt:
LoadU64(reg.gp(), liftoff::GetStackSlot(offset));
break;
case kF32:
LoadF32(reg.fp(), liftoff::GetStackSlot(offset + stack_bias));
break;
case kF64:
LoadF64(reg.fp(), liftoff::GetStackSlot(offset));
break;
case kS128: {
UseScratchRegisterScope temps(this);
Register scratch = temps.Acquire();
LoadV128(reg.fp(), liftoff::GetStackSlot(offset), scratch);
break;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::FillI64Half(Register, int offset, RegPairHalf) {
UNREACHABLE();
}
void LiftoffAssembler::FillStackSlotsWithZero(int start, int size) {
DCHECK_LT(0, size);
DCHECK_EQ(0, size % 4);
RecordUsedSpillOffset(start + size);
// We need a zero reg. Always use r0 for that, and push it before to restore
// its value afterwards.
push(r0);
mov(r0, Operand(0));
if (size <= 5 * kStackSlotSize) {
// Special straight-line code for up to five slots. Generates two
// instructions per slot.
uint32_t remainder = size;
for (; remainder >= kStackSlotSize; remainder -= kStackSlotSize) {
StoreU64(r0, liftoff::GetStackSlot(start + remainder));
}
DCHECK(remainder == 4 || remainder == 0);
if (remainder) {
StoreU32(r0, liftoff::GetStackSlot(start + remainder));
}
} else {
// General case for bigger counts (9 instructions).
// Use r3 for start address (inclusive), r4 for end address (exclusive).
push(r3);
push(r4);
lay(r3, MemOperand(fp, -start - size));
lay(r4, MemOperand(fp, -start));
Label loop;
bind(&loop);
StoreU64(r0, MemOperand(r3));
lay(r3, MemOperand(r3, kSystemPointerSize));
CmpU64(r3, r4);
bne(&loop);
pop(r4);
pop(r3);
}
pop(r0);
}
void LiftoffAssembler::LoadSpillAddress(Register dst, int offset,
ValueKind kind) {
if (kind == kI32) offset = offset + stack_bias;
SubS64(dst, fp, Operand(offset));
}
#define SIGN_EXT(r) lgfr(r, r)
#define INT32_AND_WITH_1F(x) Operand(x & 0x1f)
#define REGISTER_AND_WITH_1F \
([&](Register rhs) { \
AndP(r1, rhs, Operand(31)); \
return r1; \
})
#define LFR_TO_REG(reg) reg.gp()
// V(name, instr, dtype, stype, dcast, scast, rcast, return_val, return_type)
#define UNOP_LIST(V) \
V(i32_popcnt, Popcnt32, Register, Register, , , USE, true, bool) \
V(i64_popcnt, Popcnt64, LiftoffRegister, LiftoffRegister, LFR_TO_REG, \
LFR_TO_REG, USE, true, bool) \
V(u32_to_uintptr, LoadU32, Register, Register, , , USE, , void) \
V(i32_signextend_i8, lbr, Register, Register, , , USE, , void) \
V(i32_signextend_i16, lhr, Register, Register, , , USE, , void) \
V(i64_signextend_i8, lgbr, LiftoffRegister, LiftoffRegister, LFR_TO_REG, \
LFR_TO_REG, USE, , void) \
V(i64_signextend_i16, lghr, LiftoffRegister, LiftoffRegister, LFR_TO_REG, \
LFR_TO_REG, USE, , void) \
V(i64_signextend_i32, LoadS32, LiftoffRegister, LiftoffRegister, LFR_TO_REG, \
LFR_TO_REG, USE, , void) \
V(i32_clz, CountLeadingZerosU32, Register, Register, , , USE, , void) \
V(i32_ctz, CountTrailingZerosU32, Register, Register, , , USE, , void) \
V(i64_clz, CountLeadingZerosU64, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_ctz, CountTrailingZerosU64, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(f32_ceil, CeilF32, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f32_floor, FloorF32, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f32_trunc, TruncF32, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f32_nearest_int, NearestIntF32, DoubleRegister, DoubleRegister, , , USE, \
true, bool) \
V(f32_abs, lpebr, DoubleRegister, DoubleRegister, , , USE, , void) \
V(f32_neg, lcebr, DoubleRegister, DoubleRegister, , , USE, , void) \
V(f32_sqrt, sqebr, DoubleRegister, DoubleRegister, , , USE, , void) \
V(f64_ceil, CeilF64, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f64_floor, FloorF64, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f64_trunc, TruncF64, DoubleRegister, DoubleRegister, , , USE, true, bool) \
V(f64_nearest_int, NearestIntF64, DoubleRegister, DoubleRegister, , , USE, \
true, bool) \
V(f64_abs, lpdbr, DoubleRegister, DoubleRegister, , , USE, , void) \
V(f64_neg, lcdbr, DoubleRegister, DoubleRegister, , , USE, , void) \
V(f64_sqrt, sqdbr, DoubleRegister, DoubleRegister, , , USE, , void)
#define EMIT_UNOP_FUNCTION(name, instr, dtype, stype, dcast, scast, rcast, \
ret, return_type) \
return_type LiftoffAssembler::emit_##name(dtype dst, stype src) { \
auto _dst = dcast(dst); \
auto _src = scast(src); \
instr(_dst, _src); \
rcast(_dst); \
return ret; \
}
UNOP_LIST(EMIT_UNOP_FUNCTION)
#undef EMIT_UNOP_FUNCTION
#undef UNOP_LIST
// V(name, instr, dtype, stype1, stype2, dcast, scast1, scast2, rcast,
// return_val, return_type)
#define BINOP_LIST(V) \
V(f32_min, FloatMin, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f32_max, FloatMax, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_min, DoubleMin, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_max, DoubleMax, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_add, AddF64, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_sub, SubF64, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_mul, MulF64, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f64_div, DivF64, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f32_add, AddF32, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f32_sub, SubF32, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f32_mul, MulF32, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(f32_div, DivF32, DoubleRegister, DoubleRegister, DoubleRegister, , , , \
USE, , void) \
V(i32_shli, ShiftLeftU32, Register, Register, int32_t, , , \
INT32_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_sari, ShiftRightS32, Register, Register, int32_t, , , \
INT32_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_shri, ShiftRightU32, Register, Register, int32_t, , , \
INT32_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_shl, ShiftLeftU32, Register, Register, Register, , , \
REGISTER_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_sar, ShiftRightS32, Register, Register, Register, , , \
REGISTER_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_shr, ShiftRightU32, Register, Register, Register, , , \
REGISTER_AND_WITH_1F, SIGN_EXT, , void) \
V(i32_addi, AddS32, Register, Register, int32_t, , , Operand, SIGN_EXT, , \
void) \
V(i32_subi, SubS32, Register, Register, int32_t, , , Operand, SIGN_EXT, , \
void) \
V(i32_andi, And, Register, Register, int32_t, , , Operand, SIGN_EXT, , void) \
V(i32_ori, Or, Register, Register, int32_t, , , Operand, SIGN_EXT, , void) \
V(i32_xori, Xor, Register, Register, int32_t, , , Operand, SIGN_EXT, , void) \
V(i32_add, AddS32, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i32_sub, SubS32, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i32_and, And, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i32_or, Or, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i32_xor, Xor, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i32_mul, MulS32, Register, Register, Register, , , , SIGN_EXT, , void) \
V(i64_add, AddS64, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_sub, SubS64, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_mul, MulS64, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_and, AndP, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_or, OrP, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_xor, XorP, LiftoffRegister, LiftoffRegister, LiftoffRegister, \
LFR_TO_REG, LFR_TO_REG, LFR_TO_REG, USE, , void) \
V(i64_shl, ShiftLeftU64, LiftoffRegister, LiftoffRegister, Register, \
LFR_TO_REG, LFR_TO_REG, , USE, , void) \
V(i64_sar, ShiftRightS64, LiftoffRegister, LiftoffRegister, Register, \
LFR_TO_REG, LFR_TO_REG, , USE, , void) \
V(i64_shr, ShiftRightU64, LiftoffRegister, LiftoffRegister, Register, \
LFR_TO_REG, LFR_TO_REG, , USE, , void) \
V(i64_addi, AddS64, LiftoffRegister, LiftoffRegister, int64_t, LFR_TO_REG, \
LFR_TO_REG, Operand, USE, , void) \
V(i64_andi, AndP, LiftoffRegister, LiftoffRegister, int32_t, LFR_TO_REG, \
LFR_TO_REG, Operand, USE, , void) \
V(i64_ori, OrP, LiftoffRegister, LiftoffRegister, int32_t, LFR_TO_REG, \
LFR_TO_REG, Operand, USE, , void) \
V(i64_xori, XorP, LiftoffRegister, LiftoffRegister, int32_t, LFR_TO_REG, \
LFR_TO_REG, Operand, USE, , void) \
V(i64_shli, ShiftLeftU64, LiftoffRegister, LiftoffRegister, int32_t, \
LFR_TO_REG, LFR_TO_REG, Operand, USE, , void) \
V(i64_sari, ShiftRightS64, LiftoffRegister, LiftoffRegister, int32_t, \
LFR_TO_REG, LFR_TO_REG, Operand, USE, , void) \
V(i64_shri, ShiftRightU64, LiftoffRegister, LiftoffRegister, int32_t, \
LFR_TO_REG, LFR_TO_REG, Operand, USE, , void)
#define EMIT_BINOP_FUNCTION(name, instr, dtype, stype1, stype2, dcast, scast1, \
scast2, rcast, ret, return_type) \
return_type LiftoffAssembler::emit_##name(dtype dst, stype1 lhs, \
stype2 rhs) { \
auto _dst = dcast(dst); \
auto _lhs = scast1(lhs); \
auto _rhs = scast2(rhs); \
instr(_dst, _lhs, _rhs); \
rcast(_dst); \
return ret; \
}
BINOP_LIST(EMIT_BINOP_FUNCTION)
#undef BINOP_LIST
#undef EMIT_BINOP_FUNCTION
#undef SIGN_EXT
#undef INT32_AND_WITH_1F
#undef REGISTER_AND_WITH_1F
#undef LFR_TO_REG
void LiftoffAssembler::IncrementSmi(LiftoffRegister dst, int offset) {
UseScratchRegisterScope temps(this);
if (COMPRESS_POINTERS_BOOL) {
DCHECK(SmiValuesAre31Bits());
Register scratch = temps.Acquire();
LoadS32(scratch, MemOperand(dst.gp(), offset));
AddU32(scratch, Operand(Smi::FromInt(1)));
StoreU32(scratch, MemOperand(dst.gp(), offset));
} else {
Register scratch = temps.Acquire();
SmiUntag(scratch, MemOperand(dst.gp(), offset));
AddU64(scratch, Operand(1));
SmiTag(scratch);
StoreU64(scratch, MemOperand(dst.gp(), offset));
}
}
void LiftoffAssembler::emit_i32_divs(Register dst, Register lhs, Register rhs,
Label* trap_div_by_zero,
Label* trap_div_unrepresentable) {
Label cont;
// Check for division by zero.
ltr(r0, rhs);
b(eq, trap_div_by_zero);
// Check for kMinInt / -1. This is unrepresentable.
CmpS32(rhs, Operand(-1));
bne(&cont);
CmpS32(lhs, Operand(kMinInt));
b(eq, trap_div_unrepresentable);
bind(&cont);
DivS32(dst, lhs, rhs);
}
void LiftoffAssembler::emit_i32_divu(Register dst, Register lhs, Register rhs,
Label* trap_div_by_zero) {
// Check for division by zero.
ltr(r0, rhs);
beq(trap_div_by_zero);
DivU32(dst, lhs, rhs);
}
void LiftoffAssembler::emit_i32_rems(Register dst, Register lhs, Register rhs,
Label* trap_div_by_zero) {
Label cont;
Label done;
Label trap_div_unrepresentable;
// Check for division by zero.
ltr(r0, rhs);
beq(trap_div_by_zero);
// Check kMinInt/-1 case.
CmpS32(rhs, Operand(-1));
bne(&cont);
CmpS32(lhs, Operand(kMinInt));
beq(&trap_div_unrepresentable);
// Continue noraml calculation.
bind(&cont);
ModS32(dst, lhs, rhs);
bne(&done);
// trap by kMinInt/-1 case.
bind(&trap_div_unrepresentable);
mov(dst, Operand(0));
bind(&done);
}
void LiftoffAssembler::emit_i32_remu(Register dst, Register lhs, Register rhs,
Label* trap_div_by_zero) {
// Check for division by zero.
ltr(r0, rhs);
beq(trap_div_by_zero);
ModU32(dst, lhs, rhs);
}
bool LiftoffAssembler::emit_i64_divs(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs,
Label* trap_div_by_zero,
Label* trap_div_unrepresentable) {
// Use r0 to check for kMinInt / -1.
constexpr int64_t kMinInt64 = static_cast<int64_t>(1) << 63;
Label cont;
// Check for division by zero.
ltgr(r0, rhs.gp());
beq(trap_div_by_zero);
// Check for kMinInt / -1. This is unrepresentable.
CmpS64(rhs.gp(), Operand(-1));
bne(&cont);
mov(r0, Operand(kMinInt64));
CmpS64(lhs.gp(), r0);
b(eq, trap_div_unrepresentable);
bind(&cont);
DivS64(dst.gp(), lhs.gp(), rhs.gp());
return true;
}
bool LiftoffAssembler::emit_i64_divu(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs,
Label* trap_div_by_zero) {
ltgr(r0, rhs.gp());
b(eq, trap_div_by_zero);
// Do div.
DivU64(dst.gp(), lhs.gp(), rhs.gp());
return true;
}
bool LiftoffAssembler::emit_i64_rems(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs,
Label* trap_div_by_zero) {
constexpr int64_t kMinInt64 = static_cast<int64_t>(1) << 63;
Label trap_div_unrepresentable;
Label done;
Label cont;
// Check for division by zero.
ltgr(r0, rhs.gp());
beq(trap_div_by_zero);
// Check for kMinInt / -1. This is unrepresentable.
CmpS64(rhs.gp(), Operand(-1));
bne(&cont);
mov(r0, Operand(kMinInt64));
CmpS64(lhs.gp(), r0);
beq(&trap_div_unrepresentable);
bind(&cont);
ModS64(dst.gp(), lhs.gp(), rhs.gp());
bne(&done);
bind(&trap_div_unrepresentable);
mov(dst.gp(), Operand(0));
bind(&done);
return true;
}
bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs,
Label* trap_div_by_zero) {
// Check for division by zero.
ltgr(r0, rhs.gp());
beq(trap_div_by_zero);
ModU64(dst.gp(), lhs.gp(), rhs.gp());
return true;
}
void LiftoffAssembler::emit_f32_copysign(DoubleRegister dst, DoubleRegister lhs,
DoubleRegister rhs) {
constexpr uint64_t kF64SignBit = uint64_t{1} << 63;
UseScratchRegisterScope temps(this);
Register scratch2 = temps.Acquire();
MovDoubleToInt64(r0, lhs);
// Clear sign bit in {r0}.
AndP(r0, Operand(~kF64SignBit));
MovDoubleToInt64(scratch2, rhs);
// Isolate sign bit in {scratch2}.
AndP(scratch2, Operand(kF64SignBit));
// Combine {scratch2} into {r0}.
OrP(r0, r0, scratch2);
MovInt64ToDouble(dst, r0);
}
void LiftoffAssembler::emit_f64_copysign(DoubleRegister dst, DoubleRegister lhs,
DoubleRegister rhs) {
constexpr uint64_t kF64SignBit = uint64_t{1} << 63;
UseScratchRegisterScope temps(this);
Register scratch2 = temps.Acquire();
MovDoubleToInt64(r0, lhs);
// Clear sign bit in {r0}.
AndP(r0, Operand(~kF64SignBit));
MovDoubleToInt64(scratch2, rhs);
// Isolate sign bit in {scratch2}.
AndP(scratch2, Operand(kF64SignBit));
// Combine {scratch2} into {r0}.
OrP(r0, r0, scratch2);
MovInt64ToDouble(dst, r0);
}
bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode,
LiftoffRegister dst,
LiftoffRegister src, Label* trap) {
switch (opcode) {
case kExprI32ConvertI64:
lgfr(dst.gp(), src.gp());
return true;
case kExprI32SConvertF32: {
ConvertFloat32ToInt32(dst.gp(), src.fp(),
kRoundToZero); // f32 -> i32 round to zero.
b(Condition(1), trap);
return true;
}
case kExprI32UConvertF32: {
ConvertFloat32ToUnsignedInt32(dst.gp(), src.fp(), kRoundToZero);
b(Condition(1), trap);
return true;
}
case kExprI32SConvertF64: {
ConvertDoubleToInt32(dst.gp(), src.fp());
b(Condition(1), trap);
return true;
}
case kExprI32UConvertF64: {
ConvertDoubleToUnsignedInt32(dst.gp(), src.fp(), kRoundToZero);
b(Condition(1), trap);
return true;
}
case kExprI32SConvertSatF32: {
Label done, src_is_nan;
lzer(kScratchDoubleReg);
cebr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
// source is a finite number
ConvertFloat32ToInt32(dst.gp(), src.fp(),
kRoundToZero); // f32 -> i32 round to zero.
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI32UConvertSatF32: {
Label done, src_is_nan;
lzer(kScratchDoubleReg);
cebr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
// source is a finite number
ConvertFloat32ToUnsignedInt32(dst.gp(), src.fp(), kRoundToZero);
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI32SConvertSatF64: {
Label done, src_is_nan;
lzdr(kScratchDoubleReg, r0);
cdbr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
ConvertDoubleToInt32(dst.gp(), src.fp());
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI32UConvertSatF64: {
Label done, src_is_nan;
lzdr(kScratchDoubleReg, r0);
cdbr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
ConvertDoubleToUnsignedInt32(dst.gp(), src.fp());
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI32ReinterpretF32:
lgdr(dst.gp(), src.fp());
srlg(dst.gp(), dst.gp(), Operand(32));
return true;
case kExprI64SConvertI32:
LoadS32(dst.gp(), src.gp());
return true;
case kExprI64UConvertI32:
llgfr(dst.gp(), src.gp());
return true;
case kExprI64ReinterpretF64:
lgdr(dst.gp(), src.fp());
return true;
case kExprF32SConvertI32: {
ConvertIntToFloat(dst.fp(), src.gp());
return true;
}
case kExprF32UConvertI32: {
ConvertUnsignedIntToFloat(dst.fp(), src.gp());
return true;
}
case kExprF32ConvertF64:
ledbr(dst.fp(), src.fp());
return true;
case kExprF32ReinterpretI32: {
sllg(r0, src.gp(), Operand(32));
ldgr(dst.fp(), r0);
return true;
}
case kExprF64SConvertI32: {
ConvertIntToDouble(dst.fp(), src.gp());
return true;
}
case kExprF64UConvertI32: {
ConvertUnsignedIntToDouble(dst.fp(), src.gp());
return true;
}
case kExprF64ConvertF32:
ldebr(dst.fp(), src.fp());
return true;
case kExprF64ReinterpretI64:
ldgr(dst.fp(), src.gp());
return true;
case kExprF64SConvertI64:
ConvertInt64ToDouble(dst.fp(), src.gp());
return true;
case kExprF64UConvertI64:
ConvertUnsignedInt64ToDouble(dst.fp(), src.gp());
return true;
case kExprI64SConvertF32: {
ConvertFloat32ToInt64(dst.gp(), src.fp()); // f32 -> i64 round to zero.
b(Condition(1), trap);
return true;
}
case kExprI64UConvertF32: {
ConvertFloat32ToUnsignedInt64(dst.gp(),
src.fp()); // f32 -> i64 round to zero.
b(Condition(1), trap);
return true;
}
case kExprF32SConvertI64:
ConvertInt64ToFloat(dst.fp(), src.gp());
return true;
case kExprF32UConvertI64:
ConvertUnsignedInt64ToFloat(dst.fp(), src.gp());
return true;
case kExprI64SConvertF64: {
ConvertDoubleToInt64(dst.gp(), src.fp()); // f64 -> i64 round to zero.
b(Condition(1), trap);
return true;
}
case kExprI64UConvertF64: {
ConvertDoubleToUnsignedInt64(dst.gp(),
src.fp()); // f64 -> i64 round to zero.
b(Condition(1), trap);
return true;
}
case kExprI64SConvertSatF32: {
Label done, src_is_nan;
lzer(kScratchDoubleReg);
cebr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
// source is a finite number
ConvertFloat32ToInt64(dst.gp(), src.fp()); // f32 -> i64 round to zero.
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI64UConvertSatF32: {
Label done, src_is_nan;
lzer(kScratchDoubleReg);
cebr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
// source is a finite number
ConvertFloat32ToUnsignedInt64(dst.gp(),
src.fp()); // f32 -> i64 round to zero.
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI64SConvertSatF64: {
Label done, src_is_nan;
lzdr(kScratchDoubleReg, r0);
cdbr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
ConvertDoubleToInt64(dst.gp(), src.fp()); // f64 -> i64 round to zero.
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
case kExprI64UConvertSatF64: {
Label done, src_is_nan;
lzdr(kScratchDoubleReg, r0);
cdbr(src.fp(), kScratchDoubleReg);
b(Condition(1), &src_is_nan);
ConvertDoubleToUnsignedInt64(dst.gp(),
src.fp()); // f64 -> i64 round to zero.
b(&done);
bind(&src_is_nan);
lghi(dst.gp(), Operand::Zero());
bind(&done);
return true;
}
default:
UNREACHABLE();
}
}
void LiftoffAssembler::emit_jump(Label* label) { b(al, label); }
void LiftoffAssembler::emit_jump(Register target) { Jump(target); }
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label,
ValueKind kind, Register lhs,
Register rhs,
const FreezeCacheState& frozen) {
bool use_signed = is_signed(cond);
if (rhs != no_reg) {
switch (kind) {
case kI32:
if (use_signed) {
CmpS32(lhs, rhs);
} else {
CmpU32(lhs, rhs);
}
break;
case kRef:
case kRefNull:
case kRtt:
DCHECK(cond == kEqual || cond == kNotEqual);
#if defined(V8_COMPRESS_POINTERS)
if (use_signed) {
CmpS32(lhs, rhs);
} else {
CmpU32(lhs, rhs);
}
#else
if (use_signed) {
CmpS64(lhs, rhs);
} else {
CmpU64(lhs, rhs);
}
#endif
break;
case kI64:
if (use_signed) {
CmpS64(lhs, rhs);
} else {
CmpU64(lhs, rhs);
}
break;
default:
UNREACHABLE();
}
} else {
DCHECK_EQ(kind, kI32);
CHECK(use_signed);
CmpS32(lhs, Operand::Zero());
}
b(to_condition(cond), label);
}
void LiftoffAssembler::emit_i32_cond_jumpi(Condition cond, Label* label,
Register lhs, int32_t imm,
const FreezeCacheState& frozen) {
bool use_signed = is_signed(cond);
if (use_signed) {
CmpS32(lhs, Operand(imm));
} else {
CmpU32(lhs, Operand(imm));
}
b(to_condition(cond), label);
}
#define EMIT_EQZ(test, src) \
{ \
Label done; \
test(r0, src); \
mov(dst, Operand(1)); \
beq(&done); \
mov(dst, Operand(0)); \
bind(&done); \
}
void LiftoffAssembler::emit_i32_subi_jump_negative(
Register value, int subtrahend, Label* result_negative,
const FreezeCacheState& frozen) {
SubS64(value, value, Operand(subtrahend));
blt(result_negative);
}
void LiftoffAssembler::emit_i32_eqz(Register dst, Register src) {
EMIT_EQZ(ltr, src);
}
#define EMIT_SET_CONDITION(dst, cond) \
{ \
Label done; \
lghi(dst, Operand(1)); \
b(cond, &done); \
lghi(dst, Operand(0)); \
bind(&done); \
}
void LiftoffAssembler::emit_i32_set_cond(Condition cond, Register dst,
Register lhs, Register rhs) {
bool use_signed = is_signed(cond);
if (use_signed) {
CmpS32(lhs, rhs);
} else {
CmpU32(lhs, rhs);
}
EMIT_SET_CONDITION(dst, to_condition(cond));
}
void LiftoffAssembler::emit_i64_eqz(Register dst, LiftoffRegister src) {
EMIT_EQZ(ltgr, src.gp());
}
void LiftoffAssembler::emit_i64_set_cond(Condition cond, Register dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
bool use_signed = is_signed(cond);
if (use_signed) {
CmpS64(lhs.gp(), rhs.gp());
} else {
CmpU64(lhs.gp(), rhs.gp());
}
EMIT_SET_CONDITION(dst, to_condition(cond));
}
void LiftoffAssembler::emit_f32_set_cond(Condition cond, Register dst,
DoubleRegister lhs,
DoubleRegister rhs) {
cebr(lhs, rhs);
EMIT_SET_CONDITION(dst, to_condition(cond));
}
void LiftoffAssembler::emit_f64_set_cond(Condition cond, Register dst,
DoubleRegister lhs,
DoubleRegister rhs) {
cdbr(lhs, rhs);
EMIT_SET_CONDITION(dst, to_condition(cond));
}
bool LiftoffAssembler::emit_select(LiftoffRegister dst, Register condition,
LiftoffRegister true_value,
LiftoffRegister false_value,
ValueKind kind) {
return false;
}
void LiftoffAssembler::emit_smi_check(Register obj, Label* target,
SmiCheckMode mode,
const FreezeCacheState& frozen) {
TestIfSmi(obj);
Condition condition = mode == kJumpOnSmi ? eq : ne;
b(condition, target); // branch if SMI
}
#define SIMD_BINOP_RR_LIST(V) \
V(f64x2_add, F64x2Add) \
V(f64x2_sub, F64x2Sub) \
V(f64x2_mul, F64x2Mul) \
V(f64x2_div, F64x2Div) \
V(f64x2_min, F64x2Min) \
V(f64x2_max, F64x2Max) \
V(f64x2_eq, F64x2Eq) \
V(f64x2_ne, F64x2Ne) \
V(f64x2_lt, F64x2Lt) \
V(f64x2_le, F64x2Le) \
V(f64x2_pmin, F64x2Pmin) \
V(f64x2_pmax, F64x2Pmax) \
V(f32x4_add, F32x4Add) \
V(f32x4_sub, F32x4Sub) \
V(f32x4_mul, F32x4Mul) \
V(f32x4_div, F32x4Div) \
V(f32x4_min, F32x4Min) \
V(f32x4_max, F32x4Max) \
V(f32x4_eq, F32x4Eq) \
V(f32x4_ne, F32x4Ne) \
V(f32x4_lt, F32x4Lt) \
V(f32x4_le, F32x4Le) \
V(f32x4_pmin, F32x4Pmin) \
V(f32x4_pmax, F32x4Pmax) \
V(i64x2_add, I64x2Add) \
V(i64x2_sub, I64x2Sub) \
V(i64x2_eq, I64x2Eq) \
V(i64x2_ne, I64x2Ne) \
V(i64x2_gt_s, I64x2GtS) \
V(i64x2_ge_s, I64x2GeS) \
V(i32x4_add, I32x4Add) \
V(i32x4_sub, I32x4Sub) \
V(i32x4_mul, I32x4Mul) \
V(i32x4_eq, I32x4Eq) \
V(i32x4_ne, I32x4Ne) \
V(i32x4_gt_s, I32x4GtS) \
V(i32x4_ge_s, I32x4GeS) \
V(i32x4_gt_u, I32x4GtU) \
V(i32x4_min_s, I32x4MinS) \
V(i32x4_min_u, I32x4MinU) \
V(i32x4_max_s, I32x4MaxS) \
V(i32x4_max_u, I32x4MaxU) \
V(i16x8_add, I16x8Add) \
V(i16x8_sub, I16x8Sub) \
V(i16x8_mul, I16x8Mul) \
V(i16x8_eq, I16x8Eq) \
V(i16x8_ne, I16x8Ne) \
V(i16x8_gt_s, I16x8GtS) \
V(i16x8_ge_s, I16x8GeS) \
V(i16x8_gt_u, I16x8GtU) \
V(i16x8_min_s, I16x8MinS) \
V(i16x8_min_u, I16x8MinU) \
V(i16x8_max_s, I16x8MaxS) \
V(i16x8_max_u, I16x8MaxU) \
V(i16x8_rounding_average_u, I16x8RoundingAverageU) \
V(i8x16_add, I8x16Add) \
V(i8x16_sub, I8x16Sub) \
V(i8x16_eq, I8x16Eq) \
V(i8x16_ne, I8x16Ne) \
V(i8x16_gt_s, I8x16GtS) \
V(i8x16_ge_s, I8x16GeS) \
V(i8x16_gt_u, I8x16GtU) \
V(i8x16_min_s, I8x16MinS) \
V(i8x16_min_u, I8x16MinU) \
V(i8x16_max_s, I8x16MaxS) \
V(i8x16_max_u, I8x16MaxU) \
V(i8x16_rounding_average_u, I8x16RoundingAverageU) \
V(s128_and, S128And) \
V(s128_or, S128Or) \
V(s128_xor, S128Xor) \
V(s128_and_not, S128AndNot)
#define EMIT_SIMD_BINOP_RR(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister lhs, \
LiftoffRegister rhs) { \
op(dst.fp(), lhs.fp(), rhs.fp()); \
}
SIMD_BINOP_RR_LIST(EMIT_SIMD_BINOP_RR)
#undef EMIT_SIMD_BINOP_RR
#undef SIMD_BINOP_RR_LIST
#define SIMD_SHIFT_RR_LIST(V) \
V(i64x2_shl, I64x2Shl) \
V(i64x2_shr_s, I64x2ShrS) \
V(i64x2_shr_u, I64x2ShrU) \
V(i32x4_shl, I32x4Shl) \
V(i32x4_shr_s, I32x4ShrS) \
V(i32x4_shr_u, I32x4ShrU) \
V(i16x8_shl, I16x8Shl) \
V(i16x8_shr_s, I16x8ShrS) \
V(i16x8_shr_u, I16x8ShrU) \
V(i8x16_shl, I8x16Shl) \
V(i8x16_shr_s, I8x16ShrS) \
V(i8x16_shr_u, I8x16ShrU)
#define EMIT_SIMD_SHIFT_RR(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister lhs, \
LiftoffRegister rhs) { \
op(dst.fp(), lhs.fp(), rhs.gp(), kScratchDoubleReg); \
}
SIMD_SHIFT_RR_LIST(EMIT_SIMD_SHIFT_RR)
#undef EMIT_SIMD_SHIFT_RR
#undef SIMD_SHIFT_RR_LIST
#define SIMD_SHIFT_RI_LIST(V) \
V(i64x2_shli, I64x2Shl) \
V(i64x2_shri_s, I64x2ShrS) \
V(i64x2_shri_u, I64x2ShrU) \
V(i32x4_shli, I32x4Shl) \
V(i32x4_shri_s, I32x4ShrS) \
V(i32x4_shri_u, I32x4ShrU) \
V(i16x8_shli, I16x8Shl) \
V(i16x8_shri_s, I16x8ShrS) \
V(i16x8_shri_u, I16x8ShrU) \
V(i8x16_shli, I8x16Shl) \
V(i8x16_shri_s, I8x16ShrS) \
V(i8x16_shri_u, I8x16ShrU)
#define EMIT_SIMD_SHIFT_RI(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister lhs, \
int32_t rhs) { \
op(dst.fp(), lhs.fp(), Operand(rhs), r0, kScratchDoubleReg); \
}
SIMD_SHIFT_RI_LIST(EMIT_SIMD_SHIFT_RI)
#undef EMIT_SIMD_SHIFT_RI
#undef SIMD_SHIFT_RI_LIST
#define SIMD_UNOP_LIST(V) \
V(f64x2_splat, F64x2Splat, fp, fp, , void) \
V(f64x2_abs, F64x2Abs, fp, fp, , void) \
V(f64x2_neg, F64x2Neg, fp, fp, , void) \
V(f64x2_sqrt, F64x2Sqrt, fp, fp, , void) \
V(f64x2_ceil, F64x2Ceil, fp, fp, true, bool) \
V(f64x2_floor, F64x2Floor, fp, fp, true, bool) \
V(f64x2_trunc, F64x2Trunc, fp, fp, true, bool) \
V(f64x2_nearest_int, F64x2NearestInt, fp, fp, true, bool) \
V(f32x4_abs, F32x4Abs, fp, fp, , void) \
V(f32x4_splat, F32x4Splat, fp, fp, , void) \
V(f32x4_neg, F32x4Neg, fp, fp, , void) \
V(f32x4_sqrt, F32x4Sqrt, fp, fp, , void) \
V(f32x4_ceil, F32x4Ceil, fp, fp, true, bool) \
V(f32x4_floor, F32x4Floor, fp, fp, true, bool) \
V(f32x4_trunc, F32x4Trunc, fp, fp, true, bool) \
V(f32x4_nearest_int, F32x4NearestInt, fp, fp, true, bool) \
V(i64x2_abs, I64x2Abs, fp, fp, , void) \
V(i64x2_splat, I64x2Splat, fp, gp, , void) \
V(i64x2_neg, I64x2Neg, fp, fp, , void) \
V(i64x2_sconvert_i32x4_low, I64x2SConvertI32x4Low, fp, fp, , void) \
V(i64x2_sconvert_i32x4_high, I64x2SConvertI32x4High, fp, fp, , void) \
V(i64x2_uconvert_i32x4_low, I64x2UConvertI32x4Low, fp, fp, , void) \
V(i64x2_uconvert_i32x4_high, I64x2UConvertI32x4High, fp, fp, , void) \
V(i32x4_abs, I32x4Abs, fp, fp, , void) \
V(i32x4_neg, I32x4Neg, fp, fp, , void) \
V(i32x4_splat, I32x4Splat, fp, gp, , void) \
V(i32x4_sconvert_i16x8_low, I32x4SConvertI16x8Low, fp, fp, , void) \
V(i32x4_sconvert_i16x8_high, I32x4SConvertI16x8High, fp, fp, , void) \
V(i32x4_uconvert_i16x8_low, I32x4UConvertI16x8Low, fp, fp, , void) \
V(i32x4_uconvert_i16x8_high, I32x4UConvertI16x8High, fp, fp, , void) \
V(i16x8_abs, I16x8Abs, fp, fp, , void) \
V(i16x8_neg, I16x8Neg, fp, fp, , void) \
V(i16x8_splat, I16x8Splat, fp, gp, , void) \
V(i16x8_sconvert_i8x16_low, I16x8SConvertI8x16Low, fp, fp, , void) \
V(i16x8_sconvert_i8x16_high, I16x8SConvertI8x16High, fp, fp, , void) \
V(i16x8_uconvert_i8x16_low, I16x8UConvertI8x16Low, fp, fp, , void) \
V(i16x8_uconvert_i8x16_high, I16x8UConvertI8x16High, fp, fp, , void) \
V(i8x16_abs, I8x16Abs, fp, fp, , void) \
V(i8x16_neg, I8x16Neg, fp, fp, , void) \
V(i8x16_splat, I8x16Splat, fp, gp, , void) \
V(s128_not, S128Not, fp, fp, , void)
#define EMIT_SIMD_UNOP(name, op, dtype, stype, return_val, return_type) \
return_type LiftoffAssembler::emit_##name(LiftoffRegister dst, \
LiftoffRegister src) { \
op(dst.dtype(), src.stype()); \
return return_val; \
}
SIMD_UNOP_LIST(EMIT_SIMD_UNOP)
#undef EMIT_SIMD_UNOP
#undef SIMD_UNOP_LIST
#define SIMD_EXTRACT_LANE_LIST(V) \
V(f64x2_extract_lane, F64x2ExtractLane, fp) \
V(f32x4_extract_lane, F32x4ExtractLane, fp) \
V(i64x2_extract_lane, I64x2ExtractLane, gp) \
V(i32x4_extract_lane, I32x4ExtractLane, gp) \
V(i16x8_extract_lane_u, I16x8ExtractLaneU, gp) \
V(i16x8_extract_lane_s, I16x8ExtractLaneS, gp) \
V(i8x16_extract_lane_u, I8x16ExtractLaneU, gp) \
V(i8x16_extract_lane_s, I8x16ExtractLaneS, gp)
#define EMIT_SIMD_EXTRACT_LANE(name, op, dtype) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister src, \
uint8_t imm_lane_idx) { \
op(dst.dtype(), src.fp(), imm_lane_idx, r0); \
}
SIMD_EXTRACT_LANE_LIST(EMIT_SIMD_EXTRACT_LANE)
#undef EMIT_SIMD_EXTRACT_LANE
#undef SIMD_EXTRACT_LANE_LIST
#define SIMD_REPLACE_LANE_LIST(V) \
V(f64x2_replace_lane, F64x2ReplaceLane, fp) \
V(f32x4_replace_lane, F32x4ReplaceLane, fp) \
V(i64x2_replace_lane, I64x2ReplaceLane, gp) \
V(i32x4_replace_lane, I32x4ReplaceLane, gp) \
V(i16x8_replace_lane, I16x8ReplaceLane, gp) \
V(i8x16_replace_lane, I8x16ReplaceLane, gp)
#define EMIT_SIMD_REPLACE_LANE(name, op, stype) \
void LiftoffAssembler::emit_##name( \
LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, \
uint8_t imm_lane_idx) { \
op(dst.fp(), src1.fp(), src2.stype(), imm_lane_idx, r0); \
}
SIMD_REPLACE_LANE_LIST(EMIT_SIMD_REPLACE_LANE)
#undef EMIT_SIMD_REPLACE_LANE
#undef SIMD_REPLACE_LANE_LIST
#define SIMD_EXT_MUL_LIST(V) \
V(i64x2_extmul_low_i32x4_s, I64x2ExtMulLowI32x4S) \
V(i64x2_extmul_low_i32x4_u, I64x2ExtMulLowI32x4U) \
V(i64x2_extmul_high_i32x4_s, I64x2ExtMulHighI32x4S) \
V(i64x2_extmul_high_i32x4_u, I64x2ExtMulHighI32x4U) \
V(i32x4_extmul_low_i16x8_s, I32x4ExtMulLowI16x8S) \
V(i32x4_extmul_low_i16x8_u, I32x4ExtMulLowI16x8U) \
V(i32x4_extmul_high_i16x8_s, I32x4ExtMulHighI16x8S) \
V(i32x4_extmul_high_i16x8_u, I32x4ExtMulHighI16x8U) \
V(i16x8_extmul_low_i8x16_s, I16x8ExtMulLowI8x16S) \
V(i16x8_extmul_low_i8x16_u, I16x8ExtMulLowI8x16U) \
V(i16x8_extmul_high_i8x16_s, I16x8ExtMulHighI8x16S) \
V(i16x8_extmul_high_i8x16_u, I16x8ExtMulHighI8x16U)
#define EMIT_SIMD_EXT_MUL(name, op) \
void LiftoffAssembler::emit_##name( \
LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2) { \
op(dst.fp(), src1.fp(), src2.fp(), kScratchDoubleReg); \
}
SIMD_EXT_MUL_LIST(EMIT_SIMD_EXT_MUL)
#undef EMIT_SIMD_EXT_MUL
#undef SIMD_EXT_MUL_LIST
#define SIMD_ALL_TRUE_LIST(V) \
V(i64x2_alltrue, I64x2AllTrue) \
V(i32x4_alltrue, I32x4AllTrue) \
V(i16x8_alltrue, I16x8AllTrue) \
V(i8x16_alltrue, I8x16AllTrue)
#define EMIT_SIMD_ALL_TRUE(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, \
LiftoffRegister src) { \
op(dst.gp(), src.fp(), r0, kScratchDoubleReg); \
}
SIMD_ALL_TRUE_LIST(EMIT_SIMD_ALL_TRUE)
#undef EMIT_SIMD_ALL_TRUE
#undef SIMD_ALL_TRUE_LIST
#define SIMD_ADD_SUB_SAT_LIST(V) \
V(i16x8_add_sat_s, I16x8AddSatS) \
V(i16x8_sub_sat_s, I16x8SubSatS) \
V(i16x8_add_sat_u, I16x8AddSatU) \
V(i16x8_sub_sat_u, I16x8SubSatU) \
V(i8x16_add_sat_s, I8x16AddSatS) \
V(i8x16_sub_sat_s, I8x16SubSatS) \
V(i8x16_add_sat_u, I8x16AddSatU) \
V(i8x16_sub_sat_u, I8x16SubSatU)
#define EMIT_SIMD_ADD_SUB_SAT(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister lhs, \
LiftoffRegister rhs) { \
Simd128Register src1 = lhs.fp(); \
Simd128Register src2 = rhs.fp(); \
Simd128Register dest = dst.fp(); \
/* lhs and rhs are unique based on their selection under liftoff-compiler \
* `EmitBinOp`. */ \
/* Make sure dst and temp are also unique. */ \
if (dest == src1 || dest == src2) { \
dest = GetUnusedRegister(kFpReg, LiftoffRegList{src1, src2}).fp(); \
} \
Simd128Register temp = \
GetUnusedRegister(kFpReg, LiftoffRegList{dest, src1, src2}).fp(); \
op(dest, src1, src2, kScratchDoubleReg, temp); \
/* Original dst register needs to be populated. */ \
if (dest != dst.fp()) { \
vlr(dst.fp(), dest, Condition(0), Condition(0), Condition(0)); \
} \
}
SIMD_ADD_SUB_SAT_LIST(EMIT_SIMD_ADD_SUB_SAT)
#undef EMIT_SIMD_ADD_SUB_SAT
#undef SIMD_ADD_SUB_SAT_LIST
#define SIMD_EXT_ADD_PAIRWISE_LIST(V) \
V(i32x4_extadd_pairwise_i16x8_s, I32x4ExtAddPairwiseI16x8S) \
V(i32x4_extadd_pairwise_i16x8_u, I32x4ExtAddPairwiseI16x8U) \
V(i16x8_extadd_pairwise_i8x16_s, I16x8ExtAddPairwiseI8x16S) \
V(i16x8_extadd_pairwise_i8x16_u, I16x8ExtAddPairwiseI8x16U)
#define EMIT_SIMD_EXT_ADD_PAIRWISE(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, \
LiftoffRegister src) { \
Simd128Register src1 = src.fp(); \
Simd128Register dest = dst.fp(); \
/* Make sure dst and temp are unique. */ \
if (dest == src1) { \
dest = GetUnusedRegister(kFpReg, LiftoffRegList{src1}).fp(); \
} \
Simd128Register temp = \
GetUnusedRegister(kFpReg, LiftoffRegList{dest, src1}).fp(); \
op(dest, src1, kScratchDoubleReg, temp); \
if (dest != dst.fp()) { \
vlr(dst.fp(), dest, Condition(0), Condition(0), Condition(0)); \
} \
}
SIMD_EXT_ADD_PAIRWISE_LIST(EMIT_SIMD_EXT_ADD_PAIRWISE)
#undef EMIT_SIMD_EXT_ADD_PAIRWISE
#undef SIMD_EXT_ADD_PAIRWISE_LIST
#define SIMD_QFM_LIST(V) \
V(f64x2_qfma, F64x2Qfma) \
V(f64x2_qfms, F64x2Qfms) \
V(f32x4_qfma, F32x4Qfma) \
V(f32x4_qfms, F32x4Qfms)
#define EMIT_SIMD_QFM(name, op) \
void LiftoffAssembler::emit_##name( \
LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, \
LiftoffRegister src3) { \
op(dst.fp(), src1.fp(), src2.fp(), src3.fp()); \
}
SIMD_QFM_LIST(EMIT_SIMD_QFM)
#undef EMIT_SIMD_QFM
#undef SIMD_QFM_LIST
#define SIMD_RELAXED_BINOP_LIST(V) \
V(i8x16_relaxed_swizzle, i8x16_swizzle) \
V(f64x2_relaxed_min, f64x2_pmin) \
V(f64x2_relaxed_max, f64x2_pmax) \
V(f32x4_relaxed_min, f32x4_pmin) \
V(f32x4_relaxed_max, f32x4_pmax) \
V(i16x8_relaxed_q15mulr_s, i16x8_q15mulr_sat_s)
#define SIMD_VISIT_RELAXED_BINOP(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, LiftoffRegister lhs, \
LiftoffRegister rhs) { \
emit_##op(dst, lhs, rhs); \
}
SIMD_RELAXED_BINOP_LIST(SIMD_VISIT_RELAXED_BINOP)
#undef SIMD_VISIT_RELAXED_BINOP
#undef SIMD_RELAXED_BINOP_LIST
#define SIMD_RELAXED_UNOP_LIST(V) \
V(i32x4_relaxed_trunc_f32x4_s, i32x4_sconvert_f32x4) \
V(i32x4_relaxed_trunc_f32x4_u, i32x4_uconvert_f32x4) \
V(i32x4_relaxed_trunc_f64x2_s_zero, i32x4_trunc_sat_f64x2_s_zero) \
V(i32x4_relaxed_trunc_f64x2_u_zero, i32x4_trunc_sat_f64x2_u_zero)
#define SIMD_VISIT_RELAXED_UNOP(name, op) \
void LiftoffAssembler::emit_##name(LiftoffRegister dst, \
LiftoffRegister src) { \
emit_##op(dst, src); \
}
SIMD_RELAXED_UNOP_LIST(SIMD_VISIT_RELAXED_UNOP)
#undef SIMD_VISIT_RELAXED_UNOP
#undef SIMD_RELAXED_UNOP_LIST
void LiftoffAssembler::LoadTransform(LiftoffRegister dst, Register src_addr,
Register offset_reg, uintptr_t offset_imm,
LoadType type,
LoadTransformationKind transform,
uint32_t* protected_load_pc) {
if (!is_int20(offset_imm)) {
mov(ip, Operand(offset_imm));
if (offset_reg != no_reg) {
AddS64(ip, offset_reg);
}
offset_reg = ip;
offset_imm = 0;
}
MemOperand src_op =
MemOperand(src_addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm);
*protected_load_pc = pc_offset();
MachineType memtype = type.mem_type();
if (transform == LoadTransformationKind::kExtend) {
if (memtype == MachineType::Int8()) {
LoadAndExtend8x8SLE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Uint8()) {
LoadAndExtend8x8ULE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Int16()) {
LoadAndExtend16x4SLE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Uint16()) {
LoadAndExtend16x4ULE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Int32()) {
LoadAndExtend32x2SLE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Uint32()) {
LoadAndExtend32x2ULE(dst.fp(), src_op, r1);
}
} else if (transform == LoadTransformationKind::kZeroExtend) {
if (memtype == MachineType::Int32()) {
LoadV32ZeroLE(dst.fp(), src_op, r1);
} else {
DCHECK_EQ(MachineType::Int64(), memtype);
LoadV64ZeroLE(dst.fp(), src_op, r1);
}
} else {
DCHECK_EQ(LoadTransformationKind::kSplat, transform);
if (memtype == MachineType::Int8()) {
LoadAndSplat8x16LE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Int16()) {
LoadAndSplat16x8LE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Int32()) {
LoadAndSplat32x4LE(dst.fp(), src_op, r1);
} else if (memtype == MachineType::Int64()) {
LoadAndSplat64x2LE(dst.fp(), src_op, r1);
}
}
}
void LiftoffAssembler::LoadLane(LiftoffRegister dst, LiftoffRegister src,
Register addr, Register offset_reg,
uintptr_t offset_imm, LoadType type,
uint8_t laneidx, uint32_t* protected_load_pc,
bool i64_offset) {
PREP_MEM_OPERAND(offset_reg, offset_imm, ip)
MemOperand src_op =
MemOperand(addr, offset_reg == no_reg ? r0 : offset_reg, offset_imm);
MachineType mem_type = type.mem_type();
if (dst != src) {
vlr(dst.fp(), src.fp(), Condition(0), Condition(0), Condition(0));
}
if (protected_load_pc) *protected_load_pc = pc_offset();
if (mem_type == MachineType::Int8()) {
LoadLane8LE(dst.fp(), src_op, 15 - laneidx, r1);
} else if (mem_type == MachineType::Int16()) {
LoadLane16LE(dst.fp(), src_op, 7 - laneidx, r1);
} else if (mem_type == MachineType::Int32()) {
LoadLane32LE(dst.fp(), src_op, 3 - laneidx, r1);
} else {
DCHECK_EQ(MachineType::Int64(), mem_type);
LoadLane64LE(dst.fp(), src_op, 1 - laneidx, r1);
}
}
void LiftoffAssembler::StoreLane(Register dst, Register offset,
uintptr_t offset_imm, LiftoffRegister src,
StoreType type, uint8_t lane,
uint32_t* protected_store_pc,
bool i64_offset) {
PREP_MEM_OPERAND(offset, offset_imm, ip)
MemOperand dst_op =
MemOperand(dst, offset == no_reg ? r0 : offset, offset_imm);
if (protected_store_pc) *protected_store_pc = pc_offset();
MachineRepresentation rep = type.mem_rep();
if (rep == MachineRepresentation::kWord8) {
StoreLane8LE(src.fp(), dst_op, 15 - lane, r1);
} else if (rep == MachineRepresentation::kWord16) {
StoreLane16LE(src.fp(), dst_op, 7 - lane, r1);
} else if (rep == MachineRepresentation::kWord32) {
StoreLane32LE(src.fp(), dst_op, 3 - lane, r1);
} else {
DCHECK_EQ(MachineRepresentation::kWord64, rep);
StoreLane64LE(src.fp(), dst_op, 1 - lane, r1);
}
}
void LiftoffAssembler::emit_i64x2_mul(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs) {
I64x2Mul(dst.fp(), lhs.fp(), rhs.fp(), r0, r1, ip);
}
void LiftoffAssembler::emit_i32x4_ge_u(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs) {
I32x4GeU(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i16x8_ge_u(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs) {
I16x8GeU(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i8x16_ge_u(LiftoffRegister dst, LiftoffRegister lhs,
LiftoffRegister rhs) {
I8x16GeU(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i8x16_swizzle(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
Simd128Register src1 = lhs.fp();
Simd128Register src2 = rhs.fp();
Simd128Register dest = dst.fp();
Simd128Register temp =
GetUnusedRegister(kFpReg, LiftoffRegList{dest, src1, src2}).fp();
I8x16Swizzle(dest, src1, src2, r0, r1, kScratchDoubleReg, temp);
}
void LiftoffAssembler::emit_f64x2_convert_low_i32x4_s(LiftoffRegister dst,
LiftoffRegister src) {
F64x2ConvertLowI32x4S(dst.fp(), src.fp());
}
void LiftoffAssembler::emit_f64x2_convert_low_i32x4_u(LiftoffRegister dst,
LiftoffRegister src) {
F64x2ConvertLowI32x4U(dst.fp(), src.fp());
}
void LiftoffAssembler::emit_f64x2_promote_low_f32x4(LiftoffRegister dst,
LiftoffRegister src) {
F64x2PromoteLowF32x4(dst.fp(), src.fp(), kScratchDoubleReg, r0, r1, ip);
}
void LiftoffAssembler::emit_i64x2_bitmask(LiftoffRegister dst,
LiftoffRegister src) {
I64x2BitMask(dst.gp(), src.fp(), r0, kScratchDoubleReg);
}
void LiftoffAssembler::emit_i32x4_bitmask(LiftoffRegister dst,
LiftoffRegister src) {
I32x4BitMask(dst.gp(), src.fp(), r0, kScratchDoubleReg);
}
void LiftoffAssembler::emit_i32x4_dot_i16x8_s(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I32x4DotI16x8S(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i16x8_bitmask(LiftoffRegister dst,
LiftoffRegister src) {
I16x8BitMask(dst.gp(), src.fp(), r0, kScratchDoubleReg);
}
void LiftoffAssembler::emit_i16x8_q15mulr_sat_s(LiftoffRegister dst,
LiftoffRegister src1,
LiftoffRegister src2) {
Simd128Register s1 = src1.fp();
Simd128Register s2 = src2.fp();
Simd128Register dest = dst.fp();
// Make sure temp registers are unique.
Simd128Register temp1 =
GetUnusedRegister(kFpReg, LiftoffRegList{dest, s1, s2}).fp();
Simd128Register temp2 =
GetUnusedRegister(kFpReg, LiftoffRegList{dest, s1, s2, temp1}).fp();
I16x8Q15MulRSatS(dest, s1, s2, kScratchDoubleReg, temp1, temp2);
}
void LiftoffAssembler::emit_i16x8_dot_i8x16_i7x16_s(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I16x8DotI8x16S(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i32x4_dot_i8x16_i7x16_add_s(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs,
LiftoffRegister acc) {
// Make sure temp register is unique.
Simd128Register temp =
GetUnusedRegister(kFpReg, LiftoffRegList{dst, lhs, rhs}).fp();
I32x4DotI8x16AddS(dst.fp(), lhs.fp(), rhs.fp(), acc.fp(), kScratchDoubleReg,
temp);
}
void LiftoffAssembler::emit_i8x16_shuffle(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs,
const uint8_t shuffle[16],
bool is_swizzle) {
// Remap the shuffle indices to match IBM lane numbering.
// TODO(miladfarca): Put this in a function and share it with the instrction
// selector.
int max_index = 15;
int total_lane_count = 2 * kSimd128Size;
uint8_t shuffle_remapped[kSimd128Size];
for (int i = 0; i < kSimd128Size; i++) {
uint8_t current_index = shuffle[i];
shuffle_remapped[i] = (current_index <= max_index
? max_index - current_index
: total_lane_count - current_index + max_index);
}
uint64_t vals[2];
memcpy(vals, shuffle_remapped, sizeof(shuffle_remapped));
#ifdef V8_TARGET_BIG_ENDIAN
vals[0] = ByteReverse(vals[0]);
vals[1] = ByteReverse(vals[1]);
#endif
I8x16Shuffle(dst.fp(), lhs.fp(), rhs.fp(), vals[1], vals[0], r0, ip,
kScratchDoubleReg);
}
void LiftoffAssembler::emit_i8x16_popcnt(LiftoffRegister dst,
LiftoffRegister src) {
I8x16Popcnt(dst.fp(), src.fp());
}
void LiftoffAssembler::emit_v128_anytrue(LiftoffRegister dst,
LiftoffRegister src) {
V128AnyTrue(dst.gp(), src.fp(), r0);
}
void LiftoffAssembler::emit_i8x16_bitmask(LiftoffRegister dst,
LiftoffRegister src) {
I8x16BitMask(dst.gp(), src.fp(), r0, ip, kScratchDoubleReg);
}
void LiftoffAssembler::emit_s128_const(LiftoffRegister dst,
const uint8_t imms[16]) {
uint64_t vals[2];
memcpy(vals, imms, sizeof(vals));
#ifdef V8_TARGET_BIG_ENDIAN
vals[0] = ByteReverse(vals[0]);
vals[1] = ByteReverse(vals[1]);
#endif
S128Const(dst.fp(), vals[1], vals[0], r0, ip);
}
void LiftoffAssembler::emit_s128_select(LiftoffRegister dst,
LiftoffRegister src1,
LiftoffRegister src2,
LiftoffRegister mask) {
S128Select(dst.fp(), src1.fp(), src2.fp(), mask.fp());
}
void LiftoffAssembler::emit_i32x4_sconvert_f32x4(LiftoffRegister dst,
LiftoffRegister src) {
I32x4SConvertF32x4(dst.fp(), src.fp(), kScratchDoubleReg, r0);
}
void LiftoffAssembler::emit_i32x4_uconvert_f32x4(LiftoffRegister dst,
LiftoffRegister src) {
I32x4UConvertF32x4(dst.fp(), src.fp(), kScratchDoubleReg, r0);
}
void LiftoffAssembler::emit_f32x4_sconvert_i32x4(LiftoffRegister dst,
LiftoffRegister src) {
F32x4SConvertI32x4(dst.fp(), src.fp(), kScratchDoubleReg, r0);
}
void LiftoffAssembler::emit_f32x4_uconvert_i32x4(LiftoffRegister dst,
LiftoffRegister src) {
F32x4UConvertI32x4(dst.fp(), src.fp(), kScratchDoubleReg, r0);
}
void LiftoffAssembler::emit_f32x4_demote_f64x2_zero(LiftoffRegister dst,
LiftoffRegister src) {
F32x4DemoteF64x2Zero(dst.fp(), src.fp(), kScratchDoubleReg, r0, r1, ip);
}
void LiftoffAssembler::emit_i8x16_sconvert_i16x8(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I8x16SConvertI16x8(dst.fp(), lhs.fp(), rhs.fp());
}
void LiftoffAssembler::emit_i8x16_uconvert_i16x8(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I8x16UConvertI16x8(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i16x8_sconvert_i32x4(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I16x8SConvertI32x4(dst.fp(), lhs.fp(), rhs.fp());
}
void LiftoffAssembler::emit_i16x8_uconvert_i32x4(LiftoffRegister dst,
LiftoffRegister lhs,
LiftoffRegister rhs) {
I16x8UConvertI32x4(dst.fp(), lhs.fp(), rhs.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_s_zero(LiftoffRegister dst,
LiftoffRegister src) {
I32x4TruncSatF64x2SZero(dst.fp(), src.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_u_zero(LiftoffRegister dst,
LiftoffRegister src) {
I32x4TruncSatF64x2UZero(dst.fp(), src.fp(), kScratchDoubleReg);
}
void LiftoffAssembler::emit_s128_relaxed_laneselect(LiftoffRegister dst,
LiftoffRegister src1,
LiftoffRegister src2,
LiftoffRegister mask) {
emit_s128_select(dst, src1, src2, mask);
}
void LiftoffAssembler::StackCheck(Label* ool_code, Register limit_address) {
LoadU64(limit_address, MemOperand(limit_address));
CmpU64(sp, limit_address);
b(le, ool_code);
}
void LiftoffAssembler::CallTrapCallbackForTesting() {
PrepareCallCFunction(0, 0, no_reg);
CallCFunction(ExternalReference::wasm_call_trap_callback_for_testing(), 0);
}
void LiftoffAssembler::AssertUnreachable(AbortReason reason) {
// Asserts unreachable within the wasm code.
MacroAssembler::AssertUnreachable(reason);
}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {
MultiPush(regs.GetGpList());
MultiPushF64OrV128(regs.GetFpList(), ip);
}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {
MultiPopF64OrV128(regs.GetFpList(), ip);
MultiPop(regs.GetGpList());
}
void LiftoffAssembler::RecordSpillsInSafepoint(
SafepointTableBuilder::Safepoint& safepoint, LiftoffRegList all_spills,
LiftoffRegList ref_spills, int spill_offset) {
int spill_space_size = 0;
while (!all_spills.is_empty()) {
LiftoffRegister reg = all_spills.GetLastRegSet();
if (ref_spills.has(reg)) {
safepoint.DefineTaggedStackSlot(spill_offset);
}
all_spills.clear(reg);
++spill_offset;
spill_space_size += kSystemPointerSize;
}
// Record the number of additional spill slots.
RecordOolSpillSpaceSize(spill_space_size);
}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {
Drop(num_stack_slots);
Ret();
}
void LiftoffAssembler::CallC(const ValueKindSig* sig,
const LiftoffRegister* args,
const LiftoffRegister* rets,
ValueKind out_argument_kind, int stack_bytes,
ExternalReference ext_ref) {
int total_size = RoundUp(stack_bytes, 8);
int size = total_size;
constexpr int kStackPageSize = 4 * KB;
// Reserve space in the stack.
while (size > kStackPageSize) {
lay(sp, MemOperand(sp, -kStackPageSize));
StoreU64(r0, MemOperand(sp));
size -= kStackPageSize;
}
lay(sp, MemOperand(sp, -size));
int arg_bytes = 0;
for (ValueKind param_kind : sig->parameters()) {
switch (param_kind) {
case kI32:
StoreU32(args->gp(), MemOperand(sp, arg_bytes));
break;
case kI64:
StoreU64(args->gp(), MemOperand(sp, arg_bytes));
break;
case kF32:
StoreF32(args->fp(), MemOperand(sp, arg_bytes));
break;
case kF64:
StoreF64(args->fp(), MemOperand(sp, arg_bytes));
break;
case kS128:
StoreV128(args->fp(), MemOperand(sp, arg_bytes), r0);
break;
default:
UNREACHABLE();
}
args++;
arg_bytes += value_kind_size(param_kind);
}
DCHECK_LE(arg_bytes, stack_bytes);
// Pass a pointer to the buffer with the arguments to the C function.
mov(r2, sp);
// Now call the C function.
constexpr int kNumCCallArgs = 1;
PrepareCallCFunction(kNumCCallArgs, no_reg);
CallCFunction(ext_ref, kNumCCallArgs);
// Move return value to the right register.
const LiftoffRegister* result_reg = rets;
if (sig->return_count() > 0) {
DCHECK_EQ(1, sig->return_count());
constexpr Register kReturnReg = r2;
if (kReturnReg != rets->gp()) {
Move(*rets, LiftoffRegister(kReturnReg), sig->GetReturn(0));
}
result_reg++;
}
// Load potential output value from the buffer on the stack.
if (out_argument_kind != kVoid) {
switch (out_argument_kind) {
case kI32:
LoadS32(result_reg->gp(), MemOperand(sp));
break;
case kI64:
case kRefNull:
case kRef:
case kRtt:
LoadU64(result_reg->gp(), MemOperand(sp));
break;
case kF32:
LoadF32(result_reg->fp(), MemOperand(sp));
break;
case kF64:
LoadF64(result_reg->fp(), MemOperand(sp));
break;
case kS128:
LoadV128(result_reg->fp(), MemOperand(sp), r0);
break;
default:
UNREACHABLE();
}
}
lay(sp, MemOperand(sp, total_size));
}
void LiftoffAssembler::CallNativeWasmCode(Address addr) {
Call(addr, RelocInfo::WASM_CALL);
}
void LiftoffAssembler::TailCallNativeWasmCode(Address addr) {
Jump(addr, RelocInfo::WASM_CALL);
}
void LiftoffAssembler::CallIndirect(const ValueKindSig* sig,
compiler::CallDescriptor* call_descriptor,
Register target) {
DCHECK(target != no_reg);
Call(target);
}
void LiftoffAssembler::TailCallIndirect(Register target) {
DCHECK(target != no_reg);
Jump(target);
}
void LiftoffAssembler::CallRuntimeStub(WasmCode::RuntimeStubId sid) {
Call(static_cast<Address>(sid), RelocInfo::WASM_STUB_CALL);
}
void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) {
lay(sp, MemOperand(sp, -size));
MacroAssembler::Move(addr, sp);
}
void LiftoffAssembler::DeallocateStackSlot(uint32_t size) {
lay(sp, MemOperand(sp, size));
}
void LiftoffAssembler::MaybeOSR() {}
void LiftoffAssembler::emit_set_if_nan(Register dst, DoubleRegister src,
ValueKind kind) {
Label return_nan, done;
if (kind == kF32) {
cebr(src, src);
bunordered(&return_nan);
} else {
DCHECK_EQ(kind, kF64);
cdbr(src, src);
bunordered(&return_nan);
}
b(&done);
bind(&return_nan);
StoreF32LE(src, MemOperand(dst), r0);
bind(&done);
}
void LiftoffAssembler::emit_s128_set_if_nan(Register dst, LiftoffRegister src,
Register tmp_gp,
LiftoffRegister tmp_s128,
ValueKind lane_kind) {
Label return_nan, done;
if (lane_kind == kF32) {
vfce(tmp_s128.fp(), src.fp(), src.fp(), Condition(1), Condition(0),
Condition(2));
b(Condition(0x5), &return_nan); // If any or all are NaN.
} else {
DCHECK_EQ(lane_kind, kF64);
vfce(tmp_s128.fp(), src.fp(), src.fp(), Condition(1), Condition(0),
Condition(3));
b(Condition(0x5), &return_nan);
}
b(&done);
bind(&return_nan);
StoreF32LE(src.fp(), MemOperand(dst), r0);
bind(&done);
}
void LiftoffStackSlots::Construct(int param_slots) {
DCHECK_LT(0, slots_.size());
SortInPushOrder();
int last_stack_slot = param_slots;
for (auto& slot : slots_) {
const int stack_slot = slot.dst_slot_;
int stack_decrement = (last_stack_slot - stack_slot) * kSystemPointerSize;
DCHECK_LT(0, stack_decrement);
last_stack_slot = stack_slot;
const LiftoffAssembler::VarState& src = slot.src_;
switch (src.loc()) {
case LiftoffAssembler::VarState::kStack: {
switch (src.kind()) {
case kI32:
case kRef:
case kRefNull:
case kRtt:
case kI64: {
asm_->AllocateStackSpace(stack_decrement - kSystemPointerSize);
UseScratchRegisterScope temps(asm_);
Register scratch = temps.Acquire();
asm_->LoadU64(scratch, liftoff::GetStackSlot(slot.src_offset_));
asm_->Push(scratch);
break;
}
case kF32: {
asm_->AllocateStackSpace(stack_decrement - kSystemPointerSize);
asm_->LoadF32(kScratchDoubleReg,
liftoff::GetStackSlot(slot.src_offset_));
asm_->lay(sp, MemOperand(sp, -kSystemPointerSize));
asm_->StoreF32(kScratchDoubleReg, MemOperand(sp));
break;
}
case kF64: {
asm_->AllocateStackSpace(stack_decrement - kDoubleSize);
asm_->LoadF64(kScratchDoubleReg,
liftoff::GetStackSlot(slot.src_offset_));
asm_->push(kScratchDoubleReg);
break;
}
case kS128: {
asm_->AllocateStackSpace(stack_decrement - kSimd128Size);
UseScratchRegisterScope temps(asm_);
Register scratch = temps.Acquire();
asm_->LoadV128(kScratchDoubleReg,
liftoff::GetStackSlot(slot.src_offset_), scratch);
asm_->lay(sp, MemOperand(sp, -kSimd128Size));
asm_->StoreV128(kScratchDoubleReg, MemOperand(sp), scratch);
break;
}
default:
UNREACHABLE();
}
break;
}
case LiftoffAssembler::VarState::kRegister: {
int pushed_bytes = SlotSizeInBytes(slot);
asm_->AllocateStackSpace(stack_decrement - pushed_bytes);
switch (src.kind()) {
case kI64:
case kI32:
case kRef:
case kRefNull:
case kRtt:
asm_->push(src.reg().gp());
break;
case kF32:
asm_->lay(sp, MemOperand(sp, -kSystemPointerSize));
asm_->StoreF32(src.reg().fp(), MemOperand(sp));
break;
case kF64:
asm_->push(src.reg().fp());
break;
case kS128: {
UseScratchRegisterScope temps(asm_);
Register scratch = temps.Acquire();
asm_->lay(sp, MemOperand(sp, -kSimd128Size));
asm_->StoreV128(src.reg().fp(), MemOperand(sp), scratch);
break;
}
default:
UNREACHABLE();
}
break;
}
case LiftoffAssembler::VarState::kIntConst: {
asm_->AllocateStackSpace(stack_decrement - kSystemPointerSize);
DCHECK(src.kind() == kI32 || src.kind() == kI64);
UseScratchRegisterScope temps(asm_);
Register scratch = temps.Acquire();
switch (src.kind()) {
case kI32:
asm_->mov(scratch, Operand(src.i32_const()));
break;
case kI64:
asm_->mov(scratch, Operand(int64_t{slot.src_.i32_const()}));
break;
default:
UNREACHABLE();
}
asm_->push(scratch);
break;
}
}
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
#undef BAILOUT
#endif // V8_WASM_BASELINE_S390_LIFTOFF_ASSEMBLER_S390_H_