blob: c70cb454ad7d1fe9c96ccd34a598bd022603c535 [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.
#include "src/sandbox/compactible-external-entity-table-inl.h"
#include "src/sandbox/external-buffer-table.h"
namespace v8 {
namespace internal {
void ExternalBufferTableEntry::MakeExternalBufferEntry(
std::pair<Address, size_t> buffer, ExternalBufferTag tag) {
DCHECK_EQ(0, buffer.first & kExternalBufferTagMask);
DCHECK(tag & kExternalBufferMarkBit);
DCHECK_NE(tag, kExternalBufferFreeEntryTag);
DCHECK_NE(tag, kExternalBufferEvacuationEntryTag);
Payload new_payload(buffer.first, tag);, std::memory_order_relaxed);, std::memory_order_relaxed);
std::pair<Address, size_t> ExternalBufferTableEntry::GetExternalBuffer(
ExternalBufferTag tag) const {
auto payload = payload_.load(std::memory_order_relaxed);
auto size = size_.load(std::memory_order_relaxed);
return {payload.Untag(tag), size};
bool ExternalBufferTableEntry::HasExternalBuffer(ExternalBufferTag tag) const {
auto payload = payload_.load(std::memory_order_relaxed);
return payload.IsTaggedWith(tag);
void ExternalBufferTableEntry::MakeFreelistEntry(uint32_t next_entry_index) {
// The next freelist entry is stored in the lower bits of the entry.
Payload new_payload(next_entry_index, kExternalBufferFreeEntryTag);, std::memory_order_relaxed);, std::memory_order_relaxed);
uint32_t ExternalBufferTableEntry::GetNextFreelistEntryIndex() const {
auto payload = payload_.load(std::memory_order_relaxed);
return payload.ExtractFreelistLink();
void ExternalBufferTableEntry::Mark() {
auto old_payload = payload_.load(std::memory_order_relaxed);
auto new_payload = old_payload;
// We don't need to perform the CAS in a loop: if the new value is not equal
// to the old value, then the mutator must've just written a new value into
// the entry. This in turn must've set the marking bit already (see e.g.
// StoreExternalPointer), so we don't need to do it again.
bool success = payload_.compare_exchange_strong(old_payload, new_payload,
DCHECK(success || old_payload.HasMarkBitSet());
void ExternalBufferTableEntry::MakeEvacuationEntry(Address handle_location) {
Payload new_payload(handle_location, kExternalBufferEvacuationEntryTag);, std::memory_order_relaxed);
bool ExternalBufferTableEntry::HasEvacuationEntry() const {
auto payload = payload_.load(std::memory_order_relaxed);
return payload.ContainsEvacuationEntry();
void ExternalBufferTableEntry::MigrateInto(ExternalBufferTableEntry& other) {
auto payload = payload_.load(std::memory_order_relaxed);
auto size = size_.load(std::memory_order_relaxed);
// We expect to only migrate entries containing external pointers.
DCHECK(payload.ContainsPointer());, std::memory_order_relaxed);, std::memory_order_relaxed);
#ifdef DEBUG
// In debug builds, we clobber this old entry so that any sharing of table
// entries is easily detected. Shared entries would require write barriers,
// so we'd like to avoid them. See the compaction algorithm explanation in
// compactible-external-entity-table.h for more details.
constexpr Address kClobberedEntryMarker = static_cast<Address>(-1);
Payload clobbered(kClobberedEntryMarker, kExternalBufferNullTag);
DCHECK_NE(payload, clobbered);, std::memory_order_relaxed);
#endif // DEBUG
std::pair<Address, size_t> ExternalBufferTable::Get(
ExternalBufferHandle handle, ExternalBufferTag tag) const {
uint32_t index = HandleToIndex(handle);
DCHECK(index == 0 || at(index).HasExternalBuffer(tag));
return at(index).GetExternalBuffer(tag);
ExternalBufferHandle ExternalBufferTable::AllocateAndInitializeEntry(
Space* space, std::pair<Address, size_t> initial_buffer,
ExternalBufferTag tag) {
uint32_t index = AllocateEntry(space);
at(index).MakeExternalBufferEntry(initial_buffer, tag);
return IndexToHandle(index);
void ExternalBufferTable::Mark(Space* space, ExternalBufferHandle handle,
Address handle_location) {
// The null entry is immortal and immutable, so no need to mark it as alive.
if (handle == kNullExternalBufferHandle) return;
// The handle_location must always contain the given handle.
uint32_t index = HandleToIndex(handle);
// If the table is being compacted and the entry is inside the evacuation
// area, then allocate and set up an evacuation entry for it.
MaybeCreateEvacuationEntry(space, index, handle_location);
// Even if the entry is marked for evacuation, it still needs to be marked as
// alive as it may be visited during sweeping before being evacuation.
// static
bool ExternalBufferTable::IsValidHandle(ExternalBufferHandle handle) {
uint32_t index = handle >> kExternalBufferHandleShift;
return handle == index << kExternalBufferHandleShift;
// static
uint32_t ExternalBufferTable::HandleToIndex(ExternalBufferHandle handle) {
uint32_t index = handle >> kExternalBufferHandleShift;
DCHECK_LE(index, kMaxExternalBufferPointers);
return index;
// static
ExternalBufferHandle ExternalBufferTable::IndexToHandle(uint32_t index) {
DCHECK_LE(index, kMaxExternalBufferPointers);
ExternalBufferHandle handle = index << kExternalBufferHandleShift;
DCHECK_NE(handle, kNullExternalBufferHandle);
return handle;
void ExternalBufferTable::Space::NotifyExternalPointerFieldInvalidated(
Address field_address) {
#ifdef DEBUG
ExternalBufferHandle handle = base::AsAtomic32::Acquire_Load(
} // namespace internal
} // namespace v8