|
| 1 | +Google Translate version :)) |
| 2 | +Old version of V8 |
| 3 | + |
| 4 | +"Chrome V8 Source Code" 33. Technical details of Lazy Compile |
| 5 | + |
| 6 | + |
| 7 | +# 1. Summary |
| 8 | +This article is the eighth in the Builtin topic. This article will track the execution process of Bytecode, explain the startup method, workflow, and important data structures of Lazy Compile, and also introduce Builtins related to Lazy Compile. |
| 9 | + |
| 10 | +# 2. Starting Lazy Compile |
| 11 | +Before entering Lazy Compile, you must first understand the Bytecode execution process, and use this process to understand how to start Lazy Compile. The source code is as follows: |
| 12 | +```cpp |
| 13 | +```text |
| 14 | +1. function ignition(s) { |
| 15 | +2. this.slogan=s; |
| 16 | +3. this.start=function(){eval('console.log(this.slogan);')} |
| 17 | +4. } |
| 18 | +5. worker = new ignition("here we go!"); |
| 19 | +6. worker.start(); |
| 20 | +7. //............分隔线................ |
| 21 | +8. --- AST --- |
| 22 | +9. . . FUNCTION "ignition" = function ignition |
| 23 | +10. . EXPRESSION STATEMENT at 106 |
| 24 | +11. . . ASSIGN at 113 |
| 25 | +12. . . . VAR PROXY unallocated (0000016B96A17A78) (mode = DYNAMIC_GLOBAL, assigned = true) "worker" |
| 26 | +13. . . . CALL NEW at 115 |
| 27 | +14. . . . . VAR PROXY unallocated (0000016B96A17790) (mode = VAR, assigned = true) "ignition" |
| 28 | +15. . . . . LITERAL "here we go!"//...........省略.............. |
| 29 | +16. //...............分隔线................. |
| 30 | +17. 0000025885361EAE @ 0 : 13 00 LdaConstant [0] |
| 31 | +18. 0000025885361EB0 @ 2 : c2 Star1 |
| 32 | +19. 0000025885361EB1 @ 3 : 19 fe f8 Mov <closure>, r2 |
| 33 | +20. 0000025885361EB4 @ 6 : 64 51 01 f9 02 CallRuntime [DeclareGlobals], r1-r2 |
| 34 | +21. 0000025885361EB9 @ 11 : 21 01 00 LdaGlobal [1], [0] |
| 35 | +22. 0000025885361EBC @ 14 : c2 Star1 |
| 36 | +23. 0000025885361EBD @ 15 : 13 02 LdaConstant [2] |
| 37 | +24. 0000025885361EBF @ 17 : c1 Star2 |
| 38 | +25. 0000025885361EC0 @ 18 : 0b f9 Ldar r1 |
| 39 | +26. 0000025885361EC2 @ 20 : 68 f9 f8 01 02 Construct r1, r2-r2, [2] |
| 40 | +27. 0000025885361EC7 @ 25 : 23 03 04 StaGlobal [3], [4] |
| 41 | +28. 0000025885361ECA @ 28 : 21 03 06 LdaGlobal [3], [6] |
| 42 | +29. 0000025885361ECD @ 31 : c1 Star2 |
| 43 | +30. 0000025885361ECE @ 32 : 2d f8 04 08 LdaNamedProperty r2, [4], [8] |
| 44 | +31. 0000025885361ED2 @ 36 : c2 Star1 |
| 45 | +32. 0000025885361ED3 @ 37 : 5c f9 f8 0a CallProperty0 r1, r2, [10] |
| 46 | +33. 0000025885361ED7 @ 41 : c3 Star0 |
| 47 | +34. 0000025885361ED8 @ 42 : a8 Return |
| 48 | +35. //..............省略................ |
| 49 | +36. - length: 5 |
| 50 | +37. 0: 0x02841b4e1d31 <FixedArray[2]> |
| 51 | +38. 1: 0x02841b4e1c09 <String[8]: #ignition> |
| 52 | +39. 2: 0x02841b4e1c51 <String[11]: #here we go!> |
| 53 | +40. 3: 0x02841b4e1c39 <String[6]: #worker> |
| 54 | +41. 4: 0x02841b4e1c71 <String[5]: #start> |
| 55 | +`````` |
| 56 | +
|
| 57 | +The above code is divided into three parts. |
| 58 | +The first part (lines 1-6) is the test code used in this article, where line 5 starts Lazy Compile; |
| 59 | +the second part (lines 8-15) is the AST of the test code; |
| 60 | +the third part (lines 17-41) is the Bytecode of the test code. |
| 61 | +
|
| 62 | +Let's start with the Bytecode: (1) LdaGlobal [1], [0] (line 21) uses the string in the constant pool [1] as the key to obtain the global object, that is, to obtain the ignition function; |
| 63 | +Star1 (line 22) stores ignition in r1; |
| 64 | +Ldar r1 (line 25) takes ignition from r1 and stores it in the accumulator register; |
| 65 | +(2) LdaConstant [2] (line 23) and Star2 (line 24) store the string "here we go!" in r2. Construct r1, r2-r2, [2] (26 lines) The Compiler is started when constructing the ignition function. The source code is as follows: |
| 66 | +
|
| 67 | +```cpp |
| 68 | +```text |
| 69 | +1. RUNTIME_FUNCTION(Runtime_NewObject) { |
| 70 | +2. HandleScope scope(isolate); |
| 71 | +3. DCHECK_EQ(2, args.length()); |
| 72 | +4. CONVERT_ARG_HANDLE_CHECKED(JSFunction, target, 0); |
| 73 | +5. CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, 1); |
| 74 | +6. RETURN_RESULT_OR_FAILURE( |
| 75 | +7. isolate, |
| 76 | +8. JSObject::New(target, new_target, Handle<AllocationSite>::null())); |
| 77 | +9. } |
| 78 | +10. //.............分隔线............... |
| 79 | +11. int JSFunction::CalculateExpectedNofProperties(Isolate* isolate, |
| 80 | +12. Handle<JSFunction> function) { |
| 81 | +13. int expected_nof_properties = 0; |
| 82 | +14. for (PrototypeIterator iter(isolate, function, kStartAtReceiver); |
| 83 | +15. !iter.IsAtEnd(); iter.Advance()) { |
| 84 | +16. Handle<JSReceiver> current = |
| 85 | +17. PrototypeIterator::GetCurrent<JSReceiver>(iter); |
| 86 | +18. if (!current->IsJSFunction()) break; |
| 87 | +19. Handle<JSFunction> func = Handle<JSFunction>::cast(current); |
| 88 | +20. // The super constructor should be compiled for the number of expected |
| 89 | +21. // properties to be available. |
| 90 | +22. Handle<SharedFunctionInfo> shared(func->shared(), isolate); |
| 91 | +23. IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate)); |
| 92 | +24. if (is_compiled_scope.is_compiled() || |
| 93 | +25. Compiler::Compile(isolate, func, Compiler::CLEAR_EXCEPTION, |
| 94 | +26. &is_compiled_scope)) { |
| 95 | +27. } else { |
| 96 | +28. } |
| 97 | +29. } |
| 98 | +30. } |
| 99 | +`````` |
| 100 | + |
| 101 | +The above code is divided into two parts. New() (line 8) in Runtime_NewObject creates a new object, that is, creates the ignition function. The second part of the code (lines 11-30) is called in New(). When line 24 calculates the properties of ignition, the Compiler is started to generate and execute bytecode. The source code is as follows: |
| 102 | + |
| 103 | +``` |
| 104 | +00000258853621BE @ 0 : 82 00 04 CreateFunctionContext [0], [4] |
| 105 | + 00000258853621C1 @ 3 : 1a f9 PushContext r1 |
| 106 | +//...省略............ |
| 107 | + 00000258853621E7 @ 41 : a8 Return |
| 108 | +``` |
| 109 | + |
| 110 | +The Compiler will not be started when the above code is executed, so the Return instruction will return to the test code and execute line 32 CallProperty0 r1, r2, [10]. The source code is as follows: |
| 111 | + |
| 112 | + |
| 113 | +```cpp |
| 114 | +1. IGNITION_HANDLER(CallProperty0, InterpreterJSCallAssembler) { |
| 115 | +2. JSCallN(0, ConvertReceiverMode::kNotNullOrUndefined); |
| 116 | +3. } |
| 117 | +4. //.............分隔线...................... |
| 118 | +5. void JSCallN(int arg_count, ConvertReceiverMode receiver_mode) { |
| 119 | +6. Comment("sea node1"); |
| 120 | +7. const int kFirstArgumentOperandIndex = 1; |
| 121 | +8. const int kReceiverOperandCount = (receiver_mode == ConvertReceiverMode::kNullOrUndefined) ? 0 : 1; |
| 122 | +9. const int kReceiverAndArgOperandCount = kReceiverOperandCount + arg_count; |
| 123 | +10. const int kSlotOperandIndex = kFirstArgumentOperandIndex + kReceiverAndArgOperandCount; |
| 124 | +11. TNode<Object> function = LoadRegisterAtOperandIndex(0); |
| 125 | +12. LazyNode<Object> receiver = [=] {return receiver_mode == ConvertReceiverMode::kNullOrUndefined |
| 126 | +13. ? UndefinedConstant() : LoadRegisterAtOperandIndex(1); }; |
| 127 | +14. TNode<UintPtrT> slot_id = BytecodeOperandIdx(kSlotOperandIndex); |
| 128 | +15. TNode<HeapObject> maybe_feedback_vector = LoadFeedbackVector(); |
| 129 | +16. TNode<Context> context = GetContext(); |
| 130 | +17. CollectCallFeedback(function, receiver, context, maybe_feedback_vector, |
| 131 | +18. slot_id); |
| 132 | +19. switch (kReceiverAndArgOperandCount) { |
| 133 | +20. case 0: |
| 134 | +21. CallJSAndDispatch(function, context, Int32Constant(arg_count), |
| 135 | +22. receiver_mode); |
| 136 | +23. break; |
| 137 | +24. case 1: |
| 138 | +25. CallJSAndDispatch( |
| 139 | +26. function, context, Int32Constant(arg_count), receiver_mode, |
| 140 | +27. LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex)); |
| 141 | +28. break;//....省略....... |
| 142 | +29. default: |
| 143 | +30. UNREACHABLE(); |
| 144 | +31. } |
| 145 | +32. } |
| 146 | +33. }; |
| 147 | +``` |
| 148 | +
|
| 149 | +In the above code, the value of register r1 is JSFunction start, and the value of register r2 is ignition map. Line 2 calls JSCallN(); line 9 calls kReceiverAndArgOperandCount to 2; line 11 calls function to JSFunction start; line 25 calls CallJSAndDIspatch(), which uses TailCallN() to complete the function call and finally enters Lazy Compile. Figure 1 shows the call stack at this time. |
| 150 | +
|
| 151 | + |
| 152 | +
|
| 153 | +# 3. Lazy Compile |
| 154 | +The way to start Lazy Compile in the test code is Runtime. The source code is as follows: |
| 155 | +```cpp |
| 156 | +1. RUNTIME_FUNCTION(Runtime_CompileLazy) { |
| 157 | +2. HandleScope scope(isolate); |
| 158 | +3. DCHECK_EQ(1, args.length()); |
| 159 | +4. CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0); |
| 160 | +5. Handle<SharedFunctionInfo> sfi(function->shared(), isolate); |
| 161 | +6. #ifdef DEBUG |
| 162 | +7. if (FLAG_trace_lazy && !sfi->is_compiled()) { |
| 163 | +8. PrintF("[unoptimized: "); |
| 164 | +9. function->PrintName(); |
| 165 | +10. PrintF("]\n"); |
| 166 | +11. } |
| 167 | +12. #endif |
| 168 | +13. StackLimitCheck check(isolate); |
| 169 | +14. if (check.JsHasOverflowed(kStackSpaceRequiredForCompilation * KB)) { |
| 170 | +15. return isolate->StackOverflow(); |
| 171 | +16. } |
| 172 | +17. IsCompiledScope is_compiled_scope; |
| 173 | +18. if (!Compiler::Compile(isolate, function, Compiler::KEEP_EXCEPTION, |
| 174 | +19. &is_compiled_scope)) { |
| 175 | +20. return ReadOnlyRoots(isolate).exception(); |
| 176 | +21. } |
| 177 | +22. DCHECK(function->is_compiled()); |
| 178 | +23. return function->code(); |
| 179 | +24. } |
| 180 | +``` |
| 181 | + |
| 182 | +The value of function in line 3 of the above code is JSFunction start; line 18 starts the compilation process. The source code is as follows: |
| 183 | +```cpp |
| 184 | +1. bool Compiler::Compile(...省略....) { |
| 185 | +2. Handle<Script> script(Script::cast(shared_info->script()), isolate); |
| 186 | +3. UnoptimizedCompileFlags flags = |
| 187 | +4. UnoptimizedCompileFlags::ForFunctionCompile(isolate, *shared_info); |
| 188 | +5. UnoptimizedCompileState compile_state(isolate); |
| 189 | +6. ParseInfo parse_info(isolate, flags, &compile_state); |
| 190 | +7. LazyCompileDispatcher* dispatcher = isolate->lazy_compile_dispatcher(); |
| 191 | +8. if (dispatcher->IsEnqueued(shared_info)) { |
| 192 | +9. } |
| 193 | +10. if (shared_info->HasUncompiledDataWithPreparseData()) { |
| 194 | +11. } |
| 195 | +12. if (!parsing::ParseAny(&parse_info, shared_info, isolate, |
| 196 | +13. parsing::ReportStatisticsMode::kYes)) { |
| 197 | +14. return FailWithPendingException(isolate, script, &parse_info, flag); |
| 198 | +15. }//..........省略........ |
| 199 | +16. FinalizeUnoptimizedCompilationDataList |
| 200 | +17. finalize_unoptimized_compilation_data_list; |
| 201 | +18. if (!IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs( |
| 202 | +19. isolate, shared_info, script, &parse_info, isolate->allocator(), |
| 203 | +20. is_compiled_scope, &finalize_unoptimized_compilation_data_list, |
| 204 | +21. nullptr)) { |
| 205 | +22. return FailWithPendingException(isolate, script, &parse_info, flag); |
| 206 | +23. } |
| 207 | +24. FinalizeUnoptimizedCompilation(isolate, script, flags, &compile_state, |
| 208 | +25. finalize_unoptimized_compilation_data_list); |
| 209 | +26. if (FLAG_always_sparkplug) { |
| 210 | +27. CompileAllWithBaseline(isolate, finalize_unoptimized_compilation_data_list); |
| 211 | +28. } |
| 212 | +29. return true; |
| 213 | +30. } |
| 214 | +``` |
| 215 | +
|
| 216 | +The above code is consistent with the compilation process described above, please analyze it yourself. Note: Line 27 is the new compilation component added by V8, which is located between Ignition and Turbofan. Figure 2 shows the call stack at this time. |
| 217 | + |
| 218 | +
|
| 219 | +Technical summary (1) This article involves two compiles, one for calculating object properties and the other for Lazy Compile; (2) TailCallN() is used to add a Node to the end of the current Block and complete the function call, see sea of nodes for details. |
0 commit comments