// Copyright 2020 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/external-pointer-table.h" #include #include "src/execution/isolate.h" #include "src/logging/counters.h" #include "src/sandbox/external-pointer-table-inl.h" #ifdef V8_SANDBOX_IS_AVAILABLE namespace v8 { namespace internal { STATIC_ASSERT(sizeof(ExternalPointerTable) == ExternalPointerTable::kSize); // static uint32_t ExternalPointerTable::AllocateEntry(ExternalPointerTable* table) { return table->Allocate(); } uint32_t ExternalPointerTable::Sweep(Isolate* isolate) { // Sweep top to bottom and rebuild the freelist from newly dead and // previously freed entries. This way, the freelist ends up sorted by index, // which helps defragment the table. This method must run either on the // mutator thread or while the mutator is stopped. Also clear marking bits on // live entries. // TODO(v8:10391, saelo) could also shrink the table using DecommitPages() if // elements at the end are free. This might require some form of compaction. uint32_t freelist_size = 0; uint32_t current_freelist_head = 0; // Skip the special null entry. DCHECK_GE(capacity_, 1); for (uint32_t i = capacity_ - 1; i > 0; i--) { // No other threads are active during sweep, so there is no need to use // atomic operations here. Address entry = load(i); if (!is_marked(entry)) { store(i, make_freelist_entry(current_freelist_head)); current_freelist_head = i; freelist_size++; } else { store(i, clear_mark_bit(entry)); } } freelist_head_ = current_freelist_head; uint32_t num_active_entries = capacity_ - freelist_size; isolate->counters()->sandboxed_external_pointers_count()->AddSample( num_active_entries); return num_active_entries; } uint32_t ExternalPointerTable::Grow() { // Freelist should be empty. DCHECK_EQ(0, freelist_head_); // Mutex must be held when calling this method. mutex_->AssertHeld(); // Grow the table by one block. uint32_t old_capacity = capacity_; uint32_t new_capacity = old_capacity + kEntriesPerBlock; CHECK_LE(new_capacity, kMaxSandboxedExternalPointers); // Failure likely means OOM. TODO(saelo) handle this. VirtualAddressSpace* root_space = GetPlatformVirtualAddressSpace(); DCHECK(IsAligned(kBlockSize, root_space->page_size())); CHECK(root_space->SetPagePermissions(buffer_ + old_capacity * sizeof(Address), kBlockSize, PagePermissions::kReadWrite)); capacity_ = new_capacity; // Build freelist bottom to top, which might be more cache friendly. uint32_t start = std::max(old_capacity, 1); // Skip entry zero uint32_t last = new_capacity - 1; for (uint32_t i = start; i < last; i++) { store(i, make_freelist_entry(i + 1)); } store(last, make_freelist_entry(0)); // This must be a release store to prevent reordering of the preceeding // stores to the freelist from being reordered past this store. See // Allocate() for more details. base::Release_Store(reinterpret_cast(&freelist_head_), start); return start; } } // namespace internal } // namespace v8 #endif // V8_SANDBOX_IS_AVAILABLE