1// Copyright 2020 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#include "src/sandbox/external-pointer-table.h" 6 7#include <algorithm> 8 9#include "src/execution/isolate.h" 10#include "src/logging/counters.h" 11#include "src/sandbox/external-pointer-table-inl.h" 12 13#ifdef V8_SANDBOX_IS_AVAILABLE 14 15namespace v8 { 16namespace internal { 17 18STATIC_ASSERT(sizeof(ExternalPointerTable) == ExternalPointerTable::kSize); 19 20// static 21uint32_t ExternalPointerTable::AllocateEntry(ExternalPointerTable* table) { 22 return table->Allocate(); 23} 24 25uint32_t ExternalPointerTable::Sweep(Isolate* isolate) { 26 // Sweep top to bottom and rebuild the freelist from newly dead and 27 // previously freed entries. This way, the freelist ends up sorted by index, 28 // which helps defragment the table. This method must run either on the 29 // mutator thread or while the mutator is stopped. Also clear marking bits on 30 // live entries. 31 // TODO(v8:10391, saelo) could also shrink the table using DecommitPages() if 32 // elements at the end are free. This might require some form of compaction. 33 uint32_t freelist_size = 0; 34 uint32_t current_freelist_head = 0; 35 36 // Skip the special null entry. 37 DCHECK_GE(capacity_, 1); 38 for (uint32_t i = capacity_ - 1; i > 0; i--) { 39 // No other threads are active during sweep, so there is no need to use 40 // atomic operations here. 41 Address entry = load(i); 42 if (!is_marked(entry)) { 43 store(i, make_freelist_entry(current_freelist_head)); 44 current_freelist_head = i; 45 freelist_size++; 46 } else { 47 store(i, clear_mark_bit(entry)); 48 } 49 } 50 51 freelist_head_ = current_freelist_head; 52 53 uint32_t num_active_entries = capacity_ - freelist_size; 54 isolate->counters()->sandboxed_external_pointers_count()->AddSample( 55 num_active_entries); 56 return num_active_entries; 57} 58 59uint32_t ExternalPointerTable::Grow() { 60 // Freelist should be empty. 61 DCHECK_EQ(0, freelist_head_); 62 // Mutex must be held when calling this method. 63 mutex_->AssertHeld(); 64 65 // Grow the table by one block. 66 uint32_t old_capacity = capacity_; 67 uint32_t new_capacity = old_capacity + kEntriesPerBlock; 68 CHECK_LE(new_capacity, kMaxSandboxedExternalPointers); 69 70 // Failure likely means OOM. TODO(saelo) handle this. 71 VirtualAddressSpace* root_space = GetPlatformVirtualAddressSpace(); 72 DCHECK(IsAligned(kBlockSize, root_space->page_size())); 73 CHECK(root_space->SetPagePermissions(buffer_ + old_capacity * sizeof(Address), 74 kBlockSize, 75 PagePermissions::kReadWrite)); 76 capacity_ = new_capacity; 77 78 // Build freelist bottom to top, which might be more cache friendly. 79 uint32_t start = std::max<uint32_t>(old_capacity, 1); // Skip entry zero 80 uint32_t last = new_capacity - 1; 81 for (uint32_t i = start; i < last; i++) { 82 store(i, make_freelist_entry(i + 1)); 83 } 84 store(last, make_freelist_entry(0)); 85 86 // This must be a release store to prevent reordering of the preceeding 87 // stores to the freelist from being reordered past this store. See 88 // Allocate() for more details. 89 base::Release_Store(reinterpret_cast<base::Atomic32*>(&freelist_head_), 90 start); 91 return start; 92} 93 94} // namespace internal 95} // namespace v8 96 97#endif // V8_SANDBOX_IS_AVAILABLE 98