0x00 前言
js代碼解析的過程爲編譯成字節碼後再加載字節碼執行, ScriptCompiler::Compile()
的過程是分爲詞法分析與語法分析,將js代碼解析成AST樹後就可以很順利的轉換成字節碼。
本節先跳過複雜的編譯過程看下執行邏輯。
0x01 調用棧
Thread 1 "d8" hit Breakpoint 1, v8::Shell::ExecuteString (isolate=0x59000000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult,
report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527
527 maybe_result = script->Run(realm);
(gdb) s
v8::Local<v8::Script>::operator-> (this=0x7fffffffd0a0) at ../../include/v8.h:213
213 V8_INLINE T* operator->() const { return val_; }
(gdb) s
v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2143
2143 auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
(gdb) bt
#0 v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2143
#1 0x00005555555efd77 in v8::Shell::ExecuteString (isolate=0x59000000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult,
report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527
#2 0x00005555555fd57e in v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x59000000000) at ../../src/d8/d8.cc:2620
#3 0x000055555560008b in v8::Shell::RunMain (isolate=0x59000000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100
#4 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741
#5 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
0x02 Script::Run函數
MaybeLocal<Value> Script::Run(Local<Context> context) {
auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
//如果開啓--trace-events-enabled選項的話,則初始化TraceEvent相關的對象,進行堆棧,性能相關
//指標的跟蹤,當然這裏沒有開啓,可以忽略
TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute");
//日誌記錄V8開始執行,聲明布爾型變量has_pending_exception用於保存執行返回結果
ENTER_V8(isolate, context, Script, Run, MaybeLocal<Value>(),
InternalEscapableScope);
//初始化直方圖計時器
i::HistogramTimerScope execute_timer(isolate->counters()->execute(), true);
//初始化聚合直方圖計時器
i::AggregatingHistogramTimerScope timer(isolate->counters()->compile_lazy());
//初始化計時器事件
i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate);
//初始化一個js函數句柄fun
auto fun = i::Handle<i::JSFunction>::cast(Utils::OpenHandle(this));
//初始化receiver
i::Handle<i::Object> receiver = isolate->global_proxy();
//初始化一個js變量用於保存js的執行結果
Local<Value> result;
//執行js代碼
has_pending_exception = !ToLocal<Value>(
i::Execution::Call(isolate, fun, receiver, 0, nullptr), &result);
RETURN_ON_FAILED_EXECUTION(Value);
RETURN_ESCAPED(result);
}
TRACE_EVENT_CALL_STATS_SCOPED宏
事件跟蹤(trace event)是v8引擎的一個重要調試輔助的功能,事件有不同的分組(category),例如堆棧調用,函數執行時間等等。
瀏覽器可以圖形化這些事件日誌,方便分析性能瓶頸。借用V8官網的一張圖,同學們可以有個初步印象
回到源碼上來,看下這個宏展開
#define TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name) \
INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name)
繼續展開
#define INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name) \
INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \
v8::internal::tracing::CallStatsScopedTracer INTERNAL_TRACE_EVENT_UID( \
tracer); \
if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \
INTERNAL_TRACE_EVENT_UID(tracer) \
.Initialize(isolate, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \
name); \
}
第一句
INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group);
看下宏定義
#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \
static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \
const uint8_t* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \
INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \
category_group, INTERNAL_TRACE_EVENT_UID(atomic), \
INTERNAL_TRACE_EVENT_UID(category_group_enabled));
宏定義太多了,就不一一介紹了,這段翻譯成人話如下:
//初始化一個事件臨時變量
static v8::base::AtomicWord trace_event_unique_atomic2144 = 0;
//聲明事件指針,爲啥沒有賦值?
const uint8_t* trace_event_unique_category_group_enabled2144;
//這裏賦了初值,通過原子操作,原子操作的主要主用是避免多線程中讀寫錯亂的問題
trace_event_unique_category_group_enabled2144 = reinterpret_cast<const uint8_t*>(
v8::base::Relaxed_Load(&(trace_event_unique_atomic2144)));
//判斷事件指針 != NULL則進到block中執行
if (!trace_event_unique_category_group_enabled2144) {
//獲取TraceEventHelper的函數句柄,給事件指針
trace_event_unique_category_group_enabled2144 =
v8::internal::tracing::TraceEventHelper::GetTracingController(
)->GetCategoryGroupEnabled(trace_event_unique_category_group_enabled2144);
v8::base::Relaxed_Store(
&(trace_event_unique_atomic2144),
(reinterpret_cast<v8::base::AtomicWord>(
trace_event_unique_category_group_enabled2144)));
}
第二句
v8::internal::tracing::CallStatsScopedTracer INTERNAL_TRACE_EVENT_UID( \
tracer); \
展開後
v8::internal::tracing::CallStatsScopedTracer trace_event_unique_tracer2144;
聲明瞭一個CallStatsScopedTracer類型的scope狀態跟蹤器。
第三句
if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \
INTERNAL_TRACE_EVENT_UID(tracer) \
.Initialize(isolate, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \
name); \
}
展開後
if( v8::base::Relaxed_Load(reinterpret_cast<const v8::base::Atomic8*>(
trace_event_unique_category_group_enabled2144))& (1|4) ){
trace_event_unique_tracer2144.Initialize(isolate, trace_event_unique_category_group_enabled2144, name)
}
分解如下:
INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED
中有一個if判斷語句if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE())
比較關鍵。
看下if判斷的內容
#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE() \
TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED() & \
(kEnabledForRecording_CategoryGroupEnabledFlags | \
kEnabledForEventCallback_CategoryGroupEnabledFlags)
其中:
kEnabledForRecording_CategoryGroupEnabledFlags = 1
kEnabledForEventCallback_CategoryGroupEnabledFlags = 4
繼續展開前面的宏TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED()
#define TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED() \
v8::base::Relaxed_Load(reinterpret_cast<const v8::base::Atomic8*>( \
INTERNAL_TRACE_EVENT_UID(category_group_enabled)))
INTERNAL_TRACE_EVENT_UID
宏的用處是:創建臨時變量降低指令開銷。這個變量名是由name_prefix和入口代碼行號拼接組成的唯一名稱,可以有效的避免衝突。
P.S. 說是降低指令開銷,但完全想不出來降低什麼了?你人工命名也不會多一條指令,最大的好處就是不用費腦想變量名了而已。
看看這組宏:
#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_unique_##a##b
#define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b)
#define INTERNAL_TRACE_EVENT_UID(name_prefix) \
INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__)
拼接後的函數調用棧如下:
(gdb) bt
#0 v8::base::Relaxed_Load (ptr=0x7ffff7fb3b08 <v8::Script::Run(v8::Local<v8::Context>)::trace_event_unique_atomic2144>)
at ../../src/base/atomicops_internals_portable.h:199
#1 0x00007ffff66aafab in v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2144
#2 0x00005555555efd77 in v8::Shell::ExecuteString (isolate=0x167c00000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult,
report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527
#3 0x00005555555fd57e in v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x167c00000000) at ../../src/d8/d8.cc:2620
#4 0x000055555560008b in v8::Shell::RunMain (isolate=0x167c00000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100
#5 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741
#6 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
trace_event_unique_atomic2144
確實是由固定字符串trace_event_unique_
+name_prefix字符串atomic
和frame 1中的行號2144
組成。
TRACE EVENT宏小結
主要用於事件追蹤的註冊,增加了多線程安全的原子操作保護。不是LZ關心的主要問題,不關心的同學也可以忽略掉。
0x03 小結
- 執行js前增加trace event和計時器幫助性能優化
- 用了很多宏