forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaliased_struct.h
More file actions
248 lines (204 loc) · 8.46 KB
/
Copy pathaliased_struct.h
File metadata and controls
248 lines (204 loc) · 8.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#ifndef SRC_ALIASED_STRUCT_H_
#define SRC_ALIASED_STRUCT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <memory>
#include <vector>
#include "node_internals.h"
#include "v8.h"
namespace node {
// AliasedStruct is a utility that allows uses a V8 Backing Store
// to be exposed to the C++/C side as a struct and to the
// JavaScript side as an ArrayBuffer to efficiently share
// data without marshalling. It is similar in nature to
// AliasedBuffer.
//
// struct Foo { int x; }
//
// AliasedStruct<Foo> foo;
// foo->x = 1;
//
// Local<ArrayBuffer> ab = foo.GetArrayBuffer();
template <typename T>
class AliasedStruct final {
public:
template <typename... Args>
explicit AliasedStruct(v8::Isolate* isolate, Args&&... args);
inline AliasedStruct(const AliasedStruct& that);
inline ~AliasedStruct();
inline AliasedStruct& operator=(AliasedStruct&& that) noexcept;
v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
return buffer_.Get(isolate_);
}
const T* Data() const { return ptr_; }
T* Data() { return ptr_; }
const T& operator*() const { return *ptr_; }
T& operator*() { return *ptr_; }
const T* operator->() const { return ptr_; }
T* operator->() { return ptr_; }
private:
v8::Isolate* isolate_;
std::shared_ptr<v8::BackingStore> store_;
T* ptr_;
v8::Global<v8::ArrayBuffer> buffer_;
};
// ---------------------------------------------------------------------------
// ArenaSlot — type-erased handle to a slot in an AliasedStructArena page.
// This can be stored in headers where T is incomplete. The typed accessors
// are provided via a thin typed wrapper (AliasedStructArena<T>::Slot).
struct ArenaSlotBase {
// Opaque page pointer — only the arena knows the concrete type.
void* page = nullptr;
void* ptr = nullptr;
uint32_t index = 0;
size_t byte_offset = 0;
explicit operator bool() const { return ptr != nullptr; }
// Returns the page's ArrayBuffer. Implemented below after ArenaPageHeader.
v8::Local<v8::ArrayBuffer> GetArrayBuffer(v8::Isolate* isolate) const;
size_t GetByteOffset() const { return byte_offset; }
// Returns the page's cached DataView over the full page.
// Callers use byte_offset to index into the correct slot region.
v8::Local<v8::DataView> GetPageDataView(v8::Isolate* isolate) const;
// Returns the page's cached BigUint64Array over the full page.
// Callers use byte_offset / sizeof(uint64_t) to index into the
// correct slot region.
v8::Local<v8::BigUint64Array> GetPageBigUint64Array(
v8::Isolate* isolate) const;
};
// ---------------------------------------------------------------------------
// AliasedStructArena<T> — pool allocator for AliasedStruct-style shared
// memory. Instead of creating a separate ArrayBuffer + BackingStore per
// instance, the arena pre-allocates pages of N slots backed by a single
// ArrayBuffer each. Callers receive a Slot handle that provides the same
// T*/operator-> interface as AliasedStruct, plus the ability to create a
// JS typed-array view over just that slot's region of the page buffer.
//
// Pages target kPageBytes (default 16 KB) for L1 cache residency during
// sequential access patterns. Slots are recycled via an intrusive
// freelist, and empty pages are dropped when their last slot is released.
//
// Usage:
// AliasedStructArena<MyState> arena;
// auto slot = arena.Allocate(isolate);
// slot->some_field = 42;
// auto view = slot.GetArrayBuffer(isolate); // JS-visible view
// ...
// arena.Release(std::move(slot)); // return to freelist
//
template <typename T, size_t kPageBytes = 16384>
class AliasedStructArena final {
public:
static constexpr size_t kSlotsPerPage = kPageBytes / sizeof(T);
static_assert(kSlotsPerPage >= 4, "Page too small for type T");
static_assert(sizeof(T) >= sizeof(uint32_t),
"T must be at least 4 bytes for freelist linkage");
AliasedStructArena() = default;
~AliasedStructArena() = default;
AliasedStructArena(const AliasedStructArena&) = delete;
AliasedStructArena& operator=(const AliasedStructArena&) = delete;
struct Page {
std::shared_ptr<v8::BackingStore> store;
v8::Global<v8::ArrayBuffer> buffer;
// Lazily created full-page views shared by all slots in
// this page. Typically only one is used per arena.
v8::Global<v8::DataView> data_view;
v8::Global<v8::BigUint64Array> big_uint64_array;
size_t page_byte_length = 0;
T* base = nullptr;
uint32_t free_head = 0;
uint32_t used_count = 0;
static constexpr uint32_t kNoFreeSlot = UINT32_MAX;
void Init(v8::Isolate* isolate) {
const v8::HandleScope handle_scope(isolate);
const size_t total_bytes = kSlotsPerPage * sizeof(T);
store = v8::ArrayBuffer::NewBackingStore(isolate, total_bytes);
memset(store->Data(), 0, total_bytes);
base = static_cast<T*>(store->Data());
page_byte_length = total_bytes;
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, store);
buffer = v8::Global<v8::ArrayBuffer>(isolate, ab);
// Build freelist: each slot points to the next.
for (uint32_t i = 0; i < kSlotsPerPage - 1; i++) {
*reinterpret_cast<uint32_t*>(&base[i]) = i + 1;
}
*reinterpret_cast<uint32_t*>(&base[kSlotsPerPage - 1]) = kNoFreeSlot;
free_head = 0;
used_count = 0;
}
bool HasFreeSlots() const { return free_head != kNoFreeSlot; }
};
// Typed slot handle — wraps ArenaSlotBase with T* accessors.
class Slot : public ArenaSlotBase {
public:
Slot() = default;
const T& operator*() const { return *static_cast<T*>(ptr); }
T& operator*() { return *static_cast<T*>(ptr); }
const T* operator->() const { return static_cast<T*>(ptr); }
T* operator->() { return static_cast<T*>(ptr); }
T* Data() { return static_cast<T*>(ptr); }
const T* Data() const { return static_cast<T*>(ptr); }
};
// Allocate a slot, placement-constructing T with the given args.
// Creates a new page if all existing pages are full.
template <typename... Args>
Slot Allocate(v8::Isolate* isolate, Args&&... args);
// Release a slot back to the arena freelist. Calls ~T() and zeros
// the memory so that any JS views see clean data.
void Release(Slot&& slot);
// Release a slot given a type-erased ArenaSlotBase reference.
// Convenience for callers that store ArenaSlotBase in headers where
// T is incomplete.
void ReleaseSlot(ArenaSlotBase& base);
private:
Page* FindOrCreatePage(v8::Isolate* isolate);
std::vector<std::unique_ptr<Page>> pages_;
};
// ArenaSlotBase accessors need to reach the v8::Globals inside a Page.
// All AliasedStructArena<T>::Page types share the same leading layout.
// The page_byte_length field allows lazy view creation without knowing T.
namespace detail {
struct ArenaPageHeader {
std::shared_ptr<v8::BackingStore> store;
v8::Global<v8::ArrayBuffer> buffer;
v8::Global<v8::DataView> data_view;
v8::Global<v8::BigUint64Array> big_uint64_array;
size_t page_byte_length = 0;
v8::Local<v8::DataView> GetDataView(v8::Isolate* isolate) {
if (data_view.IsEmpty()) {
const v8::HandleScope handle_scope(isolate);
auto dv = v8::DataView::New(buffer.Get(isolate), 0, page_byte_length);
data_view = v8::Global<v8::DataView>(isolate, dv);
}
return data_view.Get(isolate);
}
v8::Local<v8::BigUint64Array> GetBigUint64Array(v8::Isolate* isolate) {
if (big_uint64_array.IsEmpty()) {
const v8::HandleScope handle_scope(isolate);
auto bu = v8::BigUint64Array::New(
buffer.Get(isolate), 0, page_byte_length / sizeof(uint64_t));
big_uint64_array = v8::Global<v8::BigUint64Array>(isolate, bu);
}
return big_uint64_array.Get(isolate);
}
};
} // namespace detail
inline v8::Local<v8::ArrayBuffer> ArenaSlotBase::GetArrayBuffer(
v8::Isolate* isolate) const {
DCHECK_NOT_NULL(page);
auto* header = static_cast<detail::ArenaPageHeader*>(page);
return header->buffer.Get(isolate);
}
inline v8::Local<v8::DataView> ArenaSlotBase::GetPageDataView(
v8::Isolate* isolate) const {
DCHECK_NOT_NULL(page);
auto* header = static_cast<detail::ArenaPageHeader*>(page);
return header->GetDataView(isolate);
}
inline v8::Local<v8::BigUint64Array> ArenaSlotBase::GetPageBigUint64Array(
v8::Isolate* isolate) const {
DCHECK_NOT_NULL(page);
auto* header = static_cast<detail::ArenaPageHeader*>(page);
return header->GetBigUint64Array(isolate);
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ALIASED_STRUCT_H_