Давайте разберемся в Chrome V8 — Глава 12: Что такое JSFunction?

Первоисточник: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-12-what-is-jsfunction-2a03cd2291c6
Добро пожаловать в другие главы «Давайте поймем 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 и контекст в соответствующие позиции, что и является упомянутой выше привязкой.

Что такое GetCode в строке 10? Смотрите ниже.

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.  }
Вход в полноэкранный режим Выход из полноэкранного режима

Наш тестовый пример (см. главу 4) — это массив байткода, поэтому возвращаемое значение GetCode — kInterpreterEntryTrampoline. Роль GetCode заключается в возврате метода входа в соответствии с типом SharedFunctionInfo, который используется для перехода к соответствующему байткоду и его выполнения (см. главу 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 управляет объектами кучи.

Ладно, на этом мы заканчиваем эту статью. Увидимся в следующий раз, ребята, всего доброго!

Пожалуйста, свяжитесь со мной, если у вас возникнут какие-либо вопросы. WeChat: qq9123013 Email: v8blink@outlook.com

Оцените статью
devanswers.ru
Добавить комментарий