• 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 /**
13  * Do not use this class directly when creating instances of it - use the
14  * Aliased*Array defined at the end of this file instead.
15  *
16  * This class encapsulates the technique of having a native buffer mapped to
17  * a JS object. Writes to the native buffer can happen efficiently without
18  * going through JS, and the data is then available to user's via the exposed
19  * JS object.
20  *
21  * While this technique is computationally efficient, it is effectively a
22  * write to JS program state w/out going through the standard
23  * (monitored) API. Thus any VM capabilities to detect the modification are
24  * circumvented.
25  *
26  * The encapsulation herein provides a placeholder where such writes can be
27  * observed. Any notification APIs will be left as a future exercise.
28  */
29 template <class NativeT,
30           class V8T,
31           // SFINAE NativeT to be scalar
32           typename = std::enable_if_t<std::is_scalar<NativeT>::value>>
33 class AliasedBufferBase {
34  public:
AliasedBufferBase(v8::Isolate * isolate,const size_t count)35   AliasedBufferBase(v8::Isolate* isolate, const size_t count)
36       : isolate_(isolate), count_(count), byte_offset_(0) {
37     CHECK_GT(count, 0);
38     const v8::HandleScope handle_scope(isolate_);
39     const size_t size_in_bytes =
40         MultiplyWithOverflowCheck(sizeof(NativeT), count);
41 
42     // allocate v8 ArrayBuffer
43     v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
44         isolate_, size_in_bytes);
45     buffer_ = static_cast<NativeT*>(ab->GetBackingStore()->Data());
46 
47     // allocate v8 TypedArray
48     v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count);
49     js_array_ = v8::Global<V8T>(isolate, js_array);
50   }
51 
52   /**
53    * Create an AliasedBufferBase over a sub-region of another aliased buffer.
54    * The two will share a v8::ArrayBuffer instance &
55    * a native buffer, but will each read/write to different sections of the
56    * native buffer.
57    *
58    *  Note that byte_offset must by aligned by sizeof(NativeT).
59    */
60   // TODO(refack): refactor into a non-owning `AliasedBufferBaseView`
AliasedBufferBase(v8::Isolate * isolate,const size_t byte_offset,const size_t count,const AliasedBufferBase<uint8_t,v8::Uint8Array> & backing_buffer)61   AliasedBufferBase(
62       v8::Isolate* isolate,
63       const size_t byte_offset,
64       const size_t count,
65       const AliasedBufferBase<uint8_t, v8::Uint8Array>& backing_buffer)
66       : isolate_(isolate), count_(count), byte_offset_(byte_offset) {
67     const v8::HandleScope handle_scope(isolate_);
68 
69     v8::Local<v8::ArrayBuffer> ab = backing_buffer.GetArrayBuffer();
70 
71     // validate that the byte_offset is aligned with sizeof(NativeT)
72     CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0);
73     // validate this fits inside the backing buffer
74     CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count),
75              ab->ByteLength() - byte_offset);
76 
77     buffer_ = reinterpret_cast<NativeT*>(
78         const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset));
79 
80     v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count);
81     js_array_ = v8::Global<V8T>(isolate, js_array);
82   }
83 
AliasedBufferBase(const AliasedBufferBase & that)84   AliasedBufferBase(const AliasedBufferBase& that)
85       : isolate_(that.isolate_),
86         count_(that.count_),
87         byte_offset_(that.byte_offset_),
88         buffer_(that.buffer_) {
89     js_array_ = v8::Global<V8T>(that.isolate_, that.GetJSArray());
90   }
91 
92   AliasedBufferBase& operator=(AliasedBufferBase&& that) noexcept {
93     this->~AliasedBufferBase();
94     isolate_ = that.isolate_;
95     count_ = that.count_;
96     byte_offset_ = that.byte_offset_;
97     buffer_ = that.buffer_;
98 
99     js_array_.Reset(isolate_, that.js_array_.Get(isolate_));
100 
101     that.buffer_ = nullptr;
102     that.js_array_.Reset();
103     return *this;
104   }
105 
106   /**
107    * Helper class that is returned from operator[] to support assignment into
108    * a specified location.
109    */
110   class Reference {
111    public:
Reference(AliasedBufferBase<NativeT,V8T> * aliased_buffer,size_t index)112     Reference(AliasedBufferBase<NativeT, V8T>* aliased_buffer, size_t index)
113         : aliased_buffer_(aliased_buffer), index_(index) {}
114 
Reference(const Reference & that)115     Reference(const Reference& that)
116         : aliased_buffer_(that.aliased_buffer_),
117           index_(that.index_) {
118     }
119 
120     inline Reference& operator=(const NativeT& val) {
121       aliased_buffer_->SetValue(index_, val);
122       return *this;
123     }
124 
125     inline Reference& operator=(const Reference& val) {
126       return *this = static_cast<NativeT>(val);
127     }
128 
NativeT()129     operator NativeT() const {
130       return aliased_buffer_->GetValue(index_);
131     }
132 
133     inline Reference& operator+=(const NativeT& val) {
134       const NativeT current = aliased_buffer_->GetValue(index_);
135       aliased_buffer_->SetValue(index_, current + val);
136       return *this;
137     }
138 
139     inline Reference& operator+=(const Reference& val) {
140       return this->operator+=(static_cast<NativeT>(val));
141     }
142 
143     inline Reference& operator-=(const NativeT& val) {
144       const NativeT current = aliased_buffer_->GetValue(index_);
145       aliased_buffer_->SetValue(index_, current - val);
146       return *this;
147     }
148 
149    private:
150     AliasedBufferBase<NativeT, V8T>* aliased_buffer_;
151     size_t index_;
152   };
153 
154   /**
155    *  Get the underlying v8 TypedArray overlayed on top of the native buffer
156    */
GetJSArray()157   v8::Local<V8T> GetJSArray() const {
158     return js_array_.Get(isolate_);
159   }
160 
161   /**
162   *  Get the underlying v8::ArrayBuffer underlying the TypedArray and
163   *  overlaying the native buffer
164   */
GetArrayBuffer()165   v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
166     return GetJSArray()->Buffer();
167   }
168 
169   /**
170    *  Get the underlying native buffer. Note that all reads/writes should occur
171    *  through the GetValue/SetValue/operator[] methods
172    */
GetNativeBuffer()173   inline const NativeT* GetNativeBuffer() const {
174     return buffer_;
175   }
176 
177   /**
178    *  Synonym for GetBuffer()
179    */
180   inline const NativeT* operator * () const {
181     return GetNativeBuffer();
182   }
183 
184   /**
185    *  Set position index to given value.
186    */
SetValue(const size_t index,NativeT value)187   inline void SetValue(const size_t index, NativeT value) {
188     DCHECK_LT(index, count_);
189     buffer_[index] = value;
190   }
191 
192   /**
193    *  Get value at position index
194    */
GetValue(const size_t index)195   inline const NativeT GetValue(const size_t index) const {
196     DCHECK_LT(index, count_);
197     return buffer_[index];
198   }
199 
200   /**
201    *  Effectively, a synonym for GetValue/SetValue
202    */
203   Reference operator[](size_t index) {
204     return Reference(this, index);
205   }
206 
207   NativeT operator[](size_t index) const {
208     return GetValue(index);
209   }
210 
Length()211   size_t Length() const {
212     return count_;
213   }
214 
215   // Should only be used to extend the array.
216   // Should only be used on an owning array, not one created as a sub array of
217   // an owning `AliasedBufferBase`.
reserve(size_t new_capacity)218   void reserve(size_t new_capacity) {
219     DCHECK_GE(new_capacity, count_);
220     DCHECK_EQ(byte_offset_, 0);
221     const v8::HandleScope handle_scope(isolate_);
222 
223     const size_t old_size_in_bytes = sizeof(NativeT) * count_;
224     const size_t new_size_in_bytes = MultiplyWithOverflowCheck(sizeof(NativeT),
225                                                               new_capacity);
226 
227     // allocate v8 new ArrayBuffer
228     v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
229         isolate_, new_size_in_bytes);
230 
231     // allocate new native buffer
232     NativeT* new_buffer = static_cast<NativeT*>(ab->GetBackingStore()->Data());
233     // copy old content
234     memcpy(new_buffer, buffer_, old_size_in_bytes);
235 
236     // allocate v8 TypedArray
237     v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, new_capacity);
238 
239     // move over old v8 TypedArray
240     js_array_ = std::move(v8::Global<V8T>(isolate_, js_array));
241 
242     buffer_ = new_buffer;
243     count_ = new_capacity;
244   }
245 
246  private:
247   v8::Isolate* isolate_;
248   size_t count_;
249   size_t byte_offset_;
250   NativeT* buffer_;
251   v8::Global<V8T> js_array_;
252 };
253 
254 typedef AliasedBufferBase<int32_t, v8::Int32Array> AliasedInt32Array;
255 typedef AliasedBufferBase<uint8_t, v8::Uint8Array> AliasedUint8Array;
256 typedef AliasedBufferBase<uint32_t, v8::Uint32Array> AliasedUint32Array;
257 typedef AliasedBufferBase<double, v8::Float64Array> AliasedFloat64Array;
258 typedef AliasedBufferBase<uint64_t, v8::BigUint64Array> AliasedBigUint64Array;
259 }  // namespace node
260 
261 #endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
262 
263 #endif  // SRC_ALIASED_BUFFER_H_
264