V8源碼分析之d8源碼註解(第六篇)

0x00 前言

d8自己封裝了一個js代碼執行器,上一篇我們的代碼執行到options.isolate_sources[0].Execute(isolate,本文將做進一步分析。

0x01 調用棧

#0  v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x1e9d00000000) at ../../src/d8/d8.cc:2567
#1  0x000055555560008b in v8::Shell::RunMain (isolate=0x1e9d00000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100
#2  0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741
#3  0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777

0x02 SourceGroup::Execute函數

bool SourceGroup::Execute(Isolate* isolate) {
  bool success = true;
  for (int i = begin_offset_; i < end_offset_; ++i) {
    const char* arg = argv_[i];
    //解析-e參數,d8需要執行一段js代碼字符串的話,走這個條件分支
    if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
      // Execute argument given to -e option directly.
      //創建作用域
      HandleScope handle_scope(isolate);
      //創建匿名文件名
      Local<String> file_name =
          String::NewFromUtf8(isolate, "unnamed", NewStringType::kNormal)
              .ToLocalChecked();
      //讀取js代碼
      Local<String> source =
          String::NewFromUtf8(isolate, argv_[i + 1], NewStringType::kNormal)
              .ToLocalChecked();
      Shell::set_script_executed();
      //執行js代碼字符串
      if (!Shell::ExecuteString(isolate, source, file_name,
                                Shell::kNoPrintResult, Shell::kReportExceptions,
                                Shell::kNoProcessMessageQueue)) {
        success = false;
        break;
      }
      ++i;
      continue;
      //判斷是否爲js的module文件,規則後綴名必須爲.mjs與--module參數連用
    } else if (ends_with(arg, ".mjs")) {
      Shell::set_script_executed();
      if (!Shell::ExecuteModule(isolate, arg)) {
        success = false;
        break;
      }
      continue;
      // 判斷是否爲module執行模式
    } else if (strcmp(arg, "--module") == 0 && i + 1 < end_offset_) {
      // Treat the next file as a module.
      arg = argv_[++i];
      Shell::set_script_executed();
      if (!Shell::ExecuteModule(isolate, arg)) {
        success = false;
        break;
      }
      continue;
    } else if (arg[0] == '-') {
      // Ignore other options. They have been parsed already.
      continue;
    }
	//LZ這裏的執行命令爲d8 test.js,所以前面的邏輯都會跳過,真正的入口位置在這裏
    // Use all other arguments as names of files to load and run.
    //定義作用域
    HandleScope handle_scope(isolate);
    //創建文件名字符串
    Local<String> file_name =
        String::NewFromUtf8(isolate, arg, NewStringType::kNormal)
            .ToLocalChecked();
    //從文件中讀取文件內容
    Local<String> source = ReadFile(isolate, arg);
    if (source.IsEmpty()) {
      printf("Error reading '%s'\n", arg);
      base::OS::ExitProcess(1);
    }
    //設置執行狀態爲true,該靜態函數在d8.h中定義
    Shell::set_script_executed();
    //執行js代碼
    if (!Shell::ExecuteString(isolate, source, file_name, Shell::kNoPrintResult,
                              Shell::kReportExceptions,
                              Shell::kProcessMessageQueue)) {
      success = false;
      break;
    }
  }
  return success;
}

0x03 Shell::ExecuteString函數

// Executes a string within the current v8 context.
bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
                          Local<Value> name, PrintResult print_result,
                          ReportExceptions report_exceptions,
                          ProcessMessageQueue process_message_queue) {
  //i::FLAG_parse_only 爲false
  if (i::FLAG_parse_only) {
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
    i::VMState<PARSER> state(i_isolate);
    i::Handle<i::String> str = Utils::OpenHandle(*(source));

    // Set up ParseInfo.
    i::ParseInfo parse_info(i_isolate);
    parse_info.set_toplevel();
    parse_info.set_allow_lazy_parsing();
    parse_info.set_language_mode(
        i::construct_language_mode(i::FLAG_use_strict));
    parse_info.set_script(
        parse_info.CreateScript(i_isolate, str, options.compile_options));

    if (!i::parsing::ParseProgram(&parse_info, i_isolate)) {
      fprintf(stderr, "Failed parsing\n");
      return false;
    }
    return true;
  }

  HandleScope handle_scope(isolate);
  TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  MaybeLocal<Value> maybe_result;
  bool success = true;
  {
    //獲取自定義數據,get後爲null。
    PerIsolateData* data = PerIsolateData::Get(isolate);
    //創建realm變量
    Local<Context> realm =
        Local<Context>::New(isolate, data->realms_[data->realm_current_]);
    Context::Scope context_scope(realm);
    MaybeLocal<Script> maybe_script;
    //創建當前上下文context
    Local<Context> context(isolate->GetCurrentContext());
    //創建ScriptOrigin對象origin
    ScriptOrigin origin(name);
     //v8有code caching功能,輸入的js代碼第一次被編譯後,會生成一份cache,再次運行這份js代碼時,會優先加載cache,減少重複編譯帶來的開銷。
	//編譯選項如果爲ScriptCompiler::kConsumeCodeCache,則尋找cache並加載
    if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
     //根據js代碼字符串搜索cache
      ScriptCompiler::CachedData* cached_code =
          LookupCodeCache(isolate, source);
      //如果cache不爲空
      if (cached_code != nullptr) {
        ScriptCompiler::Source script_source(source, origin, cached_code);
        maybe_script = ScriptCompiler::Compile(context, &script_source,
                                               options.compile_options);
        CHECK(!cached_code->rejected);
      } else {
        ScriptCompiler::Source script_source(source, origin);
        maybe_script = ScriptCompiler::Compile(
            context, &script_source, ScriptCompiler::kNoCompileOptions);
      }
      // options.stress_background_compile爲true,則後臺編譯
    } else if (options.stress_background_compile) {
    // 啓動一個後臺線程用於編譯js腳本,後臺編譯就是script streaming 的優化方式
    // 同code cache的地位一樣重要。瀏覽器加載多個js腳本時,邊下載邊編譯的過程稱爲script streaming
      // Start a background thread compiling the script.
      BackgroundCompileThread background_compile_thread(isolate, source);
      //檢查線程是否已經就緒
      CHECK(background_compile_thread.Start());

      // In parallel, compile on the main thread to flush out any data races.
      {
        TryCatch ignore_try_catch(isolate);
        ScriptCompiler::Source script_source(source, origin);
        USE(ScriptCompiler::Compile(context, &script_source,
                                    ScriptCompiler::kNoCompileOptions));
      }

      // Join with background thread and finalize compilation.
      background_compile_thread.Join();
      maybe_script = v8::ScriptCompiler::Compile(
          context, background_compile_thread.streamed_source(), source, origin);
    } else {
      //沒有任何優化的編譯方式
      ScriptCompiler::Source script_source(source, origin);
      maybe_script = ScriptCompiler::Compile(context, &script_source,
                                             options.compile_options);
    }
	//定義script變量
    Local<Script> script;
    if (!maybe_script.ToLocal(&script)) {
      //打印編譯過程中的所有報錯
      // Print errors that happened during compilation.
      if (report_exceptions) ReportException(isolate, &try_catch);
      return false;
    }
	//如果kProduceCache選項開啓,則將cache落地。
    if (options.code_cache_options ==
        ShellOptions::CodeCacheOptions::kProduceCache) {
      // Serialize and store it in memory for the next execution.
      ScriptCompiler::CachedData* cached_data =
          ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
      StoreInCodeCache(isolate, source, cached_data);
      delete cached_data;
    }
    //【重點】運行js腳本
    maybe_result = script->Run(realm);
    //處理選項,如果是kProduceCacheAfterExecute,則運行後在落地cache
    if (options.code_cache_options ==
        ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
      // Serialize and store it in memory for the next execution.
      ScriptCompiler::CachedData* cached_data =
          ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
      StoreInCodeCache(isolate, source, cached_data);
      delete cached_data;
    }
    if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
    data->realm_current_ = data->realm_switch_;
  }
  Local<Value> result;
  //讀取結果數據
  if (!maybe_result.ToLocal(&result)) {
    DCHECK(try_catch.HasCaught());
    // Print errors that happened during execution.
    if (report_exceptions) ReportException(isolate, &try_catch);
    return false;
  }
  DCHECK(!try_catch.HasCaught());
  if (print_result) {
    //如果配置了test_shell選項,則把結果重定向到標準輸出
    if (options.test_shell) {
      //如果結果格式未定義,需要Stringify和格式化
      if (!result->IsUndefined()) {
        // If all went well and the result wasn't undefined then print
        // the returned value.
        v8::String::Utf8Value str(isolate, result);
        fwrite(*str, sizeof(**str), str.length(), stdout);
        printf("\n");
      }
    } else {
      v8::String::Utf8Value str(isolate, Stringify(isolate, result));
      fwrite(*str, sizeof(**str), str.length(), stdout);
      printf("\n");
    }
  }
  return success;
}

0x04 小結

Shell::ExecuteString函數說明了v8運行的大致流程,如果寫demo可以參考這個函數。

發佈了94 篇原創文章 · 獲贊 93 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章