15

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?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Steve Klabnik
  • 14,521
  • 4
  • 58
  • 99
  • Are you asking about objects or primitive values now? – Bergi Jul 23 '15 at 23:44
  • Primitives + Object, technically speaking. – Steve Klabnik Jul 23 '15 at 23:46
  • 4
    Are you asking for a nodejs-c binding or for developing directly against v8? There is documentation for both both in different places -- and you DON'T want to access the memory directly in either case, but use the API – Soren Jul 23 '15 at 23:54
  • If a C binding exists, that would save me a lot of time, but I couldn't find one. I want to do things like "take a String from JavaScript and do work on it in C", and was assuming I would have to write a C++ V8 wrapper that exposes C functions, since V8 is in C++. – Steve Klabnik Jul 23 '15 at 23:56
  • (The eventual intention is to use this with Node) – Steve Klabnik Jul 23 '15 at 23:57
  • The Node.js manual have some information -- see this for additional information -- http://stackoverflow.com/questions/11051070/node-js-c-addon-threading/22946062 – Soren Jul 23 '15 at 23:59
  • @soren, that's all C++. I need C. – Steve Klabnik Jul 24 '15 at 00:00

2 Answers2

5

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 Handles. 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);
}
Vyacheslav Egorov
  • 10,302
  • 2
  • 43
  • 45
  • excellent, thank you a ton. For my use case, I'm mostly interested in just copying out the data immediately rather than worrying about keeping objects alive, specifically because I was worried about GC rooting and other things you mention. – Steve Klabnik Jul 24 '15 at 02:11
  • @SteveKlabnik well, V8 api is explicitly designed this way to prevent any worries about rooting - that's why all objects are always rooted (unless you violate HandleScope nesting and pass around invalid handles) – Vyacheslav Egorov Jul 24 '15 at 15:16
1

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

Community
  • 1
  • 1
Soren
  • 14,402
  • 4
  • 41
  • 67
  • My question is about how to write C extensions, not C++. – Steve Klabnik Jul 24 '15 at 00:04
  • 1
    You will have to wrap your C in C++ – Soren Jul 24 '15 at 00:05
  • 2
    Yes. In order to do that, I need to know things like the memory layout of the structs, hence the question. – Steve Klabnik Jul 24 '15 at 00:06
  • The only way you can do what you need to do is to write C++ code which moves data to/from your C code into C++ and then call the Nodejs/V8 API from C++ – Soren Jul 24 '15 at 00:08
  • 1
    Correct. Which is why I need to understand these details, so I know how to turn those C structures into the correct ones V8 uses. – Steve Klabnik Jul 24 '15 at 00:09
  • You need to read the C++ code I have referenced -- `Number::New(42)` is how you create a int value=42 in the interface -- you cannot access the memory directly – Soren Jul 24 '15 at 00:13
  • 1
    Keep in mind also that the V8 API changes fairly frequently. So even if you were somehow able to create C structs that mapped to the V8 internal goodies, they can change release-to-release. I guess [node-ffi](https://github.com/node-ffi/node-ffi) isn't going to help, but ... passing it along just in case. There's also [nan](https://github.com/nodejs/nan) - tries to abstract away the shifting V8 API for node developers. – Patrick Mueller Jul 24 '15 at 02:04
  • @PatrickMueller yeah, that was something I was thinking of too, such is the life of a bindings author. – Steve Klabnik Jul 24 '15 at 02:12