v8

What is the relationship between `v8::Isolate` class and `v8::internal::Isolate` in V8


I am studying the source code of V8 recently. There are 2 definitions of Isolate class, one in v8::Isolate and the other in v8::internal::Isolate. It seems that v8::Isolate uses v8::internal::Isolate, but I could not understand the relationship between these 2 definitions/

I have tried to look into the class defitions of both, v8::Isolate in https://github.com/v8/v8/blob/master/include/v8.h#L7391 and v8::internal::Isolate in https://github.com/v8/v8/blob/master/src/execution/isolate.h#L452

but could not figure it out.

To be more specific, in v8::Isolate::New(https://github.com/v8/v8/blob/master/src/api/api.cc#L7903), it returns a C++ object of type v8::Isolate.

Isolate* Isolate::New(const Isolate::CreateParams& params) {
  Isolate* isolate = Allocate();
  Initialize(isolate, params);
  return isolate;
}

But internally in Allocate function, it returns an object of type v8::internal::Isolate, and reinterpret_casted to v8::Isolate

Isolate* Isolate::Allocate() {
    return reinterpret_cast<Isolate*>(i::Isolate::New());
}

How can object of class v8::Isolate be casted from v8::internal::Isolate?

Can anyone familiar with V8 give me some guidance?


Solution

  • It's a technique not unusual for libraries: v8::internal::Isolate is the implementation, but its details are entirely encapsulated within the library, and hidden from the public API. v8::Isolate is just an opaque reference. Observe how it has no fields; embedders know nothing about what it looks like in memory (or whether it has a memory representation at all -- as far as they are concerned, it could be something like a kernel's file descriptor). The reason for this encapsulation, of course, is to separate concerns, and to make components independent from each other: the library can change the internal definition of the class without embedders having to care (i.e. they can't possibly depend on the internal state, so it's guaranteed they won't be broken by changes; they don't even have to get recompiled because the public API [and ABI] doesn't change when the internal class layout changes).

    Consider this reduced example demonstrating the principle:

    /* public API */
    
    class BlackBox {
    public:
      static BlackBox* Create();
      void DoSomething();
    }
    
    void StandaloneFunction(BlackBox* box);
    
    
    /* internal implementation */
    
    class TheRealThing {
     public:
      TheRealThing(int value) : value_(value) {}
    
     private:
      int value_;
    }
    
    BlackBox* BlackBox::Create() {
      TheRealThing* real = new TheRealThing(42);
      return reinterpret_cast<BlackBox*>(real);
    }
    
    void BlackBox::DoSomething() {
      TheRealThing* real = reinterpret_cast<TheRealThing*>(this);
      printf("Hello %d\n", real->value_);
    }
    
    void StandaloneFunction(BlackBox* box) {
      TheRealThing* real = reinterpret_cast<TheRealThing*>(box);
      printf("Good bye %d\n", real->value_);
    }