// 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-builtin-reducer.h" #include "src/base/bits.h" #include "src/builtins/builtins-utils.h" #include "src/code-factory.h" #include "src/compilation-dependencies.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/simplified-operator.h" #include "src/compiler/type-cache.h" #include "src/compiler/types.h" #include "src/objects-inl.h" namespace v8 { namespace internal { namespace compiler { // Helper class to access JSCall nodes that are potential candidates // for reduction when they have a BuiltinFunctionId associated with them. class JSCallReduction { public: explicit JSCallReduction(Node* node) : node_(node) {} // Determines whether the node is a JSCall operation that targets a // constant callee being a well-known builtin with a BuiltinFunctionId. bool HasBuiltinFunctionId() { if (node_->opcode() != IrOpcode::kJSCall) return false; HeapObjectMatcher m(NodeProperties::GetValueInput(node_, 0)); if (!m.HasValue() || !m.Value()->IsJSFunction()) return false; Handle function = Handle::cast(m.Value()); return function->shared()->HasBuiltinFunctionId(); } bool BuiltinCanBeInlined() { DCHECK_EQ(IrOpcode::kJSCall, node_->opcode()); HeapObjectMatcher m(NodeProperties::GetValueInput(node_, 0)); Handle function = Handle::cast(m.Value()); // Do not inline if the builtin may have break points. return !function->shared()->HasBreakInfo(); } // Retrieves the BuiltinFunctionId as described above. BuiltinFunctionId GetBuiltinFunctionId() { DCHECK_EQ(IrOpcode::kJSCall, node_->opcode()); HeapObjectMatcher m(NodeProperties::GetValueInput(node_, 0)); Handle function = Handle::cast(m.Value()); return function->shared()->builtin_function_id(); } bool ReceiverMatches(Type* type) { return NodeProperties::GetType(receiver())->Is(type); } // Determines whether the call takes zero inputs. bool InputsMatchZero() { return GetJSCallArity() == 0; } // Determines whether the call takes one input of the given type. bool InputsMatchOne(Type* t1) { return GetJSCallArity() == 1 && NodeProperties::GetType(GetJSCallInput(0))->Is(t1); } // Determines whether the call takes two inputs of the given types. bool InputsMatchTwo(Type* t1, Type* t2) { return GetJSCallArity() == 2 && NodeProperties::GetType(GetJSCallInput(0))->Is(t1) && NodeProperties::GetType(GetJSCallInput(1))->Is(t2); } // Determines whether the call takes inputs all of the given type. bool InputsMatchAll(Type* t) { for (int i = 0; i < GetJSCallArity(); i++) { if (!NodeProperties::GetType(GetJSCallInput(i))->Is(t)) { return false; } } return true; } Node* receiver() { return NodeProperties::GetValueInput(node_, 1); } Node* left() { return GetJSCallInput(0); } Node* right() { return GetJSCallInput(1); } int GetJSCallArity() { DCHECK_EQ(IrOpcode::kJSCall, node_->opcode()); // Skip first (i.e. callee) and second (i.e. receiver) operand. return node_->op()->ValueInputCount() - 2; } Node* GetJSCallInput(int index) { DCHECK_EQ(IrOpcode::kJSCall, node_->opcode()); DCHECK_LT(index, GetJSCallArity()); // Skip first (i.e. callee) and second (i.e. receiver) operand. return NodeProperties::GetValueInput(node_, index + 2); } private: Node* node_; }; JSBuiltinReducer::JSBuiltinReducer(Editor* editor, JSGraph* jsgraph, CompilationDependencies* dependencies, Handle native_context) : AdvancedReducer(editor), dependencies_(dependencies), jsgraph_(jsgraph), native_context_(native_context), type_cache_(TypeCache::Get()) {} // ES6 section 22.1.2.2 Array.isArray ( arg ) Reduction JSBuiltinReducer::ReduceArrayIsArray(Node* node) { // We certainly know that undefined is not an array. if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* value = NodeProperties::GetValueInput(node, 2); 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); } // ES6 section 20.3.3.1 Date.now ( ) Reduction JSBuiltinReducer::ReduceDateNow(Node* node) { NodeProperties::RemoveValueInputs(node); NodeProperties::ChangeOp( node, javascript()->CallRuntime(Runtime::kDateCurrentTime)); return Changed(node); } // ES6 section 20.3.4.10 Date.prototype.getTime ( ) Reduction JSBuiltinReducer::ReduceDateGetTime(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (NodeProperties::HasInstanceTypeWitness(receiver, effect, JS_DATE_TYPE)) { Node* value = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSDateValue()), receiver, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } return NoChange(); } // ES6 section 18.2.2 isFinite ( number ) Reduction JSBuiltinReducer::ReduceGlobalIsFinite(Node* node) { JSCallReduction r(node); if (r.InputsMatchOne(Type::PlainPrimitive())) { // isFinite(a:plain-primitive) -> NumberEqual(a', a') // where a' = NumberSubtract(ToNumber(a), ToNumber(a)) Node* input = ToNumber(r.GetJSCallInput(0)); Node* diff = graph()->NewNode(simplified()->NumberSubtract(), input, input); Node* value = graph()->NewNode(simplified()->NumberEqual(), diff, diff); return Replace(value); } return NoChange(); } // ES6 section 18.2.3 isNaN ( number ) Reduction JSBuiltinReducer::ReduceGlobalIsNaN(Node* node) { JSCallReduction r(node); if (r.InputsMatchOne(Type::PlainPrimitive())) { // isNaN(a:plain-primitive) -> BooleanNot(NumberEqual(a', a')) // where a' = ToNumber(a) Node* input = ToNumber(r.GetJSCallInput(0)); Node* check = graph()->NewNode(simplified()->NumberEqual(), input, input); Node* value = graph()->NewNode(simplified()->BooleanNot(), check); return Replace(value); } return NoChange(); } // ES6 section 20.1.2.13 Number.parseInt ( string, radix ) Reduction JSBuiltinReducer::ReduceNumberParseInt(Node* node) { JSCallReduction r(node); if (r.InputsMatchOne(type_cache_.kSafeInteger) || r.InputsMatchTwo(type_cache_.kSafeInteger, type_cache_.kZeroOrUndefined) || r.InputsMatchTwo(type_cache_.kSafeInteger, type_cache_.kTenOrUndefined)) { // Number.parseInt(a:safe-integer) -> a // Number.parseInt(a:safe-integer,b:#0\/undefined) -> a // Number.parseInt(a:safe-integer,b:#10\/undefined) -> a Node* value = r.GetJSCallInput(0); return Replace(value); } return NoChange(); } // ES6 section #sec-object.create Object.create(proto, properties) Reduction JSBuiltinReducer::ReduceObjectCreate(Node* node) { // We need exactly target, receiver and value parameters. int arg_count = node->op()->ValueInputCount(); if (arg_count != 3) return NoChange(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* prototype = NodeProperties::GetValueInput(node, 2); Type* prototype_type = NodeProperties::GetType(prototype); if (!prototype_type->IsHeapConstant()) return NoChange(); Handle prototype_const = prototype_type->AsHeapConstant()->Value(); Handle instance_map; MaybeHandle maybe_instance_map = Map::TryGetObjectCreateMap(prototype_const); if (!maybe_instance_map.ToHandle(&instance_map)) return NoChange(); Node* properties = jsgraph()->EmptyFixedArrayConstant(); if (instance_map->is_dictionary_map()) { // Allocated an empty NameDictionary as backing store for the properties. Handle map(isolate()->heap()->name_dictionary_map(), isolate()); int capacity = NameDictionary::ComputeCapacity(NameDictionary::kInitialCapacity); DCHECK(base::bits::IsPowerOfTwo(capacity)); int length = NameDictionary::EntryToIndex(capacity); int size = NameDictionary::SizeFor(length); AllocationBuilder a(jsgraph(), effect, control); a.Allocate(size, NOT_TENURED, Type::Any()); a.Store(AccessBuilder::ForMap(), map); // Initialize FixedArray fields. a.Store(AccessBuilder::ForFixedArrayLength(), jsgraph()->SmiConstant(length)); // Initialize HashTable fields. a.Store(AccessBuilder::ForHashTableBaseNumberOfElements(), jsgraph()->SmiConstant(0)); a.Store(AccessBuilder::ForHashTableBaseNumberOfDeletedElement(), jsgraph()->SmiConstant(0)); a.Store(AccessBuilder::ForHashTableBaseCapacity(), jsgraph()->SmiConstant(capacity)); // Initialize Dictionary fields. a.Store(AccessBuilder::ForDictionaryNextEnumerationIndex(), jsgraph()->SmiConstant(PropertyDetails::kInitialIndex)); a.Store(AccessBuilder::ForDictionaryObjectHashIndex(), jsgraph()->SmiConstant(PropertyArray::kNoHashSentinel)); // Initialize the Properties fields. Node* undefined = jsgraph()->UndefinedConstant(); STATIC_ASSERT(NameDictionary::kElementsStartIndex == NameDictionary::kObjectHashIndex + 1); for (int index = NameDictionary::kElementsStartIndex; index < length; index++) { a.Store(AccessBuilder::ForFixedArraySlot(index, kNoWriteBarrier), undefined); } properties = effect = a.Finish(); } int const instance_size = instance_map->instance_size(); if (instance_size > kMaxRegularHeapObjectSize) return NoChange(); dependencies()->AssumeInitialMapCantChange(instance_map); // Emit code to allocate the JSObject instance for the given // {instance_map}. AllocationBuilder a(jsgraph(), effect, control); a.Allocate(instance_size, NOT_TENURED, Type::Any()); a.Store(AccessBuilder::ForMap(), instance_map); a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties); a.Store(AccessBuilder::ForJSObjectElements(), jsgraph()->EmptyFixedArrayConstant()); // Initialize Object fields. Node* undefined = jsgraph()->UndefinedConstant(); for (int offset = JSObject::kHeaderSize; offset < instance_size; offset += kPointerSize) { a.Store(AccessBuilder::ForJSObjectOffset(offset, kNoWriteBarrier), undefined); } Node* value = effect = a.Finish(); // replace it ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSBuiltinReducer::Reduce(Node* node) { Reduction reduction = NoChange(); JSCallReduction r(node); // Dispatch according to the BuiltinFunctionId if present. if (!r.HasBuiltinFunctionId()) return NoChange(); if (!r.BuiltinCanBeInlined()) return NoChange(); switch (r.GetBuiltinFunctionId()) { case kArrayIsArray: return ReduceArrayIsArray(node); case kDateNow: return ReduceDateNow(node); case kDateGetTime: return ReduceDateGetTime(node); case kGlobalIsFinite: reduction = ReduceGlobalIsFinite(node); break; case kGlobalIsNaN: reduction = ReduceGlobalIsNaN(node); break; case kNumberParseInt: reduction = ReduceNumberParseInt(node); break; case kObjectCreate: reduction = ReduceObjectCreate(node); break; default: break; } // Replace builtin call assuming replacement nodes are pure values that don't // produce an effect. Replaces {node} with {reduction} and relaxes effects. if (reduction.Changed()) ReplaceWithValue(node, reduction.replacement()); return reduction; } Node* JSBuiltinReducer::ToNumber(Node* input) { Type* input_type = NodeProperties::GetType(input); if (input_type->Is(Type::Number())) return input; return graph()->NewNode(simplified()->PlainPrimitiveToNumber(), input); } Node* JSBuiltinReducer::ToUint32(Node* input) { input = ToNumber(input); Type* input_type = NodeProperties::GetType(input); if (input_type->Is(Type::Unsigned32())) return input; return graph()->NewNode(simplified()->NumberToUint32(), input); } Graph* JSBuiltinReducer::graph() const { return jsgraph()->graph(); } Factory* JSBuiltinReducer::factory() const { return isolate()->factory(); } Isolate* JSBuiltinReducer::isolate() const { return jsgraph()->isolate(); } CommonOperatorBuilder* JSBuiltinReducer::common() const { return jsgraph()->common(); } SimplifiedOperatorBuilder* JSBuiltinReducer::simplified() const { return jsgraph()->simplified(); } JSOperatorBuilder* JSBuiltinReducer::javascript() const { return jsgraph()->javascript(); } } // namespace compiler } // namespace internal } // namespace v8