作者:灰豆
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org
介紹
substring、getDate、catch 等是常用的 JavaScript API,接下來的幾篇文章將對 V8 中 API 的設計思想、源碼和關鍵函數進行講解,并通過例子講解 JavaScript 在 V8 中的初始化、運行方式,以及它與解釋器、編譯器、字節碼之間的關系。 本文講解 API Equal 和 StrictEqual 的設計與實現。
Equal 和 StrictEqual 的調用方式
來看一段 JS 源碼和它的字節碼:
1. var a="123";
2. var b = a == 123;
3. var c = a === 123;
4. console.log(b);
5. console.log(c);
6. //分隔線............
7. //省略...............................
8. 000000CFDD021F54 @ 22 : 6a f9 04 TestEqual r1, [4]
9. 000000CFDD021F57 @ 25 : 23 03 05 StaGlobal [3], [5]
10. 000000CFDD021F5A @ 28 : 21 02 02 LdaGlobal [2], [2]
11. 000000CFDD021F5D @ 31 : c2 Star1
12. 000000CFDD021F5E @ 32 : 0d 7b LdaSmi [123]
13. 000000CFDD021F60 @ 34 : 6b f9 07 TestEqualStrict r1, [7]
14. //省略...............................
上述代碼中,第 2 行代碼 '==' 的字節碼是第 8 行代碼 TestEqual; 第 3 行代碼 '===' 的字節碼是第 13 行代碼 TestEqualStrict; 下面講解字節碼 TestEqual 和 TestEqualStrict,源碼如下:
IGNITION_HANDLER(TestEqual, InterpreterCompareOpAssembler) {
CompareOpWithFeedback(Operation::kEqual);
}
//分隔線............
IGNITION_HANDLER(TestEqualStrict, InterpreterCompareOpAssembler) {
CompareOpWithFeedback(Operation::kStrictEqual);
}
上述兩條字節碼中都使用了 CompareOpWithFeedback(),區別是參數不同,CompareOpWithFeedback 源碼如下:
1. void CompareOpWithFeedback(Operation compare_op) {
2. TNode<Object> lhs = LoadRegisterAtOperandIndex(0);
3. TNode<Object> rhs = GetAccumulator();
4. TNode<Context> context = GetContext();
5. //省略.......................
6. TVARIABLE(Smi, var_type_feedback);
7. TNode<Oddball> result;
8. switch (compare_op) {
9. case Operation::kEqual:
10. result = Equal(lhs, rhs, context, &var_type_feedback);
11. break;
12. case Operation::kStrictEqual:
13. result = StrictEqual(lhs, rhs, &var_type_feedback);
14. break;
15. case Operation::kLessThan:
16. case Operation::kGreaterThan:
17. case Operation::kLessThanOrEqual:
18. case Operation::kGreaterThanOrEqual:
19. result = RelationalComparison(compare_op, lhs, rhs, context,
20. &var_type_feedback);
21. break;
22. default:
23. UNREACHABLE();
24. }
25. //省略.......................
26. SetAccumulator(result);
27. Dispatch();
28. }
上述代碼中,第 2 行取出左操作數 lhs;
第 3 行取出右操作數 rhs。rhs 存儲在累加器中,所以字節碼 TestEqual 的操作數只有 r1,也就是 lhs。[4] 不屬于 TestEqual 的操作數,它用于信息收集。TestEqualStrict 的情況也一樣。
第 8 行代碼根據 compare_op 選擇 Equal 或是 StrictEqual。
第 15~19 行代碼是小于、大于等操作的實現,本文不做講解。
Equal 源碼分析
圖 1 給出了 Equal 的源碼和函數調用堆棧,需要使用 mksnapshot 進行跟蹤。
下面講解 Equal 源碼。
1. TNode<Oddball> CodeStubAssembler::Equal(/*省略*/) {
2. //省略................
3. TVARIABLE(Object, var_left, left);
4. TVARIABLE(Object, var_right, right);
5. //省略...............
6. BIND(&loop);
7. {
8. left = var_left.value();
9. right = var_right.value();
10. Label if_notsame(this);
11. GotoIf(TaggedNotEqual(left, right), &if_notsame);
12. {GenerateEqual_Same(left, &if_equal, &if_notequal, var_type_feedback);}
13. BIND(&if_notsame);
14. Label if_left_smi(this), if_left_not_smi(this);
15. Branch(TaggedIsSmi(left), &if_left_smi, &if_left_not_smi);
16. BIND(&if_left_smi);
17. {
18. Label if_right_smi(this), if_right_not_smi(this);
19. CombineFeedback(var_type_feedback,
20. CompareOperationFeedback::kSignedSmall);
21. Branch(TaggedIsSmi(right), &if_right_smi, &if_right_not_smi);
22. BIND(&if_right_smi);
23. { Goto(&if_notequal); }
24. BIND(&if_right_not_smi);
25. { TNode<Map> right_map = LoadMap(CAST(right));
26. Label if_right_heapnumber(this), if_right_oddball(this),
27. if_right_bigint(this, Label::kDeferred),
28. if_right_receiver(this, Label::kDeferred);
29. GotoIf(IsHeapNumberMap(right_map), &if_right_heapnumber);
30. TNode<Uint16T> right_type = LoadMapInstanceType(right_map);
31. GotoIf(IsStringInstanceType(right_type), &do_right_stringtonumber);
32. GotoIf(IsOddballInstanceType(right_type), &if_right_oddball);
33. GotoIf(IsBigIntInstanceType(right_type), &if_right_bigint);
34. GotoIf(IsJSReceiverInstanceType(right_type), &if_right_receiver);
35. CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
36. Goto(&if_notequal); } }
37. BIND(&if_left_not_smi);
38. { GotoIf(TaggedIsSmi(right), &use_symmetry);
39. Label if_left_symbol(this), if_left_number(this),
40. if_left_string(this, Label::kDeferred),
41. if_left_bigint(this, Label::kDeferred), if_left_oddball(this),
42. if_left_receiver(this);
43. TNode<Map> left_map = LoadMap(CAST(left));
44. TNode<Map> right_map = LoadMap(CAST(right));
45. TNode<Uint16T> left_type = LoadMapInstanceType(left_map);
46. TNode<Uint16T> right_type = LoadMapInstanceType(right_map);
47. GotoIf(IsStringInstanceType(left_type), &if_left_string);
48. GotoIf(IsSymbolInstanceType(left_type), &if_left_symbol);
49. GotoIf(IsHeapNumberInstanceType(left_type), &if_left_number);
50. GotoIf(IsOddballInstanceType(left_type), &if_left_oddball);
51. Branch(IsBigIntInstanceType(left_type), &if_left_bigint,
52. &if_left_receiver);
53. BIND(&if_left_string);
54. { GotoIfNot(IsStringInstanceType(right_type), &use_symmetry);
55. result =
56. CAST(CallBuiltin(Builtin::kStringEqual, context(), left, right));
57. CombineFeedback(var_type_feedback,
58. SmiOr(CollectFeedbackForString(left_type),
59. CollectFeedbackForString(right_type)));
60. Goto(&end); }
61. BIND(&if_left_number);
62. { Label if_right_not_number(this);
63. CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
64. GotoIf(Word32NotEqual(left_type, right_type), &if_right_not_number);
65. var_left_float = LoadHeapNumberValue(CAST(left));
66. var_right_float = LoadHeapNumberValue(CAST(right));
67. Goto(&do_float_comparison);
68. BIND(&if_right_not_number);
69. { Label if_right_oddball(this);
70. GotoIf(IsStringInstanceType(right_type), &do_right_stringtonumber);
71. //省略...............
72. Goto(&if_notequal);
73. BIND(&if_right_oddball);
74. { Label if_right_boolean(this);
75. GotoIf(IsBooleanMap(right_map), &if_right_boolean);
76. CombineFeedback(var_type_feedback,
77. CompareOperationFeedback::kOddball);
78. Goto(&if_notequal);
79. BIND(&if_right_boolean);
80. {CombineFeedback(var_type_feedback,
81. CompareOperationFeedback::kBoolean);
82. var_right =
83. LoadObjectField(CAST(right), Oddball::kToNumberOffset);
84. Goto(&loop);
85. } } } }
86. BIND(&if_left_bigint);
87. {
88. Label if_right_heapnumber(this), if_right_bigint(this),
89. if_right_string(this), if_right_boolean(this);
90. CombineFeedback(var_type_feedback, CompareOperationFeedback::kBigInt);
91. GotoIf(IsStringInstanceType(right_type), &if_right_string);
92. //省略...............
93. BIND(&if_right_heapnumber);
94. { CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
95. result = CAST(CallRuntime(Runtime::kBigIntEqualToNumber,
96. NoContextConstant(), left, right));
97. Goto(&end); }
98. BIND(&if_right_bigint);
99. {
100. result = CAST(CallRuntime(Runtime::kBigIntEqualToBigInt,
101. NoContextConstant(), left, right));
102. Goto(&end); }
103. BIND(&if_right_string);
104. {
105. CombineFeedback(var_type_feedback, CompareOperationFeedback::kString);
106. result = CAST(CallRuntime(Runtime::kBigIntEqualToString,
107. NoContextConstant(), left, right));
108. Goto(&end); }
109. BIND(&if_right_boolean);
110. { CombineFeedback(var_type_feedback,
111. CompareOperationFeedback::kBoolean);
112. var_right = LoadObjectField(CAST(right), Oddball::kToNumberOffset);
113. Goto(&loop); } }
114. BIND(&if_left_oddball);
115. { Label if_left_boolean(this), if_left_not_boolean(this);
116. GotoIf(IsBooleanMap(left_map), &if_left_boolean);
117. if (var_type_feedback != nullptr) {
118. CombineFeedback(var_type_feedback,
119. CompareOperationFeedback::kNullOrUndefined);
120. GotoIf(IsUndetectableMap(left_map), &if_left_not_boolean);
121. }
122. Goto(&if_left_not_boolean);
123. BIND(&if_left_not_boolean);
124. {
125. Label if_right_undetectable(this), if_right_number(this),
126. if_right_oddball(this),
127. if_right_not_number_or_oddball_or_undetectable(this);
128. //省略...............
129. } }
130. BIND(&if_left_receiver);
131. {
132. CSA_DCHECK(this, IsJSReceiverInstanceType(left_type));
133. Label if_right_receiver(this), if_right_not_receiver(this);
134. Branch(IsJSReceiverInstanceType(right_type), &if_right_receiver,
135. &if_right_not_receiver);
136. BIND(&if_right_receiver);
137. {
138. CombineFeedback(var_type_feedback,
139. CompareOperationFeedback::kReceiver);
140. Goto(&if_notequal); }
141. BIND(&if_right_not_receiver);
142. {
143. Label if_right_undetectable(this),
144. if_right_not_undetectable(this, Label::kDeferred);
145. Branch(IsUndetectableMap(right_map), &if_right_undetectable,
146. &if_right_not_undetectable);
147. BIND(&if_right_undetectable);
148. {
149. CSA_DCHECK(this, IsNullOrUndefined(right));
150. if (var_type_feedback != nullptr) {
151. *var_type_feedback = SmiConstant(
152. CompareOperationFeedback::kReceiverOrNullOrUndefined); }
153. Branch(IsUndetectableMap(left_map), &if_equal, &if_notequal); }
154. BIND(&if_right_not_undetectable);
155. {
156. CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
157. Callable callable = CodeFactory::NonPrimitiveToPrimitive(isolate());
158. var_left = CallStub(callable, context(), left);
159. Goto(&loop); } }}
160. }
161. BIND(&do_right_stringtonumber);
162. {
163. if (var_type_feedback != nullptr) {
164. TNode<Map> right_map = LoadMap(CAST(right));
165. TNode<Uint16T> right_type = LoadMapInstanceType(right_map);
166. CombineFeedback(var_type_feedback,
167. CollectFeedbackForString(right_type)); }
168. var_right = CallBuiltin(Builtin::kStringToNumber, context(), right);
169. Goto(&loop); }
170. BIND(&use_symmetry);
171. {
172. var_left = right;
173. var_right = left;
174. Goto(&loop); }
175. }
176. BIND(&if_equal);
177. {
178. result = TrueConstant();
179. Goto(&end); }
180. BIND(&end);
181. return result.value();}
我們根據測試用例代碼的執行順序來講解上述代碼。
第 3 行創建左操作數變量 var_left,并把參數 left 的值賦給 var_left;
第 4 行創建右操作數變量 var_right,并把參數 right的值賦給 var_right;
第 6 行進入循環;
第 8、9 行獲取左、右操作數并賦給 left 和 right。根據我們的測試用例,left 為 "123",rigth 為 123。
第 11 行代碼判斷 left 和 right 的Tag 是否一致,left 和rigth 分別是 HeapObject 類型和 SMI 類型,所以 Tag 不一致;跳轉到 if_notsame 標簽;
第 14-15 行判斷左操作數 left 是否為 SMI,left 是 HeapObject,再次跳轉到 if_left_not_smi 標簽;
第 38 行判斷 right 是否為 SMI,結果為真,跳轉到 use_symmetry;
第 170-174 行調換左、右操作數,重新到第 6 行進入循環;
再次執行第 11 行代碼,依舊跳轉到 if_notsame 標簽;
第 15 行判斷結果為真,跳轉到 if_left_smi 標簽;
第 21 行判斷結果為假,跳轉到 if_right_not_smi 標簽;
第 30-35 行判斷 right 的類型,目前右操作數為 "123",字串符串類型,跳轉到 do_right_stringtonumber 標簽;
第 163-169 行調用 Builtin::kStringToNumber 把 right 轉為 Number 類型,我們的測試用例轉為了 SMI;再次進入循環;
第 11 行判斷結果為真,進入第 12 行代碼;
第 12 行檢測到 left 為SMI,所以得出結果 left = right,跳轉到 if_equal 標簽;
第 176-180 行生成 V8 值對象 true 并返回結果。
為什么沒有做值的比較,第 16、18 行就得到了結果?
答:left、right 均為 SMI,所以他們的值是直接存儲的,如果二者的 Tag 相等,也就是值相等。
StrictEqual 源碼分析
與 Equal 不同,StrictEqual 要求左、右操作數的類型和值都相同時,返回值才為真。下面給出 StrictEqual 源碼的算法偽代碼:
1. //Pseudo-code for the algorithm below:
2. //偽代碼選自 StrictEqual 源碼
3. if (lhs == rhs) {
4. if (lhs->IsHeapNumber()) return HeapNumber::cast(lhs)->value() != NaN;
5. return true;
6. }
7. if (!lhs->IsSmi()) {
8. if (lhs->IsHeapNumber()) {
9. if (rhs->IsSmi()) {
10. return Smi::ToInt(rhs) == HeapNumber::cast(lhs)->value();
11. } else if (rhs->IsHeapNumber()) {
12. return HeapNumber::cast(rhs)->value() ==
13. HeapNumber::cast(lhs)->value();
14. } else {
15. return false;
16. }
17. } else {
18. if (rhs->IsSmi()) {
19. return false;
20. } else {
21. if (lhs->IsString()) {
22. if (rhs->IsString()) {
23. return %StringEqual(lhs, rhs);
24. } else {
25. return false;
26. }
27. } else if (lhs->IsBigInt()) {
28. if (rhs->IsBigInt()) {
29. return %BigIntEqualToBigInt(lhs, rhs);
30. } else {
31. return false;
32. }
33. } else {
34. return false;
35. }
36. }
37. }
38. } else {
39. if (rhs->IsSmi()) {
40. return false;
41. } else {
42. if (rhs->IsHeapNumber()) {
43. return Smi::ToInt(lhs) == HeapNumber::cast(rhs)->value();
44. } else {
45. return false;
46. }
47. }
48. }
上述代碼中,第 3 行代碼對左、右操作數進行判斷、其中 "==" 為重載運行符;
第 4 行代碼說明左、右操作數相同。如果均為 HeapNumber,再進一步判斷 HeapNumber 的原始值是否為空,如果原始值存在則判斷結果為真;
第 7-38 行代碼處理左操作數不是 SMI 的情況,在此情況下:
第 8-9 行代碼如果左操作數為 HeapNumber,右操作數為 SMI 時,則判斷 SMI 與堆對象的原始值是否相同,相同結果為真;
第 11-13 行代碼如果左、右均為 HeapNumber,則判斷他們的原始值是否相同,相同結果為真;
第 18-19 行代碼左操作數不是 SMI,如果右操作數是 SMI,則結果為假;
第 21-26 行代碼如果左、右均為 String,使用 StringEqual() 方法判斷;
第 27-34 行代碼如果左、右均為 BigInt,使用 BigIntEqualToBigInt() 方法判斷;
第 39-45 行代碼如果右操作數是 SMI,結果為假;如果右操作數為 HeapNumber,則判斷左操作數和右操作數的原始值。
技術總結
Equal 方法先將左、右操作數的類型統一,然后再進行值的判斷。Equal 采用了如下的優化技巧:
(1) Equal 大部分情況下用于數字類型判斷,所以 Equal 開始就判斷 Tagged 和 SMI 類型,如果相同則直接返回結果;
(2) 為了簡化 Equal的編碼,將左、右操作調換以滿足左操作數為 SMI 的情況;
(3) 如果左、右操作數均不是 SMI,通過循環的方式統一左、右操作數的類型。
好了,今天到這里,下次見。
個人能力有限,有不足與紕漏,歡迎批評指正
微信:qq9123013 備注:v8交流
知乎:https://www.zhihu.com/people/v8blink
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1838/
暫無評論