Let’s Understand Chrome V8 — Chapter 12: What is JSFunction?
灰豆
Posted on August 28, 2022
Original source: https://medium.com/@huidou/lets-understand-chrome-v8-chapter-12-what-is-jsfunction-2a03cd2291c6
Welcome to other chapters of Let’s Understand Chrome V8
The data structure that saves bytecodes is SharedFunction, and the SharedFunction bound to the context is JSFunction. What is the difference? In my opinion, a SharedFunction is like a DLL, a common library that can be shared by multiple programs; and, a JSFunction is a specific executable program. In this paper, we’ll talk about the JSFunction’s code and the memory layout.
1. JSFunction’s code
Below is the definition of 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. };
Three index members are defined from lines 5 to 7, which are static members and apply to all SharedFunction instances. Lines 10, 11, and 12 bind the context and the global receiver. Line 17 binds InterpreterEntryTrampoline , which is the entry point for executing bytecode. Lines 18 to 45 define the functions for optimization and de-optimization, which are used in TurboFan. Let’s talk about the process of new JSFunction.
(1) Create SharedFunction and install bytecode
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. }
When creating a new JSFunction instance, we need to create an SharedFunction instance first. An important step when creating the SharedFunction instance is to install the bytecode, see line 13 of the above code.
(2) New JSFunction
Through the below code, you will see that creating JS is the process of binding a SharedFunction and a context actually.
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. }
Line 2 of the above code casts this to the SharedFunctionInfo (this is the SharedFunctionInfo originally). Line 5, call NewFunctionFromSharedFunctionInfo to create JSFunction. Note: its parameters are S and C, see line 11. Finally, call NewFunction which is below.
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. }
The 9th and 11th lines of code above set the SharedFunctionInfo and the context to the corresponding positions, which is the bind mentioned above.
What is the GetCode on line 10? See below.
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. }
Our test case (see Chapter 4) is the bytecode array, so the return value of GetCode is kInterpreterEntryTrampoline. The role of GetCode is to return an entry method according to the type of SharedFunctionInfo, which is used to jump to the corresponding bytecode and execute it (see Chapter 8).
To sum up, when creating JS, the bytecode type, context, and entry method are what we care about, and the others are not important. Figure 1 shows the call stack.
2. JSFunction’s memory layout
The JSFunction is V8’s heap-managed object and uses memory offsets to represent internal members. Code is below:
#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) \
};
The TORQUE_GENERATED_JSFUNCTION_FIELDS defines the offset of all members, and StartOffset is the base address. V8 reads and writes members by StartOffset+offset. Take set_code as an example to explain, the code is below.
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
}
Macro template RELAXED_WRITE_FIELD is below.
#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));
}
The above two Relaxed_Store implement the write operation of set_code. Figure 2 shows the call stack.
The read operation is the same as the write mentioned above. The bytes are read from the offset and converted to the expected type. The code is below, please analyze it by yourself.
#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)));
}
By the way, Relaxed_Load is a base method, which is used frequently when V8 manages heap objects.
Okay, that wraps it up for this share. I’ll see you guys next time, take care!
Please reach out to me if you have any issues. WeChat: qq9123013 Email: v8blink@outlook.com
Posted on August 28, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.