Skip to content

Commit a32c8a5

Browse files
Mike Kaufmanjasnell
Mike Kaufman
authored andcommitted
http2,async-wrap: introduce AliasedBuffer class
This change introduces an AliasedBuffer class and updates asytnc-wrap and http2 to use this class. A common technique to optimize performance is to create a native buffer and then map that native buffer to user space via JS array. The runtime can efficiently write to the native buffer without having to route though JS, and the values being written are accessible from user space. While efficient, this technique allows modifications to user space memory w/out going through JS type system APIs, effectively bypassing any monitoring the JS VM has in place to track program state modifications. The result is that monitors have an incorrect view of prorgram state. The AliasedBuffer class provides a future placeholder where this technique can be used, but writes can still be observed. To achieve this, the node-chakra-core fork will add in appropriate tracking logic in the AliasedBuffer's SetValue() method. Going forward, this class can evolve to support more sophisticated mechanisms if necessary. PR-URL: #15077 Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent b0f5b2a commit a32c8a5

File tree

11 files changed

+610
-123
lines changed

11 files changed

+610
-123
lines changed

node.gyp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
'src/util.cc',
229229
'src/uv.cc',
230230
# headers to make for a more pleasant IDE experience
231+
'src/aliased_buffer.h',
231232
'src/async-wrap.h',
232233
'src/async-wrap-inl.h',
233234
'src/base-object.h',
@@ -246,6 +247,7 @@
246247
'src/node_constants.h',
247248
'src/node_debug_options.h',
248249
'src/node_http2.h',
250+
'src/node_http2_state.h',
249251
'src/node_internals.h',
250252
'src/node_javascript.h',
251253
'src/node_mutex.h',
@@ -650,6 +652,8 @@
650652
'sources': [
651653
'src/node_platform.cc',
652654
'src/node_platform.h',
655+
'test/cctest/node_test_fixture.cc',
656+
'test/cctest/test_aliased_buffer.cc',
653657
'test/cctest/test_base64.cc',
654658
'test/cctest/test_environment.cc',
655659
'test/cctest/test_util.cc',

src/aliased_buffer.h

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
2+
#ifndef SRC_ALIASED_BUFFER_H_
3+
#define SRC_ALIASED_BUFFER_H_
4+
5+
#include "v8.h"
6+
#include "util.h"
7+
#include "util-inl.h"
8+
9+
namespace node {
10+
11+
/**
12+
* This class encapsulates the technique of having a native buffer mapped to
13+
* a JS object. Writes to the native buffer can happen efficiently without
14+
* going through JS, and the data is then available to user's via the exposed
15+
* JS object.
16+
*
17+
* While this technique is computationaly efficient, it is effectively a
18+
* write to JS program state w/out going through the standard
19+
* (monitored) API. Thus any VM capabilities to detect the modification are
20+
* circumvented.
21+
*
22+
* The encapsulation herein provides a placeholder where such writes can be
23+
* observed. Any notification APIs will be left as a future exercise.
24+
*/
25+
template <class NativeT, class V8T>
26+
class AliasedBuffer {
27+
public:
28+
AliasedBuffer(v8::Isolate* isolate, const size_t count)
29+
: isolate_(isolate),
30+
count_(count),
31+
byte_offset_(0),
32+
free_buffer_(true) {
33+
CHECK_GT(count, 0);
34+
const v8::HandleScope handle_scope(isolate_);
35+
36+
const size_t sizeInBytes = sizeof(NativeT) * count;
37+
38+
// allocate native buffer
39+
buffer_ = Calloc<NativeT>(count);
40+
41+
// allocate v8 ArrayBuffer
42+
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(
43+
isolate_, buffer_, sizeInBytes);
44+
45+
// allocate v8 TypedArray
46+
v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count);
47+
js_array_ = v8::Global<V8T>(isolate, js_array);
48+
}
49+
50+
/**
51+
* Create an AliasedBuffer over a sub-region of another aliased buffer.
52+
* The two will share a v8::ArrayBuffer instance &
53+
* a native buffer, but will each read/write to different sections of the
54+
* native buffer.
55+
*
56+
* Note that byte_offset must by aligned by sizeof(NativeT).
57+
*/
58+
AliasedBuffer(v8::Isolate* isolate,
59+
const size_t byte_offset,
60+
const size_t count,
61+
const AliasedBuffer<uint8_t,
62+
v8::Uint8Array>& backing_buffer)
63+
: isolate_(isolate),
64+
count_(count),
65+
byte_offset_(byte_offset),
66+
free_buffer_(false) {
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(sizeof(NativeT) * count, ab->ByteLength() - byte_offset);
75+
76+
buffer_ = reinterpret_cast<NativeT*>(
77+
const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset));
78+
79+
v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count);
80+
js_array_ = v8::Global<V8T>(isolate, js_array);
81+
}
82+
83+
AliasedBuffer(const AliasedBuffer& that)
84+
: isolate_(that.isolate_),
85+
count_(that.count_),
86+
byte_offset_(that.byte_offset_),
87+
buffer_(that.buffer_),
88+
free_buffer_(false) {
89+
js_array_ = v8::Global<V8T>(that.isolate_, that.GetJSArray());
90+
}
91+
92+
~AliasedBuffer() {
93+
if (free_buffer_ && buffer_ != nullptr) {
94+
free(buffer_);
95+
}
96+
js_array_.Reset();
97+
}
98+
99+
/**
100+
* Helper class that is returned from operator[] to support assignment into
101+
* a specified location.
102+
*/
103+
class Reference {
104+
public:
105+
Reference(AliasedBuffer<NativeT, V8T>* aliased_buffer, size_t index)
106+
: aliased_buffer_(aliased_buffer),
107+
index_(index) {
108+
}
109+
110+
Reference(const Reference& that)
111+
: aliased_buffer_(that.aliased_buffer_),
112+
index_(that.index_) {
113+
}
114+
115+
inline Reference& operator=(const NativeT &val) {
116+
aliased_buffer_->SetValue(index_, val);
117+
return *this;
118+
}
119+
120+
operator NativeT() const {
121+
return aliased_buffer_->GetValue(index_);
122+
}
123+
124+
private:
125+
AliasedBuffer<NativeT, V8T>* aliased_buffer_;
126+
size_t index_;
127+
};
128+
129+
/**
130+
* Get the underlying v8 TypedArray overlayed on top of the native buffer
131+
*/
132+
v8::Local<V8T> GetJSArray() const {
133+
return js_array_.Get(isolate_);
134+
}
135+
136+
/**
137+
* Get the underlying v8::ArrayBuffer underlying the TypedArray and
138+
* overlaying the native buffer
139+
*/
140+
v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
141+
return GetJSArray()->Buffer();
142+
}
143+
144+
/**
145+
* Get the underlying native buffer. Note that all reads/writes should occur
146+
* through the GetValue/SetValue/operator[] methods
147+
*/
148+
inline const NativeT* GetNativeBuffer() const {
149+
return buffer_;
150+
}
151+
152+
/**
153+
* Synonym for GetBuffer()
154+
*/
155+
inline const NativeT* operator * () const {
156+
return GetNativeBuffer();
157+
}
158+
159+
/**
160+
* Set position index to given value.
161+
*/
162+
inline void SetValue(const size_t index, NativeT value) {
163+
#if defined(DEBUG) && DEBUG
164+
CHECK_LT(index, count_);
165+
#endif
166+
buffer_[index] = value;
167+
}
168+
169+
/**
170+
* Get value at position index
171+
*/
172+
inline const NativeT GetValue(const size_t index) const {
173+
#if defined(DEBUG) && DEBUG
174+
CHECK_LT(index, count_);
175+
#endif
176+
return buffer_[index];
177+
}
178+
179+
/**
180+
* Effectively, a synonym for GetValue/SetValue
181+
*/
182+
Reference operator[](size_t index) {
183+
return Reference(this, index);
184+
}
185+
186+
NativeT operator[](size_t index) const {
187+
return GetValue(index);
188+
}
189+
190+
private:
191+
v8::Isolate* const isolate_;
192+
size_t count_;
193+
size_t byte_offset_;
194+
NativeT* buffer_;
195+
v8::Global<V8T> js_array_;
196+
bool free_buffer_;
197+
};
198+
} // namespace node
199+
200+
#endif // SRC_ALIASED_BUFFER_H_

src/async-wrap.cc

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
#include "v8-profiler.h"
3232

3333
using v8::Array;
34-
using v8::ArrayBuffer;
3534
using v8::Context;
3635
using v8::Float64Array;
3736
using v8::Function;
@@ -53,7 +52,6 @@ using v8::RetainedObjectInfo;
5352
using v8::String;
5453
using v8::Symbol;
5554
using v8::TryCatch;
56-
using v8::Uint32Array;
5755
using v8::Undefined;
5856
using v8::Value;
5957

@@ -476,13 +474,9 @@ void AsyncWrap::Initialize(Local<Object> target,
476474
// callbacks waiting to be called on a particular event. It can then be
477475
// incremented/decremented from JS quickly to communicate to C++ if there are
478476
// any callbacks waiting to be called.
479-
uint32_t* fields_ptr = env->async_hooks()->fields();
480-
int fields_count = env->async_hooks()->fields_count();
481-
Local<ArrayBuffer> fields_ab =
482-
ArrayBuffer::New(isolate, fields_ptr, fields_count * sizeof(*fields_ptr));
483477
FORCE_SET_TARGET_FIELD(target,
484478
"async_hook_fields",
485-
Uint32Array::New(fields_ab, 0, fields_count));
479+
env->async_hooks()->fields().GetJSArray());
486480

487481
// The following v8::Float64Array has 5 fields. These fields are shared in
488482
// this way to allow JS and C++ to read/write each value as quickly as
@@ -493,15 +487,9 @@ void AsyncWrap::Initialize(Local<Object> target,
493487
// kInitTriggerId: Write the id of the resource responsible for a handle's
494488
// creation just before calling the new handle's constructor. After the new
495489
// handle is constructed kInitTriggerId is set back to 0.
496-
double* uid_fields_ptr = env->async_hooks()->uid_fields();
497-
int uid_fields_count = env->async_hooks()->uid_fields_count();
498-
Local<ArrayBuffer> uid_fields_ab = ArrayBuffer::New(
499-
isolate,
500-
uid_fields_ptr,
501-
uid_fields_count * sizeof(*uid_fields_ptr));
502490
FORCE_SET_TARGET_FIELD(target,
503491
"async_uid_fields",
504-
Float64Array::New(uid_fields_ab, 0, uid_fields_count));
492+
env->async_hooks()->uid_fields().GetJSArray());
505493

506494
Local<Object> constants = Object::New(isolate);
507495
#define SET_HOOKS_CONSTANT(name) \

src/env-inl.h

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
2626

27+
#include "aliased_buffer.h"
2728
#include "env.h"
2829
#include "node.h"
2930
#include "util.h"
@@ -82,8 +83,8 @@ inline uint32_t* IsolateData::zero_fill_field() const {
8283

8384
inline Environment::AsyncHooks::AsyncHooks(v8::Isolate* isolate)
8485
: isolate_(isolate),
85-
fields_(),
86-
uid_fields_() {
86+
fields_(isolate, kFieldsCount),
87+
uid_fields_(isolate, kUidFieldsCount) {
8788
v8::HandleScope handle_scope(isolate_);
8889

8990
// kAsyncUidCntr should start at 1 because that'll be the id the execution
@@ -105,15 +106,17 @@ inline Environment::AsyncHooks::AsyncHooks(v8::Isolate* isolate)
105106
#undef V
106107
}
107108

108-
inline uint32_t* Environment::AsyncHooks::fields() {
109+
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
110+
Environment::AsyncHooks::fields() {
109111
return fields_;
110112
}
111113

112114
inline int Environment::AsyncHooks::fields_count() const {
113115
return kFieldsCount;
114116
}
115117

116-
inline double* Environment::AsyncHooks::uid_fields() {
118+
inline AliasedBuffer<double, v8::Float64Array>&
119+
Environment::AsyncHooks::uid_fields() {
117120
return uid_fields_;
118121
}
119122

@@ -147,7 +150,7 @@ inline bool Environment::AsyncHooks::pop_ids(double async_id) {
147150
fprintf(stderr,
148151
"Error: async hook stack has become corrupted ("
149152
"actual: %.f, expected: %.f)\n",
150-
uid_fields_[kCurrentAsyncId],
153+
uid_fields_.GetValue(kCurrentAsyncId),
151154
async_id);
152155
Environment* env = Environment::GetCurrent(isolate_);
153156
DumpBacktrace(stderr);
@@ -326,7 +329,7 @@ inline Environment::~Environment() {
326329
delete[] heap_statistics_buffer_;
327330
delete[] heap_space_statistics_buffer_;
328331
delete[] http_parser_buffer_;
329-
free(http2_state_buffer_);
332+
delete http2_state_;
330333
free(performance_state_);
331334
}
332335

@@ -425,7 +428,9 @@ inline std::vector<double>* Environment::destroy_ids_list() {
425428
}
426429

427430
inline double Environment::new_async_id() {
428-
return ++async_hooks()->uid_fields()[AsyncHooks::kAsyncUidCntr];
431+
async_hooks()->uid_fields()[AsyncHooks::kAsyncUidCntr] =
432+
async_hooks()->uid_fields()[AsyncHooks::kAsyncUidCntr] + 1;
433+
return async_hooks()->uid_fields()[AsyncHooks::kAsyncUidCntr];
429434
}
430435

431436
inline double Environment::current_async_id() {
@@ -437,7 +442,8 @@ inline double Environment::trigger_id() {
437442
}
438443

439444
inline double Environment::get_init_trigger_id() {
440-
double* uid_fields = async_hooks()->uid_fields();
445+
AliasedBuffer<double, v8::Float64Array>& uid_fields =
446+
async_hooks()->uid_fields();
441447
double tid = uid_fields[AsyncHooks::kInitTriggerId];
442448
uid_fields[AsyncHooks::kInitTriggerId] = 0;
443449
if (tid <= 0) tid = current_async_id();
@@ -477,13 +483,13 @@ inline void Environment::set_http_parser_buffer(char* buffer) {
477483
http_parser_buffer_ = buffer;
478484
}
479485

480-
inline http2::http2_state* Environment::http2_state_buffer() const {
481-
return http2_state_buffer_;
486+
inline http2::http2_state* Environment::http2_state() const {
487+
return http2_state_;
482488
}
483489

484-
inline void Environment::set_http2_state_buffer(http2::http2_state* buffer) {
485-
CHECK_EQ(http2_state_buffer_, nullptr); // Should be set only once.
486-
http2_state_buffer_ = buffer;
490+
inline void Environment::set_http2_state(http2::http2_state* buffer) {
491+
CHECK_EQ(http2_state_, nullptr); // Should be set only once.
492+
http2_state_ = buffer;
487493
}
488494

489495
inline double* Environment::fs_stats_field_array() const {

0 commit comments

Comments
 (0)