// 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/code-factory.h" #include "src/compiler/access-builder.h" #include "src/compiler/js-graph.h" #include "src/compiler/js-typed-lowering.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/types.h" namespace v8 { namespace internal { namespace compiler { // 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, Zone* zone) : AdvancedReducer(editor), jsgraph_(jsgraph), simplified_(graph()->zone()) { for (size_t k = 0; k < arraysize(shifted_int32_ranges_); ++k) { double min = kMinInt / (1 << k); double max = kMaxInt / (1 << k); shifted_int32_ranges_[k] = Type::Range(min, max, graph()->zone()); } } // A helper class to construct inline allocations on the simplified operator // level. This keeps track of the effect chain for initial stores on a newly // allocated object and also provides helpers for commonly allocated objects. class AllocationBuilder final { public: AllocationBuilder(JSGraph* jsgraph, SimplifiedOperatorBuilder* simplified, Node* effect, Node* control) : jsgraph_(jsgraph), simplified_(simplified), allocation_(nullptr), effect_(effect), control_(control) {} // Primitive allocation of static size. void Allocate(int size) { allocation_ = graph()->NewNode( simplified()->Allocate(), jsgraph()->Constant(size), effect_, control_); effect_ = allocation_; } // Primitive store into a field. void Store(const FieldAccess& access, Node* value) { effect_ = graph()->NewNode(simplified()->StoreField(access), allocation_, value, effect_, control_); } // Compound allocation of a FixedArray. void AllocateArray(int length, Handle<Map> map) { Allocate(FixedArray::SizeFor(length)); Store(AccessBuilder::ForMap(), map); Store(AccessBuilder::ForFixedArrayLength(), jsgraph()->Constant(length)); } // Compound store of a constant into a field. void Store(const FieldAccess& access, Handle<Object> value) { Store(access, jsgraph()->Constant(value)); } Node* allocation() const { return allocation_; } Node* effect() const { return effect_; } protected: JSGraph* jsgraph() { return jsgraph_; } Graph* graph() { return jsgraph_->graph(); } SimplifiedOperatorBuilder* simplified() { return simplified_; } private: JSGraph* const jsgraph_; SimplifiedOperatorBuilder* simplified_; Node* allocation_; Node* effect_; Node* control_; }; // 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) {} void ConvertInputsToNumber(Node* frame_state) { // To convert the inputs to numbers, we have to provide frame states // for lazy bailouts in the ToNumber conversions. // We use a little hack here: we take the frame state before the binary // operation and use it to construct the frame states for the conversion // so that after the deoptimization, the binary operation IC gets // already converted values from full code. This way we are sure that we // will not re-do any of the side effects. Node* left_input = nullptr; Node* right_input = nullptr; bool left_is_primitive = left_type()->Is(Type::PlainPrimitive()); bool right_is_primitive = right_type()->Is(Type::PlainPrimitive()); bool handles_exception = NodeProperties::IsExceptionalCall(node_); if (!left_is_primitive && !right_is_primitive && handles_exception) { ConvertBothInputsToNumber(&left_input, &right_input, frame_state); } else { left_input = left_is_primitive ? ConvertPlainPrimitiveToNumber(left()) : ConvertSingleInputToNumber( left(), CreateFrameStateForLeftInput(frame_state)); right_input = right_is_primitive ? ConvertPlainPrimitiveToNumber(right()) : ConvertSingleInputToNumber( right(), CreateFrameStateForRightInput( frame_state, left_input)); } node_->ReplaceInput(0, left_input); node_->ReplaceInput(1, right_input); } void ConvertInputsToUI32(Signedness left_signedness, Signedness right_signedness) { node_->ReplaceInput(0, ConvertToUI32(left(), left_signedness)); node_->ReplaceInput(1, ConvertToUI32(right(), right_signedness)); } void ConvertInputsToString() { node_->ReplaceInput(0, ConvertToString(left())); node_->ReplaceInput(1, ConvertToString(right())); } 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}, possibly inserting a boolean inversion. Reduction ChangeToPureOperator(const Operator* op, bool invert = false, 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. node_->set_op(op); // TODO(jarin): Replace the explicit typing hack with a call to some method // that encapsulates changing the operator and re-typing. Bounds const bounds = NodeProperties::GetBounds(node_); NodeProperties::SetBounds(node_, Bounds::NarrowUpper(bounds, type, zone())); if (invert) { // Insert an boolean not to invert the value. Node* value = graph()->NewNode(simplified()->BooleanNot(), node_); node_->ReplaceUses(value); // Note: ReplaceUses() smashes all uses, so smash it back here. value->ReplaceInput(0, node_); return lowering_->Replace(value); } return lowering_->Changed(node_); } Reduction ChangeToPureOperator(const Operator* op, Type* type) { return ChangeToPureOperator(op, false, type); } bool IsStrong() { return is_strong(OpParameter<LanguageMode>(node_)); } bool OneInputIs(Type* t) { return left_type()->Is(t) || right_type()->Is(t); } bool BothInputsAre(Type* t) { return left_type()->Is(t) && right_type()->Is(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::GetBounds(node_->InputAt(0)).upper; } Type* right_type() { return NodeProperties::GetBounds(node_->InputAt(1)).upper; } SimplifiedOperatorBuilder* simplified() { return lowering_->simplified(); } Graph* graph() const { return lowering_->graph(); } JSGraph* jsgraph() { return lowering_->jsgraph(); } JSOperatorBuilder* javascript() { return lowering_->javascript(); } MachineOperatorBuilder* machine() { return lowering_->machine(); } CommonOperatorBuilder* common() { return jsgraph()->common(); } Zone* zone() const { return graph()->zone(); } private: JSTypedLowering* lowering_; // The containing lowering instance. Node* node_; // The original node. Node* ConvertToString(Node* node) { // Avoid introducing too many eager ToString() operations. Reduction reduced = lowering_->ReduceJSToStringInput(node); if (reduced.Changed()) return reduced.replacement(); Node* n = graph()->NewNode(javascript()->ToString(), node, context(), effect(), control()); update_effect(n); return n; } Node* CreateFrameStateForLeftInput(Node* frame_state) { FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); if (state_info.bailout_id() == BailoutId::None()) { // Dummy frame state => just leave it as is. return frame_state; } // If the frame state is already the right one, just return it. if (state_info.state_combine().kind() == OutputFrameStateCombine::kPokeAt && state_info.state_combine().GetOffsetToPokeAt() == 1) { return frame_state; } // Here, we smash the result of the conversion into the slot just below // the stack top. This is the slot that full code uses to store the // left operand. const Operator* op = jsgraph()->common()->FrameState( state_info.bailout_id(), OutputFrameStateCombine::PokeAt(1), state_info.function_info()); return graph()->NewNode(op, frame_state->InputAt(kFrameStateParametersInput), frame_state->InputAt(kFrameStateLocalsInput), frame_state->InputAt(kFrameStateStackInput), frame_state->InputAt(kFrameStateContextInput), frame_state->InputAt(kFrameStateFunctionInput), frame_state->InputAt(kFrameStateOuterStateInput)); } Node* CreateFrameStateForRightInput(Node* frame_state, Node* converted_left) { FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); if (state_info.bailout_id() == BailoutId::None()) { // Dummy frame state => just leave it as is. return frame_state; } // Create a frame state that stores the result of the operation to the // top of the stack (i.e., the slot used for the right operand). const Operator* op = jsgraph()->common()->FrameState( state_info.bailout_id(), OutputFrameStateCombine::PokeAt(0), state_info.function_info()); // Change the left operand {converted_left} on the expression stack. Node* stack = frame_state->InputAt(2); DCHECK_EQ(stack->opcode(), IrOpcode::kStateValues); DCHECK_GE(stack->InputCount(), 2); // TODO(jarin) Allocate in a local zone or a reusable buffer. NodeVector new_values(stack->InputCount(), zone()); for (int i = 0; i < stack->InputCount(); i++) { if (i == stack->InputCount() - 2) { new_values[i] = converted_left; } else { new_values[i] = stack->InputAt(i); } } Node* new_stack = graph()->NewNode(stack->op(), stack->InputCount(), &new_values.front()); return graph()->NewNode( op, frame_state->InputAt(kFrameStateParametersInput), frame_state->InputAt(kFrameStateLocalsInput), new_stack, frame_state->InputAt(kFrameStateContextInput), frame_state->InputAt(kFrameStateFunctionInput), frame_state->InputAt(kFrameStateOuterStateInput)); } Node* ConvertPlainPrimitiveToNumber(Node* node) { DCHECK(NodeProperties::GetBounds(node).upper->Is(Type::PlainPrimitive())); // Avoid inserting too many eager ToNumber() operations. Reduction const reduction = lowering_->ReduceJSToNumberInput(node); if (reduction.Changed()) return reduction.replacement(); // TODO(jarin) Use PlainPrimitiveToNumber once we have it. return graph()->NewNode( javascript()->ToNumber(), node, jsgraph()->NoContextConstant(), jsgraph()->EmptyFrameState(), graph()->start(), graph()->start()); } Node* ConvertSingleInputToNumber(Node* node, Node* frame_state) { DCHECK(!NodeProperties::GetBounds(node).upper->Is(Type::PlainPrimitive())); Node* const n = graph()->NewNode(javascript()->ToNumber(), node, context(), frame_state, effect(), control()); NodeProperties::ReplaceUses(node_, node_, node_, n, n); update_effect(n); return n; } void ConvertBothInputsToNumber(Node** left_result, Node** right_result, Node* frame_state) { Node* projections[2]; // Find {IfSuccess} and {IfException} continuations of the operation. NodeProperties::CollectControlProjections(node_, projections, 2); IfExceptionHint hint = OpParameter<IfExceptionHint>(projections[1]); Node* if_exception = projections[1]; Node* if_success = projections[0]; // Insert two ToNumber() operations that both potentially throw. Node* left_state = CreateFrameStateForLeftInput(frame_state); Node* left_conv = graph()->NewNode(javascript()->ToNumber(), left(), context(), left_state, effect(), control()); Node* left_success = graph()->NewNode(common()->IfSuccess(), left_conv); Node* right_state = CreateFrameStateForRightInput(frame_state, left_conv); Node* right_conv = graph()->NewNode(javascript()->ToNumber(), right(), context(), right_state, left_conv, left_success); Node* left_exception = graph()->NewNode(common()->IfException(hint), left_conv, left_conv); Node* right_exception = graph()->NewNode(common()->IfException(hint), right_conv, right_conv); NodeProperties::ReplaceControlInput(if_success, right_conv); update_effect(right_conv); // Wire conversions to existing {IfException} continuation. Node* exception_merge = if_exception; Node* exception_value = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), left_exception, right_exception, exception_merge); Node* exception_effect = graph()->NewNode(common()->EffectPhi(2), left_exception, right_exception, exception_merge); for (Edge edge : exception_merge->use_edges()) { if (NodeProperties::IsEffectEdge(edge)) edge.UpdateTo(exception_effect); if (NodeProperties::IsValueEdge(edge)) edge.UpdateTo(exception_value); } NodeProperties::RemoveBounds(exception_merge); exception_merge->ReplaceInput(0, left_exception); exception_merge->ReplaceInput(1, right_exception); exception_merge->set_op(common()->Merge(2)); *left_result = left_conv; *right_result = right_conv; } Node* ConvertToUI32(Node* node, Signedness signedness) { // Avoid introducing too many eager NumberToXXnt32() operations. Type* type = NodeProperties::GetBounds(node).upper; 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); } }; 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.NeitherInputCanBe(Type::StringOrReceiver()) && !r.IsStrong()) { // JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y)) Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); r.ConvertInputsToNumber(frame_state); return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number()); } if (r.BothInputsAre(Type::String())) { // JSAdd(x:string, y:string) => CallStub[StringAdd](x, y) Callable const callable = CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED); CallDescriptor const* const desc = Linkage::GetStubCallDescriptor( isolate(), graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNeedsFrameState, node->op()->properties()); DCHECK_EQ(2, OperatorProperties::GetFrameStateInputCount(node->op())); node->RemoveInput(NodeProperties::FirstFrameStateIndex(node) + 1); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->set_op(common()->Call(desc)); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSModulus(Node* node) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::Number())) { // JSModulus(x:number, x:number) => NumberModulus(x, y) return r.ChangeToPureOperator(simplified()->NumberModulus(), Type::Number()); } return NoChange(); } Reduction JSTypedLowering::ReduceNumberBinop(Node* node, const Operator* numberOp) { JSBinopReduction r(this, node); if (r.IsStrong() || numberOp == simplified()->NumberModulus()) { if (r.BothInputsAre(Type::Number())) { return r.ChangeToPureOperator(numberOp, Type::Number()); } return NoChange(); } Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); r.ConvertInputsToNumber(frame_state); return r.ChangeToPureOperator(numberOp, Type::Number()); } Reduction JSTypedLowering::ReduceInt32Binop(Node* node, const Operator* intOp) { JSBinopReduction r(this, node); if (r.IsStrong()) { if (r.BothInputsAre(Type::Number())) { r.ConvertInputsToUI32(kSigned, kSigned); return r.ChangeToPureOperator(intOp, Type::Integral32()); } return NoChange(); } Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); r.ConvertInputsToNumber(frame_state); r.ConvertInputsToUI32(kSigned, kSigned); return r.ChangeToPureOperator(intOp, Type::Integral32()); } Reduction JSTypedLowering::ReduceUI32Shift(Node* node, Signedness left_signedness, const Operator* shift_op) { JSBinopReduction r(this, node); if (r.IsStrong()) { if (r.BothInputsAre(Type::Number())) { r.ConvertInputsToUI32(left_signedness, kUnsigned); return r.ChangeToPureOperator(shift_op); } return NoChange(); } Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); r.ConvertInputsToNumber(frame_state); r.ConvertInputsToUI32(left_signedness, kUnsigned); return r.ChangeToPureOperator(shift_op); } 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(); } return r.ChangeToPureOperator(stringOp); } if (r.OneInputCannotBe(Type::StringOrReceiver())) { const Operator* less_than; const Operator* less_than_or_equal; if (r.BothInputsAre(Type::Unsigned32())) { less_than = machine()->Uint32LessThan(); less_than_or_equal = machine()->Uint32LessThanOrEqual(); } else if (r.BothInputsAre(Type::Signed32())) { less_than = machine()->Int32LessThan(); less_than_or_equal = machine()->Int32LessThanOrEqual(); } else { // TODO(turbofan): mixed signed/unsigned int32 comparisons. if (r.IsStrong() && !r.BothInputsAre(Type::Number())) { return NoChange(); } Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); r.ConvertInputsToNumber(frame_state); less_than = simplified()->NumberLessThan(); less_than_or_equal = simplified()->NumberLessThanOrEqual(); } 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); } // TODO(turbofan): relax/remove effects of this operator in other cases. return NoChange(); // Keep a generic comparison. } Reduction JSTypedLowering::ReduceJSEqual(Node* node, bool invert) { JSBinopReduction r(this, node); if (r.BothInputsAre(Type::Number())) { return r.ChangeToPureOperator(simplified()->NumberEqual(), invert); } if (r.BothInputsAre(Type::String())) { return r.ChangeToPureOperator(simplified()->StringEqual(), invert); } if (r.BothInputsAre(Type::Receiver())) { return r.ChangeToPureOperator( simplified()->ReferenceEqual(Type::Receiver()), invert); } // TODO(turbofan): js-typed-lowering of Equal(undefined) // TODO(turbofan): js-typed-lowering of Equal(null) // TODO(turbofan): js-typed-lowering of Equal(boolean) return NoChange(); } Reduction JSTypedLowering::ReduceJSStrictEqual(Node* node, bool invert) { JSBinopReduction r(this, node); if (r.left() == r.right()) { // x === x is always true if x != NaN if (!r.left_type()->Maybe(Type::NaN())) { Node* replacement = jsgraph()->BooleanConstant(!invert); Replace(node, replacement); return Replace(replacement); } } if (r.OneInputCannotBe(Type::NumberOrString())) { // For values with canonical representation (i.e. not string nor number) an // empty type intersection means the values cannot be strictly equal. if (!r.left_type()->Maybe(r.right_type())) { Node* replacement = jsgraph()->BooleanConstant(invert); Replace(node, replacement); return Replace(replacement); } } if (r.OneInputIs(Type::Undefined())) { return r.ChangeToPureOperator( simplified()->ReferenceEqual(Type::Undefined()), invert); } if (r.OneInputIs(Type::Null())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual(Type::Null()), invert); } if (r.OneInputIs(Type::Boolean())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual(Type::Boolean()), invert); } if (r.OneInputIs(Type::Object())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual(Type::Object()), invert); } if (r.OneInputIs(Type::Receiver())) { return r.ChangeToPureOperator( simplified()->ReferenceEqual(Type::Receiver()), invert); } if (r.BothInputsAre(Type::Unique())) { return r.ChangeToPureOperator(simplified()->ReferenceEqual(Type::Unique()), invert); } if (r.BothInputsAre(Type::String())) { return r.ChangeToPureOperator(simplified()->StringEqual(), invert); } if (r.BothInputsAre(Type::Number())) { return r.ChangeToPureOperator(simplified()->NumberEqual(), invert); } // TODO(turbofan): js-typed-lowering of StrictEqual(mixed types) return NoChange(); } Reduction JSTypedLowering::ReduceJSUnaryNot(Node* node) { Node* const input = node->InputAt(0); Type* const input_type = NodeProperties::GetBounds(input).upper; if (input_type->Is(Type::Boolean())) { // JSUnaryNot(x:boolean) => BooleanNot(x) node->set_op(simplified()->BooleanNot()); node->TrimInputCount(1); return Changed(node); } else if (input_type->Is(Type::OrderedNumber())) { // JSUnaryNot(x:number) => NumberEqual(x,#0) node->set_op(simplified()->NumberEqual()); node->ReplaceInput(1, jsgraph()->ZeroConstant()); DCHECK_EQ(2, node->InputCount()); return Changed(node); } else if (input_type->Is(Type::String())) { // JSUnaryNot(x:string) => NumberEqual(x.length,#0) FieldAccess const access = AccessBuilder::ForStringLength(graph()->zone()); // It is safe for the load to be effect-free (i.e. not linked into effect // chain) because we assume String::length to be immutable. Node* length = graph()->NewNode(simplified()->LoadField(access), input, graph()->start(), graph()->start()); node->set_op(simplified()->NumberEqual()); node->ReplaceInput(0, length); node->ReplaceInput(1, jsgraph()->ZeroConstant()); ReplaceWithValue(node, node, length); DCHECK_EQ(2, node->InputCount()); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToBoolean(Node* node) { Node* const input = node->InputAt(0); Type* const input_type = NodeProperties::GetBounds(input).upper; if (input_type->Is(Type::Boolean())) { // JSToBoolean(x:boolean) => x return Replace(input); } else if (input_type->Is(Type::OrderedNumber())) { // JSToBoolean(x:ordered-number) => BooleanNot(NumberEqual(x,#0)) node->set_op(simplified()->BooleanNot()); node->ReplaceInput(0, graph()->NewNode(simplified()->NumberEqual(), input, jsgraph()->ZeroConstant())); node->TrimInputCount(1); return Changed(node); } else if (input_type->Is(Type::String())) { // JSToBoolean(x:string) => NumberLessThan(#0,x.length) FieldAccess const access = AccessBuilder::ForStringLength(graph()->zone()); // It is safe for the load to be effect-free (i.e. not linked into effect // chain) because we assume String::length to be immutable. Node* length = graph()->NewNode(simplified()->LoadField(access), input, graph()->start(), graph()->start()); node->set_op(simplified()->NumberLessThan()); node->ReplaceInput(0, jsgraph()->ZeroConstant()); node->ReplaceInput(1, length); DCHECK_EQ(2, node->InputCount()); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSToNumberInput(Node* input) { if (input->opcode() == IrOpcode::kJSToNumber) { // Recursively try to reduce the input first. Reduction result = ReduceJSToNumber(input); if (result.Changed()) return result; return Changed(input); // JSToNumber(JSToNumber(x)) => JSToNumber(x) } // Check if we have a cached conversion. Type* input_type = NodeProperties::GetBounds(input).upper; 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()); } if (input_type->Is(Type::Boolean())) { // JSToNumber(x:boolean) => BooleanToNumber(x) return Replace(graph()->NewNode(simplified()->BooleanToNumber(), input)); } // TODO(turbofan): js-typed-lowering of ToNumber(x:string) return NoChange(); } Reduction JSTypedLowering::ReduceJSToNumber(Node* node) { // Try to reduce the input first. Node* const input = node->InputAt(0); Reduction reduction = ReduceJSToNumberInput(input); if (reduction.Changed()) { ReplaceWithValue(node, reduction.replacement()); return reduction; } Type* const input_type = NodeProperties::GetBounds(input).upper; if (input_type->Is(Type::PlainPrimitive())) { if (NodeProperties::GetContextInput(node) != jsgraph()->NoContextConstant() || NodeProperties::GetEffectInput(node) != graph()->start() || NodeProperties::GetControlInput(node) != graph()->start()) { // JSToNumber(x:plain-primitive,context,effect,control) // => JSToNumber(x,no-context,start,start) RelaxEffectsAndControls(node); NodeProperties::ReplaceContextInput(node, jsgraph()->NoContextConstant()); NodeProperties::ReplaceControlInput(node, graph()->start()); NodeProperties::ReplaceEffectInput(node, graph()->start()); DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op())); NodeProperties::ReplaceFrameStateInput(node, 0, jsgraph()->EmptyFrameState()); return Changed(node); } } 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::GetBounds(input).upper; if (input_type->Is(Type::String())) { return Changed(input); // JSToString(x:string) => x } 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())); } // TODO(turbofan): js-typed-lowering of ToString(x:boolean) // TODO(turbofan): js-typed-lowering of ToString(x:number) return NoChange(); } Reduction JSTypedLowering::ReduceJSToString(Node* node) { // 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::ReduceJSLoadGlobal(Node* node) { // Optimize global constants like "undefined", "Infinity", and "NaN". Handle<Name> name = LoadGlobalParametersOf(node->op()).name().handle(); Handle<Object> constant_value = factory()->GlobalConstantFor(name); if (!constant_value.is_null()) { Node* constant = jsgraph()->Constant(constant_value); ReplaceWithValue(node, constant); return Replace(constant); } return NoChange(); } Reduction JSTypedLowering::ReduceJSLoadNamed(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 0); Type* receiver_type = NodeProperties::GetBounds(receiver).upper; Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Handle<Name> name = LoadNamedParametersOf(node->op()).name().handle(); // Optimize "length" property of strings. if (name.is_identical_to(factory()->length_string()) && receiver_type->Is(Type::String())) { Node* value = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForStringLength(graph()->zone())), receiver, effect, control); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } Reduction JSTypedLowering::ReduceJSLoadProperty(Node* node) { Node* key = NodeProperties::GetValueInput(node, 1); Node* base = NodeProperties::GetValueInput(node, 0); Type* key_type = NodeProperties::GetBounds(key).upper; HeapObjectMatcher mbase(base); if (mbase.HasValue() && mbase.Value().handle()->IsJSTypedArray()) { Handle<JSTypedArray> const array = Handle<JSTypedArray>::cast(mbase.Value().handle()); if (!array->GetBuffer()->was_neutered()) { array->GetBuffer()->set_is_neuterable(false); BufferAccess const access(array->type()); size_t const k = ElementSizeLog2Of(access.machine_type()); double const byte_length = array->byte_length()->Number(); CHECK_LT(k, arraysize(shifted_int32_ranges_)); if (key_type->Is(shifted_int32_ranges_[k]) && byte_length <= kMaxInt) { // JSLoadProperty(typed-array, int32) Handle<FixedTypedArrayBase> elements = Handle<FixedTypedArrayBase>::cast(handle(array->elements())); Node* buffer = jsgraph()->PointerConstant(elements->external_pointer()); Node* length = jsgraph()->Constant(byte_length); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check if we can avoid the bounds check. if (key_type->Min() >= 0 && key_type->Max() < array->length_value()) { Node* load = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForTypedArrayElement(array->type(), true)), buffer, key, effect, control); ReplaceWithValue(node, load, load); return Replace(load); } // Compute byte offset. Node* offset = Word32Shl(key, static_cast<int>(k)); Node* load = graph()->NewNode(simplified()->LoadBuffer(access), buffer, offset, length, effect, control); ReplaceWithValue(node, load, load); return Replace(load); } } } return NoChange(); } Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) { Node* key = NodeProperties::GetValueInput(node, 1); Node* base = NodeProperties::GetValueInput(node, 0); Node* value = NodeProperties::GetValueInput(node, 2); Type* key_type = NodeProperties::GetBounds(key).upper; Type* value_type = NodeProperties::GetBounds(value).upper; HeapObjectMatcher mbase(base); if (mbase.HasValue() && mbase.Value().handle()->IsJSTypedArray()) { Handle<JSTypedArray> const array = Handle<JSTypedArray>::cast(mbase.Value().handle()); if (!array->GetBuffer()->was_neutered()) { array->GetBuffer()->set_is_neuterable(false); BufferAccess const access(array->type()); size_t const k = ElementSizeLog2Of(access.machine_type()); double const byte_length = array->byte_length()->Number(); CHECK_LT(k, arraysize(shifted_int32_ranges_)); if (access.external_array_type() != kExternalUint8ClampedArray && key_type->Is(shifted_int32_ranges_[k]) && byte_length <= kMaxInt) { // JSLoadProperty(typed-array, int32) Handle<FixedTypedArrayBase> elements = Handle<FixedTypedArrayBase>::cast(handle(array->elements())); Node* buffer = jsgraph()->PointerConstant(elements->external_pointer()); Node* length = jsgraph()->Constant(byte_length); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Convert to a number first. if (!value_type->Is(Type::Number())) { Reduction number_reduction = ReduceJSToNumberInput(value); if (number_reduction.Changed()) { value = number_reduction.replacement(); } else { Node* frame_state_for_to_number = NodeProperties::GetFrameStateInput(node, 1); value = effect = graph()->NewNode(javascript()->ToNumber(), value, context, frame_state_for_to_number, effect, control); } } // For integer-typed arrays, convert to the integer type. if (TypeOf(access.machine_type()) == kTypeInt32 && !value_type->Is(Type::Signed32())) { value = graph()->NewNode(simplified()->NumberToInt32(), value); } else if (TypeOf(access.machine_type()) == kTypeUint32 && !value_type->Is(Type::Unsigned32())) { value = graph()->NewNode(simplified()->NumberToUint32(), value); } // Check if we can avoid the bounds check. if (key_type->Min() >= 0 && key_type->Max() < array->length_value()) { node->set_op(simplified()->StoreElement( AccessBuilder::ForTypedArrayElement(array->type(), true))); node->ReplaceInput(0, buffer); DCHECK_EQ(key, node->InputAt(1)); node->ReplaceInput(2, value); node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); RelaxControls(node); return Changed(node); } // Compute byte offset. Node* offset = Word32Shl(key, static_cast<int>(k)); // Turn into a StoreBuffer operation. node->set_op(simplified()->StoreBuffer(access)); node->ReplaceInput(0, buffer); node->ReplaceInput(1, offset); node->ReplaceInput(2, length); node->ReplaceInput(3, value); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); RelaxControls(node); return Changed(node); } } } return NoChange(); } Reduction JSTypedLowering::ReduceJSLoadContext(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode()); ContextAccess const& access = ContextAccessOf(node->op()); Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = graph()->start(); for (size_t i = 0; i < access.depth(); ++i) { node->ReplaceInput( 0, graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)), NodeProperties::GetValueInput(node, 0), effect, control)); } node->set_op( simplified()->LoadField(AccessBuilder::ForContextSlot(access.index()))); node->ReplaceInput(1, effect); node->ReplaceInput(2, control); DCHECK_EQ(3, node->InputCount()); return Changed(node); } Reduction JSTypedLowering::ReduceJSStoreContext(Node* node) { DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode()); ContextAccess const& access = ContextAccessOf(node->op()); Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = graph()->start(); for (size_t i = 0; i < access.depth(); ++i) { node->ReplaceInput( 0, graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)), NodeProperties::GetValueInput(node, 0), effect, control)); } node->set_op( simplified()->StoreField(AccessBuilder::ForContextSlot(access.index()))); node->RemoveInput(2); DCHECK_EQ(4, node->InputCount()); return Changed(node); } Reduction JSTypedLowering::ReduceJSLoadDynamicGlobal(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadDynamicGlobal, node->opcode()); DynamicGlobalAccess const& access = DynamicGlobalAccessOf(node->op()); Node* const vector = NodeProperties::GetValueInput(node, 0); Node* const context = NodeProperties::GetContextInput(node); Node* const state1 = NodeProperties::GetFrameStateInput(node, 0); Node* const state2 = NodeProperties::GetFrameStateInput(node, 1); Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = NodeProperties::GetControlInput(node); if (access.RequiresFullCheck()) return NoChange(); // Perform checks whether the fast mode applies, by looking for any extension // object which might shadow the optimistic declaration. uint32_t bitset = access.check_bitset(); Node* check_true = control; Node* check_false = graph()->NewNode(common()->Merge(0)); for (int depth = 0; bitset != 0; bitset >>= 1, depth++) { if ((bitset & 1) == 0) continue; Node* load = graph()->NewNode( javascript()->LoadContext(depth, Context::EXTENSION_INDEX, false), context, context, effect); Node* check = graph()->NewNode(simplified()->ReferenceEqual(Type::Tagged()), load, jsgraph()->ZeroConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, check_true); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); check_false->set_op(common()->Merge(check_false->InputCount() + 1)); check_false->AppendInput(graph()->zone(), if_false); check_true = if_true; } // Fast case, because variable is not shadowed. Perform global object load. Unique<Name> name = Unique<Name>::CreateUninitialized(access.name()); Node* global = graph()->NewNode( javascript()->LoadContext(0, Context::GLOBAL_OBJECT_INDEX, true), context, context, effect); Node* fast = graph()->NewNode( javascript()->LoadGlobal(name, access.feedback(), access.typeof_mode()), context, global, vector, context, state1, state2, global, check_true); // Slow case, because variable potentially shadowed. Perform dynamic lookup. uint32_t check_bitset = DynamicGlobalAccess::kFullCheckRequired; Node* slow = graph()->NewNode( javascript()->LoadDynamicGlobal(access.name(), check_bitset, access.feedback(), access.typeof_mode()), vector, context, context, state1, state2, effect, check_false); // Replace value, effect and control uses accordingly. Node* new_control = graph()->NewNode(common()->Merge(2), check_true, check_false); Node* new_effect = graph()->NewNode(common()->EffectPhi(2), fast, slow, new_control); Node* new_value = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), fast, slow, new_control); ReplaceWithValue(node, new_value, new_effect, new_control); return Changed(new_value); } Reduction JSTypedLowering::ReduceJSLoadDynamicContext(Node* node) { DCHECK_EQ(IrOpcode::kJSLoadDynamicContext, node->opcode()); DynamicContextAccess const& access = DynamicContextAccessOf(node->op()); ContextAccess const& context_access = access.context_access(); Node* const context = NodeProperties::GetContextInput(node); Node* const state = NodeProperties::GetFrameStateInput(node, 0); Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = NodeProperties::GetControlInput(node); if (access.RequiresFullCheck()) return NoChange(); // Perform checks whether the fast mode applies, by looking for any extension // object which might shadow the optimistic declaration. uint32_t bitset = access.check_bitset(); Node* check_true = control; Node* check_false = graph()->NewNode(common()->Merge(0)); for (int depth = 0; bitset != 0; bitset >>= 1, depth++) { if ((bitset & 1) == 0) continue; Node* load = graph()->NewNode( javascript()->LoadContext(depth, Context::EXTENSION_INDEX, false), context, context, effect); Node* check = graph()->NewNode(simplified()->ReferenceEqual(Type::Tagged()), load, jsgraph()->ZeroConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, check_true); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); check_false->set_op(common()->Merge(check_false->InputCount() + 1)); check_false->AppendInput(graph()->zone(), if_false); check_true = if_true; } // Fast case, because variable is not shadowed. Perform context slot load. Node* fast = graph()->NewNode(javascript()->LoadContext(context_access.depth(), context_access.index(), false), context, context, effect); // Slow case, because variable potentially shadowed. Perform dynamic lookup. uint32_t check_bitset = DynamicContextAccess::kFullCheckRequired; Node* slow = graph()->NewNode(javascript()->LoadDynamicContext( access.name(), check_bitset, context_access.depth(), context_access.index()), context, context, state, effect, check_false); // Replace value, effect and control uses accordingly. Node* new_control = graph()->NewNode(common()->Merge(2), check_true, check_false); Node* new_effect = graph()->NewNode(common()->EffectPhi(2), fast, slow, new_control); Node* new_value = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), fast, slow, new_control); ReplaceWithValue(node, new_value, new_effect, new_control); return Changed(new_value); } Reduction JSTypedLowering::ReduceJSCreateClosure(Node* node) { DCHECK_EQ(IrOpcode::kJSCreateClosure, node->opcode()); CreateClosureParameters const& p = CreateClosureParametersOf(node->op()); Handle<SharedFunctionInfo> shared = p.shared_info(); // Use the FastNewClosureStub that allocates in new space only for nested // functions that don't need literals cloning. if (p.pretenure() == NOT_TENURED && shared->num_literals() == 0) { Isolate* isolate = jsgraph()->isolate(); Callable callable = CodeFactory::FastNewClosure( isolate, shared->language_mode(), shared->kind()); CallDescriptor* desc = Linkage::GetStubCallDescriptor( isolate, graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags); const Operator* new_op = common()->Call(desc); Node* stub_code = jsgraph()->HeapConstant(callable.code()); node->ReplaceInput(0, jsgraph()->HeapConstant(shared)); node->InsertInput(graph()->zone(), 0, stub_code); node->set_op(new_op); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCreateLiteralArray(Node* node) { DCHECK_EQ(IrOpcode::kJSCreateLiteralArray, node->opcode()); HeapObjectMatcher mconst(NodeProperties::GetValueInput(node, 2)); int length = Handle<FixedArray>::cast(mconst.Value().handle())->length(); int flags = OpParameter<int>(node->op()); // Use the FastCloneShallowArrayStub only for shallow boilerplates up to the // initial length limit for arrays with "fast" elements kind. // TODO(rossberg): Teach strong mode to FastCloneShallowArrayStub. if ((flags & ArrayLiteral::kShallowElements) != 0 && (flags & ArrayLiteral::kIsStrong) == 0 && length < JSObject::kInitialMaxFastElementArray) { Isolate* isolate = jsgraph()->isolate(); Callable callable = CodeFactory::FastCloneShallowArray(isolate); CallDescriptor* desc = Linkage::GetStubCallDescriptor( isolate, graph()->zone(), callable.descriptor(), 0, (OperatorProperties::GetFrameStateInputCount(node->op()) != 0) ? CallDescriptor::kNeedsFrameState : CallDescriptor::kNoFlags); const Operator* new_op = common()->Call(desc); Node* stub_code = jsgraph()->HeapConstant(callable.code()); node->InsertInput(graph()->zone(), 0, stub_code); node->set_op(new_op); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCreateLiteralObject(Node* node) { DCHECK_EQ(IrOpcode::kJSCreateLiteralObject, node->opcode()); HeapObjectMatcher mconst(NodeProperties::GetValueInput(node, 2)); // Constants are pairs, see ObjectLiteral::properties_count(). int length = Handle<FixedArray>::cast(mconst.Value().handle())->length() / 2; int flags = OpParameter<int>(node->op()); // Use the FastCloneShallowObjectStub only for shallow boilerplates without // elements up to the number of properties that the stubs can handle. if ((flags & ObjectLiteral::kShallowProperties) != 0 && length <= FastCloneShallowObjectStub::kMaximumClonedProperties) { Isolate* isolate = jsgraph()->isolate(); Callable callable = CodeFactory::FastCloneShallowObject(isolate, length); CallDescriptor* desc = Linkage::GetStubCallDescriptor( isolate, graph()->zone(), callable.descriptor(), 0, (OperatorProperties::GetFrameStateInputCount(node->op()) != 0) ? CallDescriptor::kNeedsFrameState : CallDescriptor::kNoFlags); const Operator* new_op = common()->Call(desc); Node* stub_code = jsgraph()->HeapConstant(callable.code()); node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(flags)); node->InsertInput(graph()->zone(), 0, stub_code); node->set_op(new_op); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCreateWithContext(Node* node) { DCHECK_EQ(IrOpcode::kJSCreateWithContext, node->opcode()); Node* const input = NodeProperties::GetValueInput(node, 0); Type* input_type = NodeProperties::GetBounds(input).upper; if (FLAG_turbo_allocate && input_type->Is(Type::Receiver())) { // JSCreateWithContext(o:receiver, f) Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = NodeProperties::GetControlInput(node); Node* const closure = NodeProperties::GetValueInput(node, 1); Node* const context = NodeProperties::GetContextInput(node); Node* const load = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)), context, effect, control); AllocationBuilder a(jsgraph(), simplified(), effect, control); STATIC_ASSERT(Context::MIN_CONTEXT_SLOTS == 4); // Ensure fully covered. a.AllocateArray(Context::MIN_CONTEXT_SLOTS, factory()->with_context_map()); a.Store(AccessBuilder::ForContextSlot(Context::CLOSURE_INDEX), closure); a.Store(AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX), context); a.Store(AccessBuilder::ForContextSlot(Context::EXTENSION_INDEX), input); a.Store(AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX), load); // TODO(mstarzinger): We could mutate {node} into the allocation instead. NodeProperties::SetBounds(a.allocation(), NodeProperties::GetBounds(node)); ReplaceWithValue(node, node, a.effect()); node->ReplaceInput(0, a.allocation()); node->ReplaceInput(1, a.effect()); node->set_op(common()->Finish(1)); node->TrimInputCount(2); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCreateBlockContext(Node* node) { DCHECK_EQ(IrOpcode::kJSCreateBlockContext, node->opcode()); Node* const input = NodeProperties::GetValueInput(node, 0); HeapObjectMatcher minput(input); DCHECK(minput.HasValue()); // TODO(mstarzinger): Make ScopeInfo static. int context_length = Handle<ScopeInfo>::cast(minput.Value().handle())->ContextLength(); if (FLAG_turbo_allocate && context_length < kBlockContextAllocationLimit) { // JSCreateBlockContext(s:scope[length < limit], f) Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = NodeProperties::GetControlInput(node); Node* const closure = NodeProperties::GetValueInput(node, 1); Node* const context = NodeProperties::GetContextInput(node); Node* const load = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)), context, effect, control); AllocationBuilder a(jsgraph(), simplified(), effect, control); STATIC_ASSERT(Context::MIN_CONTEXT_SLOTS == 4); // Ensure fully covered. a.AllocateArray(context_length, factory()->block_context_map()); a.Store(AccessBuilder::ForContextSlot(Context::CLOSURE_INDEX), closure); a.Store(AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX), context); a.Store(AccessBuilder::ForContextSlot(Context::EXTENSION_INDEX), input); a.Store(AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX), load); for (int i = Context::MIN_CONTEXT_SLOTS; i < context_length; ++i) { a.Store(AccessBuilder::ForContextSlot(i), jsgraph()->TheHoleConstant()); } // TODO(mstarzinger): We could mutate {node} into the allocation instead. NodeProperties::SetBounds(a.allocation(), NodeProperties::GetBounds(node)); ReplaceWithValue(node, node, a.effect()); node->ReplaceInput(0, a.allocation()); node->ReplaceInput(1, a.effect()); node->set_op(common()->Finish(1)); node->TrimInputCount(2); return Changed(node); } return NoChange(); } Reduction JSTypedLowering::ReduceJSCallFunction(Node* node) { DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); int const arity = static_cast<int>(p.arity() - 2); Node* const function = NodeProperties::GetValueInput(node, 0); Type* const function_type = NodeProperties::GetBounds(function).upper; Node* const receiver = NodeProperties::GetValueInput(node, 1); Type* const receiver_type = NodeProperties::GetBounds(receiver).upper; Node* const effect = NodeProperties::GetEffectInput(node); Node* const control = NodeProperties::GetControlInput(node); // Check that {function} is actually a JSFunction with the correct arity. if (function_type->IsFunction() && function_type->AsFunction()->Arity() == arity) { // Check that the {receiver} doesn't need to be wrapped. if (receiver_type->Is(Type::ReceiverOrUndefined())) { Node* const context = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), function, effect, control); NodeProperties::ReplaceContextInput(node, context); CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; if (is_strict(p.language_mode())) { flags |= CallDescriptor::kSupportsTailCalls; } node->set_op(common()->Call(Linkage::GetJSCallDescriptor( graph()->zone(), false, 1 + arity, flags))); return Changed(node); } } return NoChange(); } Reduction JSTypedLowering::ReduceJSForInDone(Node* node) { DCHECK_EQ(IrOpcode::kJSForInDone, node->opcode()); node->set_op(machine()->Word32Equal()); node->TrimInputCount(2); return Changed(node); } Reduction JSTypedLowering::ReduceJSForInPrepare(Node* node) { DCHECK_EQ(IrOpcode::kJSForInPrepare, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 0); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Get the set of properties to enumerate. Node* cache_type = effect = graph()->NewNode( javascript()->CallRuntime(Runtime::kGetPropertyNamesFast, 1), receiver, context, frame_state, effect, control); control = graph()->NewNode(common()->IfSuccess(), cache_type); Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); Node* cache_type_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), cache_type, effect, control); Node* meta_map = jsgraph()->HeapConstant(factory()->meta_map()); // If we got a map from the GetPropertyNamesFast runtime call, we can do a // fast modification check. Otherwise, we got a fixed array, and we have to // perform a slow check on every iteration. Node* check0 = graph()->NewNode(simplified()->ReferenceEqual(Type::Any()), cache_type_map, meta_map); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control); Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* cache_array_true0; Node* cache_length_true0; Node* cache_type_true0; Node* etrue0; { // Enum cache case. Node* cache_type_enum_length = etrue0 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField3()), cache_type, effect, if_true0); cache_length_true0 = graph()->NewNode(machine()->Word32And(), cache_type_enum_length, jsgraph()->Uint32Constant(Map::EnumLengthBits::kMask)); Node* check1 = graph()->NewNode(machine()->Word32Equal(), cache_length_true0, jsgraph()->Int32Constant(0)); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check1, if_true0); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* cache_array_true1; Node* etrue1; { // No properties to enumerate. cache_array_true1 = jsgraph()->HeapConstant(factory()->empty_fixed_array()); etrue1 = etrue0; } Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); Node* cache_array_false1; Node* efalse1; { // Load the enumeration cache from the instance descriptors of {receiver}. Node* receiver_map_descriptors = efalse1 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapDescriptors()), receiver_map, etrue0, if_false1); Node* object_map_enum_cache = efalse1 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()), receiver_map_descriptors, efalse1, if_false1); cache_array_false1 = efalse1 = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForDescriptorArrayEnumCacheBridgeCache()), object_map_enum_cache, efalse1, if_false1); } if_true0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); etrue0 = graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_true0); cache_array_true0 = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), cache_array_true1, cache_array_false1, if_true0); cache_type_true0 = cache_type; } Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* cache_array_false0; Node* cache_length_false0; Node* cache_type_false0; Node* efalse0; { // FixedArray case. Node* receiver_instance_type = efalse0 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapInstanceType()), receiver_map, effect, if_false0); STATIC_ASSERT(FIRST_JS_PROXY_TYPE == FIRST_SPEC_OBJECT_TYPE); cache_type_false0 = graph()->NewNode( common()->Select(kMachAnyTagged, BranchHint::kFalse), graph()->NewNode(machine()->Uint32LessThanOrEqual(), receiver_instance_type, jsgraph()->Uint32Constant(LAST_JS_PROXY_TYPE)), jsgraph()->ZeroConstant(), // Zero indicagtes proxy. jsgraph()->OneConstant()); // One means slow check. cache_array_false0 = cache_type; cache_length_false0 = efalse0 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), cache_array_false0, efalse0, if_false0); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); Node* cache_array = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), cache_array_true0, cache_array_false0, control); Node* cache_length = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), cache_length_true0, cache_length_false0, control); cache_type = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), cache_type_true0, cache_type_false0, control); for (auto edge : node->use_edges()) { Node* const use = edge.from(); if (NodeProperties::IsEffectEdge(edge)) { edge.UpdateTo(effect); Revisit(use); } else { if (NodeProperties::IsControlEdge(edge)) { if (use->opcode() == IrOpcode::kIfSuccess) { Replace(use, control); } else if (use->opcode() == IrOpcode::kIfException) { edge.UpdateTo(cache_type_true0); continue; } else { UNREACHABLE(); } } else { DCHECK(NodeProperties::IsValueEdge(edge)); DCHECK_EQ(IrOpcode::kProjection, use->opcode()); switch (ProjectionIndexOf(use->op())) { case 0: Replace(use, cache_type); break; case 1: Replace(use, cache_array); break; case 2: Replace(use, cache_length); break; default: UNREACHABLE(); break; } } use->Kill(); } } return NoChange(); // All uses were replaced already above. } Reduction JSTypedLowering::ReduceJSForInNext(Node* node) { DCHECK_EQ(IrOpcode::kJSForInNext, node->opcode()); 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, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Load the next {key} from the {cache_array}. Node* key = effect = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), cache_array, index, effect, control); // Load the map of the {receiver}. Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); // Check if the expected map still matches that of the {receiver}. Node* check0 = graph()->NewNode(simplified()->ReferenceEqual(Type::Any()), receiver_map, cache_type); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control); Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0; Node* vtrue0; { // Don't need filtering since expected map still matches that of the // {receiver}. etrue0 = effect; vtrue0 = key; } Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* efalse0; Node* vfalse0; { // Check if the {cache_type} is zero, which indicates proxy. Node* check1 = graph()->NewNode(simplified()->ReferenceEqual(Type::Any()), cache_type, jsgraph()->ZeroConstant()); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, if_false0); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* etrue1; Node* vtrue1; { // Don't do filtering for proxies. etrue1 = effect; vtrue1 = key; } Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); Node* efalse1; Node* vfalse1; { // Filter the {key} to check if it's still a valid property of the // {receiver} (does the ToName conversion implicitly). vfalse1 = efalse1 = graph()->NewNode( javascript()->CallRuntime(Runtime::kForInFilter, 2), receiver, key, context, frame_state, effect, if_false1); if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1); } if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); efalse0 = graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0); vfalse0 = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), vtrue1, vfalse1, if_false0); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); ReplaceWithValue(node, node, effect, control); node->set_op(common()->Phi(kMachAnyTagged, 2)); node->ReplaceInput(0, vtrue0); node->ReplaceInput(1, vfalse0); node->ReplaceInput(2, control); node->TrimInputCount(3); return Changed(node); } Reduction JSTypedLowering::ReduceJSForInStep(Node* node) { DCHECK_EQ(IrOpcode::kJSForInStep, node->opcode()); node->set_op(machine()->Int32Add()); node->ReplaceInput(1, jsgraph()->Int32Constant(1)); DCHECK_EQ(2, node->InputCount()); return Changed(node); } Reduction JSTypedLowering::Reduce(Node* node) { // Check if the output type is a singleton. In that case we already know the // result value and can simply replace the node if it's eliminable. if (!NodeProperties::IsConstant(node) && NodeProperties::IsTyped(node) && node->op()->HasProperty(Operator::kEliminatable)) { Type* upper = NodeProperties::GetBounds(node).upper; if (upper->IsConstant()) { Node* replacement = jsgraph()->Constant(upper->AsConstant()->Value()); ReplaceWithValue(node, replacement); return Changed(replacement); } else if (upper->Is(Type::MinusZero())) { Node* replacement = jsgraph()->Constant(factory()->minus_zero_value()); ReplaceWithValue(node, replacement); return Changed(replacement); } else if (upper->Is(Type::NaN())) { Node* replacement = jsgraph()->NaNConstant(); ReplaceWithValue(node, replacement); return Changed(replacement); } else if (upper->Is(Type::Null())) { Node* replacement = jsgraph()->NullConstant(); ReplaceWithValue(node, replacement); return Changed(replacement); } else if (upper->Is(Type::PlainNumber()) && upper->Min() == upper->Max()) { Node* replacement = jsgraph()->Constant(upper->Min()); ReplaceWithValue(node, replacement); return Changed(replacement); } else if (upper->Is(Type::Undefined())) { Node* replacement = jsgraph()->UndefinedConstant(); ReplaceWithValue(node, replacement); return Changed(replacement); } } switch (node->opcode()) { case IrOpcode::kJSEqual: return ReduceJSEqual(node, false); case IrOpcode::kJSNotEqual: return ReduceJSEqual(node, true); case IrOpcode::kJSStrictEqual: return ReduceJSStrictEqual(node, false); case IrOpcode::kJSStrictNotEqual: return ReduceJSStrictEqual(node, true); case IrOpcode::kJSLessThan: // fall through case IrOpcode::kJSGreaterThan: // fall through case IrOpcode::kJSLessThanOrEqual: // fall through case IrOpcode::kJSGreaterThanOrEqual: return ReduceJSComparison(node); case IrOpcode::kJSBitwiseOr: return ReduceInt32Binop(node, machine()->Word32Or()); case IrOpcode::kJSBitwiseXor: return ReduceInt32Binop(node, machine()->Word32Xor()); case IrOpcode::kJSBitwiseAnd: return ReduceInt32Binop(node, machine()->Word32And()); case IrOpcode::kJSShiftLeft: return ReduceUI32Shift(node, kSigned, simplified()->NumberShiftLeft()); case IrOpcode::kJSShiftRight: return ReduceUI32Shift(node, kSigned, simplified()->NumberShiftRight()); case IrOpcode::kJSShiftRightLogical: return ReduceUI32Shift(node, kUnsigned, simplified()->NumberShiftRightLogical()); case IrOpcode::kJSAdd: return ReduceJSAdd(node); case IrOpcode::kJSSubtract: return ReduceNumberBinop(node, simplified()->NumberSubtract()); case IrOpcode::kJSMultiply: return ReduceNumberBinop(node, simplified()->NumberMultiply()); case IrOpcode::kJSDivide: return ReduceNumberBinop(node, simplified()->NumberDivide()); case IrOpcode::kJSModulus: return ReduceJSModulus(node); case IrOpcode::kJSUnaryNot: return ReduceJSUnaryNot(node); case IrOpcode::kJSToBoolean: return ReduceJSToBoolean(node); case IrOpcode::kJSToNumber: return ReduceJSToNumber(node); case IrOpcode::kJSToString: return ReduceJSToString(node); case IrOpcode::kJSLoadGlobal: return ReduceJSLoadGlobal(node); case IrOpcode::kJSLoadNamed: return ReduceJSLoadNamed(node); case IrOpcode::kJSLoadProperty: return ReduceJSLoadProperty(node); case IrOpcode::kJSStoreProperty: return ReduceJSStoreProperty(node); case IrOpcode::kJSLoadContext: return ReduceJSLoadContext(node); case IrOpcode::kJSStoreContext: return ReduceJSStoreContext(node); case IrOpcode::kJSLoadDynamicGlobal: return ReduceJSLoadDynamicGlobal(node); case IrOpcode::kJSLoadDynamicContext: return ReduceJSLoadDynamicContext(node); case IrOpcode::kJSCreateClosure: return ReduceJSCreateClosure(node); case IrOpcode::kJSCreateLiteralArray: return ReduceJSCreateLiteralArray(node); case IrOpcode::kJSCreateLiteralObject: return ReduceJSCreateLiteralObject(node); case IrOpcode::kJSCreateWithContext: return ReduceJSCreateWithContext(node); case IrOpcode::kJSCreateBlockContext: return ReduceJSCreateBlockContext(node); case IrOpcode::kJSCallFunction: return ReduceJSCallFunction(node); case IrOpcode::kJSForInDone: return ReduceJSForInDone(node); case IrOpcode::kJSForInNext: return ReduceJSForInNext(node); case IrOpcode::kJSForInPrepare: return ReduceJSForInPrepare(node); case IrOpcode::kJSForInStep: return ReduceJSForInStep(node); default: break; } return NoChange(); } Node* JSTypedLowering::Word32Shl(Node* const lhs, int32_t const rhs) { if (rhs == 0) return lhs; return graph()->NewNode(machine()->Word32Shl(), lhs, jsgraph()->Int32Constant(rhs)); } 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(); } MachineOperatorBuilder* JSTypedLowering::machine() const { return jsgraph()->machine(); } } // namespace compiler } // namespace internal } // namespace v8