• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   AliasedBufferBase(v8::Isolate* isolate,
38                     const size_t count,
39                     const AliasedBufferIndex* index = nullptr)
isolate_(isolate)40       : isolate_(isolate), count_(count), byte_offset_(0), index_(index) {
41     CHECK_GT(count, 0);
42     if (index != nullptr) {
43       // Will be deserialized later.
44       return;
45     }
46     const v8::HandleScope handle_scope(isolate_);
47     const size_t size_in_bytes =
48         MultiplyWithOverflowCheck(sizeof(NativeT), count);
49 
50     // allocate v8 ArrayBuffer
51     v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
52         isolate_, size_in_bytes);
53     buffer_ = static_cast<NativeT*>(ab->Data());
54 
55     // allocate v8 TypedArray
56     v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count);
57     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   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)
isolate_(isolate)75       : isolate_(isolate),
76         count_(count),
77         byte_offset_(byte_offset),
78         index_(index) {
79     if (index != nullptr) {
80       // Will be deserialized later.
81       return;
82     }
83     const v8::HandleScope handle_scope(isolate_);
84     v8::Local<v8::ArrayBuffer> ab = backing_buffer.GetArrayBuffer();
85 
86     // validate that the byte_offset is aligned with sizeof(NativeT)
87     CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0);
88     // validate this fits inside the backing buffer
89     CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count),
90              ab->ByteLength() - byte_offset);
91 
92     buffer_ = reinterpret_cast<NativeT*>(
93         const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset));
94 
95     v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count);
96     js_array_ = v8::Global<V8T>(isolate, js_array);
97   }
98 
AliasedBufferBase(const AliasedBufferBase & that)99   AliasedBufferBase(const AliasedBufferBase& that)
100       : isolate_(that.isolate_),
101         count_(that.count_),
102         byte_offset_(that.byte_offset_),
103         buffer_(that.buffer_) {
104     DCHECK_NULL(index_);
105     js_array_ = v8::Global<V8T>(that.isolate_, that.GetJSArray());
106   }
107 
Serialize(v8::Local<v8::Context> context,v8::SnapshotCreator * creator)108   AliasedBufferIndex Serialize(v8::Local<v8::Context> context,
109                               v8::SnapshotCreator* creator) {
110     DCHECK_NULL(index_);
111     return creator->AddData(context, GetJSArray());
112   }
113 
Deserialize(v8::Local<v8::Context> context)114   inline void Deserialize(v8::Local<v8::Context> context) {
115     DCHECK_NOT_NULL(index_);
116     v8::Local<V8T> arr =
117         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     uint8_t* raw = static_cast<uint8_t*>(arr->Buffer()->Data());
123     buffer_ = reinterpret_cast<NativeT*>(raw + byte_offset_);
124     js_array_.Reset(isolate_, arr);
125     index_ = nullptr;
126   }
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:
Reference(AliasedBufferBase<NativeT,V8T> * aliased_buffer,size_t index)149     Reference(AliasedBufferBase<NativeT, V8T>* aliased_buffer, size_t index)
150         : aliased_buffer_(aliased_buffer), index_(index) {}
151 
Reference(const Reference & that)152     Reference(const Reference& that)
153         : aliased_buffer_(that.aliased_buffer_),
154           index_(that.index_) {
155     }
156 
157     inline Reference& operator=(const NativeT& val) {
158       aliased_buffer_->SetValue(index_, val);
159       return *this;
160     }
161 
162     inline Reference& operator=(const Reference& val) {
163       return *this = static_cast<NativeT>(val);
164     }
165 
NativeT()166     operator NativeT() const {
167       return aliased_buffer_->GetValue(index_);
168     }
169 
170     inline Reference& operator+=(const NativeT& val) {
171       const NativeT current = aliased_buffer_->GetValue(index_);
172       aliased_buffer_->SetValue(index_, current + val);
173       return *this;
174     }
175 
176     inline Reference& operator+=(const Reference& val) {
177       return this->operator+=(static_cast<NativeT>(val));
178     }
179 
180     inline Reference& operator-=(const NativeT& val) {
181       const NativeT current = aliased_buffer_->GetValue(index_);
182       aliased_buffer_->SetValue(index_, current - val);
183       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    */
GetJSArray()194   v8::Local<V8T> GetJSArray() const {
195     DCHECK_NULL(index_);
196     return js_array_.Get(isolate_);
197   }
198 
Release()199   void Release() {
200     DCHECK_NULL(index_);
201     js_array_.Reset();
202   }
203 
204   /**
205   *  Get the underlying v8::ArrayBuffer underlying the TypedArray and
206   *  overlaying the native buffer
207   */
GetArrayBuffer()208   v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
209     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    */
GetNativeBuffer()216   inline const NativeT* GetNativeBuffer() const {
217     DCHECK_NULL(index_);
218     return buffer_;
219   }
220 
221   /**
222    *  Synonym for GetBuffer()
223    */
224   inline const NativeT* operator * () const {
225     return GetNativeBuffer();
226   }
227 
228   /**
229    *  Set position index to given value.
230    */
SetValue(const size_t index,NativeT value)231   inline void SetValue(const size_t index, NativeT value) {
232     DCHECK_LT(index, count_);
233     DCHECK_NULL(index_);
234     buffer_[index] = value;
235   }
236 
237   /**
238    *  Get value at position index
239    */
GetValue(const size_t index)240   inline const NativeT GetValue(const size_t index) const {
241     DCHECK_NULL(index_);
242     DCHECK_LT(index, count_);
243     return buffer_[index];
244   }
245 
246   /**
247    *  Effectively, a synonym for GetValue/SetValue
248    */
249   Reference operator[](size_t index) {
250     DCHECK_NULL(index_);
251     return Reference(this, index);
252   }
253 
254   NativeT operator[](size_t index) const {
255     return GetValue(index);
256   }
257 
Length()258   size_t Length() const {
259     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`.
reserve(size_t new_capacity)265   void reserve(size_t new_capacity) {
266     DCHECK_NULL(index_);
267     DCHECK_GE(new_capacity, count_);
268     DCHECK_EQ(byte_offset_, 0);
269     const v8::HandleScope handle_scope(isolate_);
270 
271     const size_t old_size_in_bytes = sizeof(NativeT) * count_;
272     const size_t new_size_in_bytes = MultiplyWithOverflowCheck(sizeof(NativeT),
273                                                               new_capacity);
274 
275     // allocate v8 new ArrayBuffer
276     v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
277         isolate_, new_size_in_bytes);
278 
279     // allocate new native buffer
280     NativeT* new_buffer = static_cast<NativeT*>(ab->Data());
281     // copy old content
282     memcpy(new_buffer, buffer_, old_size_in_bytes);
283 
284     // allocate v8 TypedArray
285     v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, new_capacity);
286 
287     // move over old v8 TypedArray
288     js_array_ = std::move(v8::Global<V8T>(isolate_, js_array));
289 
290     buffer_ = new_buffer;
291     count_ = new_capacity;
292   }
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_
315