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