blob: 065c26a0ec180e566c8ef0b5c1277697721e4f71 [file] [log] [blame]
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WASM_WASM_CODE_POINTER_TABLE_INL_H_
#define V8_WASM_WASM_CODE_POINTER_TABLE_INL_H_
#include "src/common/code-memory-access-inl.h"
#include "src/common/segmented-table-inl.h"
#include "src/wasm/wasm-code-pointer-table.h"
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
namespace v8::internal::wasm {
void WasmCodePointerTableEntry::MakeCodePointerEntry(Address entrypoint,
uint64_t signature_hash) {
entrypoint_.store(entrypoint, std::memory_order_relaxed);
#ifdef V8_ENABLE_SANDBOX
signature_hash_ = signature_hash;
#endif
}
void WasmCodePointerTableEntry::UpdateCodePointerEntry(
Address entrypoint, uint64_t signature_hash) {
#ifdef V8_ENABLE_SANDBOX
SBXCHECK_EQ(signature_hash_, signature_hash);
#endif
entrypoint_.store(entrypoint, std::memory_order_relaxed);
}
Address WasmCodePointerTableEntry::GetEntrypoint(
uint64_t signature_hash) const {
#ifdef V8_ENABLE_SANDBOX
SBXCHECK_EQ(signature_hash_, signature_hash);
#endif
return entrypoint_.load(std::memory_order_relaxed);
}
Address WasmCodePointerTableEntry::GetEntrypointWithoutSignatureCheck() const {
return entrypoint_.load(std::memory_order_relaxed);
}
void WasmCodePointerTableEntry::MakeFreelistEntry(uint32_t next_entry_index) {
entrypoint_.store(next_entry_index, std::memory_order_relaxed);
#ifdef V8_ENABLE_SANDBOX
signature_hash_ = kInvalidWasmSignatureHash;
#endif
}
uint32_t WasmCodePointerTableEntry::GetNextFreelistEntryIndex() const {
return static_cast<uint32_t>(entrypoint_.load(std::memory_order_relaxed));
}
Address WasmCodePointerTable::GetEntrypoint(WasmCodePointer index,
uint64_t signature_hash) const {
return at(index.value()).GetEntrypoint(signature_hash);
}
Address WasmCodePointerTable::GetEntrypointWithoutSignatureCheck(
WasmCodePointer index) const {
return at(index.value()).GetEntrypointWithoutSignatureCheck();
}
void WasmCodePointerTable::UpdateEntrypoint(WasmCodePointer index,
Address value,
uint64_t signature_hash) {
WriteScope write_scope("WasmCodePointerTable write");
at(index.value()).UpdateCodePointerEntry(value, signature_hash);
}
void WasmCodePointerTable::SetEntrypointAndSignature(WasmCodePointer index,
Address value,
uint64_t signature_hash) {
WriteScope write_scope("WasmCodePointerTable write");
at(index.value()).MakeCodePointerEntry(value, signature_hash);
}
void WasmCodePointerTable::SetEntrypointWithWriteScope(
WasmCodePointer index, Address value, uint64_t signature_hash,
WriteScope& write_scope) {
at(index.value()).MakeCodePointerEntry(value, signature_hash);
}
WasmCodePointer WasmCodePointerTable::AllocateAndInitializeEntry(
Address entrypoint, uint64_t signature_hash) {
WasmCodePointer index = AllocateUninitializedEntry();
WriteScope write_scope("WasmCodePointerTable write");
at(index.value()).MakeCodePointerEntry(entrypoint, signature_hash);
return index;
}
WasmCodePointerTable::FreelistHead WasmCodePointerTable::ReadFreelistHead() {
while (true) {
FreelistHead freelist = freelist_head_.load(std::memory_order_acquire);
if (IsRetryMarker(freelist)) {
// The retry marker will only be stored for a short amount of time. We can
// check for it in a busy loop.
continue;
}
return freelist;
}
}
WasmCodePointer WasmCodePointerTable::AllocateUninitializedEntry() {
DCHECK(is_initialized());
while (true) {
// Fast path, try to take an entry from the freelist.
uint32_t allocated_entry;
if (TryAllocateFromFreelist(&allocated_entry)) {
return WasmCodePointer{allocated_entry};
}
// This is essentially DCLP (see
// https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/)
// and so requires an acquire load as well as a release store in
// AllocateTableSegment() to prevent reordering of memory accesses, which
// could for example cause one thread to read a freelist entry before it
// has been properly initialized.
// The freelist is empty. We take a lock to avoid another thread from
// allocating a new segment in the meantime. However, the freelist can
// still grow if another thread frees an entry, so we'll merge the
// freelists atomically in the end.
base::MutexGuard guard(&segment_allocation_mutex_);
// Reload freelist head in case another thread already grew the table.
if (!freelist_head_.load(std::memory_order_relaxed).is_empty()) {
// Something changed, retry.
continue;
}
// Freelist is (still) empty so extend this space by another segment.
auto [segment, freelist] = AllocateAndInitializeSegment();
// Take out the first entry before we link it to the freelist_head.
allocated_entry = AllocateEntryFromFreelistNonAtomic(&freelist);
// Merge the new freelist entries into our freelist.
LinkFreelist(freelist, segment.last_entry());
return WasmCodePointer{allocated_entry};
}
}
bool WasmCodePointerTable::TryAllocateFromFreelist(uint32_t* index) {
while (true) {
FreelistHead current_freelist_head = ReadFreelistHead();
if (current_freelist_head.is_empty()) {
return false;
}
// Temporarily replace the freelist head with a marker to gain exclusive
// access to it. This avoids a race condition where another thread could
// unmap the memory while we're trying to read from it.
if (!freelist_head_.compare_exchange_strong(current_freelist_head,
kRetryMarker)) {
continue;
}
uint32_t next_freelist_entry =
at(current_freelist_head.next()).GetNextFreelistEntryIndex();
FreelistHead new_freelist_head(next_freelist_entry,
current_freelist_head.length() - 1);
// We are allowed to overwrite the freelist_head_ since we stored the
// kRetryMarker in there.
freelist_head_.store(new_freelist_head, std::memory_order_relaxed);
*index = current_freelist_head.next();
return true;
}
}
uint32_t WasmCodePointerTable::AllocateEntryFromFreelistNonAtomic(
FreelistHead* freelist_head) {
DCHECK(!freelist_head->is_empty());
uint32_t index = freelist_head->next();
uint32_t next_next = at(freelist_head->next()).GetNextFreelistEntryIndex();
*freelist_head = FreelistHead(next_next, freelist_head->length() - 1);
return index;
}
void WasmCodePointerTable::FreeEntry(WasmCodePointer entry) {
// TODO(sroettger): adding to the inline freelist requires a WriteScope. We
// could keep a second fixed size out-of-line freelist to avoid frequent
// permission changes here.
LinkFreelist(FreelistHead(entry.value(), 1), entry.value());
}
WasmCodePointerTable::FreelistHead WasmCodePointerTable::LinkFreelist(
FreelistHead freelist_to_link, uint32_t last_element) {
DCHECK(!freelist_to_link.is_empty());
FreelistHead current_head, new_head;
do {
current_head = ReadFreelistHead();
new_head = FreelistHead(freelist_to_link.next(),
freelist_to_link.length() + current_head.length());
WriteScope write_scope("write free list entry");
at(last_element).MakeFreelistEntry(current_head.next());
// This must be a release store since we previously wrote the freelist
// entries in AllocateTableSegment() and we need to prevent the writes from
// being reordered past this store. See AllocateEntry() for more details.
} while (!freelist_head_.compare_exchange_strong(current_head, new_head,
std::memory_order_release));
return new_head;
}
} // namespace v8::internal::wasm
#endif // V8_WASM_WASM_CODE_POINTER_TABLE_INL_H_