v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
js-dispatch-table.h
Go to the documentation of this file.
1// Copyright 2024 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_SANDBOX_JS_DISPATCH_TABLE_H_
6#define V8_SANDBOX_JS_DISPATCH_TABLE_H_
7
8#include "include/v8config.h"
10#include "src/base/memory.h"
11#include "src/common/globals.h"
12#include "src/runtime/runtime.h"
14
15#ifdef V8_ENABLE_LEAPTIERING
16
17namespace v8 {
18namespace internal {
19
20class Isolate;
21class Counters;
22class Code;
23enum class TieringBuiltin;
24
33struct JSDispatchEntry {
34 // We write-protect the JSDispatchTable on platforms that support it for
35 // forward-edge CFI.
36 static constexpr bool IsWriteProtected = true;
37
38 inline void MakeJSDispatchEntry(Address object, Address entrypoint,
39 uint16_t parameter_count, bool mark_as_alive);
40
41 inline Address GetEntrypoint() const;
42 inline Address GetCodePointer() const;
43 inline Tagged<Code> GetCode() const;
44 inline uint16_t GetParameterCount() const;
45
46 inline void SetCodeAndEntrypointPointer(Address new_object,
47 Address new_entrypoint);
48 inline void SetEntrypointPointer(Address new_entrypoint);
49
50 // Make this entry a freelist entry, containing the index of the next entry
51 // on the freelist.
52 inline void MakeFreelistEntry(uint32_t next_entry_index);
53
54 // Returns true if this entry is a freelist entry.
55 inline bool IsFreelistEntry() const;
56
57 // Get the index of the next entry on the freelist. This method may be
58 // called even when the entry is not a freelist entry. However, the result
59 // is only valid if this is a freelist entry. This behaviour is required
60 // for efficient entry allocation, see TryAllocateEntryFromFreelist.
61 inline uint32_t GetNextFreelistEntryIndex() const;
62
63 // Mark this entry as alive during garbage collection.
64 inline void Mark();
65
66 // Unmark this entry during sweeping.
67 inline void Unmark();
68
69 // Test whether this entry is currently marked as alive.
70 inline bool IsMarked() const;
71
72 // Constants for access from generated code.
73 // These are static_assert'ed to be correct in CheckFieldOffsets().
74 static constexpr uintptr_t kEntrypointOffset = 0;
75 static constexpr uintptr_t kCodeObjectOffset = kSystemPointerSize;
76 static constexpr size_t kParameterCountSize = 2;
77
78#if defined(V8_TARGET_ARCH_64_BIT)
79 // Freelist entries contain the index of the next free entry in their lower 32
80 // bits and are tagged with this tag.
81 static constexpr Address kFreeEntryTag = 0xffff000000000000ull;
82#ifdef V8_TARGET_BIG_ENDIAN
83 // 2-byte parameter count is on the least significant side of encoded_word_.
84 static constexpr int kBigEndianParamCountOffset =
85 sizeof(Address) - sizeof(uint16_t);
86 static constexpr uintptr_t kParameterCountOffset =
87 kCodeObjectOffset + kBigEndianParamCountOffset;
88#else
89 static constexpr uintptr_t kParameterCountOffset = kCodeObjectOffset;
90#endif // V8_TARGET_BIG_ENDIAN
91 static constexpr uint32_t kObjectPointerShift = 16;
92 static constexpr uint32_t kParameterCountMask = 0xffff;
93#elif defined(V8_TARGET_ARCH_32_BIT)
94 static constexpr uintptr_t kParameterCountOffset =
95 kCodeObjectOffset + kSystemPointerSize;
96 static constexpr uint32_t kObjectPointerShift = 0;
97 static constexpr uint32_t kParameterCountMask = 0x0;
98#else
99#error "Unsupported Architecture"
100#endif
101
102 static void CheckFieldOffsets();
103
104 private:
105 friend class JSDispatchTable;
106
107 // The first word contains the pointer to the (executable) entrypoint.
108 std::atomic<Address> entrypoint_;
109
110 // On 64 bit architectures the second word of the entry contains (1) the
111 // pointer to the code object associated with this entry, (2) the marking bit
112 // of the entry in the LSB of the object pointer (which must be unused as the
113 // address must be aligned), and (3) the 16-bit parameter count. The parameter
114 // count is stored in the lower 16 bits and therefore the pointer is shifted
115 // to the left. The final format therefore looks as follows:
116 //
117 // +----------------------+---------------+-------------------+
118 // | Bits 63 ... 17 | Bit 16 | Bits 15 ... 0 |
119 // | HeapObject pointer | Marking bit | Parameter count |
120 // +----------------------+---------------+-------------------+
121 //
122 // On 32 bit architectures only the mark bit is shared with the pointer.
123 //
124 // +----------------------+---------------+
125 // | Bits 32 ... 1 | Bit 0 |
126 // | HeapObject pointer | Marking bit |
127 // +----------------------+---------------+
128 //
129 // TODO(olivf): Find a better format that allows us to write atomically to the
130 // individual parts and unify with 32 bit. For instance we could try to store
131 // the code pointer in some compressd format, such that it fits into 32 bits.
132
133 static constexpr Address kMarkingBit = 1 << kObjectPointerShift;
134 std::atomic<Address> encoded_word_;
135
136#ifdef V8_TARGET_ARCH_32_BIT
137 // TODO(olivf): Investigate if we could shrink the entry size on 32bit
138 // platforms to 12 bytes.
139 std::atomic<uint16_t> parameter_count_;
140 // 16 bits of padding
141 std::atomic<uint32_t> next_free_entry_;
142#endif // V8_TARGET_ARCH_32_BIT
143};
144
145static_assert(sizeof(JSDispatchEntry) == kJSDispatchTableEntrySize);
146
169class V8_EXPORT_PRIVATE JSDispatchTable
170 : public ExternalEntityTable<JSDispatchEntry,
171 kJSDispatchTableReservationSize> {
172 using Base =
173 ExternalEntityTable<JSDispatchEntry, kJSDispatchTableReservationSize>;
174
175 public:
176#ifdef V8_ENABLE_SANDBOX
177 static_assert(kMaxJSDispatchEntries == kMaxCapacity);
178#endif // V8_ENABLE_SANDBOX
179 static_assert(!kSupportsCompaction);
180
181 JSDispatchTable() = default;
182 JSDispatchTable(const JSDispatchTable&) = delete;
183 JSDispatchTable& operator=(const JSDispatchTable&) = delete;
184
185 // The Spaces used by a JSDispatchTable.
186 using Space = Base::SpaceWithBlackAllocationSupport;
187
188 // Retrieves the entrypoint of the entry referenced by the given handle.
189 inline Address GetEntrypoint(JSDispatchHandle handle);
190
191 // Retrieves the Code stored in the entry referenced by the given handle.
192 //
193 // TODO(saelo): in the future, we might store either a Code or a
194 // BytecodeArray in the entries. At that point, this could be changed to
195 // return a Tagged<Union<Code, BytecodeArray>>.
196 inline Tagged<Code> GetCode(JSDispatchHandle handle);
197
198 // Returns the address of the Code object stored in the specified entry.
199 inline Address GetCodeAddress(JSDispatchHandle handle);
200
201 // Retrieves the parameter count of the entry referenced by the given handle.
202 inline uint16_t GetParameterCount(JSDispatchHandle handle);
203
204 // Updates the entry referenced by the given handle to the given Code and its
205 // entrypoint. The code must be compatible with the specified entry. In
206 // particular, the two must use the same parameter count.
207 // NB: Callee must emit JS_DISPATCH_HANDLE_WRITE_BARRIER if needed!
208 inline void SetCodeNoWriteBarrier(JSDispatchHandle handle,
209 Tagged<Code> new_code);
210
211 // Execute a tiering builtin instead of the actual code. Leaves the Code
212 // pointer untouched and changes only the entrypoint.
213 inline void SetTieringRequest(JSDispatchHandle handle, TieringBuiltin builtin,
214 Isolate* isolate);
215 inline void SetCodeKeepTieringRequestNoWriteBarrier(JSDispatchHandle handle,
216 Tagged<Code> new_code);
217 // Resets the entrypoint to the code's entrypoint.
218 inline void ResetTieringRequest(JSDispatchHandle handle);
219 // Check if and/or which tiering builtin is installed.
220 inline bool IsTieringRequested(JSDispatchHandle handle);
221 inline bool IsTieringRequested(JSDispatchHandle handle,
222 TieringBuiltin builtin, Isolate* isolate);
223
224 // Allocates a new entry in the table and initialize it.
225 //
226 // Note: If possible allocate dispatch handles through the factory.
227 //
228 // This method is atomic and can be called from background threads.
229 inline JSDispatchHandle AllocateAndInitializeEntry(Space* space,
230 uint16_t parameter_count,
231 Tagged<Code> code);
232 inline std::optional<JSDispatchHandle> TryAllocateAndInitializeEntry(
233 Space* space, uint16_t parameter_count, Tagged<Code> code);
234
235 // The following methods are used to pre allocate entries and then initialize
236 // them later.
237 JSDispatchHandle PreAllocateEntries(Space* space, int num,
238 bool ensure_static_handles);
239 bool PreAllocatedEntryNeedsInitialization(Space* space,
240 JSDispatchHandle handle);
241 void InitializePreAllocatedEntry(Space* space, JSDispatchHandle handle,
242 Tagged<Code> code, uint16_t parameter_count);
243
244 // Can be used to statically predict the handles if the pre allocated entries
245 // are in the overall first read only segment of the whole table.
246#if V8_STATIC_DISPATCH_HANDLES_BOOL
247 static JSDispatchHandle GetStaticHandleForReadOnlySegmentEntry(int index) {
248 return IndexToHandle(kInternalNullEntryIndex + 1 + index);
249 }
250#endif // V8_STATIC_DISPATCH_HANDLES_BOOL
251 static bool InReadOnlySegment(JSDispatchHandle handle) {
252 return HandleToIndex(handle) <= kEndOfInternalReadOnlySegment;
253 }
254 static int OffsetOfEntry(JSDispatchHandle handle) {
255 return JSDispatchTable::HandleToIndex(handle)
257 }
258
259 // Marks the specified entry as alive.
260 //
261 // This method is atomic and can be called from background threads.
262 inline void Mark(JSDispatchHandle handle);
263
264 // Frees all unmarked entries in the given space.
265 //
266 // This method must only be called while mutator threads are stopped as it is
267 // not safe to allocate table entries while a space is being swept.
268 //
269 // Returns the number of live entries after sweeping.
270 template <typename Callback>
271 uint32_t Sweep(Space* space, Counters* counters, Callback callback);
272
273 // Iterate over all active entries in the given space.
274 //
275 // The callback function will be invoked once for every entry that is
276 // currently in use, i.e. has been allocated and not yet freed, and will
277 // receive the handle of that entry.
278 template <typename Callback>
279 void IterateActiveEntriesIn(Space* space, Callback callback);
280
281 template <typename Callback>
282 void IterateMarkedEntriesIn(Space* space, Callback callback);
283
284 // The base address of this table, for use in JIT compilers.
285 Address base_address() const { return base(); }
286
287#ifdef DEBUG
288 bool IsMarked(JSDispatchHandle handle);
289#endif // DEBUG
290#if defined(DEBUG) || defined(VERIFY_HEAP)
291 inline void VerifyEntry(JSDispatchHandle handle, Space* space,
292 Space* ro_space);
293#endif // defined(DEBUG) || defined(VERIFY_HEAP)
294
295 void PrintEntry(JSDispatchHandle handle);
296 void PrintCurrentTieringRequest(JSDispatchHandle handle, Isolate* isolate,
297 std::ostream& os);
298
299 static constexpr bool kWriteBarrierSetsEntryMarkBit = true;
300
301 private:
302 static inline bool IsCompatibleCode(Tagged<Code> code,
303 uint16_t parameter_count);
304
305 inline void SetCodeAndEntrypointNoWriteBarrier(JSDispatchHandle handle,
306 Tagged<Code> new_code,
307 Address entrypoint);
308
309 static uint32_t HandleToIndex(JSDispatchHandle handle) {
310 uint32_t index = handle.value() >> kJSDispatchHandleShift;
311 DCHECK_EQ(handle.value(), index << kJSDispatchHandleShift);
312 return index;
313 }
314 static JSDispatchHandle IndexToHandle(uint32_t index) {
315 JSDispatchHandle handle(index << kJSDispatchHandleShift);
316 DCHECK_EQ(index, handle.value() >> kJSDispatchHandleShift);
317 return handle;
318 }
319
320 friend class MarkCompactCollector;
321};
322
323} // namespace internal
324} // namespace v8
325
326#endif // V8_ENABLE_LEAPTIERING
327
328#endif // V8_SANDBOX_JS_DISPATCH_TABLE_H_
int16_t parameter_count
Definition builtins.cc:67
OptionalOpIndex index
TNode< Object > callback
unsigned short uint16_t
Definition unicode.cc:39
uintptr_t Address
Definition memory.h:13
V8_INLINE IndirectHandle< T > handle(Tagged< T > object, Isolate *isolate)
Definition handles-inl.h:72
base::StrongAlias< JSDispatchHandleAliasTag, uint32_t > JSDispatchHandle
Definition globals.h:557
constexpr int kSystemPointerSize
Definition globals.h:410
constexpr size_t kMaxJSDispatchEntries
Definition globals.h:570
constexpr int kJSDispatchTableEntrySizeLog2
Definition globals.h:562
constexpr int kJSDispatchTableEntrySize
Definition globals.h:561
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define V8_EXPORT_PRIVATE
Definition macros.h:460