Chrome V8 source code 48 The mystery of weak type addition, "+" source code analysis

1 Introduction

JavaScript is a weakly typed language. When its variables and expressions participate in the operation, even if the type is incorrect, they can get the correct type through implicit conversion, which is like all types can carry out all operations for users. By analyzing the addition source code of V8, this paper leads you to understand the details of JavaScript addition and see how V8 does it.

2 ADD_HANDLER

V8 executes JavaScript source code from bytecode, so we analyze it from the bytecode handler of addition. The source code is as follows:

IGNITION_HANDLER(Add, InterpreterBinaryOpAssembler) {
BinaryOpWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback);
}
All addition operations in JavaScript source code should start from the add above_ Starting with handler, it will call Builtin::kAdd and other functions internally. In TurboFan, ADD_HANDLER may be optimized for functions such as newconstring or StringConcat. Let's follow the code to see how V8 organizes these functions.

3 Generate_AddWithFeedback

1.  TNode<Object> BinaryOpAssembler::Generate_AddWithFeedback() {
2.    Label if_lhsisnotsmi(this,
3.                         rhs_known_smi ? Label::kDeferred : Label::kNonDeferred);
4.    Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi);
5.    BIND(&if_lhsissmi);
6.    {
7.      TNode<Smi> lhs_smi = CAST(lhs);
8.      if (!rhs_known_smi) {
9.        Label if_rhsissmi(this), if_rhsisnotsmi(this);
10.        Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);
11.        BIND(&if_rhsisnotsmi);
12.        {
13.          TNode<HeapObject> rhs_heap_object = CAST(rhs);
14.          GotoIfNot(IsHeapNumber(rhs_heap_object), &check_rhsisoddball);
15.          var_fadd_lhs = SmiToFloat64(lhs_smi);
16.          var_fadd_rhs = LoadHeapNumberValue(rhs_heap_object);
17.          Goto(&do_fadd);      }
18.        BIND(&if_rhsissmi);    }
19.      {
20.        TNode<Smi> rhs_smi = CAST(rhs);
21.        Label if_overflow(this,
22.                          rhs_known_smi ? Label::kDeferred : Label::kNonDeferred);
23.        TNode<Smi> smi_result = TrySmiAdd(lhs_smi, rhs_smi, &if_overflow);
24.        {
25.          var_type_feedback = SmiConstant(BinaryOperationFeedback::kSignedSmall);
26.          UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector(),
27.                         slot_id, update_feedback_mode);
28.          var_result = smi_result;
29.          Goto(&end);      }
30.        BIND(&if_overflow);
31.        {
32.          var_fadd_lhs = SmiToFloat64(lhs_smi);
33.          var_fadd_rhs = SmiToFloat64(rhs_smi);
34.          Goto(&do_fadd);
35.        }    }  }
36.    BIND(&if_lhsisnotsmi);
37.    {
38.      TNode<HeapObject> lhs_heap_object = CAST(lhs);
39.      GotoIfNot(IsHeapNumber(lhs_heap_object), &if_lhsisnotnumber);
40.      if (!rhs_known_smi) {
41.        Label if_rhsissmi(this), if_rhsisnotsmi(this);
42.        Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);
43.        BIND(&if_rhsisnotsmi);
44.        {
45.          TNode<HeapObject> rhs_heap_object = CAST(rhs);
46.          GotoIfNot(IsHeapNumber(rhs_heap_object), &check_rhsisoddball);
47.          var_fadd_lhs = LoadHeapNumberValue(lhs_heap_object);
48.          var_fadd_rhs = LoadHeapNumberValue(rhs_heap_object);
49.          Goto(&do_fadd);
50.        }
51.        BIND(&if_rhsissmi);
52.      }
53.      {
54.        var_fadd_lhs = LoadHeapNumberValue(lhs_heap_object);
55.        var_fadd_rhs = SmiToFloat64(CAST(rhs));
56.        Goto(&do_fadd);
57.      }
58.    }
59.    BIND(&do_fadd);
60.    {
61.      var_type_feedback = SmiConstant(BinaryOperationFeedback::kNumber);
62.      UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector(), slot_id,
63.                     update_feedback_mode);
64.      TNode<Float64T> value =
65.          Float64Add(var_fadd_lhs.value(), var_fadd_rhs.value());
66.      TNode<HeapNumber> result = AllocateHeapNumberWithValue(value);
67.      var_result = result;
68.      Goto(&end);
69.    }
70.    BIND(&if_lhsisnotnumber);
71.    {
72.      TNode<Uint16T> lhs_instance_type = LoadInstanceType(CAST(lhs));
73.      TNode<BoolT> lhs_is_oddball =
74.          InstanceTypeEqual(lhs_instance_type, ODDBALL_TYPE);
75.      Branch(lhs_is_oddball, &if_lhsisoddball, &if_lhsisnotoddball);
76.      BIND(&if_lhsisoddball);
77.      {
78.        GotoIf(TaggedIsSmi(rhs), &call_with_oddball_feedback);
79.        Branch(IsHeapNumber(CAST(rhs)), &call_with_oddball_feedback,
80.               &check_rhsisoddball);
81.      }
82.      BIND(&if_lhsisnotoddball);
83.      {
84.        GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback);
85.        TNode<HeapObject> rhs_heap_object = CAST(rhs);
86.        GotoIf(IsStringInstanceType(lhs_instance_type), &lhs_is_string);
87.        GotoIf(IsBigIntInstanceType(lhs_instance_type), &lhs_is_bigint);
88.        Goto(&call_with_any_feedback);
89.        BIND(&lhs_is_bigint);
90.        Branch(IsBigInt(rhs_heap_object), &bigint, &call_with_any_feedback);
91.        BIND(&lhs_is_string);
92.        {
93.          TNode<Uint16T> rhs_instance_type = LoadInstanceType(rhs_heap_object);
94.          GotoIfNot(IsStringInstanceType(rhs_instance_type),
95.                    &call_with_any_feedback);
96.          var_type_feedback = SmiConstant(BinaryOperationFeedback::kString);
97.          UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector(),
98.                         slot_id, update_feedback_mode);
99.          var_result =
100.              CallBuiltin(Builtin::kStringAdd_CheckNone, context(), lhs, rhs);
101.          Goto(&end);
102.        }  } }
103.    BIND(&check_rhsisoddball);
104.    {
105.      TNode<Uint16T> rhs_instance_type = LoadInstanceType(CAST(rhs));
106.      TNode<BoolT> rhs_is_oddball =
107.          InstanceTypeEqual(rhs_instance_type, ODDBALL_TYPE);
108.      GotoIf(rhs_is_oddball, &call_with_oddball_feedback);
109.      Goto(&call_with_any_feedback);
110.    }
111.    BIND(&bigint);
112.    {
113.      var_result = CallBuiltin(Builtin::kBigIntAddNoThrow, context(), lhs, rhs);
114.      GotoIf(TaggedIsSmi(var_result.value()), &bigint_too_big);
115.      var_type_feedback = SmiConstant(BinaryOperationFeedback::kBigInt);
116.      UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector(), slot_id,
117.                     update_feedback_mode);
118.      Goto(&end);
119.      BIND(&bigint_too_big);
120.      {
121.        UpdateFeedback(SmiConstant(BinaryOperationFeedback::kAny),
122.                       maybe_feedback_vector(), slot_id, update_feedback_mode);
123.        ThrowRangeError(context(), MessageTemplate::kBigIntTooBig);
124.      }  }
125.    BIND(&call_with_oddball_feedback);
126.    {
127.      var_type_feedback = SmiConstant(BinaryOperationFeedback::kNumberOrOddball);
128.      Goto(&call_add_stub);  }
129.    BIND(&call_with_any_feedback);
130.    {
131.      var_type_feedback = SmiConstant(BinaryOperationFeedback::kAny);
132.      Goto(&call_add_stub);  }
133.    BIND(&call_add_stub);
134.    {
135.      UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector(), slot_id,
136.                     update_feedback_mode);
137.      var_result = CallBuiltin(Builtin::kAdd, context(), lhs, rhs);
138.      Goto(&end);  }
139.    BIND(&end);
140.    return var_result.value();
141.  }

Part I: the left operand is Smi
The fourth line of code determines whether the left operand is Smi; If it is Smi, enter line 5 code, otherwise enter line 36 code; It is suggested that when we write JS program, the addition operation is most often the addition of two values, so we first judge whether the left and right operands are values.
The code in line 7 converts the left operand to Smi;
The code in line 8-10 determines the right operation type;
Lines 12-17 deal with the case where the right operand is not Smi; The left and right types are inconsistent. Type conversion is required
Lines 13-14 convert the right operand to HeapObject and HeapNumber, convert the left operand (line 15) to HeapNumber and jump to do_fadd tag (lines 59-68) to complete float addition;

Line 18 deals with the case where the right operand is Smi:

Lines 20-23 complete Smi addition; Note that TrySmiAdd() is also responsible for judging whether the result is out of bounds while summing. Smi is 31bit;
Line 26 updates the Feedback, which is used in TurboFan optimization, and will be explained in the next article;
When the addition in lines 30-34 exceeds the limit, use the float addition (line 59);

Part II: the left operation is HeapNumber

The code in lines 38-39 turns the left operand into HeapObject and determines whether it is HeapNumber. Instead, jump to line 70;
The left operand in lines 40-58 is HeapNumber:

Lines 44-49 judge that the right operand is HeapNumber, and jump to line 59 to complete the float addition; Lines 54-56 judge that the right operand is Smi, convert the right operand to HeapNumber and jump to line 59;

Lines 59-67 complete the float addition;

Part 3: the left operation is HeapObject, such as BigInt and string
Lines 72-91 take out the instance of the left and right operands_ Type flag, and further determine the types of the two operations.
In lines 96-100, the left and right operands are both String. Call Builtin::kStringAdd_CheckNone completes String addition;
Lines 91-94 jump to line 131 when the left operand is String and the right operand is not String;
Line 131-135 updates feedback to kAny and calls Builtin::kAdd to complete the addition of "string" and "non string".
Please analyze other situations in the above source code by yourself. Here is a brief explanation of the Builtin:kAdd source code

4 Builtin:kAdd

The source code is written in TQ, and the source code is as follows:

1.  transitioning builtin Add(implicit context: Context)(
2.      leftArg: JSAny, rightArg: JSAny): JSAny {
3.    try {
4.      while (true) {
5.        typeswitch (left) {
6.          case (left: Smi): {
7.  //Omit
8.          }
9.          case (left: HeapNumber): {
10.            typeswitch (right) {
11.  //Omit
12.          }
13.          case (left: BigInt): {
14.  //Omit
15.          }
16.          case (left: String): {
17.            goto StringAddConvertRight(left, right);
18.          }
19.          case (leftReceiver: JSReceiver): {
20.  //Omit
21.          }
22.          case (HeapObject): {
23.  //Omit
24.          }      }    }  }
25.    unreachable;
26.  }

The above code first determines the type of left operand, then determines the type of right operand, and then performs type conversion and calculates the result. Line 17 shows the addition implementation of the left operand is a string and the right operand is not a string, that is, the Builtin method StringAddConvertRight, which is implemented by TQ, and its source code is in builtins string In TQ, the next article explains.

5 technical summary

(1) The addition operation (all operations) starts with the bytecode and enters TurboFan when the hot spot condition is reached;
(2) Generate_ The "Feedback" of addwithfeedback collects the type information of left and right operands for speculative optimization of TurboFan;
(3) Generate_ The addition that addwithfeedback cannot do is completed with builtin::kAdd, and kAdd will further subdivide the functions.

Well, that's all for today. See you next time.
Personal ability is limited, there are deficiencies and mistakes, welcome criticism and correction
Wechat: qq9123013 remarks: v8 communication knowledge: https://www.zhihu.com/people/...

Keywords: Javascript Front-end TypeScript chrome

Added by sheen.andola on Tue, 01 Mar 2022 12:02:00 +0200