blob: 7a69ea0902ed1d497863579a1a1858d6bcf77fb5 [file] [log] [blame]
// Copyright 2018 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.
#include "src/wasm/jump-table-assembler.h"
#include "src/base/sanitizer/ubsan.h"
#include "src/codegen/macro-assembler-inl.h"
namespace v8 {
namespace internal {
namespace wasm {
// static
void JumpTableAssembler::GenerateLazyCompileTable(
Address base, uint32_t num_slots, uint32_t num_imported_functions,
Address wasm_compile_lazy_target) {
uint32_t lazy_compile_table_size = num_slots * kLazyCompileTableSlotSize;
WritableJitAllocation jit_allocation = ThreadIsolation::LookupJitAllocation(
base, RoundUp<kCodeAlignment>(lazy_compile_table_size),
ThreadIsolation::JitAllocationType::kWasmLazyCompileTable);
// Assume enough space, so the Assembler does not try to grow the buffer.
JumpTableAssembler jtasm(jit_allocation, base);
for (uint32_t slot_index = 0; slot_index < num_slots; ++slot_index) {
DCHECK_EQ(slot_index * kLazyCompileTableSlotSize, jtasm.pc_offset());
jtasm.EmitLazyCompileJumpSlot(slot_index + num_imported_functions,
wasm_compile_lazy_target);
}
DCHECK_EQ(lazy_compile_table_size, jtasm.pc_offset());
FlushInstructionCache(base, lazy_compile_table_size);
}
void JumpTableAssembler::InitializeJumpsToLazyCompileTable(
Address base, uint32_t num_slots, Address lazy_compile_table_start) {
uint32_t jump_table_size = SizeForNumberOfSlots(num_slots);
WritableJitAllocation jit_allocation = ThreadIsolation::LookupJitAllocation(
base, RoundUp<kCodeAlignment>(jump_table_size),
ThreadIsolation::JitAllocationType::kWasmJumpTable);
JumpTableAssembler jtasm(jit_allocation, base);
for (uint32_t slot_index = 0; slot_index < num_slots; ++slot_index) {
// Make sure we write at the correct offset.
int slot_offset =
static_cast<int>(JumpTableAssembler::JumpSlotIndexToOffset(slot_index));
jtasm.SkipUntil(slot_offset);
Address target =
lazy_compile_table_start +
JumpTableAssembler::LazyCompileSlotIndexToOffset(slot_index);
#ifdef DEBUG
int offset_before_emit = jtasm.pc_offset();
#endif
// This function initializes the first jump table with jumps to the lazy
// compile table. Both get allocated in the constructor of the
// {NativeModule}, so they both should end up in the initial code space.
// Jumps within one code space can always be near jumps, so the following
// call to {EmitJumpSlot} should always succeed. If the call fails, then
// either the jump table allocation was changed incorrectly so that the lazy
// compile table was not within near-jump distance of the jump table
// anymore (e.g. the initial code space was too small to fit both tables),
// or the code space was allocated larger than the maximum near-jump
// distance.
CHECK(jtasm.EmitJumpSlot(target));
DCHECK_EQ(kJumpTableSlotSize, jtasm.pc_offset() - offset_before_emit);
}
FlushInstructionCache(base, jump_table_size);
}
template <typename T>
void JumpTableAssembler::emit(T value) {
jit_allocation_.WriteUnalignedValue(pc_, value);
pc_ += sizeof(T);
}
template <typename T>
void JumpTableAssembler::emit(T value, RelaxedStoreTag) DISABLE_UBSAN {
// We disable ubsan for these stores since they don't follow the alignment
// requirements. We instead guarantee in the jump table layout that the writes
// will still be atomic since they don't cross a qword boundary.
#if V8_TARGET_ARCH_X64
#ifdef DEBUG
Address write_start = pc_;
Address write_end = write_start + sizeof(T) - 1;
// Check that the write doesn't cross a qword boundary.
DCHECK_EQ(write_start >> kSystemPointerSizeLog2,
write_end >> kSystemPointerSizeLog2);
#endif
#endif
jit_allocation_.WriteValue(pc_, value, kRelaxedStore);
pc_ += sizeof(T);
}
// The implementation is compact enough to implement it inline here. If it gets
// much bigger, we might want to split it in a separate file per architecture.
#if V8_TARGET_ARCH_X64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
// Use a push, because mov to an extended register takes 6 bytes.
const uint8_t inst[kLazyCompileTableSlotSize] = {
0x68, 0, 0, 0, 0, // pushq func_index
0xe9, 0, 0, 0, 0, // near_jmp displacement
};
intptr_t displacement =
lazy_compile_target - (pc_ + kLazyCompileTableSlotSize);
emit<uint8_t>(inst[0]);
emit<uint32_t>(func_index);
emit<uint8_t>(inst[5]);
emit<int32_t>(base::checked_cast<int32_t>(displacement));
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
#ifdef V8_ENABLE_CET_IBT
uint32_t endbr_insn = 0xfa1e0ff3;
uint32_t nop = 0x00401f0f;
emit<uint32_t>(endbr_insn, kRelaxedStore);
// Add a nop to ensure that the next block is 8 byte aligned.
emit<uint32_t>(nop, kRelaxedStore);
#endif
intptr_t displacement =
target - (pc_ + MacroAssembler::kIntraSegmentJmpInstrSize);
if (!is_int32(displacement)) return false;
uint8_t inst[kJumpTableSlotSize] = {
0xe9, 0, 0, 0, 0, // near_jmp displacement
0xcc, 0xcc, 0xcc, // int3 * 3
};
int32_t displacement32 = base::checked_cast<int32_t>(displacement);
memcpy(&inst[1], &displacement32, sizeof(int32_t));
// The jump table is updated live, so the write has to be atomic.
emit<uint64_t>(*reinterpret_cast<uint64_t*>(inst), kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
const uint8_t inst[kFarJumpTableSlotSize] = {
0xff, 0x25, 0x02, 0, 0, 0, // jmp [rip+0x2]
0x66, 0x90, // Nop(2)
0, 0, 0, 0, 0, 0, 0, 0, // target
};
emit<uint64_t>(*reinterpret_cast<const uint64_t*>(inst));
emit<uint64_t>(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
// The slot needs to be pointer-size aligned so we can atomically update it.
DCHECK(IsAligned(slot, kSystemPointerSize));
// Offset of the target is at 8 bytes, see {EmitFarJumpSlot}.
jit_allocation.WriteValue(slot + kSystemPointerSize, target, kRelaxedStore);
// The update is atomic because the address is properly aligned.
// Because of cache coherence, the data update will eventually be seen by all
// cores. It's ok if they temporarily jump to the old target.
}
void JumpTableAssembler::SkipUntil(int offset) {
DCHECK_GE(offset, pc_offset());
pc_ += offset - pc_offset();
}
#elif V8_TARGET_ARCH_IA32
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kWasmCompileLazyFuncIndexRegister == edi);
const uint8_t inst[kLazyCompileTableSlotSize] = {
0xbf, 0, 0, 0, 0, // mov edi, func_index
0xe9, 0, 0, 0, 0, // near_jmp displacement
};
intptr_t displacement =
lazy_compile_target - (pc_ + kLazyCompileTableSlotSize);
emit<uint8_t>(inst[0]);
emit<uint32_t>(func_index);
emit<uint8_t>(inst[5]);
emit<int32_t>(base::checked_cast<int32_t>(displacement));
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
intptr_t displacement = target - (pc_ + kJumpTableSlotSize);
const uint8_t inst[kJumpTableSlotSize] = {
0xe9, 0, 0, 0, 0, // near_jmp displacement
};
// The jump table is updated live, so the writes have to be atomic.
emit<uint8_t>(inst[0], kRelaxedStore);
emit<int32_t>(base::checked_cast<int32_t>(displacement), kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
static_assert(kJumpTableSlotSize == kFarJumpTableSlotSize);
EmitJumpSlot(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
UNREACHABLE();
}
void JumpTableAssembler::SkipUntil(int offset) {
DCHECK_GE(offset, pc_offset());
pc_ += offset - pc_offset();
}
#elif V8_TARGET_ARCH_ARM
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kWasmCompileLazyFuncIndexRegister == r4);
// Note that below, [pc] points to the instruction after the next.
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
0xe59f4000, // ldr r4, [pc]
0xe59ff000, // ldr pc, [pc]
0x00000000, // func_index
0x00000000, // target
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(func_index);
emit<Address>(lazy_compile_target);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
static_assert(kInstrSize == kInt32Size);
static_assert(kJumpTableSlotSize == 2 * kInstrSize);
// Load from [pc + kInstrSize] to pc. Note that {pc} points two instructions
// after the currently executing one.
const uint32_t inst[kJumpTableSlotSize / kInstrSize] = {
0xe51ff004, // ldr pc, [pc, -4]
0x00000000, // target
};
// This function is also used for patching existing jump slots and the writes
// need to be atomic.
emit<uint32_t>(inst[0], kRelaxedStore);
emit<uint32_t>(target, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
static_assert(kJumpTableSlotSize == kFarJumpTableSlotSize);
EmitJumpSlot(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
UNREACHABLE();
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_ARM64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
uint16_t func_index_low = func_index & 0xffff;
uint16_t func_index_high = func_index >> 16;
// TODO(sroettger): This bti instruction is a temporary fix for crashes that
// we observed in the wild. We can probably avoid this again if we change the
// callee to jump to the far jump table instead.
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
0xd50324df, // bti.jc
0x52800008, // mov w8, func_index_low
0x72a00008, // movk w8, func_index_high, LSL#0x10
0x14000000, // b lazy_compile_target
};
static_assert(kWasmCompileLazyFuncIndexRegister == x8);
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
lazy_compile_target, RelocInfo::NO_INFO,
reinterpret_cast<uint8_t*>(pc_ + 3 * kInstrSize));
DCHECK(MacroAssembler::IsNearCallOffset(target_offset));
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1] | Assembler::ImmMoveWide(func_index_low));
emit<uint32_t>(inst[2] | Assembler::ImmMoveWide(func_index_high));
emit<uint32_t>(inst[3] | Assembler::ImmUncondBranch(
base::checked_cast<int32_t>(target_offset)));
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
static constexpr ptrdiff_t kCodeEntryMarkerSize = kInstrSize;
#else
static constexpr ptrdiff_t kCodeEntryMarkerSize = 0;
#endif
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
target, RelocInfo::NO_INFO,
reinterpret_cast<uint8_t*>(pc_ + kCodeEntryMarkerSize));
if (!MacroAssembler::IsNearCallOffset(target_offset)) {
return false;
}
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
uint32_t bti_inst = 0xd503245f; // bti c
emit<uint32_t>(bti_inst, kRelaxedStore);
#endif
uint32_t branch_inst =
0x14000000 |
Assembler::ImmUncondBranch(base::checked_cast<int32_t>(target_offset));
emit<uint32_t>(branch_inst, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
DCHECK(MacroAssembler::DefaultTmpList().IncludesAliasOf(x16));
const uint32_t inst[kFarJumpTableSlotSize / 4] = {
0x58000050, // ldr x16, #8
0xd61f0200, // br x16
0x00000000, // target[0]
0x00000000, // target[1]
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<Address>(target);
static_assert(2 * kInstrSize == kSystemPointerSize);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
// See {EmitFarJumpSlot} for the offset of the target (16 bytes with
// CFI enabled, 8 bytes otherwise).
int kTargetOffset = 2 * kInstrSize;
// The slot needs to be pointer-size aligned so we can atomically update it.
DCHECK(IsAligned(slot + kTargetOffset, kSystemPointerSize));
jit_allocation.WriteValue(slot + kTargetOffset, target, kRelaxedStore);
// The data update is guaranteed to be atomic since it's a properly aligned
// and stores a single machine word. This update will eventually be observed
// by any concurrent [ldr] on the same address because of the data cache
// coherence. It's ok if other cores temporarily jump to the old target.
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_S390X
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kWasmCompileLazyFuncIndexRegister == r7);
uint8_t inst[kLazyCompileTableSlotSize] = {
0xc0, 0x71, 0x00, 0x00, 0x00, 0x00, // lgfi r7, 0
0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl r1, 0
0xe3, 0x10, 0x10, 0x12, 0x00, 0x04, // lg r1, 18(r1)
0x07, 0xf1, // br r1
0xb9, 0x04, 0x00, 0x00, // nop (alignment)
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 // lazy_compile_target
};
#if V8_TARGET_LITTLE_ENDIAN
// We need to emit the value in big endian format.
func_index = base::bits::ReverseBytes(func_index);
#endif
memcpy(&inst[2], &func_index, sizeof(int32_t));
for (size_t i = 0; i < (kLazyCompileTableSlotSize - sizeof(Address)); i++) {
emit<uint8_t>(inst[i]);
}
emit<Address>(lazy_compile_target);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
intptr_t relative_target = target - pc_;
if (!is_int32(relative_target / 2)) {
return false;
}
uint8_t inst[kJumpTableSlotSize] = {
0xc0, 0xf4, 0x00,
0x00, 0x00, 0x00, // brcl(al, Operand(relative_target / 2))
0x18, 0x00 // nop (alignment)
};
int32_t relative_target_addr = static_cast<int32_t>(relative_target / 2);
#if V8_TARGET_LITTLE_ENDIAN
// We need to emit the value in big endian format.
relative_target_addr = base::bits::ReverseBytes(relative_target_addr);
#endif
memcpy(&inst[2], &relative_target_addr, sizeof(int32_t));
// The jump table is updated live, so the write has to be atomic.
emit<uint64_t>(*reinterpret_cast<uint64_t*>(inst), kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
const uint8_t inst[kFarJumpTableSlotSize] = {
0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl r1, 0
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg r1, 16(r1)
0x07, 0xf1, // br r1
0x18, 0x00, // nop (alignment)
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 // target
};
for (size_t i = 0; i < (kFarJumpTableSlotSize - sizeof(Address)); i++) {
emit<uint8_t>(inst[i]);
}
emit<Address>(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
Address target_addr = slot + 8;
jit_allocation.WriteValue(target_addr, target, kRelaxedStore);
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_MIPS64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
uint32_t func_index_low = func_index & 0xffff;
uint32_t func_index_high = func_index >> 16;
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
0x3c0c0000, // lui $t0, func_index_high
0x358c0000, // ori $t0, $t0, func_index_low
0x03e00825, // move $at, $ra
0x04110001, // bal 1
0x00000000, // nop (alignment, in delay slot)
0xdff9000c, // ld $t9, 12($ra) (ra = pc)
0x03200008, // jr $t9
0x0020f825, // move $ra, $at (in delay slot)
0x00000000, // lazy_compile_target[0]
0x00000000, // layz_compile_target[1]
};
static_assert(kWasmCompileLazyFuncIndexRegister == t0);
emit<uint32_t>(inst[0] | func_index_high);
emit<uint32_t>(inst[1] | func_index_low);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
emit<uint32_t>(inst[4]);
emit<uint32_t>(inst[5]);
emit<uint32_t>(inst[6]);
emit<uint32_t>(inst[7]);
DCHECK(IsAligned(pc_, kSystemPointerSize));
emit<Address>(lazy_compile_target);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
const uint32_t inst[kJumpTableSlotSize / kInstrSize] = {
0x03e00825, // move $at, $ra
0x04110001, // bal 1
0x00000000, // nop (alignment, in delay slot)
0xdff9000c, // ld $t9, 12($ra) (ra = pc)
0x03200008, // jr $t9
0x0020f825, // move $ra, $at (in delay slot)
0x00000000, // lazy_compile_target[0]
0x00000000, // layz_compile_target[1]
};
// This function is also used for patching existing jump slots and the writes
// need to be atomic.
emit<uint32_t>(inst[0], kRelaxedStore);
emit<uint32_t>(inst[1], kRelaxedStore);
emit<uint32_t>(inst[2], kRelaxedStore);
emit<uint32_t>(inst[3], kRelaxedStore);
emit<uint32_t>(inst[4], kRelaxedStore);
emit<uint32_t>(inst[5], kRelaxedStore);
DCHECK(IsAligned(pc_, kSystemPointerSize));
emit<Address>(target, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
static_assert(kJumpTableSlotSize == kFarJumpTableSlotSize);
EmitJumpSlot(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
UNREACHABLE();
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_LOONG64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
uint32_t func_index_low_12 = func_index & 0xfff;
uint32_t func_index_high_20 = func_index >> 12;
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
0x1400000c, // lu12i.w $t0, func_index_high_20
0x0380018c, // ori $t0, $t0, func_index_low_12
0x50000000, // b lazy_compile_target
};
static_assert(kWasmCompileLazyFuncIndexRegister == t0);
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
lazy_compile_target, RelocInfo::NO_INFO,
reinterpret_cast<uint8_t*>(pc_ + 2 * kInstrSize));
DCHECK(MacroAssembler::IsNearCallOffset(target_offset));
uint32_t target_offset_offs26 = (target_offset & 0xfffffff) >> 2;
uint32_t target_offset_low_16 = target_offset_offs26 & 0xffff;
uint32_t target_offset_high_10 = target_offset_offs26 >> 16;
emit<uint32_t>(inst[0] | func_index_high_20 << kRjShift);
emit<uint32_t>(inst[1] | func_index_low_12 << kRkShift);
emit<uint32_t>(inst[2] | target_offset_low_16 << kRkShift |
target_offset_high_10);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
target, RelocInfo::NO_INFO, reinterpret_cast<uint8_t*>(pc_));
if (!MacroAssembler::IsNearCallOffset(target_offset)) {
return false;
}
uint32_t target_offset_offs26 = (target_offset & 0xfffffff) >> 2;
uint32_t target_offset_low_16 = target_offset_offs26 & 0xffff;
uint32_t target_offset_high_10 = target_offset_offs26 >> 16;
uint32_t branch_inst =
0x50000000 | target_offset_low_16 << kRkShift | target_offset_high_10;
emit<uint32_t>(branch_inst, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
const uint32_t inst[kFarJumpTableSlotSize / 4] = {
0x18000093, // pcaddi $t7, 4
0x28c00273, // ld.d $t7, $t7, 0
0x4c000260, // jirl $zero, $t7, 0
0x03400000, // nop (make target pointer-size aligned)
0x00000000, // target[0]
0x00000000, // target[1]
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
DCHECK(IsAligned(pc_, kSystemPointerSize));
emit<Address>(target);
}
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
// See {EmitFarJumpSlot} for the address of the target.
Address target_addr = slot + kFarJumpTableSlotSize - kSystemPointerSize;
// The slot needs to be pointer-size aligned so we can atomically update it.
DCHECK(IsAligned(target_addr, kSystemPointerSize));
jit_allocation.WriteValue(target_addr, target, kRelaxedStore);
// The data update is guaranteed to be atomic since it's a properly aligned
// and stores a single machine word. This update will eventually be observed
// by any concurrent [ld.d] on the same address because of the data cache
// coherence. It's ok if other cores temporarily jump to the old target.
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_PPC64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kWasmCompileLazyFuncIndexRegister == r15);
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
0x7c0802a6, // mflr r0
0x48000005, // b(4, SetLK)
0x7d8802a6, // mflr ip
0x7c0803a6, // mtlr r0
0x81ec0018, // lwz r15, 24(ip)
0xe80c0020, // ld r0, 32(ip)
0x7c0903a6, // mtctr r0
0x4e800420, // bctr
0x00000000, // func_index
0x60000000, // nop (alignment)
0x00000000, // lazy_compile_target_0
0x00000000 // lazy_compile_target_1
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
emit<uint32_t>(inst[4]);
emit<uint32_t>(inst[5]);
emit<uint32_t>(inst[6]);
emit<uint32_t>(inst[7]);
emit<uint32_t>(func_index);
emit<uint32_t>(inst[9]);
emit<Address>(lazy_compile_target);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
intptr_t relative_target = target - pc_;
if (!is_int26(relative_target)) {
return false;
}
const uint32_t inst[kJumpTableSlotSize / kInstrSize] = {
0x48000000 // b(relative_target, LeaveLK)
};
CHECK((relative_target & (kAAMask | kLKMask)) == 0);
// The jump table is updated live, so the write has to be atomic.
emit<uint32_t>(inst[0] | relative_target, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
const uint32_t inst[kFarJumpTableSlotSize / 4] = {
0x7c0802a6, // mflr r0
0x48000005, // b(4, SetLK)
0x7d8802a6, // mflr ip
0x7c0803a6, // mtlr r0
0xe98c0018, // ld ip, 24(ip)
0x7d8903a6, // mtctr ip
0x4e800420, // bctr
0x60000000, // nop (alignment)
0x00000000, // target_0
0x00000000, // target_1
0x60000000, // nop
0x60000000 // nop
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
emit<uint32_t>(inst[4]);
emit<uint32_t>(inst[5]);
emit<uint32_t>(inst[6]);
emit<uint32_t>(inst[7]);
emit<Address>(target);
emit<uint32_t>(inst[10]);
emit<uint32_t>(inst[11]);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
Address target_addr = slot + kFarJumpTableSlotSize - 8;
jit_allocation.WriteValue(target_addr, target, kRelaxedStore);
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_RISCV64
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kLazyCompileTableSlotSize == 3 * kInstrSize);
int64_t high_20 = (func_index + 0x800) >> 12;
int64_t low_12 = int64_t(func_index) << 52 >> 52;
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
lazy_compile_target, RelocInfo::NO_INFO,
reinterpret_cast<uint8_t*>(pc_ + 2 * kInstrSize));
DCHECK(is_int21(target_offset));
DCHECK_EQ(target_offset & 0x1, 0);
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
(RO_LUI | (kWasmCompileLazyFuncIndexRegister.code() << kRdShift) |
int32_t(high_20 << kImm20Shift)), // lui t0, high_20
(RO_ADDI | (kWasmCompileLazyFuncIndexRegister.code() << kRdShift) |
(kWasmCompileLazyFuncIndexRegister.code() << kRs1Shift) |
int32_t(low_12 << kImm12Shift)), // addi t0, t0, low_12
(RO_JAL | (zero_reg.code() << kRdShift) |
uint32_t(target_offset & 0xff000) | // bits 19-12
uint32_t((target_offset & 0x800) << 9) | // bit 11
uint32_t((target_offset & 0x7fe) << 20) | // bits 10-1
uint32_t((target_offset & 0x100000) << 11)), // bit 20 ), // jal
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
static_assert(kInstrSize == kInt32Size);
static_assert(kJumpTableSlotSize == 2 * kInstrSize);
intptr_t relative_target = target - pc_;
if (!is_int32(relative_target)) {
return false;
}
uint32_t inst[kJumpTableSlotSize / kInstrSize] = {kNopByte, kNopByte};
int64_t high_20 = (relative_target + 0x800) >> 12;
int64_t low_12 = int64_t(relative_target) << 52 >> 52;
inst[0] = (RO_AUIPC | (t6.code() << kRdShift) |
int32_t(high_20 << kImm20Shift)); // auipc t6, high_20
inst[1] =
(RO_JALR | (zero_reg.code() << kRdShift) | (t6.code() << kRs1Shift) |
int32_t(low_12 << kImm12Shift)); // jalr t6, t6, low_12
// This function is also used for patching existing jump slots and the writes
// need to be atomic.
emit<uint64_t>(*reinterpret_cast<uint64_t*>(inst), kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
uint32_t high_20 = (int64_t(4 * kInstrSize + 0x800) >> 12);
uint32_t low_12 = (int64_t(4 * kInstrSize) << 52 >> 52);
const uint32_t inst[kFarJumpTableSlotSize / 4] = {
(RO_AUIPC | (t6.code() << kRdShift) |
(high_20 << kImm20Shift)), // auipc t6, high_20
(RO_LD | (t6.code() << kRdShift) | (t6.code() << kRs1Shift) |
(low_12 << kImm12Shift)), // jalr t6, t6, low_12
(RO_JALR | (t6.code() << kRs1Shift) | zero_reg.code() << kRdShift),
(kNopByte), // nop
0x0000, // target[0]
0x0000, // target[1]
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
emit<Address>(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
// See {EmitFarJumpSlot} for the offset of the target (16 bytes with
// CFI enabled, 8 bytes otherwise).
int kTargetOffset = kFarJumpTableSlotSize - sizeof(Address);
jit_allocation.WriteValue(slot + kTargetOffset, target, kRelaxedStore);
// The data update is guaranteed to be atomic since it's a properly aligned
// and stores a single machine word. This update will eventually be observed
// by any concurrent [ldr] on the same address because of the data cache
// coherence. It's ok if other cores temporarily jump to the old target.
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#elif V8_TARGET_ARCH_RISCV32
void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index,
Address lazy_compile_target) {
static_assert(kLazyCompileTableSlotSize == 3 * kInstrSize);
int64_t high_20 = (func_index + 0x800) >> 12;
int64_t low_12 = int64_t(func_index) << 52 >> 52;
int64_t target_offset = MacroAssembler::CalculateTargetOffset(
lazy_compile_target, RelocInfo::NO_INFO,
reinterpret_cast<uint8_t*>(pc_ + 2 * kInstrSize));
DCHECK(is_int21(target_offset));
DCHECK_EQ(target_offset & 0x1, 0);
const uint32_t inst[kLazyCompileTableSlotSize / 4] = {
(RO_LUI | (kWasmCompileLazyFuncIndexRegister.code() << kRdShift) |
int32_t(high_20 << kImm20Shift)), // lui t0, high_20
(RO_ADDI | (kWasmCompileLazyFuncIndexRegister.code() << kRdShift) |
(kWasmCompileLazyFuncIndexRegister.code() << kRs1Shift) |
int32_t(low_12 << kImm12Shift)), // addi t0, t0, low_12
(RO_JAL | (zero_reg.code() << kRdShift) |
uint32_t(target_offset & 0xff000) | // bits 19-12
uint32_t((target_offset & 0x800) << 9) | // bit 11
uint32_t((target_offset & 0x7fe) << 20) | // bits 10-1
uint32_t((target_offset & 0x100000) << 11)), // bit 20 ), // jal
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
}
bool JumpTableAssembler::EmitJumpSlot(Address target) {
uint32_t high_20 = (int64_t(4 * kInstrSize + 0x800) >> 12);
uint32_t low_12 = (int64_t(4 * kInstrSize) << 52 >> 52);
const uint32_t inst[kJumpTableSlotSize / 4] = {
(RO_AUIPC | (t6.code() << kRdShift) |
(high_20 << kImm20Shift)), // auipc t6, high_20
(RO_LW | (t6.code() << kRdShift) | (t6.code() << kRs1Shift) |
(low_12 << kImm12Shift)), // jalr t6, t6, low_12
(RO_JALR | (t6.code() << kRs1Shift) | zero_reg.code() << kRdShift),
(kNopByte), // nop
0x0000, // target
};
emit<uint32_t>(inst[0]);
emit<uint32_t>(inst[1]);
emit<uint32_t>(inst[2]);
emit<uint32_t>(inst[3]);
emit<uint32_t>(target, kRelaxedStore);
return true;
}
void JumpTableAssembler::EmitFarJumpSlot(Address target) {
static_assert(kJumpTableSlotSize == kFarJumpTableSlotSize);
EmitJumpSlot(target);
}
// static
void JumpTableAssembler::PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
Address slot, Address target) {
UNREACHABLE();
}
void JumpTableAssembler::SkipUntil(int offset) {
// On this platform the jump table is not zapped with valid instructions, so
// skipping over bytes is not allowed.
DCHECK_EQ(offset, pc_offset());
}
#else
#error Unknown architecture.
#endif
} // namespace wasm
} // namespace internal
} // namespace v8