1 |
|
|
#ifndef SRC_ALIASED_BUFFER_H_ |
2 |
|
|
#define SRC_ALIASED_BUFFER_H_ |
3 |
|
|
|
4 |
|
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
5 |
|
|
|
6 |
|
|
#include <cinttypes> |
7 |
|
|
#include "util-inl.h" |
8 |
|
|
#include "v8.h" |
9 |
|
|
|
10 |
|
|
namespace node { |
11 |
|
|
|
12 |
|
|
typedef size_t AliasedBufferIndex; |
13 |
|
|
|
14 |
|
|
/** |
15 |
|
|
* Do not use this class directly when creating instances of it - use the |
16 |
|
|
* Aliased*Array defined at the end of this file instead. |
17 |
|
|
* |
18 |
|
|
* This class encapsulates the technique of having a native buffer mapped to |
19 |
|
|
* a JS object. Writes to the native buffer can happen efficiently without |
20 |
|
|
* going through JS, and the data is then available to user's via the exposed |
21 |
|
|
* JS object. |
22 |
|
|
* |
23 |
|
|
* While this technique is computationally efficient, it is effectively a |
24 |
|
|
* write to JS program state w/out going through the standard |
25 |
|
|
* (monitored) API. Thus any VM capabilities to detect the modification are |
26 |
|
|
* circumvented. |
27 |
|
|
* |
28 |
|
|
* The encapsulation herein provides a placeholder where such writes can be |
29 |
|
|
* observed. Any notification APIs will be left as a future exercise. |
30 |
|
|
*/ |
31 |
|
|
template <class NativeT, |
32 |
|
|
class V8T, |
33 |
|
|
// SFINAE NativeT to be scalar |
34 |
|
|
typename = std::enable_if_t<std::is_scalar<NativeT>::value>> |
35 |
|
|
class AliasedBufferBase { |
36 |
|
|
public: |
37 |
|
160884 |
AliasedBufferBase(v8::Isolate* isolate, |
38 |
|
|
const size_t count, |
39 |
|
|
const AliasedBufferIndex* index = nullptr) |
40 |
✗✓ |
160884 |
: isolate_(isolate), count_(count), byte_offset_(0), index_(index) { |
41 |
✗✓ |
160884 |
CHECK_GT(count, 0); |
42 |
✓✓ |
160884 |
if (index != nullptr) { |
43 |
|
|
// Will be deserialized later. |
44 |
|
102744 |
return; |
45 |
|
|
} |
46 |
|
58140 |
const v8::HandleScope handle_scope(isolate_); |
47 |
|
|
const size_t size_in_bytes = |
48 |
|
58140 |
MultiplyWithOverflowCheck(sizeof(NativeT), count); |
49 |
|
|
|
50 |
|
|
// allocate v8 ArrayBuffer |
51 |
|
58140 |
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New( |
52 |
|
|
isolate_, size_in_bytes); |
53 |
|
58140 |
buffer_ = static_cast<NativeT*>(ab->Data()); |
54 |
|
|
|
55 |
|
|
// allocate v8 TypedArray |
56 |
|
58140 |
v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count); |
57 |
✓✗ |
116280 |
js_array_ = v8::Global<V8T>(isolate, js_array); |
58 |
|
|
} |
59 |
|
|
|
60 |
|
|
/** |
61 |
|
|
* Create an AliasedBufferBase over a sub-region of another aliased buffer. |
62 |
|
|
* The two will share a v8::ArrayBuffer instance & |
63 |
|
|
* a native buffer, but will each read/write to different sections of the |
64 |
|
|
* native buffer. |
65 |
|
|
* |
66 |
|
|
* Note that byte_offset must by aligned by sizeof(NativeT). |
67 |
|
|
*/ |
68 |
|
|
// TODO(refack): refactor into a non-owning `AliasedBufferBaseView` |
69 |
|
29744 |
AliasedBufferBase( |
70 |
|
|
v8::Isolate* isolate, |
71 |
|
|
const size_t byte_offset, |
72 |
|
|
const size_t count, |
73 |
|
|
const AliasedBufferBase<uint8_t, v8::Uint8Array>& backing_buffer, |
74 |
|
|
const AliasedBufferIndex* index = nullptr) |
75 |
|
|
: isolate_(isolate), |
76 |
|
|
count_(count), |
77 |
|
|
byte_offset_(byte_offset), |
78 |
✓✓ |
29744 |
index_(index) { |
79 |
✓✓ |
29744 |
if (index != nullptr) { |
80 |
|
|
// Will be deserialized later. |
81 |
|
22836 |
return; |
82 |
|
|
} |
83 |
|
6908 |
const v8::HandleScope handle_scope(isolate_); |
84 |
|
6908 |
v8::Local<v8::ArrayBuffer> ab = backing_buffer.GetArrayBuffer(); |
85 |
|
|
|
86 |
|
|
// validate that the byte_offset is aligned with sizeof(NativeT) |
87 |
✗✓ |
6896 |
CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0); |
88 |
|
|
// validate this fits inside the backing buffer |
89 |
✗✓ |
13816 |
CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count), |
90 |
|
|
ab->ByteLength() - byte_offset); |
91 |
|
|
|
92 |
|
6908 |
buffer_ = reinterpret_cast<NativeT*>( |
93 |
|
6908 |
const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset)); |
94 |
|
|
|
95 |
|
6908 |
v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count); |
96 |
✓✗ |
13816 |
js_array_ = v8::Global<V8T>(isolate, js_array); |
97 |
|
|
} |
98 |
|
|
|
99 |
|
16 |
AliasedBufferBase(const AliasedBufferBase& that) |
100 |
|
16 |
: isolate_(that.isolate_), |
101 |
|
16 |
count_(that.count_), |
102 |
|
16 |
byte_offset_(that.byte_offset_), |
103 |
|
16 |
buffer_(that.buffer_) { |
104 |
|
|
DCHECK_NULL(index_); |
105 |
✓✗ |
32 |
js_array_ = v8::Global<V8T>(that.isolate_, that.GetJSArray()); |
106 |
|
16 |
} |
107 |
|
|
|
108 |
|
154 |
AliasedBufferIndex Serialize(v8::Local<v8::Context> context, |
109 |
|
|
v8::SnapshotCreator* creator) { |
110 |
|
|
DCHECK_NULL(index_); |
111 |
|
308 |
return creator->AddData(context, GetJSArray()); |
112 |
|
|
} |
113 |
|
|
|
114 |
|
125576 |
inline void Deserialize(v8::Local<v8::Context> context) { |
115 |
|
|
DCHECK_NOT_NULL(index_); |
116 |
|
125576 |
v8::Local<V8T> arr = |
117 |
✗✓ |
376728 |
context->GetDataFromSnapshotOnce<V8T>(*index_).ToLocalChecked(); |
118 |
|
|
// These may not hold true for AliasedBuffers that have grown, so should |
119 |
|
|
// be removed when we expand the snapshot support. |
120 |
|
|
DCHECK_EQ(count_, arr->Length()); |
121 |
|
|
DCHECK_EQ(byte_offset_, arr->ByteOffset()); |
122 |
|
251152 |
uint8_t* raw = static_cast<uint8_t*>(arr->Buffer()->Data()); |
123 |
|
125576 |
buffer_ = reinterpret_cast<NativeT*>(raw + byte_offset_); |
124 |
|
125576 |
js_array_.Reset(isolate_, arr); |
125 |
|
125576 |
index_ = nullptr; |
126 |
|
125576 |
} |
127 |
|
|
|
128 |
|
|
AliasedBufferBase& operator=(AliasedBufferBase&& that) noexcept { |
129 |
|
|
DCHECK_NULL(index_); |
130 |
|
|
this->~AliasedBufferBase(); |
131 |
|
|
isolate_ = that.isolate_; |
132 |
|
|
count_ = that.count_; |
133 |
|
|
byte_offset_ = that.byte_offset_; |
134 |
|
|
buffer_ = that.buffer_; |
135 |
|
|
|
136 |
|
|
js_array_.Reset(isolate_, that.js_array_.Get(isolate_)); |
137 |
|
|
|
138 |
|
|
that.buffer_ = nullptr; |
139 |
|
|
that.js_array_.Reset(); |
140 |
|
|
return *this; |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
/** |
144 |
|
|
* Helper class that is returned from operator[] to support assignment into |
145 |
|
|
* a specified location. |
146 |
|
|
*/ |
147 |
|
|
class Reference { |
148 |
|
|
public: |
149 |
|
42558850 |
Reference(AliasedBufferBase<NativeT, V8T>* aliased_buffer, size_t index) |
150 |
|
42558850 |
: aliased_buffer_(aliased_buffer), index_(index) {} |
151 |
|
|
|
152 |
|
1 |
Reference(const Reference& that) |
153 |
|
1 |
: aliased_buffer_(that.aliased_buffer_), |
154 |
|
1 |
index_(that.index_) { |
155 |
|
1 |
} |
156 |
|
|
|
157 |
|
13366892 |
inline Reference& operator=(const NativeT& val) { |
158 |
|
13366892 |
aliased_buffer_->SetValue(index_, val); |
159 |
|
13366892 |
return *this; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
3398553 |
inline Reference& operator=(const Reference& val) { |
163 |
|
3398553 |
return *this = static_cast<NativeT>(val); |
164 |
|
|
} |
165 |
|
|
|
166 |
|
26215372 |
operator NativeT() const { |
167 |
|
26215372 |
return aliased_buffer_->GetValue(index_); |
168 |
|
|
} |
169 |
|
|
|
170 |
|
2008771 |
inline Reference& operator+=(const NativeT& val) { |
171 |
|
2008771 |
const NativeT current = aliased_buffer_->GetValue(index_); |
172 |
|
2008771 |
aliased_buffer_->SetValue(index_, current + val); |
173 |
|
2008771 |
return *this; |
174 |
|
|
} |
175 |
|
|
|
176 |
|
1 |
inline Reference& operator+=(const Reference& val) { |
177 |
|
1 |
return this->operator+=(static_cast<NativeT>(val)); |
178 |
|
|
} |
179 |
|
|
|
180 |
|
232783 |
inline Reference& operator-=(const NativeT& val) { |
181 |
|
232783 |
const NativeT current = aliased_buffer_->GetValue(index_); |
182 |
|
232783 |
aliased_buffer_->SetValue(index_, current - val); |
183 |
|
232783 |
return *this; |
184 |
|
|
} |
185 |
|
|
|
186 |
|
|
private: |
187 |
|
|
AliasedBufferBase<NativeT, V8T>* aliased_buffer_; |
188 |
|
|
size_t index_; |
189 |
|
|
}; |
190 |
|
|
|
191 |
|
|
/** |
192 |
|
|
* Get the underlying v8 TypedArray overlayed on top of the native buffer |
193 |
|
|
*/ |
194 |
|
463072 |
v8::Local<V8T> GetJSArray() const { |
195 |
|
|
DCHECK_NULL(index_); |
196 |
✗✓ |
926144 |
return js_array_.Get(isolate_); |
197 |
|
|
} |
198 |
|
|
|
199 |
|
28 |
void Release() { |
200 |
|
|
DCHECK_NULL(index_); |
201 |
|
28 |
js_array_.Reset(); |
202 |
|
28 |
} |
203 |
|
|
|
204 |
|
|
/** |
205 |
|
|
* Get the underlying v8::ArrayBuffer underlying the TypedArray and |
206 |
|
|
* overlaying the native buffer |
207 |
|
|
*/ |
208 |
|
3454 |
v8::Local<v8::ArrayBuffer> GetArrayBuffer() const { |
209 |
|
6908 |
return GetJSArray()->Buffer(); |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
/** |
213 |
|
|
* Get the underlying native buffer. Note that all reads/writes should occur |
214 |
|
|
* through the GetValue/SetValue/operator[] methods |
215 |
|
|
*/ |
216 |
|
3744 |
inline const NativeT* GetNativeBuffer() const { |
217 |
|
|
DCHECK_NULL(index_); |
218 |
|
3744 |
return buffer_; |
219 |
|
|
} |
220 |
|
|
|
221 |
|
|
/** |
222 |
|
|
* Synonym for GetBuffer() |
223 |
|
|
*/ |
224 |
|
96 |
inline const NativeT* operator * () const { |
225 |
|
96 |
return GetNativeBuffer(); |
226 |
|
|
} |
227 |
|
|
|
228 |
|
|
/** |
229 |
|
|
* Set position index to given value. |
230 |
|
|
*/ |
231 |
|
23329594 |
inline void SetValue(const size_t index, NativeT value) { |
232 |
|
|
DCHECK_LT(index, count_); |
233 |
|
|
DCHECK_NULL(index_); |
234 |
|
23329594 |
buffer_[index] = value; |
235 |
|
23329594 |
} |
236 |
|
|
|
237 |
|
|
/** |
238 |
|
|
* Get value at position index |
239 |
|
|
*/ |
240 |
|
34318093 |
inline const NativeT GetValue(const size_t index) const { |
241 |
|
|
DCHECK_NULL(index_); |
242 |
|
|
DCHECK_LT(index, count_); |
243 |
|
34318093 |
return buffer_[index]; |
244 |
|
|
} |
245 |
|
|
|
246 |
|
|
/** |
247 |
|
|
* Effectively, a synonym for GetValue/SetValue |
248 |
|
|
*/ |
249 |
|
42552911 |
Reference operator[](size_t index) { |
250 |
|
|
DCHECK_NULL(index_); |
251 |
|
42552911 |
return Reference(this, index); |
252 |
|
|
} |
253 |
|
|
|
254 |
|
2702748 |
NativeT operator[](size_t index) const { |
255 |
|
2702748 |
return GetValue(index); |
256 |
|
|
} |
257 |
|
|
|
258 |
|
856194 |
size_t Length() const { |
259 |
|
856194 |
return count_; |
260 |
|
|
} |
261 |
|
|
|
262 |
|
|
// Should only be used to extend the array. |
263 |
|
|
// Should only be used on an owning array, not one created as a sub array of |
264 |
|
|
// an owning `AliasedBufferBase`. |
265 |
|
4 |
void reserve(size_t new_capacity) { |
266 |
|
|
DCHECK_NULL(index_); |
267 |
|
|
DCHECK_GE(new_capacity, count_); |
268 |
|
|
DCHECK_EQ(byte_offset_, 0); |
269 |
|
4 |
const v8::HandleScope handle_scope(isolate_); |
270 |
|
|
|
271 |
|
4 |
const size_t old_size_in_bytes = sizeof(NativeT) * count_; |
272 |
|
4 |
const size_t new_size_in_bytes = MultiplyWithOverflowCheck(sizeof(NativeT), |
273 |
|
|
new_capacity); |
274 |
|
|
|
275 |
|
|
// allocate v8 new ArrayBuffer |
276 |
|
4 |
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New( |
277 |
|
|
isolate_, new_size_in_bytes); |
278 |
|
|
|
279 |
|
|
// allocate new native buffer |
280 |
|
4 |
NativeT* new_buffer = static_cast<NativeT*>(ab->Data()); |
281 |
|
|
// copy old content |
282 |
|
4 |
memcpy(new_buffer, buffer_, old_size_in_bytes); |
283 |
|
|
|
284 |
|
|
// allocate v8 TypedArray |
285 |
|
4 |
v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, new_capacity); |
286 |
|
|
|
287 |
|
|
// move over old v8 TypedArray |
288 |
✓✗ |
8 |
js_array_ = std::move(v8::Global<V8T>(isolate_, js_array)); |
289 |
|
|
|
290 |
|
4 |
buffer_ = new_buffer; |
291 |
|
4 |
count_ = new_capacity; |
292 |
|
4 |
} |
293 |
|
|
|
294 |
|
|
private: |
295 |
|
|
v8::Isolate* isolate_ = nullptr; |
296 |
|
|
size_t count_ = 0; |
297 |
|
|
size_t byte_offset_ = 0; |
298 |
|
|
NativeT* buffer_ = nullptr; |
299 |
|
|
v8::Global<V8T> js_array_; |
300 |
|
|
|
301 |
|
|
// Deserialize data |
302 |
|
|
const AliasedBufferIndex* index_ = nullptr; |
303 |
|
|
}; |
304 |
|
|
|
305 |
|
|
typedef AliasedBufferBase<int32_t, v8::Int32Array> AliasedInt32Array; |
306 |
|
|
typedef AliasedBufferBase<uint8_t, v8::Uint8Array> AliasedUint8Array; |
307 |
|
|
typedef AliasedBufferBase<uint32_t, v8::Uint32Array> AliasedUint32Array; |
308 |
|
|
typedef AliasedBufferBase<double, v8::Float64Array> AliasedFloat64Array; |
309 |
|
|
typedef AliasedBufferBase<int64_t, v8::BigInt64Array> AliasedBigInt64Array; |
310 |
|
|
} // namespace node |
311 |
|
|
|
312 |
|
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
313 |
|
|
|
314 |
|
|
#endif // SRC_ALIASED_BUFFER_H_ |