Chrome V8을 이해해 봅시다 — 12장: JSFunction이란 무엇입니까?

55087 단어 v8cppjavascriptchrome
원본 소스: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-12-what-is-jsfunction-2a03cd2291c6
Let’s Understand Chrome V8의 다른 장에 오신 것을 환영합니다.

바이트 코드를 저장하는 데이터 구조는 SharedFunction이고 컨텍스트에 바인딩된 SharedFunction은 JSFunction입니다. 차이점은 무엇입니까? 내 생각에 SharedFunction은 여러 프로그램에서 공유할 수 있는 공통 라이브러리인 DLL과 같습니다. JSFunction은 특정 실행 프로그램입니다. 이 백서에서는 JSFunction의 코드와 메모리 레이아웃에 대해 설명합니다.

1. JSFunction의 코드



다음은 JSFunction의 정의입니다.

1.  class JSFunction : public JSObject {
2.    public:
3.     DECL_ACCESSORS(prototype_or_initial_map, HeapObject)
4.     DECL_ACCESSORS(shared, SharedFunctionInfo)
5.     static const int kLengthDescriptorIndex = 0;
6.     static const int kNameDescriptorIndex = 1;
7.     static const int kMaybeHomeObjectDescriptorIndex = 2;
8.     inline Context context();
9.     inline bool has_context() const;
10.    inline void set_context(HeapObject context);
11.    inline JSGlobalProxy global_proxy();
12.    inline NativeContext native_context();
13.    inline int length();
14.    static Handle<Object> GetName(Isolate* isolate, Handle<JSFunction> function);
15.    static Handle<NativeContext> GetFunctionRealm(Handle<JSFunction> function);
16.    inline Code code() const;
17.    inline void set_code(Code code);
18.    inline void set_code_no_write_barrier(Code code);
19.    inline AbstractCode abstract_code();
20.    inline bool IsInterpreted();
21.    inline bool ChecksOptimizationMarker();
22.    inline bool IsOptimized();
23.    inline bool HasOptimizedCode();
24.    inline bool HasOptimizationMarker();
25.    void MarkForOptimization(ConcurrencyMode mode);
26.    inline bool IsMarkedForOptimization();
27.    inline bool IsMarkedForConcurrentOptimization();
28.    inline bool IsInOptimizationQueue();
29.    inline void ClearOptimizedCodeSlot(const char* reason);
30.    inline void SetOptimizationMarker(OptimizationMarker marker);
31.    inline void ClearOptimizationMarker();
32.    int ComputeInstanceSizeWithMinSlack(Isolate* isolate);
33.    inline void CompleteInobjectSlackTrackingIfActive();
34.    DECL_ACCESSORS(raw_feedback_cell, FeedbackCell)
35.    inline FeedbackVector feedback_vector() const;
36.    inline bool has_feedback_vector() const;
37.    V8_EXPORT_PRIVATE static void EnsureFeedbackVector(
38.        Handle<JSFunction> function);
39.    inline bool has_closure_feedback_cell_array() const;
40.    inline ClosureFeedbackCellArray closure_feedback_cell_array() const;
41.    static void EnsureClosureFeedbackCellArray(Handle<JSFunction> function);
42.    static void InitializeFeedbackCell(Handle<JSFunction> function);
43.    void ClearTypeFeedbackInfo();
44.    inline bool NeedsResetDueToFlushedBytecode();
45.    inline void ResetIfBytecodeFlushed();
46.    DECL_GETTER(has_prototype_slot, bool)
47.    DECL_GETTER(initial_map, Map)
48.    static void SetInitialMap(Handle<JSFunction> function, Handle<Map> map,
49.                              Handle<HeapObject> prototype);
50.    DECL_GETTER(has_initial_map, bool)
51.    V8_EXPORT_PRIVATE static void EnsureHasInitialMap(
52.        Handle<JSFunction> function);
53.    static V8_WARN_UNUSED_RESULT MaybeHandle<Map> GetDerivedMap(
54.        Isolate* isolate, Handle<JSFunction> constructor,
55.        Handle<JSReceiver> new_target);
56.    DECL_GETTER(has_prototype, bool)
57.    DECL_GETTER(has_instance_prototype, bool)
58.    DECL_GETTER(prototype, Object)
59.    DECL_GETTER(instance_prototype, HeapObject)
60.    DECL_GETTER(has_prototype_property, bool)
61.    DECL_GETTER(PrototypeRequiresRuntimeLookup, bool)
62.    static void SetPrototype(Handle<JSFunction> function, Handle<Object> value);
63.    inline bool is_compiled() const;
64.    static int GetHeaderSize(bool function_has_prototype_slot) {
65.      return function_has_prototype_slot ? JSFunction::kSizeWithPrototype
66.                                         : JSFunction::kSizeWithoutPrototype;
67.    }
68.    void PrintName(FILE* out = stdout);
69.    DECL_CAST(JSFunction)
70.    static V8_WARN_UNUSED_RESULT int CalculateExpectedNofProperties(
71.        Isolate* isolate, Handle<JSFunction> function);
72.    static void CalculateInstanceSizeHelper(InstanceType instance_type,
73.                                            bool has_prototype_slot,
74.                                            int requested_embedder_fields,
75.                                            int requested_in_object_properties,
76.                                            int* instance_size,
77.                                            int* in_object_properties);
78.    DECL_PRINTER(JSFunction)
79.    DECL_VERIFIER(JSFunction)
80.    static Handle<String> GetName(Handle<JSFunction> function);
81.    static V8_WARN_UNUSED_RESULT bool SetName(Handle<JSFunction> function,
82.                                              Handle<Name> name,
83.                                              Handle<String> prefix);
84.    static Handle<String> GetDebugName(Handle<JSFunction> function);
85.    static Handle<String> ToString(Handle<JSFunction> function);
86.  //
87.    DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
88.                                  TORQUE_GENERATED_JSFUNCTION_FIELDS)
89.    static constexpr int kSizeWithoutPrototype = kPrototypeOrInitialMapOffset;
90.    static constexpr int kSizeWithPrototype = kSize;
91.    OBJECT_CONSTRUCTORS(JSFunction, JSObject);
92.  };


세 개의 인덱스 멤버는 5행에서 7행까지 정의되며 정적 멤버이며 모든 SharedFunction 인스턴스에 적용됩니다. 10, 11, 12행은 컨텍스트와 전역 수신기를 바인딩합니다. 17행은 바이트코드 실행을 위한 진입점인 InterpreterEntryTrampoline을 바인딩합니다. 18~45행은 TurboFan에서 사용되는 최적화 및 역최적화 기능을 정의합니다. 새로운 JSFunction의 프로세스에 대해 이야기해 봅시다.

(1) SharedFunction 생성 및 바이트코드 설치

1.  void InstallBytecodeArray(Handle<BytecodeArray> bytecode_array,
2.                            Handle<SharedFunctionInfo> shared_info,
3.                            ParseInfo* parse_info, Isolate* isolate) {
4.    if (!FLAG_interpreted_frames_native_stack) {
5.      shared_info->set_bytecode_array(*bytecode_array);
6.      return;
7.    }
8.    Handle<Code> code = isolate->factory()->CopyCode(Handle<Code>::cast(
9.        isolate->factory()->interpreter_entry_trampoline_for_profiling()));
10.    Handle<InterpreterData> interpreter_data =
11.        Handle<InterpreterData>::cast(isolate->factory()->NewStruct(
12.            INTERPRETER_DATA_TYPE, AllocationType::kOld));
13.    interpreter_data->set_bytecode_array(*bytecode_array);
14.    interpreter_data->set_interpreter_trampoline(*code);
15.    shared_info->set_interpreter_data(*interpreter_data);
16.    Handle<Script> script = parse_info->script();
17.    Handle<AbstractCode> abstract_code = Handle<AbstractCode>::cast(code);
18.    int line_num =
19.        Script::GetLineNumber(script, shared_info->StartPosition()) + 1;
20.    int column_num =
21.        Script::GetColumnNumber(script, shared_info->StartPosition()) + 1;
22.    String script_name = script->name().IsString()
23.                             ? String::cast(script->name())
24.                             : ReadOnlyRoots(isolate).empty_string();
25.    CodeEventListener::LogEventsAndTags log_tag = Logger::ToNativeByScript(
26.        CodeEventListener::INTERPRETED_FUNCTION_TAG, *script);
27.    PROFILE(isolate, CodeCreateEvent(log_tag, *abstract_code, *shared_info,
28.                                     script_name, line_num, column_num));
29.  }


새 JSFunction 인스턴스를 만들 때 먼저 SharedFunction 인스턴스를 만들어야 합니다. SharedFunction 인스턴스를 생성할 때 중요한 단계는 바이트코드를 설치하는 것입니다. 위 코드의 13행을 참조하십시오.

(2) 새로운 JSFunction

아래 코드를 통해 JS를 생성하는 것이 실제로 SharedFunction과 컨텍스트를 바인딩하는 과정임을 알 수 있습니다.

1.  Local<Script> UnboundScript::BindToCurrentContext() {
2.    auto function_info =
3.        i::Handle<i::SharedFunctionInfo>::cast(Utils::OpenHandle(this));
4.    i::Isolate* isolate = function_info->GetIsolate();
5.    i::Handle<i::JSFunction> function =
6.        isolate->factory()->NewFunctionFromSharedFunctionInfo(
7.            function_info, isolate->native_context());
8.    return ToApiHandle<Script>(function);
9.  }
10.  //..................分隔线........................................
11.  Handle<JSFunction> Factory::NewFunctionFromSharedFunctionInfo(
12.      Handle<Map> initial_map, Handle<SharedFunctionInfo> info,
13.      Handle<Context> context, AllocationType allocation) {
14.    DCHECK_EQ(JS_FUNCTION_TYPE, initial_map->instance_type());
15.    Handle<JSFunction> result =
16.        NewFunction(initial_map, info, context, allocation);
17.    // Give compiler a chance to pre-initialize.
18.    Compiler::PostInstantiation(result);
19.    return result;
20.  }


위 코드의 2행은 이것을 SharedFunctionInfo(원래 SharedFunctionInfo임)로 캐스팅합니다. 5행, NewFunctionFromSharedFunctionInfo를 호출하여 JSFunction을 생성합니다. 참고: 매개변수는 S와 C입니다. 11행을 참조하세요. 마지막으로 아래에 있는 NewFunction을 호출합니다.

1.  Handle<JSFunction> Factory::NewFunction(Handle<Map> map,
2.                                          Handle<SharedFunctionInfo> info,
3.                                          Handle<Context> context,
4.                                          AllocationType allocation) {
5.    Handle<JSFunction> function(JSFunction::cast(New(map, allocation)),
6.                                isolate());
7.    function->initialize_properties(isolate());
8.    function->initialize_elements();
9.    function->set_shared(*info);
10.    function->set_code(info->GetCode());
11.    function->set_context(*context);
12.    function->set_raw_feedback_cell(*many_closures_cell());
13.    int header_size;
14.    if (map->has_prototype_slot()) {
15.      header_size = JSFunction::kSizeWithPrototype;
16.      function->set_prototype_or_initial_map(*the_hole_value());
17.    } else {
18.      header_size = JSFunction::kSizeWithoutPrototype;
19.    }
20.    InitializeJSObjectBody(function, map, header_size);
21.    return function;
22.  }


위 코드의 9번째 및 11번째 줄은 SharedFunctionInfo 및 컨텍스트를 해당 위치로 설정하며, 이는 위에서 언급한 바인드입니다.

10행의 GetCode는 무엇입니까? 아래를 참조하십시오.

1.  Code SharedFunctionInfo::GetCode() const {
2.    Isolate* isolate = GetIsolate();
3.    Object data = function_data();
4.    if (data.IsSmi()) {
5.      DCHECK(HasBuiltinId());
6.      return isolate->builtins()->builtin(builtin_id());
7.    } else if (data.IsBytecodeArray()) {
8.      DCHECK(HasBytecodeArray());
9.      return isolate->builtins()->builtin(Builtins::kInterpreterEntryTrampoline);
10.    } else if (data.IsAsmWasmData()) {
11.      DCHECK(HasAsmWasmData());
12.      return isolate->builtins()->builtin(Builtins::kInstantiateAsmJs);
13.    } else if (data.IsUncompiledData()) {
14.      DCHECK(HasUncompiledData());
15.      return isolate->builtins()->builtin(Builtins::kCompileLazy);
16.    } else if (data.IsFunctionTemplateInfo()) {
17.      DCHECK(IsApiFunction());
18.      return isolate->builtins()->builtin(Builtins::kHandleApiCall);
19.    } else if (data.IsWasmExportedFunctionData()) {
20.      DCHECK(HasWasmExportedFunctionData());
21.      return wasm_exported_function_data().wrapper_code();
22.    } else if (data.IsInterpreterData()) {
23.      Code code = InterpreterTrampoline();
24.      DCHECK(code.IsCode());
25.      DCHECK(code.is_interpreter_trampoline_builtin());
26.      return code;
27.    } else if (data.IsWasmJSFunctionData()) {
28.      return wasm_js_function_data().wrapper_code();
29.    } else if (data.IsWasmCapiFunctionData()) {
30.      return wasm_capi_function_data().wrapper_code();
31.    }
32.    UNREACHABLE();
33.  }


테스트 사례(Chapter 4 참조)는 바이트코드 배열이므로 GetCode의 반환 값은 kInterpreterEntryTrampoline입니다. GetCode의 역할은 SharedFunctionInfo의 유형에 따라 해당 바이트코드로 점프하여 실행하는 데 사용되는 진입 방법을 반환하는 것입니다(Chapter 8 참조).

요약하면 JS를 만들 때 바이트코드 유형, 컨텍스트 및 입력 방법은 우리가 중요하게 생각하고 나머지는 중요하지 않습니다. 그림 1은 호출 스택을 보여줍니다.



2. JSFunction의 메모리 레이아웃



JSFunction은 V8의 힙 관리 객체이며 메모리 오프셋을 사용하여 내부 멤버를 나타냅니다. 코드는 다음과 같습니다.

#define TORQUE_GENERATED_JSFUNCTION_FIELDS(V) \
V(kStartOfStrongFieldsOffset, 0) \
V(kSharedFunctionInfoOffset, kTaggedSize) \
V(kContextOffset, kTaggedSize) \
V(kFeedbackCellOffset, kTaggedSize) \
V(kEndOfStrongFieldsOffset, 0) \
V(kStartOfWeakFieldsOffset, 0) \
V(kCodeOffset, kTaggedSize) \
V(kPrototypeOrInitialMapOffset, kTaggedSize) \
V(kEndOfWeakFieldsOffset, 0) \
V(kSize, 0) \

#define DEFINE_ONE_FIELD_OFFSET(Name, Size) Name, Name##End = Name + (Size)-1,

#define DEFINE_FIELD_OFFSET_CONSTANTS(StartOffset, LIST_MACRO) \
  enum {                                                       \
    LIST_MACRO##_StartOffset = StartOffset - 1,                \
    LIST_MACRO(DEFINE_ONE_FIELD_OFFSET)                        \
  };


TORQUE_GENERATED_JSFUNCTION_FIELDS는 모든 멤버의 오프셋을 정의하고 StartOffset은 기본 주소입니다. V8은 StartOffset+offset으로 멤버를 읽고 씁니다. set_code를 예로 들어 설명하면 코드는 아래와 같습니다.

void JSFunction::set_code(Code value) {
  DCHECK(!ObjectInYoungGeneration(value));
  RELAXED_WRITE_FIELD(*this, kCodeOffset, value);
#ifndef V8_DISABLE_WRITE_BARRIERS
  MarkingBarrier(*this, RawField(kCodeOffset), value);
#endif
}


매크로 템플릿 RELAXED_WRITE_FIELD는 다음과 같습니다.

#define RELAXED_WRITE_FIELD(p, offset, value) \
  TaggedField<Object>::Relaxed_Store(p, offset, value)
//...........................................
template <typename T, int kFieldOffset>
void TaggedField<T, kFieldOffset>::Relaxed_Store(HeapObject host, int offset,
                                                 T value) {
  AsAtomicTagged::Relaxed_Store(location(host, offset),
                                full_to_tagged(value.ptr()));
}
//........................................
  template <typename T>
  static void Relaxed_Store(T* addr,
                            typename std::remove_reference<T>::type new_value) {
    STATIC_ASSERT(sizeof(T) <= sizeof(AtomicStorageType));
    base::Relaxed_Store(to_storage_addr(addr),
                        cast_helper<T>::to_storage_type(new_value));
  }


위의 두 개의 Relaxed_Store는 set_code의 쓰기 작업을 구현합니다. 그림 2는 호출 스택을 보여줍니다.


읽기 동작은 위에서 언급한 쓰기와 동일합니다. 오프셋에서 바이트를 읽고 예상 유형으로 변환합니다. 코드는 아래에 있습니다. 직접 분석하십시오.

#define RELAXED_READ_FIELD(p, offset) \
  TaggedField<Object>::Relaxed_Load(p, offset)
//............................................
  template <typename T>
  static T Relaxed_Load(T* addr) {
    STATIC_ASSERT(sizeof(T) <= sizeof(AtomicStorageType));
    return cast_helper<T>::to_return_type(
        base::Relaxed_Load(to_storage_addr(addr)));
  }


그건 그렇고, Relaxed_Load는 V8에서 힙 객체를 관리할 때 자주 사용되는 기본 메서드입니다.

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

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

좋은 웹페이지 즐겨찾기