| // Copyright 2012 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-stubs.h" |
| |
| #include <memory> |
| |
| #include "src/bailout-reason.h" |
| #include "src/code-factory.h" |
| #include "src/code-stub-assembler.h" |
| #include "src/crankshaft/hydrogen.h" |
| #include "src/crankshaft/lithium.h" |
| #include "src/field-index.h" |
| #include "src/ic/ic.h" |
| #include "src/objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| |
| static LChunk* OptimizeGraph(HGraph* graph) { |
| DisallowHeapAllocation no_allocation; |
| DisallowHandleAllocation no_handles; |
| DisallowHandleDereference no_deref; |
| |
| DCHECK(graph != NULL); |
| BailoutReason bailout_reason = kNoReason; |
| if (!graph->Optimize(&bailout_reason)) { |
| FATAL(GetBailoutReason(bailout_reason)); |
| } |
| LChunk* chunk = LChunk::NewChunk(graph); |
| if (chunk == NULL) { |
| FATAL(GetBailoutReason(graph->info()->bailout_reason())); |
| } |
| return chunk; |
| } |
| |
| |
| class CodeStubGraphBuilderBase : public HGraphBuilder { |
| public: |
| explicit CodeStubGraphBuilderBase(CompilationInfo* info, CodeStub* code_stub) |
| : HGraphBuilder(info, code_stub->GetCallInterfaceDescriptor(), false), |
| arguments_length_(NULL), |
| info_(info), |
| code_stub_(code_stub), |
| descriptor_(code_stub), |
| context_(NULL) { |
| int parameter_count = GetParameterCount(); |
| parameters_.reset(new HParameter*[parameter_count]); |
| } |
| virtual bool BuildGraph(); |
| |
| protected: |
| virtual HValue* BuildCodeStub() = 0; |
| int GetParameterCount() const { return descriptor_.GetParameterCount(); } |
| int GetRegisterParameterCount() const { |
| return descriptor_.GetRegisterParameterCount(); |
| } |
| HParameter* GetParameter(int parameter) { |
| DCHECK(parameter < GetParameterCount()); |
| return parameters_[parameter]; |
| } |
| Representation GetParameterRepresentation(int parameter) { |
| return RepresentationFromMachineType( |
| descriptor_.GetParameterType(parameter)); |
| } |
| bool IsParameterCountRegister(int index) const { |
| return descriptor_.GetRegisterParameter(index) |
| .is(descriptor_.stack_parameter_count()); |
| } |
| HValue* GetArgumentsLength() { |
| // This is initialized in BuildGraph() |
| DCHECK(arguments_length_ != NULL); |
| return arguments_length_; |
| } |
| CompilationInfo* info() { return info_; } |
| CodeStub* stub() { return code_stub_; } |
| HContext* context() { return context_; } |
| Isolate* isolate() { return info_->isolate(); } |
| |
| private: |
| std::unique_ptr<HParameter* []> parameters_; |
| HValue* arguments_length_; |
| CompilationInfo* info_; |
| CodeStub* code_stub_; |
| CodeStubDescriptor descriptor_; |
| HContext* context_; |
| }; |
| |
| |
| bool CodeStubGraphBuilderBase::BuildGraph() { |
| // Update the static counter each time a new code stub is generated. |
| isolate()->counters()->code_stubs()->Increment(); |
| |
| if (FLAG_trace_hydrogen_stubs) { |
| const char* name = CodeStub::MajorName(stub()->MajorKey()); |
| PrintF("-----------------------------------------------------------\n"); |
| PrintF("Compiling stub %s using hydrogen\n", name); |
| isolate()->GetHTracer()->TraceCompilation(info()); |
| } |
| |
| int param_count = GetParameterCount(); |
| int register_param_count = GetRegisterParameterCount(); |
| HEnvironment* start_environment = graph()->start_environment(); |
| HBasicBlock* next_block = CreateBasicBlock(start_environment); |
| Goto(next_block); |
| next_block->SetJoinId(BailoutId::StubEntry()); |
| set_current_block(next_block); |
| |
| bool runtime_stack_params = descriptor_.stack_parameter_count().is_valid(); |
| HInstruction* stack_parameter_count = NULL; |
| for (int i = 0; i < param_count; ++i) { |
| Representation r = GetParameterRepresentation(i); |
| HParameter* param; |
| if (i >= register_param_count) { |
| param = Add<HParameter>(i - register_param_count, |
| HParameter::STACK_PARAMETER, r); |
| } else { |
| param = Add<HParameter>(i, HParameter::REGISTER_PARAMETER, r); |
| } |
| start_environment->Bind(i, param); |
| parameters_[i] = param; |
| if (i < register_param_count && IsParameterCountRegister(i)) { |
| param->set_type(HType::Smi()); |
| stack_parameter_count = param; |
| arguments_length_ = stack_parameter_count; |
| } |
| } |
| |
| DCHECK(!runtime_stack_params || arguments_length_ != NULL); |
| if (!runtime_stack_params) { |
| stack_parameter_count = |
| Add<HConstant>(param_count - register_param_count - 1); |
| // graph()->GetConstantMinus1(); |
| arguments_length_ = graph()->GetConstant0(); |
| } |
| |
| context_ = Add<HContext>(); |
| start_environment->BindContext(context_); |
| start_environment->Bind(param_count, context_); |
| |
| Add<HSimulate>(BailoutId::StubEntry()); |
| |
| NoObservableSideEffectsScope no_effects(this); |
| |
| HValue* return_value = BuildCodeStub(); |
| |
| // We might have extra expressions to pop from the stack in addition to the |
| // arguments above. |
| HInstruction* stack_pop_count = stack_parameter_count; |
| if (descriptor_.function_mode() == JS_FUNCTION_STUB_MODE) { |
| if (!stack_parameter_count->IsConstant() && |
| descriptor_.hint_stack_parameter_count() < 0) { |
| HInstruction* constant_one = graph()->GetConstant1(); |
| stack_pop_count = AddUncasted<HAdd>(stack_parameter_count, constant_one); |
| stack_pop_count->ClearFlag(HValue::kCanOverflow); |
| // TODO(mvstanton): verify that stack_parameter_count+1 really fits in a |
| // smi. |
| } else { |
| int count = descriptor_.hint_stack_parameter_count(); |
| stack_pop_count = Add<HConstant>(count); |
| } |
| } |
| |
| if (current_block() != NULL) { |
| HReturn* hreturn_instruction = New<HReturn>(return_value, |
| stack_pop_count); |
| FinishCurrentBlock(hreturn_instruction); |
| } |
| return true; |
| } |
| |
| |
| template <class Stub> |
| class CodeStubGraphBuilder: public CodeStubGraphBuilderBase { |
| public: |
| explicit CodeStubGraphBuilder(CompilationInfo* info, CodeStub* stub) |
| : CodeStubGraphBuilderBase(info, stub) {} |
| |
| typedef typename Stub::Descriptor Descriptor; |
| |
| protected: |
| virtual HValue* BuildCodeStub() { |
| if (casted_stub()->IsUninitialized()) { |
| return BuildCodeUninitializedStub(); |
| } else { |
| return BuildCodeInitializedStub(); |
| } |
| } |
| |
| virtual HValue* BuildCodeInitializedStub() { |
| UNIMPLEMENTED(); |
| return NULL; |
| } |
| |
| virtual HValue* BuildCodeUninitializedStub() { |
| // Force a deopt that falls back to the runtime. |
| HValue* undefined = graph()->GetConstantUndefined(); |
| IfBuilder builder(this); |
| builder.IfNot<HCompareObjectEqAndBranch, HValue*>(undefined, undefined); |
| builder.Then(); |
| builder.ElseDeopt(DeoptimizeReason::kForcedDeoptToRuntime); |
| return undefined; |
| } |
| |
| Stub* casted_stub() { return static_cast<Stub*>(stub()); } |
| }; |
| |
| |
| Handle<Code> HydrogenCodeStub::GenerateLightweightMissCode( |
| ExternalReference miss) { |
| Factory* factory = isolate()->factory(); |
| |
| // Generate the new code. |
| MacroAssembler masm(isolate(), NULL, 256, CodeObjectRequired::kYes); |
| |
| { |
| // Update the static counter each time a new code stub is generated. |
| isolate()->counters()->code_stubs()->Increment(); |
| |
| // Generate the code for the stub. |
| masm.set_generating_stub(true); |
| // TODO(yangguo): remove this once we can serialize IC stubs. |
| masm.enable_serializer(); |
| NoCurrentFrameScope scope(&masm); |
| GenerateLightweightMiss(&masm, miss); |
| } |
| |
| // Create the code object. |
| CodeDesc desc; |
| masm.GetCode(&desc); |
| |
| // Copy the generated code into a heap object. |
| Handle<Code> new_object = factory->NewCode( |
| desc, GetCodeFlags(), masm.CodeObject(), NeedsImmovableCode()); |
| return new_object; |
| } |
| |
| Handle<Code> HydrogenCodeStub::GenerateRuntimeTailCall( |
| CodeStubDescriptor* descriptor) { |
| const char* name = CodeStub::MajorName(MajorKey()); |
| Zone zone(isolate()->allocator(), ZONE_NAME); |
| CallInterfaceDescriptor interface_descriptor(GetCallInterfaceDescriptor()); |
| compiler::CodeAssemblerState state(isolate(), &zone, interface_descriptor, |
| GetCodeFlags(), name); |
| CodeStubAssembler assembler(&state); |
| int total_params = interface_descriptor.GetStackParameterCount() + |
| interface_descriptor.GetRegisterParameterCount(); |
| switch (total_params) { |
| case 0: |
| assembler.TailCallRuntime(descriptor->miss_handler_id(), |
| assembler.Parameter(0)); |
| break; |
| case 1: |
| assembler.TailCallRuntime(descriptor->miss_handler_id(), |
| assembler.Parameter(1), assembler.Parameter(0)); |
| break; |
| case 2: |
| assembler.TailCallRuntime(descriptor->miss_handler_id(), |
| assembler.Parameter(2), assembler.Parameter(0), |
| assembler.Parameter(1)); |
| break; |
| case 3: |
| assembler.TailCallRuntime(descriptor->miss_handler_id(), |
| assembler.Parameter(3), assembler.Parameter(0), |
| assembler.Parameter(1), assembler.Parameter(2)); |
| break; |
| case 4: |
| assembler.TailCallRuntime(descriptor->miss_handler_id(), |
| assembler.Parameter(4), assembler.Parameter(0), |
| assembler.Parameter(1), assembler.Parameter(2), |
| assembler.Parameter(3)); |
| break; |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| return compiler::CodeAssembler::GenerateCode(&state); |
| } |
| |
| template <class Stub> |
| static Handle<Code> DoGenerateCode(Stub* stub) { |
| Isolate* isolate = stub->isolate(); |
| CodeStubDescriptor descriptor(stub); |
| |
| if (FLAG_minimal && descriptor.has_miss_handler()) { |
| return stub->GenerateRuntimeTailCall(&descriptor); |
| } |
| |
| // If we are uninitialized we can use a light-weight stub to enter |
| // the runtime that is significantly faster than using the standard |
| // stub-failure deopt mechanism. |
| if (stub->IsUninitialized() && descriptor.has_miss_handler()) { |
| DCHECK(!descriptor.stack_parameter_count().is_valid()); |
| return stub->GenerateLightweightMissCode(descriptor.miss_handler()); |
| } |
| base::ElapsedTimer timer; |
| if (FLAG_profile_hydrogen_code_stub_compilation) { |
| timer.Start(); |
| } |
| Zone zone(isolate->allocator(), ZONE_NAME); |
| CompilationInfo info(CStrVector(CodeStub::MajorName(stub->MajorKey())), |
| isolate, &zone, stub->GetCodeFlags()); |
| // Parameter count is number of stack parameters. |
| int parameter_count = descriptor.GetStackParameterCount(); |
| if (descriptor.function_mode() == NOT_JS_FUNCTION_STUB_MODE) { |
| parameter_count--; |
| } |
| info.set_parameter_count(parameter_count); |
| CodeStubGraphBuilder<Stub> builder(&info, stub); |
| LChunk* chunk = OptimizeGraph(builder.CreateGraph()); |
| Handle<Code> code = chunk->Codegen(); |
| if (FLAG_profile_hydrogen_code_stub_compilation) { |
| OFStream os(stdout); |
| os << "[Lazy compilation of " << stub << " took " |
| << timer.Elapsed().InMillisecondsF() << " ms]" << std::endl; |
| } |
| return code; |
| } |
| |
| template <> |
| HValue* CodeStubGraphBuilder<TransitionElementsKindStub>::BuildCodeStub() { |
| ElementsKind const from_kind = casted_stub()->from_kind(); |
| ElementsKind const to_kind = casted_stub()->to_kind(); |
| HValue* const object = GetParameter(Descriptor::kObject); |
| HValue* const map = GetParameter(Descriptor::kMap); |
| |
| // The {object} is known to be a JSObject (otherwise it wouldn't have elements |
| // anyways). |
| object->set_type(HType::JSObject()); |
| |
| info()->MarkAsSavesCallerDoubles(); |
| |
| DCHECK_IMPLIES(IsFastHoleyElementsKind(from_kind), |
| IsFastHoleyElementsKind(to_kind)); |
| |
| if (AllocationSite::GetMode(from_kind, to_kind) == TRACK_ALLOCATION_SITE) { |
| Add<HTrapAllocationMemento>(object); |
| } |
| |
| if (!IsSimpleMapChangeTransition(from_kind, to_kind)) { |
| HInstruction* elements = AddLoadElements(object); |
| |
| IfBuilder if_objecthaselements(this); |
| if_objecthaselements.IfNot<HCompareObjectEqAndBranch>( |
| elements, Add<HConstant>(isolate()->factory()->empty_fixed_array())); |
| if_objecthaselements.Then(); |
| { |
| // Determine the elements capacity. |
| HInstruction* elements_length = AddLoadFixedArrayLength(elements); |
| |
| // Determine the effective (array) length. |
| IfBuilder if_objectisarray(this); |
| if_objectisarray.If<HHasInstanceTypeAndBranch>(object, JS_ARRAY_TYPE); |
| if_objectisarray.Then(); |
| { |
| // The {object} is a JSArray, load the special "length" property. |
| Push(Add<HLoadNamedField>(object, nullptr, |
| HObjectAccess::ForArrayLength(from_kind))); |
| } |
| if_objectisarray.Else(); |
| { |
| // The {object} is some other JSObject. |
| Push(elements_length); |
| } |
| if_objectisarray.End(); |
| HValue* length = Pop(); |
| |
| BuildGrowElementsCapacity(object, elements, from_kind, to_kind, length, |
| elements_length); |
| } |
| if_objecthaselements.End(); |
| } |
| |
| Add<HStoreNamedField>(object, HObjectAccess::ForMap(), map); |
| |
| return object; |
| } |
| |
| |
| Handle<Code> TransitionElementsKindStub::GenerateCode() { |
| return DoGenerateCode(this); |
| } |
| |
| template <> |
| HValue* CodeStubGraphBuilder<BinaryOpICStub>::BuildCodeInitializedStub() { |
| BinaryOpICState state = casted_stub()->state(); |
| |
| HValue* left = GetParameter(Descriptor::kLeft); |
| HValue* right = GetParameter(Descriptor::kRight); |
| |
| AstType* left_type = state.GetLeftType(); |
| AstType* right_type = state.GetRightType(); |
| AstType* result_type = state.GetResultType(); |
| |
| DCHECK(!left_type->Is(AstType::None()) && !right_type->Is(AstType::None()) && |
| (state.HasSideEffects() || !result_type->Is(AstType::None()))); |
| |
| HValue* result = NULL; |
| HAllocationMode allocation_mode(NOT_TENURED); |
| if (state.op() == Token::ADD && (left_type->Maybe(AstType::String()) || |
| right_type->Maybe(AstType::String())) && |
| !left_type->Is(AstType::String()) && !right_type->Is(AstType::String())) { |
| // For the generic add stub a fast case for string addition is performance |
| // critical. |
| if (left_type->Maybe(AstType::String())) { |
| IfBuilder if_leftisstring(this); |
| if_leftisstring.If<HIsStringAndBranch>(left); |
| if_leftisstring.Then(); |
| { |
| Push(BuildBinaryOperation(state.op(), left, right, AstType::String(), |
| right_type, result_type, |
| state.fixed_right_arg(), allocation_mode)); |
| } |
| if_leftisstring.Else(); |
| { |
| Push(BuildBinaryOperation(state.op(), left, right, left_type, |
| right_type, result_type, |
| state.fixed_right_arg(), allocation_mode)); |
| } |
| if_leftisstring.End(); |
| result = Pop(); |
| } else { |
| IfBuilder if_rightisstring(this); |
| if_rightisstring.If<HIsStringAndBranch>(right); |
| if_rightisstring.Then(); |
| { |
| Push(BuildBinaryOperation(state.op(), left, right, left_type, |
| AstType::String(), result_type, |
| state.fixed_right_arg(), allocation_mode)); |
| } |
| if_rightisstring.Else(); |
| { |
| Push(BuildBinaryOperation(state.op(), left, right, left_type, |
| right_type, result_type, |
| state.fixed_right_arg(), allocation_mode)); |
| } |
| if_rightisstring.End(); |
| result = Pop(); |
| } |
| } else { |
| result = BuildBinaryOperation(state.op(), left, right, left_type, |
| right_type, result_type, |
| state.fixed_right_arg(), allocation_mode); |
| } |
| |
| // If we encounter a generic argument, the number conversion is |
| // observable, thus we cannot afford to bail out after the fact. |
| if (!state.HasSideEffects()) { |
| result = EnforceNumberType(result, result_type); |
| } |
| |
| return result; |
| } |
| |
| |
| Handle<Code> BinaryOpICStub::GenerateCode() { |
| return DoGenerateCode(this); |
| } |
| |
| |
| template <> |
| HValue* CodeStubGraphBuilder<BinaryOpWithAllocationSiteStub>::BuildCodeStub() { |
| BinaryOpICState state = casted_stub()->state(); |
| |
| HValue* allocation_site = GetParameter(Descriptor::kAllocationSite); |
| HValue* left = GetParameter(Descriptor::kLeft); |
| HValue* right = GetParameter(Descriptor::kRight); |
| |
| AstType* left_type = state.GetLeftType(); |
| AstType* right_type = state.GetRightType(); |
| AstType* result_type = state.GetResultType(); |
| HAllocationMode allocation_mode(allocation_site); |
| |
| return BuildBinaryOperation(state.op(), left, right, left_type, right_type, |
| result_type, state.fixed_right_arg(), |
| allocation_mode); |
| } |
| |
| |
| Handle<Code> BinaryOpWithAllocationSiteStub::GenerateCode() { |
| return DoGenerateCode(this); |
| } |
| |
| |
| template <> |
| HValue* CodeStubGraphBuilder<ToBooleanICStub>::BuildCodeInitializedStub() { |
| ToBooleanICStub* stub = casted_stub(); |
| IfBuilder if_true(this); |
| if_true.If<HBranch>(GetParameter(Descriptor::kArgument), stub->hints()); |
| if_true.Then(); |
| if_true.Return(graph()->GetConstantTrue()); |
| if_true.Else(); |
| if_true.End(); |
| return graph()->GetConstantFalse(); |
| } |
| |
| Handle<Code> ToBooleanICStub::GenerateCode() { return DoGenerateCode(this); } |
| |
| } // namespace internal |
| } // namespace v8 |