| // Copyright 2016 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/ic/binary-op-assembler.h" | |
| #include "src/globals.h" | |
| namespace v8 { | |
| namespace internal { | |
| using compiler::Node; | |
| Node* BinaryOpAssembler::Generate_AddWithFeedback(Node* context, Node* lhs, | |
| Node* rhs, Node* slot_id, | |
| Node* feedback_vector, | |
| bool rhs_is_smi) { | |
| // Shared entry for floating point addition. | |
| Label do_fadd(this), if_lhsisnotnumber(this, Label::kDeferred), | |
| check_rhsisoddball(this, Label::kDeferred), | |
| call_with_oddball_feedback(this), call_with_any_feedback(this), | |
| call_add_stub(this), end(this), bigint(this, Label::kDeferred); | |
| VARIABLE(var_fadd_lhs, MachineRepresentation::kFloat64); | |
| VARIABLE(var_fadd_rhs, MachineRepresentation::kFloat64); | |
| VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned); | |
| VARIABLE(var_result, MachineRepresentation::kTagged); | |
| // Check if the {lhs} is a Smi or a HeapObject. | |
| Label if_lhsissmi(this); | |
| // If rhs is known to be an Smi we want to fast path Smi operation. This is | |
| // for AddSmi operation. For the normal Add operation, we want to fast path | |
| // both Smi and Number operations, so this path should not be marked as | |
| // Deferred. | |
| Label if_lhsisnotsmi(this, | |
| rhs_is_smi ? Label::kDeferred : Label::kNonDeferred); | |
| Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi); | |
| BIND(&if_lhsissmi); | |
| { | |
| Comment("lhs is Smi"); | |
| if (!rhs_is_smi) { | |
| // Check if the {rhs} is also a Smi. | |
| Label if_rhsissmi(this), if_rhsisnotsmi(this); | |
| Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi); | |
| BIND(&if_rhsisnotsmi); | |
| { | |
| // Check if the {rhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball); | |
| var_fadd_lhs.Bind(SmiToFloat64(lhs)); | |
| var_fadd_rhs.Bind(LoadHeapNumberValue(rhs)); | |
| Goto(&do_fadd); | |
| } | |
| BIND(&if_rhsissmi); | |
| } | |
| { | |
| Comment("perform smi operation"); | |
| // If rhs is known to be an Smi we want to fast path Smi operation. This | |
| // is for AddSmi operation. For the normal Add operation, we want to fast | |
| // path both Smi and Number operations, so this path should not be marked | |
| // as Deferred. | |
| Label if_overflow(this, | |
| rhs_is_smi ? Label::kDeferred : Label::kNonDeferred); | |
| TNode<Smi> smi_result = TrySmiAdd(CAST(lhs), CAST(rhs), &if_overflow); | |
| // Not overflowed. | |
| { | |
| var_type_feedback.Bind( | |
| SmiConstant(BinaryOperationFeedback::kSignedSmall)); | |
| var_result.Bind(smi_result); | |
| Goto(&end); | |
| } | |
| BIND(&if_overflow); | |
| { | |
| var_fadd_lhs.Bind(SmiToFloat64(lhs)); | |
| var_fadd_rhs.Bind(SmiToFloat64(rhs)); | |
| Goto(&do_fadd); | |
| } | |
| } | |
| } | |
| BIND(&if_lhsisnotsmi); | |
| { | |
| // Check if {lhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber); | |
| if (!rhs_is_smi) { | |
| // Check if the {rhs} is Smi. | |
| Label if_rhsissmi(this), if_rhsisnotsmi(this); | |
| Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi); | |
| BIND(&if_rhsisnotsmi); | |
| { | |
| // Check if the {rhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball); | |
| var_fadd_lhs.Bind(LoadHeapNumberValue(lhs)); | |
| var_fadd_rhs.Bind(LoadHeapNumberValue(rhs)); | |
| Goto(&do_fadd); | |
| } | |
| BIND(&if_rhsissmi); | |
| } | |
| { | |
| var_fadd_lhs.Bind(LoadHeapNumberValue(lhs)); | |
| var_fadd_rhs.Bind(SmiToFloat64(rhs)); | |
| Goto(&do_fadd); | |
| } | |
| } | |
| BIND(&do_fadd); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber)); | |
| Node* value = Float64Add(var_fadd_lhs.value(), var_fadd_rhs.value()); | |
| Node* result = AllocateHeapNumberWithValue(value); | |
| var_result.Bind(result); | |
| Goto(&end); | |
| } | |
| BIND(&if_lhsisnotnumber); | |
| { | |
| // No checks on rhs are done yet. We just know lhs is not a number or Smi. | |
| Label if_lhsisoddball(this), if_lhsisnotoddball(this); | |
| Node* lhs_instance_type = LoadInstanceType(lhs); | |
| Node* lhs_is_oddball = InstanceTypeEqual(lhs_instance_type, ODDBALL_TYPE); | |
| Branch(lhs_is_oddball, &if_lhsisoddball, &if_lhsisnotoddball); | |
| BIND(&if_lhsisoddball); | |
| { | |
| GotoIf(TaggedIsSmi(rhs), &call_with_oddball_feedback); | |
| // Check if {rhs} is a HeapNumber. | |
| Branch(IsHeapNumber(rhs), &call_with_oddball_feedback, | |
| &check_rhsisoddball); | |
| } | |
| BIND(&if_lhsisnotoddball); | |
| { | |
| Label lhs_is_string(this), lhs_is_bigint(this); | |
| GotoIf(IsStringInstanceType(lhs_instance_type), &lhs_is_string); | |
| GotoIf(IsBigIntInstanceType(lhs_instance_type), &lhs_is_bigint); | |
| Goto(&call_with_any_feedback); | |
| BIND(&lhs_is_bigint); | |
| { | |
| GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback); | |
| Branch(IsBigInt(rhs), &bigint, &call_with_any_feedback); | |
| } | |
| BIND(&lhs_is_string); | |
| // Check if the {rhs} is a smi, and exit the string check early if it is. | |
| GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback); | |
| Node* rhs_instance_type = LoadInstanceType(rhs); | |
| // Exit unless {rhs} is a string. Since {lhs} is a string we no longer | |
| // need an Oddball check. | |
| GotoIfNot(IsStringInstanceType(rhs_instance_type), | |
| &call_with_any_feedback); | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kString)); | |
| Callable callable = | |
| CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); | |
| var_result.Bind(CallStub(callable, context, lhs, rhs)); | |
| Goto(&end); | |
| } | |
| } | |
| BIND(&check_rhsisoddball); | |
| { | |
| // Check if rhs is an oddball. At this point we know lhs is either a | |
| // Smi or number or oddball and rhs is not a number or Smi. | |
| Node* rhs_instance_type = LoadInstanceType(rhs); | |
| Node* rhs_is_oddball = InstanceTypeEqual(rhs_instance_type, ODDBALL_TYPE); | |
| GotoIf(rhs_is_oddball, &call_with_oddball_feedback); | |
| Branch(IsBigIntInstanceType(rhs_instance_type), &bigint, | |
| &call_with_any_feedback); | |
| } | |
| BIND(&bigint); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kBigInt)); | |
| var_result.Bind(CallRuntime(Runtime::kBigIntBinaryOp, context, lhs, rhs, | |
| SmiConstant(Operation::kAdd))); | |
| Goto(&end); | |
| } | |
| BIND(&call_with_oddball_feedback); | |
| { | |
| var_type_feedback.Bind( | |
| SmiConstant(BinaryOperationFeedback::kNumberOrOddball)); | |
| Goto(&call_add_stub); | |
| } | |
| BIND(&call_with_any_feedback); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny)); | |
| Goto(&call_add_stub); | |
| } | |
| BIND(&call_add_stub); | |
| { | |
| var_result.Bind(CallBuiltin(Builtins::kAdd, context, lhs, rhs)); | |
| Goto(&end); | |
| } | |
| BIND(&end); | |
| UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id); | |
| return var_result.value(); | |
| } | |
| Node* BinaryOpAssembler::Generate_BinaryOperationWithFeedback( | |
| Node* context, Node* lhs, Node* rhs, Node* slot_id, Node* feedback_vector, | |
| const SmiOperation& smiOperation, const FloatOperation& floatOperation, | |
| Operation op, bool rhs_is_smi) { | |
| Label do_float_operation(this), end(this), call_stub(this), | |
| check_rhsisoddball(this, Label::kDeferred), call_with_any_feedback(this), | |
| if_lhsisnotnumber(this, Label::kDeferred), | |
| if_bigint(this, Label::kDeferred); | |
| VARIABLE(var_float_lhs, MachineRepresentation::kFloat64); | |
| VARIABLE(var_float_rhs, MachineRepresentation::kFloat64); | |
| VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned); | |
| VARIABLE(var_result, MachineRepresentation::kTagged); | |
| Label if_lhsissmi(this); | |
| // If rhs is known to be an Smi (in the SubSmi, MulSmi, DivSmi, ModSmi | |
| // bytecode handlers) we want to fast path Smi operation. For the normal | |
| // operation, we want to fast path both Smi and Number operations, so this | |
| // path should not be marked as Deferred. | |
| Label if_lhsisnotsmi(this, | |
| rhs_is_smi ? Label::kDeferred : Label::kNonDeferred); | |
| Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi); | |
| // Check if the {lhs} is a Smi or a HeapObject. | |
| BIND(&if_lhsissmi); | |
| { | |
| Comment("lhs is Smi"); | |
| if (!rhs_is_smi) { | |
| // Check if the {rhs} is also a Smi. | |
| Label if_rhsissmi(this), if_rhsisnotsmi(this); | |
| Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi); | |
| BIND(&if_rhsisnotsmi); | |
| { | |
| // Check if {rhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball); | |
| // Perform a floating point operation. | |
| var_float_lhs.Bind(SmiToFloat64(lhs)); | |
| var_float_rhs.Bind(LoadHeapNumberValue(rhs)); | |
| Goto(&do_float_operation); | |
| } | |
| BIND(&if_rhsissmi); | |
| } | |
| { | |
| Comment("perform smi operation"); | |
| var_result.Bind(smiOperation(lhs, rhs, &var_type_feedback)); | |
| Goto(&end); | |
| } | |
| } | |
| BIND(&if_lhsisnotsmi); | |
| { | |
| Comment("lhs is not Smi"); | |
| // Check if the {lhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber); | |
| if (!rhs_is_smi) { | |
| // Check if the {rhs} is a Smi. | |
| Label if_rhsissmi(this), if_rhsisnotsmi(this); | |
| Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi); | |
| BIND(&if_rhsisnotsmi); | |
| { | |
| // Check if the {rhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball); | |
| // Perform a floating point operation. | |
| var_float_lhs.Bind(LoadHeapNumberValue(lhs)); | |
| var_float_rhs.Bind(LoadHeapNumberValue(rhs)); | |
| Goto(&do_float_operation); | |
| } | |
| BIND(&if_rhsissmi); | |
| } | |
| { | |
| // Perform floating point operation. | |
| var_float_lhs.Bind(LoadHeapNumberValue(lhs)); | |
| var_float_rhs.Bind(SmiToFloat64(rhs)); | |
| Goto(&do_float_operation); | |
| } | |
| } | |
| BIND(&do_float_operation); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber)); | |
| Node* lhs_value = var_float_lhs.value(); | |
| Node* rhs_value = var_float_rhs.value(); | |
| Node* value = floatOperation(lhs_value, rhs_value); | |
| var_result.Bind(AllocateHeapNumberWithValue(value)); | |
| Goto(&end); | |
| } | |
| BIND(&if_lhsisnotnumber); | |
| { | |
| // No checks on rhs are done yet. We just know lhs is not a number or Smi. | |
| Label if_left_bigint(this), if_left_oddball(this); | |
| Node* lhs_instance_type = LoadInstanceType(lhs); | |
| GotoIf(IsBigIntInstanceType(lhs_instance_type), &if_left_bigint); | |
| Node* lhs_is_oddball = InstanceTypeEqual(lhs_instance_type, ODDBALL_TYPE); | |
| Branch(lhs_is_oddball, &if_left_oddball, &call_with_any_feedback); | |
| BIND(&if_left_oddball); | |
| { | |
| Label if_rhsissmi(this), if_rhsisnotsmi(this); | |
| Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi); | |
| BIND(&if_rhsissmi); | |
| { | |
| var_type_feedback.Bind( | |
| SmiConstant(BinaryOperationFeedback::kNumberOrOddball)); | |
| Goto(&call_stub); | |
| } | |
| BIND(&if_rhsisnotsmi); | |
| { | |
| // Check if {rhs} is a HeapNumber. | |
| GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball); | |
| var_type_feedback.Bind( | |
| SmiConstant(BinaryOperationFeedback::kNumberOrOddball)); | |
| Goto(&call_stub); | |
| } | |
| } | |
| BIND(&if_left_bigint); | |
| { | |
| GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback); | |
| Branch(IsBigInt(rhs), &if_bigint, &call_with_any_feedback); | |
| } | |
| } | |
| BIND(&check_rhsisoddball); | |
| { | |
| // Check if rhs is an oddball. At this point we know lhs is either a | |
| // Smi or number or oddball and rhs is not a number or Smi. | |
| Node* rhs_instance_type = LoadInstanceType(rhs); | |
| GotoIf(IsBigIntInstanceType(rhs_instance_type), &if_bigint); | |
| Node* rhs_is_oddball = InstanceTypeEqual(rhs_instance_type, ODDBALL_TYPE); | |
| GotoIfNot(rhs_is_oddball, &call_with_any_feedback); | |
| var_type_feedback.Bind( | |
| SmiConstant(BinaryOperationFeedback::kNumberOrOddball)); | |
| Goto(&call_stub); | |
| } | |
| // This handles the case where at least one input is a BigInt. | |
| BIND(&if_bigint); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kBigInt)); | |
| var_result.Bind(CallRuntime(Runtime::kBigIntBinaryOp, context, lhs, rhs, | |
| SmiConstant(op))); | |
| Goto(&end); | |
| } | |
| BIND(&call_with_any_feedback); | |
| { | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny)); | |
| Goto(&call_stub); | |
| } | |
| BIND(&call_stub); | |
| { | |
| Node* result; | |
| switch (op) { | |
| case Operation::kSubtract: | |
| result = CallBuiltin(Builtins::kSubtract, context, lhs, rhs); | |
| break; | |
| case Operation::kMultiply: | |
| result = CallBuiltin(Builtins::kMultiply, context, lhs, rhs); | |
| break; | |
| case Operation::kDivide: | |
| result = CallBuiltin(Builtins::kDivide, context, lhs, rhs); | |
| break; | |
| case Operation::kModulus: | |
| result = CallBuiltin(Builtins::kModulus, context, lhs, rhs); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| var_result.Bind(result); | |
| Goto(&end); | |
| } | |
| BIND(&end); | |
| UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id); | |
| return var_result.value(); | |
| } | |
| Node* BinaryOpAssembler::Generate_SubtractWithFeedback(Node* context, Node* lhs, | |
| Node* rhs, Node* slot_id, | |
| Node* feedback_vector, | |
| bool rhs_is_smi) { | |
| auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) { | |
| Label end(this); | |
| TVARIABLE(Number, var_result); | |
| // If rhs is known to be an Smi (for SubSmi) we want to fast path Smi | |
| // operation. For the normal Sub operation, we want to fast path both | |
| // Smi and Number operations, so this path should not be marked as Deferred. | |
| Label if_overflow(this, | |
| rhs_is_smi ? Label::kDeferred : Label::kNonDeferred); | |
| var_result = TrySmiSub(CAST(lhs), CAST(rhs), &if_overflow); | |
| var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall)); | |
| Goto(&end); | |
| BIND(&if_overflow); | |
| { | |
| var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kNumber)); | |
| Node* value = Float64Sub(SmiToFloat64(lhs), SmiToFloat64(rhs)); | |
| var_result = AllocateHeapNumberWithValue(value); | |
| Goto(&end); | |
| } | |
| BIND(&end); | |
| return var_result.value(); | |
| }; | |
| auto floatFunction = [=](Node* lhs, Node* rhs) { | |
| return Float64Sub(lhs, rhs); | |
| }; | |
| return Generate_BinaryOperationWithFeedback( | |
| context, lhs, rhs, slot_id, feedback_vector, smiFunction, floatFunction, | |
| Operation::kSubtract, rhs_is_smi); | |
| } | |
| Node* BinaryOpAssembler::Generate_MultiplyWithFeedback(Node* context, Node* lhs, | |
| Node* rhs, Node* slot_id, | |
| Node* feedback_vector, | |
| bool rhs_is_smi) { | |
| auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) { | |
| TNode<Number> result = SmiMul(CAST(lhs), CAST(rhs)); | |
| var_type_feedback->Bind(SelectSmiConstant( | |
| TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall, | |
| BinaryOperationFeedback::kNumber)); | |
| return result; | |
| }; | |
| auto floatFunction = [=](Node* lhs, Node* rhs) { | |
| return Float64Mul(lhs, rhs); | |
| }; | |
| return Generate_BinaryOperationWithFeedback( | |
| context, lhs, rhs, slot_id, feedback_vector, smiFunction, floatFunction, | |
| Operation::kMultiply, rhs_is_smi); | |
| } | |
| Node* BinaryOpAssembler::Generate_DivideWithFeedback( | |
| Node* context, Node* dividend, Node* divisor, Node* slot_id, | |
| Node* feedback_vector, bool rhs_is_smi) { | |
| auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) { | |
| VARIABLE(var_result, MachineRepresentation::kTagged); | |
| // If rhs is known to be an Smi (for DivSmi) we want to fast path Smi | |
| // operation. For the normal Div operation, we want to fast path both | |
| // Smi and Number operations, so this path should not be marked as Deferred. | |
| Label bailout(this, rhs_is_smi ? Label::kDeferred : Label::kNonDeferred), | |
| end(this); | |
| var_result.Bind(TrySmiDiv(CAST(lhs), CAST(rhs), &bailout)); | |
| var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall)); | |
| Goto(&end); | |
| BIND(&bailout); | |
| { | |
| var_type_feedback->Bind( | |
| SmiConstant(BinaryOperationFeedback::kSignedSmallInputs)); | |
| Node* value = Float64Div(SmiToFloat64(lhs), SmiToFloat64(rhs)); | |
| var_result.Bind(AllocateHeapNumberWithValue(value)); | |
| Goto(&end); | |
| } | |
| BIND(&end); | |
| return var_result.value(); | |
| }; | |
| auto floatFunction = [=](Node* lhs, Node* rhs) { | |
| return Float64Div(lhs, rhs); | |
| }; | |
| return Generate_BinaryOperationWithFeedback( | |
| context, dividend, divisor, slot_id, feedback_vector, smiFunction, | |
| floatFunction, Operation::kDivide, rhs_is_smi); | |
| } | |
| Node* BinaryOpAssembler::Generate_ModulusWithFeedback( | |
| Node* context, Node* dividend, Node* divisor, Node* slot_id, | |
| Node* feedback_vector, bool rhs_is_smi) { | |
| auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) { | |
| TNode<Number> result = SmiMod(CAST(lhs), CAST(rhs)); | |
| var_type_feedback->Bind(SelectSmiConstant( | |
| TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall, | |
| BinaryOperationFeedback::kNumber)); | |
| return result; | |
| }; | |
| auto floatFunction = [=](Node* lhs, Node* rhs) { | |
| return Float64Mod(lhs, rhs); | |
| }; | |
| return Generate_BinaryOperationWithFeedback( | |
| context, dividend, divisor, slot_id, feedback_vector, smiFunction, | |
| floatFunction, Operation::kModulus, rhs_is_smi); | |
| } | |
| Node* BinaryOpAssembler::Generate_ExponentiateWithFeedback( | |
| Node* context, Node* base, Node* exponent, Node* slot_id, | |
| Node* feedback_vector, bool rhs_is_smi) { | |
| // We currently don't optimize exponentiation based on feedback. | |
| Node* dummy_feedback = SmiConstant(BinaryOperationFeedback::kAny); | |
| UpdateFeedback(dummy_feedback, feedback_vector, slot_id); | |
| return CallBuiltin(Builtins::kExponentiate, context, base, exponent); | |
| } | |
| } // namespace internal | |
| } // namespace v8 |