GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: aliased_buffer.h Lines: 97 97 100.0 %
Date: 2022-08-16 04:20:39 Branches: 32 54 59.3 %

Line Branch Exec Source
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
181932
  AliasedBufferBase(v8::Isolate* isolate,
38
                    const size_t count,
39
                    const AliasedBufferIndex* index = nullptr)
40
181932
      : isolate_(isolate), count_(count), byte_offset_(0), index_(index) {
41
181932
    CHECK_GT(count, 0);
42
181932
    if (index != nullptr) {
43
      // Will be deserialized later.
44
95958
      return;
45
    }
46
85974
    const v8::HandleScope handle_scope(isolate_);
47
    const size_t size_in_bytes =
48
85974
        MultiplyWithOverflowCheck(sizeof(NativeT), count);
49
50
    // allocate v8 ArrayBuffer
51
85974
    v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
52
        isolate_, size_in_bytes);
53
85974
    buffer_ = static_cast<NativeT*>(ab->Data());
54
55
    // allocate v8 TypedArray
56
85974
    v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count);
57
171948
    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
27956
  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


13978
        index_(index) {
79


13978
    if (index != nullptr) {
80
      // Will be deserialized later.
81
10662
      return;
82
    }
83
3316
    const v8::HandleScope handle_scope(isolate_);
84
3316
    v8::Local<v8::ArrayBuffer> ab = backing_buffer.GetArrayBuffer();
85
86
    // validate that the byte_offset is aligned with sizeof(NativeT)
87

3310
    CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0);
88
    // validate this fits inside the backing buffer
89


6632
    CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count),
90
             ab->ByteLength() - byte_offset);
91
92
3316
    buffer_ = reinterpret_cast<NativeT*>(
93
3316
        const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset));
94
95
3316
    v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count);
96


6632
    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
132
  AliasedBufferIndex Serialize(v8::Local<v8::Context> context,
109
                              v8::SnapshotCreator* creator) {
110
    DCHECK_NULL(index_);
111
264
    return creator->AddData(context, GetJSArray());
112
  }
113
114
117282
  inline void Deserialize(v8::Local<v8::Context> context) {
115
    DCHECK_NOT_NULL(index_);
116
117282
    v8::Local<V8T> arr =
117
351846
        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
234564
    uint8_t* raw = static_cast<uint8_t*>(arr->Buffer()->Data());
123
117282
    buffer_ = reinterpret_cast<NativeT*>(raw + byte_offset_);
124
117282
    js_array_.Reset(isolate_, arr);
125
117282
    index_ = nullptr;
126
117282
  }
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
40865308
    Reference(AliasedBufferBase<NativeT, V8T>* aliased_buffer, size_t index)
150
40865308
        : 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
12133121
    inline Reference& operator=(const NativeT& val) {
158
12133121
      aliased_buffer_->SetValue(index_, val);
159
12133121
      return *this;
160
    }
161
162
3262835
    inline Reference& operator=(const Reference& val) {
163
3262835
      return *this = static_cast<NativeT>(val);
164
    }
165
166
25327483
    operator NativeT() const {
167
25327483
      return aliased_buffer_->GetValue(index_);
168
    }
169
170
1913693
    inline Reference& operator+=(const NativeT& val) {
171
1913693
      const NativeT current = aliased_buffer_->GetValue(index_);
172
1913693
      aliased_buffer_->SetValue(index_, current + val);
173
1913693
      return *this;
174
    }
175
176
1
    inline Reference& operator+=(const Reference& val) {
177
1
      return this->operator+=(static_cast<NativeT>(val));
178
    }
179
180
218961
    inline Reference& operator-=(const NativeT& val) {
181
218961
      const NativeT current = aliased_buffer_->GetValue(index_);
182
218961
      aliased_buffer_->SetValue(index_, current - val);
183
218961
      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
465542
  v8::Local<V8T> GetJSArray() const {
195
    DCHECK_NULL(index_);
196
931084
    return js_array_.Get(isolate_);
197
  }
198
199
60
  void Release() {
200
    DCHECK_NULL(index_);
201
60
    js_array_.Reset();
202
60
  }
203
204
  /**
205
  *  Get the underlying v8::ArrayBuffer underlying the TypedArray and
206
  *  overlaying the native buffer
207
  */
208
3316
  v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
209
6632
    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
3602
  inline const NativeT* GetNativeBuffer() const {
217
    DCHECK_NULL(index_);
218
3602
    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
22234414
  inline void SetValue(const size_t index, NativeT value) {
232
    DCHECK_LT(index, count_);
233
    DCHECK_NULL(index_);
234
22234414
    buffer_[index] = value;
235
22234414
  }
236
237
  /**
238
   *  Get value at position index
239
   */
240
33020978
  inline const NativeT GetValue(const size_t index) const {
241
    DCHECK_NULL(index_);
242
    DCHECK_LT(index, count_);
243
33020978
    return buffer_[index];
244
  }
245
246
  /**
247
   *  Effectively, a synonym for GetValue/SetValue
248
   */
249
40859064
  Reference operator[](size_t index) {
250
    DCHECK_NULL(index_);
251
40859064
    return Reference(this, index);
252
  }
253
254
2561233
  NativeT operator[](size_t index) const {
255
2561233
    return GetValue(index);
256
  }
257
258
821818
  size_t Length() const {
259
821818
    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_