v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
jump-table-assembler.h
Go to the documentation of this file.
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#ifndef V8_WASM_JUMP_TABLE_ASSEMBLER_H_
6#define V8_WASM_JUMP_TABLE_ASSEMBLER_H_
7
8#if !V8_ENABLE_WEBASSEMBLY
9#error This header should only be included if WebAssembly is enabled.
10#endif // !V8_ENABLE_WEBASSEMBLY
11
14
15namespace v8 {
16namespace internal {
17namespace wasm {
18
19// The jump table is the central dispatch point for all (direct and indirect)
20// invocations in WebAssembly. It holds one slot per function in a module, with
21// each slot containing a dispatch to the currently published {WasmCode} that
22// corresponds to the function.
23//
24// Additionally to this main jump table, there exist special jump tables for
25// other purposes:
26// - the far stub table contains one entry per wasm runtime stub (see
27// {WasmCode::RuntimeStubId}, which jumps to the corresponding embedded
28// builtin, plus (if not the full address space can be reached via the jump
29// table) one entry per wasm function.
30// - the lazy compile table contains one entry per wasm function which jumps to
31// the common {WasmCompileLazy} builtin and passes the function index that was
32// invoked.
33//
34// The main jump table is split into lines of fixed size, with lines laid out
35// consecutively within the executable memory of the {NativeModule}. The slots
36// in turn are consecutive within a line, but do not cross line boundaries.
37//
38// +- L1 -------------------+ +- L2 -------------------+ +- L3 ...
39// | S1 | S2 | ... | Sn | x | | S1 | S2 | ... | Sn | x | | S1 ...
40// +------------------------+ +------------------------+ +---- ...
41//
42// The above illustrates jump table lines {Li} containing slots {Si} with each
43// line containing {n} slots and some padding {x} for alignment purposes.
44// Other jump tables are just consecutive.
45//
46// The main jump table will be patched concurrently while other threads execute
47// it. The code at the new target might also have been emitted concurrently, so
48// we need to ensure that there is proper synchronization between code emission,
49// jump table patching and code execution.
50// On Intel platforms, this all works out of the box because there is cache
51// coherency between i-cache and d-cache.
52// On ARM, it is safe because the i-cache flush after code emission executes an
53// "ic ivau" (Instruction Cache line Invalidate by Virtual Address to Point of
54// Unification), which broadcasts to all cores. A core which sees the jump table
55// update thus also sees the new code. Since the other core does not explicitly
56// execute an "isb" (Instruction Synchronization Barrier), it might still
57// execute the old code afterwards, which is no problem, since that code remains
58// available until it is garbage collected. Garbage collection itself is a
59// synchronization barrier though.
61 public:
62 // Translate an offset into the continuous jump table to a jump table index.
63 static uint32_t SlotOffsetToIndex(uint32_t slot_offset) {
64 uint32_t line_index = slot_offset / kJumpTableLineSize;
65 uint32_t line_offset = slot_offset % kJumpTableLineSize;
66 DCHECK_EQ(0, line_offset % kJumpTableSlotSize);
67 return line_index * kJumpTableSlotsPerLine +
68 line_offset / kJumpTableSlotSize;
69 }
70
71 // Translate a jump table index to an offset into the continuous jump table.
72 static uint32_t JumpSlotIndexToOffset(uint32_t slot_index) {
73 uint32_t line_index = slot_index / kJumpTableSlotsPerLine;
74 uint32_t line_offset =
75 (slot_index % kJumpTableSlotsPerLine) * kJumpTableSlotSize;
76 return line_index * kJumpTableLineSize + line_offset;
77 }
78
79 // Determine the size of a jump table containing the given number of slots.
80 static constexpr uint32_t SizeForNumberOfSlots(uint32_t slot_count) {
81 return ((slot_count + kJumpTableSlotsPerLine - 1) /
82 kJumpTableSlotsPerLine) *
83 kJumpTableLineSize;
84 }
85
86 // Translate a far jump table index to an offset into the table.
87 static uint32_t FarJumpSlotIndexToOffset(uint32_t slot_index) {
88 return slot_index * kFarJumpTableSlotSize;
89 }
90
91 // Translate a far jump table offset to the index into the table.
92 static uint32_t FarJumpSlotOffsetToIndex(uint32_t offset) {
93 DCHECK_EQ(0, offset % kFarJumpTableSlotSize);
94 return offset / kFarJumpTableSlotSize;
95 }
96
97 // Determine the size of a far jump table containing the given number of
98 // slots.
99 static constexpr uint32_t SizeForNumberOfFarJumpSlots(
100 int num_runtime_slots, int num_function_slots) {
101 int num_entries = num_runtime_slots + num_function_slots;
102 return num_entries * kFarJumpTableSlotSize;
103 }
104
105 // Translate a slot index to an offset into the lazy compile table.
106 static uint32_t LazyCompileSlotIndexToOffset(uint32_t slot_index) {
107 return slot_index * kLazyCompileTableSlotSize;
108 }
109
110 // Determine the size of a lazy compile table.
111 static constexpr uint32_t SizeForNumberOfLazyFunctions(uint32_t slot_count) {
112 return slot_count * kLazyCompileTableSlotSize;
113 }
114
115 static void GenerateLazyCompileTable(Address base, uint32_t num_slots,
116 uint32_t num_imported_functions,
117 Address wasm_compile_lazy_target);
118
119 // Initializes the jump table starting at {base} with jumps to the lazy
120 // compile table starting at {lazy_compile_table_start}.
121 static void InitializeJumpsToLazyCompileTable(
122 Address base, uint32_t num_slots, Address lazy_compile_table_start);
123
124 static void GenerateFarJumpTable(WritableJitAllocation& jit_allocation,
125 Address base, Address* stub_targets,
126 int num_runtime_slots,
127 int num_function_slots) {
128 uint32_t table_size =
129 SizeForNumberOfFarJumpSlots(num_runtime_slots, num_function_slots);
130 // Assume enough space, so the Assembler does not try to grow the buffer.
131 JumpTableAssembler jtasm(jit_allocation, base);
132 int offset = 0;
133 for (int index = 0; index < num_runtime_slots + num_function_slots;
134 ++index) {
135 DCHECK_EQ(offset, FarJumpSlotIndexToOffset(index));
136 // Functions slots initially jump to themselves. They are patched before
137 // being used.
138 Address target =
139 index < num_runtime_slots ? stub_targets[index] : base + offset;
140 jtasm.EmitFarJumpSlot(target);
141 offset += kFarJumpTableSlotSize;
142 DCHECK_EQ(offset, jtasm.pc_offset());
143 }
144 FlushInstructionCache(base, table_size);
145 }
146
147 static void PatchJumpTableSlot(WritableJumpTablePair& jump_table_pair,
148 Address jump_table_slot,
149 Address far_jump_table_slot, Address target) {
150 // First, try to patch the jump table slot.
151 JumpTableAssembler jtasm(jump_table_pair.jump_table(), jump_table_slot);
152 if (!jtasm.EmitJumpSlot(target)) {
153 // If that fails, we need to patch the far jump table slot, and then
154 // update the jump table slot to jump to this far jump table slot.
155 DCHECK_NE(kNullAddress, far_jump_table_slot);
156 JumpTableAssembler::PatchFarJumpSlot(jump_table_pair.far_jump_table(),
157 far_jump_table_slot, target);
158 CHECK(jtasm.EmitJumpSlot(far_jump_table_slot));
159 }
160 // We write nops here instead of skipping to avoid partial instructions in
161 // the jump table. Partial instructions can cause problems for the
162 // disassembler.
163 DCHECK_EQ(kJumpTableSlotSize, jtasm.pc_offset());
164 FlushInstructionCache(jump_table_slot, kJumpTableSlotSize);
165 }
166
167 private:
168 // Instantiate a {JumpTableAssembler} for patching.
170 Address slot_addr)
171 : jit_allocation_(jit_allocation),
172 buffer_start_(slot_addr),
173 pc_(slot_addr) {}
174
176 const Address buffer_start_;
177 Address pc_;
178
179 // To allow concurrent patching of the jump table entries, we need to ensure
180 // atomicity of the jump table updates. On most architectures, unaligned
181 // writes are atomic if they don't cross a cache line. The AMD manual however
182 // only guarantees atomicity if the write happens inside a naturally aligned
183 // qword. The jump table line size has been chosen to satisfy this.
184#if V8_TARGET_ARCH_X64
185#ifdef V8_ENABLE_CET_IBT
186 static constexpr int kJumpTableSlotSize = 16;
187#else // V8_ENABLE_CET_IBT
188 static constexpr int kJumpTableSlotSize = 8;
189#endif
190 static constexpr int kJumpTableLineSize = kJumpTableSlotSize;
191 static constexpr int kFarJumpTableSlotSize = 16;
192 static constexpr int kLazyCompileTableSlotSize = 10;
193#elif V8_TARGET_ARCH_IA32
194 static constexpr int kJumpTableLineSize = 64;
195 static constexpr int kJumpTableSlotSize = 5;
196 static constexpr int kFarJumpTableSlotSize = 5;
197 static constexpr int kLazyCompileTableSlotSize = 10;
198#elif V8_TARGET_ARCH_ARM
199 static constexpr int kJumpTableLineSize = 2 * kInstrSize;
200 static constexpr int kJumpTableSlotSize = 2 * kInstrSize;
201 static constexpr int kFarJumpTableSlotSize = 2 * kInstrSize;
202 static constexpr int kLazyCompileTableSlotSize = 4 * kInstrSize;
203#elif V8_TARGET_ARCH_ARM64
204#if V8_ENABLE_CONTROL_FLOW_INTEGRITY
205 static constexpr int kJumpTableLineSize = 2 * kInstrSize;
206 static constexpr int kJumpTableSlotSize = 2 * kInstrSize;
207#else
208 static constexpr int kJumpTableLineSize = 1 * kInstrSize;
209 static constexpr int kJumpTableSlotSize = 1 * kInstrSize;
210#endif
211 static constexpr int kFarJumpTableSlotSize = 4 * kInstrSize;
212 static constexpr int kLazyCompileTableSlotSize = 4 * kInstrSize;
213#elif V8_TARGET_ARCH_S390X
214 static constexpr int kJumpTableLineSize = 128;
215 static constexpr int kJumpTableSlotSize = 8;
216 static constexpr int kFarJumpTableSlotSize = 24;
217 static constexpr int kLazyCompileTableSlotSize = 32;
218#elif V8_TARGET_ARCH_PPC64
219 static constexpr int kJumpTableLineSize = 64;
220 static constexpr int kJumpTableSlotSize = 1 * kInstrSize;
221 static constexpr int kFarJumpTableSlotSize = 12 * kInstrSize;
222 static constexpr int kLazyCompileTableSlotSize = 12 * kInstrSize;
223#elif V8_TARGET_ARCH_MIPS
224 static constexpr int kJumpTableLineSize = 8 * kInstrSize;
225 static constexpr int kJumpTableSlotSize = 8 * kInstrSize;
226 static constexpr int kFarJumpTableSlotSize = 4 * kInstrSize;
227 static constexpr int kLazyCompileTableSlotSize = 6 * kInstrSize;
228#elif V8_TARGET_ARCH_MIPS64
229 static constexpr int kJumpTableLineSize = 8 * kInstrSize;
230 static constexpr int kJumpTableSlotSize = 8 * kInstrSize;
231 static constexpr int kFarJumpTableSlotSize = 8 * kInstrSize;
232 static constexpr int kLazyCompileTableSlotSize = 10 * kInstrSize;
233#elif V8_TARGET_ARCH_RISCV64
234 static constexpr int kJumpTableSlotSize = 2 * kInstrSize;
235 static constexpr int kJumpTableLineSize = kJumpTableSlotSize;
236 static constexpr int kFarJumpTableSlotSize = 6 * kInstrSize;
237 static constexpr int kLazyCompileTableSlotSize = 3 * kInstrSize;
238#elif V8_TARGET_ARCH_RISCV32
239 static constexpr int kJumpTableSlotSize = 5 * kInstrSize;
240 static constexpr int kJumpTableLineSize = kJumpTableSlotSize;
241 static constexpr int kFarJumpTableSlotSize = kJumpTableSlotSize;
242 static constexpr int kLazyCompileTableSlotSize = 3 * kInstrSize;
243#elif V8_TARGET_ARCH_LOONG64
244 static constexpr int kJumpTableLineSize = 1 * kInstrSize;
245 static constexpr int kJumpTableSlotSize = 1 * kInstrSize;
246 static constexpr int kFarJumpTableSlotSize = 6 * kInstrSize;
247 static constexpr int kLazyCompileTableSlotSize = 3 * kInstrSize;
248#else
249#error Unknown architecture.
250#endif
251
252 static constexpr int kJumpTableSlotsPerLine =
253 kJumpTableLineSize / kJumpTableSlotSize;
254 static_assert(kJumpTableSlotsPerLine >= 1);
255
256 void EmitLazyCompileJumpSlot(uint32_t func_index,
257 Address lazy_compile_target);
258
259 // Returns {true} if the jump fits in the jump table slot, {false} otherwise.
260 bool EmitJumpSlot(Address target);
261
262 // Initially emit a far jump slot.
263 void EmitFarJumpSlot(Address target);
264
265 // Patch an existing far jump slot, and make sure that this updated eventually
266 // becomes available to all execution units that might execute this code.
267 static void PatchFarJumpSlot(WritableJitAllocation& jit_allocation,
268 Address slot, Address target);
269
270 void SkipUntil(int offset);
271
272 int pc_offset() const { return static_cast<int>(pc_ - buffer_start_); }
273
274 template <typename V>
275 void emit(V value);
276
277 template <typename V>
278 void emit(V value, RelaxedStoreTag);
279};
280
281} // namespace wasm
282} // namespace internal
283} // namespace v8
284
285#endif // V8_WASM_JUMP_TABLE_ASSEMBLER_H_
static uint32_t FarJumpSlotOffsetToIndex(uint32_t offset)
static void PatchJumpTableSlot(WritableJumpTablePair &jump_table_pair, Address jump_table_slot, Address far_jump_table_slot, Address target)
static uint32_t FarJumpSlotIndexToOffset(uint32_t slot_index)
static constexpr uint32_t SizeForNumberOfLazyFunctions(uint32_t slot_count)
static constexpr uint32_t SizeForNumberOfSlots(uint32_t slot_count)
void emit(V value, RelaxedStoreTag)
static uint32_t JumpSlotIndexToOffset(uint32_t slot_index)
static constexpr uint32_t SizeForNumberOfFarJumpSlots(int num_runtime_slots, int num_function_slots)
JumpTableAssembler(WritableJitAllocation &jit_allocation, Address slot_addr)
static void GenerateFarJumpTable(WritableJitAllocation &jit_allocation, Address base, Address *stub_targets, int num_runtime_slots, int num_function_slots)
void EmitLazyCompileJumpSlot(uint32_t func_index, Address lazy_compile_target)
static uint32_t LazyCompileSlotIndexToOffset(uint32_t slot_index)
static void PatchFarJumpSlot(WritableJitAllocation &jit_allocation, Address slot, Address target)
static uint32_t SlotOffsetToIndex(uint32_t slot_offset)
OptionalOpIndex index
int32_t offset
void FlushInstructionCache(void *start, size_t size)
constexpr uint8_t kInstrSize
Definition c-api.cc:87
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define V8_EXPORT_PRIVATE
Definition macros.h:460