Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include <bitset>
6 :
7 : #include "src/assembler-inl.h"
8 : #include "src/macro-assembler-inl.h"
9 : #include "src/simulator.h"
10 : #include "src/utils.h"
11 : #include "src/wasm/jump-table-assembler.h"
12 : #include "test/cctest/cctest.h"
13 : #include "test/common/assembler-tester.h"
14 :
15 : namespace v8 {
16 : namespace internal {
17 : namespace wasm {
18 :
19 : #if 0
20 : #define TRACE(...) PrintF(__VA_ARGS__)
21 : #else
22 : #define TRACE(...)
23 : #endif
24 :
25 : #define __ masm.
26 :
27 : namespace {
28 :
29 : static volatile int global_stop_bit = 0;
30 :
31 : constexpr int kJumpTableSlotCount = 128;
32 : constexpr uint32_t kJumpTableSize =
33 : JumpTableAssembler::SizeForNumberOfSlots(kJumpTableSlotCount);
34 :
35 : #if V8_TARGET_ARCH_ARM64
36 : constexpr uint32_t kAvailableBufferSlots =
37 : (kMaxWasmCodeMemory - kJumpTableSize) / AssemblerBase::kMinimalBufferSize;
38 : constexpr uint32_t kBufferSlotStartOffset =
39 : RoundUp<AssemblerBase::kMinimalBufferSize>(kJumpTableSize);
40 : #else
41 : constexpr uint32_t kAvailableBufferSlots = 0;
42 : #endif
43 :
44 1024 : Address GenerateJumpTableThunk(
45 : Address jump_target, byte* thunk_slot_buffer,
46 : std::bitset<kAvailableBufferSlots>* used_slots,
47 : std::vector<std::unique_ptr<TestingAssemblerBuffer>>* thunk_buffers) {
48 : #if V8_TARGET_ARCH_ARM64
49 : // To guarantee that the branch range lies within the near-call range,
50 : // generate the thunk in the same (kMaxWasmCodeMemory-sized) buffer as the
51 : // jump_target itself.
52 : //
53 : // Allocate a slot that we haven't already used. This is necessary because
54 : // each test iteration expects to generate two unique addresses and we leave
55 : // each slot executable (and not writable).
56 : base::RandomNumberGenerator* rng =
57 : CcTest::i_isolate()->random_number_generator();
58 : // Ensure a chance of completion without too much thrashing.
59 : DCHECK(used_slots->count() < (used_slots->size() / 2));
60 : int buffer_index;
61 : do {
62 : buffer_index = rng->NextInt(kAvailableBufferSlots);
63 : } while (used_slots->test(buffer_index));
64 : used_slots->set(buffer_index);
65 : byte* buffer =
66 : thunk_slot_buffer + buffer_index * AssemblerBase::kMinimalBufferSize;
67 :
68 : DCHECK(TurboAssembler::IsNearCallOffset(
69 : (reinterpret_cast<byte*>(jump_target) - buffer) / kInstrSize));
70 :
71 : #else
72 : USE(thunk_slot_buffer);
73 : USE(used_slots);
74 2048 : thunk_buffers->emplace_back(AllocateAssemblerBuffer(
75 1024 : AssemblerBase::kMinimalBufferSize, GetRandomMmapAddr()));
76 1024 : byte* buffer = thunk_buffers->back()->start();
77 : #endif
78 :
79 : MacroAssembler masm(
80 : nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
81 3072 : ExternalAssemblerBuffer(buffer, AssemblerBase::kMinimalBufferSize));
82 :
83 1024 : Label exit;
84 : Register scratch = kReturnRegister0;
85 : Address stop_bit_address = reinterpret_cast<Address>(&global_stop_bit);
86 : #if V8_TARGET_ARCH_X64
87 : __ Move(scratch, stop_bit_address, RelocInfo::NONE);
88 2048 : __ testl(MemOperand(scratch, 0), Immediate(1));
89 1024 : __ j(not_zero, &exit);
90 1024 : __ Jump(jump_target, RelocInfo::NONE);
91 : #elif V8_TARGET_ARCH_IA32
92 : __ Move(scratch, Immediate(stop_bit_address, RelocInfo::NONE));
93 : __ test(MemOperand(scratch, 0), Immediate(1));
94 : __ j(not_zero, &exit);
95 : __ jmp(jump_target, RelocInfo::NONE);
96 : #elif V8_TARGET_ARCH_ARM
97 : __ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
98 : __ ldr(scratch, MemOperand(scratch, 0));
99 : __ tst(scratch, Operand(1));
100 : __ b(ne, &exit);
101 : __ Jump(jump_target, RelocInfo::NONE);
102 : #elif V8_TARGET_ARCH_ARM64
103 : __ Mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
104 : __ Ldr(scratch, MemOperand(scratch, 0));
105 : __ Tbnz(scratch, 0, &exit);
106 : __ Mov(scratch, Immediate(jump_target, RelocInfo::NONE));
107 : __ Br(scratch);
108 : #elif V8_TARGET_ARCH_PPC64
109 : __ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
110 : __ LoadP(scratch, MemOperand(scratch));
111 : __ cmpi(scratch, Operand::Zero());
112 : __ bne(&exit);
113 : __ mov(scratch, Operand(jump_target, RelocInfo::NONE));
114 : __ Jump(scratch);
115 : #elif V8_TARGET_ARCH_S390X
116 : __ mov(scratch, Operand(stop_bit_address, RelocInfo::NONE));
117 : __ LoadP(scratch, MemOperand(scratch));
118 : __ CmpP(scratch, Operand(0));
119 : __ bne(&exit);
120 : __ mov(scratch, Operand(jump_target, RelocInfo::NONE));
121 : __ Jump(scratch);
122 : #elif V8_TARGET_ARCH_MIPS64
123 : __ li(scratch, Operand(stop_bit_address, RelocInfo::NONE));
124 : __ Lw(scratch, MemOperand(scratch, 0));
125 : __ Branch(&exit, ne, scratch, Operand(zero_reg));
126 : __ Jump(jump_target, RelocInfo::NONE);
127 : #elif V8_TARGET_ARCH_MIPS
128 : __ li(scratch, Operand(stop_bit_address, RelocInfo::NONE));
129 : __ lw(scratch, MemOperand(scratch, 0));
130 : __ Branch(&exit, ne, scratch, Operand(zero_reg));
131 : __ Jump(jump_target, RelocInfo::NONE);
132 : #else
133 : #error Unsupported architecture
134 : #endif
135 1024 : __ bind(&exit);
136 1024 : __ Ret();
137 :
138 1024 : CodeDesc desc;
139 : masm.GetCode(nullptr, &desc);
140 2048 : return reinterpret_cast<Address>(buffer);
141 : }
142 :
143 2560 : class JumpTableRunner : public v8::base::Thread {
144 : public:
145 : JumpTableRunner(Address slot_address, int runner_id)
146 : : Thread(Options("JumpTableRunner")),
147 : slot_address_(slot_address),
148 2560 : runner_id_(runner_id) {}
149 :
150 2555 : void Run() override {
151 : TRACE("Runner #%d is starting ...\n", runner_id_);
152 2555 : GeneratedCode<void>::FromAddress(CcTest::i_isolate(), slot_address_).Call();
153 : TRACE("Runner #%d is stopping ...\n", runner_id_);
154 : USE(runner_id_);
155 2429 : }
156 :
157 : private:
158 : Address slot_address_;
159 : int runner_id_;
160 : };
161 :
162 512 : class JumpTablePatcher : public v8::base::Thread {
163 : public:
164 : JumpTablePatcher(Address slot_start, uint32_t slot_index, Address thunk1,
165 : Address thunk2)
166 : : Thread(Options("JumpTablePatcher")),
167 : slot_start_(slot_start),
168 : slot_index_(slot_index),
169 512 : thunks_{thunk1, thunk2} {}
170 :
171 512 : void Run() override {
172 : TRACE("Patcher is starting ...\n");
173 : constexpr int kNumberOfPatchIterations = 64;
174 66048 : for (int i = 0; i < kNumberOfPatchIterations; ++i) {
175 : TRACE(" patch slot " V8PRIxPTR_FMT " to thunk #%d\n",
176 : slot_start_ + JumpTableAssembler::SlotIndexToOffset(slot_index_),
177 : i % 2);
178 32768 : JumpTableAssembler::PatchJumpTableSlot(
179 65536 : slot_start_, slot_index_, thunks_[i % 2], WasmCode::kFlushICache);
180 : }
181 : TRACE("Patcher is stopping ...\n");
182 512 : }
183 :
184 : private:
185 : Address slot_start_;
186 : uint32_t slot_index_;
187 : Address thunks_[2];
188 : };
189 :
190 : } // namespace
191 :
192 : // This test is intended to stress concurrent patching of jump-table slots. It
193 : // uses the following setup:
194 : // 1) Picks a particular slot of the jump-table. Slots are iterated over to
195 : // ensure multiple entries (at different offset alignments) are tested.
196 : // 2) Starts multiple runners that spin through the above slot. The runners
197 : // use thunk code that will jump to the same jump-table slot repeatedly
198 : // until the {global_stop_bit} indicates a test-end condition.
199 : // 3) Start a patcher that repeatedly patches the jump-table slot back and
200 : // forth between two thunk. If there is a race then chances are high that
201 : // one of the runners is currently executing the jump-table slot.
202 26067 : TEST(JumpTablePatchingStress) {
203 : constexpr int kNumberOfRunnerThreads = 5;
204 :
205 : #if V8_TARGET_ARCH_ARM64
206 : // We need the branches (from GenerateJumpTableThunk) to be within near-call
207 : // range of the jump table slots. The address hint to AllocateAssemblerBuffer
208 : // is not reliable enough to guarantee that we can always achieve this with
209 : // separate allocations, so for Arm64 we generate all code in a single
210 : // kMaxMasmCodeMemory-sized chunk.
211 : //
212 : // TODO(wasm): Currently {kMaxWasmCodeMemory} limits code sufficiently, so
213 : // that the jump table only supports {near_call} distances.
214 : STATIC_ASSERT(kMaxWasmCodeMemory >= kJumpTableSize);
215 : auto buffer = AllocateAssemblerBuffer(kMaxWasmCodeMemory);
216 : byte* thunk_slot_buffer = buffer->start() + kBufferSlotStartOffset;
217 : #else
218 : auto buffer = AllocateAssemblerBuffer(kJumpTableSize);
219 : byte* thunk_slot_buffer = nullptr;
220 : #endif
221 : std::bitset<kAvailableBufferSlots> used_thunk_slots;
222 4 : buffer->MakeWritableAndExecutable();
223 :
224 : // Iterate through jump-table slots to hammer at different alignments within
225 : // the jump-table, thereby increasing stress for variable-length ISAs.
226 4 : Address slot_start = reinterpret_cast<Address>(buffer->start());
227 1028 : for (int slot = 0; slot < kJumpTableSlotCount; ++slot) {
228 : TRACE("Hammering on jump table slot #%d ...\n", slot);
229 512 : uint32_t slot_offset = JumpTableAssembler::SlotIndexToOffset(slot);
230 512 : std::vector<std::unique_ptr<TestingAssemblerBuffer>> thunk_buffers;
231 : Address thunk1 =
232 512 : GenerateJumpTableThunk(slot_start + slot_offset, thunk_slot_buffer,
233 512 : &used_thunk_slots, &thunk_buffers);
234 : Address thunk2 =
235 : GenerateJumpTableThunk(slot_start + slot_offset, thunk_slot_buffer,
236 512 : &used_thunk_slots, &thunk_buffers);
237 : TRACE(" generated thunk1: " V8PRIxPTR_FMT "\n", thunk1);
238 : TRACE(" generated thunk2: " V8PRIxPTR_FMT "\n", thunk2);
239 : JumpTableAssembler::PatchJumpTableSlot(slot_start, slot, thunk1,
240 512 : WasmCode::kFlushICache);
241 :
242 2560 : for (auto& buf : thunk_buffers) buf->MakeExecutable();
243 : // Start multiple runner threads and a patcher thread that hammer on the
244 : // same jump-table slot concurrently.
245 : std::list<JumpTableRunner> runners;
246 3072 : for (int runner = 0; runner < kNumberOfRunnerThreads; ++runner) {
247 2560 : runners.emplace_back(slot_start + slot_offset, runner);
248 : }
249 : JumpTablePatcher patcher(slot_start, slot, thunk1, thunk2);
250 512 : global_stop_bit = 0; // Signal runners to keep going.
251 3072 : for (auto& runner : runners) runner.Start();
252 512 : patcher.Start();
253 512 : patcher.Join();
254 512 : global_stop_bit = -1; // Signal runners to stop.
255 3072 : for (auto& runner : runners) runner.Join();
256 : }
257 4 : }
258 :
259 : #undef __
260 : #undef TRACE
261 :
262 : } // namespace wasm
263 : } // namespace internal
264 78189 : } // namespace v8
|