// Copyright 2014 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/compiler/js-typed-lowering.h" #include "src/ast/modules.h" #include "src/builtins/builtins-utils.h" #include "src/code-factory.h" #include "src/compiler/access-builder.h" #include "src/compiler/allocation-builder.h" #include "src/compiler/js-graph.h" #include "src/compiler/linkage.h" #include "src/compiler/node-matchers.h" #include "src/compiler/node-properties.h" #include "src/compiler/operator-properties.h" #include "src/compiler/type-cache.h" #include "src/compiler/types.h" #include "src/objects-inl.h" #include "src/objects/js-generator.h" #include "src/objects/module-inl.h" namespace v8 { namespace internal { namespace compiler { // A helper class to simplify the process of reducing a single binop node with a // JSOperator. This class manages the rewriting of context, control, and effect // dependencies during lowering of a binop and contains numerous helper // functions for matching the types of inputs to an operation. class JSBinopReduction final { public: JSBinopReduction(JSTypedLowering* lowering, Node* node) : lowering_(lowering), node_(node) {} bool GetCompareNumberOperationHint(NumberOperationHint* hint) { DCHECK_EQ(1, node_->op()->EffectOutputCount()); switch (CompareOperationHintOf(node_->op())) { case CompareOperationHint::kSignedSmall: *hint = NumberOperationHint::kSignedSmall; return true; case CompareOperationHint::kNumber: *hint = NumberOperationHint::kNumber; return true; case CompareOperationHint::kNumberOrOddball: *hint = NumberOperationHint::kNumberOrOddball; return true; case CompareOperationHint::kAny: case CompareOperationHint::kNone: case CompareOperationHint::kString: case CompareOperationHint::kSymbol: case CompareOperationHint::kBigInt: case CompareOperationHint::kReceiver: case CompareOperationHint::kInternalizedString: break; } return false; } bool IsInternalizedStringCompareOperation() { DCHECK_EQ(1, node_->op()->EffectOutputCount()); return (CompareOperationHintOf(node_->op()) == CompareOperationHint::kInternalizedString) && BothInputsMaybe(Type::InternalizedString()); } bool IsReceiverCompareOperation() { DCHECK_EQ(1, node_->op()->EffectOutputCount()); return (CompareOperationHintOf(node_->op()) == CompareOperationHint::kReceiver) && BothInputsMaybe(Type::Receiver()); } bool IsStringCompareOperation() { DCHECK_EQ(1, node_->op()->EffectOutputCount()); return (CompareOperationHintOf(node_->op()) == CompareOperationHint::kString) && BothInputsMaybe(Type::String()); } bool IsSymbolCompareOperation() { DCHECK_EQ(1, node_->op()->EffectOutputCount()); return (CompareOperationHintOf(node_->op()) == CompareOperationHint::kSymbol) && BothInputsMaybe(Type::Symbol()); } // Check if a string addition will definitely result in creating a ConsString, // i.e. if the combined length of the resulting string exceeds the ConsString // minimum length. bool ShouldCreateConsString() { DCHECK_EQ(IrOpcode::kJSAdd, node_->opcode()); DCHECK(OneInputIs(Type::String())); if (BothInputsAre(Type::String()) || BinaryOperationHintOf(node_->op()) == BinaryOperationHint::kString) { HeapObjectBinopMatcher m(node_); JSHeapBroker* broker = lowering_->js_heap_broker(); if (m.right().HasValue() && m.right().Ref(broker).IsString()) { StringRef right_string = m.right().Ref(broker).AsString(); if (right_string.length() >= ConsString::kMinLength) return true; } if (m.left().HasValue() && m.left().Ref(broker).IsString()) { StringRef left_string = m.left().Ref(broker).AsString(); if (left_string.length() >= ConsString::kMinLength) { // The invariant for ConsString requires the left hand side to be // a sequential or external string if the right hand side is the // empty string. Since we don't know anything about the right hand // side here, we must ensure that the left hand side satisfy the // constraints independent of the right hand side. return left_string.IsSeqString() || left_string.IsExternalString(); } } } return false; } // Inserts a CheckReceiver for the left input. void CheckLeftInputToReceiver() { Node* left_input = graph()->NewNode(simplified()->CheckReceiver(), left(), effect(), control()); node_->ReplaceInput(0, left_input); update_effect(left_input); } // Checks that both inputs are Receiver, and if we don't know // statically that one side is already a Receiver, insert a // CheckReceiver node. void CheckInputsToReceiver() { if (!left_type().Is(Type::Receiver())) { CheckLeftInputToReceiver(); } if (!right_type().Is(Type::Receiver())) { Node* right_input = graph()->NewNode(simplified()->CheckReceiver(), right(), effect(), control()); node_->ReplaceInput(1, right_input); update_effect(right_input); } } // Checks that both inputs are Symbol, and if we don't know // statically that one side is already a Symbol, insert a // CheckSymbol node. void CheckInputsToSymbol() { if (!left_type().Is(Type::Symbol())) { Node* left_input = graph()->NewNode(simplified()->CheckSymbol(), left(), effect(), control()); node_->ReplaceInput(0, left_input); update_effect(left_input); } if (!right_type().Is(Type::Symbol())) { Node* right_input = graph()->NewNode(simplified()->CheckSymbol(), right(), effect(), control()); node_->ReplaceInput(1, right_input); update_effect(right_input); } } // Checks that both inputs are String, and if we don't know // statically that one side is already a String, insert a // CheckString node. void CheckInputsToString() { if (!left_type().Is(Type::String())) { Node* left_input = graph()->NewNode(simplified()->CheckString(VectorSlotPair()), left(), effect(), control()); node_->ReplaceInput(0, left_input); update_effect(left_input); } if (!right_type().Is(Type::String())) { Node* right_input = graph()->NewNode(simplified()->CheckString(VectorSlotPair()), right(), effect(), control()); node_->ReplaceInput(1, right_input); update_effect(right_input); } } // Checks that both inputs are InternalizedString, and if we don't know // statically that one side is already an InternalizedString, insert a // CheckInternalizedString node. void CheckInputsToInternalizedString() { if (!left_type().Is(Type::UniqueName())) { Node* left_input = graph()->NewNode( simplified()->CheckInternalizedString(), left(), effect(), control()); node_->ReplaceInput(0, left_input); update_effect(left_input); } if (!right_type().Is(Type::UniqueName())) { Node* right_input = graph()->NewNode(simplified()->CheckInternalizedString(), right(), effect(), control()); node_->ReplaceInput(1, right_input); update_effect(right_input); } } void ConvertInputsToNumber() { DCHECK(left_type().Is(Type::PlainPrimitive())); DCHECK(right_type().Is(Type::PlainPrimitive())); node_->ReplaceInput(0, ConvertPlainPrimitiveToNumber(left())); node_->ReplaceInput(1, ConvertPlainPrimitiveToNumber(right())); } void ConvertInputsToUI32(Signedness left_signedness, Signedness right_signedness) { node_->ReplaceInput(0, ConvertToUI32(left(), left_signedness)); node_->ReplaceInput(1, ConvertToUI32(right(), right_signedness)); } void SwapInputs() { Node* l = left(); Node* r = right(); node_->ReplaceInput(0, r); node_->ReplaceInput(1, l); } // Remove all effect and control inputs and outputs to this node and change // to the pure operator {op}. Reduction ChangeToPureOperator(const Operator* op, Type type = Type::Any()) { DCHECK_EQ(0, op->EffectInputCount()); DCHECK_EQ(false, OperatorProperties::HasContextInput(op)); DCHECK_EQ(0, op->ControlInputCount()); DCHECK_EQ(2, op->ValueInputCount()); // Remove the effects from the node, and update its effect/control usages. if (node_->op()->EffectInputCount() > 0) { lowering_->RelaxEffectsAndControls(node_); } // Remove the inputs corresponding to context, effect, and control. NodeProperties::RemoveNonValueInputs(node_); // Finally, update the operator to the new one. NodeProperties::ChangeOp(node_, op); // TODO(jarin): Replace the explicit typing hack with a call to some method // that encapsulates changing the operator and re-typing. Type node_type = NodeProperties::GetType(node_); NodeProperties::SetType(node_, Type::Intersect(node_type, type, zone())); return lowering_->Changed(node_); } Reduction ChangeToSpeculativeOperator(const Operator* op, Type upper_bound) { DCHECK_EQ(1, op->EffectInputCount()); DCHECK_EQ(1, op->EffectOutputCount()); DCHECK_EQ(false, OperatorProperties::HasContextInput(op)); DCHECK_EQ(1, op->ControlInputCount()); DCHECK_EQ(0, op->ControlOutputCount()); DCHECK_EQ(0, OperatorProperties::GetFrameStateInputCount(op)); DCHECK_EQ(2, op->ValueInputCount()); DCHECK_EQ(1, node_->op()->EffectInputCount()); DCHECK_EQ(1, node_->op()->EffectOutputCount()); DCHECK_EQ(1, node_->op()->ControlInputCount()); DCHECK_EQ(2, node_->op()->ValueInputCount()); // Reconnect the control output to bypass the IfSuccess node and // possibly disconnect from the IfException node. lowering_->RelaxControls(node_); // Remove the frame state and the context. if (OperatorProperties::HasFrameStateInput(node_->op())) { node_->RemoveInput(NodeProperties::FirstFrameStateIndex(node_)); } node_->RemoveInput(NodeProperties::FirstContextIndex(node_)); NodeProperties::ChangeOp(node_, op); // Update the type to number. Type node_type = NodeProperties::GetType(node_); NodeProperties::SetType(node_, Type::Intersect(node_type, upper_bound, zone())); return lowering_->Changed(node_); } const Operator* NumberOp() { switch (node_->opcode()) { case IrOpcode::kJSAdd: return simplified()->NumberAdd(); case IrOpcode::kJSSubtract: return simplified()->NumberSubtract(); case IrOpcode::kJSMultiply: return simplified()->NumberMultiply(); case IrOpcode::kJSDivide: return simplified()->NumberDivide(); case IrOpcode::kJSModulus: return simplified()->NumberModulus(); case IrOpcode::kJSExponentiate: return simplified()->NumberPow(); case IrOpcode::kJSBitwiseAnd: return simplified()->NumberBitwiseAnd(); case IrOpcode::kJSBitwiseOr: return simplified()->NumberBitwiseOr(); case IrOpcode::kJSBitwiseXor: return simplified()->NumberBitwiseXor(); case IrOpcode::kJSShiftLeft: return simplified()->NumberShiftLeft(); case IrOpcode::kJSShiftRight: return simplified()->NumberShiftRight(); case IrOpcode::kJSShiftRightLogical: return simplified()->NumberShiftRightLogical(); default: break; } UNREACHABLE(); } const Operator* NumberOpFromSpeculativeNumberOp() { switch (node_->opcode()) { case IrOpcode::kSpeculativeNumberEqual: return simplified()->NumberEqual(); case IrOpcode::kSpeculativeNumberLessThan: return simplified()->NumberLessThan(); case IrOpcode::kSpeculativeNumberLessThanOrEqual: return simplified()->NumberLessThanOrEqual(); case IrOpcode::kSpeculativeNumberAdd: // Handled by ReduceSpeculativeNumberAdd. UNREACHABLE(); case IrOpcode::kSpeculativeNumberSubtract: return simplified()->NumberSubtract(); case IrOpcode::kSpeculativeNumberMultiply: return simplified()->NumberMultiply(); case IrOpcode::kSpeculativeNumberDivide: return simplified()->NumberDivide(); case IrOpcode::kSpeculativeNumberModulus: return simplified()->NumberModulus(); default: break; } UNREACHABLE(); } bool LeftInputIs(Type t) { return left_type().Is(t); } bool RightInputIs(Type t) { return right_type().Is(t); } bool OneInputIs(Type t) { return LeftInputIs(t) || RightInputIs(t); } bool BothInputsAre(Type t) { return LeftInputIs(t) && RightInputIs(t); } bool BothInputsMaybe(Type t) { return left_type().Maybe(t) && right_type().Maybe(t); } bool OneInputCannotBe(Type t) { return !left_type().Maybe(t) || !right_type().Maybe(t); } bool NeitherInputCanBe(Type t) { return !left_type().Maybe(t) && !right_type().Maybe(t); } Node* effect() { return NodeProperties::GetEffectInput(node_); } Node* control() { return NodeProperties::GetControlInput(node_); } Node* context() { return NodeProperties::GetContextInput(node_); } Node* left() { return NodeProperties::GetValueInput(node_, 0); } Node* right() { return NodeProperties::GetValueInput(node_, 1); } Type left_type() { return NodeProperties::GetType(node_->InputAt(0)); } Type right_type() { return NodeProperties::GetType(node_->InputAt(1)); } Type type() { return NodeProperties::GetType(node_); } SimplifiedOperatorBuilder* simplified() { return lowering_->simplified(); } Graph* graph() const { return lowering_->graph(); } JSGraph* jsgraph() { return lowering_->jsgraph(); } Isolate* isolate() { return jsgraph()->isolate(); } JSOperatorBuilder* javascript() { return lowering_->javascript(); } CommonOperatorBuilder* common() { return jsgraph()->common(); } Zone* zone() const { return graph()->zone(); } private: JSTypedLowering* lowering_; // The containing lowering instance. Node* node_; // The original node. Node* ConvertPlainPrimitiveToNumber(Node* node) { DCHECK(NodeProperties::GetType(node).Is(Type::PlainPrimitive())); // Avoid inserting too many eager ToNumber() operations. Reduction const reduction = lowering_->ReduceJSToNumberOrNumericInput(node); if (reduction.Changed()) return reduction.replacement(); if (NodeProperties::GetType(node).Is(Type::Number())) { return node; } return graph()->NewNode(simplified()->PlainPrimitiveToNumber(), node); } Node* ConvertToUI32(Node* node, Signedness signedness) { // Avoid introducing too many eager NumberToXXnt32() operations. Type type = NodeProperties::GetType(node); if (signedness == kSigned) { if (!type.Is(Type::Signed32())) { node = graph()->NewNode(simplified()->NumberToInt32(), node); } } else { DCHECK_EQ(kUnsigned, signedness); if (!type.Is(Type::Unsigned32())) { node = graph()->NewNode(simplified()->NumberToUint32(), node); } } return node; } void update_effect(Node* effect) { NodeProperties::ReplaceEffectInput(node_, effect); } }; // TODO(turbofan): js-typed-lowering improvements possible // - immediately put in type bounds for all new nodes // - relax effects from generic but not-side-effecting operations JSTypedLowering::JSTypedLowering(Editor* editor, JSGraph* jsgraph, JSHeapBroker* js_heap_broker, Zone* zone) : AdvancedReducer(editor), jsgraph_(jsgraph), js_heap_broker_(js_heap_broker), empty_string_type_(Type::HeapConstant( js_heap_broker, factory()->empty_string(), graph()->zone())), pointer_comparable_type_( Type::Union(Type::Oddball(), Type::Union(Type::SymbolOrReceiver(), empty_string_type_, graph()->zone()), graph()->zone())), type_cache_(TypeCache::Get()) {} Reduction JSTypedLowering::ReduceSpeculativeNumberAdd(Node* node) { JSBinopReduction r(this, node); NumberOperationHint hint = NumberOperationHintOf(node->op()); if ((hint == NumberOperationHint::kNumber || hint == NumberOperationHint::kNumberOrOddball) && r.BothInputsAre(Type::PlainPrimitive()) && r.NeitherInputCanBe(Type::StringOrReceiver())) { // SpeculativeNumberAdd(x:-string, y:-string) => // NumberAdd(ToNumber(x), ToNumber(y)) r.ConvertInputsToNumber(); return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSBitwiseNot(Node* node) { Node* input = NodeProperties::GetValueInput(node, 0); Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::PlainPrimitive())) { // JSBitwiseNot(x) => NumberBitwiseXor(ToInt32(x), -1) node->InsertInput(graph()->zone(), 1, jsgraph()->SmiConstant(-1)); NodeProperties::ChangeOp(node, javascript()->BitwiseXor()); JSBinopReduction r(this, node); r.ConvertInputsToNumber(); r.ConvertInputsToUI32(kSigned, kSigned); return r.ChangeToPureOperator(r.NumberOp(), Type::Signed32()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSDecrement(Node* node) { Node* input = NodeProperties::GetValueInput(node, 0); Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::PlainPrimitive())) { // JSDecrement(x) => NumberSubtract(ToNumber(x), 1) node->InsertInput(graph()->zone(), 1, jsgraph()->OneConstant()); NodeProperties::ChangeOp(node, javascript()->Subtract()); JSBinopReduction r(this, node); r.ConvertInputsToNumber(); DCHECK_EQ(simplified()->NumberSubtract(), r.NumberOp()); return r.ChangeToPureOperator(r.NumberOp(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSIncrement(Node* node) { Node* input = NodeProperties::GetValueInput(node, 0); Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::PlainPrimitive())) { // JSIncrement(x) => NumberAdd(ToNumber(x), 1) node->InsertInput(graph()->zone(), 1, jsgraph()->OneConstant()); BinaryOperationHint hint = BinaryOperationHint::kAny; // Dummy. NodeProperties::ChangeOp(node, javascript()->Add(hint)); JSBinopReduction r(this, node); r.ConvertInputsToNumber(); DCHECK_EQ(simplified()->NumberAdd(), r.NumberOp()); return r.ChangeToPureOperator(r.NumberOp(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSNegate(Node* node) { Node* input = NodeProperties::GetValueInput(node, 0); Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::PlainPrimitive())) { // JSNegate(x) => NumberMultiply(ToNumber(x), -1) node->InsertInput(graph()->zone(), 1, jsgraph()->SmiConstant(-1)); NodeProperties::ChangeOp(node, javascript()->Multiply()); JSBinopReduction r(this, node); r.ConvertInputsToNumber(); return r.ChangeToPureOperator(r.NumberOp(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSAdd(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::Number())) { // JSAdd(x:number, y:number) => NumberAdd(x, y) return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number()); } if (r.BothInputsAre(Type::PlainPrimitive()) && r.NeitherInputCanBe(Type::StringOrReceiver())) { // JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y)) r.ConvertInputsToNumber(); return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number()); } if (BinaryOperationHintOf(node->op()) == BinaryOperationHint::kString) { // Always bake in String feedback into the graph. // TODO(bmeurer): Consider adding a SpeculativeStringAdd operator, // and use that in JSTypeHintLowering instead of looking at the // binary operation feedback here. r.CheckInputsToString(); } if (r.OneInputIs(Type::String())) { // We know that (at least) one input is already a String, // so try to strength-reduce the non-String input. if (r.LeftInputIs(Type::String())) { Reduction const reduction = ReduceJSToStringInput(r.right()); if (reduction.Changed()) { NodeProperties::ReplaceValueInput(node, reduction.replacement(), 1); } } else if (r.RightInputIs(Type::String())) { Reduction const reduction = ReduceJSToStringInput(r.left()); if (reduction.Changed()) { NodeProperties::ReplaceValueInput(node, reduction.replacement(), 0); } } // We might be able to constant-fold the String concatenation now. if (r.BothInputsAre(Type::String())) { HeapObjectBinopMatcher m(node); if (m.IsFoldable()) { StringRef left = m.left().Ref(js_heap_broker()).AsString(); StringRef right = m.right().Ref(js_heap_broker()).AsString(); if (left.length() + right.length() > String::kMaxLength) { // No point in trying to optimize this, as it will just throw. return NoChange(); } // TODO(mslekova): get rid of these allows by doing either one of: // 1. remove the optimization and check if it ruins the performance // 2. leave a placeholder and do the actual allocations once back on the // MT AllowHandleDereference allow_handle_dereference; AllowHandleAllocation allow_handle_allocation; AllowHeapAllocation allow_heap_allocation; ObjectRef cons( js_heap_broker(), factory() ->NewConsString(left.object(), right.object()) .ToHandleChecked()); Node* value = jsgraph()->Constant(cons); ReplaceWithValue(node, value); return Replace(value); } } // We might know for sure that we're creating a ConsString here. if (r.ShouldCreateConsString()) { return ReduceCreateConsString(node); } // Eliminate useless concatenation of empty string. if (r.BothInputsAre(Type::String())) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (r.LeftInputIs(empty_string_type_)) { Node* value = effect = graph()->NewNode(simplified()->CheckString(VectorSlotPair()), r.right(), effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } else if (r.RightInputIs(empty_string_type_)) { Node* value = effect = graph()->NewNode(simplified()->CheckString(VectorSlotPair()), r.left(), effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } } StringAddFlags flags = STRING_ADD_CHECK_NONE; if (!r.LeftInputIs(Type::String())) { flags = STRING_ADD_CONVERT_LEFT; } else if (!r.RightInputIs(Type::String())) { flags = STRING_ADD_CONVERT_RIGHT; } Operator::Properties properties = node->op()->properties(); if (r.NeitherInputCanBe(Type::Receiver())) { // Both sides are already strings, so we know that the // string addition will not cause any observable side // effects; it can still throw obviously. properties = Operator::kNoWrite | Operator::kNoDeopt; } // JSAdd(x:string, y) => CallStub[StringAdd](x, y) // JSAdd(x, y:string) => CallStub[StringAdd](x, y) Callable const callable = CodeFactory::StringAdd(isolate(), flags, NOT_TENURED); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNeedsFrameState, properties); DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op())); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); return Changed(node); } // We never get here when we had String feedback. DCHECK_NE(BinaryOperationHint::kString, BinaryOperationHintOf(node->op())); return NoChange(); } Reduction JSTypedLowering::ReduceNumberBinop(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::PlainPrimitive())) { r.ConvertInputsToNumber(); return r.ChangeToPureOperator(r.NumberOp(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceSpeculativeNumberBinop(Node* node) { JSBinopReduction r(this, node); NumberOperationHint hint = NumberOperationHintOf(node->op()); if ((hint == NumberOperationHint::kNumber || hint == NumberOperationHint::kNumberOrOddball) && r.BothInputsAre(Type::NumberOrUndefinedOrNullOrBoolean())) { // We intentionally do this only in the Number and NumberOrOddball hint case // because simplified lowering of these speculative ops may do some clever // reductions in the other cases. r.ConvertInputsToNumber(); return r.ChangeToPureOperator(r.NumberOpFromSpeculativeNumberOp(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceInt32Binop(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::PlainPrimitive())) { r.ConvertInputsToNumber(); r.ConvertInputsToUI32(kSigned, kSigned); return r.ChangeToPureOperator(r.NumberOp(), Type::Signed32()); } return NoChange(); } Reduction JSTypedLowering::ReduceUI32Shift(Node* node, Signedness signedness) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::PlainPrimitive())) { r.ConvertInputsToNumber(); r.ConvertInputsToUI32(signedness, kUnsigned); return r.ChangeToPureOperator(r.NumberOp(), signedness == kUnsigned ? Type::Unsigned32() : Type::Signed32()); } return NoChange(); } Reduction JSTypedLowering::ReduceCreateConsString(Node* node) { Node* first = NodeProperties::GetValueInput(node, 0); Node* second = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Make sure {first} is actually a String. Type first_type = NodeProperties::GetType(first); if (!first_type.Is(Type::String())) { first = effect = graph()->NewNode( simplified()->CheckString(VectorSlotPair()), first, effect, control); first_type = NodeProperties::GetType(first); } // Make sure {second} is actually a String. Type second_type = NodeProperties::GetType(second); if (!second_type.Is(Type::String())) { second = effect = graph()->NewNode( simplified()->CheckString(VectorSlotPair()), second, effect, control); second_type = NodeProperties::GetType(second); } // Determine the {first} length. Node* first_length = BuildGetStringLength(first); Node* second_length = BuildGetStringLength(second); // Compute the resulting length. Node* length = graph()->NewNode(simplified()->NumberAdd(), first_length, second_length); if (isolate()->IsStringLengthOverflowIntact()) { // We can just deoptimize if the {length} is out-of-bounds. Besides // generating a shorter code sequence than the version below, this // has the additional benefit of not holding on to the lazy {frame_state} // and thus potentially reduces the number of live ranges and allows for // more truncations. length = effect = graph()->NewNode( simplified()->CheckBounds(VectorSlotPair()), length, jsgraph()->Constant(String::kMaxLength), effect, control); } else { // Check if we would overflow the allowed maximum string length. Node* check = graph()->NewNode(simplified()->NumberLessThanOrEqual(), length, jsgraph()->Constant(String::kMaxLength)); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; { // Throw a RangeError in case of overflow. Node* vfalse = efalse = if_false = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowInvalidStringLength), context, frame_state, efalse, if_false); // Update potential {IfException} uses of {node} to point to the // %ThrowInvalidStringLength runtime call node instead. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { NodeProperties::ReplaceControlInput(on_exception, vfalse); NodeProperties::ReplaceEffectInput(on_exception, efalse); if_false = graph()->NewNode(common()->IfSuccess(), vfalse); Revisit(on_exception); } // The above %ThrowInvalidStringLength runtime call is an unconditional // throw, making it impossible to return a successful completion in this // case. We simply connect the successful completion to the graph end. if_false = graph()->NewNode(common()->Throw(), efalse, if_false); // TODO(bmeurer): This should be on the AdvancedReducer somehow. NodeProperties::MergeControlToEnd(graph(), common(), if_false); Revisit(graph()->end()); } control = graph()->NewNode(common()->IfTrue(), branch); length = effect = graph()->NewNode(common()->TypeGuard(type_cache_.kStringLengthType), length, effect, control); } Node* value = graph()->NewNode(simplified()->NewConsString(), length, first, second); ReplaceWithValue(node, value, effect, control); return Replace(value); } Node* JSTypedLowering::BuildGetStringLength(Node* value) { // TODO(bmeurer): Get rid of this hack and instead have a way to // express the string length in the types. HeapObjectMatcher m(value); if (!m.HasValue() || !m.Ref(js_heap_broker()).IsString()) { return graph()->NewNode(simplified()->StringLength(), value); } return jsgraph()->Constant(m.Ref(js_heap_broker()).AsString().length()); } Reduction JSTypedLowering::ReduceSpeculativeNumberComparison(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::Signed32()) || r.BothInputsAre(Type::Unsigned32())) { return r.ChangeToPureOperator(r.NumberOpFromSpeculativeNumberOp()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSComparison(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::String())) { // If both inputs are definitely strings, perform a string comparison. const Operator* stringOp; switch (node->opcode()) { case IrOpcode::kJSLessThan: stringOp = simplified()->StringLessThan(); break; case IrOpcode::kJSGreaterThan: stringOp = simplified()->StringLessThan(); r.SwapInputs(); // a > b => b < a break; case IrOpcode::kJSLessThanOrEqual: stringOp = simplified()->StringLessThanOrEqual(); break; case IrOpcode::kJSGreaterThanOrEqual: stringOp = simplified()->StringLessThanOrEqual(); r.SwapInputs(); // a >= b => b <= a break; default: return NoChange(); } r.ChangeToPureOperator(stringOp); return Changed(node); } const Operator* less_than; const Operator* less_than_or_equal; if (r.BothInputsAre(Type::Signed32()) || r.BothInputsAre(Type::Unsigned32())) { less_than = simplified()->NumberLessThan(); less_than_or_equal = simplified()->NumberLessThanOrEqual(); } else if (r.OneInputCannotBe(Type::StringOrReceiver()) && r.BothInputsAre(Type::PlainPrimitive())) { r.ConvertInputsToNumber(); less_than = simplified()->NumberLessThan(); less_than_or_equal = simplified()->NumberLessThanOrEqual(); } else if (r.IsStringCompareOperation()) { r.CheckInputsToString(); less_than = simplified()->StringLessThan(); less_than_or_equal = simplified()->StringLessThanOrEqual(); } else { return NoChange(); } const Operator* comparison; switch (node->opcode()) { case IrOpcode::kJSLessThan: comparison = less_than; break; case IrOpcode::kJSGreaterThan: comparison = less_than; r.SwapInputs(); // a > b => b < a break; case IrOpcode::kJSLessThanOrEqual: comparison = less_than_or_equal; break; case IrOpcode::kJSGreaterThanOrEqual: comparison = less_than_or_equal; r.SwapInputs(); // a >= b => b <= a break; default: return NoChange(); } return r.ChangeToPureOperator(comparison); } Reduction JSTypedLowering::ReduceJSEqual(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::UniqueName())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.IsInternalizedStringCompareOperation()) { r.CheckInputsToInternalizedString(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.BothInputsAre(Type::String())) { return r.ChangeToPureOperator(simplified()->StringEqual()); } if (r.BothInputsAre(Type::Boolean())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.BothInputsAre(Type::Receiver())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.OneInputIs(Type::Undetectable())) { RelaxEffectsAndControls(node); node->RemoveInput(r.LeftInputIs(Type::Undetectable()) ? 0 : 1); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->ObjectIsUndetectable()); return Changed(node); } if (r.BothInputsAre(Type::Signed32()) || r.BothInputsAre(Type::Unsigned32())) { return r.ChangeToPureOperator(simplified()->NumberEqual()); } else if (r.BothInputsAre(Type::Number())) { return r.ChangeToPureOperator(simplified()->NumberEqual()); } else if (r.IsReceiverCompareOperation()) { r.CheckInputsToReceiver(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } else if (r.IsStringCompareOperation()) { r.CheckInputsToString(); return r.ChangeToPureOperator(simplified()->StringEqual()); } else if (r.IsSymbolCompareOperation()) { r.CheckInputsToSymbol(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSStrictEqual(Node* node) { JSBinopReduction r(this, node); if (r.left() == r.right()) { // x === x is always true if x != NaN Node* replacement = graph()->NewNode( simplified()->BooleanNot(), graph()->NewNode(simplified()->ObjectIsNaN(), r.left())); ReplaceWithValue(node, replacement); return Replace(replacement); } if (r.OneInputCannotBe(Type::NumericOrString())) { // For values with canonical representation (i.e. neither String nor // Numeric) an empty type intersection means the values cannot be strictly // equal. if (!r.left_type().Maybe(r.right_type())) { Node* replacement = jsgraph()->FalseConstant(); ReplaceWithValue(node, replacement); return Replace(replacement); } } if (r.BothInputsAre(Type::Unique())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.OneInputIs(pointer_comparable_type_)) { return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.IsInternalizedStringCompareOperation()) { r.CheckInputsToInternalizedString(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } if (r.BothInputsAre(Type::String())) { return r.ChangeToPureOperator(simplified()->StringEqual()); } NumberOperationHint hint; if (r.BothInputsAre(Type::Signed32()) || r.BothInputsAre(Type::Unsigned32())) { return r.ChangeToPureOperator(simplified()->NumberEqual()); } else if (r.GetCompareNumberOperationHint(&hint)) { return r.ChangeToSpeculativeOperator( simplified()->SpeculativeNumberEqual(hint), Type::Boolean()); } else if (r.BothInputsAre(Type::Number())) { return r.ChangeToPureOperator(simplified()->NumberEqual()); } else if (r.IsReceiverCompareOperation()) { // For strict equality, it's enough to know that one input is a Receiver, // as a strict equality comparison with a Receiver can only yield true if // both sides refer to the same Receiver than. r.CheckLeftInputToReceiver(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } else if (r.IsStringCompareOperation()) { r.CheckInputsToString(); return r.ChangeToPureOperator(simplified()->StringEqual()); } else if (r.IsSymbolCompareOperation()) { r.CheckInputsToSymbol(); return r.ChangeToPureOperator(simplified()->ReferenceEqual()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToInteger(Node* node) { Node* const input = NodeProperties::GetValueInput(node, 0); Type const input_type = NodeProperties::GetType(input); if (input_type.Is(type_cache_.kIntegerOrMinusZero)) { // JSToInteger(x:integer) => x ReplaceWithValue(node, input); return Replace(input); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToName(Node* node) { Node* const input = NodeProperties::GetValueInput(node, 0); Type const input_type = NodeProperties::GetType(input); if (input_type.Is(Type::Name())) { // JSToName(x:name) => x ReplaceWithValue(node, input); return Replace(input); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToLength(Node* node) { Node* input = NodeProperties::GetValueInput(node, 0); Type input_type = NodeProperties::GetType(input); if (input_type.Is(type_cache_.kIntegerOrMinusZero)) { if (input_type.IsNone() || input_type.Max() <= 0.0) { input = jsgraph()->ZeroConstant(); } else if (input_type.Min() >= kMaxSafeInteger) { input = jsgraph()->Constant(kMaxSafeInteger); } else { if (input_type.Min() <= 0.0) { input = graph()->NewNode(simplified()->NumberMax(), jsgraph()->ZeroConstant(), input); } if (input_type.Max() > kMaxSafeInteger) { input = graph()->NewNode(simplified()->NumberMin(), jsgraph()->Constant(kMaxSafeInteger), input); } } ReplaceWithValue(node, input); return Replace(input); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToNumberOrNumericInput(Node* input) { // Try constant-folding of JSToNumber/JSToNumeric with constant inputs. Here // we only cover cases where ToNumber and ToNumeric coincide. Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::String())) { HeapObjectMatcher m(input); if (m.HasValue() && m.Ref(js_heap_broker()).IsString()) { StringRef input_value = m.Ref(js_heap_broker()).AsString(); double number; ASSIGN_RETURN_NO_CHANGE_IF_DATA_MISSING(number, input_value.ToNumber()); return Replace(jsgraph()->Constant(number)); } } if (input_type.IsHeapConstant()) { ObjectRef input_value = input_type.AsHeapConstant()->Ref(); if (input_value.oddball_type() != OddballType::kNone) { return Replace(jsgraph()->Constant(input_value.OddballToNumber())); } } if (input_type.Is(Type::Number())) { // JSToNumber(x:number) => x return Changed(input); } if (input_type.Is(Type::Undefined())) { // JSToNumber(undefined) => #NaN return Replace(jsgraph()->NaNConstant()); } if (input_type.Is(Type::Null())) { // JSToNumber(null) => #0 return Replace(jsgraph()->ZeroConstant()); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToNumberOrNumeric(Node* node) { // Try to reduce the input first. Node* const input = node->InputAt(0); Reduction reduction = ReduceJSToNumberOrNumericInput(input); if (reduction.Changed()) { ReplaceWithValue(node, reduction.replacement()); return reduction; } Type const input_type = NodeProperties::GetType(input); if (input_type.Is(Type::PlainPrimitive())) { RelaxEffectsAndControls(node); node->TrimInputCount(1); // For a PlainPrimitive, ToNumeric is the same as ToNumber. Type node_type = NodeProperties::GetType(node); NodeProperties::SetType( node, Type::Intersect(node_type, Type::Number(), graph()->zone())); NodeProperties::ChangeOp(node, simplified()->PlainPrimitiveToNumber()); return Changed(node); } // TODO(neis): Reduce ToNumeric to ToNumber if input can't be BigInt? return NoChange(); } Reduction JSTypedLowering::ReduceJSToStringInput(Node* input) { if (input->opcode() == IrOpcode::kJSToString) { // Recursively try to reduce the input first. Reduction result = ReduceJSToString(input); if (result.Changed()) return result; return Changed(input); // JSToString(JSToString(x)) => JSToString(x) } Type input_type = NodeProperties::GetType(input); if (input_type.Is(Type::String())) { return Changed(input); // JSToString(x:string) => x } if (input_type.Is(Type::Boolean())) { return Replace(graph()->NewNode( common()->Select(MachineRepresentation::kTagged), input, jsgraph()->HeapConstant(factory()->true_string()), jsgraph()->HeapConstant(factory()->false_string()))); } if (input_type.Is(Type::Undefined())) { return Replace(jsgraph()->HeapConstant(factory()->undefined_string())); } if (input_type.Is(Type::Null())) { return Replace(jsgraph()->HeapConstant(factory()->null_string())); } if (input_type.Is(Type::NaN())) { return Replace(jsgraph()->HeapConstant(factory()->NaN_string())); } if (input_type.Is(Type::OrderedNumber()) && input_type.Min() == input_type.Max()) { // TODO(mslekova): get rid of these allows by doing either one of: // 1. remove the optimization and check if it ruins the performance // 2. allocate all the ToString's from numbers before the compilation // 3. leave a placeholder and do the actual allocations once back on the MT AllowHandleDereference allow_handle_dereference; AllowHandleAllocation allow_handle_allocation; AllowHeapAllocation allow_heap_allocation; // Note that we can use Type::OrderedNumber(), since // both 0 and -0 map to the String "0" in JavaScript. return Replace(jsgraph()->HeapConstant( factory()->NumberToString(factory()->NewNumber(input_type.Min())))); } if (input_type.Is(Type::Number())) { return Replace(graph()->NewNode(simplified()->NumberToString(), input)); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToString(Node* node) { DCHECK_EQ(IrOpcode::kJSToString, node->opcode()); // Try to reduce the input first. Node* const input = node->InputAt(0); Reduction reduction = ReduceJSToStringInput(input); if (reduction.Changed()) { ReplaceWithValue(node, reduction.replacement()); return reduction; } return NoChange(); } Reduction JSTypedLowering::ReduceJSToObject(Node* node) { DCHECK_EQ(IrOpcode::kJSToObject, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 0); Type receiver_type = NodeProperties::GetType(receiver); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (receiver_type.Is(Type::Receiver())) { ReplaceWithValue(node, receiver, effect, control); return Replace(receiver); } // Check whether {receiver} is a spec object. Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), receiver); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* rtrue = receiver; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* rfalse; { // Convert {receiver} using the ToObjectStub. Callable callable = Builtins::CallableFor(isolate(), Builtins::kToObject); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNeedsFrameState, node->op()->properties()); rfalse = efalse = if_false = graph()->NewNode(common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), receiver, context, frame_state, efalse, if_false); } // Update potential {IfException} uses of {node} to point to the above // ToObject stub call node instead. Note that the stub can only throw on // receivers that can be null or undefined. Node* on_exception = nullptr; if (receiver_type.Maybe(Type::NullOrUndefined()) && NodeProperties::IsExceptionalCall(node, &on_exception)) { NodeProperties::ReplaceControlInput(on_exception, if_false); NodeProperties::ReplaceEffectInput(on_exception, efalse); if_false = graph()->NewNode(common()->IfSuccess(), if_false); Revisit(on_exception); } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); // Morph the {node} into an appropriate Phi. ReplaceWithValue(node, node, effect, control); node->ReplaceInput(0, rtrue); node->ReplaceInput(1, rfalse); node->ReplaceInput(2, control); node->TrimInputCount(3); NodeProperties::ChangeOp(node, common()->Phi(MachineRepresentation::kTagged, 2)); return Changed(node); } Reduction JSTypedLowering::ReduceJSLoadNamed(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 0); Type receiver_type = NodeProperties::GetType(receiver); NameRef name(js_heap_broker(), NamedAccessOf(node->op()).name()); NameRef length_str(js_heap_broker(), factory()->length_string()); // Optimize "length" property of strings. if (name.equals(length_str) && receiver_type.Is(Type::String())) { Node* value = graph()->NewNode(simplified()->StringLength(), receiver); ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } Reduction JSTypedLowering::ReduceJSHasInPrototypeChain(Node* node) { DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode()); Node* value = NodeProperties::GetValueInput(node, 0); Type value_type = NodeProperties::GetType(value); Node* prototype = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // If {value} cannot be a receiver, then it cannot have {prototype} in // it's prototype chain (all Primitive values have a null prototype). if (value_type.Is(Type::Primitive())) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value, effect, control); return Replace(value); } Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0 = effect; Node* vtrue0 = jsgraph()->FalseConstant(); control = graph()->NewNode(common()->IfFalse(), branch0); // Loop through the {value}s prototype chain looking for the {prototype}. Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = effect = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* vloop = value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), value, value, loop); NodeProperties::SetType(vloop, Type::NonInternal()); // Load the {value} map and instance type. Node* value_map = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMap()), value, effect, control); Node* value_instance_type = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapInstanceType()), value_map, effect, control); // Check if the {value} is a special receiver, because for special // receivers, i.e. proxies or API values that need access checks, // we have to use the %HasInPrototypeChain runtime function instead. Node* check1 = graph()->NewNode( simplified()->NumberLessThanOrEqual(), value_instance_type, jsgraph()->Constant(LAST_SPECIAL_RECEIVER_TYPE)); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, control); control = graph()->NewNode(common()->IfFalse(), branch1); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* etrue1 = effect; Node* vtrue1; // Check if the {value} is not a receiver at all. Node* check10 = graph()->NewNode(simplified()->NumberLessThan(), value_instance_type, jsgraph()->Constant(FIRST_JS_RECEIVER_TYPE)); Node* branch10 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check10, if_true1); // A primitive value cannot match the {prototype} we're looking for. if_true1 = graph()->NewNode(common()->IfTrue(), branch10); vtrue1 = jsgraph()->FalseConstant(); Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch10); Node* efalse1 = etrue1; Node* vfalse1; { // Slow path, need to call the %HasInPrototypeChain runtime function. vfalse1 = efalse1 = if_false1 = graph()->NewNode( javascript()->CallRuntime(Runtime::kHasInPrototypeChain), value, prototype, context, frame_state, efalse1, if_false1); // Replace any potential {IfException} uses of {node} to catch // exceptions from this %HasInPrototypeChain runtime call instead. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { NodeProperties::ReplaceControlInput(on_exception, vfalse1); NodeProperties::ReplaceEffectInput(on_exception, efalse1); if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1); Revisit(on_exception); } } // Load the {value} prototype. Node* value_prototype = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapPrototype()), value_map, effect, control); // Check if we reached the end of {value}s prototype chain. Node* check2 = graph()->NewNode(simplified()->ReferenceEqual(), value_prototype, jsgraph()->NullConstant()); Node* branch2 = graph()->NewNode(common()->Branch(), check2, control); Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2); Node* etrue2 = effect; Node* vtrue2 = jsgraph()->FalseConstant(); control = graph()->NewNode(common()->IfFalse(), branch2); // Check if we reached the {prototype}. Node* check3 = graph()->NewNode(simplified()->ReferenceEqual(), value_prototype, prototype); Node* branch3 = graph()->NewNode(common()->Branch(), check3, control); Node* if_true3 = graph()->NewNode(common()->IfTrue(), branch3); Node* etrue3 = effect; Node* vtrue3 = jsgraph()->TrueConstant(); control = graph()->NewNode(common()->IfFalse(), branch3); // Close the loop. vloop->ReplaceInput(1, value_prototype); eloop->ReplaceInput(1, effect); loop->ReplaceInput(1, control); control = graph()->NewNode(common()->Merge(5), if_true0, if_true1, if_true2, if_true3, if_false1); effect = graph()->NewNode(common()->EffectPhi(5), etrue0, etrue1, etrue2, etrue3, efalse1, control); // Morph the {node} into an appropriate Phi. ReplaceWithValue(node, node, effect, control); node->ReplaceInput(0, vtrue0); node->ReplaceInput(1, vtrue1); node->ReplaceInput(2, vtrue2); node->ReplaceInput(3, vtrue3); node->ReplaceInput(4, vfalse1); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, common()->Phi(MachineRepresentation::kTagged, 5)); return Changed(node); } Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) { DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode()); Node* constructor = NodeProperties::GetValueInput(node, 0); Type constructor_type = NodeProperties::GetType(constructor); Node* object = NodeProperties::GetValueInput(node, 1); Type object_type = NodeProperties::GetType(object); // Check if the {constructor} cannot be callable. // See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 1. if (!constructor_type.Maybe(Type::Callable())) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } // If the {constructor} cannot be a JSBoundFunction and then {object} // cannot be a JSReceiver, then this can be constant-folded to false. // See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 2 and 3. if (!object_type.Maybe(Type::Receiver()) && !constructor_type.Maybe(Type::BoundFunction())) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } Reduction JSTypedLowering::ReduceJSLoadContext(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode()); ContextAccess const& access = ContextAccessOf(node->op()); Node* effect = NodeProperties::GetEffectInput(node); Node* context = NodeProperties::GetContextInput(node); Node* control = graph()->start(); for (size_t i = 0; i < access.depth(); ++i) { context = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)), context, effect, control); } node->ReplaceInput(0, context); node->ReplaceInput(1, effect); node->AppendInput(jsgraph()->zone(), control); NodeProperties::ChangeOp( node, simplified()->LoadField(AccessBuilder::ForContextSlot(access.index()))); return Changed(node); } Reduction JSTypedLowering::ReduceJSStoreContext(Node* node) { DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode()); ContextAccess const& access = ContextAccessOf(node->op()); Node* effect = NodeProperties::GetEffectInput(node); Node* context = NodeProperties::GetContextInput(node); Node* control = graph()->start(); Node* value = NodeProperties::GetValueInput(node, 0); for (size_t i = 0; i < access.depth(); ++i) { context = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)), context, effect, control); } node->ReplaceInput(0, context); node->ReplaceInput(1, value); node->ReplaceInput(2, effect); NodeProperties::ChangeOp( node, simplified()->StoreField(AccessBuilder::ForContextSlot(access.index()))); return Changed(node); } Node* JSTypedLowering::BuildGetModuleCell(Node* node) { DCHECK(node->opcode() == IrOpcode::kJSLoadModule || node->opcode() == IrOpcode::kJSStoreModule); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); int32_t cell_index = OpParameter(node->op()); Node* module = NodeProperties::GetValueInput(node, 0); Type module_type = NodeProperties::GetType(module); if (module_type.IsHeapConstant()) { ModuleRef module_constant = module_type.AsHeapConstant()->Ref().AsModule(); CellRef cell_constant = module_constant.GetCell(cell_index); return jsgraph()->Constant(cell_constant); } FieldAccess field_access; int index; if (ModuleDescriptor::GetCellIndexKind(cell_index) == ModuleDescriptor::kExport) { field_access = AccessBuilder::ForModuleRegularExports(); index = cell_index - 1; } else { DCHECK_EQ(ModuleDescriptor::GetCellIndexKind(cell_index), ModuleDescriptor::kImport); field_access = AccessBuilder::ForModuleRegularImports(); index = -cell_index - 1; } Node* array = effect = graph()->NewNode(simplified()->LoadField(field_access), module, effect, control); return graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArraySlot(index)), array, effect, control); } Reduction JSTypedLowering::ReduceJSLoadModule(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadModule, node->opcode()); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* cell = BuildGetModuleCell(node); if (cell->op()->EffectOutputCount() > 0) effect = cell; Node* value = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()), cell, effect, control); ReplaceWithValue(node, value, effect, control); return Changed(value); } Reduction JSTypedLowering::ReduceJSStoreModule(Node* node) { DCHECK_EQ(IrOpcode::kJSStoreModule, node->opcode()); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* value = NodeProperties::GetValueInput(node, 1); DCHECK_EQ( ModuleDescriptor::GetCellIndexKind(OpParameter(node->op())), ModuleDescriptor::kExport); Node* cell = BuildGetModuleCell(node); if (cell->op()->EffectOutputCount() > 0) effect = cell; effect = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForCellValue()), cell, value, effect, control); ReplaceWithValue(node, effect, effect, control); return Changed(value); } namespace { void ReduceBuiltin(JSGraph* jsgraph, Node* node, int builtin_index, int arity, CallDescriptor::Flags flags) { // Patch {node} to a direct CEntry call. // // ----------- A r g u m e n t s ----------- // -- 0: CEntry // --- Stack args --- // -- 1: receiver // -- [2, 2 + n[: the n actual arguments passed to the builtin // -- 2 + n: argc, including the receiver and implicit args (Smi) // -- 2 + n + 1: target // -- 2 + n + 2: new_target // --- Register args --- // -- 2 + n + 3: the C entry point // -- 2 + n + 4: argc (Int32) // ----------------------------------- // The logic contained here is mirrored in Builtins::Generate_Adaptor. // Keep these in sync. const bool is_construct = (node->opcode() == IrOpcode::kJSConstruct); DCHECK(Builtins::HasCppImplementation(builtin_index)); Node* target = NodeProperties::GetValueInput(node, 0); Node* new_target = is_construct ? NodeProperties::GetValueInput(node, arity + 1) : jsgraph->UndefinedConstant(); // API and CPP builtins are implemented in C++, and we can inline both. // CPP builtins create a builtin exit frame, API builtins don't. const bool has_builtin_exit_frame = Builtins::IsCpp(builtin_index); Node* stub = jsgraph->CEntryStubConstant(1, kDontSaveFPRegs, kArgvOnStack, has_builtin_exit_frame); node->ReplaceInput(0, stub); Zone* zone = jsgraph->zone(); if (is_construct) { // Unify representations between construct and call nodes. // Remove new target and add receiver as a stack parameter. Node* receiver = jsgraph->UndefinedConstant(); node->RemoveInput(arity + 1); node->InsertInput(zone, 1, receiver); } const int argc = arity + BuiltinArguments::kNumExtraArgsWithReceiver; Node* argc_node = jsgraph->Constant(argc); static const int kStubAndReceiver = 2; int cursor = arity + kStubAndReceiver; node->InsertInput(zone, cursor++, jsgraph->PaddingConstant()); node->InsertInput(zone, cursor++, argc_node); node->InsertInput(zone, cursor++, target); node->InsertInput(zone, cursor++, new_target); Address entry = Builtins::CppEntryOf(builtin_index); ExternalReference entry_ref = ExternalReference::Create(entry); Node* entry_node = jsgraph->ExternalConstant(entry_ref); node->InsertInput(zone, cursor++, entry_node); node->InsertInput(zone, cursor++, argc_node); static const int kReturnCount = 1; const char* debug_name = Builtins::name(builtin_index); Operator::Properties properties = node->op()->properties(); auto call_descriptor = Linkage::GetCEntryStubCallDescriptor( zone, kReturnCount, argc, debug_name, properties, flags); NodeProperties::ChangeOp(node, jsgraph->common()->Call(call_descriptor)); } bool NeedsArgumentAdaptorFrame(SharedFunctionInfoRef shared, int arity) { static const int sentinel = SharedFunctionInfo::kDontAdaptArgumentsSentinel; const int num_decl_parms = shared.internal_formal_parameter_count(); return (num_decl_parms != arity && num_decl_parms != sentinel); } } // namespace Reduction JSTypedLowering::ReduceJSConstructForwardVarargs(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructForwardVarargs, node->opcode()); ConstructForwardVarargsParameters p = ConstructForwardVarargsParametersOf(node->op()); DCHECK_LE(2u, p.arity()); int const arity = static_cast(p.arity() - 2); int const start_index = static_cast(p.start_index()); Node* target = NodeProperties::GetValueInput(node, 0); Type target_type = NodeProperties::GetType(target); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); // Check if {target} is a JSFunction. if (target_type.IsHeapConstant() && target_type.AsHeapConstant()->Ref().IsJSFunction()) { // Only optimize [[Construct]] here if {function} is a Constructor. JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction(); if (!function.IsConstructor()) return NoChange(); // Patch {node} to an indirect call via ConstructFunctionForwardVarargs. Callable callable = CodeFactory::ConstructFunctionForwardVarargs(isolate()); node->RemoveInput(arity + 1); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->InsertInput(graph()->zone(), 2, new_target); node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(arity)); node->InsertInput(graph()->zone(), 4, jsgraph()->Constant(start_index)); node->InsertInput(graph()->zone(), 5, jsgraph()->UndefinedConstant()); NodeProperties::ChangeOp( node, common()->Call(Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), arity + 1, CallDescriptor::kNeedsFrameState))); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSConstruct(Node* node) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); DCHECK_LE(2u, p.arity()); int const arity = static_cast(p.arity() - 2); Node* target = NodeProperties::GetValueInput(node, 0); Type target_type = NodeProperties::GetType(target); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); // Check if {target} is a known JSFunction. if (target_type.IsHeapConstant() && target_type.AsHeapConstant()->Ref().IsJSFunction()) { JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction(); SharedFunctionInfoRef shared = function.shared(); // Only optimize [[Construct]] here if {function} is a Constructor. if (!function.IsConstructor()) return NoChange(); CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; // Patch {node} to an indirect call via the {function}s construct stub. bool use_builtin_construct_stub = shared.construct_as_builtin(); CodeRef code(js_heap_broker(), use_builtin_construct_stub ? BUILTIN_CODE(isolate(), JSBuiltinsConstructStub) : BUILTIN_CODE(isolate(), JSConstructStubGeneric)); node->RemoveInput(arity + 1); node->InsertInput(graph()->zone(), 0, jsgraph()->Constant(code)); node->InsertInput(graph()->zone(), 2, new_target); node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(arity)); node->InsertInput(graph()->zone(), 4, jsgraph()->UndefinedConstant()); node->InsertInput(graph()->zone(), 5, jsgraph()->UndefinedConstant()); NodeProperties::ChangeOp( node, common()->Call(Linkage::GetStubCallDescriptor( graph()->zone(), ConstructStubDescriptor{}, 1 + arity, flags))); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCallForwardVarargs(Node* node) { DCHECK_EQ(IrOpcode::kJSCallForwardVarargs, node->opcode()); CallForwardVarargsParameters p = CallForwardVarargsParametersOf(node->op()); DCHECK_LE(2u, p.arity()); int const arity = static_cast(p.arity() - 2); int const start_index = static_cast(p.start_index()); Node* target = NodeProperties::GetValueInput(node, 0); Type target_type = NodeProperties::GetType(target); // Check if {target} is a JSFunction. if (target_type.Is(Type::Function())) { // Compute flags for the call. CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; // Patch {node} to an indirect call via CallFunctionForwardVarargs. Callable callable = CodeFactory::CallFunctionForwardVarargs(isolate()); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(arity)); node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(start_index)); NodeProperties::ChangeOp( node, common()->Call(Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), arity + 1, flags))); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCall(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int const arity = static_cast(p.arity() - 2); ConvertReceiverMode convert_mode = p.convert_mode(); Node* target = NodeProperties::GetValueInput(node, 0); Type target_type = NodeProperties::GetType(target); Node* receiver = NodeProperties::GetValueInput(node, 1); Type receiver_type = NodeProperties::GetType(receiver); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Try to infer receiver {convert_mode} from {receiver} type. if (receiver_type.Is(Type::NullOrUndefined())) { convert_mode = ConvertReceiverMode::kNullOrUndefined; } else if (!receiver_type.Maybe(Type::NullOrUndefined())) { convert_mode = ConvertReceiverMode::kNotNullOrUndefined; } // Check if {target} is a known JSFunction. if (target_type.IsHeapConstant() && target_type.AsHeapConstant()->Ref().IsJSFunction()) { JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction(); SharedFunctionInfoRef shared = function.shared(); if (shared.HasBreakInfo()) { // Do not inline the call if we need to check whether to break at entry. return NoChange(); } // Class constructors are callable, but [[Call]] will raise an exception. // See ES6 section 9.2.1 [[Call]] ( thisArgument, argumentsList ). if (IsClassConstructor(shared.kind())) return NoChange(); // Load the context from the {target}. Node* context = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, effect, control); NodeProperties::ReplaceContextInput(node, context); // Check if we need to convert the {receiver}. if (is_sloppy(shared.language_mode()) && !shared.native() && !receiver_type.Is(Type::Receiver())) { Node* global_proxy = jsgraph()->Constant(function.global_proxy()); receiver = effect = graph()->NewNode(simplified()->ConvertReceiver(convert_mode), receiver, global_proxy, effect, control); NodeProperties::ReplaceValueInput(node, receiver, 1); } // Update the effect dependency for the {node}. NodeProperties::ReplaceEffectInput(node, effect); // Compute flags for the call. CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; Node* new_target = jsgraph()->UndefinedConstant(); Node* argument_count = jsgraph()->Constant(arity); if (NeedsArgumentAdaptorFrame(shared, arity)) { // Patch {node} to an indirect call via the ArgumentsAdaptorTrampoline. Callable callable = CodeFactory::ArgumentAdaptor(isolate()); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->InsertInput(graph()->zone(), 2, new_target); node->InsertInput(graph()->zone(), 3, argument_count); node->InsertInput( graph()->zone(), 4, jsgraph()->Constant(shared.internal_formal_parameter_count())); NodeProperties::ChangeOp( node, common()->Call(Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), 1 + arity, flags))); } else if (shared.HasBuiltinId() && Builtins::HasCppImplementation(shared.builtin_id())) { // Patch {node} to a direct CEntry call. ReduceBuiltin(jsgraph(), node, shared.builtin_id(), arity, flags); } else if (shared.HasBuiltinId() && Builtins::KindOf(shared.builtin_id()) == Builtins::TFJ) { // Patch {node} to a direct code object call. Callable callable = Builtins::CallableFor( isolate(), static_cast(shared.builtin_id())); CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; const CallInterfaceDescriptor& descriptor = callable.descriptor(); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), descriptor, 1 + arity, flags); Node* stub_code = jsgraph()->HeapConstant(callable.code()); node->InsertInput(graph()->zone(), 0, stub_code); // Code object. node->InsertInput(graph()->zone(), 2, new_target); node->InsertInput(graph()->zone(), 3, argument_count); NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); } else { // Patch {node} to a direct call. node->InsertInput(graph()->zone(), arity + 2, new_target); node->InsertInput(graph()->zone(), arity + 3, argument_count); NodeProperties::ChangeOp(node, common()->Call(Linkage::GetJSCallDescriptor( graph()->zone(), false, 1 + arity, flags))); } return Changed(node); } // Check if {target} is a JSFunction. if (target_type.Is(Type::Function())) { // Compute flags for the call. CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; // Patch {node} to an indirect call via the CallFunction builtin. Callable callable = CodeFactory::CallFunction(isolate(), convert_mode); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(arity)); NodeProperties::ChangeOp( node, common()->Call(Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), 1 + arity, flags))); return Changed(node); } // Maybe we did at least learn something about the {receiver}. if (p.convert_mode() != convert_mode) { NodeProperties::ChangeOp( node, javascript()->Call(p.arity(), p.frequency(), p.feedback(), convert_mode, p.speculation_mode())); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSForInNext(Node* node) { DCHECK_EQ(IrOpcode::kJSForInNext, node->opcode()); ForInMode const mode = ForInModeOf(node->op()); Node* receiver = NodeProperties::GetValueInput(node, 0); Node* cache_array = NodeProperties::GetValueInput(node, 1); Node* cache_type = NodeProperties::GetValueInput(node, 2); Node* index = NodeProperties::GetValueInput(node, 3); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Load the map of the {receiver}. Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); switch (mode) { case ForInMode::kUseEnumCacheKeys: case ForInMode::kUseEnumCacheKeysAndIndices: { // Ensure that the expected map still matches that of the {receiver}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, cache_type); effect = graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap), check, effect, control); // Since the change to LoadElement() below is effectful, we connect // node to all effect uses. ReplaceWithValue(node, node, node, control); // Morph the {node} into a LoadElement. node->ReplaceInput(0, cache_array); node->ReplaceInput(1, index); node->ReplaceInput(2, effect); node->ReplaceInput(3, control); node->TrimInputCount(4); NodeProperties::ChangeOp( node, simplified()->LoadElement(AccessBuilder::ForFixedArrayElement())); NodeProperties::SetType(node, Type::InternalizedString()); break; } case ForInMode::kGeneric: { // Load the next {key} from the {cache_array}. Node* key = effect = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), cache_array, index, effect, control); // Check if the expected map still matches that of the {receiver}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, cache_type); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue; Node* vtrue; { // Don't need filtering since expected map still matches that of the // {receiver}. etrue = effect; vtrue = key; } Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse; Node* vfalse; { // Filter the {key} to check if it's still a valid property of the // {receiver} (does the ToName conversion implicitly). Callable const callable = Builtins::CallableFor(isolate(), Builtins::kForInFilter); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNeedsFrameState); vfalse = efalse = if_false = graph()->NewNode(common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), key, receiver, context, frame_state, effect, if_false); // Update potential {IfException} uses of {node} to point to the above // ForInFilter stub call node instead. Node* if_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &if_exception)) { if_false = graph()->NewNode(common()->IfSuccess(), vfalse); NodeProperties::ReplaceControlInput(if_exception, vfalse); NodeProperties::ReplaceEffectInput(if_exception, efalse); Revisit(if_exception); } } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); ReplaceWithValue(node, node, effect, control); // Morph the {node} into a Phi. node->ReplaceInput(0, vtrue); node->ReplaceInput(1, vfalse); node->ReplaceInput(2, control); node->TrimInputCount(3); NodeProperties::ChangeOp( node, common()->Phi(MachineRepresentation::kTagged, 2)); } } return Changed(node); } Reduction JSTypedLowering::ReduceJSForInPrepare(Node* node) { DCHECK_EQ(IrOpcode::kJSForInPrepare, node->opcode()); ForInMode const mode = ForInModeOf(node->op()); Node* enumerator = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* cache_type = enumerator; Node* cache_array = nullptr; Node* cache_length = nullptr; switch (mode) { case ForInMode::kUseEnumCacheKeys: case ForInMode::kUseEnumCacheKeysAndIndices: { // Check that the {enumerator} is a Map. effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, ZoneHandleSet(factory()->meta_map())), enumerator, effect, control); // Load the enum cache from the {enumerator} map. Node* descriptor_array = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapDescriptors()), enumerator, effect, control); Node* enum_cache = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()), descriptor_array, effect, control); cache_array = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForEnumCacheKeys()), enum_cache, effect, control); // Load the enum length of the {enumerator} map. Node* bit_field3 = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField3()), enumerator, effect, control); STATIC_ASSERT(Map::EnumLengthBits::kShift == 0); cache_length = graph()->NewNode(simplified()->NumberBitwiseAnd(), bit_field3, jsgraph()->Constant(Map::EnumLengthBits::kMask)); break; } case ForInMode::kGeneric: { // Check if the {enumerator} is a Map or a FixedArray. Node* check = effect = graph()->NewNode( simplified()->CompareMaps(ZoneHandleSet(factory()->meta_map())), enumerator, effect, control); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* cache_array_true; Node* cache_length_true; { // Load the enum cache from the {enumerator} map. Node* descriptor_array = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapDescriptors()), enumerator, etrue, if_true); Node* enum_cache = etrue = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForDescriptorArrayEnumCache()), descriptor_array, etrue, if_true); cache_array_true = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForEnumCacheKeys()), enum_cache, etrue, if_true); // Load the enum length of the {enumerator} map. Node* bit_field3 = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField3()), enumerator, etrue, if_true); STATIC_ASSERT(Map::EnumLengthBits::kShift == 0); cache_length_true = graph()->NewNode(simplified()->NumberBitwiseAnd(), bit_field3, jsgraph()->Constant(Map::EnumLengthBits::kMask)); } Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* cache_array_false; Node* cache_length_false; { // The {enumerator} is the FixedArray with the keys to iterate. cache_array_false = enumerator; cache_length_false = efalse = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), cache_array_false, efalse, if_false); } // Rewrite the uses of the {node}. control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); cache_array = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), cache_array_true, cache_array_false, control); cache_length = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), cache_length_true, cache_length_false, control); break; } } // Update the uses of {node}. for (Edge edge : node->use_edges()) { Node* const user = edge.from(); if (NodeProperties::IsEffectEdge(edge)) { edge.UpdateTo(effect); Revisit(user); } else if (NodeProperties::IsControlEdge(edge)) { edge.UpdateTo(control); Revisit(user); } else { DCHECK(NodeProperties::IsValueEdge(edge)); switch (ProjectionIndexOf(user->op())) { case 0: Replace(user, cache_type); break; case 1: Replace(user, cache_array); break; case 2: Replace(user, cache_length); break; default: UNREACHABLE(); } } } node->Kill(); return Replace(effect); } Reduction JSTypedLowering::ReduceJSLoadMessage(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadMessage, node->opcode()); ExternalReference const ref = ExternalReference::address_of_pending_message_obj(isolate()); node->ReplaceInput(0, jsgraph()->ExternalConstant(ref)); NodeProperties::ChangeOp( node, simplified()->LoadField(AccessBuilder::ForExternalTaggedValue())); return Changed(node); } Reduction JSTypedLowering::ReduceJSStoreMessage(Node* node) { DCHECK_EQ(IrOpcode::kJSStoreMessage, node->opcode()); ExternalReference const ref = ExternalReference::address_of_pending_message_obj(isolate()); Node* value = NodeProperties::GetValueInput(node, 0); node->ReplaceInput(0, jsgraph()->ExternalConstant(ref)); node->ReplaceInput(1, value); NodeProperties::ChangeOp( node, simplified()->StoreField(AccessBuilder::ForExternalTaggedValue())); return Changed(node); } Reduction JSTypedLowering::ReduceJSGeneratorStore(Node* node) { DCHECK_EQ(IrOpcode::kJSGeneratorStore, node->opcode()); Node* generator = NodeProperties::GetValueInput(node, 0); Node* continuation = NodeProperties::GetValueInput(node, 1); Node* offset = NodeProperties::GetValueInput(node, 2); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); int value_count = GeneratorStoreValueCountOf(node->op()); FieldAccess array_field = AccessBuilder::ForJSGeneratorObjectParametersAndRegisters(); FieldAccess context_field = AccessBuilder::ForJSGeneratorObjectContext(); FieldAccess continuation_field = AccessBuilder::ForJSGeneratorObjectContinuation(); FieldAccess input_or_debug_pos_field = AccessBuilder::ForJSGeneratorObjectInputOrDebugPos(); Node* array = effect = graph()->NewNode(simplified()->LoadField(array_field), generator, effect, control); for (int i = 0; i < value_count; ++i) { Node* value = NodeProperties::GetValueInput(node, 3 + i); if (value != jsgraph()->OptimizedOutConstant()) { effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForFixedArraySlot(i)), array, value, effect, control); } } effect = graph()->NewNode(simplified()->StoreField(context_field), generator, context, effect, control); effect = graph()->NewNode(simplified()->StoreField(continuation_field), generator, continuation, effect, control); effect = graph()->NewNode(simplified()->StoreField(input_or_debug_pos_field), generator, offset, effect, control); ReplaceWithValue(node, effect, effect, control); return Changed(effect); } Reduction JSTypedLowering::ReduceJSGeneratorRestoreContinuation(Node* node) { DCHECK_EQ(IrOpcode::kJSGeneratorRestoreContinuation, node->opcode()); Node* generator = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); FieldAccess continuation_field = AccessBuilder::ForJSGeneratorObjectContinuation(); Node* continuation = effect = graph()->NewNode( simplified()->LoadField(continuation_field), generator, effect, control); Node* executing = jsgraph()->Constant(JSGeneratorObject::kGeneratorExecuting); effect = graph()->NewNode(simplified()->StoreField(continuation_field), generator, executing, effect, control); ReplaceWithValue(node, continuation, effect, control); return Changed(continuation); } Reduction JSTypedLowering::ReduceJSGeneratorRestoreContext(Node* node) { DCHECK_EQ(IrOpcode::kJSGeneratorRestoreContext, node->opcode()); const Operator* new_op = simplified()->LoadField(AccessBuilder::ForJSGeneratorObjectContext()); // Mutate the node in-place. DCHECK(OperatorProperties::HasContextInput(node->op())); DCHECK(!OperatorProperties::HasContextInput(new_op)); node->RemoveInput(NodeProperties::FirstContextIndex(node)); NodeProperties::ChangeOp(node, new_op); return Changed(node); } Reduction JSTypedLowering::ReduceJSGeneratorRestoreRegister(Node* node) { DCHECK_EQ(IrOpcode::kJSGeneratorRestoreRegister, node->opcode()); Node* generator = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); int index = RestoreRegisterIndexOf(node->op()); FieldAccess array_field = AccessBuilder::ForJSGeneratorObjectParametersAndRegisters(); FieldAccess element_field = AccessBuilder::ForFixedArraySlot(index); Node* array = effect = graph()->NewNode(simplified()->LoadField(array_field), generator, effect, control); Node* element = effect = graph()->NewNode( simplified()->LoadField(element_field), array, effect, control); Node* stale = jsgraph()->StaleRegisterConstant(); effect = graph()->NewNode(simplified()->StoreField(element_field), array, stale, effect, control); ReplaceWithValue(node, element, effect, control); return Changed(element); } Reduction JSTypedLowering::ReduceJSGeneratorRestoreInputOrDebugPos(Node* node) { DCHECK_EQ(IrOpcode::kJSGeneratorRestoreInputOrDebugPos, node->opcode()); FieldAccess input_or_debug_pos_field = AccessBuilder::ForJSGeneratorObjectInputOrDebugPos(); const Operator* new_op = simplified()->LoadField(input_or_debug_pos_field); // Mutate the node in-place. DCHECK(OperatorProperties::HasContextInput(node->op())); DCHECK(!OperatorProperties::HasContextInput(new_op)); node->RemoveInput(NodeProperties::FirstContextIndex(node)); NodeProperties::ChangeOp(node, new_op); return Changed(node); } Reduction JSTypedLowering::ReduceObjectIsArray(Node* node) { Node* value = NodeProperties::GetValueInput(node, 0); Type value_type = NodeProperties::GetType(value); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Constant-fold based on {value} type. if (value_type.Is(Type::Array())) { Node* value = jsgraph()->TrueConstant(); ReplaceWithValue(node, value); return Replace(value); } else if (!value_type.Maybe(Type::ArrayOrProxy())) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } int count = 0; Node* values[5]; Node* effects[5]; Node* controls[4]; // Check if the {value} is a Smi. Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), value); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); // The {value} is a Smi. controls[count] = graph()->NewNode(common()->IfTrue(), control); effects[count] = effect; values[count] = jsgraph()->FalseConstant(); count++; control = graph()->NewNode(common()->IfFalse(), control); // Load the {value}s instance type. Node* value_map = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMap()), value, effect, control); Node* value_instance_type = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapInstanceType()), value_map, effect, control); // Check if the {value} is a JSArray. check = graph()->NewNode(simplified()->NumberEqual(), value_instance_type, jsgraph()->Constant(JS_ARRAY_TYPE)); control = graph()->NewNode(common()->Branch(), check, control); // The {value} is a JSArray. controls[count] = graph()->NewNode(common()->IfTrue(), control); effects[count] = effect; values[count] = jsgraph()->TrueConstant(); count++; control = graph()->NewNode(common()->IfFalse(), control); // Check if the {value} is a JSProxy. check = graph()->NewNode(simplified()->NumberEqual(), value_instance_type, jsgraph()->Constant(JS_PROXY_TYPE)); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); // The {value} is neither a JSArray nor a JSProxy. controls[count] = graph()->NewNode(common()->IfFalse(), control); effects[count] = effect; values[count] = jsgraph()->FalseConstant(); count++; control = graph()->NewNode(common()->IfTrue(), control); // Let the %ArrayIsArray runtime function deal with the JSProxy {value}. value = effect = control = graph()->NewNode(javascript()->CallRuntime(Runtime::kArrayIsArray), value, context, frame_state, effect, control); NodeProperties::SetType(value, Type::Boolean()); // Update potential {IfException} uses of {node} to point to the above // %ArrayIsArray runtime call node instead. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { NodeProperties::ReplaceControlInput(on_exception, control); NodeProperties::ReplaceEffectInput(on_exception, effect); control = graph()->NewNode(common()->IfSuccess(), control); Revisit(on_exception); } // The {value} is neither a JSArray nor a JSProxy. controls[count] = control; effects[count] = effect; values[count] = value; count++; control = graph()->NewNode(common()->Merge(count), count, controls); effects[count] = control; values[count] = control; effect = graph()->NewNode(common()->EffectPhi(count), count + 1, effects); value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, values); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSTypedLowering::ReduceJSParseInt(Node* node) { Node* value = NodeProperties::GetValueInput(node, 0); Type value_type = NodeProperties::GetType(value); Node* radix = NodeProperties::GetValueInput(node, 1); Type radix_type = NodeProperties::GetType(radix); // We need kTenOrUndefined and kZeroOrUndefined because // the type representing {0,10} would become the range 1-10. if (value_type.Is(type_cache_.kSafeInteger) && (radix_type.Is(type_cache_.kTenOrUndefined) || radix_type.Is(type_cache_.kZeroOrUndefined))) { // Number.parseInt(a:safe-integer) -> a // Number.parseInt(a:safe-integer,b:#0\/undefined) -> a // Number.parseInt(a:safe-integer,b:#10\/undefined) -> a ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } Reduction JSTypedLowering::Reduce(Node* node) { DisallowHeapAccess no_heap_access; switch (node->opcode()) { case IrOpcode::kJSEqual: return ReduceJSEqual(node); case IrOpcode::kJSStrictEqual: return ReduceJSStrictEqual(node); case IrOpcode::kJSLessThan: // fall through case IrOpcode::kJSGreaterThan: // fall through case IrOpcode::kJSLessThanOrEqual: // fall through case IrOpcode::kJSGreaterThanOrEqual: return ReduceJSComparison(node); case IrOpcode::kJSBitwiseOr: case IrOpcode::kJSBitwiseXor: case IrOpcode::kJSBitwiseAnd: return ReduceInt32Binop(node); case IrOpcode::kJSShiftLeft: case IrOpcode::kJSShiftRight: return ReduceUI32Shift(node, kSigned); case IrOpcode::kJSShiftRightLogical: return ReduceUI32Shift(node, kUnsigned); case IrOpcode::kJSAdd: return ReduceJSAdd(node); case IrOpcode::kJSSubtract: case IrOpcode::kJSMultiply: case IrOpcode::kJSDivide: case IrOpcode::kJSModulus: case IrOpcode::kJSExponentiate: return ReduceNumberBinop(node); case IrOpcode::kJSBitwiseNot: return ReduceJSBitwiseNot(node); case IrOpcode::kJSDecrement: return ReduceJSDecrement(node); case IrOpcode::kJSIncrement: return ReduceJSIncrement(node); case IrOpcode::kJSNegate: return ReduceJSNegate(node); case IrOpcode::kJSHasInPrototypeChain: return ReduceJSHasInPrototypeChain(node); case IrOpcode::kJSOrdinaryHasInstance: return ReduceJSOrdinaryHasInstance(node); case IrOpcode::kJSToInteger: return ReduceJSToInteger(node); case IrOpcode::kJSToLength: return ReduceJSToLength(node); case IrOpcode::kJSToName: return ReduceJSToName(node); case IrOpcode::kJSToNumber: case IrOpcode::kJSToNumberConvertBigInt: case IrOpcode::kJSToNumeric: return ReduceJSToNumberOrNumeric(node); case IrOpcode::kJSToString: return ReduceJSToString(node); case IrOpcode::kJSToObject: return ReduceJSToObject(node); case IrOpcode::kJSLoadNamed: return ReduceJSLoadNamed(node); case IrOpcode::kJSLoadContext: return ReduceJSLoadContext(node); case IrOpcode::kJSStoreContext: return ReduceJSStoreContext(node); case IrOpcode::kJSLoadModule: return ReduceJSLoadModule(node); case IrOpcode::kJSStoreModule: return ReduceJSStoreModule(node); case IrOpcode::kJSConstructForwardVarargs: return ReduceJSConstructForwardVarargs(node); case IrOpcode::kJSConstruct: return ReduceJSConstruct(node); case IrOpcode::kJSCallForwardVarargs: return ReduceJSCallForwardVarargs(node); case IrOpcode::kJSCall: return ReduceJSCall(node); case IrOpcode::kJSForInPrepare: return ReduceJSForInPrepare(node); case IrOpcode::kJSForInNext: return ReduceJSForInNext(node); case IrOpcode::kJSLoadMessage: return ReduceJSLoadMessage(node); case IrOpcode::kJSStoreMessage: return ReduceJSStoreMessage(node); case IrOpcode::kJSGeneratorStore: return ReduceJSGeneratorStore(node); case IrOpcode::kJSGeneratorRestoreContinuation: return ReduceJSGeneratorRestoreContinuation(node); case IrOpcode::kJSGeneratorRestoreContext: return ReduceJSGeneratorRestoreContext(node); case IrOpcode::kJSGeneratorRestoreRegister: return ReduceJSGeneratorRestoreRegister(node); case IrOpcode::kJSGeneratorRestoreInputOrDebugPos: return ReduceJSGeneratorRestoreInputOrDebugPos(node); // TODO(mstarzinger): Simplified operations hiding in JS-level reducer not // fooling anyone. Consider moving this into a separate reducer. case IrOpcode::kSpeculativeNumberAdd: return ReduceSpeculativeNumberAdd(node); case IrOpcode::kSpeculativeNumberSubtract: case IrOpcode::kSpeculativeNumberMultiply: case IrOpcode::kSpeculativeNumberDivide: case IrOpcode::kSpeculativeNumberModulus: return ReduceSpeculativeNumberBinop(node); case IrOpcode::kSpeculativeNumberEqual: case IrOpcode::kSpeculativeNumberLessThan: case IrOpcode::kSpeculativeNumberLessThanOrEqual: return ReduceSpeculativeNumberComparison(node); case IrOpcode::kJSObjectIsArray: return ReduceObjectIsArray(node); case IrOpcode::kJSParseInt: return ReduceJSParseInt(node); default: break; } return NoChange(); } Factory* JSTypedLowering::factory() const { return jsgraph()->factory(); } Graph* JSTypedLowering::graph() const { return jsgraph()->graph(); } Isolate* JSTypedLowering::isolate() const { return jsgraph()->isolate(); } JSOperatorBuilder* JSTypedLowering::javascript() const { return jsgraph()->javascript(); } CommonOperatorBuilder* JSTypedLowering::common() const { return jsgraph()->common(); } SimplifiedOperatorBuilder* JSTypedLowering::simplified() const { return jsgraph()->simplified(); } } // namespace compiler } // namespace internal } // namespace v8