I'm looking to writing some C bindings to V8, and so I'll need to figure out the memory layout of the various primitive JavaScript types. Is there any documentation on these details anywhere?
Memory spaces in V8 “Variables in JavaScript (and most other programming languages) are stored in two places: stack and heap. A stack is usually a continuous region of memory allocating local context for each executing function. Heap is a much larger region storing everything allocated dynamically.
The heap is a different space for storing data where JavaScript stores objects and functions. Unlike the stack, the engine doesn't allocate a fixed amount of memory for these objects. Instead, more space will be allocated as needed. Allocating memory this way is also called dynamic memory allocation.
JavaScript uses double-precision (64-bit) floating point numbers. 64 bits are 8 bytes, but each number actually takes up an average of 9.7 bytes. Likewise, Chrome shows the size of each individual empty array as 32 bytes and the size of each empty object as 56 bytes.
In V8, the garbage collector is named Orinoco. It divides the heap memory space into 2 regions: young generation and old generation. This design is based on a generational hypothesis: In most cases, young objects are much more likely to die than old objects. And the young/old generation take different strategies.
You don't need to know data types layout to write C bindings for V8. Object's are never really accessed directly when you work with V8 but through an API - only V8 implementation knows how they are laid out. For example getting a property foo
from an object o
looks like this in C++:
v8::Handle<v8::Object> o;
v8::Handle<v8::Object> v =
o->Get(v8::String::NewFromUtf8(isolate, "foo"));
Now to wrap this into C you only need to know how to represent and pass around v8::Handle<T>
, then you can write wrappers like:
template<typename T> Handle<T> FromC(v8_handle_t h) { /* ... */ }
template<typename T> v8_handle_t ToC(Handle<T> h) { /* ... */ }
extern "C" v8_handle_t v8_object_get(v8_handle_t self,
v8_handle_t key) {
return ToC(FromC<Object>(self)->Get(FromC<Value>(key)));
}
So what's inside v8::Handle<T>
? In reality it's just a pointer to some slot which in turn contains an actual pointer to an underlying V8 object. This double indirection exists to enable V8 GC to precisely track which objects are in use in C++ code and to allow it moving this objects.
So theoretically you can define v8_handle_t
as an opaque pointer and marshall it like this:
typedef struct v8_object_pointer_t* v8_handle_t; // Opaque pointer
static_assert(sizeof(v8_handle_t) == sizeof(Handle<Object>))
template<typename T> Handle<T> FromC(v8_handle_t h) {
return *(Handle<T>*)&h;
}
template<typename T> v8_handle_t ToC(const Handle<T>& h) {
return *(v8_handle_t*)&h;
}
A minor complication comes from managing the structure called HandleScope
that manages Handle
s. In C++ API it relies on RAII-pattern to manage some backing storage. The simplest way to deal with it probably is:
typedef struct {
void* a[3];
} v8_handle_scope_t;
static_assert(sizeof(v8_handle_scope_t) == sizeof(HandleScope))
void v8_handle_scope_enter(v8_handle_scope_t* scope) {
new(scope) HandleScope;
}
void v8_handle_scope_leave(v8_handle_scope_t* scope) {
delete (HandleScope*)scope;
}
With carefully balanced usage in code that needs handle scopes:
for (i = 0; i < N; i++) {
v8_handle_scope_t scope;
v8_handle_scope_enter(&scope);
// action
v8_handle_scope_leave(&scope);
}
As your question is about how to write V8 C++ addons for Node.js you should just refer to the manual for nodeJs which has a reasonably simple guide for writing addons;
If you question is about how to make background threads then there are nodejs plugins which helps you with that, but also read this for how to write it directly.
You should never try to access the memory for V8 directly, as the memory and object may move --always use the APIs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With