Chrome V8을 이해해 봅시다 — 10장: Ignition Execution Unit

65890 단어 v8cppjavascriptchrome
원본 소스: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-10-ignition-execution-unit-66073e14f6aa
Let’s Understand Chrome V8의 다른 장에 오신 것을 환영합니다.

실행은 바이트코드 실행을 담당합니다. 이 백서에서는 S에서 E로의 워크플로우와 여러 커널 함수에 대해 설명합니다.

테스트 사례:

function JsPrint(a){
    if(a >5){
        return "Debug";
    }
}
console.log(JsPrint(6));


워크플로우



GetSharedFunctionInfoForScript()는 JS 소스 코드를 바이트코드로 컴파일합니다.

0.  MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
1.      Isolate* isolate, Handle<String> source,
2.      const Compiler::ScriptDetails& script_details,
3.      ScriptOriginOptions origin_options, v8::Extension* extension,
4.      ScriptData* cached_data, ScriptCompiler::CompileOptions compile_options,
5.      ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives) {
6.  //.omit.............
7.    // Do a lookup in the compilation cache but not for extensions.
8.    MaybeHandle<SharedFunctionInfo> maybe_result;
9.    IsCompiledScope is_compiled_scope;
10.    if (extension == nullptr) {
11.      bool can_consume_code_cache =
12.          compile_options == ScriptCompiler::kConsumeCodeCache;
13.      if (can_consume_code_cache) {
14.        compile_timer.set_consuming_code_cache();
15.      }
16.      // First check per-isolate compilation cache.
17.      maybe_result = compilation_cache->LookupScript(
18.          source, script_details.name_obj, script_details.line_offset,
19.          script_details.column_offset, origin_options, isolate->native_context(),
20.          language_mode);
21.      if (!maybe_result.is_null()) {
22.        compile_timer.set_hit_isolate_cache();
23.      } 
24.    }
25.  //.....omit..............
26.    if (maybe_result.is_null()) {
27.      ParseInfo parse_info(isolate);
28.      // No cache entry found compile the script.
29.      NewScript(isolate, &parse_info, source, script_details, origin_options,
30.                natives);
31.      // Compile the function and add it to the isolate cache.
32.      if (origin_options.IsModule()) parse_info.set_module();
33.      parse_info.set_extension(extension);
34.      parse_info.set_eager(compile_options == ScriptCompiler::kEagerCompile);
35.      parse_info.set_language_mode(
36.          stricter_language_mode(parse_info.language_mode(), language_mode));
37.      maybe_result = CompileToplevel(&parse_info, isolate, &is_compiled_scope);
38.      Handle<SharedFunctionInfo> result;
39.      if (extension == nullptr && maybe_result.ToHandle(&result)) {
40.        DCHECK(is_compiled_scope.is_compiled());
41.        compilation_cache->PutScript(source, isolate->native_context(),
42.                                     language_mode, result);
43.      } else if (maybe_result.is_null() && natives != EXTENSION_CODE) {
44.        isolate->ReportPendingMessages();
45.      }
46.    }
47.    return maybe_result;
48.  }


코드의 17번째 줄 lookup compilation_cache에서 캐시 적중은 이미 바이트 코드가 있고 다시 컴파일할 필요가 없음을 나타냅니다. 코드의 26번째 줄은 캐시 미스를 나타내므로 37번째 줄에서 CompileToplevel을 호출하여 바이트 코드를 생성합니다.

바이트코드는 아래와 같이 실행 전에 컨텍스트에 바인딩되어야 합니다.

0.  Local<Script> UnboundScript::BindToCurrentContext() {
1.    auto function_info =
2.        i::Handle<i::SharedFunctionInfo>::cast(Utils::OpenHandle(this));
3.    i::Isolate* isolate = function_info->GetIsolate();
4.    i::Handle<i::JSFunction> function =
5.        isolate->factory()->NewFunctionFromSharedFunctionInfo(
6.            function_info, isolate->native_context());
7.    return ToApiHandle<Script>(function);
8.  }


바인딩 후에는 실행에서 예상되는 입력인 JSFunction이라는 중요한 멤버를 얻습니다.

0.  bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
1.                            Local<Value> name, PrintResult print_result,
2.                            ReportExceptions report_exceptions,
3.                            ProcessMessageQueue process_message_queue) {
4.    bool success = true;
5.    {
6.      PerIsolateData* data = PerIsolateData::Get(isolate);
7.      Local<Context> realm =
8.          Local<Context>::New(isolate, data->realms_[data->realm_current_]);
9.      Context::Scope context_scope(realm);
10.      MaybeLocal<Script> maybe_script;
11.      Local<Context> context(isolate->GetCurrentContext());
12.      ScriptOrigin origin(name);
13.      if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
14.  //.......omit
15.      } else if (options.stress_background_compile) {
16.  //.......omit
17.      } else {
18.        ScriptCompiler::Source script_source(source, origin);
19.        maybe_script = ScriptCompiler::Compile(context, &script_source,
20.                                               options.compile_options);
21.      }
22.      maybe_result = script->Run(realm);
23.  //......omit
24.      if (options.code_cache_options ==
25.          ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
26.        // Serialize and store it in memory for the next execution.
27.        ScriptCompiler::CachedData* cached_data =
28.            ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
29.        StoreInCodeCache(isolate, source, cached_data);
30.        delete cached_data;
31.      }
32.      if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
33.      data->realm_current_ = data->realm_switch_;
34.    }
35.    DCHECK(!try_catch.HasCaught());
36.    return success;
37.  }


위 함수는 JS를 실행하기 위한 입구입니다. 19행은 JS를 바이트코드로 컴파일합니다. 22행은 바이트코드를 실행합니다. 디버그 라인 22는 Run(영역)으로 들어갑니다.

0.  MaybeLocal<Value> Script::Run(Local<Context> context) {
1.    auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
2.    TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute");
3.    ENTER_V8(isolate, context, Script, Run, MaybeLocal<Value>(),
4.             InternalEscapableScope);
5.    i::HistogramTimerScope execute_timer(isolate->counters()->execute(), true);
6.    i::AggregatingHistogramTimerScope timer(isolate->counters()->compile_lazy());
7.    i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate);
8.    auto fun = i::Handle<i::JSFunction>::cast(Utils::OpenHandle(this));
9.    i::Handle<i::Object> receiver = isolate->global_proxy();
10.    Local<Value> result;
11.    has_pending_exception = !ToLocal<Value>(
12.        i::Execution::Call(isolate, fun, receiver, 0, nullptr), &result);
13.    RETURN_ON_FAILED_EXECUTION(Value);
14.    RETURN_ESCAPED(result);
15.  }


여덟 번째 줄은 이 포인터를 JSFunction으로 캐스팅합니다. 우리의 경우 변수 fun은 console.log(JsPrint(6))이고 12번째 줄인 call()을 호출합니다.

call()에서 아래 Invoke()가 호출됩니다.

0.  V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
1.                                                   const InvokeParams& params) {
2.  //...............omit
3.    Object value;
4.    Handle<Code> code =
5.        JSEntry(isolate, params.execution_target, params.is_construct);
6.    {
7.      SaveContext save(isolate);
8.      SealHandleScope shs(isolate);
9.      if (FLAG_clear_exceptions_on_js_entry) isolate->clear_pending_exception();
10.      if (params.execution_target == Execution::Target::kCallable) {
11.        // clang-format off
12.        // {new_target}, {target}, {receiver}, return value: tagged pointers
13.        // {argv}: pointer to array of tagged pointers
14.        using JSEntryFunction = GeneratedCode<Address(
15.            Address root_register_value, Address new_target, Address target,
16.            Address receiver, intptr_t argc, Address** argv)>;
17.        // clang-format on
18.        JSEntryFunction stub_entry =
19.            JSEntryFunction::FromAddress(isolate, code->InstructionStart());
20.        Address orig_func = params.new_target->ptr();
21.        Address func = params.target->ptr();
22.        Address recv = params.receiver->ptr();
23.        Address** argv = reinterpret_cast<Address**>(params.argv);
24.        RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
25.        value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
26.                                       orig_func, func, recv, params.argc, argv));
27.      } else {
28.  //..................omit     
29.      }
30.    }


18행은 내장 주소를 가져오고 색인은 40이며 이름은 JSEntry입니다. 이 빌트인은 바이트코드를 위한 실행 환경을 구축합니다. 그런 다음 바이트 코드가 실행되는 라인 25로 이동합니다. 그 후에는 어셈블리 코드만 디버깅할 수 있지만 C++는 디버깅할 수 없습니다. 그림 1은 호출 스택입니다.


바이트 코드를 단계별로 실행하기 전에 Generate_JSEntryVariant가 호출됩니다.

0.  void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
1.                               Builtins::Name entry_trampoline) {
2.    Label invoke, handler_entry, exit;
3.    Label not_outermost_js, not_outermost_js_2;
4.    {  // NOLINT. Scope block confuses linter.
5.      NoRootArrayScope uninitialized_root_register(masm);
6.      // Set up frame.
7.      __ pushq(rbp);
8.      __ movq(rbp, rsp);
9.      // Push the stack frame type.
10.      __ Push(Immediate(StackFrame::TypeToMarker(type)));
11.      // Reserve a slot for the context. It is filled after the root register has
12.      // been set up.
13.      __ AllocateStackSpace(kSystemPointerSize);
14.      // Save callee-saved registers (X64/X32/Win64 calling conventions).
15.      __ pushq(r12);
16.      __ pushq(r13);
17.      __ pushq(r14);
18.      __ pushq(r15);
19.  #ifdef _WIN64
20.      __ pushq(rdi);  // Only callee save in Win64 ABI, argument in AMD64 ABI.
21.      __ pushq(rsi);  // Only callee save in Win64 ABI, argument in AMD64 ABI.
22.  #endif
23.      __ pushq(rbx);
24.  //.....................omit............................
25.    __ ret(0);
26.  }
27.  }  // namespace
28.  //==================separation===============================
29.  void Builtins::Generate_JSEntry(MacroAssembler* masm) {
30.    Generate_JSEntryVariant(masm, StackFrame::ENTRY,
31.                            Builtins::kJSEntryTrampoline);
32.  }


코드의 30번째 줄을 보면 40번째 builtin의 생성기 함수는 B이고 그 분석 방법은 필자의 이전 논문을 참고한다. 그림 2는 해당 어셈블리 코드를 보여줍니다.


그림 2에서도 약간의 C++를 볼 수 있지만 C++를 볼 수 있는 마지막 기회입니다. 그 다음에는 어셈블리 코드만.

우리의 경우는 전역 함수인 console.log를 사용합니다. 아래에 있습니다.

1.  #define RUNTIME_FUNCTION_RETURNS_TYPE(Type, InternalType, Convert, Name)      \
2.    static V8_INLINE InternalType __RT_impl_##Name(Arguments args,              \
3.                                                   Isolate* isolate);           \
4.                                                                                \
5.    V8_NOINLINE static Type Stats_##Name(int args_length, Address* args_object, \
6.                                         Isolate* isolate) {                    \
7.      RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::k##Name);      \
8.      TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.runtime"),                     \
9.                   "V8.Runtime_" #Name);                                        \
10.     Arguments args(args_length, args_object);                                 \
11.      return Convert(__RT_impl_##Name(args, isolate));                          \
12.    }                                                                           \
13.                                                                                \
14.    Type Name(int args_length, Address* args_object, Isolate* isolate) {        \
15.      DCHECK(isolate->context().is_null() || isolate->context().IsContext());   \
16.      CLOBBER_DOUBLE_REGISTERS();                                               \
17.      if (V8_UNLIKELY(TracingFlags::is_runtime_stats_enabled())) {              \
18.        return Stats_##Name(args_length, args_object, isolate);                 \
19.      }                                                                         \
20.      Arguments args(args_length, args_object);                                 \
21.      return Convert(__RT_impl_##Name(args, isolate));                          \
22.    }                                                                           \
23.                                                                                \
24.    static InternalType __RT_impl_##Name(Arguments args, Isolate* isolate)
25.  #define CONVERT_OBJECT(x) (x).ptr()
26.  #define CONVERT_OBJECTPAIR(x) (x)
27.  #define RUNTIME_FUNCTION(Name) \
28.    RUNTIME_FUNCTION_RETURNS_TYPE(Address, Object, CONVERT_OBJECT, Name)
29.  //=================separation==============================
30.  //==================separation===============================
31.  Object DeclareGlobals(Isolate* isolate, Handle<FixedArray> declarations,
32.                        int flags, Handle<JSFunction> closure) {
33.    HandleScope scope(isolate);
34.    Handle<JSGlobalObject> global(isolate->global_object());
35.    Handle<Context> context(isolate->context(), isolate);
36.    Handle<FeedbackVector> feedback_vector = Handle<FeedbackVector>::null();
37.    Handle<ClosureFeedbackCellArray> closure_feedback_cell_array =
38.        Handle<ClosureFeedbackCellArray>::null();
39.    if (closure->has_feedback_vector()) {
40.      feedback_vector =
41.          Handle<FeedbackVector>(closure->feedback_vector(), isolate);
42.      closure_feedback_cell_array = Handle<ClosureFeedbackCellArray>(
43.          feedback_vector->closure_feedback_cell_array(), isolate);
44.    } else {
45.      closure_feedback_cell_array = Handle<ClosureFeedbackCellArray>(
46.          closure->closure_feedback_cell_array(), isolate);
47.    }
48.    // Traverse the name/value pairs and set the properties.
49.    int length = declarations->length();
50.    FOR_WITH_HANDLE_SCOPE(isolate, int, i = 0, i, i < length, i += 4, {
51.      Handle<String> name(String::cast(declarations->get(i)), isolate);
52.      FeedbackSlot slot(Smi::ToInt(declarations->get(i + 1)));
53.      Handle<Object> possibly_feedback_cell_slot(declarations->get(i + 2),
54.                                                 isolate);
55.      Handle<Object> initial_value(declarations->get(i + 3), isolate);
56.      bool is_var = initial_value->IsUndefined(isolate);
57.      bool is_function = initial_value->IsSharedFunctionInfo();
58.      DCHECK_NE(is_var, is_function);
59.      Handle<Object> value;
60.      if (is_function) {
61.        DCHECK(possibly_feedback_cell_slot->IsSmi());
62.        Handle<FeedbackCell> feedback_cell =
63.            closure_feedback_cell_array->GetFeedbackCell(
64.                Smi::ToInt(*possibly_feedback_cell_slot));
65.        // Copy the function and update its context. Use it as value.
66.        Handle<SharedFunctionInfo> shared =
67.            Handle<SharedFunctionInfo>::cast(initial_value);
68.        Handle<JSFunction> function =
69.            isolate->factory()->NewFunctionFromSharedFunctionInfo(
70.                shared, context, feedback_cell, AllocationType::kOld);
71.        value = function;
72.      } else {
73.        value = isolate->factory()->undefined_value();
74.      }
75.      // Compute the property attributes. According to ECMA-262,
76.      // the property must be non-configurable except in eval.
77.      bool is_eval = DeclareGlobalsEvalFlag::decode(flags);
78.      int attr = NONE;
79.      if (!is_eval) attr |= DONT_DELETE;
80.      // ES#sec-globaldeclarationinstantiation 5.d:
81.      // If hasRestrictedGlobal is true, throw a SyntaxError exception.
82.      Object result = DeclareGlobal(isolate, global, name, value,
83.                                    static_cast<PropertyAttributes>(attr), is_var,
84.                                    is_function, RedeclarationType::kSyntaxError,
85.                                    feedback_vector, slot);
86.      if (isolate->has_pending_exception()) return result;
87.    });
88.    return ReadOnlyRoots(isolate).undefined_value();
89.  }
90.  //==================separation===============================
91.  //==================separation===============================
92.  RUNTIME_FUNCTION(Runtime_DeclareGlobals) {
93.    HandleScope scope(isolate);
94.    DCHECK_EQ(3, args.length());
95.    CONVERT_ARG_HANDLE_CHECKED(FixedArray, declarations, 0);
96.    CONVERT_SMI_ARG_CHECKED(flags, 1);
97.    CONVERT_ARG_HANDLE_CHECKED(JSFunction, closure, 2);
98.    return DeclareGlobals(isolate, declarations, flags, closure);
99.  }



RUNTIME_FUNCTION(Runtime_DeclareGlobals)은 매크로 템플릿에 의해 정의된 런타임 함수입니다. 이 함수는 매개변수를 확인한 다음 DeclareGlobals()를 호출하여 해당 작업을 수행합니다.

우리의 경우 DeclareGlobals()는 전역 개체 콘솔을 조회한 다음 콘솔에서 log()를 꺼내고 console.log()의 주소를 가져옵니다. 마지막으로 매개변수 JsPrint(6)를 log()에 전달하면 새로운 컴파일과 실행이 시작됩니다.


자, 이것으로 이번 공유를 마치겠습니다. 다음에 뵙겠습니다 여러분 건강하세요!

문제가 있으면 저에게 연락해 주세요. 위챗: qq9123013 이메일: [email protected]

좋은 웹페이지 즐겨찾기