v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
js-dispatch-table-inl.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_INL_H_
6#define V8_SANDBOX_JS_DISPATCH_TABLE_INL_H_
7
9// Include the non-inl header before the rest of the headers.
10
16
17#ifdef V8_ENABLE_LEAPTIERING
18
19namespace v8 {
20namespace internal {
21
22void JSDispatchEntry::MakeJSDispatchEntry(Address object, Address entrypoint,
23 uint16_t parameter_count,
24 bool mark_as_alive) {
25 DCHECK_EQ(object & kHeapObjectTag, 0);
26 DCHECK_EQ((object << kObjectPointerShift) >> kObjectPointerShift, object);
27
28 Address payload =
29 (object << kObjectPointerShift) | (parameter_count & kParameterCountMask);
30 DCHECK(!(payload & kMarkingBit));
31 if (mark_as_alive) payload |= kMarkingBit;
32#ifdef V8_TARGET_ARCH_32_BIT
33 parameter_count_.store(parameter_count, std::memory_order_relaxed);
34 next_free_entry_.store(0, std::memory_order_relaxed);
35#endif
36 encoded_word_.store(payload, std::memory_order_relaxed);
37 entrypoint_.store(entrypoint, std::memory_order_relaxed);
38 DCHECK(!IsFreelistEntry());
39}
40
41Address JSDispatchEntry::GetEntrypoint() const {
42 CHECK(!IsFreelistEntry());
43 return entrypoint_.load(std::memory_order_relaxed);
44}
45
46Address JSDispatchEntry::GetCodePointer() const {
47 CHECK(!IsFreelistEntry());
48 // The pointer tag bit (LSB) of the object pointer is used as marking bit,
49 // and so may be 0 or 1 here. As the return value is a tagged pointer, the
50 // bit must be 1 when returned, so we need to set it here.
51 Address payload = encoded_word_.load(std::memory_order_relaxed);
52 return (payload >> kObjectPointerShift) | kHeapObjectTag;
53}
54
55Tagged<Code> JSDispatchEntry::GetCode() const {
56 return Cast<Code>(Tagged<Object>(GetCodePointer()));
57}
58
59uint16_t JSDispatchEntry::GetParameterCount() const {
60 // Loading a pointer out of a freed entry will always result in an invalid
61 // pointer (e.g. upper bits set or nullptr). However, here we're just loading
62 // an integer (the parameter count), so we probably want to make sure that
63 // we're not getting that from a freed entry.
64 CHECK(!IsFreelistEntry());
65#ifdef V8_TARGET_ARCH_32_BIT
66 return parameter_count_.load(std::memory_order_relaxed);
67#else
68 static_assert(kParameterCountMask != 0);
69 Address payload = encoded_word_.load(std::memory_order_relaxed);
70 return payload & kParameterCountMask;
71#endif
72}
73
74Tagged<Code> JSDispatchTable::GetCode(JSDispatchHandle handle) {
75 uint32_t index = HandleToIndex(handle);
76 return at(index).GetCode();
77}
78
79void JSDispatchTable::SetCodeNoWriteBarrier(JSDispatchHandle handle,
80 Tagged<Code> new_code) {
81 SetCodeAndEntrypointNoWriteBarrier(handle, new_code,
82 new_code->instruction_start());
83}
84
85void JSDispatchTable::SetCodeKeepTieringRequestNoWriteBarrier(
87 if (IsTieringRequested(handle)) {
88 SetCodeAndEntrypointNoWriteBarrier(handle, new_code, GetEntrypoint(handle));
89 } else {
90 SetCodeAndEntrypointNoWriteBarrier(handle, new_code,
91 new_code->instruction_start());
92 }
93}
94
95void JSDispatchTable::SetCodeAndEntrypointNoWriteBarrier(
96 JSDispatchHandle handle, Tagged<Code> new_code, Address new_entrypoint) {
97 SBXCHECK(IsCompatibleCode(new_code, GetParameterCount(handle)));
98
99 // The object should be in old space to avoid creating old-to-new references.
101
102 uint32_t index = HandleToIndex(handle);
103 DCHECK_GE(index, kEndOfInternalReadOnlySegment);
104 CFIMetadataWriteScope write_scope("JSDispatchTable update");
105 at(index).SetCodeAndEntrypointPointer(new_code.ptr(), new_entrypoint);
106}
107
108void JSDispatchTable::SetTieringRequest(JSDispatchHandle handle,
109 TieringBuiltin builtin,
110 Isolate* isolate) {
112 uint32_t index = HandleToIndex(handle);
113 DCHECK_GE(index, kEndOfInternalReadOnlySegment);
114 CFIMetadataWriteScope write_scope("JSDispatchTable update");
115 at(index).SetEntrypointPointer(
116 isolate->builtin_entry_table()[static_cast<uint32_t>(builtin)]);
117}
118
119bool JSDispatchTable::IsTieringRequested(JSDispatchHandle handle) {
120 uint32_t index = HandleToIndex(handle);
121 DCHECK_GE(index, kEndOfInternalReadOnlySegment);
122 Address entrypoint = at(index).GetEntrypoint();
123 Address code_entrypoint = at(index).GetCode()->instruction_start();
124 return code_entrypoint != entrypoint;
125}
126
127bool JSDispatchTable::IsTieringRequested(JSDispatchHandle handle,
128 TieringBuiltin builtin,
129 Isolate* isolate) {
130 uint32_t index = HandleToIndex(handle);
131 DCHECK_GE(index, kEndOfInternalReadOnlySegment);
132 Address entrypoint = at(index).GetEntrypoint();
133 Address code_entrypoint = at(index).GetCode()->instruction_start();
134 if (entrypoint == code_entrypoint) return false;
135 return entrypoint == EmbeddedData::FromBlob(isolate).InstructionStartOf(
136 static_cast<Builtin>(builtin));
137}
138
139void JSDispatchTable::ResetTieringRequest(JSDispatchHandle handle) {
140 uint32_t index = HandleToIndex(handle);
141 DCHECK_GE(index, kEndOfInternalReadOnlySegment);
142 CFIMetadataWriteScope write_scope("JSDispatchTable update");
143 at(index).SetEntrypointPointer(at(index).GetCode()->instruction_start());
144}
145
146JSDispatchHandle JSDispatchTable::AllocateAndInitializeEntry(
147 Space* space, uint16_t parameter_count, Tagged<Code> new_code) {
148 if (auto res =
149 TryAllocateAndInitializeEntry(space, parameter_count, new_code)) {
150 return *res;
151 }
153 "JSDispatchTable::AllocateAndInitializeEntry");
154}
155
156std::optional<JSDispatchHandle> JSDispatchTable::TryAllocateAndInitializeEntry(
157 Space* space, uint16_t parameter_count, Tagged<Code> new_code) {
158 DCHECK(space->BelongsTo(this));
159 SBXCHECK(IsCompatibleCode(new_code, parameter_count));
160
161 uint32_t index;
162 if (auto maybe_index = TryAllocateEntry(space)) {
163 index = *maybe_index;
164 } else {
165 return {};
166 }
167 JSDispatchEntry& entry = at(index);
168 CFIMetadataWriteScope write_scope("JSDispatchTable initialize");
169 entry.MakeJSDispatchEntry(new_code.address(), new_code->instruction_start(),
170 parameter_count, space->allocate_black());
171 return IndexToHandle(index);
172}
173
174void JSDispatchEntry::SetCodeAndEntrypointPointer(Address new_object,
175 Address new_entrypoint) {
176 Address old_payload = encoded_word_.load(std::memory_order_relaxed);
177 Address marking_bit = old_payload & kMarkingBit;
178 Address parameter_count = old_payload & kParameterCountMask;
179 // We want to preserve the marking bit of the entry. Since that happens to
180 // be the tag bit of the pointer, we need to explicitly clear it here.
181 Address object = (new_object << kObjectPointerShift) & ~kMarkingBit;
182 Address new_payload = object | marking_bit | parameter_count;
183 encoded_word_.store(new_payload, std::memory_order_relaxed);
184 entrypoint_.store(new_entrypoint, std::memory_order_relaxed);
185 DCHECK(!IsFreelistEntry());
186}
187
188void JSDispatchEntry::SetEntrypointPointer(Address new_entrypoint) {
189 entrypoint_.store(new_entrypoint, std::memory_order_relaxed);
190}
191
192void JSDispatchEntry::MakeFreelistEntry(uint32_t next_entry_index) {
193#ifdef V8_TARGET_ARCH_64_BIT
194 Address payload = kFreeEntryTag | next_entry_index;
195 entrypoint_.store(payload, std::memory_order_relaxed);
196#else
197 // Store index + 1 such that we can use 0 for non-free entries.
198 next_free_entry_.store(next_entry_index + 1, std::memory_order_relaxed);
199 entrypoint_.store(kNullAddress, std::memory_order_relaxed);
200#endif
201 encoded_word_.store(kNullAddress, std::memory_order_relaxed);
202 DCHECK(IsFreelistEntry());
203}
204
205bool JSDispatchEntry::IsFreelistEntry() const {
206#ifdef V8_TARGET_ARCH_64_BIT
207 auto entrypoint = entrypoint_.load(std::memory_order_relaxed);
208 return (entrypoint & kFreeEntryTag) == kFreeEntryTag;
209#else
210 return next_free_entry_.load(std::memory_order_relaxed) != 0;
211#endif
212}
213
214uint32_t JSDispatchEntry::GetNextFreelistEntryIndex() const {
215 DCHECK(IsFreelistEntry());
216#ifdef V8_TARGET_ARCH_64_BIT
217 return static_cast<uint32_t>(entrypoint_.load(std::memory_order_relaxed));
218#else
219 return next_free_entry_.load(std::memory_order_relaxed) - 1;
220#endif
221}
222
223void JSDispatchEntry::Mark() {
224 Address old_value = encoded_word_.load(std::memory_order_relaxed);
225 Address new_value = old_value | kMarkingBit;
226 // We don't need this cas to succeed. If marking races with
227 // `SetCodeAndEntrypointPointer`, then we are bound to re-set the mark bit in
228 // the write barrier.
229 static_assert(JSDispatchTable::kWriteBarrierSetsEntryMarkBit);
230 encoded_word_.compare_exchange_strong(old_value, new_value,
231 std::memory_order_relaxed);
232}
233
234void JSDispatchEntry::Unmark() {
235 Address value = encoded_word_.load(std::memory_order_relaxed);
236 value &= ~kMarkingBit;
237 encoded_word_.store(value, std::memory_order_relaxed);
238}
239
240bool JSDispatchEntry::IsMarked() const {
241 Address value = encoded_word_.load(std::memory_order_relaxed);
242 return value & kMarkingBit;
243}
244
245Address JSDispatchTable::GetEntrypoint(JSDispatchHandle handle) {
246 uint32_t index = HandleToIndex(handle);
247 return at(index).GetEntrypoint();
248}
249
250Address JSDispatchTable::GetCodeAddress(JSDispatchHandle handle) {
251 uint32_t index = HandleToIndex(handle);
252 Address ptr = at(index).GetCodePointer();
254 return ptr;
255}
256
257uint16_t JSDispatchTable::GetParameterCount(JSDispatchHandle handle) {
258 uint32_t index = HandleToIndex(handle);
259 return at(index).GetParameterCount();
260}
261
262void JSDispatchTable::Mark(JSDispatchHandle handle) {
263 uint32_t index = HandleToIndex(handle);
264
265 // The read-only space is immortal and cannot be written to.
266 if (index < kEndOfInternalReadOnlySegment) return;
267
268 CFIMetadataWriteScope write_scope("JSDispatchTable write");
269 at(index).Mark();
270}
271
272#if defined(DEBUG) || defined(VERIFY_HEAP)
273void JSDispatchTable::VerifyEntry(JSDispatchHandle handle, Space* space,
274 Space* ro_space) {
275 DCHECK(space->BelongsTo(this));
276 DCHECK(ro_space->BelongsTo(this));
278 return;
279 }
280 uint32_t index = HandleToIndex(handle);
281 if (ro_space->Contains(index)) {
282 CHECK(at(index).IsMarked());
283 } else {
284 CHECK(space->Contains(index));
285 }
286}
287#endif // defined(DEBUG) || defined(VERIFY_HEAP)
288
289template <typename Callback>
290void JSDispatchTable::IterateActiveEntriesIn(Space* space, Callback callback) {
291 IterateEntriesIn(space, [&](uint32_t index) {
292 if (!at(index).IsFreelistEntry()) {
293 callback(IndexToHandle(index));
294 }
295 });
296}
297
298template <typename Callback>
299void JSDispatchTable::IterateMarkedEntriesIn(Space* space, Callback callback) {
300 IterateEntriesIn(space, [&](uint32_t index) {
301 if (at(index).IsMarked()) {
302 callback(IndexToHandle(index));
303 }
304 });
305}
306
307template <typename Callback>
308uint32_t JSDispatchTable::Sweep(Space* space, Counters* counters,
309 Callback callback) {
310 uint32_t num_live_entries = GenericSweep(space, callback);
311 counters->js_dispatch_table_entries_count()->AddSample(num_live_entries);
312 return num_live_entries;
313}
314
315// static
316bool JSDispatchTable::IsCompatibleCode(Tagged<Code> code,
317 uint16_t parameter_count) {
318 if (code->entrypoint_tag() != kJSEntrypointTag) {
319 // Target code doesn't use JS linkage. This cannot be valid.
320 return false;
321 }
322 if (code->parameter_count() == parameter_count) {
323 DCHECK_IMPLIES(code->is_builtin(),
325 Builtins::GetFormalParameterCount(code->builtin_id()));
326 // Dispatch entry and code have the same signature. This is correct.
327 return true;
328 }
329
330 // Signature mismatch. This is mostly not safe, except for certain varargs
331 // builtins which are able to correctly handle such a mismatch. Examples
332 // include builtins like the InterpreterEntryTrampoline or the JSToWasm and
333 // JSToJS wrappers which determine their actual parameter count at runtime
334 // (see CodeStubAssembler::SetSupportsDynamicParameterCount()), or internal
335 // builtins that end up tailcalling into other code such as CompileLazy.
336 //
337 // Currently, we also allow this for testing code (from our test suites).
338 // TODO(saelo): maybe we should also forbid this just to be sure.
339 if (code->kind() == CodeKind::FOR_TESTING) {
340 return true;
341 }
342 DCHECK(code->is_builtin());
343 DCHECK_EQ(code->parameter_count(), kDontAdaptArgumentsSentinel);
344 switch (code->builtin_id()) {
345 case Builtin::kIllegal:
346 case Builtin::kCompileLazy:
347 case Builtin::kInterpreterEntryTrampoline:
348 case Builtin::kInstantiateAsmJs:
349 case Builtin::kDebugBreakTrampoline:
350#ifdef V8_ENABLE_WEBASSEMBLY
351 case Builtin::kJSToWasmWrapper:
352 case Builtin::kJSToJSWrapper:
353 case Builtin::kJSToJSWrapperInvalidSig:
354 case Builtin::kWasmPromising:
355#if V8_ENABLE_DRUMBRAKE
356 case Builtin::kGenericJSToWasmInterpreterWrapper:
357#endif
358 case Builtin::kWasmStressSwitch:
359#endif
360 return true;
361 default:
362 return false;
363 }
364}
365
366} // namespace internal
367} // namespace v8
368
369#endif // V8_ENABLE_LEAPTIERING
370
371#endif // V8_SANDBOX_JS_DISPATCH_TABLE_INL_H_
int16_t parameter_count
Definition builtins.cc:67
#define SBXCHECK(condition)
Definition check.h:61
static int GetFormalParameterCount(Builtin builtin)
Address InstructionStartOf(Builtin builtin) const
static EmbeddedData FromBlob()
static V8_INLINE bool InYoungGeneration(Tagged< Object > object)
static V8_INLINE constexpr bool HasHeapObjectTag(Address value)
static V8_EXPORT_PRIVATE void FatalProcessOutOfMemory(Isolate *isolate, const char *location, const OOMDetails &details=kNoOOMDetails)
TNode< Object > callback
unsigned short uint16_t
Definition unicode.cc:39
V8_INLINE IndirectHandle< T > handle(Tagged< T > object, Isolate *isolate)
Definition handles-inl.h:72
Tagged(T object) -> Tagged< T >
constexpr uint16_t kDontAdaptArgumentsSentinel
Definition globals.h:2779
base::StrongAlias< JSDispatchHandleAliasTag, uint32_t > JSDispatchHandle
Definition globals.h:557
constexpr JSDispatchHandle kNullJSDispatchHandle(0)
const int kHeapObjectTag
Definition v8-internal.h:72
V8_INLINE bool IsValidTieringBuiltin(TieringBuiltin builtin)
Definition builtins.h:65
static constexpr Address kNullAddress
Definition v8-internal.h:53
RwxMemoryWriteScope CFIMetadataWriteScope
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485