通過Handle理解V8的代碼設計(基于V0.1.5)
本文轉(zhuǎn)載自微信公眾號「編程雜技」,作者theanarkh。轉(zhuǎn)載本文請聯(lián)系編程雜技公眾號。
前言:Handle在V8里是一個非常重要的概念,本文從早期的源碼分析Handle的原理,在分析的過程中我們還可以看到V8在代碼設計上的一些細節(jié)。
假設我們有以下代碼
- HandleScope scope;
- Local<String> hello = String::New(參數(shù));
這個看起來很簡單的過程,其實在V8的內(nèi)部實現(xiàn)起來比較復雜。
HandleScope
我們從創(chuàng)建一個HandleScope對象開始分析。HandleScope是負責管理多個Handle的對象,主要是為了方便管理Handle的分配和釋放。
- class HandleScope {
- public:
- HandleScope() : previous_(current_), is_closed_(false) {
- current_.extensions = 0;
- }
- static void** CreateHandle(void* value);
- private:
- class Data {
- public:
- // 分配了一塊內(nèi)存后,又額外分配的塊數(shù)
- int extensions;
- // 下一個可用的位置
- void** next;
- // 達到limit執(zhí)行的地址后說明當前內(nèi)存塊用完了
- void** limit;
- inline void Initialize() {
- extensions = -1;
- next = limit = NULL;
- }
- };
- // 當前的HandleScope
- static Data current_;
- // 上一個HandleScope
- const Data previous_;
- };
- HandleScope::Data HandleScope::current_ = { -1, NULL, NULL };
通過HandleScope的構造函數(shù)我們知道每次定義一個HandleScope對象的時候,previous就會指向前一個HandleScope的數(shù)據(jù)(但是current_除了第一次創(chuàng)建HandleScope的時候更新了(見CreateHandle),后續(xù)似乎沒有更新?后續(xù)詳細看一下),從HandleScope的定義中我們知道他的布局如下。
接著我們看HandleScope的CreateHandle方法。
- void** v8::HandleScope::CreateHandle(void* value) {
- // 獲取下一個可用的地址
- void** result = current_.next;
- // 到達limit的地址了或者為空(初始化的時候)則獲取新的內(nèi)存
- if (result == current_.limit) {
- // Block是二維數(shù)組,每個元素指向一個可以存儲數(shù)據(jù)的數(shù)組。非空說明可能有可用的內(nèi)存空間
- if (!thread_local.Blocks()->is_empty()) {
- // 拿到list中最后一個元素,得到一個數(shù)組首地址,然后再獲取他的limit地址,即末地址
- void** limit = &thread_local.Blocks()->last()[i::kHandleBlockSize];
- if (current_.limit != limit) {
- current_.limit = limit;
- // v8里少了這一句,看起來是需要修改result的值的
- // result = limit - i::kHandleBlockSize;
- }
- }
- }
- // 下一個可用的地址
- current_.next = result + 1;
- *result = value;
- return result;
- }
我們看到CreateHandle會首先獲取一片內(nèi)存,然后把入?yún)alue的值保存到該內(nèi)存中。
String::New
了解了HandleScope后,我們繼續(xù)分析String::New。
- Local<String> v8::String::New(const char* data, int length) {
- i::Handle<i::String> result =
- i::Factory::NewStringFromUtf8(i::Vector<const char>(data, length));
- return Utils::ToLocal(result);
- }
我們接著看NewStringFromUtf8。
- Handle<String> Factory::NewStringFromUtf8(Vector<const char> string,PretenureFlag pretenure) {
- CALL_HEAP_FUNCTION(Heap::AllocateStringFromUtf8(string,
- pretenure),
- String);
- }
我們先看一下AllocateStringFromUtf8的實現(xiàn),然后再看CALL_HEAP_FUNCTION。
- Object* Heap::AllocateStringFromUtf8(Vector<const char> string,PretenureFlag pretenure) {
- return AllocateStringFromAscii(string, pretenure);
- }
- Object* Heap::AllocateStringFromAscii(Vector<const char> string,PretenureFlag pretenure) {
- // 從堆中分配一塊內(nèi)存
- Object* result = AllocateRawAsciiString(string.length(), pretenure);
- // 設置堆對象的內(nèi)容
- AsciiString* string_result = AsciiString::cast(result);
- for (int i = 0; i < string.length(); i++) {
- string_result->AsciiStringSet(i, string[i]);
- }
- return result;
- }
我們看到AllocateStringFromUtf8最后返回了一個堆內(nèi)存地址。接著我們看下CALL_HEAP_FUNCTION這個宏。
- #define CALL_HEAP_FUNCTION(FUNCTION_CALL, TYPE)
- do {
- Object* __object__ = FUNCTION_CALL;
- return Handle<TYPE>(TYPE::cast(__object__));
- } while (false)
CALL_HEAP_FUNCTION的作用是把函數(shù)FUNCTION_CALL執(zhí)行的結果轉(zhuǎn)成Handle對象。我們知道FUNCTION_CALL函數(shù)返回的結果是一個堆內(nèi)存指針。接下來我們看看是如何轉(zhuǎn)成Handle的。這個Handle不是我們在代碼里使用的Handle。而是V8內(nèi)部使用的Handle(代碼在handles.h),我們看看實現(xiàn)。
- template<class T>
- class Handle {
- public:
- explicit Handle(T* obj);
- private:
- T** location_;
- };
- template<class T>
- Handle<T>::Handle(T* obj) {
- location_ = reinterpret_cast<T**>(HandleScope::CreateHandle(obj));
- }
我們看到Handle內(nèi)部使用的是T**二級指針,而我們剛才拿到堆內(nèi)存地址是一級指針,自然不能直接賦值,而是通過CreateHandle又處理了一下。HandleScope::CreateHandle我們剛才已經(jīng)分析過了。執(zhí)行CreateHandle后布局如下。
所以NewStringFromUtf8最后返回了一個Handle對象(里面維護了一個二級指針location_),接著V8調(diào)用Utils::ToLocal把他轉(zhuǎn)成外部使用的Handle。接著賦值給Handle hello。這里的Handle是外部使用的Handle。
- Local<v8::String> Utils::ToLocal(v8::internal::Handle<v8::internal::String> obj) {
- return Local<String>(reinterpret_cast<String*>(obj.location()));
- }
首先通過obj.location()拿到一個二級指針。然后轉(zhuǎn)成一個String *指針。接著構造一個Local對象。ToLocal是V8代碼的分水嶺,我們看看Local的定義。
- template <class T> class Local : public Handle<T> {
- public:
- template <class S> inline Local(S* that) : Handle<T>(that) { }
- };
直接調(diào)用Handle類的函數(shù)
- template <class T> class Handle {
- explicit Handle(T* val) : val_(val) { }
- private:
- T* val_;
- }
這時候的結構圖如下
- template <class T> class Handle {
- explicit Handle(T* val) : val_(val) { }
- private:
- T* val_;
- }
所以最后通過ToLocal返回一個外部Handle對象給用戶。當執(zhí)行
- Local <String> xxx = Local對象
時就會調(diào)用Local的拷貝函數(shù)。
- template <class S>
- inline Local(Local<S> that)
- // *that即取得他底層對象的地址
- : Handle<T>(reinterpret_cast<T*>(*that)) {}
我們首先看一下that。Handle類重載了運算符。
- template <class T>
- T* Handle<T>::operator*() {
- return val_;
- }
所以reinterpret_cast(that)拿到了Handle底層指針的值并轉(zhuǎn)成String 類型。接著執(zhí)行
- explicit Handle(T* val) : val_(val) { }
整個過程下來,其實就是把被復制對象的底層指針復制過來。=
通過Handle訪問一個函數(shù)
當我們使用Handle hello這個對象的方法時是怎樣的,比如hello->Length()。Handle重載了->運算符。
- template <class T>
- T* Handle<T>::operator->() {
- return val_;
- }
我們看到執(zhí)行hello->Length()的時候首先會拿到一個String *。然后調(diào)用Length方法。其實就是調(diào)用String對象(在v8.h中定義)的Length方法。我們看看Length方法的實現(xiàn)。
- int String::Length() {
- return Utils::OpenHandle(this)->length();
- }
首先通過傳入this調(diào)用OpenHandle拿到內(nèi)部Handle。從前面的架構圖中我們知道this(即val_和location_指向的值)本質(zhì)上是一個String **,即二級指針。
- v8: :internal: :Handle < v8: :internal: :String >
- Utils: :OpenHandle(v8: :String * that) {
- return v8: :internal: :Handle < v8: :internal: :String > (reinterpret_cast < v8: :internal: :String * *>(that));
- }
OpenHandle就是首先把外部的表示轉(zhuǎn)成一個二級指針。然后再構造一個內(nèi)部Handle。在內(nèi)部Handle里保存了這個二級指針。接著訪問這個Handle對象的length方法。而Handle重載了->運算符。
- INLINE(T* operator ->() const) { return operator*(); }
- template <class T>inline T* Handle<T>::operator*() const {
- return *location_;
- }
我們看到->的操作最終會被解引用一次變成String *,然后訪問函數(shù)length,也就是訪問String對象的length函數(shù)。
后記:從上面的分析中我們不僅看到了Handle的實現(xiàn)原理,也看到了V8代碼的一些設計細節(jié),V8在內(nèi)部實現(xiàn)了一類對象,然后把內(nèi)部對象轉(zhuǎn)成外部使用的類型后返回給用戶,當用戶使用該返回的對象時,V8又會轉(zhuǎn)成內(nèi)部的對象再操作這個對象。核心的數(shù)據(jù)結構是兩個Handle族的類。因為他們是維護了真實對象的句柄。其他的一些類,比如String,同樣分為外部和內(nèi)部類,內(nèi)部類是實現(xiàn)了String的細節(jié),而外部類只是一個殼子,他負責給用戶暴露API,而不負責實現(xiàn)細節(jié),但用戶操作這些類時,V8會會轉(zhuǎn)成內(nèi)部類再進行操作。外部類的定義在v8.h中,這是我們使用V8時需要了解的最好文檔。內(nèi)部類的實現(xiàn)根據(jù)版本不同而不同,比如早期版本都是在object.h里實現(xiàn)的,而實現(xiàn)內(nèi)外部對象轉(zhuǎn)換的方法在api.c中定義。