v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
wasm-code-pointer-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_WASM_WASM_CODE_POINTER_TABLE_INL_H_
6#define V8_WASM_WASM_CODE_POINTER_TABLE_INL_H_
7
9// Include the non-inl header before the rest of the headers.
10
13
14#if !V8_ENABLE_WEBASSEMBLY
15#error This header should only be included if WebAssembly is enabled.
16#endif // !V8_ENABLE_WEBASSEMBLY
17
18namespace v8::internal::wasm {
19
21 uint64_t signature_hash) {
22 entrypoint_.store(entrypoint, std::memory_order_relaxed);
23#ifdef V8_ENABLE_SANDBOX
24 signature_hash_ = signature_hash;
25#endif
26}
27
29 Address entrypoint, uint64_t signature_hash) {
30#ifdef V8_ENABLE_SANDBOX
31 SBXCHECK_EQ(signature_hash_, signature_hash);
32#endif
33 entrypoint_.store(entrypoint, std::memory_order_relaxed);
34}
35
37 uint64_t signature_hash) const {
38#ifdef V8_ENABLE_SANDBOX
39 SBXCHECK_EQ(signature_hash_, signature_hash);
40#endif
41 return entrypoint_.load(std::memory_order_relaxed);
42}
43
45 return entrypoint_.load(std::memory_order_relaxed);
46}
47
48void WasmCodePointerTableEntry::MakeFreelistEntry(uint32_t next_entry_index) {
49 entrypoint_.store(next_entry_index, std::memory_order_relaxed);
50#ifdef V8_ENABLE_SANDBOX
51 signature_hash_ = kInvalidWasmSignatureHash;
52#endif
53}
54
56 return static_cast<uint32_t>(entrypoint_.load(std::memory_order_relaxed));
57}
58
60 uint64_t signature_hash) const {
61 return at(index.value()).GetEntrypoint(signature_hash);
62}
63
65 WasmCodePointer index) const {
66 return at(index.value()).GetEntrypointWithoutSignatureCheck();
67}
68
70 Address value,
71 uint64_t signature_hash) {
72 WriteScope write_scope("WasmCodePointerTable write");
73 at(index.value()).UpdateCodePointerEntry(value, signature_hash);
74}
75
77 Address value,
78 uint64_t signature_hash) {
79 WriteScope write_scope("WasmCodePointerTable write");
80 at(index.value()).MakeCodePointerEntry(value, signature_hash);
81}
82
84 WasmCodePointer index, Address value, uint64_t signature_hash,
85 WriteScope& write_scope) {
86 at(index.value()).MakeCodePointerEntry(value, signature_hash);
87}
88
90 Address entrypoint, uint64_t signature_hash) {
92 WriteScope write_scope("WasmCodePointerTable write");
93 at(index.value()).MakeCodePointerEntry(entrypoint, signature_hash);
94 return index;
95}
96
97WasmCodePointerTable::FreelistHead WasmCodePointerTable::ReadFreelistHead() {
98 while (true) {
99 FreelistHead freelist = freelist_head_.load(std::memory_order_acquire);
100 if (IsRetryMarker(freelist)) {
101 // The retry marker will only be stored for a short amount of time. We can
102 // check for it in a busy loop.
103 continue;
104 }
105 return freelist;
106 }
107}
108
111
112 while (true) {
113 // Fast path, try to take an entry from the freelist.
114 uint32_t allocated_entry;
115 if (TryAllocateFromFreelist(&allocated_entry)) {
116 return WasmCodePointer{allocated_entry};
117 }
118
119 // This is essentially DCLP (see
120 // https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/)
121 // and so requires an acquire load as well as a release store in
122 // AllocateTableSegment() to prevent reordering of memory accesses, which
123 // could for example cause one thread to read a freelist entry before it
124 // has been properly initialized.
125
126 // The freelist is empty. We take a lock to avoid another thread from
127 // allocating a new segment in the meantime. However, the freelist can
128 // still grow if another thread frees an entry, so we'll merge the
129 // freelists atomically in the end.
131
132 // Reload freelist head in case another thread already grew the table.
133 if (!freelist_head_.load(std::memory_order_relaxed).is_empty()) {
134 // Something changed, retry.
135 continue;
136 }
137
138 // Freelist is (still) empty so extend this space by another segment.
139 auto [segment, freelist] = AllocateAndInitializeSegment();
140
141 // Take out the first entry before we link it to the freelist_head.
142 allocated_entry = AllocateEntryFromFreelistNonAtomic(&freelist);
143
144 // Merge the new freelist entries into our freelist.
145 LinkFreelist(freelist, segment.last_entry());
146
147 return WasmCodePointer{allocated_entry};
148 }
149}
150
152 while (true) {
153 FreelistHead current_freelist_head = ReadFreelistHead();
154 if (current_freelist_head.is_empty()) {
155 return false;
156 }
157
158 // Temporarily replace the freelist head with a marker to gain exclusive
159 // access to it. This avoids a race condition where another thread could
160 // unmap the memory while we're trying to read from it.
161 if (!freelist_head_.compare_exchange_strong(current_freelist_head,
162 kRetryMarker)) {
163 continue;
164 }
165
166 uint32_t next_freelist_entry =
167 at(current_freelist_head.next()).GetNextFreelistEntryIndex();
168 FreelistHead new_freelist_head(next_freelist_entry,
169 current_freelist_head.length() - 1);
170
171 // We are allowed to overwrite the freelist_head_ since we stored the
172 // kRetryMarker in there.
173 freelist_head_.store(new_freelist_head, std::memory_order_relaxed);
174
175 *index = current_freelist_head.next();
176
177 return true;
178 }
179}
180
182 FreelistHead* freelist_head) {
183 DCHECK(!freelist_head->is_empty());
184 uint32_t index = freelist_head->next();
185 uint32_t next_next = at(freelist_head->next()).GetNextFreelistEntryIndex();
186 *freelist_head = FreelistHead(next_next, freelist_head->length() - 1);
187 return index;
188}
189
191 // TODO(sroettger): adding to the inline freelist requires a WriteScope. We
192 // could keep a second fixed size out-of-line freelist to avoid frequent
193 // permission changes here.
194 LinkFreelist(FreelistHead(entry.value(), 1), entry.value());
195}
196
197WasmCodePointerTable::FreelistHead WasmCodePointerTable::LinkFreelist(
198 FreelistHead freelist_to_link, uint32_t last_element) {
199 DCHECK(!freelist_to_link.is_empty());
200
201 FreelistHead current_head, new_head;
202 do {
203 current_head = ReadFreelistHead();
204 new_head = FreelistHead(freelist_to_link.next(),
205 freelist_to_link.length() + current_head.length());
206
207 WriteScope write_scope("write free list entry");
208 at(last_element).MakeFreelistEntry(current_head.next());
209 // This must be a release store since we previously wrote the freelist
210 // entries in AllocateTableSegment() and we need to prevent the writes from
211 // being reordered past this store. See AllocateEntry() for more details.
212 } while (!freelist_head_.compare_exchange_strong(current_head, new_head,
213 std::memory_order_release));
214
215 return new_head;
216}
217
218} // namespace v8::internal::wasm
219
220#endif // V8_WASM_WASM_CODE_POINTER_TABLE_INL_H_
#define SBXCHECK_EQ(lhs, rhs)
Definition check.h:62
V8_INLINE FreelistHead LinkFreelist(FreelistHead new_freelist, uint32_t last_element)
static bool IsRetryMarker(FreelistHead freelist)
void UpdateEntrypoint(WasmCodePointer index, Address value, uint64_t signature_hash)
Address GetEntrypoint(WasmCodePointer index, uint64_t signature_hash) const
Address GetEntrypointWithoutSignatureCheck(WasmCodePointer index) const
V8_INLINE bool TryAllocateFromFreelist(uint32_t *index)
void SetEntrypointWithWriteScope(WasmCodePointer index, Address value, uint64_t signature_hash, WriteScope &write_scope)
void SetEntrypointAndSignature(WasmCodePointer index, Address value, uint64_t signature_hash)
V8_INLINE uint32_t AllocateEntryFromFreelistNonAtomic(FreelistHead *freelist_head)
WasmCodePointer AllocateAndInitializeEntry(Address entrypoint, uint64_t signature_hash)
constexpr uint64_t kInvalidWasmSignatureHash
Definition globals.h:2896
#define DCHECK(condition)
Definition logging.h:482
void UpdateCodePointerEntry(Address entrypoint, uint64_t signature_hash)
void MakeCodePointerEntry(Address entrypoint, uint64_t signature_hash)
Address GetEntrypoint(uint64_t signature_hash) const