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