2018-08-07 10:33:50 +00:00
|
|
|
// 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.
|
|
|
|
|
2018-11-27 17:36:36 +00:00
|
|
|
#include <bitset>
|
|
|
|
|
2019-05-21 09:30:15 +00:00
|
|
|
#include "src/codegen/assembler-inl.h"
|
|
|
|
#include "src/codegen/macro-assembler-inl.h"
|
2019-05-22 07:55:37 +00:00
|
|
|
#include "src/execution/simulator.h"
|
2019-05-23 13:27:57 +00:00
|
|
|
#include "src/utils/utils.h"
|
2018-08-07 10:33:50 +00:00
|
|
|
#include "src/wasm/jump-table-assembler.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
#include "test/common/assembler-tester.h"
|
|
|
|
|
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
|
|
|
namespace wasm {
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#define TRACE(...) PrintF(__VA_ARGS__)
|
|
|
|
#else
|
|
|
|
#define TRACE(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define __ masm.
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
static volatile int global_stop_bit = 0;
|
|
|
|
|
2018-11-27 17:36:36 +00:00
|
|
|
constexpr int kJumpTableSlotCount = 128;
|
|
|
|
constexpr uint32_t kJumpTableSize =
|
|
|
|
JumpTableAssembler::SizeForNumberOfSlots(kJumpTableSlotCount);
|
|
|
|
|
2019-08-06 14:20:18 +00:00
|
|
|
constexpr size_t kThunkBufferSize = AssemblerBase::kMinimalBufferSize;
|
|
|
|
|
2019-04-10 15:46:49 +00:00
|
|
|
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64
|
2018-11-27 17:36:36 +00:00
|
|
|
constexpr uint32_t kAvailableBufferSlots =
|
2019-08-06 14:20:18 +00:00
|
|
|
(kMaxWasmCodeMemory - kJumpTableSize) / kThunkBufferSize;
|
2018-11-27 17:36:36 +00:00
|
|
|
constexpr uint32_t kBufferSlotStartOffset =
|
2019-08-06 14:20:18 +00:00
|
|
|
RoundUp<kThunkBufferSize>(kJumpTableSize);
|
2018-11-27 17:36:36 +00:00
|
|
|
#else
|
|
|
|
constexpr uint32_t kAvailableBufferSlots = 0;
|
|
|
|
#endif
|
|
|
|
|
2019-08-06 14:20:18 +00:00
|
|
|
Address AllocateJumpTableThunk(
|
2019-01-17 10:23:16 +00:00
|
|
|
Address jump_target, byte* thunk_slot_buffer,
|
|
|
|
std::bitset<kAvailableBufferSlots>* used_slots,
|
|
|
|
std::vector<std::unique_ptr<TestingAssemblerBuffer>>* thunk_buffers) {
|
2019-04-10 15:46:49 +00:00
|
|
|
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64
|
2018-11-27 17:36:36 +00:00
|
|
|
// To guarantee that the branch range lies within the near-call range,
|
|
|
|
// generate the thunk in the same (kMaxWasmCodeMemory-sized) buffer as the
|
|
|
|
// jump_target itself.
|
|
|
|
//
|
|
|
|
// Allocate a slot that we haven't already used. This is necessary because
|
|
|
|
// each test iteration expects to generate two unique addresses and we leave
|
|
|
|
// each slot executable (and not writable).
|
|
|
|
base::RandomNumberGenerator* rng =
|
|
|
|
CcTest::i_isolate()->random_number_generator();
|
|
|
|
// Ensure a chance of completion without too much thrashing.
|
|
|
|
DCHECK(used_slots->count() < (used_slots->size() / 2));
|
|
|
|
int buffer_index;
|
|
|
|
do {
|
|
|
|
buffer_index = rng->NextInt(kAvailableBufferSlots);
|
|
|
|
} while (used_slots->test(buffer_index));
|
|
|
|
used_slots->set(buffer_index);
|
2019-08-06 14:20:18 +00:00
|
|
|
return reinterpret_cast<Address>(thunk_slot_buffer +
|
|
|
|
buffer_index * kThunkBufferSize);
|
2018-11-27 17:36:36 +00:00
|
|
|
|
2018-08-09 09:07:16 +00:00
|
|
|
#else
|
2018-11-27 17:36:36 +00:00
|
|
|
USE(thunk_slot_buffer);
|
|
|
|
USE(used_slots);
|
2019-08-06 14:20:18 +00:00
|
|
|
thunk_buffers->emplace_back(
|
|
|
|
AllocateAssemblerBuffer(kThunkBufferSize, GetRandomMmapAddr()));
|
|
|
|
return reinterpret_cast<Address>(thunk_buffers->back()->start());
|
2018-08-09 09:07:16 +00:00
|
|
|
#endif
|
2019-08-06 14:20:18 +00:00
|
|
|
}
|
2019-01-17 10:23:16 +00:00
|
|
|
|
2019-08-06 14:20:18 +00:00
|
|
|
void CompileJumpTableThunk(Address thunk, Address jump_target) {
|
|
|
|
MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
|
|
|
|
ExternalAssemblerBuffer(reinterpret_cast<void*>(thunk),
|
|
|
|
kThunkBufferSize));
|
2018-08-07 10:33:50 +00:00
|
|
|
|
|
|
|
Label exit;
|
|
|
|
Register scratch = kReturnRegister0;
|
|
|
|
Address stop_bit_address = reinterpret_cast<Address>(&global_stop_bit);
|
|
|
|
#if V8_TARGET_ARCH_X64
|
|
|
|
__ Move(scratch, stop_bit_address, RelocInfo::NONE);
|
|
|
|
__ testl(MemOperand(scratch, 0), Immediate(1));
|
|
|
|
__ j(not_zero, &exit);
|
|
|
|
__ Jump(jump_target, RelocInfo::NONE);
|
|
|
|
#elif V8_TARGET_ARCH_IA32
|
|
|
|
__ Move(scratch, Immediate(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ test(MemOperand(scratch, 0), Immediate(1));
|
|
|
|
__ j(not_zero, &exit);
|
|
|
|
__ jmp(jump_target, RelocInfo::NONE);
|
2018-08-07 14:19:13 +00:00
|
|
|
#elif V8_TARGET_ARCH_ARM
|
|
|
|
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ ldr(scratch, MemOperand(scratch, 0));
|
|
|
|
__ tst(scratch, Operand(1));
|
|
|
|
__ b(ne, &exit);
|
|
|
|
__ Jump(jump_target, RelocInfo::NONE);
|
2018-08-09 09:07:16 +00:00
|
|
|
#elif V8_TARGET_ARCH_ARM64
|
|
|
|
__ Mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ Ldr(scratch, MemOperand(scratch, 0));
|
|
|
|
__ Tbnz(scratch, 0, &exit);
|
|
|
|
__ Mov(scratch, Immediate(jump_target, RelocInfo::NONE));
|
|
|
|
__ Br(scratch);
|
2019-02-20 14:18:01 +00:00
|
|
|
#elif V8_TARGET_ARCH_PPC64
|
|
|
|
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ LoadP(scratch, MemOperand(scratch));
|
|
|
|
__ cmpi(scratch, Operand::Zero());
|
|
|
|
__ bne(&exit);
|
|
|
|
__ mov(scratch, Operand(jump_target, RelocInfo::NONE));
|
|
|
|
__ Jump(scratch);
|
|
|
|
#elif V8_TARGET_ARCH_S390X
|
|
|
|
__ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ LoadP(scratch, MemOperand(scratch));
|
|
|
|
__ CmpP(scratch, Operand(0));
|
|
|
|
__ bne(&exit);
|
|
|
|
__ mov(scratch, Operand(jump_target, RelocInfo::NONE));
|
|
|
|
__ Jump(scratch);
|
2019-02-27 07:10:24 +00:00
|
|
|
#elif V8_TARGET_ARCH_MIPS64
|
|
|
|
__ li(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ Lw(scratch, MemOperand(scratch, 0));
|
|
|
|
__ Branch(&exit, ne, scratch, Operand(zero_reg));
|
|
|
|
__ Jump(jump_target, RelocInfo::NONE);
|
|
|
|
#elif V8_TARGET_ARCH_MIPS
|
|
|
|
__ li(scratch, Operand(stop_bit_address, RelocInfo::NONE));
|
|
|
|
__ lw(scratch, MemOperand(scratch, 0));
|
|
|
|
__ Branch(&exit, ne, scratch, Operand(zero_reg));
|
|
|
|
__ Jump(jump_target, RelocInfo::NONE);
|
2018-08-07 10:33:50 +00:00
|
|
|
#else
|
|
|
|
#error Unsupported architecture
|
|
|
|
#endif
|
|
|
|
__ bind(&exit);
|
|
|
|
__ Ret();
|
|
|
|
|
2019-08-06 14:20:18 +00:00
|
|
|
FlushInstructionCache(thunk, kThunkBufferSize);
|
|
|
|
CHECK(SetPermissions(GetPlatformPageAllocator(), thunk, kThunkBufferSize,
|
|
|
|
v8::PageAllocator::kReadExecute));
|
2018-08-07 10:33:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class JumpTableRunner : public v8::base::Thread {
|
|
|
|
public:
|
|
|
|
JumpTableRunner(Address slot_address, int runner_id)
|
|
|
|
: Thread(Options("JumpTableRunner")),
|
|
|
|
slot_address_(slot_address),
|
|
|
|
runner_id_(runner_id) {}
|
|
|
|
|
|
|
|
void Run() override {
|
|
|
|
TRACE("Runner #%d is starting ...\n", runner_id_);
|
2018-08-07 14:19:13 +00:00
|
|
|
GeneratedCode<void>::FromAddress(CcTest::i_isolate(), slot_address_).Call();
|
2018-08-07 10:33:50 +00:00
|
|
|
TRACE("Runner #%d is stopping ...\n", runner_id_);
|
|
|
|
USE(runner_id_);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Address slot_address_;
|
|
|
|
int runner_id_;
|
|
|
|
};
|
|
|
|
|
|
|
|
class JumpTablePatcher : public v8::base::Thread {
|
|
|
|
public:
|
|
|
|
JumpTablePatcher(Address slot_start, uint32_t slot_index, Address thunk1,
|
2019-08-06 14:20:18 +00:00
|
|
|
Address thunk2, base::Mutex* jump_table_mutex)
|
2018-08-07 10:33:50 +00:00
|
|
|
: Thread(Options("JumpTablePatcher")),
|
|
|
|
slot_start_(slot_start),
|
|
|
|
slot_index_(slot_index),
|
2019-08-06 14:20:18 +00:00
|
|
|
thunks_{thunk1, thunk2},
|
|
|
|
jump_table_mutex_(jump_table_mutex) {}
|
2018-08-07 10:33:50 +00:00
|
|
|
|
|
|
|
void Run() override {
|
2019-08-06 14:20:18 +00:00
|
|
|
TRACE("Patcher %p is starting ...\n", this);
|
|
|
|
Address slot_address =
|
|
|
|
slot_start_ + JumpTableAssembler::JumpSlotIndexToOffset(slot_index_);
|
|
|
|
// First, emit code to the two thunks.
|
|
|
|
for (Address thunk : thunks_) {
|
|
|
|
CompileJumpTableThunk(thunk, slot_address);
|
|
|
|
}
|
|
|
|
// Then, repeatedly patch the jump table to jump to one of the two thunks.
|
2018-08-07 10:33:50 +00:00
|
|
|
constexpr int kNumberOfPatchIterations = 64;
|
|
|
|
for (int i = 0; i < kNumberOfPatchIterations; ++i) {
|
2019-08-06 14:20:18 +00:00
|
|
|
TRACE(" patcher %p patch slot " V8PRIxPTR_FMT " to thunk #%d\n", this,
|
|
|
|
slot_address, i % 2);
|
|
|
|
base::MutexGuard jump_table_guard(jump_table_mutex_);
|
2018-08-07 10:33:50 +00:00
|
|
|
JumpTableAssembler::PatchJumpTableSlot(
|
|
|
|
slot_start_, slot_index_, thunks_[i % 2], WasmCode::kFlushICache);
|
|
|
|
}
|
2019-08-06 14:20:18 +00:00
|
|
|
TRACE("Patcher %p is stopping ...\n", this);
|
2018-08-07 10:33:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Address slot_start_;
|
|
|
|
uint32_t slot_index_;
|
|
|
|
Address thunks_[2];
|
2019-08-06 14:20:18 +00:00
|
|
|
base::Mutex* jump_table_mutex_;
|
2018-08-07 10:33:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
// This test is intended to stress concurrent patching of jump-table slots. It
|
|
|
|
// uses the following setup:
|
|
|
|
// 1) Picks a particular slot of the jump-table. Slots are iterated over to
|
|
|
|
// ensure multiple entries (at different offset alignments) are tested.
|
|
|
|
// 2) Starts multiple runners that spin through the above slot. The runners
|
|
|
|
// use thunk code that will jump to the same jump-table slot repeatedly
|
|
|
|
// until the {global_stop_bit} indicates a test-end condition.
|
|
|
|
// 3) Start a patcher that repeatedly patches the jump-table slot back and
|
|
|
|
// forth between two thunk. If there is a race then chances are high that
|
|
|
|
// one of the runners is currently executing the jump-table slot.
|
|
|
|
TEST(JumpTablePatchingStress) {
|
|
|
|
constexpr int kNumberOfRunnerThreads = 5;
|
2019-08-06 14:20:18 +00:00
|
|
|
constexpr int kNumberOfPatcherThreads = 3;
|
2018-08-07 10:33:50 +00:00
|
|
|
|
2019-04-10 15:46:49 +00:00
|
|
|
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64
|
2019-08-06 14:20:18 +00:00
|
|
|
// We need the branches (from CompileJumpTableThunk) to be within near-call
|
2018-11-27 17:36:36 +00:00
|
|
|
// range of the jump table slots. The address hint to AllocateAssemblerBuffer
|
|
|
|
// is not reliable enough to guarantee that we can always achieve this with
|
|
|
|
// separate allocations, so for Arm64 we generate all code in a single
|
|
|
|
// kMaxMasmCodeMemory-sized chunk.
|
|
|
|
//
|
|
|
|
// TODO(wasm): Currently {kMaxWasmCodeMemory} limits code sufficiently, so
|
|
|
|
// that the jump table only supports {near_call} distances.
|
|
|
|
STATIC_ASSERT(kMaxWasmCodeMemory >= kJumpTableSize);
|
2019-01-17 10:23:16 +00:00
|
|
|
auto buffer = AllocateAssemblerBuffer(kMaxWasmCodeMemory);
|
|
|
|
byte* thunk_slot_buffer = buffer->start() + kBufferSlotStartOffset;
|
2018-11-27 17:36:36 +00:00
|
|
|
#else
|
2019-01-17 10:23:16 +00:00
|
|
|
auto buffer = AllocateAssemblerBuffer(kJumpTableSize);
|
2018-11-27 17:36:36 +00:00
|
|
|
byte* thunk_slot_buffer = nullptr;
|
|
|
|
#endif
|
2019-04-10 15:46:49 +00:00
|
|
|
|
2018-11-27 17:36:36 +00:00
|
|
|
std::bitset<kAvailableBufferSlots> used_thunk_slots;
|
2019-01-17 10:23:16 +00:00
|
|
|
buffer->MakeWritableAndExecutable();
|
2018-08-07 10:33:50 +00:00
|
|
|
|
|
|
|
// Iterate through jump-table slots to hammer at different alignments within
|
|
|
|
// the jump-table, thereby increasing stress for variable-length ISAs.
|
2019-01-17 10:23:16 +00:00
|
|
|
Address slot_start = reinterpret_cast<Address>(buffer->start());
|
2018-08-07 10:33:50 +00:00
|
|
|
for (int slot = 0; slot < kJumpTableSlotCount; ++slot) {
|
|
|
|
TRACE("Hammering on jump table slot #%d ...\n", slot);
|
2019-07-12 12:05:34 +00:00
|
|
|
uint32_t slot_offset = JumpTableAssembler::JumpSlotIndexToOffset(slot);
|
2019-01-17 10:23:16 +00:00
|
|
|
std::vector<std::unique_ptr<TestingAssemblerBuffer>> thunk_buffers;
|
2019-08-06 14:20:18 +00:00
|
|
|
// Patch the jump table slot to jump to itself. This will later be patched
|
|
|
|
// by the patchers.
|
|
|
|
JumpTableAssembler::PatchJumpTableSlot(
|
|
|
|
slot_start, slot, slot_start + slot_offset, WasmCode::kFlushICache);
|
|
|
|
// For each patcher, generate two thunks where this patcher can emit code
|
|
|
|
// which finally jumps back to {slot} in the jump table.
|
|
|
|
std::vector<Address> patcher_thunks;
|
|
|
|
for (int i = 0; i < 2 * kNumberOfPatcherThreads; ++i) {
|
|
|
|
Address thunk =
|
|
|
|
AllocateJumpTableThunk(slot_start + slot_offset, thunk_slot_buffer,
|
|
|
|
&used_thunk_slots, &thunk_buffers);
|
|
|
|
ZapCode(thunk, kThunkBufferSize);
|
|
|
|
patcher_thunks.push_back(thunk);
|
|
|
|
TRACE(" generated jump thunk: " V8PRIxPTR_FMT "\n",
|
|
|
|
patcher_thunks.back());
|
|
|
|
}
|
2018-08-07 10:33:50 +00:00
|
|
|
|
2019-08-06 14:20:18 +00:00
|
|
|
// Start multiple runner threads that execute the jump table slot
|
|
|
|
// concurrently.
|
2018-08-07 10:33:50 +00:00
|
|
|
std::list<JumpTableRunner> runners;
|
|
|
|
for (int runner = 0; runner < kNumberOfRunnerThreads; ++runner) {
|
|
|
|
runners.emplace_back(slot_start + slot_offset, runner);
|
|
|
|
}
|
2019-08-06 14:20:18 +00:00
|
|
|
// Start multiple patcher thread that concurrently generate code and insert
|
|
|
|
// jumps to that into the jump table slot.
|
|
|
|
std::list<JumpTablePatcher> patchers;
|
|
|
|
// Only one patcher should modify the jump table at a time.
|
|
|
|
base::Mutex jump_table_mutex;
|
|
|
|
for (int i = 0; i < kNumberOfPatcherThreads; ++i) {
|
|
|
|
patchers.emplace_back(slot_start, slot, patcher_thunks[2 * i],
|
|
|
|
patcher_thunks[2 * i + 1], &jump_table_mutex);
|
|
|
|
}
|
2018-08-07 10:33:50 +00:00
|
|
|
global_stop_bit = 0; // Signal runners to keep going.
|
2019-07-29 13:09:02 +00:00
|
|
|
for (auto& runner : runners) CHECK(runner.Start());
|
2019-08-06 14:20:18 +00:00
|
|
|
for (auto& patcher : patchers) CHECK(patcher.Start());
|
|
|
|
for (auto& patcher : patchers) patcher.Join();
|
2018-08-07 10:33:50 +00:00
|
|
|
global_stop_bit = -1; // Signal runners to stop.
|
|
|
|
for (auto& runner : runners) runner.Join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef __
|
|
|
|
#undef TRACE
|
|
|
|
|
|
|
|
} // namespace wasm
|
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|