// Copyright 2013 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/crankshaft/hydrogen.h" #include #include #include "src/allocation-site-scopes.h" #include "src/ast/ast-numbering.h" #include "src/ast/compile-time-value.h" #include "src/ast/scopes.h" #include "src/code-factory.h" #include "src/crankshaft/hydrogen-bce.h" #include "src/crankshaft/hydrogen-canonicalize.h" #include "src/crankshaft/hydrogen-check-elimination.h" #include "src/crankshaft/hydrogen-dce.h" #include "src/crankshaft/hydrogen-dehoist.h" #include "src/crankshaft/hydrogen-environment-liveness.h" #include "src/crankshaft/hydrogen-escape-analysis.h" #include "src/crankshaft/hydrogen-gvn.h" #include "src/crankshaft/hydrogen-infer-representation.h" #include "src/crankshaft/hydrogen-infer-types.h" #include "src/crankshaft/hydrogen-load-elimination.h" #include "src/crankshaft/hydrogen-mark-unreachable.h" #include "src/crankshaft/hydrogen-osr.h" #include "src/crankshaft/hydrogen-range-analysis.h" #include "src/crankshaft/hydrogen-redundant-phi.h" #include "src/crankshaft/hydrogen-removable-simulates.h" #include "src/crankshaft/hydrogen-representation-changes.h" #include "src/crankshaft/hydrogen-sce.h" #include "src/crankshaft/hydrogen-store-elimination.h" #include "src/crankshaft/hydrogen-uint32-analysis.h" #include "src/crankshaft/lithium-allocator.h" #include "src/crankshaft/typing.h" #include "src/field-type.h" #include "src/full-codegen/full-codegen.h" #include "src/globals.h" #include "src/ic/call-optimization.h" #include "src/ic/ic.h" // GetRootConstructor #include "src/ic/ic-inl.h" #include "src/isolate-inl.h" #include "src/runtime/runtime.h" #if V8_TARGET_ARCH_IA32 #include "src/crankshaft/ia32/lithium-codegen-ia32.h" // NOLINT #elif V8_TARGET_ARCH_X64 #include "src/crankshaft/x64/lithium-codegen-x64.h" // NOLINT #elif V8_TARGET_ARCH_ARM64 #include "src/crankshaft/arm64/lithium-codegen-arm64.h" // NOLINT #elif V8_TARGET_ARCH_ARM #include "src/crankshaft/arm/lithium-codegen-arm.h" // NOLINT #elif V8_TARGET_ARCH_PPC #include "src/crankshaft/ppc/lithium-codegen-ppc.h" // NOLINT #elif V8_TARGET_ARCH_MIPS #include "src/crankshaft/mips/lithium-codegen-mips.h" // NOLINT #elif V8_TARGET_ARCH_MIPS64 #include "src/crankshaft/mips64/lithium-codegen-mips64.h" // NOLINT #elif V8_TARGET_ARCH_S390 #include "src/crankshaft/s390/lithium-codegen-s390.h" // NOLINT #elif V8_TARGET_ARCH_X87 #include "src/crankshaft/x87/lithium-codegen-x87.h" // NOLINT #else #error Unsupported target architecture. #endif namespace v8 { namespace internal { const auto GetRegConfig = RegisterConfiguration::Crankshaft; class HOptimizedGraphBuilderWithPositions : public HOptimizedGraphBuilder { public: explicit HOptimizedGraphBuilderWithPositions(CompilationInfo* info) : HOptimizedGraphBuilder(info, true) { SetSourcePosition(info->shared_info()->start_position()); } #define DEF_VISIT(type) \ void Visit##type(type* node) override { \ SourcePosition old_position = SourcePosition::Unknown(); \ if (node->position() != kNoSourcePosition) { \ old_position = source_position(); \ SetSourcePosition(node->position()); \ } \ HOptimizedGraphBuilder::Visit##type(node); \ if (old_position.IsKnown()) { \ set_source_position(old_position); \ } \ } EXPRESSION_NODE_LIST(DEF_VISIT) #undef DEF_VISIT #define DEF_VISIT(type) \ void Visit##type(type* node) override { \ SourcePosition old_position = SourcePosition::Unknown(); \ if (node->position() != kNoSourcePosition) { \ old_position = source_position(); \ SetSourcePosition(node->position()); \ } \ HOptimizedGraphBuilder::Visit##type(node); \ if (old_position.IsKnown()) { \ set_source_position(old_position); \ } \ } STATEMENT_NODE_LIST(DEF_VISIT) #undef DEF_VISIT #define DEF_VISIT(type) \ void Visit##type(type* node) override { \ HOptimizedGraphBuilder::Visit##type(node); \ } DECLARATION_NODE_LIST(DEF_VISIT) #undef DEF_VISIT }; HCompilationJob::Status HCompilationJob::PrepareJobImpl() { if (!isolate()->use_crankshaft() || info()->shared_info()->must_use_ignition_turbo()) { // Crankshaft is entirely disabled. return FAILED; } // Optimization requires a version of fullcode with deoptimization support. // Recompile the unoptimized version of the code if the current version // doesn't have deoptimization support already. // Otherwise, if we are gathering compilation time and space statistics // for hydrogen, gather baseline statistics for a fullcode compilation. bool should_recompile = !info()->shared_info()->has_deoptimization_support(); if (should_recompile || FLAG_hydrogen_stats) { base::ElapsedTimer timer; if (FLAG_hydrogen_stats) { timer.Start(); } if (!Compiler::EnsureDeoptimizationSupport(info())) { return FAILED; } if (FLAG_hydrogen_stats) { isolate()->GetHStatistics()->IncrementFullCodeGen(timer.Elapsed()); } } DCHECK(info()->shared_info()->has_deoptimization_support()); // Check the whitelist for Crankshaft. if (!info()->shared_info()->PassesFilter(FLAG_hydrogen_filter)) { return AbortOptimization(kHydrogenFilter); } Scope* scope = info()->scope(); if (LUnallocated::TooManyParameters(scope->num_parameters())) { // Crankshaft would require too many Lithium operands. return AbortOptimization(kTooManyParameters); } if (info()->is_osr() && LUnallocated::TooManyParametersOrStackSlots(scope->num_parameters(), scope->num_stack_slots())) { // Crankshaft would require too many Lithium operands. return AbortOptimization(kTooManyParametersLocals); } if (IsGeneratorFunction(info()->shared_info()->kind())) { // Crankshaft does not support generators. return AbortOptimization(kGenerator); } if (FLAG_trace_hydrogen) { isolate()->GetHTracer()->TraceCompilation(info()); } // Optimization could have been disabled by the parser. Note that this check // is only needed because the Hydrogen graph builder is missing some bailouts. if (info()->shared_info()->optimization_disabled()) { return AbortOptimization( info()->shared_info()->disable_optimization_reason()); } HOptimizedGraphBuilder* graph_builder = (FLAG_hydrogen_track_positions || isolate()->is_profiling() || FLAG_trace_ic) ? new (info()->zone()) HOptimizedGraphBuilderWithPositions(info()) : new (info()->zone()) HOptimizedGraphBuilder(info(), false); // Type-check the function. AstTyper(info()->isolate(), info()->zone(), info()->closure(), info()->scope(), info()->osr_ast_id(), info()->literal(), graph_builder->bounds()) .Run(); graph_ = graph_builder->CreateGraph(); if (isolate()->has_pending_exception()) { return FAILED; } if (graph_ == NULL) return FAILED; if (info()->dependencies()->HasAborted()) { // Dependency has changed during graph creation. Let's try again later. return RetryOptimization(kBailedOutDueToDependencyChange); } return SUCCEEDED; } HCompilationJob::Status HCompilationJob::ExecuteJobImpl() { DCHECK(graph_ != NULL); BailoutReason bailout_reason = kNoReason; if (graph_->Optimize(&bailout_reason)) { chunk_ = LChunk::NewChunk(graph_); if (chunk_ != NULL) return SUCCEEDED; } else if (bailout_reason != kNoReason) { info()->AbortOptimization(bailout_reason); } return FAILED; } HCompilationJob::Status HCompilationJob::FinalizeJobImpl() { DCHECK(chunk_ != NULL); DCHECK(graph_ != NULL); { // Deferred handles reference objects that were accessible during // graph creation. To make sure that we don't encounter inconsistencies // between graph creation and code generation, we disallow accessing // objects through deferred handles during the latter, with exceptions. DisallowDeferredHandleDereference no_deferred_handle_deref; Handle optimized_code = chunk_->Codegen(); if (optimized_code.is_null()) { if (info()->bailout_reason() == kNoReason) { return AbortOptimization(kCodeGenerationFailed); } return FAILED; } RegisterWeakObjectsInOptimizedCode(optimized_code); info()->SetCode(optimized_code); } // Add to the weak list of optimized code objects. info()->context()->native_context()->AddOptimizedCode(*info()->code()); return SUCCEEDED; } HBasicBlock::HBasicBlock(HGraph* graph) : block_id_(graph->GetNextBlockID()), graph_(graph), phis_(4, graph->zone()), first_(NULL), last_(NULL), end_(NULL), loop_information_(NULL), predecessors_(2, graph->zone()), dominator_(NULL), dominated_blocks_(4, graph->zone()), last_environment_(NULL), argument_count_(-1), first_instruction_index_(-1), last_instruction_index_(-1), deleted_phis_(4, graph->zone()), parent_loop_header_(NULL), inlined_entry_block_(NULL), is_inline_return_target_(false), is_reachable_(true), dominates_loop_successors_(false), is_osr_entry_(false), is_ordered_(false) { } Isolate* HBasicBlock::isolate() const { return graph_->isolate(); } void HBasicBlock::MarkUnreachable() { is_reachable_ = false; } void HBasicBlock::AttachLoopInformation() { DCHECK(!IsLoopHeader()); loop_information_ = new(zone()) HLoopInformation(this, zone()); } void HBasicBlock::DetachLoopInformation() { DCHECK(IsLoopHeader()); loop_information_ = NULL; } void HBasicBlock::AddPhi(HPhi* phi) { DCHECK(!IsStartBlock()); phis_.Add(phi, zone()); phi->SetBlock(this); } void HBasicBlock::RemovePhi(HPhi* phi) { DCHECK(phi->block() == this); DCHECK(phis_.Contains(phi)); phi->Kill(); phis_.RemoveElement(phi); phi->SetBlock(NULL); } void HBasicBlock::AddInstruction(HInstruction* instr, SourcePosition position) { DCHECK(!IsStartBlock() || !IsFinished()); DCHECK(!instr->IsLinked()); DCHECK(!IsFinished()); if (position.IsKnown()) { instr->set_position(position); } if (first_ == NULL) { DCHECK(last_environment() != NULL); DCHECK(!last_environment()->ast_id().IsNone()); HBlockEntry* entry = new(zone()) HBlockEntry(); entry->InitializeAsFirst(this); if (position.IsKnown()) { entry->set_position(position); } else { DCHECK(!FLAG_hydrogen_track_positions || !graph()->info()->IsOptimizing() || instr->IsAbnormalExit()); } first_ = last_ = entry; } instr->InsertAfter(last_); } HPhi* HBasicBlock::AddNewPhi(int merged_index) { if (graph()->IsInsideNoSideEffectsScope()) { merged_index = HPhi::kInvalidMergedIndex; } HPhi* phi = new(zone()) HPhi(merged_index, zone()); AddPhi(phi); return phi; } HSimulate* HBasicBlock::CreateSimulate(BailoutId ast_id, RemovableSimulate removable) { DCHECK(HasEnvironment()); HEnvironment* environment = last_environment(); DCHECK(ast_id.IsNone() || ast_id == BailoutId::StubEntry() || environment->closure()->shared()->VerifyBailoutId(ast_id)); int push_count = environment->push_count(); int pop_count = environment->pop_count(); HSimulate* instr = new(zone()) HSimulate(ast_id, pop_count, zone(), removable); #ifdef DEBUG instr->set_closure(environment->closure()); #endif // Order of pushed values: newest (top of stack) first. This allows // HSimulate::MergeWith() to easily append additional pushed values // that are older (from further down the stack). for (int i = 0; i < push_count; ++i) { instr->AddPushedValue(environment->ExpressionStackAt(i)); } for (GrowableBitVector::Iterator it(environment->assigned_variables(), zone()); !it.Done(); it.Advance()) { int index = it.Current(); instr->AddAssignedValue(index, environment->Lookup(index)); } environment->ClearHistory(); return instr; } void HBasicBlock::Finish(HControlInstruction* end, SourcePosition position) { DCHECK(!IsFinished()); AddInstruction(end, position); end_ = end; for (HSuccessorIterator it(end); !it.Done(); it.Advance()) { it.Current()->RegisterPredecessor(this); } } void HBasicBlock::Goto(HBasicBlock* block, SourcePosition position, FunctionState* state, bool add_simulate) { bool drop_extra = state != NULL && state->inlining_kind() == NORMAL_RETURN; if (block->IsInlineReturnTarget()) { HEnvironment* env = last_environment(); int argument_count = env->arguments_environment()->parameter_count(); AddInstruction(new(zone()) HLeaveInlined(state->entry(), argument_count), position); UpdateEnvironment(last_environment()->DiscardInlined(drop_extra)); } if (add_simulate) AddNewSimulate(BailoutId::None(), position); HGoto* instr = new(zone()) HGoto(block); Finish(instr, position); } void HBasicBlock::AddLeaveInlined(HValue* return_value, FunctionState* state, SourcePosition position) { HBasicBlock* target = state->function_return(); bool drop_extra = state->inlining_kind() == NORMAL_RETURN; DCHECK(target->IsInlineReturnTarget()); DCHECK(return_value != NULL); HEnvironment* env = last_environment(); int argument_count = env->arguments_environment()->parameter_count(); AddInstruction(new(zone()) HLeaveInlined(state->entry(), argument_count), position); UpdateEnvironment(last_environment()->DiscardInlined(drop_extra)); last_environment()->Push(return_value); AddNewSimulate(BailoutId::None(), position); HGoto* instr = new(zone()) HGoto(target); Finish(instr, position); } void HBasicBlock::SetInitialEnvironment(HEnvironment* env) { DCHECK(!HasEnvironment()); DCHECK(first() == NULL); UpdateEnvironment(env); } void HBasicBlock::UpdateEnvironment(HEnvironment* env) { last_environment_ = env; graph()->update_maximum_environment_size(env->first_expression_index()); } void HBasicBlock::SetJoinId(BailoutId ast_id) { int length = predecessors_.length(); DCHECK(length > 0); for (int i = 0; i < length; i++) { HBasicBlock* predecessor = predecessors_[i]; DCHECK(predecessor->end()->IsGoto()); HSimulate* simulate = HSimulate::cast(predecessor->end()->previous()); DCHECK(i != 0 || (predecessor->last_environment()->closure().is_null() || predecessor->last_environment()->closure()->shared() ->VerifyBailoutId(ast_id))); simulate->set_ast_id(ast_id); predecessor->last_environment()->set_ast_id(ast_id); } } bool HBasicBlock::Dominates(HBasicBlock* other) const { HBasicBlock* current = other->dominator(); while (current != NULL) { if (current == this) return true; current = current->dominator(); } return false; } bool HBasicBlock::EqualToOrDominates(HBasicBlock* other) const { if (this == other) return true; return Dominates(other); } int HBasicBlock::LoopNestingDepth() const { const HBasicBlock* current = this; int result = (current->IsLoopHeader()) ? 1 : 0; while (current->parent_loop_header() != NULL) { current = current->parent_loop_header(); result++; } return result; } void HBasicBlock::PostProcessLoopHeader(IterationStatement* stmt) { DCHECK(IsLoopHeader()); SetJoinId(stmt->EntryId()); if (predecessors()->length() == 1) { // This is a degenerated loop. DetachLoopInformation(); return; } // Only the first entry into the loop is from outside the loop. All other // entries must be back edges. for (int i = 1; i < predecessors()->length(); ++i) { loop_information()->RegisterBackEdge(predecessors()->at(i)); } } void HBasicBlock::MarkSuccEdgeUnreachable(int succ) { DCHECK(IsFinished()); HBasicBlock* succ_block = end()->SuccessorAt(succ); DCHECK(succ_block->predecessors()->length() == 1); succ_block->MarkUnreachable(); } void HBasicBlock::RegisterPredecessor(HBasicBlock* pred) { if (HasPredecessor()) { // Only loop header blocks can have a predecessor added after // instructions have been added to the block (they have phis for all // values in the environment, these phis may be eliminated later). DCHECK(IsLoopHeader() || first_ == NULL); HEnvironment* incoming_env = pred->last_environment(); if (IsLoopHeader()) { DCHECK_EQ(phis()->length(), incoming_env->length()); for (int i = 0; i < phis_.length(); ++i) { phis_[i]->AddInput(incoming_env->values()->at(i)); } } else { last_environment()->AddIncomingEdge(this, pred->last_environment()); } } else if (!HasEnvironment() && !IsFinished()) { DCHECK(!IsLoopHeader()); SetInitialEnvironment(pred->last_environment()->Copy()); } predecessors_.Add(pred, zone()); } void HBasicBlock::AddDominatedBlock(HBasicBlock* block) { DCHECK(!dominated_blocks_.Contains(block)); // Keep the list of dominated blocks sorted such that if there is two // succeeding block in this list, the predecessor is before the successor. int index = 0; while (index < dominated_blocks_.length() && dominated_blocks_[index]->block_id() < block->block_id()) { ++index; } dominated_blocks_.InsertAt(index, block, zone()); } void HBasicBlock::AssignCommonDominator(HBasicBlock* other) { if (dominator_ == NULL) { dominator_ = other; other->AddDominatedBlock(this); } else if (other->dominator() != NULL) { HBasicBlock* first = dominator_; HBasicBlock* second = other; while (first != second) { if (first->block_id() > second->block_id()) { first = first->dominator(); } else { second = second->dominator(); } DCHECK(first != NULL && second != NULL); } if (dominator_ != first) { DCHECK(dominator_->dominated_blocks_.Contains(this)); dominator_->dominated_blocks_.RemoveElement(this); dominator_ = first; first->AddDominatedBlock(this); } } } void HBasicBlock::AssignLoopSuccessorDominators() { // Mark blocks that dominate all subsequent reachable blocks inside their // loop. Exploit the fact that blocks are sorted in reverse post order. When // the loop is visited in increasing block id order, if the number of // non-loop-exiting successor edges at the dominator_candidate block doesn't // exceed the number of previously encountered predecessor edges, there is no // path from the loop header to any block with higher id that doesn't go // through the dominator_candidate block. In this case, the // dominator_candidate block is guaranteed to dominate all blocks reachable // from it with higher ids. HBasicBlock* last = loop_information()->GetLastBackEdge(); int outstanding_successors = 1; // one edge from the pre-header // Header always dominates everything. MarkAsLoopSuccessorDominator(); for (int j = block_id(); j <= last->block_id(); ++j) { HBasicBlock* dominator_candidate = graph_->blocks()->at(j); for (HPredecessorIterator it(dominator_candidate); !it.Done(); it.Advance()) { HBasicBlock* predecessor = it.Current(); // Don't count back edges. if (predecessor->block_id() < dominator_candidate->block_id()) { outstanding_successors--; } } // If more successors than predecessors have been seen in the loop up to // now, it's not possible to guarantee that the current block dominates // all of the blocks with higher IDs. In this case, assume conservatively // that those paths through loop that don't go through the current block // contain all of the loop's dependencies. Also be careful to record // dominator information about the current loop that's being processed, // and not nested loops, which will be processed when // AssignLoopSuccessorDominators gets called on their header. DCHECK(outstanding_successors >= 0); HBasicBlock* parent_loop_header = dominator_candidate->parent_loop_header(); if (outstanding_successors == 0 && (parent_loop_header == this && !dominator_candidate->IsLoopHeader())) { dominator_candidate->MarkAsLoopSuccessorDominator(); } HControlInstruction* end = dominator_candidate->end(); for (HSuccessorIterator it(end); !it.Done(); it.Advance()) { HBasicBlock* successor = it.Current(); // Only count successors that remain inside the loop and don't loop back // to a loop header. if (successor->block_id() > dominator_candidate->block_id() && successor->block_id() <= last->block_id()) { // Backwards edges must land on loop headers. DCHECK(successor->block_id() > dominator_candidate->block_id() || successor->IsLoopHeader()); outstanding_successors++; } } } } int HBasicBlock::PredecessorIndexOf(HBasicBlock* predecessor) const { for (int i = 0; i < predecessors_.length(); ++i) { if (predecessors_[i] == predecessor) return i; } UNREACHABLE(); return -1; } #ifdef DEBUG void HBasicBlock::Verify() { // Check that every block is finished. DCHECK(IsFinished()); DCHECK(block_id() >= 0); // Check that the incoming edges are in edge split form. if (predecessors_.length() > 1) { for (int i = 0; i < predecessors_.length(); ++i) { DCHECK(predecessors_[i]->end()->SecondSuccessor() == NULL); } } } #endif void HLoopInformation::RegisterBackEdge(HBasicBlock* block) { this->back_edges_.Add(block, block->zone()); AddBlock(block); } HBasicBlock* HLoopInformation::GetLastBackEdge() const { int max_id = -1; HBasicBlock* result = NULL; for (int i = 0; i < back_edges_.length(); ++i) { HBasicBlock* cur = back_edges_[i]; if (cur->block_id() > max_id) { max_id = cur->block_id(); result = cur; } } return result; } void HLoopInformation::AddBlock(HBasicBlock* block) { if (block == loop_header()) return; if (block->parent_loop_header() == loop_header()) return; if (block->parent_loop_header() != NULL) { AddBlock(block->parent_loop_header()); } else { block->set_parent_loop_header(loop_header()); blocks_.Add(block, block->zone()); for (int i = 0; i < block->predecessors()->length(); ++i) { AddBlock(block->predecessors()->at(i)); } } } #ifdef DEBUG // Checks reachability of the blocks in this graph and stores a bit in // the BitVector "reachable()" for every block that can be reached // from the start block of the graph. If "dont_visit" is non-null, the given // block is treated as if it would not be part of the graph. "visited_count()" // returns the number of reachable blocks. class ReachabilityAnalyzer BASE_EMBEDDED { public: ReachabilityAnalyzer(HBasicBlock* entry_block, int block_count, HBasicBlock* dont_visit) : visited_count_(0), stack_(16, entry_block->zone()), reachable_(block_count, entry_block->zone()), dont_visit_(dont_visit) { PushBlock(entry_block); Analyze(); } int visited_count() const { return visited_count_; } const BitVector* reachable() const { return &reachable_; } private: void PushBlock(HBasicBlock* block) { if (block != NULL && block != dont_visit_ && !reachable_.Contains(block->block_id())) { reachable_.Add(block->block_id()); stack_.Add(block, block->zone()); visited_count_++; } } void Analyze() { while (!stack_.is_empty()) { HControlInstruction* end = stack_.RemoveLast()->end(); for (HSuccessorIterator it(end); !it.Done(); it.Advance()) { PushBlock(it.Current()); } } } int visited_count_; ZoneList stack_; BitVector reachable_; HBasicBlock* dont_visit_; }; void HGraph::Verify(bool do_full_verify) const { Heap::RelocationLock relocation_lock(isolate()->heap()); AllowHandleDereference allow_deref; AllowDeferredHandleDereference allow_deferred_deref; for (int i = 0; i < blocks_.length(); i++) { HBasicBlock* block = blocks_.at(i); block->Verify(); // Check that every block contains at least one node and that only the last // node is a control instruction. HInstruction* current = block->first(); DCHECK(current != NULL && current->IsBlockEntry()); while (current != NULL) { DCHECK((current->next() == NULL) == current->IsControlInstruction()); DCHECK(current->block() == block); current->Verify(); current = current->next(); } // Check that successors are correctly set. HBasicBlock* first = block->end()->FirstSuccessor(); HBasicBlock* second = block->end()->SecondSuccessor(); DCHECK(second == NULL || first != NULL); // Check that the predecessor array is correct. if (first != NULL) { DCHECK(first->predecessors()->Contains(block)); if (second != NULL) { DCHECK(second->predecessors()->Contains(block)); } } // Check that phis have correct arguments. for (int j = 0; j < block->phis()->length(); j++) { HPhi* phi = block->phis()->at(j); phi->Verify(); } // Check that all join blocks have predecessors that end with an // unconditional goto and agree on their environment node id. if (block->predecessors()->length() >= 2) { BailoutId id = block->predecessors()->first()->last_environment()->ast_id(); for (int k = 0; k < block->predecessors()->length(); k++) { HBasicBlock* predecessor = block->predecessors()->at(k); DCHECK(predecessor->end()->IsGoto() || predecessor->end()->IsDeoptimize()); DCHECK(predecessor->last_environment()->ast_id() == id); } } } // Check special property of first block to have no predecessors. DCHECK(blocks_.at(0)->predecessors()->is_empty()); if (do_full_verify) { // Check that the graph is fully connected. ReachabilityAnalyzer analyzer(entry_block_, blocks_.length(), NULL); DCHECK(analyzer.visited_count() == blocks_.length()); // Check that entry block dominator is NULL. DCHECK(entry_block_->dominator() == NULL); // Check dominators. for (int i = 0; i < blocks_.length(); ++i) { HBasicBlock* block = blocks_.at(i); if (block->dominator() == NULL) { // Only start block may have no dominator assigned to. DCHECK(i == 0); } else { // Assert that block is unreachable if dominator must not be visited. ReachabilityAnalyzer dominator_analyzer(entry_block_, blocks_.length(), block->dominator()); DCHECK(!dominator_analyzer.reachable()->Contains(block->block_id())); } } } } #endif HConstant* HGraph::GetConstant(SetOncePointer* pointer, int32_t value) { if (!pointer->is_set()) { // Can't pass GetInvalidContext() to HConstant::New, because that will // recursively call GetConstant HConstant* constant = HConstant::New(isolate(), zone(), NULL, value); constant->InsertAfter(entry_block()->first()); pointer->set(constant); return constant; } return ReinsertConstantIfNecessary(pointer->get()); } HConstant* HGraph::ReinsertConstantIfNecessary(HConstant* constant) { if (!constant->IsLinked()) { // The constant was removed from the graph. Reinsert. constant->ClearFlag(HValue::kIsDead); constant->InsertAfter(entry_block()->first()); } return constant; } HConstant* HGraph::GetConstant0() { return GetConstant(&constant_0_, 0); } HConstant* HGraph::GetConstant1() { return GetConstant(&constant_1_, 1); } HConstant* HGraph::GetConstantMinus1() { return GetConstant(&constant_minus1_, -1); } HConstant* HGraph::GetConstantBool(bool value) { return value ? GetConstantTrue() : GetConstantFalse(); } #define DEFINE_GET_CONSTANT(Name, name, constant, type, htype, boolean_value, \ undetectable) \ HConstant* HGraph::GetConstant##Name() { \ if (!constant_##name##_.is_set()) { \ HConstant* constant = new (zone()) HConstant( \ Unique::CreateImmovable(isolate()->factory()->constant()), \ Unique::CreateImmovable(isolate()->factory()->type##_map()), \ false, Representation::Tagged(), htype, true, boolean_value, \ undetectable, ODDBALL_TYPE); \ constant->InsertAfter(entry_block()->first()); \ constant_##name##_.set(constant); \ } \ return ReinsertConstantIfNecessary(constant_##name##_.get()); \ } DEFINE_GET_CONSTANT(Undefined, undefined, undefined_value, undefined, HType::Undefined(), false, true) DEFINE_GET_CONSTANT(True, true, true_value, boolean, HType::Boolean(), true, false) DEFINE_GET_CONSTANT(False, false, false_value, boolean, HType::Boolean(), false, false) DEFINE_GET_CONSTANT(Hole, the_hole, the_hole_value, the_hole, HType::None(), false, false) DEFINE_GET_CONSTANT(Null, null, null_value, null, HType::Null(), false, true) DEFINE_GET_CONSTANT(OptimizedOut, optimized_out, optimized_out, optimized_out, HType::None(), false, false) #undef DEFINE_GET_CONSTANT #define DEFINE_IS_CONSTANT(Name, name) \ bool HGraph::IsConstant##Name(HConstant* constant) { \ return constant_##name##_.is_set() && constant == constant_##name##_.get(); \ } DEFINE_IS_CONSTANT(Undefined, undefined) DEFINE_IS_CONSTANT(0, 0) DEFINE_IS_CONSTANT(1, 1) DEFINE_IS_CONSTANT(Minus1, minus1) DEFINE_IS_CONSTANT(True, true) DEFINE_IS_CONSTANT(False, false) DEFINE_IS_CONSTANT(Hole, the_hole) DEFINE_IS_CONSTANT(Null, null) #undef DEFINE_IS_CONSTANT HConstant* HGraph::GetInvalidContext() { return GetConstant(&constant_invalid_context_, 0xFFFFC0C7); } bool HGraph::IsStandardConstant(HConstant* constant) { if (IsConstantUndefined(constant)) return true; if (IsConstant0(constant)) return true; if (IsConstant1(constant)) return true; if (IsConstantMinus1(constant)) return true; if (IsConstantTrue(constant)) return true; if (IsConstantFalse(constant)) return true; if (IsConstantHole(constant)) return true; if (IsConstantNull(constant)) return true; return false; } HGraphBuilder::IfBuilder::IfBuilder() : builder_(NULL), needs_compare_(true) {} HGraphBuilder::IfBuilder::IfBuilder(HGraphBuilder* builder) : needs_compare_(true) { Initialize(builder); } HGraphBuilder::IfBuilder::IfBuilder(HGraphBuilder* builder, HIfContinuation* continuation) : needs_compare_(false), first_true_block_(NULL), first_false_block_(NULL) { InitializeDontCreateBlocks(builder); continuation->Continue(&first_true_block_, &first_false_block_); } void HGraphBuilder::IfBuilder::InitializeDontCreateBlocks( HGraphBuilder* builder) { builder_ = builder; finished_ = false; did_then_ = false; did_else_ = false; did_else_if_ = false; did_and_ = false; did_or_ = false; captured_ = false; pending_merge_block_ = false; split_edge_merge_block_ = NULL; merge_at_join_blocks_ = NULL; normal_merge_at_join_block_count_ = 0; deopt_merge_at_join_block_count_ = 0; } void HGraphBuilder::IfBuilder::Initialize(HGraphBuilder* builder) { InitializeDontCreateBlocks(builder); HEnvironment* env = builder->environment(); first_true_block_ = builder->CreateBasicBlock(env->Copy()); first_false_block_ = builder->CreateBasicBlock(env->Copy()); } HControlInstruction* HGraphBuilder::IfBuilder::AddCompare( HControlInstruction* compare) { DCHECK(did_then_ == did_else_); if (did_else_) { // Handle if-then-elseif did_else_if_ = true; did_else_ = false; did_then_ = false; did_and_ = false; did_or_ = false; pending_merge_block_ = false; split_edge_merge_block_ = NULL; HEnvironment* env = builder()->environment(); first_true_block_ = builder()->CreateBasicBlock(env->Copy()); first_false_block_ = builder()->CreateBasicBlock(env->Copy()); } if (split_edge_merge_block_ != NULL) { HEnvironment* env = first_false_block_->last_environment(); HBasicBlock* split_edge = builder()->CreateBasicBlock(env->Copy()); if (did_or_) { compare->SetSuccessorAt(0, split_edge); compare->SetSuccessorAt(1, first_false_block_); } else { compare->SetSuccessorAt(0, first_true_block_); compare->SetSuccessorAt(1, split_edge); } builder()->GotoNoSimulate(split_edge, split_edge_merge_block_); } else { compare->SetSuccessorAt(0, first_true_block_); compare->SetSuccessorAt(1, first_false_block_); } builder()->FinishCurrentBlock(compare); needs_compare_ = false; return compare; } void HGraphBuilder::IfBuilder::Or() { DCHECK(!needs_compare_); DCHECK(!did_and_); did_or_ = true; HEnvironment* env = first_false_block_->last_environment(); if (split_edge_merge_block_ == NULL) { split_edge_merge_block_ = builder()->CreateBasicBlock(env->Copy()); builder()->GotoNoSimulate(first_true_block_, split_edge_merge_block_); first_true_block_ = split_edge_merge_block_; } builder()->set_current_block(first_false_block_); first_false_block_ = builder()->CreateBasicBlock(env->Copy()); } void HGraphBuilder::IfBuilder::And() { DCHECK(!needs_compare_); DCHECK(!did_or_); did_and_ = true; HEnvironment* env = first_false_block_->last_environment(); if (split_edge_merge_block_ == NULL) { split_edge_merge_block_ = builder()->CreateBasicBlock(env->Copy()); builder()->GotoNoSimulate(first_false_block_, split_edge_merge_block_); first_false_block_ = split_edge_merge_block_; } builder()->set_current_block(first_true_block_); first_true_block_ = builder()->CreateBasicBlock(env->Copy()); } void HGraphBuilder::IfBuilder::CaptureContinuation( HIfContinuation* continuation) { DCHECK(!did_else_if_); DCHECK(!finished_); DCHECK(!captured_); HBasicBlock* true_block = NULL; HBasicBlock* false_block = NULL; Finish(&true_block, &false_block); DCHECK(true_block != NULL); DCHECK(false_block != NULL); continuation->Capture(true_block, false_block); captured_ = true; builder()->set_current_block(NULL); End(); } void HGraphBuilder::IfBuilder::JoinContinuation(HIfContinuation* continuation) { DCHECK(!did_else_if_); DCHECK(!finished_); DCHECK(!captured_); HBasicBlock* true_block = NULL; HBasicBlock* false_block = NULL; Finish(&true_block, &false_block); merge_at_join_blocks_ = NULL; if (true_block != NULL && !true_block->IsFinished()) { DCHECK(continuation->IsTrueReachable()); builder()->GotoNoSimulate(true_block, continuation->true_branch()); } if (false_block != NULL && !false_block->IsFinished()) { DCHECK(continuation->IsFalseReachable()); builder()->GotoNoSimulate(false_block, continuation->false_branch()); } captured_ = true; End(); } void HGraphBuilder::IfBuilder::Then() { DCHECK(!captured_); DCHECK(!finished_); did_then_ = true; if (needs_compare_) { // Handle if's without any expressions, they jump directly to the "else" // branch. However, we must pretend that the "then" branch is reachable, // so that the graph builder visits it and sees any live range extending // constructs within it. HConstant* constant_false = builder()->graph()->GetConstantFalse(); ToBooleanHints boolean_type = ToBooleanHint::kBoolean; HBranch* branch = builder()->New( constant_false, boolean_type, first_true_block_, first_false_block_); builder()->FinishCurrentBlock(branch); } builder()->set_current_block(first_true_block_); pending_merge_block_ = true; } void HGraphBuilder::IfBuilder::Else() { DCHECK(did_then_); DCHECK(!captured_); DCHECK(!finished_); AddMergeAtJoinBlock(false); builder()->set_current_block(first_false_block_); pending_merge_block_ = true; did_else_ = true; } void HGraphBuilder::IfBuilder::Deopt(DeoptimizeReason reason) { DCHECK(did_then_); builder()->Add(reason, Deoptimizer::EAGER); AddMergeAtJoinBlock(true); } void HGraphBuilder::IfBuilder::Return(HValue* value) { HValue* parameter_count = builder()->graph()->GetConstantMinus1(); builder()->FinishExitCurrentBlock( builder()->New(value, parameter_count)); AddMergeAtJoinBlock(false); } void HGraphBuilder::IfBuilder::AddMergeAtJoinBlock(bool deopt) { if (!pending_merge_block_) return; HBasicBlock* block = builder()->current_block(); DCHECK(block == NULL || !block->IsFinished()); MergeAtJoinBlock* record = new (builder()->zone()) MergeAtJoinBlock(block, deopt, merge_at_join_blocks_); merge_at_join_blocks_ = record; if (block != NULL) { DCHECK(block->end() == NULL); if (deopt) { normal_merge_at_join_block_count_++; } else { deopt_merge_at_join_block_count_++; } } builder()->set_current_block(NULL); pending_merge_block_ = false; } void HGraphBuilder::IfBuilder::Finish() { DCHECK(!finished_); if (!did_then_) { Then(); } AddMergeAtJoinBlock(false); if (!did_else_) { Else(); AddMergeAtJoinBlock(false); } finished_ = true; } void HGraphBuilder::IfBuilder::Finish(HBasicBlock** then_continuation, HBasicBlock** else_continuation) { Finish(); MergeAtJoinBlock* else_record = merge_at_join_blocks_; if (else_continuation != NULL) { *else_continuation = else_record->block_; } MergeAtJoinBlock* then_record = else_record->next_; if (then_continuation != NULL) { *then_continuation = then_record->block_; } DCHECK(then_record->next_ == NULL); } void HGraphBuilder::IfBuilder::EndUnreachable() { if (captured_) return; Finish(); builder()->set_current_block(nullptr); } void HGraphBuilder::IfBuilder::End() { if (captured_) return; Finish(); int total_merged_blocks = normal_merge_at_join_block_count_ + deopt_merge_at_join_block_count_; DCHECK(total_merged_blocks >= 1); HBasicBlock* merge_block = total_merged_blocks == 1 ? NULL : builder()->graph()->CreateBasicBlock(); // Merge non-deopt blocks first to ensure environment has right size for // padding. MergeAtJoinBlock* current = merge_at_join_blocks_; while (current != NULL) { if (!current->deopt_ && current->block_ != NULL) { // If there is only one block that makes it through to the end of the // if, then just set it as the current block and continue rather then // creating an unnecessary merge block. if (total_merged_blocks == 1) { builder()->set_current_block(current->block_); return; } builder()->GotoNoSimulate(current->block_, merge_block); } current = current->next_; } // Merge deopt blocks, padding when necessary. current = merge_at_join_blocks_; while (current != NULL) { if (current->deopt_ && current->block_ != NULL) { current->block_->FinishExit( HAbnormalExit::New(builder()->isolate(), builder()->zone(), NULL), SourcePosition::Unknown()); } current = current->next_; } builder()->set_current_block(merge_block); } HGraphBuilder::LoopBuilder::LoopBuilder(HGraphBuilder* builder) { Initialize(builder, NULL, kWhileTrue, NULL); } HGraphBuilder::LoopBuilder::LoopBuilder(HGraphBuilder* builder, HValue* context, LoopBuilder::Direction direction) { Initialize(builder, context, direction, builder->graph()->GetConstant1()); } HGraphBuilder::LoopBuilder::LoopBuilder(HGraphBuilder* builder, HValue* context, LoopBuilder::Direction direction, HValue* increment_amount) { Initialize(builder, context, direction, increment_amount); increment_amount_ = increment_amount; } void HGraphBuilder::LoopBuilder::Initialize(HGraphBuilder* builder, HValue* context, Direction direction, HValue* increment_amount) { builder_ = builder; context_ = context; direction_ = direction; increment_amount_ = increment_amount; finished_ = false; header_block_ = builder->CreateLoopHeaderBlock(); body_block_ = NULL; exit_block_ = NULL; exit_trampoline_block_ = NULL; } HValue* HGraphBuilder::LoopBuilder::BeginBody( HValue* initial, HValue* terminating, Token::Value token) { DCHECK(direction_ != kWhileTrue); HEnvironment* env = builder_->environment(); phi_ = header_block_->AddNewPhi(env->values()->length()); phi_->AddInput(initial); env->Push(initial); builder_->GotoNoSimulate(header_block_); HEnvironment* body_env = env->Copy(); HEnvironment* exit_env = env->Copy(); // Remove the phi from the expression stack body_env->Pop(); exit_env->Pop(); body_block_ = builder_->CreateBasicBlock(body_env); exit_block_ = builder_->CreateBasicBlock(exit_env); builder_->set_current_block(header_block_); env->Pop(); builder_->FinishCurrentBlock(builder_->New( phi_, terminating, token, body_block_, exit_block_)); builder_->set_current_block(body_block_); if (direction_ == kPreIncrement || direction_ == kPreDecrement) { Isolate* isolate = builder_->isolate(); HValue* one = builder_->graph()->GetConstant1(); if (direction_ == kPreIncrement) { increment_ = HAdd::New(isolate, zone(), context_, phi_, one); } else { increment_ = HSub::New(isolate, zone(), context_, phi_, one); } increment_->ClearFlag(HValue::kCanOverflow); builder_->AddInstruction(increment_); return increment_; } else { return phi_; } } void HGraphBuilder::LoopBuilder::BeginBody(int drop_count) { DCHECK(direction_ == kWhileTrue); HEnvironment* env = builder_->environment(); builder_->GotoNoSimulate(header_block_); builder_->set_current_block(header_block_); env->Drop(drop_count); } void HGraphBuilder::LoopBuilder::Break() { if (exit_trampoline_block_ == NULL) { // Its the first time we saw a break. if (direction_ == kWhileTrue) { HEnvironment* env = builder_->environment()->Copy(); exit_trampoline_block_ = builder_->CreateBasicBlock(env); } else { HEnvironment* env = exit_block_->last_environment()->Copy(); exit_trampoline_block_ = builder_->CreateBasicBlock(env); builder_->GotoNoSimulate(exit_block_, exit_trampoline_block_); } } builder_->GotoNoSimulate(exit_trampoline_block_); builder_->set_current_block(NULL); } void HGraphBuilder::LoopBuilder::EndBody() { DCHECK(!finished_); if (direction_ == kPostIncrement || direction_ == kPostDecrement) { Isolate* isolate = builder_->isolate(); if (direction_ == kPostIncrement) { increment_ = HAdd::New(isolate, zone(), context_, phi_, increment_amount_); } else { increment_ = HSub::New(isolate, zone(), context_, phi_, increment_amount_); } increment_->ClearFlag(HValue::kCanOverflow); builder_->AddInstruction(increment_); } if (direction_ != kWhileTrue) { // Push the new increment value on the expression stack to merge into // the phi. builder_->environment()->Push(increment_); } HBasicBlock* last_block = builder_->current_block(); builder_->GotoNoSimulate(last_block, header_block_); header_block_->loop_information()->RegisterBackEdge(last_block); if (exit_trampoline_block_ != NULL) { builder_->set_current_block(exit_trampoline_block_); } else { builder_->set_current_block(exit_block_); } finished_ = true; } HGraph* HGraphBuilder::CreateGraph() { DCHECK(!FLAG_minimal); graph_ = new (zone()) HGraph(info_, descriptor_); if (FLAG_hydrogen_stats) isolate()->GetHStatistics()->Initialize(info_); CompilationPhase phase("H_Block building", info_); set_current_block(graph()->entry_block()); if (!BuildGraph()) return NULL; graph()->FinalizeUniqueness(); return graph_; } HInstruction* HGraphBuilder::AddInstruction(HInstruction* instr) { DCHECK(current_block() != NULL); DCHECK(!FLAG_hydrogen_track_positions || position_.IsKnown() || !info_->IsOptimizing()); current_block()->AddInstruction(instr, source_position()); if (graph()->IsInsideNoSideEffectsScope()) { instr->SetFlag(HValue::kHasNoObservableSideEffects); } return instr; } void HGraphBuilder::FinishCurrentBlock(HControlInstruction* last) { DCHECK(!FLAG_hydrogen_track_positions || !info_->IsOptimizing() || position_.IsKnown()); current_block()->Finish(last, source_position()); if (last->IsReturn() || last->IsAbnormalExit()) { set_current_block(NULL); } } void HGraphBuilder::FinishExitCurrentBlock(HControlInstruction* instruction) { DCHECK(!FLAG_hydrogen_track_positions || !info_->IsOptimizing() || position_.IsKnown()); current_block()->FinishExit(instruction, source_position()); if (instruction->IsReturn() || instruction->IsAbnormalExit()) { set_current_block(NULL); } } void HGraphBuilder::AddIncrementCounter(StatsCounter* counter) { if (FLAG_native_code_counters && counter->Enabled()) { HValue* reference = Add(ExternalReference(counter)); HValue* old_value = Add(reference, nullptr, HObjectAccess::ForCounter()); HValue* new_value = AddUncasted(old_value, graph()->GetConstant1()); new_value->ClearFlag(HValue::kCanOverflow); // Ignore counter overflow Add(reference, HObjectAccess::ForCounter(), new_value, STORE_TO_INITIALIZED_ENTRY); } } void HGraphBuilder::AddSimulate(BailoutId id, RemovableSimulate removable) { DCHECK(current_block() != NULL); DCHECK(!graph()->IsInsideNoSideEffectsScope()); current_block()->AddNewSimulate(id, source_position(), removable); } HBasicBlock* HGraphBuilder::CreateBasicBlock(HEnvironment* env) { HBasicBlock* b = graph()->CreateBasicBlock(); b->SetInitialEnvironment(env); return b; } HBasicBlock* HGraphBuilder::CreateLoopHeaderBlock() { HBasicBlock* header = graph()->CreateBasicBlock(); HEnvironment* entry_env = environment()->CopyAsLoopHeader(header); header->SetInitialEnvironment(entry_env); header->AttachLoopInformation(); return header; } HValue* HGraphBuilder::BuildGetElementsKind(HValue* object) { HValue* map = Add(object, nullptr, HObjectAccess::ForMap()); HValue* bit_field2 = Add(map, nullptr, HObjectAccess::ForMapBitField2()); return BuildDecodeField(bit_field2); } HValue* HGraphBuilder::BuildEnumLength(HValue* map) { NoObservableSideEffectsScope scope(this); HValue* bit_field3 = Add(map, nullptr, HObjectAccess::ForMapBitField3()); return BuildDecodeField(bit_field3); } HValue* HGraphBuilder::BuildCheckHeapObject(HValue* obj) { if (obj->type().IsHeapObject()) return obj; return Add(obj); } void HGraphBuilder::FinishExitWithHardDeoptimization(DeoptimizeReason reason) { Add(reason, Deoptimizer::EAGER); FinishExitCurrentBlock(New()); } HValue* HGraphBuilder::BuildCheckString(HValue* string) { if (!string->type().IsString()) { DCHECK(!string->IsConstant() || !HConstant::cast(string)->HasStringValue()); BuildCheckHeapObject(string); return Add(string, HCheckInstanceType::IS_STRING); } return string; } HValue* HGraphBuilder::BuildWrapReceiver(HValue* object, HValue* checked) { if (object->type().IsJSObject()) return object; HValue* function = checked->ActualValue(); if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { Handle f = Handle::cast( HConstant::cast(function)->handle(isolate())); SharedFunctionInfo* shared = f->shared(); if (is_strict(shared->language_mode()) || shared->native()) return object; } return Add(object, checked); } HValue* HGraphBuilder::BuildCheckAndGrowElementsCapacity( HValue* object, HValue* elements, ElementsKind kind, HValue* length, HValue* capacity, HValue* key) { HValue* max_gap = Add(static_cast(JSObject::kMaxGap)); HValue* max_capacity = AddUncasted(capacity, max_gap); Add(key, max_capacity); HValue* new_capacity = BuildNewElementsCapacity(key); HValue* new_elements = BuildGrowElementsCapacity(object, elements, kind, kind, length, new_capacity); return new_elements; } HValue* HGraphBuilder::BuildCheckForCapacityGrow( HValue* object, HValue* elements, ElementsKind kind, HValue* length, HValue* key, bool is_js_array, PropertyAccessType access_type) { IfBuilder length_checker(this); Token::Value token = IsHoleyElementsKind(kind) ? Token::GTE : Token::EQ; length_checker.If(key, length, token); length_checker.Then(); HValue* current_capacity = AddLoadFixedArrayLength(elements); if (top_info()->IsStub()) { IfBuilder capacity_checker(this); capacity_checker.If(key, current_capacity, Token::GTE); capacity_checker.Then(); HValue* new_elements = BuildCheckAndGrowElementsCapacity( object, elements, kind, length, current_capacity, key); environment()->Push(new_elements); capacity_checker.Else(); environment()->Push(elements); capacity_checker.End(); } else { HValue* result = Add( object, elements, key, current_capacity, is_js_array, kind); environment()->Push(result); } if (is_js_array) { HValue* new_length = AddUncasted(key, graph_->GetConstant1()); new_length->ClearFlag(HValue::kCanOverflow); Add(object, HObjectAccess::ForArrayLength(kind), new_length); } if (access_type == STORE && kind == FAST_SMI_ELEMENTS) { HValue* checked_elements = environment()->Top(); // Write zero to ensure that the new element is initialized with some smi. Add(checked_elements, key, graph()->GetConstant0(), nullptr, kind); } length_checker.Else(); Add(key, length); environment()->Push(elements); length_checker.End(); return environment()->Pop(); } HValue* HGraphBuilder::BuildCopyElementsOnWrite(HValue* object, HValue* elements, ElementsKind kind, HValue* length) { Factory* factory = isolate()->factory(); IfBuilder cow_checker(this); cow_checker.If(elements, factory->fixed_cow_array_map()); cow_checker.Then(); HValue* capacity = AddLoadFixedArrayLength(elements); HValue* new_elements = BuildGrowElementsCapacity(object, elements, kind, kind, length, capacity); environment()->Push(new_elements); cow_checker.Else(); environment()->Push(elements); cow_checker.End(); return environment()->Pop(); } HValue* HGraphBuilder::BuildElementIndexHash(HValue* index) { int32_t seed_value = static_cast(isolate()->heap()->HashSeed()); HValue* seed = Add(seed_value); HValue* hash = AddUncasted(Token::BIT_XOR, index, seed); // hash = ~hash + (hash << 15); HValue* shifted_hash = AddUncasted(hash, Add(15)); HValue* not_hash = AddUncasted(Token::BIT_XOR, hash, graph()->GetConstantMinus1()); hash = AddUncasted(shifted_hash, not_hash); // hash = hash ^ (hash >> 12); shifted_hash = AddUncasted(hash, Add(12)); hash = AddUncasted(Token::BIT_XOR, hash, shifted_hash); // hash = hash + (hash << 2); shifted_hash = AddUncasted(hash, Add(2)); hash = AddUncasted(hash, shifted_hash); // hash = hash ^ (hash >> 4); shifted_hash = AddUncasted(hash, Add(4)); hash = AddUncasted(Token::BIT_XOR, hash, shifted_hash); // hash = hash * 2057; hash = AddUncasted(hash, Add(2057)); hash->ClearFlag(HValue::kCanOverflow); // hash = hash ^ (hash >> 16); shifted_hash = AddUncasted(hash, Add(16)); return AddUncasted(Token::BIT_XOR, hash, shifted_hash); } HValue* HGraphBuilder::BuildUncheckedDictionaryElementLoad(HValue* receiver, HValue* elements, HValue* key, HValue* hash) { HValue* capacity = Add(elements, Add(NameDictionary::kCapacityIndex), nullptr, nullptr, FAST_ELEMENTS); HValue* mask = AddUncasted(capacity, graph()->GetConstant1()); mask->ChangeRepresentation(Representation::Integer32()); mask->ClearFlag(HValue::kCanOverflow); HValue* entry = hash; HValue* count = graph()->GetConstant1(); Push(entry); Push(count); HIfContinuation return_or_loop_continuation(graph()->CreateBasicBlock(), graph()->CreateBasicBlock()); HIfContinuation found_key_match_continuation(graph()->CreateBasicBlock(), graph()->CreateBasicBlock()); LoopBuilder probe_loop(this); probe_loop.BeginBody(2); // Drop entry, count from last environment to // appease live range building without simulates. count = Pop(); entry = Pop(); entry = AddUncasted(Token::BIT_AND, entry, mask); int entry_size = SeededNumberDictionary::kEntrySize; HValue* base_index = AddUncasted(entry, Add(entry_size)); base_index->ClearFlag(HValue::kCanOverflow); int start_offset = SeededNumberDictionary::kElementsStartIndex; HValue* key_index = AddUncasted(base_index, Add(start_offset)); key_index->ClearFlag(HValue::kCanOverflow); HValue* candidate_key = Add(elements, key_index, nullptr, nullptr, FAST_ELEMENTS); IfBuilder if_undefined(this); if_undefined.If(candidate_key, graph()->GetConstantUndefined()); if_undefined.Then(); { // element == undefined means "not found". Call the runtime. // TODO(jkummerow): walk the prototype chain instead. Add(receiver, key); Push(Add(Runtime::FunctionForId(Runtime::kKeyedGetProperty), 2)); } if_undefined.Else(); { IfBuilder if_match(this); if_match.If(candidate_key, key); if_match.Then(); if_match.Else(); // Update non-internalized string in the dictionary with internalized key? IfBuilder if_update_with_internalized(this); HValue* smi_check = if_update_with_internalized.IfNot(candidate_key); if_update_with_internalized.And(); HValue* map = AddLoadMap(candidate_key, smi_check); HValue* instance_type = Add(map, nullptr, HObjectAccess::ForMapInstanceType()); HValue* not_internalized_bit = AddUncasted( Token::BIT_AND, instance_type, Add(static_cast(kIsNotInternalizedMask))); if_update_with_internalized.If( not_internalized_bit, graph()->GetConstant0(), Token::NE); if_update_with_internalized.And(); if_update_with_internalized.IfNot( candidate_key, graph()->GetConstantHole()); if_update_with_internalized.AndIf(candidate_key, key, Token::EQ); if_update_with_internalized.Then(); // Replace a key that is a non-internalized string by the equivalent // internalized string for faster further lookups. Add(elements, key_index, key, nullptr, FAST_ELEMENTS); if_update_with_internalized.Else(); if_update_with_internalized.JoinContinuation(&found_key_match_continuation); if_match.JoinContinuation(&found_key_match_continuation); IfBuilder found_key_match(this, &found_key_match_continuation); found_key_match.Then(); // Key at current probe matches. Relevant bits in the |details| field must // be zero, otherwise the dictionary element requires special handling. HValue* details_index = AddUncasted(base_index, Add(start_offset + 2)); details_index->ClearFlag(HValue::kCanOverflow); HValue* details = Add(elements, details_index, nullptr, nullptr, FAST_ELEMENTS); int details_mask = PropertyDetails::KindField::kMask; details = AddUncasted(Token::BIT_AND, details, Add(details_mask)); IfBuilder details_compare(this); details_compare.If(details, New(kData), Token::EQ); details_compare.Then(); HValue* result_index = AddUncasted(base_index, Add(start_offset + 1)); result_index->ClearFlag(HValue::kCanOverflow); Push(Add(elements, result_index, nullptr, nullptr, FAST_ELEMENTS)); details_compare.Else(); Add(receiver, key); Push(Add(Runtime::FunctionForId(Runtime::kKeyedGetProperty), 2)); details_compare.End(); found_key_match.Else(); found_key_match.JoinContinuation(&return_or_loop_continuation); } if_undefined.JoinContinuation(&return_or_loop_continuation); IfBuilder return_or_loop(this, &return_or_loop_continuation); return_or_loop.Then(); probe_loop.Break(); return_or_loop.Else(); entry = AddUncasted(entry, count); entry->ClearFlag(HValue::kCanOverflow); count = AddUncasted(count, graph()->GetConstant1()); count->ClearFlag(HValue::kCanOverflow); Push(entry); Push(count); probe_loop.EndBody(); return_or_loop.End(); return Pop(); } HValue* HGraphBuilder::BuildCreateIterResultObject(HValue* value, HValue* done) { NoObservableSideEffectsScope scope(this); // Allocate the JSIteratorResult object. HValue* result = Add(Add(JSIteratorResult::kSize), HType::JSObject(), NOT_TENURED, JS_OBJECT_TYPE, graph()->GetConstant0()); // Initialize the JSIteratorResult object. HValue* native_context = BuildGetNativeContext(); HValue* map = Add( native_context, nullptr, HObjectAccess::ForContextSlot(Context::ITERATOR_RESULT_MAP_INDEX)); Add(result, HObjectAccess::ForMap(), map); HValue* empty_fixed_array = Add(Heap::kEmptyFixedArrayRootIndex); Add(result, HObjectAccess::ForPropertiesPointer(), empty_fixed_array); Add(result, HObjectAccess::ForElementsPointer(), empty_fixed_array); Add(result, HObjectAccess::ForObservableJSObjectOffset( JSIteratorResult::kValueOffset), value); Add(result, HObjectAccess::ForObservableJSObjectOffset( JSIteratorResult::kDoneOffset), done); STATIC_ASSERT(JSIteratorResult::kSize == 5 * kPointerSize); return result; } HValue* HGraphBuilder::BuildNumberToString(HValue* object, AstType* type) { NoObservableSideEffectsScope scope(this); // Convert constant numbers at compile time. if (object->IsConstant() && HConstant::cast(object)->HasNumberValue()) { Handle number = HConstant::cast(object)->handle(isolate()); Handle result = isolate()->factory()->NumberToString(number); return Add(result); } // Create a joinable continuation. HIfContinuation found(graph()->CreateBasicBlock(), graph()->CreateBasicBlock()); // Load the number string cache. HValue* number_string_cache = Add(Heap::kNumberStringCacheRootIndex); // Make the hash mask from the length of the number string cache. It // contains two elements (number and string) for each cache entry. HValue* mask = AddLoadFixedArrayLength(number_string_cache); mask->set_type(HType::Smi()); mask = AddUncasted(mask, graph()->GetConstant1()); mask = AddUncasted(mask, graph()->GetConstant1()); // Check whether object is a smi. IfBuilder if_objectissmi(this); if_objectissmi.If(object); if_objectissmi.Then(); { // Compute hash for smi similar to smi_get_hash(). HValue* hash = AddUncasted(Token::BIT_AND, object, mask); // Load the key. HValue* key_index = AddUncasted(hash, graph()->GetConstant1()); HValue* key = Add(number_string_cache, key_index, nullptr, nullptr, FAST_ELEMENTS, ALLOW_RETURN_HOLE); // Check if object == key. IfBuilder if_objectiskey(this); if_objectiskey.If(object, key); if_objectiskey.Then(); { // Make the key_index available. Push(key_index); } if_objectiskey.JoinContinuation(&found); } if_objectissmi.Else(); { if (type->Is(AstType::SignedSmall())) { if_objectissmi.Deopt(DeoptimizeReason::kExpectedSmi); } else { // Check if the object is a heap number. IfBuilder if_objectisnumber(this); HValue* objectisnumber = if_objectisnumber.If( object, isolate()->factory()->heap_number_map()); if_objectisnumber.Then(); { // Compute hash for heap number similar to double_get_hash(). HValue* low = Add( object, objectisnumber, HObjectAccess::ForHeapNumberValueLowestBits()); HValue* high = Add( object, objectisnumber, HObjectAccess::ForHeapNumberValueHighestBits()); HValue* hash = AddUncasted(Token::BIT_XOR, low, high); hash = AddUncasted(Token::BIT_AND, hash, mask); // Load the key. HValue* key_index = AddUncasted(hash, graph()->GetConstant1()); HValue* key = Add(number_string_cache, key_index, nullptr, nullptr, FAST_ELEMENTS, ALLOW_RETURN_HOLE); // Check if the key is a heap number and compare it with the object. IfBuilder if_keyisnotsmi(this); HValue* keyisnotsmi = if_keyisnotsmi.IfNot(key); if_keyisnotsmi.Then(); { IfBuilder if_keyisheapnumber(this); if_keyisheapnumber.If( key, isolate()->factory()->heap_number_map()); if_keyisheapnumber.Then(); { // Check if values of key and object match. IfBuilder if_keyeqobject(this); if_keyeqobject.If( Add(key, keyisnotsmi, HObjectAccess::ForHeapNumberValue()), Add(object, objectisnumber, HObjectAccess::ForHeapNumberValue()), Token::EQ); if_keyeqobject.Then(); { // Make the key_index available. Push(key_index); } if_keyeqobject.JoinContinuation(&found); } if_keyisheapnumber.JoinContinuation(&found); } if_keyisnotsmi.JoinContinuation(&found); } if_objectisnumber.Else(); { if (type->Is(AstType::Number())) { if_objectisnumber.Deopt(DeoptimizeReason::kExpectedHeapNumber); } } if_objectisnumber.JoinContinuation(&found); } } if_objectissmi.JoinContinuation(&found); // Check for cache hit. IfBuilder if_found(this, &found); if_found.Then(); { // Count number to string operation in native code. AddIncrementCounter(isolate()->counters()->number_to_string_native()); // Load the value in case of cache hit. HValue* key_index = Pop(); HValue* value_index = AddUncasted(key_index, graph()->GetConstant1()); Push(Add(number_string_cache, value_index, nullptr, nullptr, FAST_ELEMENTS, ALLOW_RETURN_HOLE)); } if_found.Else(); { // Cache miss, fallback to runtime. Add(object); Push(Add( Runtime::FunctionForId(Runtime::kNumberToStringSkipCache), 1)); } if_found.End(); return Pop(); } HValue* HGraphBuilder::BuildToNumber(HValue* input) { if (input->type().IsTaggedNumber() || input->representation().IsSpecialization()) { return input; } Callable callable = CodeFactory::ToNumber(isolate()); HValue* stub = Add(callable.code()); HValue* values[] = {input}; HCallWithDescriptor* instr = Add( stub, 0, callable.descriptor(), ArrayVector(values)); instr->set_type(HType::TaggedNumber()); return instr; } HValue* HGraphBuilder::BuildToObject(HValue* receiver) { NoObservableSideEffectsScope scope(this); // Create a joinable continuation. HIfContinuation wrap(graph()->CreateBasicBlock(), graph()->CreateBasicBlock()); // Determine the proper global constructor function required to wrap // {receiver} into a JSValue, unless {receiver} is already a {JSReceiver}, in // which case we just return it. Deopts to Runtime::kToObject if {receiver} // is undefined or null. IfBuilder receiver_is_smi(this); receiver_is_smi.If(receiver); receiver_is_smi.Then(); { // Use global Number function. Push(Add(Context::NUMBER_FUNCTION_INDEX)); } receiver_is_smi.Else(); { // Determine {receiver} map and instance type. HValue* receiver_map = Add(receiver, nullptr, HObjectAccess::ForMap()); HValue* receiver_instance_type = Add( receiver_map, nullptr, HObjectAccess::ForMapInstanceType()); // First check whether {receiver} is already a spec object (fast case). IfBuilder receiver_is_not_spec_object(this); receiver_is_not_spec_object.If( receiver_instance_type, Add(FIRST_JS_RECEIVER_TYPE), Token::LT); receiver_is_not_spec_object.Then(); { // Load the constructor function index from the {receiver} map. HValue* constructor_function_index = Add( receiver_map, nullptr, HObjectAccess::ForMapInObjectPropertiesOrConstructorFunctionIndex()); // Check if {receiver} has a constructor (null and undefined have no // constructors, so we deoptimize to the runtime to throw an exception). IfBuilder constructor_function_index_is_invalid(this); constructor_function_index_is_invalid.If( constructor_function_index, Add(Map::kNoConstructorFunctionIndex), Token::EQ); constructor_function_index_is_invalid.ThenDeopt( DeoptimizeReason::kUndefinedOrNullInToObject); constructor_function_index_is_invalid.End(); // Use the global constructor function. Push(constructor_function_index); } receiver_is_not_spec_object.JoinContinuation(&wrap); } receiver_is_smi.JoinContinuation(&wrap); // Wrap the receiver if necessary. IfBuilder if_wrap(this, &wrap); if_wrap.Then(); { // Grab the constructor function index. HValue* constructor_index = Pop(); // Load native context. HValue* native_context = BuildGetNativeContext(); // Determine the initial map for the global constructor. HValue* constructor = Add(native_context, constructor_index, nullptr, nullptr, FAST_ELEMENTS); HValue* constructor_initial_map = Add( constructor, nullptr, HObjectAccess::ForPrototypeOrInitialMap()); // Allocate and initialize a JSValue wrapper. HValue* value = BuildAllocate(Add(JSValue::kSize), HType::JSObject(), JS_VALUE_TYPE, HAllocationMode()); Add(value, HObjectAccess::ForMap(), constructor_initial_map); HValue* empty_fixed_array = Add(Heap::kEmptyFixedArrayRootIndex); Add(value, HObjectAccess::ForPropertiesPointer(), empty_fixed_array); Add(value, HObjectAccess::ForElementsPointer(), empty_fixed_array); Add(value, HObjectAccess::ForObservableJSObjectOffset( JSValue::kValueOffset), receiver); Push(value); } if_wrap.Else(); { Push(receiver); } if_wrap.End(); return Pop(); } HAllocate* HGraphBuilder::BuildAllocate( HValue* object_size, HType type, InstanceType instance_type, HAllocationMode allocation_mode) { // Compute the effective allocation size. HValue* size = object_size; if (allocation_mode.CreateAllocationMementos()) { size = AddUncasted(size, Add(AllocationMemento::kSize)); size->ClearFlag(HValue::kCanOverflow); } // Perform the actual allocation. HAllocate* object = Add( size, type, allocation_mode.GetPretenureMode(), instance_type, graph()->GetConstant0(), allocation_mode.feedback_site()); // Setup the allocation memento. if (allocation_mode.CreateAllocationMementos()) { BuildCreateAllocationMemento( object, object_size, allocation_mode.current_site()); } return object; } HValue* HGraphBuilder::BuildAddStringLengths(HValue* left_length, HValue* right_length) { // Compute the combined string length and check against max string length. HValue* length = AddUncasted(left_length, right_length); // Check that length <= kMaxLength <=> length < MaxLength + 1. HValue* max_length = Add(String::kMaxLength + 1); if (top_info()->IsStub() || !isolate()->IsStringLengthOverflowIntact()) { // This is a mitigation for crbug.com/627934; the real fix // will be to migrate the StringAddStub to TurboFan one day. IfBuilder if_invalid(this); if_invalid.If(length, max_length, Token::GT); if_invalid.Then(); { Add( Runtime::FunctionForId(Runtime::kThrowInvalidStringLength), 0); } if_invalid.End(); } else { graph()->MarkDependsOnStringLengthOverflow(); Add(length, max_length); } return length; } HValue* HGraphBuilder::BuildCreateConsString( HValue* length, HValue* left, HValue* right, HAllocationMode allocation_mode) { // Determine the string instance types. HInstruction* left_instance_type = AddLoadStringInstanceType(left); HInstruction* right_instance_type = AddLoadStringInstanceType(right); // Allocate the cons string object. HAllocate does not care whether we // pass CONS_STRING_TYPE or CONS_ONE_BYTE_STRING_TYPE here, so we just use // CONS_STRING_TYPE here. Below we decide whether the cons string is // one-byte or two-byte and set the appropriate map. DCHECK(HAllocate::CompatibleInstanceTypes(CONS_STRING_TYPE, CONS_ONE_BYTE_STRING_TYPE)); HAllocate* result = BuildAllocate(Add(ConsString::kSize), HType::String(), CONS_STRING_TYPE, allocation_mode); // Compute intersection and difference of instance types. HValue* anded_instance_types = AddUncasted( Token::BIT_AND, left_instance_type, right_instance_type); HValue* xored_instance_types = AddUncasted( Token::BIT_XOR, left_instance_type, right_instance_type); // We create a one-byte cons string if // 1. both strings are one-byte, or // 2. at least one of the strings is two-byte, but happens to contain only // one-byte characters. // To do this, we check // 1. if both strings are one-byte, or if the one-byte data hint is set in // both strings, or // 2. if one of the strings has the one-byte data hint set and the other // string is one-byte. IfBuilder if_onebyte(this); STATIC_ASSERT(kOneByteStringTag != 0); STATIC_ASSERT(kOneByteDataHintMask != 0); if_onebyte.If( AddUncasted( Token::BIT_AND, anded_instance_types, Add(static_cast( kStringEncodingMask | kOneByteDataHintMask))), graph()->GetConstant0(), Token::NE); if_onebyte.Or(); STATIC_ASSERT(kOneByteStringTag != 0 && kOneByteDataHintTag != 0 && kOneByteDataHintTag != kOneByteStringTag); if_onebyte.If( AddUncasted( Token::BIT_AND, xored_instance_types, Add(static_cast( kOneByteStringTag | kOneByteDataHintTag))), Add(static_cast( kOneByteStringTag | kOneByteDataHintTag)), Token::EQ); if_onebyte.Then(); { // We can safely skip the write barrier for storing the map here. Add( result, HObjectAccess::ForMap(), Add(isolate()->factory()->cons_one_byte_string_map())); } if_onebyte.Else(); { // We can safely skip the write barrier for storing the map here. Add( result, HObjectAccess::ForMap(), Add(isolate()->factory()->cons_string_map())); } if_onebyte.End(); // Initialize the cons string fields. Add(result, HObjectAccess::ForStringHashField(), Add(String::kEmptyHashField)); Add(result, HObjectAccess::ForStringLength(), length); Add(result, HObjectAccess::ForConsStringFirst(), left); Add(result, HObjectAccess::ForConsStringSecond(), right); // Count the native string addition. AddIncrementCounter(isolate()->counters()->string_add_native()); return result; } void HGraphBuilder::BuildCopySeqStringChars(HValue* src, HValue* src_offset, String::Encoding src_encoding, HValue* dst, HValue* dst_offset, String::Encoding dst_encoding, HValue* length) { DCHECK(dst_encoding != String::ONE_BYTE_ENCODING || src_encoding == String::ONE_BYTE_ENCODING); LoopBuilder loop(this, context(), LoopBuilder::kPostIncrement); HValue* index = loop.BeginBody(graph()->GetConstant0(), length, Token::LT); { HValue* src_index = AddUncasted(src_offset, index); HValue* value = AddUncasted(src_encoding, src, src_index); HValue* dst_index = AddUncasted(dst_offset, index); Add(dst_encoding, dst, dst_index, value); } loop.EndBody(); } HValue* HGraphBuilder::BuildObjectSizeAlignment( HValue* unaligned_size, int header_size) { DCHECK((header_size & kObjectAlignmentMask) == 0); HValue* size = AddUncasted( unaligned_size, Add(static_cast( header_size + kObjectAlignmentMask))); size->ClearFlag(HValue::kCanOverflow); return AddUncasted( Token::BIT_AND, size, Add(static_cast( ~kObjectAlignmentMask))); } HValue* HGraphBuilder::BuildUncheckedStringAdd( HValue* left, HValue* right, HAllocationMode allocation_mode) { // Determine the string lengths. HValue* left_length = AddLoadStringLength(left); HValue* right_length = AddLoadStringLength(right); // Compute the combined string length. HValue* length = BuildAddStringLengths(left_length, right_length); // Do some manual constant folding here. if (left_length->IsConstant()) { HConstant* c_left_length = HConstant::cast(left_length); DCHECK_NE(0, c_left_length->Integer32Value()); if (c_left_length->Integer32Value() + 1 >= ConsString::kMinLength) { // The right string contains at least one character. return BuildCreateConsString(length, left, right, allocation_mode); } } else if (right_length->IsConstant()) { HConstant* c_right_length = HConstant::cast(right_length); DCHECK_NE(0, c_right_length->Integer32Value()); if (c_right_length->Integer32Value() + 1 >= ConsString::kMinLength) { // The left string contains at least one character. return BuildCreateConsString(length, left, right, allocation_mode); } } // Check if we should create a cons string. IfBuilder if_createcons(this); if_createcons.If( length, Add(ConsString::kMinLength), Token::GTE); if_createcons.And(); if_createcons.If( length, Add(ConsString::kMaxLength), Token::LTE); if_createcons.Then(); { // Create a cons string. Push(BuildCreateConsString(length, left, right, allocation_mode)); } if_createcons.Else(); { // Determine the string instance types. HValue* left_instance_type = AddLoadStringInstanceType(left); HValue* right_instance_type = AddLoadStringInstanceType(right); // Compute union and difference of instance types. HValue* ored_instance_types = AddUncasted( Token::BIT_OR, left_instance_type, right_instance_type); HValue* xored_instance_types = AddUncasted( Token::BIT_XOR, left_instance_type, right_instance_type); // Check if both strings have the same encoding and both are // sequential. IfBuilder if_sameencodingandsequential(this); if_sameencodingandsequential.If( AddUncasted( Token::BIT_AND, xored_instance_types, Add(static_cast(kStringEncodingMask))), graph()->GetConstant0(), Token::EQ); if_sameencodingandsequential.And(); STATIC_ASSERT(kSeqStringTag == 0); if_sameencodingandsequential.If( AddUncasted( Token::BIT_AND, ored_instance_types, Add(static_cast(kStringRepresentationMask))), graph()->GetConstant0(), Token::EQ); if_sameencodingandsequential.Then(); { HConstant* string_map = Add(isolate()->factory()->string_map()); HConstant* one_byte_string_map = Add(isolate()->factory()->one_byte_string_map()); // Determine map and size depending on whether result is one-byte string. IfBuilder if_onebyte(this); STATIC_ASSERT(kOneByteStringTag != 0); if_onebyte.If( AddUncasted( Token::BIT_AND, ored_instance_types, Add(static_cast(kStringEncodingMask))), graph()->GetConstant0(), Token::NE); if_onebyte.Then(); { // Allocate sequential one-byte string object. Push(length); Push(one_byte_string_map); } if_onebyte.Else(); { // Allocate sequential two-byte string object. HValue* size = AddUncasted(length, graph()->GetConstant1()); size->ClearFlag(HValue::kCanOverflow); size->SetFlag(HValue::kUint32); Push(size); Push(string_map); } if_onebyte.End(); HValue* map = Pop(); // Calculate the number of bytes needed for the characters in the // string while observing object alignment. STATIC_ASSERT((SeqString::kHeaderSize & kObjectAlignmentMask) == 0); HValue* size = BuildObjectSizeAlignment(Pop(), SeqString::kHeaderSize); IfBuilder if_size(this); if_size.If( size, Add(kMaxRegularHeapObjectSize), Token::LT); if_size.Then(); { // Allocate the string object. HAllocate does not care whether we pass // STRING_TYPE or ONE_BYTE_STRING_TYPE here, so we just use STRING_TYPE. HAllocate* result = BuildAllocate(size, HType::String(), STRING_TYPE, allocation_mode); Add(result, HObjectAccess::ForMap(), map); // Initialize the string fields. Add(result, HObjectAccess::ForStringHashField(), Add(String::kEmptyHashField)); Add(result, HObjectAccess::ForStringLength(), length); // Copy characters to the result string. IfBuilder if_twobyte(this); if_twobyte.If(map, string_map); if_twobyte.Then(); { // Copy characters from the left string. BuildCopySeqStringChars( left, graph()->GetConstant0(), String::TWO_BYTE_ENCODING, result, graph()->GetConstant0(), String::TWO_BYTE_ENCODING, left_length); // Copy characters from the right string. BuildCopySeqStringChars( right, graph()->GetConstant0(), String::TWO_BYTE_ENCODING, result, left_length, String::TWO_BYTE_ENCODING, right_length); } if_twobyte.Else(); { // Copy characters from the left string. BuildCopySeqStringChars( left, graph()->GetConstant0(), String::ONE_BYTE_ENCODING, result, graph()->GetConstant0(), String::ONE_BYTE_ENCODING, left_length); // Copy characters from the right string. BuildCopySeqStringChars( right, graph()->GetConstant0(), String::ONE_BYTE_ENCODING, result, left_length, String::ONE_BYTE_ENCODING, right_length); } if_twobyte.End(); // Count the native string addition. AddIncrementCounter(isolate()->counters()->string_add_native()); // Return the sequential string. Push(result); } if_size.Else(); { // Fallback to the runtime to add the two strings. The string has to be // allocated in LO space. Add(left, right); Push(Add(Runtime::FunctionForId(Runtime::kStringAdd), 2)); } if_size.End(); } if_sameencodingandsequential.Else(); { // Fallback to the runtime to add the two strings. Add(left, right); Push(Add(Runtime::FunctionForId(Runtime::kStringAdd), 2)); } if_sameencodingandsequential.End(); } if_createcons.End(); return Pop(); } HValue* HGraphBuilder::BuildStringAdd( HValue* left, HValue* right, HAllocationMode allocation_mode) { NoObservableSideEffectsScope no_effects(this); // Determine string lengths. HValue* left_length = AddLoadStringLength(left); HValue* right_length = AddLoadStringLength(right); // Check if left string is empty. IfBuilder if_leftempty(this); if_leftempty.If( left_length, graph()->GetConstant0(), Token::EQ); if_leftempty.Then(); { // Count the native string addition. AddIncrementCounter(isolate()->counters()->string_add_native()); // Just return the right string. Push(right); } if_leftempty.Else(); { // Check if right string is empty. IfBuilder if_rightempty(this); if_rightempty.If( right_length, graph()->GetConstant0(), Token::EQ); if_rightempty.Then(); { // Count the native string addition. AddIncrementCounter(isolate()->counters()->string_add_native()); // Just return the left string. Push(left); } if_rightempty.Else(); { // Add the two non-empty strings. Push(BuildUncheckedStringAdd(left, right, allocation_mode)); } if_rightempty.End(); } if_leftempty.End(); return Pop(); } HInstruction* HGraphBuilder::BuildUncheckedMonomorphicElementAccess( HValue* checked_object, HValue* key, HValue* val, bool is_js_array, ElementsKind elements_kind, PropertyAccessType access_type, LoadKeyedHoleMode load_mode, KeyedAccessStoreMode store_mode) { DCHECK(top_info()->IsStub() || checked_object->IsCompareMap() || checked_object->IsCheckMaps()); DCHECK(!IsFixedTypedArrayElementsKind(elements_kind) || !is_js_array); // No GVNFlag is necessary for ElementsKind if there is an explicit dependency // on a HElementsTransition instruction. The flag can also be removed if the // map to check has FAST_HOLEY_ELEMENTS, since there can be no further // ElementsKind transitions. Finally, the dependency can be removed for stores // for FAST_ELEMENTS, since a transition to HOLEY elements won't change the // generated store code. if ((elements_kind == FAST_HOLEY_ELEMENTS) || (elements_kind == FAST_ELEMENTS && access_type == STORE)) { checked_object->ClearDependsOnFlag(kElementsKind); } bool fast_smi_only_elements = IsFastSmiElementsKind(elements_kind); bool fast_elements = IsFastObjectElementsKind(elements_kind); HValue* elements = AddLoadElements(checked_object); if (access_type == STORE && (fast_elements || fast_smi_only_elements) && store_mode != STORE_NO_TRANSITION_HANDLE_COW) { HCheckMaps* check_cow_map = Add( elements, isolate()->factory()->fixed_array_map()); check_cow_map->ClearDependsOnFlag(kElementsKind); } HInstruction* length = NULL; if (is_js_array) { length = Add( checked_object->ActualValue(), checked_object, HObjectAccess::ForArrayLength(elements_kind)); } else { length = AddLoadFixedArrayLength(elements); } length->set_type(HType::Smi()); HValue* checked_key = NULL; if (IsFixedTypedArrayElementsKind(elements_kind)) { checked_object = Add(checked_object); HValue* external_pointer = Add( elements, nullptr, HObjectAccess::ForFixedTypedArrayBaseExternalPointer()); HValue* base_pointer = Add( elements, nullptr, HObjectAccess::ForFixedTypedArrayBaseBasePointer()); HValue* backing_store = AddUncasted(external_pointer, base_pointer, AddOfExternalAndTagged); if (store_mode == STORE_NO_TRANSITION_IGNORE_OUT_OF_BOUNDS) { NoObservableSideEffectsScope no_effects(this); IfBuilder length_checker(this); length_checker.If(key, length, Token::LT); length_checker.Then(); IfBuilder negative_checker(this); HValue* bounds_check = negative_checker.If( key, graph()->GetConstant0(), Token::GTE); negative_checker.Then(); HInstruction* result = AddElementAccess( backing_store, key, val, bounds_check, checked_object->ActualValue(), elements_kind, access_type); negative_checker.ElseDeopt(DeoptimizeReason::kNegativeKeyEncountered); negative_checker.End(); length_checker.End(); return result; } else { DCHECK(store_mode == STANDARD_STORE); checked_key = Add(key, length); return AddElementAccess(backing_store, checked_key, val, checked_object, checked_object->ActualValue(), elements_kind, access_type); } } DCHECK(fast_smi_only_elements || fast_elements || IsFastDoubleElementsKind(elements_kind)); // In case val is stored into a fast smi array, assure that the value is a smi // before manipulating the backing store. Otherwise the actual store may // deopt, leaving the backing store in an invalid state. if (access_type == STORE && IsFastSmiElementsKind(elements_kind) && !val->type().IsSmi()) { val = AddUncasted(val, Representation::Smi()); } if (IsGrowStoreMode(store_mode)) { NoObservableSideEffectsScope no_effects(this); Representation representation = HStoreKeyed::RequiredValueRepresentation( elements_kind, STORE_TO_INITIALIZED_ENTRY); val = AddUncasted(val, representation); elements = BuildCheckForCapacityGrow(checked_object, elements, elements_kind, length, key, is_js_array, access_type); checked_key = key; } else { checked_key = Add(key, length); if (access_type == STORE && (fast_elements || fast_smi_only_elements)) { if (store_mode == STORE_NO_TRANSITION_HANDLE_COW) { NoObservableSideEffectsScope no_effects(this); elements = BuildCopyElementsOnWrite(checked_object, elements, elements_kind, length); } else { HCheckMaps* check_cow_map = Add( elements, isolate()->factory()->fixed_array_map()); check_cow_map->ClearDependsOnFlag(kElementsKind); } } } return AddElementAccess(elements, checked_key, val, checked_object, nullptr, elements_kind, access_type, load_mode); } HValue* HGraphBuilder::BuildCalculateElementsSize(ElementsKind kind, HValue* capacity) { int elements_size = IsFastDoubleElementsKind(kind) ? kDoubleSize : kPointerSize; HConstant* elements_size_value = Add(elements_size); HInstruction* mul = HMul::NewImul(isolate(), zone(), context(), capacity->ActualValue(), elements_size_value); AddInstruction(mul); mul->ClearFlag(HValue::kCanOverflow); STATIC_ASSERT(FixedDoubleArray::kHeaderSize == FixedArray::kHeaderSize); HConstant* header_size = Add(FixedArray::kHeaderSize); HValue* total_size = AddUncasted(mul, header_size); total_size->ClearFlag(HValue::kCanOverflow); return total_size; } HAllocate* HGraphBuilder::AllocateJSArrayObject(AllocationSiteMode mode) { int base_size = JSArray::kSize; if (mode == TRACK_ALLOCATION_SITE) { base_size += AllocationMemento::kSize; } HConstant* size_in_bytes = Add(base_size); return Add(size_in_bytes, HType::JSArray(), NOT_TENURED, JS_OBJECT_TYPE, graph()->GetConstant0()); } HConstant* HGraphBuilder::EstablishElementsAllocationSize( ElementsKind kind, int capacity) { int base_size = IsFastDoubleElementsKind(kind) ? FixedDoubleArray::SizeFor(capacity) : FixedArray::SizeFor(capacity); return Add(base_size); } HAllocate* HGraphBuilder::BuildAllocateElements(ElementsKind kind, HValue* size_in_bytes) { InstanceType instance_type = IsFastDoubleElementsKind(kind) ? FIXED_DOUBLE_ARRAY_TYPE : FIXED_ARRAY_TYPE; return Add(size_in_bytes, HType::HeapObject(), NOT_TENURED, instance_type, graph()->GetConstant0()); } void HGraphBuilder::BuildInitializeElementsHeader(HValue* elements, ElementsKind kind, HValue* capacity) { Factory* factory = isolate()->factory(); Handle map = IsFastDoubleElementsKind(kind) ? factory->fixed_double_array_map() : factory->fixed_array_map(); Add(elements, HObjectAccess::ForMap(), Add(map)); Add(elements, HObjectAccess::ForFixedArrayLength(), capacity); } HValue* HGraphBuilder::BuildAllocateAndInitializeArray(ElementsKind kind, HValue* capacity) { // The HForceRepresentation is to prevent possible deopt on int-smi // conversion after allocation but before the new object fields are set. capacity = AddUncasted(capacity, Representation::Smi()); HValue* size_in_bytes = BuildCalculateElementsSize(kind, capacity); HValue* new_array = BuildAllocateElements(kind, size_in_bytes); BuildInitializeElementsHeader(new_array, kind, capacity); return new_array; } void HGraphBuilder::BuildJSArrayHeader(HValue* array, HValue* array_map, HValue* elements, AllocationSiteMode mode, ElementsKind elements_kind, HValue* allocation_site_payload, HValue* length_field) { Add(array, HObjectAccess::ForMap(), array_map); HValue* empty_fixed_array = Add(Heap::kEmptyFixedArrayRootIndex); Add( array, HObjectAccess::ForPropertiesPointer(), empty_fixed_array); Add(array, HObjectAccess::ForElementsPointer(), elements != nullptr ? elements : empty_fixed_array); Add( array, HObjectAccess::ForArrayLength(elements_kind), length_field); if (mode == TRACK_ALLOCATION_SITE) { BuildCreateAllocationMemento( array, Add(JSArray::kSize), allocation_site_payload); } } HInstruction* HGraphBuilder::AddElementAccess( HValue* elements, HValue* checked_key, HValue* val, HValue* dependency, HValue* backing_store_owner, ElementsKind elements_kind, PropertyAccessType access_type, LoadKeyedHoleMode load_mode) { if (access_type == STORE) { DCHECK(val != NULL); if (elements_kind == UINT8_CLAMPED_ELEMENTS) { val = Add(val); } return Add(elements, checked_key, val, backing_store_owner, elements_kind, STORE_TO_INITIALIZED_ENTRY); } DCHECK(access_type == LOAD); DCHECK(val == NULL); HLoadKeyed* load = Add(elements, checked_key, dependency, backing_store_owner, elements_kind, load_mode); if (elements_kind == UINT32_ELEMENTS) { graph()->RecordUint32Instruction(load); } return load; } HLoadNamedField* HGraphBuilder::AddLoadMap(HValue* object, HValue* dependency) { return Add(object, dependency, HObjectAccess::ForMap()); } HLoadNamedField* HGraphBuilder::AddLoadElements(HValue* object, HValue* dependency) { return Add( object, dependency, HObjectAccess::ForElementsPointer()); } HLoadNamedField* HGraphBuilder::AddLoadFixedArrayLength( HValue* array, HValue* dependency) { return Add( array, dependency, HObjectAccess::ForFixedArrayLength()); } HLoadNamedField* HGraphBuilder::AddLoadArrayLength(HValue* array, ElementsKind kind, HValue* dependency) { return Add( array, dependency, HObjectAccess::ForArrayLength(kind)); } HValue* HGraphBuilder::BuildNewElementsCapacity(HValue* old_capacity) { HValue* half_old_capacity = AddUncasted(old_capacity, graph_->GetConstant1()); HValue* new_capacity = AddUncasted(half_old_capacity, old_capacity); new_capacity->ClearFlag(HValue::kCanOverflow); HValue* min_growth = Add(16); new_capacity = AddUncasted(new_capacity, min_growth); new_capacity->ClearFlag(HValue::kCanOverflow); return new_capacity; } HValue* HGraphBuilder::BuildGrowElementsCapacity(HValue* object, HValue* elements, ElementsKind kind, ElementsKind new_kind, HValue* length, HValue* new_capacity) { Add( new_capacity, Add((kMaxRegularHeapObjectSize - FixedArray::kHeaderSize) >> ElementsKindToShiftSize(new_kind))); HValue* new_elements = BuildAllocateAndInitializeArray(new_kind, new_capacity); BuildCopyElements(elements, kind, new_elements, new_kind, length, new_capacity); Add(object, HObjectAccess::ForElementsPointer(), new_elements); return new_elements; } void HGraphBuilder::BuildFillElementsWithValue(HValue* elements, ElementsKind elements_kind, HValue* from, HValue* to, HValue* value) { if (to == NULL) { to = AddLoadFixedArrayLength(elements); } // Special loop unfolding case STATIC_ASSERT(JSArray::kPreallocatedArrayElements <= kElementLoopUnrollThreshold); int initial_capacity = -1; if (from->IsInteger32Constant() && to->IsInteger32Constant()) { int constant_from = from->GetInteger32Constant(); int constant_to = to->GetInteger32Constant(); if (constant_from == 0 && constant_to <= kElementLoopUnrollThreshold) { initial_capacity = constant_to; } } if (initial_capacity >= 0) { for (int i = 0; i < initial_capacity; i++) { HInstruction* key = Add(i); Add(elements, key, value, nullptr, elements_kind); } } else { // Carefully loop backwards so that the "from" remains live through the loop // rather than the to. This often corresponds to keeping length live rather // then capacity, which helps register allocation, since length is used more // other than capacity after filling with holes. LoopBuilder builder(this, context(), LoopBuilder::kPostDecrement); HValue* key = builder.BeginBody(to, from, Token::GT); HValue* adjusted_key = AddUncasted(key, graph()->GetConstant1()); adjusted_key->ClearFlag(HValue::kCanOverflow); Add(elements, adjusted_key, value, nullptr, elements_kind); builder.EndBody(); } } void HGraphBuilder::BuildFillElementsWithHole(HValue* elements, ElementsKind elements_kind, HValue* from, HValue* to) { // Fast elements kinds need to be initialized in case statements below cause a // garbage collection. HValue* hole = IsFastSmiOrObjectElementsKind(elements_kind) ? graph()->GetConstantHole() : Add(HConstant::kHoleNaN); // Since we're about to store a hole value, the store instruction below must // assume an elements kind that supports heap object values. if (IsFastSmiOrObjectElementsKind(elements_kind)) { elements_kind = FAST_HOLEY_ELEMENTS; } BuildFillElementsWithValue(elements, elements_kind, from, to, hole); } void HGraphBuilder::BuildCopyProperties(HValue* from_properties, HValue* to_properties, HValue* length, HValue* capacity) { ElementsKind kind = FAST_ELEMENTS; BuildFillElementsWithValue(to_properties, kind, length, capacity, graph()->GetConstantUndefined()); LoopBuilder builder(this, context(), LoopBuilder::kPostDecrement); HValue* key = builder.BeginBody(length, graph()->GetConstant0(), Token::GT); key = AddUncasted(key, graph()->GetConstant1()); key->ClearFlag(HValue::kCanOverflow); HValue* element = Add(from_properties, key, nullptr, nullptr, kind); Add(to_properties, key, element, nullptr, kind); builder.EndBody(); } void HGraphBuilder::BuildCopyElements(HValue* from_elements, ElementsKind from_elements_kind, HValue* to_elements, ElementsKind to_elements_kind, HValue* length, HValue* capacity) { int constant_capacity = -1; if (capacity != NULL && capacity->IsConstant() && HConstant::cast(capacity)->HasInteger32Value()) { int constant_candidate = HConstant::cast(capacity)->Integer32Value(); if (constant_candidate <= kElementLoopUnrollThreshold) { constant_capacity = constant_candidate; } } bool pre_fill_with_holes = IsFastDoubleElementsKind(from_elements_kind) && IsFastObjectElementsKind(to_elements_kind); if (pre_fill_with_holes) { // If the copy might trigger a GC, make sure that the FixedArray is // pre-initialized with holes to make sure that it's always in a // consistent state. BuildFillElementsWithHole(to_elements, to_elements_kind, graph()->GetConstant0(), NULL); } if (constant_capacity != -1) { // Unroll the loop for small elements kinds. for (int i = 0; i < constant_capacity; i++) { HValue* key_constant = Add(i); HInstruction* value = Add( from_elements, key_constant, nullptr, nullptr, from_elements_kind); Add(to_elements, key_constant, value, nullptr, to_elements_kind); } } else { if (!pre_fill_with_holes && (capacity == NULL || !length->Equals(capacity))) { BuildFillElementsWithHole(to_elements, to_elements_kind, length, NULL); } LoopBuilder builder(this, context(), LoopBuilder::kPostDecrement); HValue* key = builder.BeginBody(length, graph()->GetConstant0(), Token::GT); key = AddUncasted(key, graph()->GetConstant1()); key->ClearFlag(HValue::kCanOverflow); HValue* element = Add(from_elements, key, nullptr, nullptr, from_elements_kind, ALLOW_RETURN_HOLE); ElementsKind kind = (IsHoleyElementsKind(from_elements_kind) && IsFastSmiElementsKind(to_elements_kind)) ? FAST_HOLEY_ELEMENTS : to_elements_kind; if (IsHoleyElementsKind(from_elements_kind) && from_elements_kind != to_elements_kind) { IfBuilder if_hole(this); if_hole.If(element); if_hole.Then(); HConstant* hole_constant = IsFastDoubleElementsKind(to_elements_kind) ? Add(HConstant::kHoleNaN) : graph()->GetConstantHole(); Add(to_elements, key, hole_constant, nullptr, kind); if_hole.Else(); HStoreKeyed* store = Add(to_elements, key, element, nullptr, kind); store->SetFlag(HValue::kTruncatingToNumber); if_hole.End(); } else { HStoreKeyed* store = Add(to_elements, key, element, nullptr, kind); store->SetFlag(HValue::kTruncatingToNumber); } builder.EndBody(); } Counters* counters = isolate()->counters(); AddIncrementCounter(counters->inlined_copied_elements()); } void HGraphBuilder::BuildCreateAllocationMemento( HValue* previous_object, HValue* previous_object_size, HValue* allocation_site) { DCHECK(allocation_site != NULL); HInnerAllocatedObject* allocation_memento = Add( previous_object, previous_object_size, HType::HeapObject()); AddStoreMapConstant( allocation_memento, isolate()->factory()->allocation_memento_map()); Add( allocation_memento, HObjectAccess::ForAllocationMementoSite(), allocation_site); if (FLAG_allocation_site_pretenuring) { HValue* memento_create_count = Add(allocation_site, nullptr, HObjectAccess::ForAllocationSiteOffset( AllocationSite::kPretenureCreateCountOffset)); memento_create_count = AddUncasted( memento_create_count, graph()->GetConstant1()); // This smi value is reset to zero after every gc, overflow isn't a problem // since the counter is bounded by the new space size. memento_create_count->ClearFlag(HValue::kCanOverflow); Add( allocation_site, HObjectAccess::ForAllocationSiteOffset( AllocationSite::kPretenureCreateCountOffset), memento_create_count); } } HInstruction* HGraphBuilder::BuildGetNativeContext() { return Add( context(), nullptr, HObjectAccess::ForContextSlot(Context::NATIVE_CONTEXT_INDEX)); } HInstruction* HGraphBuilder::BuildGetNativeContext(HValue* closure) { // Get the global object, then the native context HInstruction* context = Add( closure, nullptr, HObjectAccess::ForFunctionContextPointer()); return Add( context, nullptr, HObjectAccess::ForContextSlot(Context::NATIVE_CONTEXT_INDEX)); } HValue* HGraphBuilder::BuildGetParentContext(HValue* depth, int depth_value) { HValue* script_context = context(); if (depth != NULL) { HValue* zero = graph()->GetConstant0(); Push(script_context); Push(depth); LoopBuilder loop(this); loop.BeginBody(2); // Drop script_context and depth from last environment // to appease live range building without simulates. depth = Pop(); script_context = Pop(); script_context = Add( script_context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); depth = AddUncasted(depth, graph()->GetConstant1()); depth->ClearFlag(HValue::kCanOverflow); IfBuilder if_break(this); if_break.If(depth, zero, Token::EQ); if_break.Then(); { Push(script_context); // The result. loop.Break(); } if_break.Else(); { Push(script_context); Push(depth); } loop.EndBody(); if_break.End(); script_context = Pop(); } else if (depth_value > 0) { // Unroll the above loop. for (int i = 0; i < depth_value; i++) { script_context = Add( script_context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); } } return script_context; } HInstruction* HGraphBuilder::BuildGetArrayFunction() { HInstruction* native_context = BuildGetNativeContext(); HInstruction* index = Add(static_cast(Context::ARRAY_FUNCTION_INDEX)); return Add(native_context, index, nullptr, nullptr, FAST_ELEMENTS); } HValue* HGraphBuilder::BuildArrayBufferViewFieldAccessor(HValue* object, HValue* checked_object, FieldIndex index) { NoObservableSideEffectsScope scope(this); HObjectAccess access = HObjectAccess::ForObservableJSObjectOffset( index.offset(), Representation::Tagged()); HInstruction* buffer = Add( object, checked_object, HObjectAccess::ForJSArrayBufferViewBuffer()); HInstruction* field = Add(object, checked_object, access); HInstruction* flags = Add( buffer, nullptr, HObjectAccess::ForJSArrayBufferBitField()); HValue* was_neutered_mask = Add(1 << JSArrayBuffer::WasNeutered::kShift); HValue* was_neutered_test = AddUncasted(Token::BIT_AND, flags, was_neutered_mask); IfBuilder if_was_neutered(this); if_was_neutered.If( was_neutered_test, graph()->GetConstant0(), Token::NE); if_was_neutered.Then(); Push(graph()->GetConstant0()); if_was_neutered.Else(); Push(field); if_was_neutered.End(); return Pop(); } HValue* HGraphBuilder::AddLoadJSBuiltin(int context_index) { HValue* native_context = BuildGetNativeContext(); HObjectAccess function_access = HObjectAccess::ForContextSlot(context_index); return Add(native_context, nullptr, function_access); } HOptimizedGraphBuilder::HOptimizedGraphBuilder(CompilationInfo* info, bool track_positions) : HGraphBuilder(info, CallInterfaceDescriptor(), track_positions), function_state_(NULL), initial_function_state_(this, info, NORMAL_RETURN, -1, TailCallMode::kAllow), ast_context_(NULL), break_scope_(NULL), inlined_count_(0), globals_(10, info->zone()), osr_(new (info->zone()) HOsrBuilder(this)), bounds_(info->zone()) { // This is not initialized in the initializer list because the // constructor for the initial state relies on function_state_ == NULL // to know it's the initial state. function_state_ = &initial_function_state_; InitializeAstVisitor(info->isolate()); } HBasicBlock* HOptimizedGraphBuilder::CreateJoin(HBasicBlock* first, HBasicBlock* second, BailoutId join_id) { if (first == NULL) { return second; } else if (second == NULL) { return first; } else { HBasicBlock* join_block = graph()->CreateBasicBlock(); Goto(first, join_block); Goto(second, join_block); join_block->SetJoinId(join_id); return join_block; } } HBasicBlock* HOptimizedGraphBuilder::JoinContinue(IterationStatement* statement, BailoutId continue_id, HBasicBlock* exit_block, HBasicBlock* continue_block) { if (continue_block != NULL) { if (exit_block != NULL) Goto(exit_block, continue_block); continue_block->SetJoinId(continue_id); return continue_block; } return exit_block; } HBasicBlock* HOptimizedGraphBuilder::CreateLoop(IterationStatement* statement, HBasicBlock* loop_entry, HBasicBlock* body_exit, HBasicBlock* loop_successor, HBasicBlock* break_block) { if (body_exit != NULL) Goto(body_exit, loop_entry); loop_entry->PostProcessLoopHeader(statement); if (break_block != NULL) { if (loop_successor != NULL) Goto(loop_successor, break_block); break_block->SetJoinId(statement->ExitId()); return break_block; } return loop_successor; } // Build a new loop header block and set it as the current block. HBasicBlock* HOptimizedGraphBuilder::BuildLoopEntry() { HBasicBlock* loop_entry = CreateLoopHeaderBlock(); Goto(loop_entry); set_current_block(loop_entry); return loop_entry; } HBasicBlock* HOptimizedGraphBuilder::BuildLoopEntry( IterationStatement* statement) { HBasicBlock* loop_entry; if (osr()->HasOsrEntryAt(statement)) { loop_entry = osr()->BuildOsrLoopEntry(statement); if (function_state()->IsInsideDoExpressionScope()) { Bailout(kDoExpressionUnmodelable); } } else { loop_entry = BuildLoopEntry(); } return loop_entry; } void HBasicBlock::FinishExit(HControlInstruction* instruction, SourcePosition position) { Finish(instruction, position); ClearEnvironment(); } std::ostream& operator<<(std::ostream& os, const HBasicBlock& b) { return os << "B" << b.block_id(); } HGraph::HGraph(CompilationInfo* info, CallInterfaceDescriptor descriptor) : isolate_(info->isolate()), next_block_id_(0), entry_block_(NULL), blocks_(8, info->zone()), values_(16, info->zone()), phi_list_(NULL), uint32_instructions_(NULL), osr_(NULL), info_(info), descriptor_(descriptor), zone_(info->zone()), allow_code_motion_(false), use_optimistic_licm_(false), depends_on_empty_array_proto_elements_(false), depends_on_string_length_overflow_(false), type_change_checksum_(0), maximum_environment_size_(0), no_side_effects_scope_count_(0), disallow_adding_new_values_(false) { if (info->IsStub()) { // For stubs, explicitly add the context to the environment. start_environment_ = new (zone_) HEnvironment(zone_, descriptor.GetParameterCount() + 1); } else { start_environment_ = new(zone_) HEnvironment(NULL, info->scope(), info->closure(), zone_); } start_environment_->set_ast_id(BailoutId::FunctionContext()); entry_block_ = CreateBasicBlock(); entry_block_->SetInitialEnvironment(start_environment_); } HBasicBlock* HGraph::CreateBasicBlock() { HBasicBlock* result = new(zone()) HBasicBlock(this); blocks_.Add(result, zone()); return result; } void HGraph::FinalizeUniqueness() { DisallowHeapAllocation no_gc; for (int i = 0; i < blocks()->length(); ++i) { for (HInstructionIterator it(blocks()->at(i)); !it.Done(); it.Advance()) { it.Current()->FinalizeUniqueness(); } } } // Block ordering was implemented with two mutually recursive methods, // HGraph::Postorder and HGraph::PostorderLoopBlocks. // The recursion could lead to stack overflow so the algorithm has been // implemented iteratively. // At a high level the algorithm looks like this: // // Postorder(block, loop_header) : { // if (block has already been visited or is of another loop) return; // mark block as visited; // if (block is a loop header) { // VisitLoopMembers(block, loop_header); // VisitSuccessorsOfLoopHeader(block); // } else { // VisitSuccessors(block) // } // put block in result list; // } // // VisitLoopMembers(block, outer_loop_header) { // foreach (block b in block loop members) { // VisitSuccessorsOfLoopMember(b, outer_loop_header); // if (b is loop header) VisitLoopMembers(b); // } // } // // VisitSuccessorsOfLoopMember(block, outer_loop_header) { // foreach (block b in block successors) Postorder(b, outer_loop_header) // } // // VisitSuccessorsOfLoopHeader(block) { // foreach (block b in block successors) Postorder(b, block) // } // // VisitSuccessors(block, loop_header) { // foreach (block b in block successors) Postorder(b, loop_header) // } // // The ordering is started calling Postorder(entry, NULL). // // Each instance of PostorderProcessor represents the "stack frame" of the // recursion, and particularly keeps the state of the loop (iteration) of the // "Visit..." function it represents. // To recycle memory we keep all the frames in a double linked list but // this means that we cannot use constructors to initialize the frames. // class PostorderProcessor : public ZoneObject { public: // Back link (towards the stack bottom). PostorderProcessor* parent() {return father_; } // Forward link (towards the stack top). PostorderProcessor* child() {return child_; } HBasicBlock* block() { return block_; } HLoopInformation* loop() { return loop_; } HBasicBlock* loop_header() { return loop_header_; } static PostorderProcessor* CreateEntryProcessor(Zone* zone, HBasicBlock* block) { PostorderProcessor* result = new(zone) PostorderProcessor(NULL); return result->SetupSuccessors(zone, block, NULL); } PostorderProcessor* PerformStep(Zone* zone, ZoneList* order) { PostorderProcessor* next = PerformNonBacktrackingStep(zone, order); if (next != NULL) { return next; } else { return Backtrack(zone, order); } } private: explicit PostorderProcessor(PostorderProcessor* father) : father_(father), child_(NULL), successor_iterator(NULL) { } // Each enum value states the cycle whose state is kept by this instance. enum LoopKind { NONE, SUCCESSORS, SUCCESSORS_OF_LOOP_HEADER, LOOP_MEMBERS, SUCCESSORS_OF_LOOP_MEMBER }; // Each "Setup..." method is like a constructor for a cycle state. PostorderProcessor* SetupSuccessors(Zone* zone, HBasicBlock* block, HBasicBlock* loop_header) { if (block == NULL || block->IsOrdered() || block->parent_loop_header() != loop_header) { kind_ = NONE; block_ = NULL; loop_ = NULL; loop_header_ = NULL; return this; } else { block_ = block; loop_ = NULL; block->MarkAsOrdered(); if (block->IsLoopHeader()) { kind_ = SUCCESSORS_OF_LOOP_HEADER; loop_header_ = block; InitializeSuccessors(); PostorderProcessor* result = Push(zone); return result->SetupLoopMembers(zone, block, block->loop_information(), loop_header); } else { DCHECK(block->IsFinished()); kind_ = SUCCESSORS; loop_header_ = loop_header; InitializeSuccessors(); return this; } } } PostorderProcessor* SetupLoopMembers(Zone* zone, HBasicBlock* block, HLoopInformation* loop, HBasicBlock* loop_header) { kind_ = LOOP_MEMBERS; block_ = block; loop_ = loop; loop_header_ = loop_header; InitializeLoopMembers(); return this; } PostorderProcessor* SetupSuccessorsOfLoopMember( HBasicBlock* block, HLoopInformation* loop, HBasicBlock* loop_header) { kind_ = SUCCESSORS_OF_LOOP_MEMBER; block_ = block; loop_ = loop; loop_header_ = loop_header; InitializeSuccessors(); return this; } // This method "allocates" a new stack frame. PostorderProcessor* Push(Zone* zone) { if (child_ == NULL) { child_ = new(zone) PostorderProcessor(this); } return child_; } void ClosePostorder(ZoneList* order, Zone* zone) { DCHECK(block_->end()->FirstSuccessor() == NULL || order->Contains(block_->end()->FirstSuccessor()) || block_->end()->FirstSuccessor()->IsLoopHeader()); DCHECK(block_->end()->SecondSuccessor() == NULL || order->Contains(block_->end()->SecondSuccessor()) || block_->end()->SecondSuccessor()->IsLoopHeader()); order->Add(block_, zone); } // This method is the basic block to walk up the stack. PostorderProcessor* Pop(Zone* zone, ZoneList* order) { switch (kind_) { case SUCCESSORS: case SUCCESSORS_OF_LOOP_HEADER: ClosePostorder(order, zone); return father_; case LOOP_MEMBERS: return father_; case SUCCESSORS_OF_LOOP_MEMBER: if (block()->IsLoopHeader() && block() != loop_->loop_header()) { // In this case we need to perform a LOOP_MEMBERS cycle so we // initialize it and return this instead of father. return SetupLoopMembers(zone, block(), block()->loop_information(), loop_header_); } else { return father_; } case NONE: return father_; } UNREACHABLE(); return NULL; } // Walks up the stack. PostorderProcessor* Backtrack(Zone* zone, ZoneList* order) { PostorderProcessor* parent = Pop(zone, order); while (parent != NULL) { PostorderProcessor* next = parent->PerformNonBacktrackingStep(zone, order); if (next != NULL) { return next; } else { parent = parent->Pop(zone, order); } } return NULL; } PostorderProcessor* PerformNonBacktrackingStep( Zone* zone, ZoneList* order) { HBasicBlock* next_block; switch (kind_) { case SUCCESSORS: next_block = AdvanceSuccessors(); if (next_block != NULL) { PostorderProcessor* result = Push(zone); return result->SetupSuccessors(zone, next_block, loop_header_); } break; case SUCCESSORS_OF_LOOP_HEADER: next_block = AdvanceSuccessors(); if (next_block != NULL) { PostorderProcessor* result = Push(zone); return result->SetupSuccessors(zone, next_block, block()); } break; case LOOP_MEMBERS: next_block = AdvanceLoopMembers(); if (next_block != NULL) { PostorderProcessor* result = Push(zone); return result->SetupSuccessorsOfLoopMember(next_block, loop_, loop_header_); } break; case SUCCESSORS_OF_LOOP_MEMBER: next_block = AdvanceSuccessors(); if (next_block != NULL) { PostorderProcessor* result = Push(zone); return result->SetupSuccessors(zone, next_block, loop_header_); } break; case NONE: return NULL; } return NULL; } // The following two methods implement a "foreach b in successors" cycle. void InitializeSuccessors() { loop_index = 0; loop_length = 0; successor_iterator = HSuccessorIterator(block_->end()); } HBasicBlock* AdvanceSuccessors() { if (!successor_iterator.Done()) { HBasicBlock* result = successor_iterator.Current(); successor_iterator.Advance(); return result; } return NULL; } // The following two methods implement a "foreach b in loop members" cycle. void InitializeLoopMembers() { loop_index = 0; loop_length = loop_->blocks()->length(); } HBasicBlock* AdvanceLoopMembers() { if (loop_index < loop_length) { HBasicBlock* result = loop_->blocks()->at(loop_index); loop_index++; return result; } else { return NULL; } } LoopKind kind_; PostorderProcessor* father_; PostorderProcessor* child_; HLoopInformation* loop_; HBasicBlock* block_; HBasicBlock* loop_header_; int loop_index; int loop_length; HSuccessorIterator successor_iterator; }; void HGraph::OrderBlocks() { CompilationPhase phase("H_Block ordering", info()); #ifdef DEBUG // Initially the blocks must not be ordered. for (int i = 0; i < blocks_.length(); ++i) { DCHECK(!blocks_[i]->IsOrdered()); } #endif PostorderProcessor* postorder = PostorderProcessor::CreateEntryProcessor(zone(), blocks_[0]); blocks_.Rewind(0); while (postorder) { postorder = postorder->PerformStep(zone(), &blocks_); } #ifdef DEBUG // Now all blocks must be marked as ordered. for (int i = 0; i < blocks_.length(); ++i) { DCHECK(blocks_[i]->IsOrdered()); } #endif // Reverse block list and assign block IDs. for (int i = 0, j = blocks_.length(); --j >= i; ++i) { HBasicBlock* bi = blocks_[i]; HBasicBlock* bj = blocks_[j]; bi->set_block_id(j); bj->set_block_id(i); blocks_[i] = bj; blocks_[j] = bi; } } void HGraph::AssignDominators() { HPhase phase("H_Assign dominators", this); for (int i = 0; i < blocks_.length(); ++i) { HBasicBlock* block = blocks_[i]; if (block->IsLoopHeader()) { // Only the first predecessor of a loop header is from outside the loop. // All others are back edges, and thus cannot dominate the loop header. block->AssignCommonDominator(block->predecessors()->first()); block->AssignLoopSuccessorDominators(); } else { for (int j = blocks_[i]->predecessors()->length() - 1; j >= 0; --j) { blocks_[i]->AssignCommonDominator(blocks_[i]->predecessors()->at(j)); } } } } bool HGraph::CheckArgumentsPhiUses() { int block_count = blocks_.length(); for (int i = 0; i < block_count; ++i) { for (int j = 0; j < blocks_[i]->phis()->length(); ++j) { HPhi* phi = blocks_[i]->phis()->at(j); // We don't support phi uses of arguments for now. if (phi->CheckFlag(HValue::kIsArguments)) return false; } } return true; } bool HGraph::CheckConstPhiUses() { int block_count = blocks_.length(); for (int i = 0; i < block_count; ++i) { for (int j = 0; j < blocks_[i]->phis()->length(); ++j) { HPhi* phi = blocks_[i]->phis()->at(j); // Check for the hole value (from an uninitialized const). for (int k = 0; k < phi->OperandCount(); k++) { if (phi->OperandAt(k) == GetConstantHole()) return false; } } } return true; } void HGraph::CollectPhis() { int block_count = blocks_.length(); phi_list_ = new(zone()) ZoneList(block_count, zone()); for (int i = 0; i < block_count; ++i) { for (int j = 0; j < blocks_[i]->phis()->length(); ++j) { HPhi* phi = blocks_[i]->phis()->at(j); phi_list_->Add(phi, zone()); } } } // Implementation of utility class to encapsulate the translation state for // a (possibly inlined) function. FunctionState::FunctionState(HOptimizedGraphBuilder* owner, CompilationInfo* info, InliningKind inlining_kind, int inlining_id, TailCallMode tail_call_mode) : owner_(owner), compilation_info_(info), call_context_(NULL), inlining_kind_(inlining_kind), tail_call_mode_(tail_call_mode), function_return_(NULL), test_context_(NULL), entry_(NULL), arguments_object_(NULL), arguments_elements_(NULL), inlining_id_(inlining_id), outer_source_position_(SourcePosition::Unknown()), do_expression_scope_count_(0), outer_(owner->function_state()) { if (outer_ != NULL) { // State for an inline function. if (owner->ast_context()->IsTest()) { HBasicBlock* if_true = owner->graph()->CreateBasicBlock(); HBasicBlock* if_false = owner->graph()->CreateBasicBlock(); if_true->MarkAsInlineReturnTarget(owner->current_block()); if_false->MarkAsInlineReturnTarget(owner->current_block()); TestContext* outer_test_context = TestContext::cast(owner->ast_context()); Expression* cond = outer_test_context->condition(); // The AstContext constructor pushed on the context stack. This newed // instance is the reason that AstContext can't be BASE_EMBEDDED. test_context_ = new TestContext(owner, cond, if_true, if_false); } else { function_return_ = owner->graph()->CreateBasicBlock(); function_return()->MarkAsInlineReturnTarget(owner->current_block()); } // Set this after possibly allocating a new TestContext above. call_context_ = owner->ast_context(); } // Push on the state stack. owner->set_function_state(this); if (owner->is_tracking_positions()) { outer_source_position_ = owner->source_position(); owner->EnterInlinedSource(inlining_id); owner->SetSourcePosition(info->shared_info()->start_position()); } } FunctionState::~FunctionState() { delete test_context_; owner_->set_function_state(outer_); if (owner_->is_tracking_positions()) { owner_->set_source_position(outer_source_position_); owner_->EnterInlinedSource(outer_->inlining_id()); } } // Implementation of utility classes to represent an expression's context in // the AST. AstContext::AstContext(HOptimizedGraphBuilder* owner, Expression::Context kind) : owner_(owner), kind_(kind), outer_(owner->ast_context()), typeof_mode_(NOT_INSIDE_TYPEOF) { owner->set_ast_context(this); // Push. #ifdef DEBUG DCHECK_EQ(JS_FUNCTION, owner->environment()->frame_type()); original_length_ = owner->environment()->length(); #endif } AstContext::~AstContext() { owner_->set_ast_context(outer_); // Pop. } EffectContext::~EffectContext() { DCHECK(owner()->HasStackOverflow() || owner()->current_block() == NULL || (owner()->environment()->length() == original_length_ && (owner()->environment()->frame_type() == JS_FUNCTION || owner()->environment()->frame_type() == TAIL_CALLER_FUNCTION))); } ValueContext::~ValueContext() { DCHECK(owner()->HasStackOverflow() || owner()->current_block() == NULL || (owner()->environment()->length() == original_length_ + 1 && (owner()->environment()->frame_type() == JS_FUNCTION || owner()->environment()->frame_type() == TAIL_CALLER_FUNCTION))); } void EffectContext::ReturnValue(HValue* value) { // The value is simply ignored. } void ValueContext::ReturnValue(HValue* value) { // The value is tracked in the bailout environment, and communicated // through the environment as the result of the expression. if (value->CheckFlag(HValue::kIsArguments)) { if (flag_ == ARGUMENTS_FAKED) { value = owner()->graph()->GetConstantUndefined(); } else if (!arguments_allowed()) { owner()->Bailout(kBadValueContextForArgumentsValue); } } owner()->Push(value); } void TestContext::ReturnValue(HValue* value) { BuildBranch(value); } void EffectContext::ReturnInstruction(HInstruction* instr, BailoutId ast_id) { DCHECK(!instr->IsControlInstruction()); owner()->AddInstruction(instr); if (instr->HasObservableSideEffects()) { owner()->Add(ast_id, REMOVABLE_SIMULATE); } } void EffectContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) { DCHECK(!instr->HasObservableSideEffects()); HBasicBlock* empty_true = owner()->graph()->CreateBasicBlock(); HBasicBlock* empty_false = owner()->graph()->CreateBasicBlock(); instr->SetSuccessorAt(0, empty_true); instr->SetSuccessorAt(1, empty_false); owner()->FinishCurrentBlock(instr); HBasicBlock* join = owner()->CreateJoin(empty_true, empty_false, ast_id); owner()->set_current_block(join); } void EffectContext::ReturnContinuation(HIfContinuation* continuation, BailoutId ast_id) { HBasicBlock* true_branch = NULL; HBasicBlock* false_branch = NULL; continuation->Continue(&true_branch, &false_branch); if (!continuation->IsTrueReachable()) { owner()->set_current_block(false_branch); } else if (!continuation->IsFalseReachable()) { owner()->set_current_block(true_branch); } else { HBasicBlock* join = owner()->CreateJoin(true_branch, false_branch, ast_id); owner()->set_current_block(join); } } void ValueContext::ReturnInstruction(HInstruction* instr, BailoutId ast_id) { DCHECK(!instr->IsControlInstruction()); if (!arguments_allowed() && instr->CheckFlag(HValue::kIsArguments)) { return owner()->Bailout(kBadValueContextForArgumentsObjectValue); } owner()->AddInstruction(instr); owner()->Push(instr); if (instr->HasObservableSideEffects()) { owner()->Add(ast_id, REMOVABLE_SIMULATE); } } void ValueContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) { DCHECK(!instr->HasObservableSideEffects()); if (!arguments_allowed() && instr->CheckFlag(HValue::kIsArguments)) { return owner()->Bailout(kBadValueContextForArgumentsObjectValue); } HBasicBlock* materialize_false = owner()->graph()->CreateBasicBlock(); HBasicBlock* materialize_true = owner()->graph()->CreateBasicBlock(); instr->SetSuccessorAt(0, materialize_true); instr->SetSuccessorAt(1, materialize_false); owner()->FinishCurrentBlock(instr); owner()->set_current_block(materialize_true); owner()->Push(owner()->graph()->GetConstantTrue()); owner()->set_current_block(materialize_false); owner()->Push(owner()->graph()->GetConstantFalse()); HBasicBlock* join = owner()->CreateJoin(materialize_true, materialize_false, ast_id); owner()->set_current_block(join); } void ValueContext::ReturnContinuation(HIfContinuation* continuation, BailoutId ast_id) { HBasicBlock* materialize_true = NULL; HBasicBlock* materialize_false = NULL; continuation->Continue(&materialize_true, &materialize_false); if (continuation->IsTrueReachable()) { owner()->set_current_block(materialize_true); owner()->Push(owner()->graph()->GetConstantTrue()); owner()->set_current_block(materialize_true); } if (continuation->IsFalseReachable()) { owner()->set_current_block(materialize_false); owner()->Push(owner()->graph()->GetConstantFalse()); owner()->set_current_block(materialize_false); } if (continuation->TrueAndFalseReachable()) { HBasicBlock* join = owner()->CreateJoin(materialize_true, materialize_false, ast_id); owner()->set_current_block(join); } } void TestContext::ReturnInstruction(HInstruction* instr, BailoutId ast_id) { DCHECK(!instr->IsControlInstruction()); HOptimizedGraphBuilder* builder = owner(); builder->AddInstruction(instr); // We expect a simulate after every expression with side effects, though // this one isn't actually needed (and wouldn't work if it were targeted). if (instr->HasObservableSideEffects()) { builder->Push(instr); builder->Add(ast_id, REMOVABLE_SIMULATE); builder->Pop(); } BuildBranch(instr); } void TestContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) { DCHECK(!instr->HasObservableSideEffects()); HBasicBlock* empty_true = owner()->graph()->CreateBasicBlock(); HBasicBlock* empty_false = owner()->graph()->CreateBasicBlock(); instr->SetSuccessorAt(0, empty_true); instr->SetSuccessorAt(1, empty_false); owner()->FinishCurrentBlock(instr); owner()->Goto(empty_true, if_true(), owner()->function_state()); owner()->Goto(empty_false, if_false(), owner()->function_state()); owner()->set_current_block(NULL); } void TestContext::ReturnContinuation(HIfContinuation* continuation, BailoutId ast_id) { HBasicBlock* true_branch = NULL; HBasicBlock* false_branch = NULL; continuation->Continue(&true_branch, &false_branch); if (continuation->IsTrueReachable()) { owner()->Goto(true_branch, if_true(), owner()->function_state()); } if (continuation->IsFalseReachable()) { owner()->Goto(false_branch, if_false(), owner()->function_state()); } owner()->set_current_block(NULL); } void TestContext::BuildBranch(HValue* value) { // We expect the graph to be in edge-split form: there is no edge that // connects a branch node to a join node. We conservatively ensure that // property by always adding an empty block on the outgoing edges of this // branch. HOptimizedGraphBuilder* builder = owner(); if (value != NULL && value->CheckFlag(HValue::kIsArguments)) { builder->Bailout(kArgumentsObjectValueInATestContext); } ToBooleanHints expected(condition()->to_boolean_types()); ReturnControl(owner()->New(value, expected), BailoutId::None()); } // HOptimizedGraphBuilder infrastructure for bailing out and checking bailouts. #define CHECK_BAILOUT(call) \ do { \ call; \ if (HasStackOverflow()) return; \ } while (false) #define CHECK_ALIVE(call) \ do { \ call; \ if (HasStackOverflow() || current_block() == NULL) return; \ } while (false) #define CHECK_ALIVE_OR_RETURN(call, value) \ do { \ call; \ if (HasStackOverflow() || current_block() == NULL) return value; \ } while (false) void HOptimizedGraphBuilder::Bailout(BailoutReason reason) { current_info()->AbortOptimization(reason); SetStackOverflow(); } void HOptimizedGraphBuilder::VisitForEffect(Expression* expr) { EffectContext for_effect(this); Visit(expr); } void HOptimizedGraphBuilder::VisitForValue(Expression* expr, ArgumentsAllowedFlag flag) { ValueContext for_value(this, flag); Visit(expr); } void HOptimizedGraphBuilder::VisitForTypeOf(Expression* expr) { ValueContext for_value(this, ARGUMENTS_NOT_ALLOWED); for_value.set_typeof_mode(INSIDE_TYPEOF); Visit(expr); } void HOptimizedGraphBuilder::VisitForControl(Expression* expr, HBasicBlock* true_block, HBasicBlock* false_block) { TestContext for_control(this, expr, true_block, false_block); Visit(expr); } void HOptimizedGraphBuilder::VisitExpressions( ZoneList* exprs) { for (int i = 0; i < exprs->length(); ++i) { CHECK_ALIVE(VisitForValue(exprs->at(i))); } } void HOptimizedGraphBuilder::VisitExpressions(ZoneList* exprs, ArgumentsAllowedFlag flag) { for (int i = 0; i < exprs->length(); ++i) { CHECK_ALIVE(VisitForValue(exprs->at(i), flag)); } } bool HOptimizedGraphBuilder::BuildGraph() { if (IsDerivedConstructor(current_info()->literal()->kind())) { Bailout(kSuperReference); return false; } DeclarationScope* scope = current_info()->scope(); SetUpScope(scope); // Add an edge to the body entry. This is warty: the graph's start // environment will be used by the Lithium translation as the initial // environment on graph entry, but it has now been mutated by the // Hydrogen translation of the instructions in the start block. This // environment uses values which have not been defined yet. These // Hydrogen instructions will then be replayed by the Lithium // translation, so they cannot have an environment effect. The edge to // the body's entry block (along with some special logic for the start // block in HInstruction::InsertAfter) seals the start block from // getting unwanted instructions inserted. // // TODO(kmillikin): Fix this. Stop mutating the initial environment. // Make the Hydrogen instructions in the initial block into Hydrogen // values (but not instructions), present in the initial environment and // not replayed by the Lithium translation. HEnvironment* initial_env = environment()->CopyWithoutHistory(); HBasicBlock* body_entry = CreateBasicBlock(initial_env); Goto(body_entry); body_entry->SetJoinId(BailoutId::FunctionEntry()); set_current_block(body_entry); VisitDeclarations(scope->declarations()); Add(BailoutId::Declarations()); Add(HStackCheck::kFunctionEntry); VisitStatements(current_info()->literal()->body()); if (HasStackOverflow()) return false; if (current_block() != NULL) { Add(graph()->GetConstantUndefined()); set_current_block(NULL); } // If the checksum of the number of type info changes is the same as the // last time this function was compiled, then this recompile is likely not // due to missing/inadequate type feedback, but rather too aggressive // optimization. Disable optimistic LICM in that case. Handle unoptimized_code(current_info()->shared_info()->code()); DCHECK(unoptimized_code->kind() == Code::FUNCTION); Handle type_info( TypeFeedbackInfo::cast(unoptimized_code->type_feedback_info())); int checksum = type_info->own_type_change_checksum(); int composite_checksum = graph()->update_type_change_checksum(checksum); graph()->set_use_optimistic_licm( !type_info->matches_inlined_type_change_checksum(composite_checksum)); type_info->set_inlined_type_change_checksum(composite_checksum); // Set this predicate early to avoid handle deref during graph optimization. graph()->set_allow_code_motion( current_info()->IsStub() || current_info()->shared_info()->opt_count() + 1 < FLAG_max_opt_count); // Perform any necessary OSR-specific cleanups or changes to the graph. osr()->FinishGraph(); return true; } bool HGraph::Optimize(BailoutReason* bailout_reason) { OrderBlocks(); AssignDominators(); // We need to create a HConstant "zero" now so that GVN will fold every // zero-valued constant in the graph together. // The constant is needed to make idef-based bounds check work: the pass // evaluates relations with "zero" and that zero cannot be created after GVN. GetConstant0(); #ifdef DEBUG // Do a full verify after building the graph and computing dominators. Verify(true); #endif if (FLAG_analyze_environment_liveness && maximum_environment_size() != 0) { Run(); } if (!CheckConstPhiUses()) { *bailout_reason = kUnsupportedPhiUseOfConstVariable; return false; } Run(); if (!CheckArgumentsPhiUses()) { *bailout_reason = kUnsupportedPhiUseOfArguments; return false; } // Find and mark unreachable code to simplify optimizations, especially gvn, // where unreachable code could unnecessarily defeat LICM. Run(); if (FLAG_dead_code_elimination) Run(); if (FLAG_use_escape_analysis) Run(); if (FLAG_load_elimination) Run(); CollectPhis(); if (has_osr()) osr()->FinishOsrValues(); Run(); // Remove HSimulate instructions that have turned out not to be needed // after all by folding them into the following HSimulate. // This must happen after inferring representations. Run(); Run(); Run(); // Must be performed before canonicalization to ensure that Canonicalize // will not remove semantically meaningful ToInt32 operations e.g. BIT_OR with // zero. Run(); if (FLAG_use_canonicalizing) Run(); if (FLAG_use_gvn) Run(); if (FLAG_check_elimination) Run(); if (FLAG_store_elimination) Run(); Run(); // Eliminate redundant stack checks on backwards branches. Run(); if (FLAG_array_bounds_checks_elimination) Run(); if (FLAG_array_index_dehoisting) Run(); if (FLAG_dead_code_elimination) Run(); RestoreActualValues(); // Find unreachable code a second time, GVN and other optimizations may have // made blocks unreachable that were previously reachable. Run(); return true; } void HGraph::RestoreActualValues() { HPhase phase("H_Restore actual values", this); for (int block_index = 0; block_index < blocks()->length(); block_index++) { HBasicBlock* block = blocks()->at(block_index); #ifdef DEBUG for (int i = 0; i < block->phis()->length(); i++) { HPhi* phi = block->phis()->at(i); DCHECK(phi->ActualValue() == phi); } #endif for (HInstructionIterator it(block); !it.Done(); it.Advance()) { HInstruction* instruction = it.Current(); if (instruction->ActualValue() == instruction) continue; if (instruction->CheckFlag(HValue::kIsDead)) { // The instruction was marked as deleted but left in the graph // as a control flow dependency point for subsequent // instructions. instruction->DeleteAndReplaceWith(instruction->ActualValue()); } else { DCHECK(instruction->IsInformativeDefinition()); if (instruction->IsPurelyInformativeDefinition()) { instruction->DeleteAndReplaceWith(instruction->RedefinedOperand()); } else { instruction->ReplaceAllUsesWith(instruction->ActualValue()); } } } } } void HOptimizedGraphBuilder::PushArgumentsFromEnvironment(int count) { ZoneList arguments(count, zone()); for (int i = 0; i < count; ++i) { arguments.Add(Pop(), zone()); } HPushArguments* push_args = New(); while (!arguments.is_empty()) { push_args->AddInput(arguments.RemoveLast()); } AddInstruction(push_args); } template HInstruction* HOptimizedGraphBuilder::PreProcessCall(Instruction* call) { PushArgumentsFromEnvironment(call->argument_count()); return call; } void HOptimizedGraphBuilder::SetUpScope(DeclarationScope* scope) { HEnvironment* prolog_env = environment(); int parameter_count = environment()->parameter_count(); ZoneList parameters(parameter_count, zone()); for (int i = 0; i < parameter_count; ++i) { HInstruction* parameter = Add(static_cast(i)); parameters.Add(parameter, zone()); environment()->Bind(i, parameter); } HConstant* undefined_constant = graph()->GetConstantUndefined(); // Initialize specials and locals to undefined. for (int i = parameter_count + 1; i < environment()->length(); ++i) { environment()->Bind(i, undefined_constant); } Add(); HEnvironment* initial_env = environment()->CopyWithoutHistory(); HBasicBlock* body_entry = CreateBasicBlock(initial_env); GotoNoSimulate(body_entry); set_current_block(body_entry); // Initialize context of prolog environment to undefined. prolog_env->BindContext(undefined_constant); // First special is HContext. HInstruction* context = Add(); environment()->BindContext(context); // Create an arguments object containing the initial parameters. Set the // initial values of parameters including "this" having parameter index 0. DCHECK_EQ(scope->num_parameters() + 1, parameter_count); HArgumentsObject* arguments_object = New(parameter_count); for (int i = 0; i < parameter_count; ++i) { HValue* parameter = parameters.at(i); arguments_object->AddArgument(parameter, zone()); } AddInstruction(arguments_object); // Handle the arguments and arguments shadow variables specially (they do // not have declarations). if (scope->arguments() != NULL) { environment()->Bind(scope->arguments(), arguments_object); } if (scope->rest_parameter() != nullptr) { return Bailout(kRestParameter); } if (scope->this_function_var() != nullptr || scope->new_target_var() != nullptr) { return Bailout(kSuperReference); } // Trace the call. if (FLAG_trace && top_info()->IsOptimizing()) { Add(Runtime::FunctionForId(Runtime::kTraceEnter), 0); } } void HOptimizedGraphBuilder::VisitStatements(ZoneList* statements) { for (int i = 0; i < statements->length(); i++) { Statement* stmt = statements->at(i); CHECK_ALIVE(Visit(stmt)); if (stmt->IsJump()) break; } } void HOptimizedGraphBuilder::VisitBlock(Block* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); Scope* outer_scope = scope(); Scope* scope = stmt->scope(); BreakAndContinueInfo break_info(stmt, outer_scope); { BreakAndContinueScope push(&break_info, this); if (scope != NULL) { if (scope->NeedsContext()) { // Load the function object. DeclarationScope* declaration_scope = scope->GetDeclarationScope(); HInstruction* function; HValue* outer_context = environment()->context(); if (declaration_scope->is_script_scope() || declaration_scope->is_eval_scope()) { function = new (zone()) HLoadContextSlot(outer_context, Context::CLOSURE_INDEX, HLoadContextSlot::kNoCheck); } else { function = New(); } AddInstruction(function); // Allocate a block context and store it to the stack frame. HValue* scope_info = Add(scope->scope_info()); Add(scope_info, function); HInstruction* inner_context = Add( Runtime::FunctionForId(Runtime::kPushBlockContext), 2); inner_context->SetFlag(HValue::kHasNoObservableSideEffects); set_scope(scope); environment()->BindContext(inner_context); } VisitDeclarations(scope->declarations()); AddSimulate(stmt->DeclsId(), REMOVABLE_SIMULATE); } CHECK_BAILOUT(VisitStatements(stmt->statements())); } set_scope(outer_scope); if (scope != NULL && current_block() != NULL && scope->ContextLocalCount() > 0) { HValue* inner_context = environment()->context(); HValue* outer_context = Add( inner_context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); environment()->BindContext(outer_context); } HBasicBlock* break_block = break_info.break_block(); if (break_block != NULL) { if (current_block() != NULL) Goto(break_block); break_block->SetJoinId(stmt->ExitId()); set_current_block(break_block); } } void HOptimizedGraphBuilder::VisitExpressionStatement( ExpressionStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); VisitForEffect(stmt->expression()); } void HOptimizedGraphBuilder::VisitEmptyStatement(EmptyStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); } void HOptimizedGraphBuilder::VisitSloppyBlockFunctionStatement( SloppyBlockFunctionStatement* stmt) { Visit(stmt->statement()); } void HOptimizedGraphBuilder::VisitIfStatement(IfStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (stmt->condition()->ToBooleanIsTrue()) { Add(stmt->ThenId()); Visit(stmt->then_statement()); } else if (stmt->condition()->ToBooleanIsFalse()) { Add(stmt->ElseId()); Visit(stmt->else_statement()); } else { HBasicBlock* cond_true = graph()->CreateBasicBlock(); HBasicBlock* cond_false = graph()->CreateBasicBlock(); CHECK_BAILOUT(VisitForControl(stmt->condition(), cond_true, cond_false)); // Technically, we should be able to handle the case when one side of // the test is not connected, but this can trip up liveness analysis // if we did not fully connect the test context based on some optimistic // assumption. If such an assumption was violated, we would end up with // an environment with optimized-out values. So we should always // conservatively connect the test context. CHECK(cond_true->HasPredecessor()); CHECK(cond_false->HasPredecessor()); cond_true->SetJoinId(stmt->ThenId()); set_current_block(cond_true); CHECK_BAILOUT(Visit(stmt->then_statement())); cond_true = current_block(); cond_false->SetJoinId(stmt->ElseId()); set_current_block(cond_false); CHECK_BAILOUT(Visit(stmt->else_statement())); cond_false = current_block(); HBasicBlock* join = CreateJoin(cond_true, cond_false, stmt->IfId()); set_current_block(join); } } HBasicBlock* HOptimizedGraphBuilder::BreakAndContinueScope::Get( BreakableStatement* stmt, BreakType type, Scope** scope, int* drop_extra) { *drop_extra = 0; BreakAndContinueScope* current = this; while (current != NULL && current->info()->target() != stmt) { *drop_extra += current->info()->drop_extra(); current = current->next(); } DCHECK(current != NULL); // Always found (unless stack is malformed). *scope = current->info()->scope(); if (type == BREAK) { *drop_extra += current->info()->drop_extra(); } HBasicBlock* block = NULL; switch (type) { case BREAK: block = current->info()->break_block(); if (block == NULL) { block = current->owner()->graph()->CreateBasicBlock(); current->info()->set_break_block(block); } break; case CONTINUE: block = current->info()->continue_block(); if (block == NULL) { block = current->owner()->graph()->CreateBasicBlock(); current->info()->set_continue_block(block); } break; } return block; } void HOptimizedGraphBuilder::VisitContinueStatement( ContinueStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (function_state()->IsInsideDoExpressionScope()) { return Bailout(kDoExpressionUnmodelable); } Scope* outer_scope = NULL; Scope* inner_scope = scope(); int drop_extra = 0; HBasicBlock* continue_block = break_scope()->Get( stmt->target(), BreakAndContinueScope::CONTINUE, &outer_scope, &drop_extra); HValue* context = environment()->context(); Drop(drop_extra); int context_pop_count = inner_scope->ContextChainLength(outer_scope); if (context_pop_count > 0) { while (context_pop_count-- > 0) { HInstruction* context_instruction = Add( context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); context = context_instruction; } environment()->BindContext(context); } Goto(continue_block); set_current_block(NULL); } void HOptimizedGraphBuilder::VisitBreakStatement(BreakStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (function_state()->IsInsideDoExpressionScope()) { return Bailout(kDoExpressionUnmodelable); } Scope* outer_scope = NULL; Scope* inner_scope = scope(); int drop_extra = 0; HBasicBlock* break_block = break_scope()->Get( stmt->target(), BreakAndContinueScope::BREAK, &outer_scope, &drop_extra); HValue* context = environment()->context(); Drop(drop_extra); int context_pop_count = inner_scope->ContextChainLength(outer_scope); if (context_pop_count > 0) { while (context_pop_count-- > 0) { HInstruction* context_instruction = Add( context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); context = context_instruction; } environment()->BindContext(context); } Goto(break_block); set_current_block(NULL); } void HOptimizedGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); FunctionState* state = function_state(); AstContext* context = call_context(); if (context == NULL) { // Not an inlined return, so an actual one. CHECK_ALIVE(VisitForValue(stmt->expression())); HValue* result = environment()->Pop(); Add(result); } else if (state->inlining_kind() == CONSTRUCT_CALL_RETURN) { // Return from an inlined construct call. In a test context the return value // will always evaluate to true, in a value context the return value needs // to be a JSObject. if (context->IsTest()) { CHECK_ALIVE(VisitForEffect(stmt->expression())); context->ReturnValue(graph()->GetConstantTrue()); } else if (context->IsEffect()) { CHECK_ALIVE(VisitForEffect(stmt->expression())); Goto(function_return(), state); } else { DCHECK(context->IsValue()); CHECK_ALIVE(VisitForValue(stmt->expression())); HValue* return_value = Pop(); HValue* receiver = environment()->arguments_environment()->Lookup(0); HHasInstanceTypeAndBranch* typecheck = New(return_value, FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE); HBasicBlock* if_spec_object = graph()->CreateBasicBlock(); HBasicBlock* not_spec_object = graph()->CreateBasicBlock(); typecheck->SetSuccessorAt(0, if_spec_object); typecheck->SetSuccessorAt(1, not_spec_object); FinishCurrentBlock(typecheck); AddLeaveInlined(if_spec_object, return_value, state); AddLeaveInlined(not_spec_object, receiver, state); } } else if (state->inlining_kind() == SETTER_CALL_RETURN) { // Return from an inlined setter call. The returned value is never used, the // value of an assignment is always the value of the RHS of the assignment. CHECK_ALIVE(VisitForEffect(stmt->expression())); if (context->IsTest()) { HValue* rhs = environment()->arguments_environment()->Lookup(1); context->ReturnValue(rhs); } else if (context->IsEffect()) { Goto(function_return(), state); } else { DCHECK(context->IsValue()); HValue* rhs = environment()->arguments_environment()->Lookup(1); AddLeaveInlined(rhs, state); } } else { // Return from a normal inlined function. Visit the subexpression in the // expression context of the call. if (context->IsTest()) { TestContext* test = TestContext::cast(context); VisitForControl(stmt->expression(), test->if_true(), test->if_false()); } else if (context->IsEffect()) { // Visit in value context and ignore the result. This is needed to keep // environment in sync with full-codegen since some visitors (e.g. // VisitCountOperation) use the operand stack differently depending on // context. CHECK_ALIVE(VisitForValue(stmt->expression())); Pop(); Goto(function_return(), state); } else { DCHECK(context->IsValue()); CHECK_ALIVE(VisitForValue(stmt->expression())); AddLeaveInlined(Pop(), state); } } set_current_block(NULL); } void HOptimizedGraphBuilder::VisitWithStatement(WithStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kWithStatement); } void HOptimizedGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); ZoneList* clauses = stmt->cases(); int clause_count = clauses->length(); ZoneList body_blocks(clause_count, zone()); CHECK_ALIVE(VisitForValue(stmt->tag())); Add(stmt->EntryId()); HValue* tag_value = Top(); AstType* tag_type = bounds_.get(stmt->tag()).lower; // 1. Build all the tests, with dangling true branches BailoutId default_id = BailoutId::None(); for (int i = 0; i < clause_count; ++i) { CaseClause* clause = clauses->at(i); if (clause->is_default()) { body_blocks.Add(NULL, zone()); if (default_id.IsNone()) default_id = clause->EntryId(); continue; } // Generate a compare and branch. CHECK_BAILOUT(VisitForValue(clause->label())); if (current_block() == NULL) return Bailout(kUnsupportedSwitchStatement); HValue* label_value = Pop(); AstType* label_type = bounds_.get(clause->label()).lower; AstType* combined_type = clause->compare_type(); HControlInstruction* compare = BuildCompareInstruction( Token::EQ_STRICT, tag_value, label_value, tag_type, label_type, combined_type, ScriptPositionToSourcePosition(stmt->tag()->position()), ScriptPositionToSourcePosition(clause->label()->position()), PUSH_BEFORE_SIMULATE, clause->id()); HBasicBlock* next_test_block = graph()->CreateBasicBlock(); HBasicBlock* body_block = graph()->CreateBasicBlock(); body_blocks.Add(body_block, zone()); compare->SetSuccessorAt(0, body_block); compare->SetSuccessorAt(1, next_test_block); FinishCurrentBlock(compare); set_current_block(body_block); Drop(1); // tag_value set_current_block(next_test_block); } // Save the current block to use for the default or to join with the // exit. HBasicBlock* last_block = current_block(); Drop(1); // tag_value // 2. Loop over the clauses and the linked list of tests in lockstep, // translating the clause bodies. HBasicBlock* fall_through_block = NULL; BreakAndContinueInfo break_info(stmt, scope()); { BreakAndContinueScope push(&break_info, this); for (int i = 0; i < clause_count; ++i) { CaseClause* clause = clauses->at(i); // Identify the block where normal (non-fall-through) control flow // goes to. HBasicBlock* normal_block = NULL; if (clause->is_default()) { if (last_block == NULL) continue; normal_block = last_block; last_block = NULL; // Cleared to indicate we've handled it. } else { normal_block = body_blocks[i]; } if (fall_through_block == NULL) { set_current_block(normal_block); } else { HBasicBlock* join = CreateJoin(fall_through_block, normal_block, clause->EntryId()); set_current_block(join); } CHECK_BAILOUT(VisitStatements(clause->statements())); fall_through_block = current_block(); } } // Create an up-to-3-way join. Use the break block if it exists since // it's already a join block. HBasicBlock* break_block = break_info.break_block(); if (break_block == NULL) { set_current_block(CreateJoin(fall_through_block, last_block, stmt->ExitId())); } else { if (fall_through_block != NULL) Goto(fall_through_block, break_block); if (last_block != NULL) Goto(last_block, break_block); break_block->SetJoinId(stmt->ExitId()); set_current_block(break_block); } } void HOptimizedGraphBuilder::VisitLoopBody(IterationStatement* stmt, BailoutId stack_check_id, HBasicBlock* loop_entry) { Add(stack_check_id); HStackCheck* stack_check = HStackCheck::cast(Add(HStackCheck::kBackwardsBranch)); DCHECK(loop_entry->IsLoopHeader()); loop_entry->loop_information()->set_stack_check(stack_check); CHECK_BAILOUT(Visit(stmt->body())); } void HOptimizedGraphBuilder::VisitDoWhileStatement(DoWhileStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); DCHECK(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); BreakAndContinueInfo break_info(stmt, scope()); { BreakAndContinueScope push(&break_info, this); CHECK_BAILOUT(VisitLoopBody(stmt, stmt->StackCheckId(), loop_entry)); } HBasicBlock* body_exit = JoinContinue( stmt, stmt->ContinueId(), current_block(), break_info.continue_block()); HBasicBlock* loop_successor = NULL; if (body_exit != NULL) { set_current_block(body_exit); loop_successor = graph()->CreateBasicBlock(); if (stmt->cond()->ToBooleanIsFalse()) { loop_entry->loop_information()->stack_check()->Eliminate(); Goto(loop_successor); body_exit = NULL; } else { // The block for a true condition, the actual predecessor block of the // back edge. body_exit = graph()->CreateBasicBlock(); CHECK_BAILOUT(VisitForControl(stmt->cond(), body_exit, loop_successor)); } if (body_exit != NULL && body_exit->HasPredecessor()) { body_exit->SetJoinId(stmt->BackEdgeId()); } else { body_exit = NULL; } if (loop_successor->HasPredecessor()) { loop_successor->SetJoinId(stmt->ExitId()); } else { loop_successor = NULL; } } HBasicBlock* loop_exit = CreateLoop(stmt, loop_entry, body_exit, loop_successor, break_info.break_block()); set_current_block(loop_exit); } void HOptimizedGraphBuilder::VisitWhileStatement(WhileStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); DCHECK(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); // If the condition is constant true, do not generate a branch. HBasicBlock* loop_successor = NULL; HBasicBlock* body_entry = graph()->CreateBasicBlock(); loop_successor = graph()->CreateBasicBlock(); CHECK_BAILOUT(VisitForControl(stmt->cond(), body_entry, loop_successor)); if (body_entry->HasPredecessor()) { body_entry->SetJoinId(stmt->BodyId()); set_current_block(body_entry); } if (loop_successor->HasPredecessor()) { loop_successor->SetJoinId(stmt->ExitId()); } else { loop_successor = NULL; } BreakAndContinueInfo break_info(stmt, scope()); if (current_block() != NULL) { BreakAndContinueScope push(&break_info, this); CHECK_BAILOUT(VisitLoopBody(stmt, stmt->StackCheckId(), loop_entry)); } HBasicBlock* body_exit = JoinContinue( stmt, stmt->ContinueId(), current_block(), break_info.continue_block()); HBasicBlock* loop_exit = CreateLoop(stmt, loop_entry, body_exit, loop_successor, break_info.break_block()); set_current_block(loop_exit); } void HOptimizedGraphBuilder::VisitForStatement(ForStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (stmt->init() != NULL) { CHECK_ALIVE(Visit(stmt->init())); } DCHECK(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); HBasicBlock* loop_successor = graph()->CreateBasicBlock(); HBasicBlock* body_entry = graph()->CreateBasicBlock(); if (stmt->cond() != NULL) { CHECK_BAILOUT(VisitForControl(stmt->cond(), body_entry, loop_successor)); if (body_entry->HasPredecessor()) { body_entry->SetJoinId(stmt->BodyId()); set_current_block(body_entry); } if (loop_successor->HasPredecessor()) { loop_successor->SetJoinId(stmt->ExitId()); } else { loop_successor = NULL; } } else { // Create dummy control flow so that variable liveness analysis // produces teh correct result. HControlInstruction* branch = New(graph()->GetConstantTrue()); branch->SetSuccessorAt(0, body_entry); branch->SetSuccessorAt(1, loop_successor); FinishCurrentBlock(branch); set_current_block(body_entry); } BreakAndContinueInfo break_info(stmt, scope()); if (current_block() != NULL) { BreakAndContinueScope push(&break_info, this); CHECK_BAILOUT(VisitLoopBody(stmt, stmt->StackCheckId(), loop_entry)); } HBasicBlock* body_exit = JoinContinue( stmt, stmt->ContinueId(), current_block(), break_info.continue_block()); if (stmt->next() != NULL && body_exit != NULL) { set_current_block(body_exit); CHECK_BAILOUT(Visit(stmt->next())); body_exit = current_block(); } HBasicBlock* loop_exit = CreateLoop(stmt, loop_entry, body_exit, loop_successor, break_info.break_block()); set_current_block(loop_exit); } void HOptimizedGraphBuilder::VisitForInStatement(ForInStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!stmt->each()->IsVariableProxy() || !stmt->each()->AsVariableProxy()->var()->IsStackLocal()) { return Bailout(kForInStatementWithNonLocalEachVariable); } Variable* each_var = stmt->each()->AsVariableProxy()->var(); CHECK_ALIVE(VisitForValue(stmt->enumerable())); HValue* enumerable = Top(); // Leave enumerable at the top. IfBuilder if_undefined_or_null(this); if_undefined_or_null.If( enumerable, graph()->GetConstantUndefined()); if_undefined_or_null.Or(); if_undefined_or_null.If( enumerable, graph()->GetConstantNull()); if_undefined_or_null.ThenDeopt(DeoptimizeReason::kUndefinedOrNullInForIn); if_undefined_or_null.End(); BuildForInBody(stmt, each_var, enumerable); } void HOptimizedGraphBuilder::BuildForInBody(ForInStatement* stmt, Variable* each_var, HValue* enumerable) { Handle meta_map = isolate()->factory()->meta_map(); bool fast = stmt->for_in_type() == ForInStatement::FAST_FOR_IN; BuildCheckHeapObject(enumerable); Add(enumerable, HCheckInstanceType::IS_JS_RECEIVER); Add(stmt->ToObjectId()); if (fast) { HForInPrepareMap* map = Add(enumerable); Push(map); Add(stmt->EnumId()); Drop(1); Add(map, meta_map); HForInCacheArray* array = Add( enumerable, map, DescriptorArray::kEnumCacheBridgeCacheIndex); HValue* enum_length = BuildEnumLength(map); HForInCacheArray* index_cache = Add( enumerable, map, DescriptorArray::kEnumCacheBridgeIndicesCacheIndex); array->set_index_cache(index_cache); Push(map); Push(array); Push(enum_length); Add(stmt->PrepareId()); } else { Runtime::FunctionId function_id = Runtime::kForInEnumerate; Add(enumerable); HCallRuntime* array = Add(Runtime::FunctionForId(function_id), 1); Push(array); Add(stmt->EnumId()); Drop(1); IfBuilder if_fast(this); if_fast.If(array, meta_map); if_fast.Then(); { HValue* cache_map = array; HForInCacheArray* cache = Add( enumerable, cache_map, DescriptorArray::kEnumCacheBridgeCacheIndex); HValue* enum_length = BuildEnumLength(cache_map); Push(cache_map); Push(cache); Push(enum_length); Add(stmt->PrepareId(), FIXED_SIMULATE); } if_fast.Else(); { Push(graph()->GetConstant1()); Push(array); Push(AddLoadFixedArrayLength(array)); Add(stmt->PrepareId(), FIXED_SIMULATE); } } Push(graph()->GetConstant0()); HBasicBlock* loop_entry = BuildLoopEntry(stmt); // Reload the values to ensure we have up-to-date values inside of the loop. // This is relevant especially for OSR where the values don't come from the // computation above, but from the OSR entry block. HValue* index = environment()->ExpressionStackAt(0); HValue* limit = environment()->ExpressionStackAt(1); HValue* array = environment()->ExpressionStackAt(2); HValue* type = environment()->ExpressionStackAt(3); enumerable = environment()->ExpressionStackAt(4); // Check that we still have more keys. HCompareNumericAndBranch* compare_index = New(index, limit, Token::LT); compare_index->set_observed_input_representation( Representation::Smi(), Representation::Smi()); HBasicBlock* loop_body = graph()->CreateBasicBlock(); HBasicBlock* loop_successor = graph()->CreateBasicBlock(); compare_index->SetSuccessorAt(0, loop_body); compare_index->SetSuccessorAt(1, loop_successor); FinishCurrentBlock(compare_index); set_current_block(loop_successor); Drop(5); set_current_block(loop_body); // Compute the next enumerated value. HValue* key = Add(array, index, index, nullptr, FAST_ELEMENTS); HBasicBlock* continue_block = nullptr; if (fast) { // Check if expected map still matches that of the enumerable. Add(enumerable, type); Add(stmt->FilterId()); } else { // We need the continue block here to be able to skip over invalidated keys. continue_block = graph()->CreateBasicBlock(); // We cannot use the IfBuilder here, since we need to be able to jump // over the loop body in case of undefined result from %ForInFilter, // and the poor soul that is the IfBuilder get's really confused about // such "advanced control flow requirements". HBasicBlock* if_fast = graph()->CreateBasicBlock(); HBasicBlock* if_slow = graph()->CreateBasicBlock(); HBasicBlock* if_slow_pass = graph()->CreateBasicBlock(); HBasicBlock* if_slow_skip = graph()->CreateBasicBlock(); HBasicBlock* if_join = graph()->CreateBasicBlock(); // Check if expected map still matches that of the enumerable. HValue* enumerable_map = Add(enumerable, nullptr, HObjectAccess::ForMap()); FinishCurrentBlock( New(enumerable_map, type, if_fast, if_slow)); set_current_block(if_fast); { // The enum cache for enumerable is still valid, no need to check key. Push(key); Goto(if_join); } set_current_block(if_slow); { Callable callable = CodeFactory::ForInFilter(isolate()); HValue* values[] = {key, enumerable}; HConstant* stub_value = Add(callable.code()); Push(Add(stub_value, 0, callable.descriptor(), ArrayVector(values))); Add(stmt->FilterId()); FinishCurrentBlock(New( Top(), graph()->GetConstantUndefined(), if_slow_skip, if_slow_pass)); } set_current_block(if_slow_pass); { Goto(if_join); } set_current_block(if_slow_skip); { // The key is no longer valid for enumerable, skip it. Drop(1); Goto(continue_block); } if_join->SetJoinId(stmt->FilterId()); set_current_block(if_join); key = Pop(); } Bind(each_var, key); Add(stmt->AssignmentId()); BreakAndContinueInfo break_info(stmt, scope(), 5); break_info.set_continue_block(continue_block); { BreakAndContinueScope push(&break_info, this); CHECK_BAILOUT(VisitLoopBody(stmt, stmt->StackCheckId(), loop_entry)); } HBasicBlock* body_exit = JoinContinue( stmt, stmt->IncrementId(), current_block(), break_info.continue_block()); if (body_exit != NULL) { set_current_block(body_exit); HValue* current_index = Pop(); HValue* increment = AddUncasted(current_index, graph()->GetConstant1()); increment->ClearFlag(HValue::kCanOverflow); Push(increment); body_exit = current_block(); } HBasicBlock* loop_exit = CreateLoop(stmt, loop_entry, body_exit, loop_successor, break_info.break_block()); set_current_block(loop_exit); } void HOptimizedGraphBuilder::VisitForOfStatement(ForOfStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kForOfStatement); } void HOptimizedGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kTryCatchStatement); } void HOptimizedGraphBuilder::VisitTryFinallyStatement( TryFinallyStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kTryFinallyStatement); } void HOptimizedGraphBuilder::VisitDebuggerStatement(DebuggerStatement* stmt) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kDebuggerStatement); } void HOptimizedGraphBuilder::VisitCaseClause(CaseClause* clause) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitFunctionLiteral(FunctionLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); Handle shared_info = Compiler::GetSharedFunctionInfo( expr, current_info()->script(), top_info()); // We also have a stack overflow if the recursive compilation did. if (HasStackOverflow()) return; // Use the fast case closure allocation code that allocates in new // space for nested functions that don't need pretenuring. HConstant* shared_info_value = Add(shared_info); HInstruction* instr; Handle vector(current_feedback_vector(), isolate()); HValue* vector_value = Add(vector); int index = FeedbackVector::GetIndex(expr->LiteralFeedbackSlot()); HValue* index_value = Add(index); if (!expr->pretenure()) { Callable callable = CodeFactory::FastNewClosure(isolate()); HValue* values[] = {shared_info_value, vector_value, index_value}; HConstant* stub_value = Add(callable.code()); instr = New(stub_value, 0, callable.descriptor(), ArrayVector(values)); } else { Add(shared_info_value); Add(vector_value); Add(index_value); Runtime::FunctionId function_id = expr->pretenure() ? Runtime::kNewClosure_Tenured : Runtime::kNewClosure; instr = New(Runtime::FunctionForId(function_id), 3); } return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitClassLiteral(ClassLiteral* lit) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kClassLiteral); } void HOptimizedGraphBuilder::VisitNativeFunctionLiteral( NativeFunctionLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kNativeFunctionLiteral); } void HOptimizedGraphBuilder::VisitDoExpression(DoExpression* expr) { DoExpressionScope scope(this); DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); CHECK_ALIVE(VisitBlock(expr->block())); Visit(expr->result()); } void HOptimizedGraphBuilder::VisitConditional(Conditional* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); HBasicBlock* cond_true = graph()->CreateBasicBlock(); HBasicBlock* cond_false = graph()->CreateBasicBlock(); CHECK_BAILOUT(VisitForControl(expr->condition(), cond_true, cond_false)); // Visit the true and false subexpressions in the same AST context as the // whole expression. if (cond_true->HasPredecessor()) { cond_true->SetJoinId(expr->ThenId()); set_current_block(cond_true); CHECK_BAILOUT(Visit(expr->then_expression())); cond_true = current_block(); } else { cond_true = NULL; } if (cond_false->HasPredecessor()) { cond_false->SetJoinId(expr->ElseId()); set_current_block(cond_false); CHECK_BAILOUT(Visit(expr->else_expression())); cond_false = current_block(); } else { cond_false = NULL; } if (!ast_context()->IsTest()) { HBasicBlock* join = CreateJoin(cond_true, cond_false, expr->id()); set_current_block(join); if (join != NULL && !ast_context()->IsEffect()) { return ast_context()->ReturnValue(Pop()); } } } bool HOptimizedGraphBuilder::CanInlineGlobalPropertyAccess( Variable* var, LookupIterator* it, PropertyAccessType access_type) { if (var->is_this()) return false; return CanInlineGlobalPropertyAccess(it, access_type); } bool HOptimizedGraphBuilder::CanInlineGlobalPropertyAccess( LookupIterator* it, PropertyAccessType access_type) { if (!current_info()->has_global_object()) { return false; } switch (it->state()) { case LookupIterator::ACCESSOR: case LookupIterator::ACCESS_CHECK: case LookupIterator::INTERCEPTOR: case LookupIterator::INTEGER_INDEXED_EXOTIC: case LookupIterator::NOT_FOUND: return false; case LookupIterator::DATA: if (access_type == STORE && it->IsReadOnly()) return false; if (!it->GetHolder()->IsJSGlobalObject()) return false; return true; case LookupIterator::JSPROXY: case LookupIterator::TRANSITION: UNREACHABLE(); } UNREACHABLE(); return false; } HValue* HOptimizedGraphBuilder::BuildContextChainWalk(Variable* var) { DCHECK(var->IsContextSlot()); HValue* context = environment()->context(); int length = scope()->ContextChainLength(var->scope()); while (length-- > 0) { context = Add( context, nullptr, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); } return context; } void HOptimizedGraphBuilder::InlineGlobalPropertyLoad(LookupIterator* it, BailoutId ast_id) { Handle cell = it->GetPropertyCell(); top_info()->dependencies()->AssumePropertyCell(cell); auto cell_type = it->property_details().cell_type(); if (cell_type == PropertyCellType::kConstant || cell_type == PropertyCellType::kUndefined) { Handle constant_object(cell->value(), isolate()); if (constant_object->IsConsString()) { constant_object = String::Flatten(Handle::cast(constant_object)); } HConstant* constant = New(constant_object); return ast_context()->ReturnInstruction(constant, ast_id); } else { auto access = HObjectAccess::ForPropertyCellValue(); UniqueSet* field_maps = nullptr; if (cell_type == PropertyCellType::kConstantType) { switch (cell->GetConstantType()) { case PropertyCellConstantType::kSmi: access = access.WithRepresentation(Representation::Smi()); break; case PropertyCellConstantType::kStableMap: { // Check that the map really is stable. The heap object could // have mutated without the cell updating state. In that case, // make no promises about the loaded value except that it's a // heap object. access = access.WithRepresentation(Representation::HeapObject()); Handle map(HeapObject::cast(cell->value())->map()); if (map->is_stable()) { field_maps = new (zone()) UniqueSet(Unique::CreateImmovable(map), zone()); } break; } } } HConstant* cell_constant = Add(cell); HLoadNamedField* instr; if (field_maps == nullptr) { instr = New(cell_constant, nullptr, access); } else { instr = New(cell_constant, nullptr, access, field_maps, HType::HeapObject()); } instr->ClearDependsOnFlag(kInobjectFields); instr->SetDependsOnFlag(kGlobalVars); return ast_context()->ReturnInstruction(instr, ast_id); } } void HOptimizedGraphBuilder::VisitVariableProxy(VariableProxy* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); Variable* variable = expr->var(); switch (variable->location()) { case VariableLocation::UNALLOCATED: { if (IsLexicalVariableMode(variable->mode())) { // TODO(rossberg): should this be an DCHECK? return Bailout(kReferenceToGlobalLexicalVariable); } // Handle known global constants like 'undefined' specially to avoid a // load from a global cell for them. Handle constant_value = isolate()->factory()->GlobalConstantFor(variable->name()); if (!constant_value.is_null()) { HConstant* instr = New(constant_value); return ast_context()->ReturnInstruction(instr, expr->id()); } Handle global(current_info()->global_object()); // Lookup in script contexts. { Handle script_contexts( global->native_context()->script_context_table()); ScriptContextTable::LookupResult lookup; if (ScriptContextTable::Lookup(script_contexts, variable->name(), &lookup)) { Handle script_context = ScriptContextTable::GetContext( script_contexts, lookup.context_index); Handle current_value = FixedArray::get(*script_context, lookup.slot_index, isolate()); // If the values is not the hole, it will stay initialized, // so no need to generate a check. if (current_value->IsTheHole(isolate())) { return Bailout(kReferenceToUninitializedVariable); } HInstruction* result = New( Add(script_context), nullptr, HObjectAccess::ForContextSlot(lookup.slot_index)); return ast_context()->ReturnInstruction(result, expr->id()); } } LookupIterator it(global, variable->name(), LookupIterator::OWN); it.TryLookupCachedProperty(); if (CanInlineGlobalPropertyAccess(variable, &it, LOAD)) { InlineGlobalPropertyLoad(&it, expr->id()); return; } else { Handle vector(current_feedback_vector(), isolate()); FeedbackSlot slot = expr->VariableFeedbackSlot(); DCHECK(vector->IsLoadGlobalIC(slot)); HValue* vector_value = Add(vector); HValue* slot_value = Add(vector->GetIndex(slot)); Callable callable = CodeFactory::LoadGlobalICInOptimizedCode( isolate(), ast_context()->typeof_mode()); HValue* stub = Add(callable.code()); HValue* name = Add(variable->name()); HValue* values[] = {name, slot_value, vector_value}; HCallWithDescriptor* instr = New( Code::LOAD_GLOBAL_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return ast_context()->ReturnInstruction(instr, expr->id()); } } case VariableLocation::PARAMETER: case VariableLocation::LOCAL: { HValue* value = LookupAndMakeLive(variable); if (value == graph()->GetConstantHole()) { DCHECK(IsDeclaredVariableMode(variable->mode()) && variable->mode() != VAR); return Bailout(kReferenceToUninitializedVariable); } return ast_context()->ReturnValue(value); } case VariableLocation::CONTEXT: { HValue* context = BuildContextChainWalk(variable); HLoadContextSlot::Mode mode; switch (variable->mode()) { case LET: case CONST: mode = HLoadContextSlot::kCheckDeoptimize; break; default: mode = HLoadContextSlot::kNoCheck; break; } HLoadContextSlot* instr = new(zone()) HLoadContextSlot(context, variable->index(), mode); return ast_context()->ReturnInstruction(instr, expr->id()); } case VariableLocation::LOOKUP: return Bailout(kReferenceToAVariableWhichRequiresDynamicLookup); case VariableLocation::MODULE: UNREACHABLE(); } } void HOptimizedGraphBuilder::VisitLiteral(Literal* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); HConstant* instr = New(expr->value()); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitRegExpLiteral(RegExpLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); Callable callable = CodeFactory::FastCloneRegExp(isolate()); int index = FeedbackVector::GetIndex(expr->literal_slot()); HValue* values[] = {AddThisFunction(), Add(index), Add(expr->pattern()), Add(expr->flags())}; HConstant* stub_value = Add(callable.code()); HInstruction* instr = New( stub_value, 0, callable.descriptor(), ArrayVector(values)); return ast_context()->ReturnInstruction(instr, expr->id()); } static bool CanInlinePropertyAccess(Handle map) { if (map->instance_type() == HEAP_NUMBER_TYPE) return true; if (map->instance_type() < FIRST_NONSTRING_TYPE) return true; return map->IsJSObjectMap() && !map->is_dictionary_map() && !map->has_named_interceptor() && // TODO(verwaest): Whitelist contexts to which we have access. !map->is_access_check_needed(); } // Determines whether the given array or object literal boilerplate satisfies // all limits to be considered for fast deep-copying and computes the total // size of all objects that are part of the graph. static bool IsFastLiteral(Handle boilerplate, int max_depth, int* max_properties) { if (boilerplate->map()->is_deprecated() && !JSObject::TryMigrateInstance(boilerplate)) { return false; } DCHECK(max_depth >= 0 && *max_properties >= 0); if (max_depth == 0) return false; Isolate* isolate = boilerplate->GetIsolate(); Handle elements(boilerplate->elements()); if (elements->length() > 0 && elements->map() != isolate->heap()->fixed_cow_array_map()) { if (boilerplate->HasFastSmiOrObjectElements()) { Handle fast_elements = Handle::cast(elements); int length = elements->length(); for (int i = 0; i < length; i++) { if ((*max_properties)-- == 0) return false; Handle value(fast_elements->get(i), isolate); if (value->IsJSObject()) { Handle value_object = Handle::cast(value); if (!IsFastLiteral(value_object, max_depth - 1, max_properties)) { return false; } } } } else if (boilerplate->HasFastDoubleElements()) { if (elements->Size() > kMaxRegularHeapObjectSize) return false; } else { return false; } } Handle properties(boilerplate->properties()); if (properties->length() > 0) { return false; } else { Handle descriptors( boilerplate->map()->instance_descriptors()); int limit = boilerplate->map()->NumberOfOwnDescriptors(); for (int i = 0; i < limit; i++) { PropertyDetails details = descriptors->GetDetails(i); if (details.location() != kField) continue; DCHECK_EQ(kData, details.kind()); if ((*max_properties)-- == 0) return false; FieldIndex field_index = FieldIndex::ForDescriptor(boilerplate->map(), i); if (boilerplate->IsUnboxedDoubleField(field_index)) continue; Handle value(boilerplate->RawFastPropertyAt(field_index), isolate); if (value->IsJSObject()) { Handle value_object = Handle::cast(value); if (!IsFastLiteral(value_object, max_depth - 1, max_properties)) { return false; } } } } return true; } void HOptimizedGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); Handle closure = function_state()->compilation_info()->closure(); HInstruction* literal; // Check whether to use fast or slow deep-copying for boilerplate. int max_properties = kMaxFastLiteralProperties; Handle literals_cell( closure->feedback_vector()->Get(expr->literal_slot()), isolate()); Handle site; Handle boilerplate; if (!literals_cell->IsUndefined(isolate())) { // Retrieve the boilerplate site = Handle::cast(literals_cell); boilerplate = Handle(JSObject::cast(site->transition_info()), isolate()); } if (!boilerplate.is_null() && IsFastLiteral(boilerplate, kMaxFastLiteralDepth, &max_properties)) { AllocationSiteUsageContext site_context(isolate(), site, false); site_context.EnterNewScope(); literal = BuildFastLiteral(boilerplate, &site_context); site_context.ExitScope(site, boilerplate); } else { NoObservableSideEffectsScope no_effects(this); Handle constant_properties = expr->GetOrBuildConstantProperties(isolate()); int literal_index = FeedbackVector::GetIndex(expr->literal_slot()); int flags = expr->ComputeFlags(true); Add(AddThisFunction(), Add(literal_index), Add(constant_properties), Add(flags)); Runtime::FunctionId function_id = Runtime::kCreateObjectLiteral; literal = Add(Runtime::FunctionForId(function_id), 4); } // The object is expected in the bailout environment during computation // of the property values and is the value of the entire expression. Push(literal); for (int i = 0; i < expr->properties()->length(); i++) { ObjectLiteral::Property* property = expr->properties()->at(i); if (property->is_computed_name()) return Bailout(kComputedPropertyName); if (property->IsCompileTimeValue()) continue; Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); switch (property->kind()) { case ObjectLiteral::Property::MATERIALIZED_LITERAL: DCHECK(!CompileTimeValue::IsCompileTimeValue(value)); // Fall through. case ObjectLiteral::Property::COMPUTED: // It is safe to use [[Put]] here because the boilerplate already // contains computed properties with an uninitialized value. if (key->IsStringLiteral()) { DCHECK(key->IsPropertyName()); if (property->emit_store()) { CHECK_ALIVE(VisitForValue(value)); HValue* value = Pop(); Handle map = property->GetReceiverType(); Handle name = key->AsPropertyName(); HValue* store; FeedbackSlot slot = property->GetSlot(); if (map.is_null()) { // If we don't know the monomorphic type, do a generic store. CHECK_ALIVE(store = BuildNamedGeneric(STORE, NULL, slot, literal, name, value)); } else { PropertyAccessInfo info(this, STORE, map, name); if (info.CanAccessMonomorphic()) { HValue* checked_literal = Add(literal, map); DCHECK(!info.IsAccessorConstant()); info.MarkAsInitializingStore(); store = BuildMonomorphicAccess( &info, literal, checked_literal, value, BailoutId::None(), BailoutId::None()); DCHECK_NOT_NULL(store); } else { CHECK_ALIVE(store = BuildNamedGeneric(STORE, NULL, slot, literal, name, value)); } } if (store->IsInstruction()) { AddInstruction(HInstruction::cast(store)); } DCHECK(store->HasObservableSideEffects()); Add(key->id(), REMOVABLE_SIMULATE); // Add [[HomeObject]] to function literals. if (FunctionLiteral::NeedsHomeObject(property->value())) { Handle sym = isolate()->factory()->home_object_symbol(); HInstruction* store_home = BuildNamedGeneric( STORE, NULL, property->GetSlot(1), value, sym, literal); AddInstruction(store_home); DCHECK(store_home->HasObservableSideEffects()); Add(property->value()->id(), REMOVABLE_SIMULATE); } } else { CHECK_ALIVE(VisitForEffect(value)); } break; } // Fall through. case ObjectLiteral::Property::PROTOTYPE: case ObjectLiteral::Property::SETTER: case ObjectLiteral::Property::GETTER: return Bailout(kObjectLiteralWithComplexProperty); default: UNREACHABLE(); } } return ast_context()->ReturnValue(Pop()); } void HOptimizedGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); ZoneList* subexprs = expr->values(); int length = subexprs->length(); HInstruction* literal; Handle site; Handle vector(environment()->closure()->feedback_vector(), isolate()); Handle literals_cell(vector->Get(expr->literal_slot()), isolate()); Handle boilerplate_object; if (!literals_cell->IsUndefined(isolate())) { DCHECK(literals_cell->IsAllocationSite()); site = Handle::cast(literals_cell); boilerplate_object = Handle( JSObject::cast(site->transition_info()), isolate()); } // Check whether to use fast or slow deep-copying for boilerplate. int max_properties = kMaxFastLiteralProperties; if (!boilerplate_object.is_null() && IsFastLiteral(boilerplate_object, kMaxFastLiteralDepth, &max_properties)) { DCHECK(site->SitePointsToLiteral()); AllocationSiteUsageContext site_context(isolate(), site, false); site_context.EnterNewScope(); literal = BuildFastLiteral(boilerplate_object, &site_context); site_context.ExitScope(site, boilerplate_object); } else { NoObservableSideEffectsScope no_effects(this); Handle constants = expr->GetOrBuildConstantElements(isolate()); int literal_index = FeedbackVector::GetIndex(expr->literal_slot()); int flags = expr->ComputeFlags(true); Add(AddThisFunction(), Add(literal_index), Add(constants), Add(flags)); Runtime::FunctionId function_id = Runtime::kCreateArrayLiteral; literal = Add(Runtime::FunctionForId(function_id), 4); // Register to deopt if the boilerplate ElementsKind changes. if (!site.is_null()) { top_info()->dependencies()->AssumeTransitionStable(site); } } // The array is expected in the bailout environment during computation // of the property values and is the value of the entire expression. Push(literal); HInstruction* elements = NULL; for (int i = 0; i < length; i++) { Expression* subexpr = subexprs->at(i); DCHECK(!subexpr->IsSpread()); // If the subexpression is a literal or a simple materialized literal it // is already set in the cloned array. if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue; CHECK_ALIVE(VisitForValue(subexpr)); HValue* value = Pop(); if (!Smi::IsValid(i)) return Bailout(kNonSmiKeyInArrayLiteral); elements = AddLoadElements(literal); HValue* key = Add(i); if (!boilerplate_object.is_null()) { ElementsKind boilerplate_elements_kind = boilerplate_object->GetElementsKind(); switch (boilerplate_elements_kind) { case FAST_SMI_ELEMENTS: case FAST_HOLEY_SMI_ELEMENTS: case FAST_ELEMENTS: case FAST_HOLEY_ELEMENTS: case FAST_DOUBLE_ELEMENTS: case FAST_HOLEY_DOUBLE_ELEMENTS: { Add(elements, key, value, nullptr, boilerplate_elements_kind); break; } default: UNREACHABLE(); break; } } else { HInstruction* instr = BuildKeyedGeneric( STORE, expr, expr->LiteralFeedbackSlot(), literal, key, value); AddInstruction(instr); } Add(expr->GetIdForElement(i)); } return ast_context()->ReturnValue(Pop()); } HCheckMaps* HOptimizedGraphBuilder::AddCheckMap(HValue* object, Handle map) { BuildCheckHeapObject(object); return Add(object, map); } HInstruction* HOptimizedGraphBuilder::BuildLoadNamedField( PropertyAccessInfo* info, HValue* checked_object) { // Check if this is a load of an immutable or constant property. if (checked_object->ActualValue()->IsConstant()) { Handle object( HConstant::cast(checked_object->ActualValue())->handle(isolate())); if (object->IsJSObject()) { LookupIterator it(object, info->name(), LookupIterator::OWN_SKIP_INTERCEPTOR); if (it.IsFound()) { bool is_reaonly_non_configurable = it.IsReadOnly() && !it.IsConfigurable(); if (is_reaonly_non_configurable || (FLAG_track_constant_fields && info->IsDataConstantField())) { Handle value = JSReceiver::GetDataProperty(&it); if (!is_reaonly_non_configurable) { DCHECK(!it.is_dictionary_holder()); // Add dependency on the map that introduced the field. Handle field_owner_map = it.GetFieldOwnerMap(); top_info()->dependencies()->AssumeFieldOwner(field_owner_map); } return New(value); } } } } HObjectAccess access = info->access(); if (access.representation().IsDouble() && (!FLAG_unbox_double_fields || !access.IsInobject())) { // Load the heap number. checked_object = Add( checked_object, nullptr, access.WithRepresentation(Representation::Tagged())); // Load the double value from it. access = HObjectAccess::ForHeapNumberValue(); } SmallMapList* map_list = info->field_maps(); if (map_list->length() == 0) { return New(checked_object, checked_object, access); } UniqueSet* maps = new(zone()) UniqueSet(map_list->length(), zone()); for (int i = 0; i < map_list->length(); ++i) { maps->Add(Unique::CreateImmovable(map_list->at(i)), zone()); } return New( checked_object, checked_object, access, maps, info->field_type()); } HValue* HOptimizedGraphBuilder::BuildStoreNamedField(PropertyAccessInfo* info, HValue* checked_object, HValue* value) { bool transition_to_field = info->IsTransition(); // TODO(verwaest): Move this logic into PropertyAccessInfo. HObjectAccess field_access = info->access(); bool store_to_constant_field = FLAG_track_constant_fields && info->StoreMode() != INITIALIZING_STORE && info->IsDataConstantField(); HStoreNamedField *instr; if (field_access.representation().IsDouble() && (!FLAG_unbox_double_fields || !field_access.IsInobject())) { HObjectAccess heap_number_access = field_access.WithRepresentation(Representation::Tagged()); if (transition_to_field) { // The store requires a mutable HeapNumber to be allocated. NoObservableSideEffectsScope no_side_effects(this); HInstruction* heap_number_size = Add(HeapNumber::kSize); // TODO(hpayer): Allocation site pretenuring support. HInstruction* heap_number = Add(heap_number_size, HType::HeapObject(), NOT_TENURED, MUTABLE_HEAP_NUMBER_TYPE, graph()->GetConstant0()); AddStoreMapConstant( heap_number, isolate()->factory()->mutable_heap_number_map()); Add(heap_number, HObjectAccess::ForHeapNumberValue(), value); instr = New(checked_object->ActualValue(), heap_number_access, heap_number); } else { // Already holds a HeapNumber; load the box and write its value field. HInstruction* heap_number = Add(checked_object, nullptr, heap_number_access); if (store_to_constant_field) { // If the field is constant check that the value we are going to store // matches current value. HInstruction* current_value = Add( heap_number, nullptr, HObjectAccess::ForHeapNumberValue()); IfBuilder value_checker(this); value_checker.IfNot(current_value, value, Token::EQ); value_checker.ThenDeopt(DeoptimizeReason::kValueMismatch); value_checker.End(); return nullptr; } else { instr = New(heap_number, HObjectAccess::ForHeapNumberValue(), value, STORE_TO_INITIALIZED_ENTRY); } } } else { if (store_to_constant_field) { // If the field is constant check that the value we are going to store // matches current value. HInstruction* current_value = Add( checked_object->ActualValue(), checked_object, field_access); IfBuilder value_checker(this); if (field_access.representation().IsDouble()) { value_checker.IfNot(current_value, value, Token::EQ); } else { value_checker.IfNot(current_value, value); } value_checker.ThenDeopt(DeoptimizeReason::kValueMismatch); value_checker.End(); return nullptr; } else { if (field_access.representation().IsHeapObject()) { BuildCheckHeapObject(value); } if (!info->field_maps()->is_empty()) { DCHECK(field_access.representation().IsHeapObject()); value = Add(value, info->field_maps()); } // This is a normal store. instr = New(checked_object->ActualValue(), field_access, value, info->StoreMode()); } } if (transition_to_field) { Handle transition(info->transition()); DCHECK(!transition->is_deprecated()); instr->SetTransition(Add(transition)); } return instr; } Handle HOptimizedGraphBuilder::PropertyAccessInfo::GetFieldTypeFromMap( Handle map) const { DCHECK(IsFound()); DCHECK(number_ < map->NumberOfOwnDescriptors()); return handle(map->instance_descriptors()->GetFieldType(number_), isolate()); } bool HOptimizedGraphBuilder::PropertyAccessInfo::IsCompatible( PropertyAccessInfo* info) { if (!CanInlinePropertyAccess(map_)) return false; // Currently only handle AstType::Number as a polymorphic case. // TODO(verwaest): Support monomorphic handling of numbers with a HCheckNumber // instruction. if (IsNumberType()) return false; // Values are only compatible for monomorphic load if they all behave the same // regarding value wrappers. if (IsValueWrapped() != info->IsValueWrapped()) return false; if (!LookupDescriptor()) return false; if (!IsFound()) { return (!info->IsFound() || info->has_holder()) && map()->prototype() == info->map()->prototype(); } // Mismatch if the other access info found the property in the prototype // chain. if (info->has_holder()) return false; if (IsAccessorConstant()) { return accessor_.is_identical_to(info->accessor_) && api_holder_.is_identical_to(info->api_holder_); } if (IsDataConstant()) { return constant_.is_identical_to(info->constant_); } DCHECK(IsData()); if (!info->IsData()) return false; Representation r = access_.representation(); if (IsLoad()) { if (!info->access_.representation().IsCompatibleForLoad(r)) return false; } else { if (!info->access_.representation().IsCompatibleForStore(r)) return false; } if (info->access_.offset() != access_.offset()) return false; if (info->access_.IsInobject() != access_.IsInobject()) return false; if (IsLoad()) { if (field_maps_.is_empty()) { info->field_maps_.Clear(); } else if (!info->field_maps_.is_empty()) { for (int i = 0; i < field_maps_.length(); ++i) { info->field_maps_.AddMapIfMissing(field_maps_.at(i), info->zone()); } info->field_maps_.Sort(); } } else { // We can only merge stores that agree on their field maps. The comparison // below is safe, since we keep the field maps sorted. if (field_maps_.length() != info->field_maps_.length()) return false; for (int i = 0; i < field_maps_.length(); ++i) { if (!field_maps_.at(i).is_identical_to(info->field_maps_.at(i))) { return false; } } } info->GeneralizeRepresentation(r); info->field_type_ = info->field_type_.Combine(field_type_); return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::LookupDescriptor() { if (!map_->IsJSObjectMap()) return true; LookupDescriptor(*map_, *name_); return LoadResult(map_); } bool HOptimizedGraphBuilder::PropertyAccessInfo::LoadResult(Handle map) { if (!IsLoad() && IsProperty() && IsReadOnly()) { return false; } if (IsData()) { // Construct the object field access. int index = GetLocalFieldIndexFromMap(map); access_ = HObjectAccess::ForField(map, index, representation(), name_); // Load field map for heap objects. return LoadFieldMaps(map); } else if (IsAccessorConstant()) { Handle accessors = GetAccessorsFromMap(map); if (!accessors->IsAccessorPair()) return false; Object* raw_accessor = IsLoad() ? Handle::cast(accessors)->getter() : Handle::cast(accessors)->setter(); if (!raw_accessor->IsJSFunction() && !raw_accessor->IsFunctionTemplateInfo()) return false; Handle accessor = handle(HeapObject::cast(raw_accessor)); CallOptimization call_optimization(accessor); if (call_optimization.is_simple_api_call()) { CallOptimization::HolderLookup holder_lookup; api_holder_ = call_optimization.LookupHolderOfExpectedType(map_, &holder_lookup); } accessor_ = accessor; } else if (IsDataConstant()) { constant_ = GetConstantFromMap(map); } return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::LoadFieldMaps( Handle map) { // Clear any previously collected field maps/type. field_maps_.Clear(); field_type_ = HType::Tagged(); // Figure out the field type from the accessor map. Handle field_type = GetFieldTypeFromMap(map); // Collect the (stable) maps from the field type. if (field_type->IsClass()) { DCHECK(access_.representation().IsHeapObject()); Handle field_map = field_type->AsClass(); if (field_map->is_stable()) { field_maps_.Add(field_map, zone()); } } if (field_maps_.is_empty()) { // Store is not safe if the field map was cleared. return IsLoad() || !field_type->IsNone(); } // Determine field HType from field type. field_type_ = HType::FromFieldType(field_type, zone()); DCHECK(field_type_.IsHeapObject()); // Add dependency on the map that introduced the field. top_info()->dependencies()->AssumeFieldOwner(GetFieldOwnerFromMap(map)); return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::LookupInPrototypes() { Handle map = this->map(); if (name_->IsPrivate()) { NotFound(); return !map->has_hidden_prototype(); } while (map->prototype()->IsJSObject()) { holder_ = handle(JSObject::cast(map->prototype())); if (holder_->map()->is_deprecated()) { JSObject::TryMigrateInstance(holder_); } map = Handle(holder_->map()); if (!CanInlinePropertyAccess(map)) { NotFound(); return false; } LookupDescriptor(*map, *name_); if (IsFound()) return LoadResult(map); } NotFound(); return !map->prototype()->IsJSReceiver(); } bool HOptimizedGraphBuilder::PropertyAccessInfo::IsIntegerIndexedExotic() { InstanceType instance_type = map_->instance_type(); return instance_type == JS_TYPED_ARRAY_TYPE && name_->IsString() && IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name_)); } bool HOptimizedGraphBuilder::PropertyAccessInfo::CanAccessMonomorphic() { if (!CanInlinePropertyAccess(map_)) return false; if (IsJSObjectFieldAccessor()) return IsLoad(); if (map_->IsJSFunctionMap() && map_->is_constructor() && !map_->has_non_instance_prototype() && name_.is_identical_to(isolate()->factory()->prototype_string())) { return IsLoad(); } if (!LookupDescriptor()) return false; if (IsFound()) return IsLoad() || !IsReadOnly(); if (IsIntegerIndexedExotic()) return false; if (!LookupInPrototypes()) return false; if (IsLoad()) return true; if (IsAccessorConstant()) return true; LookupTransition(*map_, *name_, NONE); if (IsTransitionToData() && map_->unused_property_fields() > 0) { // Construct the object field access. int descriptor = transition()->LastAdded(); int index = transition()->instance_descriptors()->GetFieldIndex(descriptor) - map_->GetInObjectProperties(); PropertyDetails details = transition()->instance_descriptors()->GetDetails(descriptor); Representation representation = details.representation(); access_ = HObjectAccess::ForField(map_, index, representation, name_); // Load field map for heap objects. return LoadFieldMaps(transition()); } return false; } bool HOptimizedGraphBuilder::PropertyAccessInfo::CanAccessAsMonomorphic( SmallMapList* maps) { DCHECK(map_.is_identical_to(maps->first())); if (!CanAccessMonomorphic()) return false; STATIC_ASSERT(kMaxLoadPolymorphism == kMaxStorePolymorphism); if (maps->length() > kMaxLoadPolymorphism) return false; HObjectAccess access = HObjectAccess::ForMap(); // bogus default if (GetJSObjectFieldAccess(&access)) { for (int i = 1; i < maps->length(); ++i) { PropertyAccessInfo test_info(builder_, access_type_, maps->at(i), name_); HObjectAccess test_access = HObjectAccess::ForMap(); // bogus default if (!test_info.GetJSObjectFieldAccess(&test_access)) return false; if (!access.Equals(test_access)) return false; } return true; } // Currently only handle numbers as a polymorphic case. // TODO(verwaest): Support monomorphic handling of numbers with a HCheckNumber // instruction. if (IsNumberType()) return false; // Multiple maps cannot transition to the same target map. DCHECK(!IsLoad() || !IsTransition()); if (IsTransition() && maps->length() > 1) return false; for (int i = 1; i < maps->length(); ++i) { PropertyAccessInfo test_info(builder_, access_type_, maps->at(i), name_); if (!test_info.IsCompatible(this)) return false; } return true; } Handle HOptimizedGraphBuilder::PropertyAccessInfo::map() { Handle ctor; if (Map::GetConstructorFunction( map_, handle(current_info()->closure()->context()->native_context())) .ToHandle(&ctor)) { return handle(ctor->initial_map()); } return map_; } static bool NeedsWrapping(Handle map, Handle target) { return !map->IsJSObjectMap() && is_sloppy(target->shared()->language_mode()) && !target->shared()->native(); } bool HOptimizedGraphBuilder::PropertyAccessInfo::NeedsWrappingFor( Handle target) const { return NeedsWrapping(map_, target); } HValue* HOptimizedGraphBuilder::BuildMonomorphicAccess( PropertyAccessInfo* info, HValue* object, HValue* checked_object, HValue* value, BailoutId ast_id, BailoutId return_id, bool can_inline_accessor) { HObjectAccess access = HObjectAccess::ForMap(); // bogus default if (info->GetJSObjectFieldAccess(&access)) { DCHECK(info->IsLoad()); return New(object, checked_object, access); } if (info->name().is_identical_to(isolate()->factory()->prototype_string()) && info->map()->IsJSFunctionMap() && info->map()->is_constructor()) { DCHECK(!info->map()->has_non_instance_prototype()); return New(checked_object); } HValue* checked_holder = checked_object; if (info->has_holder()) { Handle prototype(JSObject::cast(info->map()->prototype())); checked_holder = BuildCheckPrototypeMaps(prototype, info->holder()); } if (!info->IsFound()) { DCHECK(info->IsLoad()); return graph()->GetConstantUndefined(); } if (info->IsData()) { if (info->IsLoad()) { return BuildLoadNamedField(info, checked_holder); } else { return BuildStoreNamedField(info, checked_object, value); } } if (info->IsTransition()) { DCHECK(!info->IsLoad()); return BuildStoreNamedField(info, checked_object, value); } if (info->IsAccessorConstant()) { MaybeHandle maybe_name = FunctionTemplateInfo::TryGetCachedPropertyName(isolate(), info->accessor()); if (!maybe_name.is_null()) { Handle name = maybe_name.ToHandleChecked(); PropertyAccessInfo cache_info(this, LOAD, info->map(), name); // Load new target. if (cache_info.CanAccessMonomorphic()) { return BuildLoadNamedField(&cache_info, checked_object); } } Push(checked_object); int argument_count = 1; if (!info->IsLoad()) { argument_count = 2; Push(value); } if (info->accessor()->IsJSFunction() && info->NeedsWrappingFor(Handle::cast(info->accessor()))) { HValue* function = Add(info->accessor()); PushArgumentsFromEnvironment(argument_count); return NewCallFunction(function, argument_count, TailCallMode::kDisallow, ConvertReceiverMode::kNotNullOrUndefined, TailCallMode::kDisallow); } else if (FLAG_inline_accessors && can_inline_accessor) { bool success = info->IsLoad() ? TryInlineGetter(info->accessor(), info->map(), ast_id, return_id) : TryInlineSetter( info->accessor(), info->map(), ast_id, return_id, value); if (success || HasStackOverflow()) return NULL; } PushArgumentsFromEnvironment(argument_count); if (!info->accessor()->IsJSFunction()) { Bailout(kInliningBailedOut); return nullptr; } return NewCallConstantFunction(Handle::cast(info->accessor()), argument_count, TailCallMode::kDisallow, TailCallMode::kDisallow); } DCHECK(info->IsDataConstant()); if (info->IsLoad()) { return New(info->constant()); } else { return New(value, Handle::cast(info->constant())); } } void HOptimizedGraphBuilder::HandlePolymorphicNamedFieldAccess( PropertyAccessType access_type, Expression* expr, FeedbackSlot slot, BailoutId ast_id, BailoutId return_id, HValue* object, HValue* value, SmallMapList* maps, Handle name) { // Something did not match; must use a polymorphic load. int count = 0; HBasicBlock* join = NULL; HBasicBlock* number_block = NULL; bool handled_string = false; bool handle_smi = false; STATIC_ASSERT(kMaxLoadPolymorphism == kMaxStorePolymorphism); int i; for (i = 0; i < maps->length() && count < kMaxLoadPolymorphism; ++i) { PropertyAccessInfo info(this, access_type, maps->at(i), name); if (info.IsStringType()) { if (handled_string) continue; handled_string = true; } if (info.CanAccessMonomorphic()) { count++; if (info.IsNumberType()) { handle_smi = true; break; } } } if (i < maps->length()) { count = -1; maps->Clear(); } else { count = 0; } HControlInstruction* smi_check = NULL; handled_string = false; for (i = 0; i < maps->length() && count < kMaxLoadPolymorphism; ++i) { PropertyAccessInfo info(this, access_type, maps->at(i), name); if (info.IsStringType()) { if (handled_string) continue; handled_string = true; } if (!info.CanAccessMonomorphic()) continue; if (count == 0) { join = graph()->CreateBasicBlock(); if (handle_smi) { HBasicBlock* empty_smi_block = graph()->CreateBasicBlock(); HBasicBlock* not_smi_block = graph()->CreateBasicBlock(); number_block = graph()->CreateBasicBlock(); smi_check = New( object, empty_smi_block, not_smi_block); FinishCurrentBlock(smi_check); GotoNoSimulate(empty_smi_block, number_block); set_current_block(not_smi_block); } else { BuildCheckHeapObject(object); } } ++count; HBasicBlock* if_true = graph()->CreateBasicBlock(); HBasicBlock* if_false = graph()->CreateBasicBlock(); HUnaryControlInstruction* compare; HValue* dependency; if (info.IsNumberType()) { Handle heap_number_map = isolate()->factory()->heap_number_map(); compare = New(object, heap_number_map, if_true, if_false); dependency = smi_check; } else if (info.IsStringType()) { compare = New(object, if_true, if_false); dependency = compare; } else { compare = New(object, info.map(), if_true, if_false); dependency = compare; } FinishCurrentBlock(compare); if (info.IsNumberType()) { GotoNoSimulate(if_true, number_block); if_true = number_block; } set_current_block(if_true); HValue* access = BuildMonomorphicAccess(&info, object, dependency, value, ast_id, return_id, FLAG_polymorphic_inlining); HValue* result = NULL; switch (access_type) { case LOAD: result = access; break; case STORE: result = value; break; } if (access == NULL) { if (HasStackOverflow()) return; } else { if (access->IsInstruction()) { HInstruction* instr = HInstruction::cast(access); if (!instr->IsLinked()) AddInstruction(instr); } if (!ast_context()->IsEffect()) Push(result); } if (current_block() != NULL) Goto(join); set_current_block(if_false); } // Finish up. Unconditionally deoptimize if we've handled all the maps we // know about and do not want to handle ones we've never seen. Otherwise // use a generic IC. if (count == maps->length() && FLAG_deoptimize_uncommon_cases) { FinishExitWithHardDeoptimization( DeoptimizeReason::kUnknownMapInPolymorphicAccess); } else { HInstruction* instr = BuildNamedGeneric(access_type, expr, slot, object, name, value); AddInstruction(instr); if (!ast_context()->IsEffect()) Push(access_type == LOAD ? instr : value); if (join != NULL) { Goto(join); } else { Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop()); return; } } DCHECK(join != NULL); if (join->HasPredecessor()) { join->SetJoinId(ast_id); set_current_block(join); if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop()); } else { set_current_block(NULL); } } static bool ComputeReceiverTypes(Expression* expr, HValue* receiver, SmallMapList** t, HOptimizedGraphBuilder* builder) { Zone* zone = builder->zone(); SmallMapList* maps = expr->GetReceiverTypes(); *t = maps; bool monomorphic = expr->IsMonomorphic(); if (maps != nullptr && receiver->HasMonomorphicJSObjectType()) { if (maps->length() > 0) { Map* root_map = receiver->GetMonomorphicJSObjectMap()->FindRootMap(); maps->FilterForPossibleTransitions(root_map); monomorphic = maps->length() == 1; } else { // No type feedback, see if we can infer the type. This is safely // possible if the receiver had a known map at some point, and no // map-changing stores have happened to it since. Handle candidate_map = receiver->GetMonomorphicJSObjectMap(); for (HInstruction* current = builder->current_block()->last(); current != nullptr; current = current->previous()) { if (current->IsBlockEntry()) break; if (current->CheckChangesFlag(kMaps)) { // Only allow map changes that store the candidate map. We don't // need to care which object the map is being written into. if (!current->IsStoreNamedField()) break; HStoreNamedField* map_change = HStoreNamedField::cast(current); if (!map_change->value()->IsConstant()) break; HConstant* map_constant = HConstant::cast(map_change->value()); if (!map_constant->representation().IsTagged()) break; Handle map = map_constant->handle(builder->isolate()); if (!map.is_identical_to(candidate_map)) break; } if (current == receiver) { // We made it all the way back to the receiver without encountering // a map change! So we can assume that the receiver still has the // candidate_map we know about. maps->Add(candidate_map, zone); monomorphic = true; break; } } } } return monomorphic && CanInlinePropertyAccess(maps->first()); } static bool AreStringTypes(SmallMapList* maps) { for (int i = 0; i < maps->length(); i++) { if (maps->at(i)->instance_type() >= FIRST_NONSTRING_TYPE) return false; } return true; } void HOptimizedGraphBuilder::BuildStore(Expression* expr, Property* prop, FeedbackSlot slot, BailoutId ast_id, BailoutId return_id, bool is_uninitialized) { if (!prop->key()->IsPropertyName()) { // Keyed store. HValue* value = Pop(); HValue* key = Pop(); HValue* object = Pop(); bool has_side_effects = false; HValue* result = HandleKeyedElementAccess(object, key, value, expr, slot, ast_id, return_id, STORE, &has_side_effects); if (has_side_effects) { if (!ast_context()->IsEffect()) Push(value); Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); } if (result == NULL) return; return ast_context()->ReturnValue(value); } // Named store. HValue* value = Pop(); HValue* object = Pop(); Literal* key = prop->key()->AsLiteral(); Handle name = Handle::cast(key->value()); DCHECK(!name.is_null()); HValue* access = BuildNamedAccess(STORE, ast_id, return_id, expr, slot, object, name, value, is_uninitialized); if (access == NULL) return; if (!ast_context()->IsEffect()) Push(value); if (access->IsInstruction()) AddInstruction(HInstruction::cast(access)); if (access->HasObservableSideEffects()) { Add(ast_id, REMOVABLE_SIMULATE); } if (!ast_context()->IsEffect()) Drop(1); return ast_context()->ReturnValue(value); } void HOptimizedGraphBuilder::HandlePropertyAssignment(Assignment* expr) { Property* prop = expr->target()->AsProperty(); DCHECK(prop != NULL); CHECK_ALIVE(VisitForValue(prop->obj())); if (!prop->key()->IsPropertyName()) { CHECK_ALIVE(VisitForValue(prop->key())); } CHECK_ALIVE(VisitForValue(expr->value())); BuildStore(expr, prop, expr->AssignmentSlot(), expr->id(), expr->AssignmentId(), expr->IsUninitialized()); } HInstruction* HOptimizedGraphBuilder::InlineGlobalPropertyStore( LookupIterator* it, HValue* value, BailoutId ast_id) { Handle cell = it->GetPropertyCell(); top_info()->dependencies()->AssumePropertyCell(cell); auto cell_type = it->property_details().cell_type(); if (cell_type == PropertyCellType::kConstant || cell_type == PropertyCellType::kUndefined) { Handle constant(cell->value(), isolate()); if (value->IsConstant()) { HConstant* c_value = HConstant::cast(value); if (!constant.is_identical_to(c_value->handle(isolate()))) { Add(DeoptimizeReason::kConstantGlobalVariableAssignment, Deoptimizer::EAGER); } } else { HValue* c_constant = Add(constant); IfBuilder builder(this); if (constant->IsNumber()) { builder.If(value, c_constant, Token::EQ); } else { builder.If(value, c_constant); } builder.Then(); builder.Else(); Add(DeoptimizeReason::kConstantGlobalVariableAssignment, Deoptimizer::EAGER); builder.End(); } } HConstant* cell_constant = Add(cell); auto access = HObjectAccess::ForPropertyCellValue(); if (cell_type == PropertyCellType::kConstantType) { switch (cell->GetConstantType()) { case PropertyCellConstantType::kSmi: access = access.WithRepresentation(Representation::Smi()); break; case PropertyCellConstantType::kStableMap: { // First check that the previous value of the {cell} still has the // map that we are about to check the new {value} for. If not, then // the stable map assumption was invalidated and we cannot continue // with the optimized code. Handle cell_value(HeapObject::cast(cell->value())); Handle cell_value_map(cell_value->map()); if (!cell_value_map->is_stable()) { Bailout(kUnstableConstantTypeHeapObject); return nullptr; } top_info()->dependencies()->AssumeMapStable(cell_value_map); // Now check that the new {value} is a HeapObject with the same map Add(value); value = Add(value, cell_value_map); access = access.WithRepresentation(Representation::HeapObject()); break; } } } HInstruction* instr = New(cell_constant, access, value); instr->ClearChangesFlag(kInobjectFields); instr->SetChangesFlag(kGlobalVars); return instr; } // Because not every expression has a position and there is not common // superclass of Assignment and CountOperation, we cannot just pass the // owning expression instead of position and ast_id separately. void HOptimizedGraphBuilder::HandleGlobalVariableAssignment(Variable* var, HValue* value, FeedbackSlot slot, BailoutId ast_id) { Handle global(current_info()->global_object()); // Lookup in script contexts. { Handle script_contexts( global->native_context()->script_context_table()); ScriptContextTable::LookupResult lookup; if (ScriptContextTable::Lookup(script_contexts, var->name(), &lookup)) { if (lookup.mode == CONST) { return Bailout(kNonInitializerAssignmentToConst); } Handle script_context = ScriptContextTable::GetContext(script_contexts, lookup.context_index); Handle current_value = FixedArray::get(*script_context, lookup.slot_index, isolate()); // If the values is not the hole, it will stay initialized, // so no need to generate a check. if (current_value->IsTheHole(isolate())) { return Bailout(kReferenceToUninitializedVariable); } HStoreNamedField* instr = Add( Add(script_context), HObjectAccess::ForContextSlot(lookup.slot_index), value); USE(instr); DCHECK(instr->HasObservableSideEffects()); Add(ast_id, REMOVABLE_SIMULATE); return; } } LookupIterator it(global, var->name(), LookupIterator::OWN); if (CanInlineGlobalPropertyAccess(var, &it, STORE)) { HInstruction* instr = InlineGlobalPropertyStore(&it, value, ast_id); if (!instr) return; AddInstruction(instr); if (instr->HasObservableSideEffects()) { Add(ast_id, REMOVABLE_SIMULATE); } } else { HValue* global_object = Add( BuildGetNativeContext(), nullptr, HObjectAccess::ForContextSlot(Context::EXTENSION_INDEX)); Handle vector = handle(current_feedback_vector(), isolate()); HValue* name = Add(var->name()); HValue* vector_value = Add(vector); HValue* slot_value = Add(vector->GetIndex(slot)); DCHECK_EQ(vector->GetLanguageMode(slot), function_language_mode()); Callable callable = CodeFactory::StoreICInOptimizedCode( isolate(), function_language_mode()); HValue* stub = Add(callable.code()); HValue* values[] = {global_object, name, value, slot_value, vector_value}; HCallWithDescriptor* instr = Add( Code::STORE_IC, stub, 0, callable.descriptor(), ArrayVector(values)); USE(instr); DCHECK(instr->HasObservableSideEffects()); Add(ast_id, REMOVABLE_SIMULATE); } } void HOptimizedGraphBuilder::HandleCompoundAssignment(Assignment* expr) { Expression* target = expr->target(); VariableProxy* proxy = target->AsVariableProxy(); Property* prop = target->AsProperty(); DCHECK(proxy == NULL || prop == NULL); // We have a second position recorded in the FullCodeGenerator to have // type feedback for the binary operation. BinaryOperation* operation = expr->binary_operation(); if (proxy != NULL) { Variable* var = proxy->var(); if (var->mode() == LET) { return Bailout(kUnsupportedLetCompoundAssignment); } CHECK_ALIVE(VisitForValue(operation)); switch (var->location()) { case VariableLocation::UNALLOCATED: HandleGlobalVariableAssignment(var, Top(), expr->AssignmentSlot(), expr->AssignmentId()); break; case VariableLocation::PARAMETER: case VariableLocation::LOCAL: if (var->mode() == CONST) { return Bailout(kNonInitializerAssignmentToConst); } BindIfLive(var, Top()); break; case VariableLocation::CONTEXT: { // Bail out if we try to mutate a parameter value in a function // using the arguments object. We do not (yet) correctly handle the // arguments property of the function. if (current_info()->scope()->arguments() != NULL) { // Parameters will be allocated to context slots. We have no // direct way to detect that the variable is a parameter so we do // a linear search of the parameter variables. int count = current_info()->scope()->num_parameters(); for (int i = 0; i < count; ++i) { if (var == current_info()->scope()->parameter(i)) { Bailout(kAssignmentToParameterFunctionUsesArgumentsObject); } } } HStoreContextSlot::Mode mode; switch (var->mode()) { case LET: mode = HStoreContextSlot::kCheckDeoptimize; break; case CONST: if (var->throw_on_const_assignment(function_language_mode())) { return Bailout(kNonInitializerAssignmentToConst); } else { return ast_context()->ReturnValue(Pop()); } default: mode = HStoreContextSlot::kNoCheck; } HValue* context = BuildContextChainWalk(var); HStoreContextSlot* instr = Add( context, var->index(), mode, Top()); if (instr->HasObservableSideEffects()) { Add(expr->AssignmentId(), REMOVABLE_SIMULATE); } break; } case VariableLocation::LOOKUP: return Bailout(kCompoundAssignmentToLookupSlot); case VariableLocation::MODULE: UNREACHABLE(); } return ast_context()->ReturnValue(Pop()); } else if (prop != NULL) { CHECK_ALIVE(VisitForValue(prop->obj())); HValue* object = Top(); HValue* key = NULL; if (!prop->key()->IsPropertyName() || prop->IsStringAccess()) { CHECK_ALIVE(VisitForValue(prop->key())); key = Top(); } CHECK_ALIVE(PushLoad(prop, object, key)); CHECK_ALIVE(VisitForValue(expr->value())); HValue* right = Pop(); HValue* left = Pop(); Push(BuildBinaryOperation(operation, left, right, PUSH_BEFORE_SIMULATE)); BuildStore(expr, prop, expr->AssignmentSlot(), expr->id(), expr->AssignmentId(), expr->IsUninitialized()); } else { return Bailout(kInvalidLhsInCompoundAssignment); } } void HOptimizedGraphBuilder::VisitAssignment(Assignment* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); VariableProxy* proxy = expr->target()->AsVariableProxy(); Property* prop = expr->target()->AsProperty(); DCHECK(proxy == NULL || prop == NULL); if (expr->is_compound()) { HandleCompoundAssignment(expr); return; } if (prop != NULL) { HandlePropertyAssignment(expr); } else if (proxy != NULL) { Variable* var = proxy->var(); if (var->mode() == CONST) { if (expr->op() != Token::INIT) { if (var->throw_on_const_assignment(function_language_mode())) { return Bailout(kNonInitializerAssignmentToConst); } else { CHECK_ALIVE(VisitForValue(expr->value())); return ast_context()->ReturnValue(Pop()); } } } // Handle the assignment. switch (var->location()) { case VariableLocation::UNALLOCATED: CHECK_ALIVE(VisitForValue(expr->value())); HandleGlobalVariableAssignment(var, Top(), expr->AssignmentSlot(), expr->AssignmentId()); return ast_context()->ReturnValue(Pop()); case VariableLocation::PARAMETER: case VariableLocation::LOCAL: { // Perform an initialization check for let declared variables // or parameters. if (var->mode() == LET && expr->op() == Token::ASSIGN) { HValue* env_value = environment()->Lookup(var); if (env_value == graph()->GetConstantHole()) { return Bailout(kAssignmentToLetVariableBeforeInitialization); } } // We do not allow the arguments object to occur in a context where it // may escape, but assignments to stack-allocated locals are // permitted. CHECK_ALIVE(VisitForValue(expr->value(), ARGUMENTS_ALLOWED)); HValue* value = Pop(); BindIfLive(var, value); return ast_context()->ReturnValue(value); } case VariableLocation::CONTEXT: { // Bail out if we try to mutate a parameter value in a function using // the arguments object. We do not (yet) correctly handle the // arguments property of the function. if (current_info()->scope()->arguments() != NULL) { // Parameters will rewrite to context slots. We have no direct way // to detect that the variable is a parameter. int count = current_info()->scope()->num_parameters(); for (int i = 0; i < count; ++i) { if (var == current_info()->scope()->parameter(i)) { return Bailout(kAssignmentToParameterInArgumentsObject); } } } CHECK_ALIVE(VisitForValue(expr->value())); HStoreContextSlot::Mode mode; if (expr->op() == Token::ASSIGN) { switch (var->mode()) { case LET: mode = HStoreContextSlot::kCheckDeoptimize; break; case CONST: // If we reached this point, the only possibility // is a sloppy assignment to a function name. DCHECK(function_language_mode() == SLOPPY && !var->throw_on_const_assignment(SLOPPY)); return ast_context()->ReturnValue(Pop()); default: mode = HStoreContextSlot::kNoCheck; } } else { DCHECK_EQ(Token::INIT, expr->op()); mode = HStoreContextSlot::kNoCheck; } HValue* context = BuildContextChainWalk(var); HStoreContextSlot* instr = Add( context, var->index(), mode, Top()); if (instr->HasObservableSideEffects()) { Add(expr->AssignmentId(), REMOVABLE_SIMULATE); } return ast_context()->ReturnValue(Pop()); } case VariableLocation::LOOKUP: return Bailout(kAssignmentToLOOKUPVariable); case VariableLocation::MODULE: UNREACHABLE(); } } else { return Bailout(kInvalidLeftHandSideInAssignment); } } void HOptimizedGraphBuilder::VisitYield(Yield* expr) { // Generators are not optimized, so we should never get here. UNREACHABLE(); } void HOptimizedGraphBuilder::VisitThrow(Throw* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!ast_context()->IsEffect()) { // The parser turns invalid left-hand sides in assignments into throw // statements, which may not be in effect contexts. We might still try // to optimize such functions; bail out now if we do. return Bailout(kInvalidLeftHandSideInAssignment); } CHECK_ALIVE(VisitForValue(expr->exception())); HValue* value = environment()->Pop(); if (!is_tracking_positions()) SetSourcePosition(expr->position()); Add(value); Add(Runtime::FunctionForId(Runtime::kThrow), 1); Add(expr->id()); // If the throw definitely exits the function, we can finish with a dummy // control flow at this point. This is not the case if the throw is inside // an inlined function which may be replaced. if (call_context() == NULL) { FinishExitCurrentBlock(New()); } } HInstruction* HGraphBuilder::AddLoadStringInstanceType(HValue* string) { if (string->IsConstant()) { HConstant* c_string = HConstant::cast(string); if (c_string->HasStringValue()) { return Add(c_string->StringValue()->map()->instance_type()); } } return Add( Add(string, nullptr, HObjectAccess::ForMap()), nullptr, HObjectAccess::ForMapInstanceType()); } HInstruction* HGraphBuilder::AddLoadStringLength(HValue* string) { return AddInstruction(BuildLoadStringLength(string)); } HInstruction* HGraphBuilder::BuildLoadStringLength(HValue* string) { if (string->IsConstant()) { HConstant* c_string = HConstant::cast(string); if (c_string->HasStringValue()) { return New(c_string->StringValue()->length()); } } return New(string, nullptr, HObjectAccess::ForStringLength()); } HInstruction* HOptimizedGraphBuilder::BuildNamedGeneric( PropertyAccessType access_type, Expression* expr, FeedbackSlot slot, HValue* object, Handle name, HValue* value, bool is_uninitialized) { if (is_uninitialized) { Add( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess, Deoptimizer::SOFT); } Handle vector(current_feedback_vector(), isolate()); HValue* key = Add(name); HValue* vector_value = Add(vector); HValue* slot_value = Add(vector->GetIndex(slot)); if (access_type == LOAD) { HValue* values[] = {object, key, slot_value, vector_value}; if (!expr->AsProperty()->key()->IsPropertyName()) { DCHECK(vector->IsKeyedLoadIC(slot)); // It's possible that a keyed load of a constant string was converted // to a named load. Here, at the last minute, we need to make sure to // use a generic Keyed Load if we are using the type vector, because // it has to share information with full code. Callable callable = CodeFactory::KeyedLoadICInOptimizedCode(isolate()); HValue* stub = Add(callable.code()); HCallWithDescriptor* result = New(Code::KEYED_LOAD_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return result; } DCHECK(vector->IsLoadIC(slot)); Callable callable = CodeFactory::LoadICInOptimizedCode(isolate()); HValue* stub = Add(callable.code()); HCallWithDescriptor* result = New( Code::LOAD_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return result; } else { HValue* values[] = {object, key, value, slot_value, vector_value}; if (vector->IsKeyedStoreIC(slot)) { // It's possible that a keyed store of a constant string was converted // to a named store. Here, at the last minute, we need to make sure to // use a generic Keyed Store if we are using the type vector, because // it has to share information with full code. DCHECK_EQ(vector->GetLanguageMode(slot), function_language_mode()); Callable callable = CodeFactory::KeyedStoreICInOptimizedCode( isolate(), function_language_mode()); HValue* stub = Add(callable.code()); HCallWithDescriptor* result = New(Code::KEYED_STORE_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return result; } HCallWithDescriptor* result; if (vector->IsStoreOwnIC(slot)) { Callable callable = CodeFactory::StoreOwnICInOptimizedCode(isolate()); HValue* stub = Add(callable.code()); result = New( Code::STORE_IC, stub, 0, callable.descriptor(), ArrayVector(values)); } else { DCHECK(vector->IsStoreIC(slot)); DCHECK_EQ(vector->GetLanguageMode(slot), function_language_mode()); Callable callable = CodeFactory::StoreICInOptimizedCode( isolate(), function_language_mode()); HValue* stub = Add(callable.code()); result = New( Code::STORE_IC, stub, 0, callable.descriptor(), ArrayVector(values)); } return result; } } HInstruction* HOptimizedGraphBuilder::BuildKeyedGeneric( PropertyAccessType access_type, Expression* expr, FeedbackSlot slot, HValue* object, HValue* key, HValue* value) { Handle vector(current_feedback_vector(), isolate()); HValue* vector_value = Add(vector); HValue* slot_value = Add(vector->GetIndex(slot)); if (access_type == LOAD) { HValue* values[] = {object, key, slot_value, vector_value}; Callable callable = CodeFactory::KeyedLoadICInOptimizedCode(isolate()); HValue* stub = Add(callable.code()); HCallWithDescriptor* result = New(Code::KEYED_LOAD_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return result; } else { HValue* values[] = {object, key, value, slot_value, vector_value}; Callable callable = CodeFactory::KeyedStoreICInOptimizedCode( isolate(), function_language_mode()); HValue* stub = Add(callable.code()); HCallWithDescriptor* result = New(Code::KEYED_STORE_IC, stub, 0, callable.descriptor(), ArrayVector(values)); return result; } } LoadKeyedHoleMode HOptimizedGraphBuilder::BuildKeyedHoleMode(Handle map) { // Loads from a "stock" fast holey double arrays can elide the hole check. // Loads from a "stock" fast holey array can convert the hole to undefined // with impunity. LoadKeyedHoleMode load_mode = NEVER_RETURN_HOLE; bool holey_double_elements = *map == isolate()->get_initial_js_array_map(FAST_HOLEY_DOUBLE_ELEMENTS); bool holey_elements = *map == isolate()->get_initial_js_array_map(FAST_HOLEY_ELEMENTS); if ((holey_double_elements || holey_elements) && isolate()->IsFastArrayConstructorPrototypeChainIntact()) { load_mode = holey_double_elements ? ALLOW_RETURN_HOLE : CONVERT_HOLE_TO_UNDEFINED; Handle prototype(JSObject::cast(map->prototype()), isolate()); Handle object_prototype = isolate()->initial_object_prototype(); BuildCheckPrototypeMaps(prototype, object_prototype); graph()->MarkDependsOnEmptyArrayProtoElements(); } return load_mode; } HInstruction* HOptimizedGraphBuilder::BuildMonomorphicElementAccess( HValue* object, HValue* key, HValue* val, HValue* dependency, Handle map, PropertyAccessType access_type, KeyedAccessStoreMode store_mode) { HCheckMaps* checked_object = Add(object, map, dependency); if (access_type == STORE && map->prototype()->IsJSObject()) { // monomorphic stores need a prototype chain check because shape // changes could allow callbacks on elements in the chain that // aren't compatible with monomorphic keyed stores. PrototypeIterator iter(map); JSObject* holder = NULL; while (!iter.IsAtEnd()) { // JSProxies can't occur here because we wouldn't have installed a // non-generic IC if there were any. holder = *PrototypeIterator::GetCurrent(iter); iter.Advance(); } DCHECK(holder && holder->IsJSObject()); BuildCheckPrototypeMaps(handle(JSObject::cast(map->prototype())), Handle(holder)); } LoadKeyedHoleMode load_mode = BuildKeyedHoleMode(map); return BuildUncheckedMonomorphicElementAccess( checked_object, key, val, map->instance_type() == JS_ARRAY_TYPE, map->elements_kind(), access_type, load_mode, store_mode); } static bool CanInlineElementAccess(Handle map) { return map->IsJSObjectMap() && (map->has_fast_elements() || map->has_fixed_typed_array_elements()) && !map->has_indexed_interceptor() && !map->is_access_check_needed(); } HInstruction* HOptimizedGraphBuilder::TryBuildConsolidatedElementLoad( HValue* object, HValue* key, HValue* val, SmallMapList* maps) { // For polymorphic loads of similar elements kinds (i.e. all tagged or all // double), always use the "worst case" code without a transition. This is // much faster than transitioning the elements to the worst case, trading a // HTransitionElements for a HCheckMaps, and avoiding mutation of the array. bool has_double_maps = false; bool has_smi_or_object_maps = false; bool has_js_array_access = false; bool has_non_js_array_access = false; bool has_seen_holey_elements = false; Handle most_general_consolidated_map; for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); if (!CanInlineElementAccess(map)) return NULL; // Don't allow mixing of JSArrays with JSObjects. if (map->instance_type() == JS_ARRAY_TYPE) { if (has_non_js_array_access) return NULL; has_js_array_access = true; } else if (has_js_array_access) { return NULL; } else { has_non_js_array_access = true; } // Don't allow mixed, incompatible elements kinds. if (map->has_fast_double_elements()) { if (has_smi_or_object_maps) return NULL; has_double_maps = true; } else if (map->has_fast_smi_or_object_elements()) { if (has_double_maps) return NULL; has_smi_or_object_maps = true; } else { return NULL; } // Remember if we've ever seen holey elements. if (IsHoleyElementsKind(map->elements_kind())) { has_seen_holey_elements = true; } // Remember the most general elements kind, the code for its load will // properly handle all of the more specific cases. if ((i == 0) || IsMoreGeneralElementsKindTransition( most_general_consolidated_map->elements_kind(), map->elements_kind())) { most_general_consolidated_map = map; } } if (!has_double_maps && !has_smi_or_object_maps) return NULL; HCheckMaps* checked_object = Add(object, maps); // FAST_ELEMENTS is considered more general than FAST_HOLEY_SMI_ELEMENTS. // If we've seen both, the consolidated load must use FAST_HOLEY_ELEMENTS. ElementsKind consolidated_elements_kind = has_seen_holey_elements ? GetHoleyElementsKind(most_general_consolidated_map->elements_kind()) : most_general_consolidated_map->elements_kind(); LoadKeyedHoleMode load_mode = NEVER_RETURN_HOLE; if (has_seen_holey_elements) { // Make sure that all of the maps we are handling have the initial array // prototype. bool saw_non_array_prototype = false; for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); if (map->prototype() != *isolate()->initial_array_prototype()) { // We can't guarantee that loading the hole is safe. The prototype may // have an element at this position. saw_non_array_prototype = true; break; } } if (!saw_non_array_prototype) { Handle holey_map = handle( isolate()->get_initial_js_array_map(consolidated_elements_kind)); load_mode = BuildKeyedHoleMode(holey_map); if (load_mode != NEVER_RETURN_HOLE) { for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); // The prototype check was already done for the holey map in // BuildKeyedHoleMode. if (!map.is_identical_to(holey_map)) { Handle prototype(JSObject::cast(map->prototype()), isolate()); Handle object_prototype = isolate()->initial_object_prototype(); BuildCheckPrototypeMaps(prototype, object_prototype); } } } } } HInstruction* instr = BuildUncheckedMonomorphicElementAccess( checked_object, key, val, most_general_consolidated_map->instance_type() == JS_ARRAY_TYPE, consolidated_elements_kind, LOAD, load_mode, STANDARD_STORE); return instr; } HValue* HOptimizedGraphBuilder::HandlePolymorphicElementAccess( Expression* expr, FeedbackSlot slot, HValue* object, HValue* key, HValue* val, SmallMapList* maps, PropertyAccessType access_type, KeyedAccessStoreMode store_mode, bool* has_side_effects) { *has_side_effects = false; BuildCheckHeapObject(object); if (access_type == LOAD) { HInstruction* consolidated_load = TryBuildConsolidatedElementLoad(object, key, val, maps); if (consolidated_load != NULL) { *has_side_effects |= consolidated_load->HasObservableSideEffects(); return consolidated_load; } } // Elements_kind transition support. MapHandleList transition_target(maps->length()); // Collect possible transition targets. MapHandleList possible_transitioned_maps(maps->length()); for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); // Loads from strings or loads with a mix of string and non-string maps // shouldn't be handled polymorphically. DCHECK(access_type != LOAD || !map->IsStringMap()); ElementsKind elements_kind = map->elements_kind(); if (CanInlineElementAccess(map) && IsFastElementsKind(elements_kind) && elements_kind != GetInitialFastElementsKind()) { possible_transitioned_maps.Add(map); } if (IsSloppyArgumentsElements(elements_kind)) { HInstruction* result = BuildKeyedGeneric(access_type, expr, slot, object, key, val); *has_side_effects = result->HasObservableSideEffects(); return AddInstruction(result); } } // Get transition target for each map (NULL == no transition). for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); Map* transitioned_map = map->is_stable() ? nullptr : map->FindElementsKindTransitionedMap(&possible_transitioned_maps); if (transitioned_map != nullptr) { transition_target.Add(handle(transitioned_map)); } else { transition_target.Add(Handle()); } } MapHandleList untransitionable_maps(maps->length()); HTransitionElementsKind* transition = NULL; for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); DCHECK(map->IsMap()); if (!transition_target.at(i).is_null()) { DCHECK(Map::IsValidElementsTransition( map->elements_kind(), transition_target.at(i)->elements_kind())); transition = Add(object, map, transition_target.at(i)); } else { untransitionable_maps.Add(map); } } // If only one map is left after transitioning, handle this case // monomorphically. DCHECK(untransitionable_maps.length() >= 1); if (untransitionable_maps.length() == 1) { Handle untransitionable_map = untransitionable_maps[0]; HInstruction* instr = NULL; if (!CanInlineElementAccess(untransitionable_map)) { instr = AddInstruction( BuildKeyedGeneric(access_type, expr, slot, object, key, val)); } else { instr = BuildMonomorphicElementAccess( object, key, val, transition, untransitionable_map, access_type, store_mode); } *has_side_effects |= instr->HasObservableSideEffects(); return access_type == STORE ? val : instr; } HBasicBlock* join = graph()->CreateBasicBlock(); for (int i = 0; i < untransitionable_maps.length(); ++i) { Handle map = untransitionable_maps[i]; ElementsKind elements_kind = map->elements_kind(); HBasicBlock* this_map = graph()->CreateBasicBlock(); HBasicBlock* other_map = graph()->CreateBasicBlock(); HCompareMap* mapcompare = New(object, map, this_map, other_map); FinishCurrentBlock(mapcompare); set_current_block(this_map); HInstruction* access = NULL; if (!CanInlineElementAccess(map)) { access = AddInstruction( BuildKeyedGeneric(access_type, expr, slot, object, key, val)); } else { DCHECK(IsFastElementsKind(elements_kind) || IsFixedTypedArrayElementsKind(elements_kind)); LoadKeyedHoleMode load_mode = BuildKeyedHoleMode(map); // Happily, mapcompare is a checked object. access = BuildUncheckedMonomorphicElementAccess( mapcompare, key, val, map->instance_type() == JS_ARRAY_TYPE, elements_kind, access_type, load_mode, store_mode); } *has_side_effects |= access->HasObservableSideEffects(); // The caller will use has_side_effects and add a correct Simulate. access->SetFlag(HValue::kHasNoObservableSideEffects); if (access_type == LOAD) { Push(access); } NoObservableSideEffectsScope scope(this); GotoNoSimulate(join); set_current_block(other_map); } // Ensure that we visited at least one map above that goes to join. This is // necessary because FinishExitWithHardDeoptimization does an AbnormalExit // rather than joining the join block. If this becomes an issue, insert a // generic access in the case length() == 0. DCHECK(join->predecessors()->length() > 0); // Deopt if none of the cases matched. NoObservableSideEffectsScope scope(this); FinishExitWithHardDeoptimization( DeoptimizeReason::kUnknownMapInPolymorphicElementAccess); set_current_block(join); return access_type == STORE ? val : Pop(); } HValue* HOptimizedGraphBuilder::HandleKeyedElementAccess( HValue* obj, HValue* key, HValue* val, Expression* expr, FeedbackSlot slot, BailoutId ast_id, BailoutId return_id, PropertyAccessType access_type, bool* has_side_effects) { // A keyed name access with type feedback may contain the name. Handle vector = handle(current_feedback_vector(), isolate()); HValue* expected_key = key; if (!key->ActualValue()->IsConstant()) { Name* name = nullptr; if (access_type == LOAD) { KeyedLoadICNexus nexus(vector, slot); name = nexus.FindFirstName(); } else { KeyedStoreICNexus nexus(vector, slot); name = nexus.FindFirstName(); } if (name != nullptr) { Handle handle_name(name); expected_key = Add(handle_name); // We need a check against the key. bool in_new_space = isolate()->heap()->InNewSpace(*handle_name); Unique unique_name = Unique::CreateUninitialized(handle_name); Add(key, unique_name, in_new_space); } } if (expected_key->ActualValue()->IsConstant()) { Handle constant = HConstant::cast(expected_key->ActualValue())->handle(isolate()); uint32_t array_index; if ((constant->IsString() && !Handle::cast(constant)->AsArrayIndex(&array_index)) || constant->IsSymbol()) { if (!constant->IsUniqueName()) { constant = isolate()->factory()->InternalizeString( Handle::cast(constant)); } HValue* access = BuildNamedAccess(access_type, ast_id, return_id, expr, slot, obj, Handle::cast(constant), val, false); if (access == NULL || access->IsPhi() || HInstruction::cast(access)->IsLinked()) { *has_side_effects = false; } else { HInstruction* instr = HInstruction::cast(access); AddInstruction(instr); *has_side_effects = instr->HasObservableSideEffects(); } return access; } } DCHECK(!expr->IsPropertyName()); HInstruction* instr = NULL; SmallMapList* maps; bool monomorphic = ComputeReceiverTypes(expr, obj, &maps, this); bool force_generic = false; if (expr->GetKeyType() == PROPERTY) { // Non-Generic accesses assume that elements are being accessed, and will // deopt for non-index keys, which the IC knows will occur. // TODO(jkummerow): Consider adding proper support for property accesses. force_generic = true; monomorphic = false; } else if (access_type == STORE && (monomorphic || (maps != NULL && !maps->is_empty()))) { // Stores can't be mono/polymorphic if their prototype chain has dictionary // elements. However a receiver map that has dictionary elements itself // should be left to normal mono/poly behavior (the other maps may benefit // from highly optimized stores). for (int i = 0; i < maps->length(); i++) { Handle current_map = maps->at(i); if (current_map->DictionaryElementsInPrototypeChainOnly()) { force_generic = true; monomorphic = false; break; } } } else if (access_type == LOAD && !monomorphic && (maps != NULL && !maps->is_empty())) { // Polymorphic loads have to go generic if any of the maps are strings. // If some, but not all of the maps are strings, we should go generic // because polymorphic access wants to key on ElementsKind and isn't // compatible with strings. for (int i = 0; i < maps->length(); i++) { Handle current_map = maps->at(i); if (current_map->IsStringMap()) { force_generic = true; break; } } } if (monomorphic) { Handle map = maps->first(); if (!CanInlineElementAccess(map)) { instr = AddInstruction( BuildKeyedGeneric(access_type, expr, slot, obj, key, val)); } else { BuildCheckHeapObject(obj); instr = BuildMonomorphicElementAccess( obj, key, val, NULL, map, access_type, expr->GetStoreMode()); } } else if (!force_generic && (maps != NULL && !maps->is_empty())) { return HandlePolymorphicElementAccess(expr, slot, obj, key, val, maps, access_type, expr->GetStoreMode(), has_side_effects); } else { if (access_type == STORE) { if (expr->IsAssignment() && expr->AsAssignment()->HasNoTypeInformation()) { Add( DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess, Deoptimizer::SOFT); } } else { if (expr->AsProperty()->HasNoTypeInformation()) { Add( DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess, Deoptimizer::SOFT); } } instr = AddInstruction( BuildKeyedGeneric(access_type, expr, slot, obj, key, val)); } *has_side_effects = instr->HasObservableSideEffects(); return instr; } void HOptimizedGraphBuilder::EnsureArgumentsArePushedForAccess() { // Outermost function already has arguments on the stack. if (function_state()->outer() == NULL) return; if (function_state()->arguments_pushed()) return; // Push arguments when entering inlined function. HEnterInlined* entry = function_state()->entry(); entry->set_arguments_pushed(); HArgumentsObject* arguments = entry->arguments_object(); const ZoneList* arguments_values = arguments->arguments_values(); HInstruction* insert_after = entry; for (int i = 0; i < arguments_values->length(); i++) { HValue* argument = arguments_values->at(i); HInstruction* push_argument = New(argument); push_argument->InsertAfter(insert_after); insert_after = push_argument; } HArgumentsElements* arguments_elements = New(true); arguments_elements->ClearFlag(HValue::kUseGVN); arguments_elements->InsertAfter(insert_after); function_state()->set_arguments_elements(arguments_elements); } bool HOptimizedGraphBuilder::IsAnyParameterContextAllocated() { int count = current_info()->scope()->num_parameters(); for (int i = 0; i < count; ++i) { if (current_info()->scope()->parameter(i)->location() == VariableLocation::CONTEXT) { return true; } } return false; } bool HOptimizedGraphBuilder::TryArgumentsAccess(Property* expr) { VariableProxy* proxy = expr->obj()->AsVariableProxy(); if (proxy == NULL) return false; if (!proxy->var()->IsStackAllocated()) return false; if (!environment()->Lookup(proxy->var())->CheckFlag(HValue::kIsArguments)) { return false; } HInstruction* result = NULL; if (expr->key()->IsPropertyName()) { Handle name = expr->key()->AsLiteral()->AsPropertyName(); if (!String::Equals(name, isolate()->factory()->length_string())) { return false; } // Make sure we visit the arguments object so that the liveness analysis // still records the access. CHECK_ALIVE_OR_RETURN(VisitForValue(expr->obj(), ARGUMENTS_ALLOWED), true); Drop(1); if (function_state()->outer() == NULL) { HInstruction* elements = Add(false); result = New(elements); } else { // Number of arguments without receiver. int argument_count = environment()-> arguments_environment()->parameter_count() - 1; result = New(argument_count); } } else { // We need to take into account the KEYED_LOAD_IC feedback to guard the // HBoundsCheck instructions below. if (!expr->IsMonomorphic() && !expr->IsUninitialized()) return false; if (IsAnyParameterContextAllocated()) return false; CHECK_ALIVE_OR_RETURN(VisitForValue(expr->obj(), ARGUMENTS_ALLOWED), true); CHECK_ALIVE_OR_RETURN(VisitForValue(expr->key()), true); HValue* key = Pop(); Drop(1); // Arguments object. if (function_state()->outer() == NULL) { HInstruction* elements = Add(false); HInstruction* length = Add(elements); HInstruction* checked_key = Add(key, length); result = New(elements, length, checked_key); } else { EnsureArgumentsArePushedForAccess(); // Number of arguments without receiver. HInstruction* elements = function_state()->arguments_elements(); int argument_count = environment()-> arguments_environment()->parameter_count() - 1; HInstruction* length = Add(argument_count); HInstruction* checked_key = Add(key, length); result = New(elements, length, checked_key); } } ast_context()->ReturnInstruction(result, expr->id()); return true; } HValue* HOptimizedGraphBuilder::BuildNamedAccess( PropertyAccessType access, BailoutId ast_id, BailoutId return_id, Expression* expr, FeedbackSlot slot, HValue* object, Handle name, HValue* value, bool is_uninitialized) { SmallMapList* maps; ComputeReceiverTypes(expr, object, &maps, this); DCHECK(maps != NULL); // Check for special case: Access via a single map to the global proxy // can also be handled monomorphically. if (maps->length() > 0) { Handle map_constructor = handle(maps->first()->GetConstructor(), isolate()); if (map_constructor->IsJSFunction()) { Handle map_context = handle(Handle::cast(map_constructor)->context()); Handle current_context(current_info()->context()); bool is_same_context_global_proxy_access = maps->length() == 1 && // >1 map => fallback to polymorphic maps->first()->IsJSGlobalProxyMap() && (*map_context == *current_context); if (is_same_context_global_proxy_access) { Handle global_object(current_info()->global_object()); LookupIterator it(global_object, name, LookupIterator::OWN); if (CanInlineGlobalPropertyAccess(&it, access)) { BuildCheckHeapObject(object); Add(object, maps); if (access == LOAD) { InlineGlobalPropertyLoad(&it, expr->id()); return nullptr; } else { return InlineGlobalPropertyStore(&it, value, expr->id()); } } } } PropertyAccessInfo info(this, access, maps->first(), name); if (!info.CanAccessAsMonomorphic(maps)) { HandlePolymorphicNamedFieldAccess(access, expr, slot, ast_id, return_id, object, value, maps, name); return NULL; } HValue* checked_object; // AstType::Number() is only supported by polymorphic load/call handling. DCHECK(!info.IsNumberType()); BuildCheckHeapObject(object); if (AreStringTypes(maps)) { checked_object = Add(object, HCheckInstanceType::IS_STRING); } else { checked_object = Add(object, maps); } return BuildMonomorphicAccess( &info, object, checked_object, value, ast_id, return_id); } return BuildNamedGeneric(access, expr, slot, object, name, value, is_uninitialized); } void HOptimizedGraphBuilder::PushLoad(Property* expr, HValue* object, HValue* key) { ValueContext for_value(this, ARGUMENTS_NOT_ALLOWED); Push(object); if (key != NULL) Push(key); BuildLoad(expr, expr->LoadId()); } void HOptimizedGraphBuilder::BuildLoad(Property* expr, BailoutId ast_id) { HInstruction* instr = NULL; if (expr->IsStringAccess() && expr->GetKeyType() == ELEMENT) { HValue* index = Pop(); HValue* string = Pop(); HInstruction* char_code = BuildStringCharCodeAt(string, index); AddInstruction(char_code); if (char_code->IsConstant()) { HConstant* c_code = HConstant::cast(char_code); if (c_code->HasNumberValue() && std::isnan(c_code->DoubleValue())) { Add(DeoptimizeReason::kOutOfBounds, Deoptimizer::EAGER); } } instr = NewUncasted(char_code); } else if (expr->key()->IsPropertyName()) { Handle name = expr->key()->AsLiteral()->AsPropertyName(); HValue* object = Pop(); HValue* value = BuildNamedAccess(LOAD, ast_id, expr->LoadId(), expr, expr->PropertyFeedbackSlot(), object, name, NULL, expr->IsUninitialized()); if (value == NULL) return; if (value->IsPhi()) return ast_context()->ReturnValue(value); instr = HInstruction::cast(value); if (instr->IsLinked()) return ast_context()->ReturnValue(instr); } else { HValue* key = Pop(); HValue* obj = Pop(); bool has_side_effects = false; HValue* load = HandleKeyedElementAccess( obj, key, NULL, expr, expr->PropertyFeedbackSlot(), ast_id, expr->LoadId(), LOAD, &has_side_effects); if (has_side_effects) { if (ast_context()->IsEffect()) { Add(ast_id, REMOVABLE_SIMULATE); } else { Push(load); Add(ast_id, REMOVABLE_SIMULATE); Drop(1); } } if (load == NULL) return; return ast_context()->ReturnValue(load); } return ast_context()->ReturnInstruction(instr, ast_id); } void HOptimizedGraphBuilder::VisitProperty(Property* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (TryArgumentsAccess(expr)) return; CHECK_ALIVE(VisitForValue(expr->obj())); if (!expr->key()->IsPropertyName() || expr->IsStringAccess()) { CHECK_ALIVE(VisitForValue(expr->key())); } BuildLoad(expr, expr->id()); } HInstruction* HGraphBuilder::BuildConstantMapCheck(Handle constant, bool ensure_no_elements) { HCheckMaps* check = Add( Add(constant), handle(constant->map())); check->ClearDependsOnFlag(kElementsKind); if (ensure_no_elements) { // TODO(ishell): remove this once we support NO_ELEMENTS elements kind. HValue* elements = AddLoadElements(check, nullptr); HValue* empty_elements = Add(isolate()->factory()->empty_fixed_array()); IfBuilder if_empty(this); if_empty.IfNot(elements, empty_elements); if_empty.ThenDeopt(DeoptimizeReason::kWrongMap); if_empty.End(); } return check; } HInstruction* HGraphBuilder::BuildCheckPrototypeMaps(Handle prototype, Handle holder, bool ensure_no_elements) { PrototypeIterator iter(isolate(), prototype, kStartAtReceiver); while (holder.is_null() || !PrototypeIterator::GetCurrent(iter).is_identical_to(holder)) { BuildConstantMapCheck(PrototypeIterator::GetCurrent(iter), ensure_no_elements); iter.Advance(); if (iter.IsAtEnd()) { return NULL; } } return BuildConstantMapCheck(holder); } void HOptimizedGraphBuilder::AddCheckPrototypeMaps(Handle holder, Handle receiver_map) { if (!holder.is_null()) { Handle prototype(JSObject::cast(receiver_map->prototype())); BuildCheckPrototypeMaps(prototype, holder); } } void HOptimizedGraphBuilder::BuildEnsureCallable(HValue* object) { NoObservableSideEffectsScope scope(this); const Runtime::Function* throw_called_non_callable = Runtime::FunctionForId(Runtime::kThrowCalledNonCallable); IfBuilder is_not_function(this); HValue* smi_check = is_not_function.If(object); is_not_function.Or(); HValue* map = AddLoadMap(object, smi_check); HValue* bit_field = Add(map, nullptr, HObjectAccess::ForMapBitField()); HValue* bit_field_masked = AddUncasted( Token::BIT_AND, bit_field, Add(1 << Map::kIsCallable)); is_not_function.IfNot( bit_field_masked, Add(1 << Map::kIsCallable), Token::EQ); is_not_function.Then(); { Add(object); Add(throw_called_non_callable, 1); } is_not_function.End(); } HInstruction* HOptimizedGraphBuilder::NewCallFunction( HValue* function, int argument_count, TailCallMode syntactic_tail_call_mode, ConvertReceiverMode convert_mode, TailCallMode tail_call_mode) { if (syntactic_tail_call_mode == TailCallMode::kAllow) { BuildEnsureCallable(function); } else { DCHECK_EQ(TailCallMode::kDisallow, tail_call_mode); } HValue* arity = Add(argument_count - 1); HValue* op_vals[] = {function, arity}; Callable callable = CodeFactory::Call(isolate(), convert_mode, tail_call_mode); HConstant* stub = Add(callable.code()); return New(stub, argument_count, callable.descriptor(), ArrayVector(op_vals), syntactic_tail_call_mode); } HInstruction* HOptimizedGraphBuilder::NewCallFunctionViaIC( HValue* function, int argument_count, TailCallMode syntactic_tail_call_mode, ConvertReceiverMode convert_mode, TailCallMode tail_call_mode, FeedbackSlot slot) { if (syntactic_tail_call_mode == TailCallMode::kAllow) { BuildEnsureCallable(function); } else { DCHECK_EQ(TailCallMode::kDisallow, tail_call_mode); } int arity = argument_count - 1; Handle vector(current_feedback_vector(), isolate()); HValue* arity_val = Add(arity); HValue* index_val = Add(vector->GetIndex(slot)); HValue* vector_val = Add(vector); HValue* op_vals[] = {function, arity_val, index_val, vector_val}; Callable callable = CodeFactory::CallIC(isolate(), convert_mode, tail_call_mode); HConstant* stub = Add(callable.code()); return New(stub, argument_count, callable.descriptor(), ArrayVector(op_vals), syntactic_tail_call_mode); } HInstruction* HOptimizedGraphBuilder::NewCallConstantFunction( Handle function, int argument_count, TailCallMode syntactic_tail_call_mode, TailCallMode tail_call_mode) { HValue* target = Add(function); return New(target, function, argument_count, syntactic_tail_call_mode, tail_call_mode); } class FunctionSorter { public: explicit FunctionSorter(int index = 0, int ticks = 0, int size = 0) : index_(index), ticks_(ticks), size_(size) {} int index() const { return index_; } int ticks() const { return ticks_; } int size() const { return size_; } private: int index_; int ticks_; int size_; }; inline bool operator<(const FunctionSorter& lhs, const FunctionSorter& rhs) { int diff = lhs.ticks() - rhs.ticks(); if (diff != 0) return diff > 0; return lhs.size() < rhs.size(); } void HOptimizedGraphBuilder::HandlePolymorphicCallNamed(Call* expr, HValue* receiver, SmallMapList* maps, Handle name) { int argument_count = expr->arguments()->length() + 1; // Includes receiver. FunctionSorter order[kMaxCallPolymorphism]; bool handle_smi = false; bool handled_string = false; int ordered_functions = 0; TailCallMode syntactic_tail_call_mode = expr->tail_call_mode(); TailCallMode tail_call_mode = function_state()->ComputeTailCallMode(syntactic_tail_call_mode); int i; for (i = 0; i < maps->length() && ordered_functions < kMaxCallPolymorphism; ++i) { PropertyAccessInfo info(this, LOAD, maps->at(i), name); if (info.CanAccessMonomorphic() && info.IsDataConstant() && info.constant()->IsJSFunction()) { if (info.IsStringType()) { if (handled_string) continue; handled_string = true; } Handle target = Handle::cast(info.constant()); if (info.IsNumberType()) { handle_smi = true; } expr->set_target(target); order[ordered_functions++] = FunctionSorter( i, target->shared()->profiler_ticks(), InliningAstSize(target)); } } std::sort(order, order + ordered_functions); if (i < maps->length()) { maps->Clear(); ordered_functions = -1; } HBasicBlock* number_block = NULL; HBasicBlock* join = NULL; handled_string = false; int count = 0; for (int fn = 0; fn < ordered_functions; ++fn) { int i = order[fn].index(); PropertyAccessInfo info(this, LOAD, maps->at(i), name); if (info.IsStringType()) { if (handled_string) continue; handled_string = true; } // Reloads the target. info.CanAccessMonomorphic(); Handle target = Handle::cast(info.constant()); expr->set_target(target); if (count == 0) { // Only needed once. join = graph()->CreateBasicBlock(); if (handle_smi) { HBasicBlock* empty_smi_block = graph()->CreateBasicBlock(); HBasicBlock* not_smi_block = graph()->CreateBasicBlock(); number_block = graph()->CreateBasicBlock(); FinishCurrentBlock(New( receiver, empty_smi_block, not_smi_block)); GotoNoSimulate(empty_smi_block, number_block); set_current_block(not_smi_block); } else { BuildCheckHeapObject(receiver); } } ++count; HBasicBlock* if_true = graph()->CreateBasicBlock(); HBasicBlock* if_false = graph()->CreateBasicBlock(); HUnaryControlInstruction* compare; Handle map = info.map(); if (info.IsNumberType()) { Handle heap_number_map = isolate()->factory()->heap_number_map(); compare = New(receiver, heap_number_map, if_true, if_false); } else if (info.IsStringType()) { compare = New(receiver, if_true, if_false); } else { compare = New(receiver, map, if_true, if_false); } FinishCurrentBlock(compare); if (info.IsNumberType()) { GotoNoSimulate(if_true, number_block); if_true = number_block; } set_current_block(if_true); AddCheckPrototypeMaps(info.holder(), map); HValue* function = Add(expr->target()); environment()->SetExpressionStackAt(0, function); Push(receiver); CHECK_ALIVE(VisitExpressions(expr->arguments())); bool needs_wrapping = info.NeedsWrappingFor(target); bool try_inline = FLAG_polymorphic_inlining && !needs_wrapping; if (FLAG_trace_inlining && try_inline) { Handle caller = current_info()->closure(); std::unique_ptr caller_name = caller->shared()->DebugName()->ToCString(); PrintF("Trying to inline the polymorphic call to %s from %s\n", name->ToCString().get(), caller_name.get()); } if (try_inline && TryInlineCall(expr)) { // Trying to inline will signal that we should bailout from the // entire compilation by setting stack overflow on the visitor. if (HasStackOverflow()) return; } else { // Since HWrapReceiver currently cannot actually wrap numbers and strings, // use the regular call builtin for method calls to wrap the receiver. // TODO(verwaest): Support creation of value wrappers directly in // HWrapReceiver. HInstruction* call = needs_wrapping ? NewCallFunction( function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNotNullOrUndefined, tail_call_mode) : NewCallConstantFunction(target, argument_count, syntactic_tail_call_mode, tail_call_mode); PushArgumentsFromEnvironment(argument_count); AddInstruction(call); Drop(1); // Drop the function. if (!ast_context()->IsEffect()) Push(call); } if (current_block() != NULL) Goto(join); set_current_block(if_false); } // Finish up. Unconditionally deoptimize if we've handled all the maps we // know about and do not want to handle ones we've never seen. Otherwise // use a generic IC. if (ordered_functions == maps->length() && FLAG_deoptimize_uncommon_cases) { FinishExitWithHardDeoptimization( DeoptimizeReason::kUnknownMapInPolymorphicCall); } else { Property* prop = expr->expression()->AsProperty(); HInstruction* function = BuildNamedGeneric(LOAD, prop, prop->PropertyFeedbackSlot(), receiver, name, NULL, prop->IsUninitialized()); AddInstruction(function); Push(function); AddSimulate(prop->LoadId(), REMOVABLE_SIMULATE); environment()->SetExpressionStackAt(1, function); environment()->SetExpressionStackAt(0, receiver); CHECK_ALIVE(VisitExpressions(expr->arguments())); HInstruction* call = NewCallFunction( function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNotNullOrUndefined, tail_call_mode); PushArgumentsFromEnvironment(argument_count); Drop(1); // Function. if (join != NULL) { AddInstruction(call); if (!ast_context()->IsEffect()) Push(call); Goto(join); } else { return ast_context()->ReturnInstruction(call, expr->id()); } } // We assume that control flow is always live after an expression. So // even without predecessors to the join block, we set it as the exit // block and continue by adding instructions there. DCHECK(join != NULL); if (join->HasPredecessor()) { set_current_block(join); join->SetJoinId(expr->id()); if (!ast_context()->IsEffect()) return ast_context()->ReturnValue(Pop()); } else { set_current_block(NULL); } } void HOptimizedGraphBuilder::TraceInline(Handle target, Handle caller, const char* reason, TailCallMode tail_call_mode) { if (FLAG_trace_inlining) { std::unique_ptr target_name = target->shared()->DebugName()->ToCString(); std::unique_ptr caller_name = caller->shared()->DebugName()->ToCString(); if (reason == NULL) { const char* call_mode = tail_call_mode == TailCallMode::kAllow ? "tail called" : "called"; PrintF("Inlined %s %s from %s.\n", target_name.get(), call_mode, caller_name.get()); } else { PrintF("Did not inline %s called from %s (%s).\n", target_name.get(), caller_name.get(), reason); } } } static const int kNotInlinable = 1000000000; int HOptimizedGraphBuilder::InliningAstSize(Handle target) { if (!FLAG_use_inlining) return kNotInlinable; // Precondition: call is monomorphic and we have found a target with the // appropriate arity. Handle caller = current_info()->closure(); Handle target_shared(target->shared()); // Always inline functions that force inlining. if (target_shared->force_inline()) { return 0; } if (!target->shared()->IsUserJavaScript()) { return kNotInlinable; } if (target_shared->IsApiFunction()) { TraceInline(target, caller, "target is api function"); return kNotInlinable; } // Do a quick check on source code length to avoid parsing large // inlining candidates. if (target_shared->SourceSize() > Min(FLAG_max_inlined_source_size, kUnlimitedMaxInlinedSourceSize)) { TraceInline(target, caller, "target text too big"); return kNotInlinable; } // Target must be inlineable. BailoutReason noopt_reason = target_shared->disable_optimization_reason(); if (!target_shared->IsInlineable() && noopt_reason != kHydrogenFilter) { TraceInline(target, caller, "target not inlineable"); return kNotInlinable; } if (noopt_reason != kNoReason && noopt_reason != kHydrogenFilter) { TraceInline(target, caller, "target contains unsupported syntax [early]"); return kNotInlinable; } int nodes_added = target_shared->ast_node_count(); return nodes_added; } bool HOptimizedGraphBuilder::TryInline(Handle target, int arguments_count, HValue* implicit_return_value, BailoutId ast_id, BailoutId return_id, InliningKind inlining_kind, TailCallMode syntactic_tail_call_mode) { if (target->context()->native_context() != top_info()->closure()->context()->native_context()) { return false; } int nodes_added = InliningAstSize(target); if (nodes_added == kNotInlinable) return false; Handle caller = current_info()->closure(); if (nodes_added > Min(FLAG_max_inlined_nodes, kUnlimitedMaxInlinedNodes)) { TraceInline(target, caller, "target AST is too large [early]"); return false; } // Don't inline deeper than the maximum number of inlining levels. HEnvironment* env = environment(); int current_level = 1; while (env->outer() != NULL) { if (current_level == FLAG_max_inlining_levels) { TraceInline(target, caller, "inline depth limit reached"); return false; } if (env->outer()->frame_type() == JS_FUNCTION) { current_level++; } env = env->outer(); } // Don't inline recursive functions. for (FunctionState* state = function_state(); state != NULL; state = state->outer()) { if (*state->compilation_info()->closure() == *target) { TraceInline(target, caller, "target is recursive"); return false; } } // We don't want to add more than a certain number of nodes from inlining. // Always inline small methods (<= 10 nodes). if (inlined_count_ > Min(FLAG_max_inlined_nodes_cumulative, kUnlimitedMaxInlinedNodesCumulative)) { TraceInline(target, caller, "cumulative AST node limit reached"); return false; } // Parse and allocate variables. // Use the same AstValueFactory for creating strings in the sub-compilation // step, but don't transfer ownership to target_info. Handle target_shared(target->shared()); ParseInfo parse_info(target_shared, top_info()->parse_info()->zone_shared()); parse_info.set_ast_value_factory( top_info()->parse_info()->ast_value_factory()); parse_info.set_ast_value_factory_owned(false); CompilationInfo target_info(parse_info.zone(), &parse_info, target); if (inlining_kind != CONSTRUCT_CALL_RETURN && IsClassConstructor(target_shared->kind())) { TraceInline(target, caller, "target is classConstructor"); return false; } if (target_shared->HasDebugInfo()) { TraceInline(target, caller, "target is being debugged"); return false; } if (!Compiler::ParseAndAnalyze(target_info.parse_info())) { if (target_info.isolate()->has_pending_exception()) { // Parse or scope error, never optimize this function. SetStackOverflow(); target_shared->DisableOptimization(kParseScopeError); } TraceInline(target, caller, "parse failure"); return false; } if (target_shared->must_use_ignition_turbo()) { TraceInline(target, caller, "ParseAndAnalyze found incompatibility"); return false; } if (target_info.scope()->NeedsContext()) { TraceInline(target, caller, "target has context-allocated variables"); return false; } if (target_info.scope()->rest_parameter() != nullptr) { TraceInline(target, caller, "target uses rest parameters"); return false; } FunctionLiteral* function = target_info.literal(); // The following conditions must be checked again after re-parsing, because // earlier the information might not have been complete due to lazy parsing. nodes_added = function->ast_node_count(); if (nodes_added > Min(FLAG_max_inlined_nodes, kUnlimitedMaxInlinedNodes)) { TraceInline(target, caller, "target AST is too large [late]"); return false; } if (function->dont_optimize()) { TraceInline(target, caller, "target contains unsupported syntax [late]"); return false; } // If the function uses the arguments object check that inlining of functions // with arguments object is enabled and the arguments-variable is // stack allocated. if (function->scope()->arguments() != NULL) { if (!FLAG_inline_arguments) { TraceInline(target, caller, "target uses arguments object"); return false; } } // Unsupported variable references present. if (function->scope()->this_function_var() != nullptr || function->scope()->new_target_var() != nullptr) { TraceInline(target, caller, "target uses new target or this function"); return false; } // All declarations must be inlineable. Declaration::List* decls = target_info.scope()->declarations(); for (Declaration* decl : *decls) { if (decl->IsFunctionDeclaration() || !decl->proxy()->var()->IsStackAllocated()) { TraceInline(target, caller, "target has non-trivial declaration"); return false; } } // Generate the deoptimization data for the unoptimized version of // the target function if we don't already have it. if (!Compiler::EnsureDeoptimizationSupport(&target_info)) { TraceInline(target, caller, "could not generate deoptimization info"); return false; } // Remember that we inlined this function. This needs to be called right // after the EnsureDeoptimizationSupport call so that the code flusher // does not remove the code with the deoptimization support. int inlining_id = top_info()->AddInlinedFunction(target_info.shared_info(), source_position()); // ---------------------------------------------------------------- // After this point, we've made a decision to inline this function (so // TryInline should always return true). // If target was lazily compiled, it's literals array may not yet be set up. JSFunction::EnsureLiterals(target); // Type-check the inlined function. DCHECK(target_shared->has_deoptimization_support()); AstTyper(target_info.isolate(), target_info.zone(), target_info.closure(), target_info.scope(), target_info.osr_ast_id(), target_info.literal(), &bounds_) .Run(); // Save the pending call context. Set up new one for the inlined function. // The function state is new-allocated because we need to delete it // in two different places. FunctionState* target_state = new FunctionState( this, &target_info, inlining_kind, inlining_id, function_state()->ComputeTailCallMode(syntactic_tail_call_mode)); HConstant* undefined = graph()->GetConstantUndefined(); HEnvironment* inner_env = environment()->CopyForInlining( target, arguments_count, function, undefined, function_state()->inlining_kind(), syntactic_tail_call_mode); HConstant* context = Add(Handle(target->context())); inner_env->BindContext(context); // Create a dematerialized arguments object for the function, also copy the // current arguments values to use them for materialization. HEnvironment* arguments_env = inner_env->arguments_environment(); int parameter_count = arguments_env->parameter_count(); HArgumentsObject* arguments_object = Add(parameter_count); for (int i = 0; i < parameter_count; i++) { arguments_object->AddArgument(arguments_env->Lookup(i), zone()); } // If the function uses arguments object then bind bind one. if (function->scope()->arguments() != NULL) { DCHECK(function->scope()->arguments()->IsStackAllocated()); inner_env->Bind(function->scope()->arguments(), arguments_object); } // Capture the state before invoking the inlined function for deopt in the // inlined function. This simulate has no bailout-id since it's not directly // reachable for deopt, and is only used to capture the state. If the simulate // becomes reachable by merging, the ast id of the simulate merged into it is // adopted. Add(BailoutId::None()); current_block()->UpdateEnvironment(inner_env); Scope* saved_scope = scope(); set_scope(target_info.scope()); HEnterInlined* enter_inlined = Add( return_id, target, context, arguments_count, function, function_state()->inlining_kind(), function->scope()->arguments(), arguments_object, syntactic_tail_call_mode); if (is_tracking_positions()) { enter_inlined->set_inlining_id(inlining_id); } function_state()->set_entry(enter_inlined); VisitDeclarations(target_info.scope()->declarations()); VisitStatements(function->body()); set_scope(saved_scope); if (HasStackOverflow()) { // Bail out if the inline function did, as we cannot residualize a call // instead, but do not disable optimization for the outer function. TraceInline(target, caller, "inline graph construction failed"); target_shared->DisableOptimization(kInliningBailedOut); current_info()->RetryOptimization(kInliningBailedOut); delete target_state; return true; } // Update inlined nodes count. inlined_count_ += nodes_added; Handle unoptimized_code(target_shared->code()); DCHECK(unoptimized_code->kind() == Code::FUNCTION); Handle type_info( TypeFeedbackInfo::cast(unoptimized_code->type_feedback_info())); graph()->update_type_change_checksum(type_info->own_type_change_checksum()); TraceInline(target, caller, NULL, syntactic_tail_call_mode); if (current_block() != NULL) { FunctionState* state = function_state(); if (state->inlining_kind() == CONSTRUCT_CALL_RETURN) { // Falling off the end of an inlined construct call. In a test context the // return value will always evaluate to true, in a value context the // return value is the newly allocated receiver. if (call_context()->IsTest()) { inlined_test_context()->ReturnValue(graph()->GetConstantTrue()); } else if (call_context()->IsEffect()) { Goto(function_return(), state); } else { DCHECK(call_context()->IsValue()); AddLeaveInlined(implicit_return_value, state); } } else if (state->inlining_kind() == SETTER_CALL_RETURN) { // Falling off the end of an inlined setter call. The returned value is // never used, the value of an assignment is always the value of the RHS // of the assignment. if (call_context()->IsTest()) { inlined_test_context()->ReturnValue(implicit_return_value); } else if (call_context()->IsEffect()) { Goto(function_return(), state); } else { DCHECK(call_context()->IsValue()); AddLeaveInlined(implicit_return_value, state); } } else { // Falling off the end of a normal inlined function. This basically means // returning undefined. if (call_context()->IsTest()) { inlined_test_context()->ReturnValue(graph()->GetConstantFalse()); } else if (call_context()->IsEffect()) { Goto(function_return(), state); } else { DCHECK(call_context()->IsValue()); AddLeaveInlined(undefined, state); } } } // Fix up the function exits. if (inlined_test_context() != NULL) { HBasicBlock* if_true = inlined_test_context()->if_true(); HBasicBlock* if_false = inlined_test_context()->if_false(); HEnterInlined* entry = function_state()->entry(); // Pop the return test context from the expression context stack. DCHECK(ast_context() == inlined_test_context()); ClearInlinedTestContext(); delete target_state; // Forward to the real test context. if (if_true->HasPredecessor()) { entry->RegisterReturnTarget(if_true, zone()); if_true->SetJoinId(ast_id); HBasicBlock* true_target = TestContext::cast(ast_context())->if_true(); Goto(if_true, true_target, function_state()); } if (if_false->HasPredecessor()) { entry->RegisterReturnTarget(if_false, zone()); if_false->SetJoinId(ast_id); HBasicBlock* false_target = TestContext::cast(ast_context())->if_false(); Goto(if_false, false_target, function_state()); } set_current_block(NULL); return true; } else if (function_return()->HasPredecessor()) { function_state()->entry()->RegisterReturnTarget(function_return(), zone()); function_return()->SetJoinId(ast_id); set_current_block(function_return()); } else { set_current_block(NULL); } delete target_state; return true; } bool HOptimizedGraphBuilder::TryInlineCall(Call* expr) { return TryInline(expr->target(), expr->arguments()->length(), NULL, expr->id(), expr->ReturnId(), NORMAL_RETURN, expr->tail_call_mode()); } bool HOptimizedGraphBuilder::TryInlineConstruct(CallNew* expr, HValue* implicit_return_value) { return TryInline(expr->target(), expr->arguments()->length(), implicit_return_value, expr->id(), expr->ReturnId(), CONSTRUCT_CALL_RETURN, TailCallMode::kDisallow); } bool HOptimizedGraphBuilder::TryInlineGetter(Handle getter, Handle receiver_map, BailoutId ast_id, BailoutId return_id) { if (TryInlineApiGetter(getter, receiver_map, ast_id)) return true; if (getter->IsJSFunction()) { Handle getter_function = Handle::cast(getter); return TryInlineBuiltinGetterCall(getter_function, receiver_map, ast_id) || TryInline(getter_function, 0, NULL, ast_id, return_id, GETTER_CALL_RETURN, TailCallMode::kDisallow); } return false; } bool HOptimizedGraphBuilder::TryInlineSetter(Handle setter, Handle receiver_map, BailoutId id, BailoutId assignment_id, HValue* implicit_return_value) { if (TryInlineApiSetter(setter, receiver_map, id)) return true; return setter->IsJSFunction() && TryInline(Handle::cast(setter), 1, implicit_return_value, id, assignment_id, SETTER_CALL_RETURN, TailCallMode::kDisallow); } bool HOptimizedGraphBuilder::TryInlineIndirectCall(Handle function, Call* expr, int arguments_count) { return TryInline(function, arguments_count, NULL, expr->id(), expr->ReturnId(), NORMAL_RETURN, expr->tail_call_mode()); } bool HOptimizedGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr) { if (!expr->target()->shared()->HasBuiltinFunctionId()) return false; BuiltinFunctionId id = expr->target()->shared()->builtin_function_id(); // We intentionally ignore expr->tail_call_mode() here because builtins // we inline here do not observe if they were tail called or not. switch (id) { case kMathCos: case kMathExp: case kMathRound: case kMathFround: case kMathFloor: case kMathAbs: case kMathSin: case kMathSqrt: case kMathLog: case kMathClz32: if (expr->arguments()->length() == 1) { HValue* argument = Pop(); Drop(2); // Receiver and function. HInstruction* op = NewUncasted(argument, id); ast_context()->ReturnInstruction(op, expr->id()); return true; } break; case kMathImul: if (expr->arguments()->length() == 2) { HValue* right = Pop(); HValue* left = Pop(); Drop(2); // Receiver and function. HInstruction* op = HMul::NewImul(isolate(), zone(), context(), left, right); ast_context()->ReturnInstruction(op, expr->id()); return true; } break; default: // Not supported for inlining yet. break; } return false; } // static bool HOptimizedGraphBuilder::IsReadOnlyLengthDescriptor( Handle jsarray_map) { DCHECK(!jsarray_map->is_dictionary_map()); Isolate* isolate = jsarray_map->GetIsolate(); Handle length_string = isolate->factory()->length_string(); DescriptorArray* descriptors = jsarray_map->instance_descriptors(); int number = descriptors->SearchWithCache(isolate, *length_string, *jsarray_map); DCHECK_NE(DescriptorArray::kNotFound, number); return descriptors->GetDetails(number).IsReadOnly(); } // static bool HOptimizedGraphBuilder::CanInlineArrayResizeOperation( Handle receiver_map) { return !receiver_map.is_null() && receiver_map->prototype()->IsJSObject() && receiver_map->instance_type() == JS_ARRAY_TYPE && IsFastElementsKind(receiver_map->elements_kind()) && !receiver_map->is_dictionary_map() && receiver_map->is_extensible() && (!receiver_map->is_prototype_map() || receiver_map->is_stable()) && !IsReadOnlyLengthDescriptor(receiver_map); } bool HOptimizedGraphBuilder::TryInlineBuiltinGetterCall( Handle function, Handle receiver_map, BailoutId ast_id) { if (!function->shared()->HasBuiltinFunctionId()) return false; BuiltinFunctionId id = function->shared()->builtin_function_id(); // Try to inline getter calls like DataView.prototype.byteLength/byteOffset // as operations in the calling function. switch (id) { case kDataViewBuffer: { if (!receiver_map->IsJSDataViewMap()) return false; HObjectAccess access = HObjectAccess::ForMapAndOffset( receiver_map, JSDataView::kBufferOffset); HValue* object = Pop(); // receiver HInstruction* result = New(object, object, access); ast_context()->ReturnInstruction(result, ast_id); return true; } case kDataViewByteLength: case kDataViewByteOffset: { if (!receiver_map->IsJSDataViewMap()) return false; int offset = (id == kDataViewByteLength) ? JSDataView::kByteLengthOffset : JSDataView::kByteOffsetOffset; HObjectAccess access = HObjectAccess::ForMapAndOffset(receiver_map, offset); HValue* object = Pop(); // receiver HValue* checked_object = Add(object); HInstruction* result = New(object, checked_object, access); ast_context()->ReturnInstruction(result, ast_id); return true; } case kTypedArrayByteLength: case kTypedArrayByteOffset: case kTypedArrayLength: { if (!receiver_map->IsJSTypedArrayMap()) return false; int offset = (id == kTypedArrayLength) ? JSTypedArray::kLengthOffset : (id == kTypedArrayByteLength) ? JSTypedArray::kByteLengthOffset : JSTypedArray::kByteOffsetOffset; HObjectAccess access = HObjectAccess::ForMapAndOffset(receiver_map, offset); HValue* object = Pop(); // receiver HValue* checked_object = Add(object); HInstruction* result = New(object, checked_object, access); ast_context()->ReturnInstruction(result, ast_id); return true; } default: return false; } } // static bool HOptimizedGraphBuilder::NoElementsInPrototypeChain( Handle receiver_map) { // TODO(ishell): remove this once we support NO_ELEMENTS elements kind. PrototypeIterator iter(receiver_map); Handle empty_fixed_array = iter.isolate()->factory()->empty_fixed_array(); while (true) { Handle current = PrototypeIterator::GetCurrent(iter); if (current->elements() != *empty_fixed_array) return false; iter.Advance(); if (iter.IsAtEnd()) { return true; } } } bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall( Handle function, Handle receiver_map, BailoutId ast_id, int args_count_no_receiver) { if (!function->shared()->HasBuiltinFunctionId()) return false; BuiltinFunctionId id = function->shared()->builtin_function_id(); int argument_count = args_count_no_receiver + 1; // Plus receiver. if (receiver_map.is_null()) { HValue* receiver = environment()->ExpressionStackAt(args_count_no_receiver); if (receiver->IsConstant() && HConstant::cast(receiver)->handle(isolate())->IsHeapObject()) { receiver_map = handle(Handle::cast( HConstant::cast(receiver)->handle(isolate()))->map()); } } // Try to inline calls like Math.* as operations in the calling function. switch (id) { case kObjectHasOwnProperty: { // It's not safe to look through the phi for elements if we're compiling // for osr. if (top_info()->is_osr()) return false; if (argument_count != 2) return false; HValue* key = Top(); if (!key->IsLoadKeyed()) return false; HValue* elements = HLoadKeyed::cast(key)->elements(); if (!elements->IsPhi() || elements->OperandCount() != 1) return false; if (!elements->OperandAt(0)->IsForInCacheArray()) return false; HForInCacheArray* cache = HForInCacheArray::cast(elements->OperandAt(0)); HValue* receiver = environment()->ExpressionStackAt(1); if (!receiver->IsPhi() || receiver->OperandCount() != 1) return false; if (cache->enumerable() != receiver->OperandAt(0)) return false; Drop(3); // key, receiver, function Add(receiver, cache->map()); ast_context()->ReturnValue(graph()->GetConstantTrue()); return true; } case kStringCharCodeAt: case kStringCharAt: if (argument_count == 2) { HValue* index = Pop(); HValue* string = Pop(); Drop(1); // Function. HInstruction* char_code = BuildStringCharCodeAt(string, index); if (id == kStringCharCodeAt) { ast_context()->ReturnInstruction(char_code, ast_id); return true; } AddInstruction(char_code); HInstruction* result = NewUncasted(char_code); ast_context()->ReturnInstruction(result, ast_id); return true; } break; case kStringFromCharCode: if (argument_count == 2) { HValue* argument = Pop(); Drop(2); // Receiver and function. argument = AddUncasted( argument, Representation::Integer32()); argument->SetFlag(HValue::kTruncatingToInt32); HInstruction* result = NewUncasted(argument); ast_context()->ReturnInstruction(result, ast_id); return true; } break; case kMathCos: case kMathExp: case kMathRound: case kMathFround: case kMathFloor: case kMathAbs: case kMathSin: case kMathSqrt: case kMathLog: case kMathClz32: if (argument_count == 2) { HValue* argument = Pop(); Drop(2); // Receiver and function. HInstruction* op = NewUncasted(argument, id); ast_context()->ReturnInstruction(op, ast_id); return true; } break; case kMathPow: if (argument_count == 3) { HValue* right = Pop(); HValue* left = Pop(); Drop(2); // Receiver and function. HInstruction* result = NULL; // Use sqrt() if exponent is 0.5 or -0.5. if (right->IsConstant() && HConstant::cast(right)->HasDoubleValue()) { double exponent = HConstant::cast(right)->DoubleValue(); if (exponent == 0.5) { result = NewUncasted(left, kMathPowHalf); } else if (exponent == -0.5) { HValue* one = graph()->GetConstant1(); HInstruction* sqrt = AddUncasted( left, kMathPowHalf); // MathPowHalf doesn't have side effects so there's no need for // an environment simulation here. DCHECK(!sqrt->HasObservableSideEffects()); result = NewUncasted(one, sqrt); } else if (exponent == 2.0) { result = NewUncasted(left, left); } } if (result == NULL) { result = NewUncasted(left, right); } ast_context()->ReturnInstruction(result, ast_id); return true; } break; case kMathMax: case kMathMin: if (argument_count == 3) { HValue* right = Pop(); HValue* left = Pop(); Drop(2); // Receiver and function. HMathMinMax::Operation op = (id == kMathMin) ? HMathMinMax::kMathMin : HMathMinMax::kMathMax; HInstruction* result = NewUncasted(left, right, op); ast_context()->ReturnInstruction(result, ast_id); return true; } break; case kMathImul: if (argument_count == 3) { HValue* right = Pop(); HValue* left = Pop(); Drop(2); // Receiver and function. HInstruction* result = HMul::NewImul(isolate(), zone(), context(), left, right); ast_context()->ReturnInstruction(result, ast_id); return true; } break; case kArrayPop: { if (!CanInlineArrayResizeOperation(receiver_map)) return false; ElementsKind elements_kind = receiver_map->elements_kind(); Drop(args_count_no_receiver); HValue* result; HValue* reduced_length; HValue* receiver = Pop(); HValue* checked_object = AddCheckMap(receiver, receiver_map); HValue* length = Add(checked_object, nullptr, HObjectAccess::ForArrayLength(elements_kind)); Drop(1); // Function. { NoObservableSideEffectsScope scope(this); IfBuilder length_checker(this); HValue* bounds_check = length_checker.If( length, graph()->GetConstant0(), Token::EQ); length_checker.Then(); if (!ast_context()->IsEffect()) Push(graph()->GetConstantUndefined()); length_checker.Else(); HValue* elements = AddLoadElements(checked_object); // Ensure that we aren't popping from a copy-on-write array. if (IsFastSmiOrObjectElementsKind(elements_kind)) { elements = BuildCopyElementsOnWrite(checked_object, elements, elements_kind, length); } reduced_length = AddUncasted(length, graph()->GetConstant1()); result = AddElementAccess(elements, reduced_length, nullptr, bounds_check, nullptr, elements_kind, LOAD); HValue* hole = IsFastSmiOrObjectElementsKind(elements_kind) ? graph()->GetConstantHole() : Add(HConstant::kHoleNaN); if (IsFastSmiOrObjectElementsKind(elements_kind)) { elements_kind = FAST_HOLEY_ELEMENTS; } AddElementAccess(elements, reduced_length, hole, bounds_check, nullptr, elements_kind, STORE); Add( checked_object, HObjectAccess::ForArrayLength(elements_kind), reduced_length, STORE_TO_INITIALIZED_ENTRY); if (!ast_context()->IsEffect()) Push(result); length_checker.End(); } result = ast_context()->IsEffect() ? graph()->GetConstant0() : Top(); Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); ast_context()->ReturnValue(result); return true; } case kArrayPush: { if (!CanInlineArrayResizeOperation(receiver_map)) return false; ElementsKind elements_kind = receiver_map->elements_kind(); // If there may be elements accessors in the prototype chain, the fast // inlined version can't be used. if (receiver_map->DictionaryElementsInPrototypeChainOnly()) return false; // If there currently can be no elements accessors on the prototype chain, // it doesn't mean that there won't be any later. Install a full prototype // chain check to trap element accessors being installed on the prototype // chain, which would cause elements to go to dictionary mode and result // in a map change. Handle prototype(JSObject::cast(receiver_map->prototype())); BuildCheckPrototypeMaps(prototype, Handle()); // Protect against adding elements to the Array prototype, which needs to // route through appropriate bottlenecks. if (isolate()->IsFastArrayConstructorPrototypeChainIntact() && !prototype->IsJSArray()) { return false; } const int argc = args_count_no_receiver; if (argc != 1) return false; HValue* value_to_push = Pop(); HValue* array = Pop(); Drop(1); // Drop function. HInstruction* new_size = NULL; HValue* length = NULL; { NoObservableSideEffectsScope scope(this); length = Add( array, nullptr, HObjectAccess::ForArrayLength(elements_kind)); new_size = AddUncasted(length, graph()->GetConstant1()); bool is_array = receiver_map->instance_type() == JS_ARRAY_TYPE; HValue* checked_array = Add(array, receiver_map); BuildUncheckedMonomorphicElementAccess( checked_array, length, value_to_push, is_array, elements_kind, STORE, NEVER_RETURN_HOLE, STORE_AND_GROW_NO_TRANSITION); if (!ast_context()->IsEffect()) Push(new_size); Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); } ast_context()->ReturnValue(new_size); return true; } case kArrayShift: { if (!CanInlineArrayResizeOperation(receiver_map)) return false; if (!NoElementsInPrototypeChain(receiver_map)) return false; ElementsKind kind = receiver_map->elements_kind(); // If there may be elements accessors in the prototype chain, the fast // inlined version can't be used. if (receiver_map->DictionaryElementsInPrototypeChainOnly()) return false; // If there currently can be no elements accessors on the prototype chain, // it doesn't mean that there won't be any later. Install a full prototype // chain check to trap element accessors being installed on the prototype // chain, which would cause elements to go to dictionary mode and result // in a map change. BuildCheckPrototypeMaps( handle(JSObject::cast(receiver_map->prototype()), isolate()), Handle::null(), true); // Threshold for fast inlined Array.shift(). HConstant* inline_threshold = Add(static_cast(16)); Drop(args_count_no_receiver); HValue* result; HValue* receiver = Pop(); HValue* checked_object = AddCheckMap(receiver, receiver_map); HValue* length = Add( receiver, checked_object, HObjectAccess::ForArrayLength(kind)); Drop(1); // Function. { NoObservableSideEffectsScope scope(this); IfBuilder if_lengthiszero(this); HValue* lengthiszero = if_lengthiszero.If( length, graph()->GetConstant0(), Token::EQ); if_lengthiszero.Then(); { if (!ast_context()->IsEffect()) Push(graph()->GetConstantUndefined()); } if_lengthiszero.Else(); { HValue* elements = AddLoadElements(receiver); // Check if we can use the fast inlined Array.shift(). IfBuilder if_inline(this); if_inline.If( length, inline_threshold, Token::LTE); if (IsFastSmiOrObjectElementsKind(kind)) { // We cannot handle copy-on-write backing stores here. if_inline.AndIf( elements, isolate()->factory()->fixed_array_map()); } if_inline.Then(); { // Remember the result. if (!ast_context()->IsEffect()) { Push(AddElementAccess(elements, graph()->GetConstant0(), nullptr, lengthiszero, nullptr, kind, LOAD)); } // Compute the new length. HValue* new_length = AddUncasted( length, graph()->GetConstant1()); new_length->ClearFlag(HValue::kCanOverflow); // Copy the remaining elements. LoopBuilder loop(this, context(), LoopBuilder::kPostIncrement); { HValue* new_key = loop.BeginBody( graph()->GetConstant0(), new_length, Token::LT); HValue* key = AddUncasted(new_key, graph()->GetConstant1()); key->ClearFlag(HValue::kCanOverflow); ElementsKind copy_kind = kind == FAST_HOLEY_SMI_ELEMENTS ? FAST_HOLEY_ELEMENTS : kind; HValue* element = AddUncasted(elements, key, lengthiszero, nullptr, copy_kind, ALLOW_RETURN_HOLE); HStoreKeyed* store = Add(elements, new_key, element, nullptr, copy_kind); store->SetFlag(HValue::kTruncatingToNumber); } loop.EndBody(); // Put a hole at the end. HValue* hole = IsFastSmiOrObjectElementsKind(kind) ? graph()->GetConstantHole() : Add(HConstant::kHoleNaN); if (IsFastSmiOrObjectElementsKind(kind)) kind = FAST_HOLEY_ELEMENTS; Add(elements, new_length, hole, nullptr, kind, INITIALIZING_STORE); // Remember new length. Add( receiver, HObjectAccess::ForArrayLength(kind), new_length, STORE_TO_INITIALIZED_ENTRY); } if_inline.Else(); { Add(receiver); result = AddInstruction(NewCallConstantFunction( function, 1, TailCallMode::kDisallow, TailCallMode::kDisallow)); if (!ast_context()->IsEffect()) Push(result); } if_inline.End(); } if_lengthiszero.End(); } result = ast_context()->IsEffect() ? graph()->GetConstant0() : Top(); Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); ast_context()->ReturnValue(result); return true; } case kArrayIndexOf: case kArrayLastIndexOf: { if (receiver_map.is_null()) return false; if (receiver_map->instance_type() != JS_ARRAY_TYPE) return false; if (!receiver_map->prototype()->IsJSObject()) return false; ElementsKind kind = receiver_map->elements_kind(); if (!IsFastElementsKind(kind)) return false; if (argument_count != 2) return false; if (!receiver_map->is_extensible()) return false; // If there may be elements accessors in the prototype chain, the fast // inlined version can't be used. if (receiver_map->DictionaryElementsInPrototypeChainOnly()) return false; // If there currently can be no elements accessors on the prototype chain, // it doesn't mean that there won't be any later. Install a full prototype // chain check to trap element accessors being installed on the prototype // chain, which would cause elements to go to dictionary mode and result // in a map change. BuildCheckPrototypeMaps( handle(JSObject::cast(receiver_map->prototype()), isolate()), Handle::null()); HValue* search_element = Pop(); HValue* receiver = Pop(); Drop(1); // Drop function. ArrayIndexOfMode mode = (id == kArrayIndexOf) ? kFirstIndexOf : kLastIndexOf; HValue* index = BuildArrayIndexOf(receiver, search_element, kind, mode); if (!ast_context()->IsEffect()) Push(index); Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); ast_context()->ReturnValue(index); return true; } default: // Not yet supported for inlining. break; } return false; } bool HOptimizedGraphBuilder::TryInlineApiFunctionCall(Call* expr, HValue* receiver) { if (V8_UNLIKELY(FLAG_runtime_stats)) return false; Handle function = expr->target(); int argc = expr->arguments()->length(); SmallMapList receiver_maps; return TryInlineApiCall(function, receiver, &receiver_maps, argc, expr->id(), kCallApiFunction, expr->tail_call_mode()); } bool HOptimizedGraphBuilder::TryInlineApiMethodCall( Call* expr, HValue* receiver, SmallMapList* receiver_maps) { if (V8_UNLIKELY(FLAG_runtime_stats)) return false; Handle function = expr->target(); int argc = expr->arguments()->length(); return TryInlineApiCall(function, receiver, receiver_maps, argc, expr->id(), kCallApiMethod, expr->tail_call_mode()); } bool HOptimizedGraphBuilder::TryInlineApiGetter(Handle function, Handle receiver_map, BailoutId ast_id) { if (V8_UNLIKELY(FLAG_runtime_stats)) return false; SmallMapList receiver_maps(1, zone()); receiver_maps.Add(receiver_map, zone()); return TryInlineApiCall(function, NULL, // Receiver is on expression stack. &receiver_maps, 0, ast_id, kCallApiGetter, TailCallMode::kDisallow); } bool HOptimizedGraphBuilder::TryInlineApiSetter(Handle function, Handle receiver_map, BailoutId ast_id) { SmallMapList receiver_maps(1, zone()); receiver_maps.Add(receiver_map, zone()); return TryInlineApiCall(function, NULL, // Receiver is on expression stack. &receiver_maps, 1, ast_id, kCallApiSetter, TailCallMode::kDisallow); } bool HOptimizedGraphBuilder::TryInlineApiCall( Handle function, HValue* receiver, SmallMapList* receiver_maps, int argc, BailoutId ast_id, ApiCallType call_type, TailCallMode syntactic_tail_call_mode) { if (V8_UNLIKELY(FLAG_runtime_stats)) return false; if (function->IsJSFunction() && Handle::cast(function)->context()->native_context() != top_info()->closure()->context()->native_context()) { return false; } if (argc > CallApiCallbackStub::kArgMax) { return false; } CallOptimization optimization(function); if (!optimization.is_simple_api_call()) return false; Handle holder_map; for (int i = 0; i < receiver_maps->length(); ++i) { auto map = receiver_maps->at(i); // Don't inline calls to receivers requiring accesschecks. if (map->is_access_check_needed()) return false; } if (call_type == kCallApiFunction) { // Cannot embed a direct reference to the global proxy map // as it maybe dropped on deserialization. CHECK(!isolate()->serializer_enabled()); DCHECK(function->IsJSFunction()); DCHECK_EQ(0, receiver_maps->length()); receiver_maps->Add( handle(Handle::cast(function)->global_proxy()->map()), zone()); } CallOptimization::HolderLookup holder_lookup = CallOptimization::kHolderNotFound; Handle api_holder = optimization.LookupHolderOfExpectedType( receiver_maps->first(), &holder_lookup); if (holder_lookup == CallOptimization::kHolderNotFound) return false; if (FLAG_trace_inlining) { PrintF("Inlining api function "); function->ShortPrint(); PrintF("\n"); } bool is_function = false; bool is_store = false; switch (call_type) { case kCallApiFunction: case kCallApiMethod: // Need to check that none of the receiver maps could have changed. Add(receiver, receiver_maps); // Need to ensure the chain between receiver and api_holder is intact. if (holder_lookup == CallOptimization::kHolderFound) { AddCheckPrototypeMaps(api_holder, receiver_maps->first()); } else { DCHECK_EQ(holder_lookup, CallOptimization::kHolderIsReceiver); } // Includes receiver. PushArgumentsFromEnvironment(argc + 1); is_function = true; break; case kCallApiGetter: // Receiver and prototype chain cannot have changed. DCHECK_EQ(0, argc); DCHECK_NULL(receiver); // Receiver is on expression stack. receiver = Pop(); Add(receiver); break; case kCallApiSetter: { is_store = true; // Receiver and prototype chain cannot have changed. DCHECK_EQ(1, argc); DCHECK_NULL(receiver); // Receiver and value are on expression stack. HValue* value = Pop(); receiver = Pop(); Add(receiver, value); break; } } HValue* holder = NULL; switch (holder_lookup) { case CallOptimization::kHolderFound: holder = Add(api_holder); break; case CallOptimization::kHolderIsReceiver: holder = receiver; break; case CallOptimization::kHolderNotFound: UNREACHABLE(); break; } Handle api_call_info = optimization.api_call_info(); Handle call_data_obj(api_call_info->data(), isolate()); bool call_data_undefined = call_data_obj->IsUndefined(isolate()); HValue* call_data = Add(call_data_obj); ApiFunction fun(v8::ToCData
(api_call_info->callback())); ExternalReference ref = ExternalReference(&fun, ExternalReference::DIRECT_API_CALL, isolate()); HValue* api_function_address = Add(ExternalReference(ref)); HValue* op_vals[] = {Add(function), call_data, holder, api_function_address}; HInstruction* call = nullptr; CHECK(argc <= CallApiCallbackStub::kArgMax); if (!is_function) { CallApiCallbackStub stub(isolate(), is_store, call_data_undefined, !optimization.is_constant_call()); Handle code = stub.GetCode(); HConstant* code_value = Add(code); call = New( code_value, argc + 1, stub.GetCallInterfaceDescriptor(), Vector(op_vals, arraysize(op_vals)), syntactic_tail_call_mode); } else { CallApiCallbackStub stub(isolate(), argc, call_data_undefined, false); Handle code = stub.GetCode(); HConstant* code_value = Add(code); call = New( code_value, argc + 1, stub.GetCallInterfaceDescriptor(), Vector(op_vals, arraysize(op_vals)), syntactic_tail_call_mode); Drop(1); // Drop function. } ast_context()->ReturnInstruction(call, ast_id); return true; } void HOptimizedGraphBuilder::HandleIndirectCall(Call* expr, HValue* function, int arguments_count) { Handle known_function; int args_count_no_receiver = arguments_count - 1; if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { known_function = Handle::cast(HConstant::cast(function)->handle(isolate())); if (TryInlineBuiltinMethodCall(known_function, Handle(), expr->id(), args_count_no_receiver)) { if (FLAG_trace_inlining) { PrintF("Inlining builtin "); known_function->ShortPrint(); PrintF("\n"); } return; } if (TryInlineIndirectCall(known_function, expr, args_count_no_receiver)) { return; } } TailCallMode syntactic_tail_call_mode = expr->tail_call_mode(); TailCallMode tail_call_mode = function_state()->ComputeTailCallMode(syntactic_tail_call_mode); PushArgumentsFromEnvironment(arguments_count); HInvokeFunction* call = New(function, known_function, arguments_count, syntactic_tail_call_mode, tail_call_mode); Drop(1); // Function ast_context()->ReturnInstruction(call, expr->id()); } bool HOptimizedGraphBuilder::TryIndirectCall(Call* expr) { DCHECK(expr->expression()->IsProperty()); if (!expr->IsMonomorphic()) { return false; } Handle function_map = expr->GetReceiverTypes()->first(); if (function_map->instance_type() != JS_FUNCTION_TYPE || !expr->target()->shared()->HasBuiltinFunctionId()) { return false; } switch (expr->target()->shared()->builtin_function_id()) { case kFunctionCall: { if (expr->arguments()->length() == 0) return false; BuildFunctionCall(expr); return true; } case kFunctionApply: { // For .apply, only the pattern f.apply(receiver, arguments) // is supported. if (!CanBeFunctionApplyArguments(expr)) return false; BuildFunctionApply(expr); return true; } default: { return false; } } UNREACHABLE(); } // f.apply(...) void HOptimizedGraphBuilder::BuildFunctionApply(Call* expr) { ZoneList* args = expr->arguments(); CHECK_ALIVE(VisitForValue(args->at(0))); HValue* receiver = Pop(); // receiver HValue* function = Pop(); // f Drop(1); // apply // Make sure the arguments object is live. VariableProxy* arg_two = args->at(1)->AsVariableProxy(); LookupAndMakeLive(arg_two->var()); Handle function_map = expr->GetReceiverTypes()->first(); HValue* checked_function = AddCheckMap(function, function_map); if (function_state()->outer() == NULL) { TailCallMode syntactic_tail_call_mode = expr->tail_call_mode(); TailCallMode tail_call_mode = function_state()->ComputeTailCallMode(syntactic_tail_call_mode); HInstruction* elements = Add(false); HInstruction* length = Add(elements); HValue* wrapped_receiver = BuildWrapReceiver(receiver, checked_function); HInstruction* result = New( function, wrapped_receiver, length, elements, tail_call_mode); ast_context()->ReturnInstruction(result, expr->id()); } else { // We are inside inlined function and we know exactly what is inside // arguments object. But we need to be able to materialize at deopt. DCHECK_EQ(environment()->arguments_environment()->parameter_count(), function_state()->entry()->arguments_object()->arguments_count()); HArgumentsObject* args = function_state()->entry()->arguments_object(); const ZoneList* arguments_values = args->arguments_values(); int arguments_count = arguments_values->length(); Push(function); Push(BuildWrapReceiver(receiver, checked_function)); for (int i = 1; i < arguments_count; i++) { Push(arguments_values->at(i)); } HandleIndirectCall(expr, function, arguments_count); } } // f.call(...) void HOptimizedGraphBuilder::BuildFunctionCall(Call* expr) { HValue* function = Top(); // f Handle function_map = expr->GetReceiverTypes()->first(); HValue* checked_function = AddCheckMap(function, function_map); // f and call are on the stack in the unoptimized code // during evaluation of the arguments. CHECK_ALIVE(VisitExpressions(expr->arguments())); int args_length = expr->arguments()->length(); int receiver_index = args_length - 1; // Patch the receiver. HValue* receiver = BuildWrapReceiver( environment()->ExpressionStackAt(receiver_index), checked_function); environment()->SetExpressionStackAt(receiver_index, receiver); // Call must not be on the stack from now on. int call_index = args_length + 1; environment()->RemoveExpressionStackAt(call_index); HandleIndirectCall(expr, function, args_length); } HValue* HOptimizedGraphBuilder::ImplicitReceiverFor(HValue* function, Handle target) { SharedFunctionInfo* shared = target->shared(); if (is_sloppy(shared->language_mode()) && !shared->native()) { // Cannot embed a direct reference to the global proxy // as is it dropped on deserialization. CHECK(!isolate()->serializer_enabled()); Handle global_proxy(target->context()->global_proxy()); return Add(global_proxy); } return graph()->GetConstantUndefined(); } HValue* HOptimizedGraphBuilder::BuildArrayIndexOf(HValue* receiver, HValue* search_element, ElementsKind kind, ArrayIndexOfMode mode) { DCHECK(IsFastElementsKind(kind)); NoObservableSideEffectsScope no_effects(this); HValue* elements = AddLoadElements(receiver); HValue* length = AddLoadArrayLength(receiver, kind); HValue* initial; HValue* terminating; Token::Value token; LoopBuilder::Direction direction; if (mode == kFirstIndexOf) { initial = graph()->GetConstant0(); terminating = length; token = Token::LT; direction = LoopBuilder::kPostIncrement; } else { DCHECK_EQ(kLastIndexOf, mode); initial = length; terminating = graph()->GetConstant0(); token = Token::GT; direction = LoopBuilder::kPreDecrement; } Push(graph()->GetConstantMinus1()); if (IsFastDoubleElementsKind(kind) || IsFastSmiElementsKind(kind)) { // Make sure that we can actually compare numbers correctly below, see // https://code.google.com/p/chromium/issues/detail?id=407946 for details. search_element = AddUncasted( search_element, IsFastSmiElementsKind(kind) ? Representation::Smi() : Representation::Double()); LoopBuilder loop(this, context(), direction); { HValue* index = loop.BeginBody(initial, terminating, token); HValue* element = AddUncasted( elements, index, nullptr, nullptr, kind, ALLOW_RETURN_HOLE); IfBuilder if_issame(this); if_issame.If(element, search_element, Token::EQ_STRICT); if_issame.Then(); { Drop(1); Push(index); loop.Break(); } if_issame.End(); } loop.EndBody(); } else { IfBuilder if_isstring(this); if_isstring.If(search_element); if_isstring.Then(); { LoopBuilder loop(this, context(), direction); { HValue* index = loop.BeginBody(initial, terminating, token); HValue* element = AddUncasted( elements, index, nullptr, nullptr, kind, ALLOW_RETURN_HOLE); IfBuilder if_issame(this); if_issame.If(element); if_issame.AndIf( element, search_element, Token::EQ_STRICT); if_issame.Then(); { Drop(1); Push(index); loop.Break(); } if_issame.End(); } loop.EndBody(); } if_isstring.Else(); { IfBuilder if_isnumber(this); if_isnumber.If(search_element); if_isnumber.OrIf( search_element, isolate()->factory()->heap_number_map()); if_isnumber.Then(); { HValue* search_number = AddUncasted(search_element, Representation::Double()); LoopBuilder loop(this, context(), direction); { HValue* index = loop.BeginBody(initial, terminating, token); HValue* element = AddUncasted( elements, index, nullptr, nullptr, kind, ALLOW_RETURN_HOLE); IfBuilder if_element_isnumber(this); if_element_isnumber.If(element); if_element_isnumber.OrIf( element, isolate()->factory()->heap_number_map()); if_element_isnumber.Then(); { HValue* number = AddUncasted(element, Representation::Double()); IfBuilder if_issame(this); if_issame.If( number, search_number, Token::EQ_STRICT); if_issame.Then(); { Drop(1); Push(index); loop.Break(); } if_issame.End(); } if_element_isnumber.End(); } loop.EndBody(); } if_isnumber.Else(); { LoopBuilder loop(this, context(), direction); { HValue* index = loop.BeginBody(initial, terminating, token); HValue* element = AddUncasted( elements, index, nullptr, nullptr, kind, ALLOW_RETURN_HOLE); IfBuilder if_issame(this); if_issame.If( element, search_element); if_issame.Then(); { Drop(1); Push(index); loop.Break(); } if_issame.End(); } loop.EndBody(); } if_isnumber.End(); } if_isstring.End(); } return Pop(); } template bool HOptimizedGraphBuilder::TryHandleArrayCall(T* expr, HValue* function) { if (!array_function().is_identical_to(expr->target())) { return false; } Handle site = expr->allocation_site(); if (site.is_null()) return false; Add(function, array_function()); int arguments_count = expr->arguments()->length(); if (TryInlineArrayCall(expr, arguments_count, site)) return true; HInstruction* call = PreProcessCall(New( function, arguments_count + 1, site->GetElementsKind(), site)); if (expr->IsCall()) Drop(1); ast_context()->ReturnInstruction(call, expr->id()); return true; } bool HOptimizedGraphBuilder::CanBeFunctionApplyArguments(Call* expr) { ZoneList* args = expr->arguments(); if (args->length() != 2) return false; VariableProxy* arg_two = args->at(1)->AsVariableProxy(); if (arg_two == NULL || !arg_two->var()->IsStackAllocated()) return false; HValue* arg_two_value = environment()->Lookup(arg_two->var()); if (!arg_two_value->CheckFlag(HValue::kIsArguments)) return false; DCHECK_NOT_NULL(current_info()->scope()->arguments()); return true; } void HOptimizedGraphBuilder::VisitCall(Call* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!is_tracking_positions()) SetSourcePosition(expr->position()); Expression* callee = expr->expression(); int argument_count = expr->arguments()->length() + 1; // Plus receiver. HInstruction* call = NULL; TailCallMode syntactic_tail_call_mode = expr->tail_call_mode(); TailCallMode tail_call_mode = function_state()->ComputeTailCallMode(syntactic_tail_call_mode); Property* prop = callee->AsProperty(); if (prop != NULL) { CHECK_ALIVE(VisitForValue(prop->obj())); HValue* receiver = Top(); SmallMapList* maps; ComputeReceiverTypes(expr, receiver, &maps, this); if (prop->key()->IsPropertyName() && maps->length() > 0) { Handle name = prop->key()->AsLiteral()->AsPropertyName(); PropertyAccessInfo info(this, LOAD, maps->first(), name); if (!info.CanAccessAsMonomorphic(maps)) { HandlePolymorphicCallNamed(expr, receiver, maps, name); return; } } HValue* key = NULL; if (!prop->key()->IsPropertyName()) { CHECK_ALIVE(VisitForValue(prop->key())); key = Pop(); } CHECK_ALIVE(PushLoad(prop, receiver, key)); HValue* function = Pop(); if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { // Push the function under the receiver. environment()->SetExpressionStackAt(0, function); Push(receiver); Handle known_function = Handle::cast( HConstant::cast(function)->handle(isolate())); expr->set_target(known_function); if (TryIndirectCall(expr)) return; CHECK_ALIVE(VisitExpressions(expr->arguments())); Handle map = maps->length() == 1 ? maps->first() : Handle(); if (TryInlineBuiltinMethodCall(known_function, map, expr->id(), expr->arguments()->length())) { if (FLAG_trace_inlining) { PrintF("Inlining builtin "); known_function->ShortPrint(); PrintF("\n"); } return; } if (TryInlineApiMethodCall(expr, receiver, maps)) return; // Wrap the receiver if necessary. if (NeedsWrapping(maps->first(), known_function)) { // Since HWrapReceiver currently cannot actually wrap numbers and // strings, use the regular call builtin for method calls to wrap // the receiver. // TODO(verwaest): Support creation of value wrappers directly in // HWrapReceiver. call = NewCallFunction( function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNotNullOrUndefined, tail_call_mode); } else if (TryInlineCall(expr)) { return; } else { call = NewCallConstantFunction(known_function, argument_count, syntactic_tail_call_mode, tail_call_mode); } } else { ArgumentsAllowedFlag arguments_flag = ARGUMENTS_NOT_ALLOWED; if (CanBeFunctionApplyArguments(expr) && expr->is_uninitialized()) { // We have to use EAGER deoptimization here because Deoptimizer::SOFT // gets ignored by the always-opt flag, which leads to incorrect code. Add( DeoptimizeReason::kInsufficientTypeFeedbackForCallWithArguments, Deoptimizer::EAGER); arguments_flag = ARGUMENTS_FAKED; } // Push the function under the receiver. environment()->SetExpressionStackAt(0, function); Push(receiver); CHECK_ALIVE(VisitExpressions(expr->arguments(), arguments_flag)); call = NewCallFunction(function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNotNullOrUndefined, tail_call_mode); } PushArgumentsFromEnvironment(argument_count); } else { if (expr->is_possibly_eval()) { return Bailout(kPossibleDirectCallToEval); } // The function is on the stack in the unoptimized code during // evaluation of the arguments. CHECK_ALIVE(VisitForValue(expr->expression())); HValue* function = Top(); if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { Handle constant = HConstant::cast(function)->handle(isolate()); Handle target = Handle::cast(constant); expr->SetKnownGlobalTarget(target); } // Placeholder for the receiver. Push(graph()->GetConstantUndefined()); CHECK_ALIVE(VisitExpressions(expr->arguments())); if (expr->IsMonomorphic() && !IsClassConstructor(expr->target()->shared()->kind())) { Add(function, expr->target()); // Patch the global object on the stack by the expected receiver. HValue* receiver = ImplicitReceiverFor(function, expr->target()); const int receiver_index = argument_count - 1; environment()->SetExpressionStackAt(receiver_index, receiver); if (TryInlineBuiltinFunctionCall(expr)) { if (FLAG_trace_inlining) { PrintF("Inlining builtin "); expr->target()->ShortPrint(); PrintF("\n"); } return; } if (TryInlineApiFunctionCall(expr, receiver)) return; if (TryHandleArrayCall(expr, function)) return; if (TryInlineCall(expr)) return; PushArgumentsFromEnvironment(argument_count); call = NewCallConstantFunction(expr->target(), argument_count, syntactic_tail_call_mode, tail_call_mode); } else { PushArgumentsFromEnvironment(argument_count); if (expr->is_uninitialized()) { // We've never seen this call before, so let's have Crankshaft learn // through the type vector. call = NewCallFunctionViaIC(function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNullOrUndefined, tail_call_mode, expr->CallFeedbackICSlot()); } else { call = NewCallFunction( function, argument_count, syntactic_tail_call_mode, ConvertReceiverMode::kNullOrUndefined, tail_call_mode); } } } Drop(1); // Drop the function. return ast_context()->ReturnInstruction(call, expr->id()); } bool HOptimizedGraphBuilder::TryInlineArrayCall(Expression* expression, int argument_count, Handle site) { Handle caller = current_info()->closure(); Handle target = array_function(); if (!site->CanInlineCall()) { TraceInline(target, caller, "AllocationSite requested no inlining."); return false; } if (argument_count > 1) { TraceInline(target, caller, "Too many arguments to inline."); return false; } int array_length = 0; // Do not inline if the constant length argument is not a smi or outside the // valid range for unrolled loop initialization. if (argument_count == 1) { HValue* argument = Top(); if (!argument->IsConstant()) { TraceInline(target, caller, "Dont inline [new] Array(n) where n isn't constant."); return false; } HConstant* constant_argument = HConstant::cast(argument); if (!constant_argument->HasSmiValue()) { TraceInline(target, caller, "Constant length outside of valid inlining range."); return false; } array_length = constant_argument->Integer32Value(); if (array_length < 0 || array_length > kElementLoopUnrollThreshold) { TraceInline(target, caller, "Constant length outside of valid inlining range."); return false; } } TraceInline(target, caller, NULL); NoObservableSideEffectsScope no_effects(this); // Register on the site for deoptimization if the transition feedback changes. top_info()->dependencies()->AssumeTransitionStable(site); // Build the array. ElementsKind kind = site->GetElementsKind(); HValue* capacity; HValue* length; if (array_length == 0) { STATIC_ASSERT(0 < JSArray::kPreallocatedArrayElements); const int initial_capacity = JSArray::kPreallocatedArrayElements; capacity = Add(initial_capacity); length = graph()->GetConstant0(); } else { length = Top(); capacity = length; kind = GetHoleyElementsKind(kind); } // These HForceRepresentations are because we store these as fields in the // objects we construct, and an int32-to-smi HChange could deopt. Accept // the deopt possibility now, before allocation occurs. length = AddUncasted(length, Representation::Smi()); capacity = AddUncasted(capacity, Representation::Smi()); // Generate size calculation code here in order to make it dominate // the JSArray allocation. HValue* elements_size = BuildCalculateElementsSize(kind, capacity); // Bail out for large objects. HValue* max_size = Add(kMaxRegularHeapObjectSize); Add(elements_size, max_size); // Allocate (dealing with failure appropriately). AllocationSiteMode mode = DONT_TRACK_ALLOCATION_SITE; HAllocate* new_object = AllocateJSArrayObject(mode); // Fill in the fields: map, properties, length. Handle map_constant(isolate()->get_initial_js_array_map(kind)); HValue* map = Add(map_constant); BuildJSArrayHeader(new_object, map, nullptr, // set elements to empty fixed array mode, kind, nullptr, length); // Allocate and initialize the elements. HAllocate* elements = BuildAllocateElements(kind, elements_size); BuildInitializeElementsHeader(elements, kind, capacity); BuildFillElementsWithHole(elements, kind, graph()->GetConstant0(), capacity); // Set the elements. Add(new_object, HObjectAccess::ForElementsPointer(), elements); int args_to_drop = argument_count + (expression->IsCall() ? 2 : 1); Drop(args_to_drop); ast_context()->ReturnValue(new_object); return true; } // Checks whether allocation using the given constructor can be inlined. static bool IsAllocationInlineable(Handle constructor) { return constructor->has_initial_map() && !IsDerivedConstructor(constructor->shared()->kind()) && constructor->initial_map()->instance_type() == JS_OBJECT_TYPE && constructor->initial_map()->instance_size() < HAllocate::kMaxInlineSize; } void HOptimizedGraphBuilder::VisitCallNew(CallNew* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!is_tracking_positions()) SetSourcePosition(expr->position()); int argument_count = expr->arguments()->length() + 1; // Plus constructor. Factory* factory = isolate()->factory(); // The constructor function is on the stack in the unoptimized code // during evaluation of the arguments. CHECK_ALIVE(VisitForValue(expr->expression())); HValue* function = Top(); CHECK_ALIVE(VisitExpressions(expr->arguments())); if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { Handle constant = HConstant::cast(function)->handle(isolate()); expr->SetKnownGlobalTarget(Handle::cast(constant)); } if (FLAG_inline_construct && expr->IsMonomorphic() && IsAllocationInlineable(expr->target())) { Handle constructor = expr->target(); DCHECK( constructor->shared()->construct_stub() == isolate()->builtins()->builtin(Builtins::kJSConstructStubGeneric) || constructor->shared()->construct_stub() == isolate()->builtins()->builtin(Builtins::kJSConstructStubApi)); HValue* check = Add(function, constructor); // Force completion of inobject slack tracking before generating // allocation code to finalize instance size. constructor->CompleteInobjectSlackTrackingIfActive(); // Calculate instance size from initial map of constructor. DCHECK(constructor->has_initial_map()); Handle initial_map(constructor->initial_map()); int instance_size = initial_map->instance_size(); // Allocate an instance of the implicit receiver object. HValue* size_in_bytes = Add(instance_size); HAllocationMode allocation_mode; HAllocate* receiver = BuildAllocate( size_in_bytes, HType::JSObject(), JS_OBJECT_TYPE, allocation_mode); receiver->set_known_initial_map(initial_map); // Initialize map and fields of the newly allocated object. { NoObservableSideEffectsScope no_effects(this); DCHECK(initial_map->instance_type() == JS_OBJECT_TYPE); Add(receiver, HObjectAccess::ForMapAndOffset(initial_map, JSObject::kMapOffset), Add(initial_map)); HValue* empty_fixed_array = Add(factory->empty_fixed_array()); Add(receiver, HObjectAccess::ForMapAndOffset(initial_map, JSObject::kPropertiesOffset), empty_fixed_array); Add(receiver, HObjectAccess::ForMapAndOffset(initial_map, JSObject::kElementsOffset), empty_fixed_array); BuildInitializeInobjectProperties(receiver, initial_map); } // Replace the constructor function with a newly allocated receiver using // the index of the receiver from the top of the expression stack. const int receiver_index = argument_count - 1; DCHECK(environment()->ExpressionStackAt(receiver_index) == function); environment()->SetExpressionStackAt(receiver_index, receiver); if (TryInlineConstruct(expr, receiver)) { // Inlining worked, add a dependency on the initial map to make sure that // this code is deoptimized whenever the initial map of the constructor // changes. top_info()->dependencies()->AssumeInitialMapCantChange(initial_map); return; } // TODO(mstarzinger): For now we remove the previous HAllocate and all // corresponding instructions and instead add HPushArguments for the // arguments in case inlining failed. What we actually should do is for // inlining to try to build a subgraph without mutating the parent graph. HInstruction* instr = current_block()->last(); do { HInstruction* prev_instr = instr->previous(); instr->DeleteAndReplaceWith(NULL); instr = prev_instr; } while (instr != check); environment()->SetExpressionStackAt(receiver_index, function); } else { // The constructor function is both an operand to the instruction and an // argument to the construct call. if (TryHandleArrayCall(expr, function)) return; } HValue* arity = Add(argument_count - 1); HValue* op_vals[] = {function, function, arity}; Callable callable = CodeFactory::Construct(isolate()); HConstant* stub = Add(callable.code()); PushArgumentsFromEnvironment(argument_count); HInstruction* construct = New( stub, argument_count, callable.descriptor(), ArrayVector(op_vals)); return ast_context()->ReturnInstruction(construct, expr->id()); } void HOptimizedGraphBuilder::BuildInitializeInobjectProperties( HValue* receiver, Handle initial_map) { if (initial_map->GetInObjectProperties() != 0) { HConstant* undefined = graph()->GetConstantUndefined(); for (int i = 0; i < initial_map->GetInObjectProperties(); i++) { int property_offset = initial_map->GetInObjectPropertyOffset(i); Add(receiver, HObjectAccess::ForMapAndOffset( initial_map, property_offset), undefined); } } } HValue* HGraphBuilder::BuildAllocateEmptyArrayBuffer(HValue* byte_length) { // We HForceRepresentation here to avoid allocations during an *-to-tagged // HChange that could cause GC while the array buffer object is not fully // initialized. HObjectAccess byte_length_access(HObjectAccess::ForJSArrayBufferByteLength()); byte_length = AddUncasted( byte_length, byte_length_access.representation()); HAllocate* result = BuildAllocate(Add(JSArrayBuffer::kSizeWithInternalFields), HType::JSObject(), JS_ARRAY_BUFFER_TYPE, HAllocationMode()); HValue* native_context = BuildGetNativeContext(); Add( result, HObjectAccess::ForMap(), Add( native_context, nullptr, HObjectAccess::ForContextSlot(Context::ARRAY_BUFFER_MAP_INDEX))); HConstant* empty_fixed_array = Add(isolate()->factory()->empty_fixed_array()); Add( result, HObjectAccess::ForJSArrayOffset(JSArray::kPropertiesOffset), empty_fixed_array); Add( result, HObjectAccess::ForJSArrayOffset(JSArray::kElementsOffset), empty_fixed_array); Add( result, HObjectAccess::ForJSArrayBufferBackingStore().WithRepresentation( Representation::Smi()), graph()->GetConstant0()); Add(result, byte_length_access, byte_length); Add(result, HObjectAccess::ForJSArrayBufferBitFieldSlot(), graph()->GetConstant0()); Add( result, HObjectAccess::ForJSArrayBufferBitField(), Add((1 << JSArrayBuffer::IsExternal::kShift) | (1 << JSArrayBuffer::IsNeuterable::kShift))); for (int field = 0; field < v8::ArrayBuffer::kInternalFieldCount; ++field) { Add( result, HObjectAccess::ForObservableJSObjectOffset( JSArrayBuffer::kSize + field * kPointerSize, Representation::Smi()), graph()->GetConstant0()); } return result; } template void HGraphBuilder::BuildArrayBufferViewInitialization( HValue* obj, HValue* buffer, HValue* byte_offset, HValue* byte_length) { for (int offset = ViewClass::kSize; offset < ViewClass::kSizeWithInternalFields; offset += kPointerSize) { Add(obj, HObjectAccess::ForObservableJSObjectOffset(offset), graph()->GetConstant0()); } Add( obj, HObjectAccess::ForJSArrayBufferViewByteOffset(), byte_offset); Add( obj, HObjectAccess::ForJSArrayBufferViewByteLength(), byte_length); Add(obj, HObjectAccess::ForJSArrayBufferViewBuffer(), buffer); } HValue* HOptimizedGraphBuilder::BuildAllocateExternalElements( ExternalArrayType array_type, bool is_zero_byte_offset, HValue* buffer, HValue* byte_offset, HValue* length) { Handle external_array_map( isolate()->heap()->MapForFixedTypedArray(array_type)); // The HForceRepresentation is to prevent possible deopt on int-smi // conversion after allocation but before the new object fields are set. length = AddUncasted(length, Representation::Smi()); HValue* elements = Add( Add(FixedTypedArrayBase::kHeaderSize), HType::HeapObject(), NOT_TENURED, external_array_map->instance_type(), graph()->GetConstant0()); AddStoreMapConstant(elements, external_array_map); Add(elements, HObjectAccess::ForFixedArrayLength(), length); HValue* backing_store = Add( buffer, nullptr, HObjectAccess::ForJSArrayBufferBackingStore()); HValue* typed_array_start; if (is_zero_byte_offset) { typed_array_start = backing_store; } else { HInstruction* external_pointer = AddUncasted(backing_store, byte_offset); // Arguments are checked prior to call to TypedArrayInitialize, // including byte_offset. external_pointer->ClearFlag(HValue::kCanOverflow); typed_array_start = external_pointer; } Add(elements, HObjectAccess::ForFixedTypedArrayBaseBasePointer(), graph()->GetConstant0()); Add(elements, HObjectAccess::ForFixedTypedArrayBaseExternalPointer(), typed_array_start); return elements; } HValue* HOptimizedGraphBuilder::BuildAllocateFixedTypedArray( ExternalArrayType array_type, size_t element_size, ElementsKind fixed_elements_kind, HValue* byte_length, HValue* length, bool initialize) { STATIC_ASSERT( (FixedTypedArrayBase::kHeaderSize & kObjectAlignmentMask) == 0); HValue* total_size; // if fixed array's elements are not aligned to object's alignment, // we need to align the whole array to object alignment. if (element_size % kObjectAlignment != 0) { total_size = BuildObjectSizeAlignment( byte_length, FixedTypedArrayBase::kHeaderSize); } else { total_size = AddUncasted(byte_length, Add(FixedTypedArrayBase::kHeaderSize)); total_size->ClearFlag(HValue::kCanOverflow); } // The HForceRepresentation is to prevent possible deopt on int-smi // conversion after allocation but before the new object fields are set. length = AddUncasted(length, Representation::Smi()); Handle fixed_typed_array_map( isolate()->heap()->MapForFixedTypedArray(array_type)); HAllocate* elements = Add( total_size, HType::HeapObject(), NOT_TENURED, fixed_typed_array_map->instance_type(), graph()->GetConstant0()); #ifndef V8_HOST_ARCH_64_BIT if (array_type == kExternalFloat64Array) { elements->MakeDoubleAligned(); } #endif AddStoreMapConstant(elements, fixed_typed_array_map); Add(elements, HObjectAccess::ForFixedArrayLength(), length); Add( elements, HObjectAccess::ForFixedTypedArrayBaseBasePointer(), elements); Add( elements, HObjectAccess::ForFixedTypedArrayBaseExternalPointer(), Add(ExternalReference::fixed_typed_array_base_data_offset())); HValue* filler = Add(static_cast(0)); if (initialize) { LoopBuilder builder(this, context(), LoopBuilder::kPostIncrement); HValue* backing_store = AddUncasted( Add(ExternalReference::fixed_typed_array_base_data_offset()), elements, AddOfExternalAndTagged); HValue* key = builder.BeginBody( Add(static_cast(0)), length, Token::LT); Add(backing_store, key, filler, elements, fixed_elements_kind); builder.EndBody(); } return elements; } void HOptimizedGraphBuilder::GenerateTypedArrayInitialize( CallRuntime* expr) { ZoneList* arguments = expr->arguments(); static const int kObjectArg = 0; static const int kArrayIdArg = 1; static const int kBufferArg = 2; static const int kByteOffsetArg = 3; static const int kByteLengthArg = 4; static const int kInitializeArg = 5; static const int kArgsLength = 6; DCHECK(arguments->length() == kArgsLength); CHECK_ALIVE(VisitForValue(arguments->at(kObjectArg))); HValue* obj = Pop(); if (!arguments->at(kArrayIdArg)->IsLiteral()) { // This should never happen in real use, but can happen when fuzzing. // Just bail out. Bailout(kNeedSmiLiteral); return; } Handle value = static_cast(arguments->at(kArrayIdArg))->value(); if (!value->IsSmi()) { // This should never happen in real use, but can happen when fuzzing. // Just bail out. Bailout(kNeedSmiLiteral); return; } int array_id = Smi::cast(*value)->value(); HValue* buffer; if (!arguments->at(kBufferArg)->IsNullLiteral()) { CHECK_ALIVE(VisitForValue(arguments->at(kBufferArg))); buffer = Pop(); } else { buffer = NULL; } HValue* byte_offset; bool is_zero_byte_offset; if (arguments->at(kByteOffsetArg)->IsLiteral() && Smi::kZero == *static_cast(arguments->at(kByteOffsetArg))->value()) { byte_offset = Add(static_cast(0)); is_zero_byte_offset = true; } else { CHECK_ALIVE(VisitForValue(arguments->at(kByteOffsetArg))); byte_offset = Pop(); is_zero_byte_offset = false; DCHECK(buffer != NULL); } CHECK_ALIVE(VisitForValue(arguments->at(kByteLengthArg))); HValue* byte_length = Pop(); CHECK(arguments->at(kInitializeArg)->IsLiteral()); bool initialize = static_cast(arguments->at(kInitializeArg)) ->value() ->BooleanValue(); NoObservableSideEffectsScope scope(this); IfBuilder byte_offset_smi(this); if (!is_zero_byte_offset) { byte_offset_smi.If(byte_offset); byte_offset_smi.Then(); } ExternalArrayType array_type = kExternalInt8Array; // Bogus initialization. size_t element_size = 1; // Bogus initialization. ElementsKind fixed_elements_kind = // Bogus initialization. INT8_ELEMENTS; Runtime::ArrayIdToTypeAndSize(array_id, &array_type, &fixed_elements_kind, &element_size); { // byte_offset is Smi. HValue* allocated_buffer = buffer; if (buffer == NULL) { allocated_buffer = BuildAllocateEmptyArrayBuffer(byte_length); } BuildArrayBufferViewInitialization(obj, allocated_buffer, byte_offset, byte_length); HInstruction* length = AddUncasted(byte_length, Add(static_cast(element_size))); // Callers (in typedarray.js) ensure that length <= %_MaxSmi(). length = AddUncasted(length, Representation::Smi()); Add(obj, HObjectAccess::ForJSTypedArrayLength(), length); HValue* elements; if (buffer != NULL) { elements = BuildAllocateExternalElements( array_type, is_zero_byte_offset, buffer, byte_offset, length); } else { DCHECK(is_zero_byte_offset); elements = BuildAllocateFixedTypedArray(array_type, element_size, fixed_elements_kind, byte_length, length, initialize); } Add( obj, HObjectAccess::ForElementsPointer(), elements); } if (!is_zero_byte_offset) { byte_offset_smi.Else(); { // byte_offset is not Smi. Push(obj); CHECK_ALIVE(VisitForValue(arguments->at(kArrayIdArg))); Push(buffer); Push(byte_offset); Push(byte_length); CHECK_ALIVE(VisitForValue(arguments->at(kInitializeArg))); PushArgumentsFromEnvironment(kArgsLength); Add(expr->function(), kArgsLength); } } byte_offset_smi.End(); } void HOptimizedGraphBuilder::GenerateMaxSmi(CallRuntime* expr) { DCHECK(expr->arguments()->length() == 0); HConstant* max_smi = New(static_cast(Smi::kMaxValue)); return ast_context()->ReturnInstruction(max_smi, expr->id()); } void HOptimizedGraphBuilder::GenerateTypedArrayMaxSizeInHeap( CallRuntime* expr) { DCHECK(expr->arguments()->length() == 0); HConstant* result = New(static_cast( FLAG_typed_array_max_size_in_heap)); return ast_context()->ReturnInstruction(result, expr->id()); } void HOptimizedGraphBuilder::GenerateArrayBufferGetByteLength( CallRuntime* expr) { DCHECK(expr->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(expr->arguments()->at(0))); HValue* buffer = Pop(); HInstruction* result = New( buffer, nullptr, HObjectAccess::ForJSArrayBufferByteLength()); return ast_context()->ReturnInstruction(result, expr->id()); } void HOptimizedGraphBuilder::GenerateArrayBufferViewGetByteLength( CallRuntime* expr) { NoObservableSideEffectsScope scope(this); DCHECK(expr->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(expr->arguments()->at(0))); HValue* view = Pop(); return ast_context()->ReturnValue(BuildArrayBufferViewFieldAccessor( view, nullptr, FieldIndex::ForInObjectOffset(JSArrayBufferView::kByteLengthOffset))); } void HOptimizedGraphBuilder::GenerateArrayBufferViewGetByteOffset( CallRuntime* expr) { NoObservableSideEffectsScope scope(this); DCHECK(expr->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(expr->arguments()->at(0))); HValue* view = Pop(); return ast_context()->ReturnValue(BuildArrayBufferViewFieldAccessor( view, nullptr, FieldIndex::ForInObjectOffset(JSArrayBufferView::kByteOffsetOffset))); } void HOptimizedGraphBuilder::GenerateTypedArrayGetLength( CallRuntime* expr) { NoObservableSideEffectsScope scope(this); DCHECK(expr->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(expr->arguments()->at(0))); HValue* view = Pop(); return ast_context()->ReturnValue(BuildArrayBufferViewFieldAccessor( view, nullptr, FieldIndex::ForInObjectOffset(JSTypedArray::kLengthOffset))); } void HOptimizedGraphBuilder::VisitCallRuntime(CallRuntime* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (expr->is_jsruntime()) { // Crankshaft always specializes to the native context, so we can just grab // the constant function from the current native context and embed that into // the code object. Handle known_function( JSFunction::cast( current_info()->native_context()->get(expr->context_index())), isolate()); // The callee and the receiver both have to be pushed onto the operand stack // before arguments are being evaluated. HConstant* function = Add(known_function); HValue* receiver = ImplicitReceiverFor(function, known_function); Push(function); Push(receiver); int argument_count = expr->arguments()->length() + 1; // Count receiver. CHECK_ALIVE(VisitExpressions(expr->arguments())); PushArgumentsFromEnvironment(argument_count); HInstruction* call = NewCallConstantFunction(known_function, argument_count, TailCallMode::kDisallow, TailCallMode::kDisallow); Drop(1); // Function return ast_context()->ReturnInstruction(call, expr->id()); } const Runtime::Function* function = expr->function(); DCHECK(function != NULL); switch (function->function_id) { #define CALL_INTRINSIC_GENERATOR(Name) \ case Runtime::kInline##Name: \ return Generate##Name(expr); FOR_EACH_HYDROGEN_INTRINSIC(CALL_INTRINSIC_GENERATOR) #undef CALL_INTRINSIC_GENERATOR default: { int argument_count = expr->arguments()->length(); CHECK_ALIVE(VisitExpressions(expr->arguments())); PushArgumentsFromEnvironment(argument_count); HCallRuntime* call = New(function, argument_count); return ast_context()->ReturnInstruction(call, expr->id()); } } } void HOptimizedGraphBuilder::VisitUnaryOperation(UnaryOperation* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); switch (expr->op()) { case Token::DELETE: return VisitDelete(expr); case Token::VOID: return VisitVoid(expr); case Token::TYPEOF: return VisitTypeof(expr); case Token::NOT: return VisitNot(expr); default: UNREACHABLE(); } } void HOptimizedGraphBuilder::VisitDelete(UnaryOperation* expr) { Property* prop = expr->expression()->AsProperty(); VariableProxy* proxy = expr->expression()->AsVariableProxy(); if (prop != NULL) { CHECK_ALIVE(VisitForValue(prop->obj())); CHECK_ALIVE(VisitForValue(prop->key())); HValue* key = Pop(); HValue* obj = Pop(); Add(obj, key); HInstruction* instr = New( Runtime::FunctionForId(is_strict(function_language_mode()) ? Runtime::kDeleteProperty_Strict : Runtime::kDeleteProperty_Sloppy), 2); return ast_context()->ReturnInstruction(instr, expr->id()); } else if (proxy != NULL) { Variable* var = proxy->var(); if (var->IsUnallocated()) { Bailout(kDeleteWithGlobalVariable); } else if (var->IsStackAllocated() || var->IsContextSlot()) { // Result of deleting non-global variables is false. 'this' is not really // a variable, though we implement it as one. The subexpression does not // have side effects. HValue* value = var->is_this() ? graph()->GetConstantTrue() : graph()->GetConstantFalse(); return ast_context()->ReturnValue(value); } else { Bailout(kDeleteWithNonGlobalVariable); } } else { // Result of deleting non-property, non-variable reference is true. // Evaluate the subexpression for side effects. CHECK_ALIVE(VisitForEffect(expr->expression())); return ast_context()->ReturnValue(graph()->GetConstantTrue()); } } void HOptimizedGraphBuilder::VisitVoid(UnaryOperation* expr) { CHECK_ALIVE(VisitForEffect(expr->expression())); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::VisitTypeof(UnaryOperation* expr) { CHECK_ALIVE(VisitForTypeOf(expr->expression())); HValue* value = Pop(); HInstruction* instr = New(value); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitNot(UnaryOperation* expr) { if (ast_context()->IsTest()) { TestContext* context = TestContext::cast(ast_context()); VisitForControl(expr->expression(), context->if_false(), context->if_true()); return; } if (ast_context()->IsEffect()) { VisitForEffect(expr->expression()); return; } DCHECK(ast_context()->IsValue()); HBasicBlock* materialize_false = graph()->CreateBasicBlock(); HBasicBlock* materialize_true = graph()->CreateBasicBlock(); CHECK_BAILOUT(VisitForControl(expr->expression(), materialize_false, materialize_true)); if (materialize_false->HasPredecessor()) { materialize_false->SetJoinId(expr->MaterializeFalseId()); set_current_block(materialize_false); Push(graph()->GetConstantFalse()); } else { materialize_false = NULL; } if (materialize_true->HasPredecessor()) { materialize_true->SetJoinId(expr->MaterializeTrueId()); set_current_block(materialize_true); Push(graph()->GetConstantTrue()); } else { materialize_true = NULL; } HBasicBlock* join = CreateJoin(materialize_false, materialize_true, expr->id()); set_current_block(join); if (join != NULL) return ast_context()->ReturnValue(Pop()); } static Representation RepresentationFor(AstType* type) { DisallowHeapAllocation no_allocation; if (type->Is(AstType::None())) return Representation::None(); if (type->Is(AstType::SignedSmall())) return Representation::Smi(); if (type->Is(AstType::Signed32())) return Representation::Integer32(); if (type->Is(AstType::Number())) return Representation::Double(); return Representation::Tagged(); } HInstruction* HOptimizedGraphBuilder::BuildIncrement(CountOperation* expr) { // The input to the count operation is on top of the expression stack. Representation rep = RepresentationFor(expr->type()); if (rep.IsNone() || rep.IsTagged()) { rep = Representation::Smi(); } // We need an explicit HValue representing ToNumber(input). The // actual HChange instruction we need is (sometimes) added in a later // phase, so it is not available now to be used as an input to HAdd and // as the return value. HInstruction* number_input = AddUncasted(Pop(), rep); if (!rep.IsDouble()) { number_input->SetFlag(HInstruction::kFlexibleRepresentation); number_input->SetFlag(HInstruction::kCannotBeTagged); } Push(number_input); // The addition has no side effects, so we do not need // to simulate the expression stack after this instruction. // Any later failures deopt to the load of the input or earlier. HConstant* delta = (expr->op() == Token::INC) ? graph()->GetConstant1() : graph()->GetConstantMinus1(); HInstruction* instr = AddUncasted(Top(), delta); if (instr->IsAdd()) { HAdd* add = HAdd::cast(instr); add->set_observed_input_representation(1, rep); add->set_observed_input_representation(2, Representation::Smi()); } instr->ClearAllSideEffects(); instr->SetFlag(HInstruction::kCannotBeTagged); return instr; } void HOptimizedGraphBuilder::BuildStoreForEffect( Expression* expr, Property* prop, FeedbackSlot slot, BailoutId ast_id, BailoutId return_id, HValue* object, HValue* key, HValue* value) { EffectContext for_effect(this); Push(object); if (key != NULL) Push(key); Push(value); BuildStore(expr, prop, slot, ast_id, return_id); } void HOptimizedGraphBuilder::VisitCountOperation(CountOperation* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!is_tracking_positions()) SetSourcePosition(expr->position()); Expression* target = expr->expression(); VariableProxy* proxy = target->AsVariableProxy(); Property* prop = target->AsProperty(); if (proxy == NULL && prop == NULL) { return Bailout(kInvalidLhsInCountOperation); } // Match the full code generator stack by simulating an extra stack // element for postfix operations in a non-effect context. The return // value is ToNumber(input). bool returns_original_input = expr->is_postfix() && !ast_context()->IsEffect(); HValue* input = NULL; // ToNumber(original_input). HValue* after = NULL; // The result after incrementing or decrementing. if (proxy != NULL) { Variable* var = proxy->var(); if (var->mode() == CONST) { return Bailout(kNonInitializerAssignmentToConst); } // Argument of the count operation is a variable, not a property. DCHECK(prop == NULL); CHECK_ALIVE(VisitForValue(target)); after = BuildIncrement(expr); input = returns_original_input ? Top() : Pop(); Push(after); switch (var->location()) { case VariableLocation::UNALLOCATED: HandleGlobalVariableAssignment(var, after, expr->CountSlot(), expr->AssignmentId()); break; case VariableLocation::PARAMETER: case VariableLocation::LOCAL: BindIfLive(var, after); break; case VariableLocation::CONTEXT: { HValue* context = BuildContextChainWalk(var); HStoreContextSlot::Mode mode = IsLexicalVariableMode(var->mode()) ? HStoreContextSlot::kCheckDeoptimize : HStoreContextSlot::kNoCheck; HStoreContextSlot* instr = Add(context, var->index(), mode, after); if (instr->HasObservableSideEffects()) { Add(expr->AssignmentId(), REMOVABLE_SIMULATE); } break; } case VariableLocation::LOOKUP: return Bailout(kLookupVariableInCountOperation); case VariableLocation::MODULE: UNREACHABLE(); } Drop(returns_original_input ? 2 : 1); return ast_context()->ReturnValue(expr->is_postfix() ? input : after); } // Argument of the count operation is a property. DCHECK(prop != NULL); if (returns_original_input) Push(graph()->GetConstantUndefined()); CHECK_ALIVE(VisitForValue(prop->obj())); HValue* object = Top(); HValue* key = NULL; if (!prop->key()->IsPropertyName() || prop->IsStringAccess()) { CHECK_ALIVE(VisitForValue(prop->key())); key = Top(); } CHECK_ALIVE(PushLoad(prop, object, key)); after = BuildIncrement(expr); if (returns_original_input) { input = Pop(); // Drop object and key to push it again in the effect context below. Drop(key == NULL ? 1 : 2); environment()->SetExpressionStackAt(0, input); CHECK_ALIVE(BuildStoreForEffect(expr, prop, expr->CountSlot(), expr->id(), expr->AssignmentId(), object, key, after)); return ast_context()->ReturnValue(Pop()); } environment()->SetExpressionStackAt(0, after); return BuildStore(expr, prop, expr->CountSlot(), expr->id(), expr->AssignmentId()); } HInstruction* HOptimizedGraphBuilder::BuildStringCharCodeAt( HValue* string, HValue* index) { if (string->IsConstant() && index->IsConstant()) { HConstant* c_string = HConstant::cast(string); HConstant* c_index = HConstant::cast(index); if (c_string->HasStringValue() && c_index->HasNumberValue()) { int32_t i = c_index->NumberValueAsInteger32(); Handle s = c_string->StringValue(); if (i < 0 || i >= s->length()) { return New(std::numeric_limits::quiet_NaN()); } return New(s->Get(i)); } } string = BuildCheckString(string); index = Add(index, AddLoadStringLength(string)); return New(string, index); } // Checks if the given shift amounts have following forms: // (N1) and (N2) with N1 + N2 = 32; (sa) and (32 - sa). static bool ShiftAmountsAllowReplaceByRotate(HValue* sa, HValue* const32_minus_sa) { if (sa->IsConstant() && const32_minus_sa->IsConstant()) { const HConstant* c1 = HConstant::cast(sa); const HConstant* c2 = HConstant::cast(const32_minus_sa); return c1->HasInteger32Value() && c2->HasInteger32Value() && (c1->Integer32Value() + c2->Integer32Value() == 32); } if (!const32_minus_sa->IsSub()) return false; HSub* sub = HSub::cast(const32_minus_sa); return sub->left()->EqualsInteger32Constant(32) && sub->right() == sa; } // Checks if the left and the right are shift instructions with the oposite // directions that can be replaced by one rotate right instruction or not. // Returns the operand and the shift amount for the rotate instruction in the // former case. bool HGraphBuilder::MatchRotateRight(HValue* left, HValue* right, HValue** operand, HValue** shift_amount) { HShl* shl; HShr* shr; if (left->IsShl() && right->IsShr()) { shl = HShl::cast(left); shr = HShr::cast(right); } else if (left->IsShr() && right->IsShl()) { shl = HShl::cast(right); shr = HShr::cast(left); } else { return false; } if (shl->left() != shr->left()) return false; if (!ShiftAmountsAllowReplaceByRotate(shl->right(), shr->right()) && !ShiftAmountsAllowReplaceByRotate(shr->right(), shl->right())) { return false; } *operand = shr->left(); *shift_amount = shr->right(); return true; } bool CanBeZero(HValue* right) { if (right->IsConstant()) { HConstant* right_const = HConstant::cast(right); if (right_const->HasInteger32Value() && (right_const->Integer32Value() & 0x1f) != 0) { return false; } } return true; } HValue* HGraphBuilder::EnforceNumberType(HValue* number, AstType* expected) { if (expected->Is(AstType::SignedSmall())) { return AddUncasted(number, Representation::Smi()); } if (expected->Is(AstType::Signed32())) { return AddUncasted(number, Representation::Integer32()); } return number; } HValue* HGraphBuilder::TruncateToNumber(HValue* value, AstType** expected) { if (value->IsConstant()) { HConstant* constant = HConstant::cast(value); Maybe number = constant->CopyToTruncatedNumber(isolate(), zone()); if (number.IsJust()) { *expected = AstType::Number(); return AddInstruction(number.FromJust()); } } // We put temporary values on the stack, which don't correspond to anything // in baseline code. Since nothing is observable we avoid recording those // pushes with a NoObservableSideEffectsScope. NoObservableSideEffectsScope no_effects(this); AstType* expected_type = *expected; // Separate the number type from the rest. AstType* expected_obj = AstType::Intersect(expected_type, AstType::NonNumber(), zone()); AstType* expected_number = AstType::Intersect(expected_type, AstType::Number(), zone()); // We expect to get a number. // (We need to check first, since AstType::None->Is(AstType::Any()) == true. if (expected_obj->Is(AstType::None())) { DCHECK(!expected_number->Is(AstType::None())); return value; } if (expected_obj->Is(AstType::Undefined())) { // This is already done by HChange. *expected = AstType::Union(expected_number, AstType::Number(), zone()); return value; } return value; } HValue* HOptimizedGraphBuilder::BuildBinaryOperation( BinaryOperation* expr, HValue* left, HValue* right, PushBeforeSimulateBehavior push_sim_result) { AstType* left_type = bounds_.get(expr->left()).lower; AstType* right_type = bounds_.get(expr->right()).lower; AstType* result_type = bounds_.get(expr).lower; Maybe fixed_right_arg = expr->fixed_right_arg(); Handle allocation_site = expr->allocation_site(); HAllocationMode allocation_mode; if (FLAG_allocation_site_pretenuring && !allocation_site.is_null()) { allocation_mode = HAllocationMode(allocation_site); } HValue* result = HGraphBuilder::BuildBinaryOperation( expr->op(), left, right, left_type, right_type, result_type, fixed_right_arg, allocation_mode, expr->id()); // Add a simulate after instructions with observable side effects, and // after phis, which are the result of BuildBinaryOperation when we // inlined some complex subgraph. if (result->HasObservableSideEffects() || result->IsPhi()) { if (push_sim_result == PUSH_BEFORE_SIMULATE) { Push(result); Add(expr->id(), REMOVABLE_SIMULATE); Drop(1); } else { Add(expr->id(), REMOVABLE_SIMULATE); } } return result; } HValue* HGraphBuilder::BuildBinaryOperation( Token::Value op, HValue* left, HValue* right, AstType* left_type, AstType* right_type, AstType* result_type, Maybe fixed_right_arg, HAllocationMode allocation_mode, BailoutId opt_id) { bool maybe_string_add = false; if (op == Token::ADD) { // If we are adding constant string with something for which we don't have // a feedback yet, assume that it's also going to be a string and don't // generate deopt instructions. if (!left_type->IsInhabited() && right->IsConstant() && HConstant::cast(right)->HasStringValue()) { left_type = AstType::String(); } if (!right_type->IsInhabited() && left->IsConstant() && HConstant::cast(left)->HasStringValue()) { right_type = AstType::String(); } maybe_string_add = (left_type->Maybe(AstType::String()) || left_type->Maybe(AstType::Receiver()) || right_type->Maybe(AstType::String()) || right_type->Maybe(AstType::Receiver())); } Representation left_rep = RepresentationFor(left_type); Representation right_rep = RepresentationFor(right_type); if (!left_type->IsInhabited()) { Add( DeoptimizeReason::kInsufficientTypeFeedbackForLHSOfBinaryOperation, Deoptimizer::SOFT); left_type = AstType::Any(); left_rep = RepresentationFor(left_type); maybe_string_add = op == Token::ADD; } if (!right_type->IsInhabited()) { Add( DeoptimizeReason::kInsufficientTypeFeedbackForRHSOfBinaryOperation, Deoptimizer::SOFT); right_type = AstType::Any(); right_rep = RepresentationFor(right_type); maybe_string_add = op == Token::ADD; } if (!maybe_string_add) { left = TruncateToNumber(left, &left_type); right = TruncateToNumber(right, &right_type); } // Special case for string addition here. if (op == Token::ADD && (left_type->Is(AstType::String()) || right_type->Is(AstType::String()))) { // Validate type feedback for left argument. if (left_type->Is(AstType::String())) { left = BuildCheckString(left); } // Validate type feedback for right argument. if (right_type->Is(AstType::String())) { right = BuildCheckString(right); } // Convert left argument as necessary. if (left_type->Is(AstType::Number())) { DCHECK(right_type->Is(AstType::String())); left = BuildNumberToString(left, left_type); } else if (!left_type->Is(AstType::String())) { DCHECK(right_type->Is(AstType::String())); return AddUncasted( left, right, allocation_mode.GetPretenureMode(), STRING_ADD_CONVERT_LEFT, allocation_mode.feedback_site()); } // Convert right argument as necessary. if (right_type->Is(AstType::Number())) { DCHECK(left_type->Is(AstType::String())); right = BuildNumberToString(right, right_type); } else if (!right_type->Is(AstType::String())) { DCHECK(left_type->Is(AstType::String())); return AddUncasted( left, right, allocation_mode.GetPretenureMode(), STRING_ADD_CONVERT_RIGHT, allocation_mode.feedback_site()); } // Fast paths for empty constant strings. Handle left_string = left->IsConstant() && HConstant::cast(left)->HasStringValue() ? HConstant::cast(left)->StringValue() : Handle(); Handle right_string = right->IsConstant() && HConstant::cast(right)->HasStringValue() ? HConstant::cast(right)->StringValue() : Handle(); if (!left_string.is_null() && left_string->length() == 0) return right; if (!right_string.is_null() && right_string->length() == 0) return left; if (!left_string.is_null() && !right_string.is_null()) { return AddUncasted( left, right, allocation_mode.GetPretenureMode(), STRING_ADD_CHECK_NONE, allocation_mode.feedback_site()); } // Register the dependent code with the allocation site. if (!allocation_mode.feedback_site().is_null()) { DCHECK(!graph()->info()->IsStub()); Handle site(allocation_mode.feedback_site()); top_info()->dependencies()->AssumeTenuringDecision(site); } // Inline the string addition into the stub when creating allocation // mementos to gather allocation site feedback, or if we can statically // infer that we're going to create a cons string. if ((graph()->info()->IsStub() && allocation_mode.CreateAllocationMementos()) || (left->IsConstant() && HConstant::cast(left)->HasStringValue() && HConstant::cast(left)->StringValue()->length() + 1 >= ConsString::kMinLength) || (right->IsConstant() && HConstant::cast(right)->HasStringValue() && HConstant::cast(right)->StringValue()->length() + 1 >= ConsString::kMinLength)) { return BuildStringAdd(left, right, allocation_mode); } // Fallback to using the string add stub. return AddUncasted( left, right, allocation_mode.GetPretenureMode(), STRING_ADD_CHECK_NONE, allocation_mode.feedback_site()); } // Special case for +x here. if (op == Token::MUL) { if (left->EqualsInteger32Constant(1)) { return BuildToNumber(right); } if (right->EqualsInteger32Constant(1)) { return BuildToNumber(left); } } if (graph()->info()->IsStub()) { left = EnforceNumberType(left, left_type); right = EnforceNumberType(right, right_type); } Representation result_rep = RepresentationFor(result_type); bool is_non_primitive = (left_rep.IsTagged() && !left_rep.IsSmi()) || (right_rep.IsTagged() && !right_rep.IsSmi()); HInstruction* instr = NULL; // Only the stub is allowed to call into the runtime, since otherwise we would // inline several instructions (including the two pushes) for every tagged // operation in optimized code, which is more expensive, than a stub call. if (graph()->info()->IsStub() && is_non_primitive) { HValue* values[] = {left, right}; #define GET_STUB(Name) \ do { \ Callable callable = CodeFactory::Name(isolate()); \ HValue* stub = Add(callable.code()); \ instr = AddUncasted(stub, 0, callable.descriptor(), \ ArrayVector(values)); \ } while (false) switch (op) { default: UNREACHABLE(); case Token::ADD: GET_STUB(Add); break; case Token::SUB: GET_STUB(Subtract); break; case Token::MUL: GET_STUB(Multiply); break; case Token::DIV: GET_STUB(Divide); break; case Token::MOD: GET_STUB(Modulus); break; case Token::BIT_OR: GET_STUB(BitwiseOr); break; case Token::BIT_AND: GET_STUB(BitwiseAnd); break; case Token::BIT_XOR: GET_STUB(BitwiseXor); break; case Token::SAR: GET_STUB(ShiftRight); break; case Token::SHR: GET_STUB(ShiftRightLogical); break; case Token::SHL: GET_STUB(ShiftLeft); break; } #undef GET_STUB } else { switch (op) { case Token::ADD: instr = AddUncasted(left, right); break; case Token::SUB: instr = AddUncasted(left, right); break; case Token::MUL: instr = AddUncasted(left, right); break; case Token::MOD: { if (fixed_right_arg.IsJust() && !right->EqualsInteger32Constant(fixed_right_arg.FromJust())) { HConstant* fixed_right = Add(static_cast(fixed_right_arg.FromJust())); IfBuilder if_same(this); if_same.If(right, fixed_right, Token::EQ); if_same.Then(); if_same.ElseDeopt(DeoptimizeReason::kUnexpectedRHSOfBinaryOperation); right = fixed_right; } instr = AddUncasted(left, right); break; } case Token::DIV: instr = AddUncasted(left, right); break; case Token::BIT_XOR: case Token::BIT_AND: instr = AddUncasted(op, left, right); break; case Token::BIT_OR: { HValue *operand, *shift_amount; if (left_type->Is(AstType::Signed32()) && right_type->Is(AstType::Signed32()) && MatchRotateRight(left, right, &operand, &shift_amount)) { instr = AddUncasted(operand, shift_amount); } else { instr = AddUncasted(op, left, right); } break; } case Token::SAR: instr = AddUncasted(left, right); break; case Token::SHR: instr = AddUncasted(left, right); if (instr->IsShr() && CanBeZero(right)) { graph()->RecordUint32Instruction(instr); } break; case Token::SHL: instr = AddUncasted(left, right); break; default: UNREACHABLE(); } } if (instr->IsBinaryOperation()) { HBinaryOperation* binop = HBinaryOperation::cast(instr); binop->set_observed_input_representation(1, left_rep); binop->set_observed_input_representation(2, right_rep); binop->initialize_output_representation(result_rep); if (graph()->info()->IsStub()) { // Stub should not call into stub. instr->SetFlag(HValue::kCannotBeTagged); // And should truncate on HForceRepresentation already. if (left->IsForceRepresentation()) { left->CopyFlag(HValue::kTruncatingToSmi, instr); left->CopyFlag(HValue::kTruncatingToInt32, instr); } if (right->IsForceRepresentation()) { right->CopyFlag(HValue::kTruncatingToSmi, instr); right->CopyFlag(HValue::kTruncatingToInt32, instr); } } } return instr; } // Check for the form (%_ClassOf(foo) === 'BarClass'). static bool IsClassOfTest(CompareOperation* expr) { if (expr->op() != Token::EQ_STRICT) return false; CallRuntime* call = expr->left()->AsCallRuntime(); if (call == NULL) return false; Literal* literal = expr->right()->AsLiteral(); if (literal == NULL) return false; if (!literal->value()->IsString()) return false; if (call->is_jsruntime()) return false; if (call->function()->function_id != Runtime::kInlineClassOf) return false; DCHECK_EQ(call->arguments()->length(), 1); return true; } void HOptimizedGraphBuilder::VisitBinaryOperation(BinaryOperation* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); switch (expr->op()) { case Token::COMMA: return VisitComma(expr); case Token::OR: case Token::AND: return VisitLogicalExpression(expr); default: return VisitArithmeticExpression(expr); } } void HOptimizedGraphBuilder::VisitComma(BinaryOperation* expr) { CHECK_ALIVE(VisitForEffect(expr->left())); // Visit the right subexpression in the same AST context as the entire // expression. Visit(expr->right()); } void HOptimizedGraphBuilder::VisitLogicalExpression(BinaryOperation* expr) { bool is_logical_and = expr->op() == Token::AND; if (ast_context()->IsTest()) { TestContext* context = TestContext::cast(ast_context()); // Translate left subexpression. HBasicBlock* eval_right = graph()->CreateBasicBlock(); if (is_logical_and) { CHECK_BAILOUT(VisitForControl(expr->left(), eval_right, context->if_false())); } else { CHECK_BAILOUT(VisitForControl(expr->left(), context->if_true(), eval_right)); } // Translate right subexpression by visiting it in the same AST // context as the entire expression. CHECK(eval_right->HasPredecessor()); eval_right->SetJoinId(expr->RightId()); set_current_block(eval_right); Visit(expr->right()); } else if (ast_context()->IsValue()) { CHECK_ALIVE(VisitForValue(expr->left())); DCHECK(current_block() != NULL); HValue* left_value = Top(); // Short-circuit left values that always evaluate to the same boolean value. if (expr->left()->ToBooleanIsTrue() || expr->left()->ToBooleanIsFalse()) { // l (evals true) && r -> r // l (evals true) || r -> l // l (evals false) && r -> l // l (evals false) || r -> r if (is_logical_and == expr->left()->ToBooleanIsTrue()) { Drop(1); CHECK_ALIVE(VisitForValue(expr->right())); } return ast_context()->ReturnValue(Pop()); } // We need an extra block to maintain edge-split form. HBasicBlock* empty_block = graph()->CreateBasicBlock(); HBasicBlock* eval_right = graph()->CreateBasicBlock(); ToBooleanHints expected(expr->left()->to_boolean_types()); HBranch* test = is_logical_and ? New(left_value, expected, eval_right, empty_block) : New(left_value, expected, empty_block, eval_right); FinishCurrentBlock(test); set_current_block(eval_right); Drop(1); // Value of the left subexpression. CHECK_BAILOUT(VisitForValue(expr->right())); HBasicBlock* join_block = CreateJoin(empty_block, current_block(), expr->id()); set_current_block(join_block); return ast_context()->ReturnValue(Pop()); } else { DCHECK(ast_context()->IsEffect()); // In an effect context, we don't need the value of the left subexpression, // only its control flow and side effects. We need an extra block to // maintain edge-split form. HBasicBlock* empty_block = graph()->CreateBasicBlock(); HBasicBlock* right_block = graph()->CreateBasicBlock(); if (is_logical_and) { CHECK_BAILOUT(VisitForControl(expr->left(), right_block, empty_block)); } else { CHECK_BAILOUT(VisitForControl(expr->left(), empty_block, right_block)); } // TODO(kmillikin): Find a way to fix this. It's ugly that there are // actually two empty blocks (one here and one inserted by // TestContext::BuildBranch, and that they both have an HSimulate though the // second one is not a merge node, and that we really have no good AST ID to // put on that first HSimulate. // Technically, we should be able to handle the case when one side of // the test is not connected, but this can trip up liveness analysis // if we did not fully connect the test context based on some optimistic // assumption. If such an assumption was violated, we would end up with // an environment with optimized-out values. So we should always // conservatively connect the test context. CHECK(right_block->HasPredecessor()); CHECK(empty_block->HasPredecessor()); empty_block->SetJoinId(expr->id()); right_block->SetJoinId(expr->RightId()); set_current_block(right_block); CHECK_BAILOUT(VisitForEffect(expr->right())); right_block = current_block(); HBasicBlock* join_block = CreateJoin(empty_block, right_block, expr->id()); set_current_block(join_block); // We did not materialize any value in the predecessor environments, // so there is no need to handle it here. } } void HOptimizedGraphBuilder::VisitArithmeticExpression(BinaryOperation* expr) { CHECK_ALIVE(VisitForValue(expr->left())); CHECK_ALIVE(VisitForValue(expr->right())); SetSourcePosition(expr->position()); HValue* right = Pop(); HValue* left = Pop(); HValue* result = BuildBinaryOperation(expr, left, right, ast_context()->IsEffect() ? NO_PUSH_BEFORE_SIMULATE : PUSH_BEFORE_SIMULATE); return ast_context()->ReturnValue(result); } void HOptimizedGraphBuilder::HandleLiteralCompareTypeof(CompareOperation* expr, Expression* sub_expr, Handle check) { CHECK_ALIVE(VisitForTypeOf(sub_expr)); SetSourcePosition(expr->position()); HValue* value = Pop(); HTypeofIsAndBranch* instr = New(value, check); return ast_context()->ReturnControl(instr, expr->id()); } namespace { bool IsLiteralCompareStrict(Isolate* isolate, HValue* left, Token::Value op, HValue* right) { return op == Token::EQ_STRICT && ((left->IsConstant() && !HConstant::cast(left)->handle(isolate)->IsNumber() && !HConstant::cast(left)->handle(isolate)->IsString()) || (right->IsConstant() && !HConstant::cast(right)->handle(isolate)->IsNumber() && !HConstant::cast(right)->handle(isolate)->IsString())); } } // namespace void HOptimizedGraphBuilder::VisitCompareOperation(CompareOperation* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); if (!is_tracking_positions()) SetSourcePosition(expr->position()); // Check for a few fast cases. The AST visiting behavior must be in sync // with the full codegen: We don't push both left and right values onto // the expression stack when one side is a special-case literal. Expression* sub_expr = NULL; Handle check; if (expr->IsLiteralCompareTypeof(&sub_expr, &check)) { return HandleLiteralCompareTypeof(expr, sub_expr, check); } if (expr->IsLiteralCompareUndefined(&sub_expr)) { return HandleLiteralCompareNil(expr, sub_expr, kUndefinedValue); } if (expr->IsLiteralCompareNull(&sub_expr)) { return HandleLiteralCompareNil(expr, sub_expr, kNullValue); } if (IsClassOfTest(expr)) { CallRuntime* call = expr->left()->AsCallRuntime(); DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); Literal* literal = expr->right()->AsLiteral(); Handle rhs = Handle::cast(literal->value()); HClassOfTestAndBranch* instr = New(value, rhs); return ast_context()->ReturnControl(instr, expr->id()); } AstType* left_type = bounds_.get(expr->left()).lower; AstType* right_type = bounds_.get(expr->right()).lower; AstType* combined_type = expr->combined_type(); CHECK_ALIVE(VisitForValue(expr->left())); CHECK_ALIVE(VisitForValue(expr->right())); HValue* right = Pop(); HValue* left = Pop(); Token::Value op = expr->op(); if (IsLiteralCompareStrict(isolate(), left, op, right)) { HCompareObjectEqAndBranch* result = New(left, right); return ast_context()->ReturnControl(result, expr->id()); } if (op == Token::INSTANCEOF) { // Check to see if the rhs of the instanceof is a known function. if (right->IsConstant() && HConstant::cast(right)->handle(isolate())->IsJSFunction()) { Handle function = Handle::cast(HConstant::cast(right)->handle(isolate())); // Make sure that the {function} already has a meaningful initial map // (i.e. we constructed at least one instance using the constructor // {function}), and has an instance as .prototype. if (function->has_initial_map() && !function->map()->has_non_instance_prototype()) { // Lookup @@hasInstance on the {function}. Handle function_map(function->map(), isolate()); PropertyAccessInfo has_instance( this, LOAD, function_map, isolate()->factory()->has_instance_symbol()); // Check if we are using the Function.prototype[@@hasInstance]. if (has_instance.CanAccessMonomorphic() && has_instance.IsDataConstant() && has_instance.constant().is_identical_to( isolate()->function_has_instance())) { // Add appropriate receiver map check and prototype chain // checks to guard the @@hasInstance lookup chain. AddCheckMap(right, function_map); if (has_instance.has_holder()) { Handle prototype( JSObject::cast(has_instance.map()->prototype()), isolate()); BuildCheckPrototypeMaps(prototype, has_instance.holder()); } // Perform the prototype chain walk. Handle initial_map(function->initial_map(), isolate()); top_info()->dependencies()->AssumeInitialMapCantChange(initial_map); HInstruction* prototype = Add(handle(initial_map->prototype(), isolate())); HHasInPrototypeChainAndBranch* result = New(left, prototype); return ast_context()->ReturnControl(result, expr->id()); } } } Callable callable = CodeFactory::InstanceOf(isolate()); HValue* stub = Add(callable.code()); HValue* values[] = {left, right}; HCallWithDescriptor* result = New( stub, 0, callable.descriptor(), ArrayVector(values)); result->set_type(HType::Boolean()); return ast_context()->ReturnInstruction(result, expr->id()); } else if (op == Token::IN) { Callable callable = CodeFactory::HasProperty(isolate()); HValue* stub = Add(callable.code()); HValue* values[] = {left, right}; HInstruction* result = New(stub, 0, callable.descriptor(), Vector(values, arraysize(values))); return ast_context()->ReturnInstruction(result, expr->id()); } PushBeforeSimulateBehavior push_behavior = ast_context()->IsEffect() ? NO_PUSH_BEFORE_SIMULATE : PUSH_BEFORE_SIMULATE; HControlInstruction* compare = BuildCompareInstruction( op, left, right, left_type, right_type, combined_type, ScriptPositionToSourcePosition(expr->left()->position()), ScriptPositionToSourcePosition(expr->right()->position()), push_behavior, expr->id()); if (compare == NULL) return; // Bailed out. return ast_context()->ReturnControl(compare, expr->id()); } HControlInstruction* HOptimizedGraphBuilder::BuildCompareInstruction( Token::Value op, HValue* left, HValue* right, AstType* left_type, AstType* right_type, AstType* combined_type, SourcePosition left_position, SourcePosition right_position, PushBeforeSimulateBehavior push_sim_result, BailoutId bailout_id) { // Cases handled below depend on collected type feedback. They should // soft deoptimize when there is no type feedback. if (!combined_type->IsInhabited()) { Add( DeoptimizeReason:: kInsufficientTypeFeedbackForCombinedTypeOfBinaryOperation, Deoptimizer::SOFT); combined_type = left_type = right_type = AstType::Any(); } Representation left_rep = RepresentationFor(left_type); Representation right_rep = RepresentationFor(right_type); Representation combined_rep = RepresentationFor(combined_type); if (combined_type->Is(AstType::Receiver())) { if (Token::IsEqualityOp(op)) { // HCompareObjectEqAndBranch can only deal with object, so // exclude numbers. if ((left->IsConstant() && HConstant::cast(left)->HasNumberValue()) || (right->IsConstant() && HConstant::cast(right)->HasNumberValue())) { Add( DeoptimizeReason::kTypeMismatchBetweenFeedbackAndConstant, Deoptimizer::SOFT); // The caller expects a branch instruction, so make it happy. return New(graph()->GetConstantTrue()); } if (op == Token::EQ) { // For abstract equality we need to check both sides are receivers. if (combined_type->IsClass()) { Handle map = combined_type->AsClass()->Map(); AddCheckMap(left, map); AddCheckMap(right, map); } else { BuildCheckHeapObject(left); Add(left, HCheckInstanceType::IS_JS_RECEIVER); BuildCheckHeapObject(right); Add(right, HCheckInstanceType::IS_JS_RECEIVER); } } else { // For strict equality we only need to check one side. HValue* operand_to_check = left->block()->block_id() < right->block()->block_id() ? left : right; if (combined_type->IsClass()) { Handle map = combined_type->AsClass()->Map(); AddCheckMap(operand_to_check, map); } else { BuildCheckHeapObject(operand_to_check); Add(operand_to_check, HCheckInstanceType::IS_JS_RECEIVER); } } HCompareObjectEqAndBranch* result = New(left, right); return result; } else { if (combined_type->IsClass()) { // TODO(bmeurer): This is an optimized version of an x < y, x > y, // x <= y or x >= y, where both x and y are spec objects with the // same map. The CompareIC collects this map for us. So if we know // that there's no @@toPrimitive on the map (including the prototype // chain), and both valueOf and toString are the default initial // implementations (on the %ObjectPrototype%), then we can reduce // the comparison to map checks on x and y, because the comparison // will turn into a comparison of "[object CLASS]" to itself (the // default outcome of toString, since valueOf returns a spec object). // This is pretty much adhoc, so in TurboFan we could do a lot better // and inline the interesting parts of ToPrimitive (actually we could // even do that in Crankshaft but we don't want to waste too much // time on this now). DCHECK(Token::IsOrderedRelationalCompareOp(op)); Handle map = combined_type->AsClass()->Map(); PropertyAccessInfo value_of(this, LOAD, map, isolate()->factory()->valueOf_string()); PropertyAccessInfo to_primitive( this, LOAD, map, isolate()->factory()->to_primitive_symbol()); PropertyAccessInfo to_string(this, LOAD, map, isolate()->factory()->toString_string()); PropertyAccessInfo to_string_tag( this, LOAD, map, isolate()->factory()->to_string_tag_symbol()); if (to_primitive.CanAccessMonomorphic() && !to_primitive.IsFound() && to_string_tag.CanAccessMonomorphic() && (!to_string_tag.IsFound() || to_string_tag.IsData() || to_string_tag.IsDataConstant()) && value_of.CanAccessMonomorphic() && value_of.IsDataConstant() && value_of.constant().is_identical_to(isolate()->object_value_of()) && to_string.CanAccessMonomorphic() && to_string.IsDataConstant() && to_string.constant().is_identical_to( isolate()->object_to_string())) { // We depend on the prototype chain to stay the same, because we // also need to deoptimize when someone installs @@toPrimitive // or @@toStringTag somewhere in the prototype chain. Handle prototype(map->prototype(), isolate()); if (prototype->IsJSObject()) { BuildCheckPrototypeMaps(Handle::cast(prototype), Handle::null()); } AddCheckMap(left, map); AddCheckMap(right, map); // The caller expects a branch instruction, so make it happy. return New( graph()->GetConstantBool(op == Token::LTE || op == Token::GTE)); } } Bailout(kUnsupportedNonPrimitiveCompare); return NULL; } } else if (combined_type->Is(AstType::InternalizedString()) && Token::IsEqualityOp(op)) { // If we have a constant argument, it should be consistent with the type // feedback (otherwise we fail assertions in HCompareObjectEqAndBranch). if ((left->IsConstant() && !HConstant::cast(left)->HasInternalizedStringValue()) || (right->IsConstant() && !HConstant::cast(right)->HasInternalizedStringValue())) { Add( DeoptimizeReason::kTypeMismatchBetweenFeedbackAndConstant, Deoptimizer::SOFT); // The caller expects a branch instruction, so make it happy. return New(graph()->GetConstantTrue()); } BuildCheckHeapObject(left); Add(left, HCheckInstanceType::IS_INTERNALIZED_STRING); BuildCheckHeapObject(right); Add(right, HCheckInstanceType::IS_INTERNALIZED_STRING); HCompareObjectEqAndBranch* result = New(left, right); return result; } else if (combined_type->Is(AstType::String())) { BuildCheckHeapObject(left); Add(left, HCheckInstanceType::IS_STRING); BuildCheckHeapObject(right); Add(right, HCheckInstanceType::IS_STRING); HStringCompareAndBranch* result = New(left, right, op); return result; } else if (combined_type->Is(AstType::Boolean())) { AddCheckMap(left, isolate()->factory()->boolean_map()); AddCheckMap(right, isolate()->factory()->boolean_map()); if (Token::IsEqualityOp(op)) { HCompareObjectEqAndBranch* result = New(left, right); return result; } left = Add( left, nullptr, HObjectAccess::ForOddballToNumber(Representation::Smi())); right = Add( right, nullptr, HObjectAccess::ForOddballToNumber(Representation::Smi())); HCompareNumericAndBranch* result = New(left, right, op); return result; } else { if (op == Token::EQ) { if (left->IsConstant() && HConstant::cast(left)->GetInstanceType() == ODDBALL_TYPE && HConstant::cast(left)->IsUndetectable()) { return New(right); } if (right->IsConstant() && HConstant::cast(right)->GetInstanceType() == ODDBALL_TYPE && HConstant::cast(right)->IsUndetectable()) { return New(left); } } if (combined_rep.IsTagged() || combined_rep.IsNone()) { HCompareGeneric* result = Add(left, right, op); result->set_observed_input_representation(1, left_rep); result->set_observed_input_representation(2, right_rep); if (result->HasObservableSideEffects()) { if (push_sim_result == PUSH_BEFORE_SIMULATE) { Push(result); AddSimulate(bailout_id, REMOVABLE_SIMULATE); Drop(1); } else { AddSimulate(bailout_id, REMOVABLE_SIMULATE); } } // TODO(jkummerow): Can we make this more efficient? HBranch* branch = New(result); return branch; } else { HCompareNumericAndBranch* result = New(left, right, op); result->set_observed_input_representation(left_rep, right_rep); return result; } } } void HOptimizedGraphBuilder::HandleLiteralCompareNil(CompareOperation* expr, Expression* sub_expr, NilValue nil) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); DCHECK(expr->op() == Token::EQ || expr->op() == Token::EQ_STRICT); if (!is_tracking_positions()) SetSourcePosition(expr->position()); CHECK_ALIVE(VisitForValue(sub_expr)); HValue* value = Pop(); HControlInstruction* instr; if (expr->op() == Token::EQ_STRICT) { HConstant* nil_constant = nil == kNullValue ? graph()->GetConstantNull() : graph()->GetConstantUndefined(); instr = New(value, nil_constant); } else { DCHECK_EQ(Token::EQ, expr->op()); instr = New(value); } return ast_context()->ReturnControl(instr, expr->id()); } void HOptimizedGraphBuilder::VisitSpread(Spread* expr) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitEmptyParentheses(EmptyParentheses* expr) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitGetIterator(GetIterator* expr) { UNREACHABLE(); } HValue* HOptimizedGraphBuilder::AddThisFunction() { return AddInstruction(BuildThisFunction()); } HInstruction* HOptimizedGraphBuilder::BuildThisFunction() { // If we share optimized code between different closures, the // this-function is not a constant, except inside an inlined body. if (function_state()->outer() != NULL) { return New( function_state()->compilation_info()->closure()); } else { return New(); } } HInstruction* HOptimizedGraphBuilder::BuildFastLiteral( Handle boilerplate_object, AllocationSiteUsageContext* site_context) { NoObservableSideEffectsScope no_effects(this); Handle initial_map(boilerplate_object->map()); InstanceType instance_type = initial_map->instance_type(); DCHECK(instance_type == JS_ARRAY_TYPE || instance_type == JS_OBJECT_TYPE); HType type = instance_type == JS_ARRAY_TYPE ? HType::JSArray() : HType::JSObject(); HValue* object_size_constant = Add(initial_map->instance_size()); PretenureFlag pretenure_flag = NOT_TENURED; Handle top_site(*site_context->top(), isolate()); if (FLAG_allocation_site_pretenuring) { pretenure_flag = top_site->GetPretenureMode(); } Handle current_site(*site_context->current(), isolate()); if (*top_site == *current_site) { // We install a dependency for pretenuring only on the outermost literal. top_info()->dependencies()->AssumeTenuringDecision(top_site); } top_info()->dependencies()->AssumeTransitionStable(current_site); HInstruction* object = Add(object_size_constant, type, pretenure_flag, instance_type, graph()->GetConstant0(), top_site); // If allocation folding reaches kMaxRegularHeapObjectSize the // elements array may not get folded into the object. Hence, we set the // elements pointer to empty fixed array and let store elimination remove // this store in the folding case. HConstant* empty_fixed_array = Add( isolate()->factory()->empty_fixed_array()); Add(object, HObjectAccess::ForElementsPointer(), empty_fixed_array); BuildEmitObjectHeader(boilerplate_object, object); // Similarly to the elements pointer, there is no guarantee that all // property allocations can get folded, so pre-initialize all in-object // properties to a safe value. BuildInitializeInobjectProperties(object, initial_map); Handle elements(boilerplate_object->elements()); int elements_size = (elements->length() > 0 && elements->map() != isolate()->heap()->fixed_cow_array_map()) ? elements->Size() : 0; if (pretenure_flag == TENURED && elements->map() == isolate()->heap()->fixed_cow_array_map() && isolate()->heap()->InNewSpace(*elements)) { // If we would like to pretenure a fixed cow array, we must ensure that the // array is already in old space, otherwise we'll create too many old-to- // new-space pointers (overflowing the store buffer). elements = Handle( isolate()->factory()->CopyAndTenureFixedCOWArray( Handle::cast(elements))); boilerplate_object->set_elements(*elements); } HInstruction* object_elements = NULL; if (elements_size > 0) { HValue* object_elements_size = Add(elements_size); InstanceType instance_type = boilerplate_object->HasFastDoubleElements() ? FIXED_DOUBLE_ARRAY_TYPE : FIXED_ARRAY_TYPE; object_elements = Add(object_elements_size, HType::HeapObject(), pretenure_flag, instance_type, graph()->GetConstant0(), top_site); BuildEmitElements(boilerplate_object, elements, object_elements, site_context); Add(object, HObjectAccess::ForElementsPointer(), object_elements); } else { Handle elements_field = Handle(boilerplate_object->elements(), isolate()); HInstruction* object_elements_cow = Add(elements_field); Add(object, HObjectAccess::ForElementsPointer(), object_elements_cow); } // Copy in-object properties. if (initial_map->NumberOfFields() != 0 || initial_map->unused_property_fields() > 0) { BuildEmitInObjectProperties(boilerplate_object, object, site_context, pretenure_flag); } return object; } void HOptimizedGraphBuilder::BuildEmitObjectHeader( Handle boilerplate_object, HInstruction* object) { DCHECK(boilerplate_object->properties()->length() == 0); Handle boilerplate_object_map(boilerplate_object->map()); AddStoreMapConstant(object, boilerplate_object_map); Handle properties_field = Handle(boilerplate_object->properties(), isolate()); DCHECK(*properties_field == isolate()->heap()->empty_fixed_array()); HInstruction* properties = Add(properties_field); HObjectAccess access = HObjectAccess::ForPropertiesPointer(); Add(object, access, properties); if (boilerplate_object->IsJSArray()) { Handle boilerplate_array = Handle::cast(boilerplate_object); Handle length_field = Handle(boilerplate_array->length(), isolate()); HInstruction* length = Add(length_field); DCHECK(boilerplate_array->length()->IsSmi()); Add(object, HObjectAccess::ForArrayLength( boilerplate_array->GetElementsKind()), length); } } void HOptimizedGraphBuilder::BuildEmitInObjectProperties( Handle boilerplate_object, HInstruction* object, AllocationSiteUsageContext* site_context, PretenureFlag pretenure_flag) { Handle boilerplate_map(boilerplate_object->map()); Handle descriptors(boilerplate_map->instance_descriptors()); int limit = boilerplate_map->NumberOfOwnDescriptors(); int copied_fields = 0; for (int i = 0; i < limit; i++) { PropertyDetails details = descriptors->GetDetails(i); if (details.location() != kField) continue; DCHECK_EQ(kData, details.kind()); copied_fields++; FieldIndex field_index = FieldIndex::ForDescriptor(*boilerplate_map, i); int property_offset = field_index.offset(); Handle name(descriptors->GetKey(i)); // The access for the store depends on the type of the boilerplate. HObjectAccess access = boilerplate_object->IsJSArray() ? HObjectAccess::ForJSArrayOffset(property_offset) : HObjectAccess::ForMapAndOffset(boilerplate_map, property_offset); if (boilerplate_object->IsUnboxedDoubleField(field_index)) { CHECK(!boilerplate_object->IsJSArray()); double value = boilerplate_object->RawFastDoublePropertyAt(field_index); access = access.WithRepresentation(Representation::Double()); Add(object, access, Add(value)); continue; } Handle value(boilerplate_object->RawFastPropertyAt(field_index), isolate()); if (value->IsJSObject()) { Handle value_object = Handle::cast(value); Handle current_site = site_context->EnterNewScope(); HInstruction* result = BuildFastLiteral(value_object, site_context); site_context->ExitScope(current_site, value_object); Add(object, access, result); } else { Representation representation = details.representation(); HInstruction* value_instruction; if (representation.IsDouble()) { // Allocate a HeapNumber box and store the value into it. HValue* heap_number_constant = Add(HeapNumber::kSize); HInstruction* double_box = Add( heap_number_constant, HType::HeapObject(), pretenure_flag, MUTABLE_HEAP_NUMBER_TYPE, graph()->GetConstant0()); AddStoreMapConstant(double_box, isolate()->factory()->mutable_heap_number_map()); // Unwrap the mutable heap number from the boilerplate. HValue* double_value = Add(Handle::cast(value)->value()); Add( double_box, HObjectAccess::ForHeapNumberValue(), double_value); value_instruction = double_box; } else if (representation.IsSmi()) { value_instruction = value->IsUninitialized(isolate()) ? graph()->GetConstant0() : Add(value); // Ensure that value is stored as smi. access = access.WithRepresentation(representation); } else { value_instruction = Add(value); } Add(object, access, value_instruction); } } int inobject_properties = boilerplate_object->map()->GetInObjectProperties(); HInstruction* value_instruction = Add(isolate()->factory()->one_pointer_filler_map()); for (int i = copied_fields; i < inobject_properties; i++) { DCHECK(boilerplate_object->IsJSObject()); int property_offset = boilerplate_object->GetInObjectPropertyOffset(i); HObjectAccess access = HObjectAccess::ForMapAndOffset(boilerplate_map, property_offset); Add(object, access, value_instruction); } } void HOptimizedGraphBuilder::BuildEmitElements( Handle boilerplate_object, Handle elements, HValue* object_elements, AllocationSiteUsageContext* site_context) { ElementsKind kind = boilerplate_object->map()->elements_kind(); int elements_length = elements->length(); HValue* object_elements_length = Add(elements_length); BuildInitializeElementsHeader(object_elements, kind, object_elements_length); // Copy elements backing store content. if (elements->IsFixedDoubleArray()) { BuildEmitFixedDoubleArray(elements, kind, object_elements); } else if (elements->IsFixedArray()) { BuildEmitFixedArray(elements, kind, object_elements, site_context); } else { UNREACHABLE(); } } void HOptimizedGraphBuilder::BuildEmitFixedDoubleArray( Handle elements, ElementsKind kind, HValue* object_elements) { HInstruction* boilerplate_elements = Add(elements); int elements_length = elements->length(); for (int i = 0; i < elements_length; i++) { HValue* key_constant = Add(i); HInstruction* value_instruction = Add(boilerplate_elements, key_constant, nullptr, nullptr, kind, ALLOW_RETURN_HOLE); HInstruction* store = Add(object_elements, key_constant, value_instruction, nullptr, kind); store->SetFlag(HValue::kTruncatingToNumber); } } void HOptimizedGraphBuilder::BuildEmitFixedArray( Handle elements, ElementsKind kind, HValue* object_elements, AllocationSiteUsageContext* site_context) { HInstruction* boilerplate_elements = Add(elements); int elements_length = elements->length(); Handle fast_elements = Handle::cast(elements); for (int i = 0; i < elements_length; i++) { Handle value(fast_elements->get(i), isolate()); HValue* key_constant = Add(i); if (value->IsJSObject()) { Handle value_object = Handle::cast(value); Handle current_site = site_context->EnterNewScope(); HInstruction* result = BuildFastLiteral(value_object, site_context); site_context->ExitScope(current_site, value_object); Add(object_elements, key_constant, result, nullptr, kind); } else { ElementsKind copy_kind = kind == FAST_HOLEY_SMI_ELEMENTS ? FAST_HOLEY_ELEMENTS : kind; HInstruction* value_instruction = Add(boilerplate_elements, key_constant, nullptr, nullptr, copy_kind, ALLOW_RETURN_HOLE); Add(object_elements, key_constant, value_instruction, nullptr, copy_kind); } } } void HOptimizedGraphBuilder::VisitThisFunction(ThisFunction* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); HInstruction* instr = BuildThisFunction(); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitSuperPropertyReference( SuperPropertyReference* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kSuperReference); } void HOptimizedGraphBuilder::VisitSuperCallReference(SuperCallReference* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); return Bailout(kSuperReference); } void HOptimizedGraphBuilder::VisitDeclarations( Declaration::List* declarations) { DCHECK(globals_.is_empty()); AstVisitor::VisitDeclarations(declarations); if (!globals_.is_empty()) { Handle array = isolate()->factory()->NewFixedArray(globals_.length(), TENURED); for (int i = 0; i < globals_.length(); ++i) array->set(i, *globals_.at(i)); int flags = current_info()->GetDeclareGlobalsFlags(); Handle vector(current_feedback_vector(), isolate()); Add(array, flags, vector); globals_.Rewind(0); } } void HOptimizedGraphBuilder::VisitVariableDeclaration( VariableDeclaration* declaration) { VariableProxy* proxy = declaration->proxy(); Variable* variable = proxy->var(); switch (variable->location()) { case VariableLocation::UNALLOCATED: { DCHECK(!variable->binding_needs_init()); globals_.Add(variable->name(), zone()); FeedbackSlot slot = proxy->VariableFeedbackSlot(); DCHECK(!slot.IsInvalid()); globals_.Add(handle(Smi::FromInt(slot.ToInt()), isolate()), zone()); globals_.Add(isolate()->factory()->undefined_value(), zone()); globals_.Add(isolate()->factory()->undefined_value(), zone()); return; } case VariableLocation::PARAMETER: case VariableLocation::LOCAL: if (variable->binding_needs_init()) { HValue* value = graph()->GetConstantHole(); environment()->Bind(variable, value); } break; case VariableLocation::CONTEXT: if (variable->binding_needs_init()) { HValue* value = graph()->GetConstantHole(); HValue* context = environment()->context(); HStoreContextSlot* store = Add( context, variable->index(), HStoreContextSlot::kNoCheck, value); if (store->HasObservableSideEffects()) { Add(proxy->id(), REMOVABLE_SIMULATE); } } break; case VariableLocation::LOOKUP: return Bailout(kUnsupportedLookupSlotInDeclaration); case VariableLocation::MODULE: UNREACHABLE(); } } void HOptimizedGraphBuilder::VisitFunctionDeclaration( FunctionDeclaration* declaration) { VariableProxy* proxy = declaration->proxy(); Variable* variable = proxy->var(); switch (variable->location()) { case VariableLocation::UNALLOCATED: { globals_.Add(variable->name(), zone()); FeedbackSlot slot = proxy->VariableFeedbackSlot(); DCHECK(!slot.IsInvalid()); globals_.Add(handle(Smi::FromInt(slot.ToInt()), isolate()), zone()); // We need the slot where the literals array lives, too. slot = declaration->fun()->LiteralFeedbackSlot(); DCHECK(!slot.IsInvalid()); globals_.Add(handle(Smi::FromInt(slot.ToInt()), isolate()), zone()); Handle function = Compiler::GetSharedFunctionInfo( declaration->fun(), current_info()->script(), top_info()); // Check for stack-overflow exception. if (function.is_null()) return SetStackOverflow(); globals_.Add(function, zone()); return; } case VariableLocation::PARAMETER: case VariableLocation::LOCAL: { CHECK_ALIVE(VisitForValue(declaration->fun())); HValue* value = Pop(); BindIfLive(variable, value); break; } case VariableLocation::CONTEXT: { CHECK_ALIVE(VisitForValue(declaration->fun())); HValue* value = Pop(); HValue* context = environment()->context(); HStoreContextSlot* store = Add( context, variable->index(), HStoreContextSlot::kNoCheck, value); if (store->HasObservableSideEffects()) { Add(proxy->id(), REMOVABLE_SIMULATE); } break; } case VariableLocation::LOOKUP: return Bailout(kUnsupportedLookupSlotInDeclaration); case VariableLocation::MODULE: UNREACHABLE(); } } void HOptimizedGraphBuilder::VisitRewritableExpression( RewritableExpression* node) { CHECK_ALIVE(Visit(node->expression())); } // Generators for inline runtime functions. // Support for types. void HOptimizedGraphBuilder::GenerateIsSmi(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HIsSmiAndBranch* result = New(value); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsJSReceiver(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsArray(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, JS_ARRAY_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsTypedArray(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, JS_TYPED_ARRAY_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateToInteger(CallRuntime* call) { DCHECK_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* input = Pop(); if (input->type().IsSmi()) { return ast_context()->ReturnValue(input); } else { Callable callable = CodeFactory::ToInteger(isolate()); HValue* stub = Add(callable.code()); HValue* values[] = {input}; HInstruction* result = New( stub, 0, callable.descriptor(), ArrayVector(values)); return ast_context()->ReturnInstruction(result, call->id()); } } void HOptimizedGraphBuilder::GenerateToObject(CallRuntime* call) { DCHECK_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HValue* result = BuildToObject(value); return ast_context()->ReturnValue(result); } void HOptimizedGraphBuilder::GenerateToString(CallRuntime* call) { DCHECK_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* input = Pop(); if (input->type().IsString()) { return ast_context()->ReturnValue(input); } else { Callable callable = CodeFactory::ToString(isolate()); HValue* stub = Add(callable.code()); HValue* values[] = {input}; HInstruction* result = New( stub, 0, callable.descriptor(), ArrayVector(values)); return ast_context()->ReturnInstruction(result, call->id()); } } void HOptimizedGraphBuilder::GenerateToLength(CallRuntime* call) { DCHECK_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); Callable callable = CodeFactory::ToLength(isolate()); HValue* input = Pop(); HValue* stub = Add(callable.code()); HValue* values[] = {input}; HInstruction* result = New( stub, 0, callable.descriptor(), ArrayVector(values)); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateToNumber(CallRuntime* call) { DCHECK_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); Callable callable = CodeFactory::ToNumber(isolate()); HValue* input = Pop(); HValue* result = BuildToNumber(input); if (result->HasObservableSideEffects()) { if (!ast_context()->IsEffect()) Push(result); Add(call->id(), REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) result = Pop(); } return ast_context()->ReturnValue(result); } void HOptimizedGraphBuilder::GenerateIsJSProxy(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HIfContinuation continuation; IfBuilder if_proxy(this); HValue* smicheck = if_proxy.IfNot(value); if_proxy.And(); HValue* map = Add(value, smicheck, HObjectAccess::ForMap()); HValue* instance_type = Add(map, nullptr, HObjectAccess::ForMapInstanceType()); if_proxy.If( instance_type, Add(JS_PROXY_TYPE), Token::EQ); if_proxy.CaptureContinuation(&continuation); return ast_context()->ReturnContinuation(&continuation, call->id()); } void HOptimizedGraphBuilder::GenerateHasFastPackedElements(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* object = Pop(); HIfContinuation continuation(graph()->CreateBasicBlock(), graph()->CreateBasicBlock()); IfBuilder if_not_smi(this); if_not_smi.IfNot(object); if_not_smi.Then(); { NoObservableSideEffectsScope no_effects(this); IfBuilder if_fast_packed(this); HValue* elements_kind = BuildGetElementsKind(object); if_fast_packed.If( elements_kind, Add(FAST_SMI_ELEMENTS), Token::EQ); if_fast_packed.Or(); if_fast_packed.If( elements_kind, Add(FAST_ELEMENTS), Token::EQ); if_fast_packed.Or(); if_fast_packed.If( elements_kind, Add(FAST_DOUBLE_ELEMENTS), Token::EQ); if_fast_packed.JoinContinuation(&continuation); } if_not_smi.JoinContinuation(&continuation); return ast_context()->ReturnContinuation(&continuation, call->id()); } // Fast support for charCodeAt(n). void HOptimizedGraphBuilder::GenerateStringCharCodeAt(CallRuntime* call) { DCHECK(call->arguments()->length() == 2); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* index = Pop(); HValue* string = Pop(); HInstruction* result = BuildStringCharCodeAt(string, index); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for SubString. void HOptimizedGraphBuilder::GenerateSubString(CallRuntime* call) { DCHECK_EQ(3, call->arguments()->length()); CHECK_ALIVE(VisitExpressions(call->arguments())); Callable callable = CodeFactory::SubString(isolate()); HValue* stub = Add(callable.code()); HValue* to = Pop(); HValue* from = Pop(); HValue* string = Pop(); HValue* values[] = {string, from, to}; HInstruction* result = New( stub, 0, callable.descriptor(), ArrayVector(values)); result->set_type(HType::String()); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for calls. void HOptimizedGraphBuilder::GenerateCall(CallRuntime* call) { DCHECK_LE(2, call->arguments()->length()); CHECK_ALIVE(VisitExpressions(call->arguments())); CallTrampolineDescriptor descriptor(isolate()); PushArgumentsFromEnvironment(call->arguments()->length() - 1); HValue* trampoline = Add(isolate()->builtins()->Call()); HValue* target = Pop(); HValue* values[] = {target, Add(call->arguments()->length() - 2)}; HInstruction* result = New(trampoline, call->arguments()->length() - 1, descriptor, ArrayVector(values)); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateFixedArrayGet(CallRuntime* call) { DCHECK(call->arguments()->length() == 2); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* index = Pop(); HValue* object = Pop(); HInstruction* result = New( object, index, nullptr, nullptr, FAST_HOLEY_ELEMENTS, ALLOW_RETURN_HOLE); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateFixedArraySet(CallRuntime* call) { DCHECK(call->arguments()->length() == 3); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); CHECK_ALIVE(VisitForValue(call->arguments()->at(2))); HValue* value = Pop(); HValue* index = Pop(); HValue* object = Pop(); NoObservableSideEffectsScope no_effects(this); Add(object, index, value, nullptr, FAST_HOLEY_ELEMENTS); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::GenerateTheHole(CallRuntime* call) { DCHECK(call->arguments()->length() == 0); return ast_context()->ReturnValue(graph()->GetConstantHole()); } void HOptimizedGraphBuilder::GenerateCreateIterResultObject(CallRuntime* call) { DCHECK_EQ(2, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* done = Pop(); HValue* value = Pop(); HValue* result = BuildCreateIterResultObject(value, done); return ast_context()->ReturnValue(result); } void HOptimizedGraphBuilder::GenerateJSCollectionGetTable(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* receiver = Pop(); HInstruction* result = New( receiver, nullptr, HObjectAccess::ForJSCollectionTable()); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateStringGetRawHashField(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* object = Pop(); HInstruction* result = New( object, nullptr, HObjectAccess::ForStringHashField()); return ast_context()->ReturnInstruction(result, call->id()); } template HValue* HOptimizedGraphBuilder::BuildAllocateOrderedHashTable() { static const int kCapacity = CollectionType::kMinCapacity; static const int kBucketCount = kCapacity / CollectionType::kLoadFactor; static const int kFixedArrayLength = CollectionType::kHashTableStartIndex + kBucketCount + (kCapacity * CollectionType::kEntrySize); static const int kSizeInBytes = FixedArray::kHeaderSize + (kFixedArrayLength * kPointerSize); // Allocate the table and add the proper map. HValue* table = Add(Add(kSizeInBytes), HType::HeapObject(), NOT_TENURED, FIXED_ARRAY_TYPE, graph()->GetConstant0()); AddStoreMapConstant(table, isolate()->factory()->ordered_hash_table_map()); // Initialize the FixedArray... HValue* length = Add(kFixedArrayLength); Add(table, HObjectAccess::ForFixedArrayLength(), length); // ...and the OrderedHashTable fields. Add( table, HObjectAccess::ForOrderedHashTableNumberOfBuckets(), Add(kBucketCount)); Add( table, HObjectAccess::ForOrderedHashTableNumberOfElements(), graph()->GetConstant0()); Add( table, HObjectAccess::ForOrderedHashTableNumberOfDeletedElements< CollectionType>(), graph()->GetConstant0()); // Fill the buckets with kNotFound. HValue* not_found = Add(CollectionType::kNotFound); for (int i = 0; i < kBucketCount; ++i) { Add( table, HObjectAccess::ForOrderedHashTableBucket(i), not_found); } // Fill the data table with undefined. HValue* undefined = graph()->GetConstantUndefined(); for (int i = 0; i < (kCapacity * CollectionType::kEntrySize); ++i) { Add(table, HObjectAccess::ForOrderedHashTableDataTableIndex< CollectionType, kBucketCount>(i), undefined); } return table; } void HOptimizedGraphBuilder::GenerateSetInitialize(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* receiver = Pop(); NoObservableSideEffectsScope no_effects(this); HValue* table = BuildAllocateOrderedHashTable(); Add(receiver, HObjectAccess::ForJSCollectionTable(), table); return ast_context()->ReturnValue(receiver); } void HOptimizedGraphBuilder::GenerateMapInitialize(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* receiver = Pop(); NoObservableSideEffectsScope no_effects(this); HValue* table = BuildAllocateOrderedHashTable(); Add(receiver, HObjectAccess::ForJSCollectionTable(), table); return ast_context()->ReturnValue(receiver); } template void HOptimizedGraphBuilder::BuildOrderedHashTableClear(HValue* receiver) { HValue* old_table = Add( receiver, nullptr, HObjectAccess::ForJSCollectionTable()); HValue* new_table = BuildAllocateOrderedHashTable(); Add( old_table, HObjectAccess::ForOrderedHashTableNextTable(), new_table); Add( old_table, HObjectAccess::ForOrderedHashTableNumberOfDeletedElements< CollectionType>(), Add(CollectionType::kClearedTableSentinel)); Add(receiver, HObjectAccess::ForJSCollectionTable(), new_table); } void HOptimizedGraphBuilder::GenerateSetClear(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* receiver = Pop(); NoObservableSideEffectsScope no_effects(this); BuildOrderedHashTableClear(receiver); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::GenerateMapClear(CallRuntime* call) { DCHECK(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* receiver = Pop(); NoObservableSideEffectsScope no_effects(this); BuildOrderedHashTableClear(receiver); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::GenerateDebugBreakInOptimizedCode( CallRuntime* call) { Add(); return ast_context()->ReturnValue(graph()->GetConstant0()); } void HOptimizedGraphBuilder::GenerateDebugIsActive(CallRuntime* call) { DCHECK(call->arguments()->length() == 0); HValue* ref = Add(ExternalReference::debug_is_active_address(isolate())); HValue* value = Add(ref, nullptr, HObjectAccess::ForExternalUInteger8()); return ast_context()->ReturnValue(value); } #undef CHECK_BAILOUT #undef CHECK_ALIVE HEnvironment::HEnvironment(HEnvironment* outer, Scope* scope, Handle closure, Zone* zone) : closure_(closure), values_(0, zone), frame_type_(JS_FUNCTION), parameter_count_(0), specials_count_(1), local_count_(0), outer_(outer), entry_(NULL), pop_count_(0), push_count_(0), ast_id_(BailoutId::None()), zone_(zone) { DeclarationScope* declaration_scope = scope->GetDeclarationScope(); Initialize(declaration_scope->num_parameters() + 1, declaration_scope->num_stack_slots(), 0); } HEnvironment::HEnvironment(Zone* zone, int parameter_count) : values_(0, zone), frame_type_(STUB), parameter_count_(parameter_count), specials_count_(1), local_count_(0), outer_(NULL), entry_(NULL), pop_count_(0), push_count_(0), ast_id_(BailoutId::None()), zone_(zone) { Initialize(parameter_count, 0, 0); } HEnvironment::HEnvironment(const HEnvironment* other, Zone* zone) : values_(0, zone), frame_type_(JS_FUNCTION), parameter_count_(0), specials_count_(0), local_count_(0), outer_(NULL), entry_(NULL), pop_count_(0), push_count_(0), ast_id_(other->ast_id()), zone_(zone) { Initialize(other); } HEnvironment::HEnvironment(HEnvironment* outer, Handle closure, FrameType frame_type, int arguments, Zone* zone) : closure_(closure), values_(arguments, zone), frame_type_(frame_type), parameter_count_(arguments), specials_count_(0), local_count_(0), outer_(outer), entry_(NULL), pop_count_(0), push_count_(0), ast_id_(BailoutId::None()), zone_(zone) { } void HEnvironment::Initialize(int parameter_count, int local_count, int stack_height) { parameter_count_ = parameter_count; local_count_ = local_count; // Avoid reallocating the temporaries' backing store on the first Push. int total = parameter_count + specials_count_ + local_count + stack_height; values_.Initialize(total + 4, zone()); for (int i = 0; i < total; ++i) values_.Add(NULL, zone()); } void HEnvironment::Initialize(const HEnvironment* other) { closure_ = other->closure(); values_.AddAll(other->values_, zone()); assigned_variables_.Union(other->assigned_variables_, zone()); frame_type_ = other->frame_type_; parameter_count_ = other->parameter_count_; local_count_ = other->local_count_; if (other->outer_ != NULL) outer_ = other->outer_->Copy(); // Deep copy. entry_ = other->entry_; pop_count_ = other->pop_count_; push_count_ = other->push_count_; specials_count_ = other->specials_count_; ast_id_ = other->ast_id_; } void HEnvironment::AddIncomingEdge(HBasicBlock* block, HEnvironment* other) { DCHECK(!block->IsLoopHeader()); DCHECK(values_.length() == other->values_.length()); int length = values_.length(); for (int i = 0; i < length; ++i) { HValue* value = values_[i]; if (value != NULL && value->IsPhi() && value->block() == block) { // There is already a phi for the i'th value. HPhi* phi = HPhi::cast(value); // Assert index is correct and that we haven't missed an incoming edge. DCHECK(phi->merged_index() == i || !phi->HasMergedIndex()); DCHECK(phi->OperandCount() == block->predecessors()->length()); phi->AddInput(other->values_[i]); } else if (values_[i] != other->values_[i]) { // There is a fresh value on the incoming edge, a phi is needed. DCHECK(values_[i] != NULL && other->values_[i] != NULL); HPhi* phi = block->AddNewPhi(i); HValue* old_value = values_[i]; for (int j = 0; j < block->predecessors()->length(); j++) { phi->AddInput(old_value); } phi->AddInput(other->values_[i]); this->values_[i] = phi; } } } void HEnvironment::Bind(int index, HValue* value) { DCHECK(value != NULL); assigned_variables_.Add(index, zone()); values_[index] = value; } bool HEnvironment::HasExpressionAt(int index) const { return index >= parameter_count_ + specials_count_ + local_count_; } bool HEnvironment::ExpressionStackIsEmpty() const { DCHECK(length() >= first_expression_index()); return length() == first_expression_index(); } void HEnvironment::SetExpressionStackAt(int index_from_top, HValue* value) { int count = index_from_top + 1; int index = values_.length() - count; DCHECK(HasExpressionAt(index)); // The push count must include at least the element in question or else // the new value will not be included in this environment's history. if (push_count_ < count) { // This is the same effect as popping then re-pushing 'count' elements. pop_count_ += (count - push_count_); push_count_ = count; } values_[index] = value; } HValue* HEnvironment::RemoveExpressionStackAt(int index_from_top) { int count = index_from_top + 1; int index = values_.length() - count; DCHECK(HasExpressionAt(index)); // Simulate popping 'count' elements and then // pushing 'count - 1' elements back. pop_count_ += Max(count - push_count_, 0); push_count_ = Max(push_count_ - count, 0) + (count - 1); return values_.Remove(index); } void HEnvironment::Drop(int count) { for (int i = 0; i < count; ++i) { Pop(); } } void HEnvironment::Print() const { OFStream os(stdout); os << *this << "\n"; } HEnvironment* HEnvironment::Copy() const { return new(zone()) HEnvironment(this, zone()); } HEnvironment* HEnvironment::CopyWithoutHistory() const { HEnvironment* result = Copy(); result->ClearHistory(); return result; } HEnvironment* HEnvironment::CopyAsLoopHeader(HBasicBlock* loop_header) const { HEnvironment* new_env = Copy(); for (int i = 0; i < values_.length(); ++i) { HPhi* phi = loop_header->AddNewPhi(i); phi->AddInput(values_[i]); new_env->values_[i] = phi; } new_env->ClearHistory(); return new_env; } HEnvironment* HEnvironment::CreateStubEnvironment(HEnvironment* outer, Handle target, FrameType frame_type, int arguments) const { HEnvironment* new_env = new(zone()) HEnvironment(outer, target, frame_type, arguments + 1, zone()); for (int i = 0; i <= arguments; ++i) { // Include receiver. new_env->Push(ExpressionStackAt(arguments - i)); } new_env->ClearHistory(); return new_env; } void HEnvironment::MarkAsTailCaller() { DCHECK_EQ(JS_FUNCTION, frame_type()); frame_type_ = TAIL_CALLER_FUNCTION; } void HEnvironment::ClearTailCallerMark() { DCHECK_EQ(TAIL_CALLER_FUNCTION, frame_type()); frame_type_ = JS_FUNCTION; } HEnvironment* HEnvironment::CopyForInlining( Handle target, int arguments, FunctionLiteral* function, HConstant* undefined, InliningKind inlining_kind, TailCallMode syntactic_tail_call_mode) const { DCHECK_EQ(JS_FUNCTION, frame_type()); // Outer environment is a copy of this one without the arguments. int arity = function->scope()->num_parameters(); HEnvironment* outer = Copy(); outer->Drop(arguments + 1); // Including receiver. outer->ClearHistory(); if (syntactic_tail_call_mode == TailCallMode::kAllow) { DCHECK_EQ(NORMAL_RETURN, inlining_kind); outer->MarkAsTailCaller(); } if (inlining_kind == CONSTRUCT_CALL_RETURN) { // Create artificial constructor stub environment. The receiver should // actually be the constructor function, but we pass the newly allocated // object instead, DoComputeConstructStubFrame() relies on that. outer = CreateStubEnvironment(outer, target, JS_CONSTRUCT, arguments); } else if (inlining_kind == GETTER_CALL_RETURN) { // We need an additional StackFrame::INTERNAL frame for restoring the // correct context. outer = CreateStubEnvironment(outer, target, JS_GETTER, arguments); } else if (inlining_kind == SETTER_CALL_RETURN) { // We need an additional StackFrame::INTERNAL frame for temporarily saving // the argument of the setter, see StoreStubCompiler::CompileStoreViaSetter. outer = CreateStubEnvironment(outer, target, JS_SETTER, arguments); } if (arity != arguments) { // Create artificial arguments adaptation environment. outer = CreateStubEnvironment(outer, target, ARGUMENTS_ADAPTOR, arguments); } HEnvironment* inner = new(zone()) HEnvironment(outer, function->scope(), target, zone()); // Get the argument values from the original environment. for (int i = 0; i <= arity; ++i) { // Include receiver. HValue* push = (i <= arguments) ? ExpressionStackAt(arguments - i) : undefined; inner->SetValueAt(i, push); } inner->SetValueAt(arity + 1, context()); for (int i = arity + 2; i < inner->length(); ++i) { inner->SetValueAt(i, undefined); } inner->set_ast_id(BailoutId::FunctionEntry()); return inner; } std::ostream& operator<<(std::ostream& os, const HEnvironment& env) { for (int i = 0; i < env.length(); i++) { if (i == 0) os << "parameters\n"; if (i == env.parameter_count()) os << "specials\n"; if (i == env.parameter_count() + env.specials_count()) os << "locals\n"; if (i == env.parameter_count() + env.specials_count() + env.local_count()) { os << "expressions\n"; } HValue* val = env.values()->at(i); os << i << ": "; if (val != NULL) { os << val; } else { os << "NULL"; } os << "\n"; } return os << "\n"; } void HTracer::TraceCompilation(CompilationInfo* info) { Tag tag(this, "compilation"); std::string name; if (info->parse_info()) { Object* source_name = info->script()->name(); if (source_name->IsString()) { String* str = String::cast(source_name); if (str->length() > 0) { name.append(str->ToCString().get()); name.append(":"); } } } std::unique_ptr method_name = info->GetDebugName(); name.append(method_name.get()); if (info->IsOptimizing()) { PrintStringProperty("name", name.c_str()); PrintIndent(); trace_.Add("method \"%s:%d\"\n", method_name.get(), info->optimization_id()); } else { PrintStringProperty("name", name.c_str()); PrintStringProperty("method", "stub"); } PrintLongProperty("date", static_cast(base::OS::TimeCurrentMillis())); } void HTracer::TraceLithium(const char* name, LChunk* chunk) { DCHECK(!chunk->isolate()->concurrent_recompilation_enabled()); AllowHandleDereference allow_deref; AllowDeferredHandleDereference allow_deferred_deref; Trace(name, chunk->graph(), chunk); } void HTracer::TraceHydrogen(const char* name, HGraph* graph) { DCHECK(!graph->isolate()->concurrent_recompilation_enabled()); AllowHandleDereference allow_deref; AllowDeferredHandleDereference allow_deferred_deref; Trace(name, graph, NULL); } void HTracer::Trace(const char* name, HGraph* graph, LChunk* chunk) { Tag tag(this, "cfg"); PrintStringProperty("name", name); const ZoneList* blocks = graph->blocks(); for (int i = 0; i < blocks->length(); i++) { HBasicBlock* current = blocks->at(i); Tag block_tag(this, "block"); PrintBlockProperty("name", current->block_id()); PrintIntProperty("from_bci", -1); PrintIntProperty("to_bci", -1); if (!current->predecessors()->is_empty()) { PrintIndent(); trace_.Add("predecessors"); for (int j = 0; j < current->predecessors()->length(); ++j) { trace_.Add(" \"B%d\"", current->predecessors()->at(j)->block_id()); } trace_.Add("\n"); } else { PrintEmptyProperty("predecessors"); } if (current->end()->SuccessorCount() == 0) { PrintEmptyProperty("successors"); } else { PrintIndent(); trace_.Add("successors"); for (HSuccessorIterator it(current->end()); !it.Done(); it.Advance()) { trace_.Add(" \"B%d\"", it.Current()->block_id()); } trace_.Add("\n"); } PrintEmptyProperty("xhandlers"); { PrintIndent(); trace_.Add("flags"); if (current->IsLoopSuccessorDominator()) { trace_.Add(" \"dom-loop-succ\""); } if (current->IsUnreachable()) { trace_.Add(" \"dead\""); } if (current->is_osr_entry()) { trace_.Add(" \"osr\""); } trace_.Add("\n"); } if (current->dominator() != NULL) { PrintBlockProperty("dominator", current->dominator()->block_id()); } PrintIntProperty("loop_depth", current->LoopNestingDepth()); if (chunk != NULL) { int first_index = current->first_instruction_index(); int last_index = current->last_instruction_index(); PrintIntProperty( "first_lir_id", LifetimePosition::FromInstructionIndex(first_index).Value()); PrintIntProperty( "last_lir_id", LifetimePosition::FromInstructionIndex(last_index).Value()); } { Tag states_tag(this, "states"); Tag locals_tag(this, "locals"); int total = current->phis()->length(); PrintIntProperty("size", current->phis()->length()); PrintStringProperty("method", "None"); for (int j = 0; j < total; ++j) { HPhi* phi = current->phis()->at(j); PrintIndent(); std::ostringstream os; os << phi->merged_index() << " " << NameOf(phi) << " " << *phi << "\n"; trace_.Add(os.str().c_str()); } } { Tag HIR_tag(this, "HIR"); for (HInstructionIterator it(current); !it.Done(); it.Advance()) { HInstruction* instruction = it.Current(); int uses = instruction->UseCount(); PrintIndent(); std::ostringstream os; os << "0 " << uses << " " << NameOf(instruction) << " " << *instruction; if (instruction->has_position()) { const SourcePosition pos = instruction->position(); os << " pos:"; if (pos.isInlined()) os << "inlining(" << pos.InliningId() << "),"; os << pos.ScriptOffset(); } os << " <|@\n"; trace_.Add(os.str().c_str()); } } if (chunk != NULL) { Tag LIR_tag(this, "LIR"); int first_index = current->first_instruction_index(); int last_index = current->last_instruction_index(); if (first_index != -1 && last_index != -1) { const ZoneList* instructions = chunk->instructions(); for (int i = first_index; i <= last_index; ++i) { LInstruction* linstr = instructions->at(i); if (linstr != NULL) { PrintIndent(); trace_.Add("%d ", LifetimePosition::FromInstructionIndex(i).Value()); linstr->PrintTo(&trace_); std::ostringstream os; os << " [hir:" << NameOf(linstr->hydrogen_value()) << "] <|@\n"; trace_.Add(os.str().c_str()); } } } } } } void HTracer::TraceLiveRanges(const char* name, LAllocator* allocator) { Tag tag(this, "intervals"); PrintStringProperty("name", name); const Vector* fixed_d = allocator->fixed_double_live_ranges(); for (int i = 0; i < fixed_d->length(); ++i) { TraceLiveRange(fixed_d->at(i), "fixed", allocator->zone()); } const Vector* fixed = allocator->fixed_live_ranges(); for (int i = 0; i < fixed->length(); ++i) { TraceLiveRange(fixed->at(i), "fixed", allocator->zone()); } const ZoneList* live_ranges = allocator->live_ranges(); for (int i = 0; i < live_ranges->length(); ++i) { TraceLiveRange(live_ranges->at(i), "object", allocator->zone()); } } void HTracer::TraceLiveRange(LiveRange* range, const char* type, Zone* zone) { if (range != NULL && !range->IsEmpty()) { PrintIndent(); trace_.Add("%d %s", range->id(), type); if (range->HasRegisterAssigned()) { LOperand* op = range->CreateAssignedOperand(zone); int assigned_reg = op->index(); if (op->IsDoubleRegister()) { trace_.Add(" \"%s\"", GetRegConfig()->GetDoubleRegisterName(assigned_reg)); } else { DCHECK(op->IsRegister()); trace_.Add(" \"%s\"", GetRegConfig()->GetGeneralRegisterName(assigned_reg)); } } else if (range->IsSpilled()) { LOperand* op = range->TopLevel()->GetSpillOperand(); if (op->IsDoubleStackSlot()) { trace_.Add(" \"double_stack:%d\"", op->index()); } else { DCHECK(op->IsStackSlot()); trace_.Add(" \"stack:%d\"", op->index()); } } int parent_index = -1; if (range->IsChild()) { parent_index = range->parent()->id(); } else { parent_index = range->id(); } LOperand* op = range->FirstHint(); int hint_index = -1; if (op != NULL && op->IsUnallocated()) { hint_index = LUnallocated::cast(op)->virtual_register(); } trace_.Add(" %d %d", parent_index, hint_index); UseInterval* cur_interval = range->first_interval(); while (cur_interval != NULL && range->Covers(cur_interval->start())) { trace_.Add(" [%d, %d[", cur_interval->start().Value(), cur_interval->end().Value()); cur_interval = cur_interval->next(); } UsePosition* current_pos = range->first_pos(); while (current_pos != NULL) { if (current_pos->RegisterIsBeneficial() || FLAG_trace_all_uses) { trace_.Add(" %d M", current_pos->pos().Value()); } current_pos = current_pos->next(); } trace_.Add(" \"\"\n"); } } void HTracer::FlushToFile() { AppendChars(filename_.start(), trace_.ToCString().get(), trace_.length(), false); trace_.Reset(); } void HStatistics::Initialize(CompilationInfo* info) { if (!info->has_shared_info()) return; source_size_ += info->shared_info()->SourceSize(); } void HStatistics::Print() { PrintF( "\n" "----------------------------------------" "----------------------------------------\n" "--- Hydrogen timing results:\n" "----------------------------------------" "----------------------------------------\n"); base::TimeDelta sum; for (int i = 0; i < times_.length(); ++i) { sum += times_[i]; } for (int i = 0; i < names_.length(); ++i) { PrintF("%33s", names_[i]); double ms = times_[i].InMillisecondsF(); double percent = times_[i].PercentOf(sum); PrintF(" %8.3f ms / %4.1f %% ", ms, percent); size_t size = sizes_[i]; double size_percent = static_cast(size) * 100 / total_size_; PrintF(" %9zu bytes / %4.1f %%\n", size, size_percent); } PrintF( "----------------------------------------" "----------------------------------------\n"); base::TimeDelta total = create_graph_ + optimize_graph_ + generate_code_; PrintF("%33s %8.3f ms / %4.1f %% \n", "Create graph", create_graph_.InMillisecondsF(), create_graph_.PercentOf(total)); PrintF("%33s %8.3f ms / %4.1f %% \n", "Optimize graph", optimize_graph_.InMillisecondsF(), optimize_graph_.PercentOf(total)); PrintF("%33s %8.3f ms / %4.1f %% \n", "Generate and install code", generate_code_.InMillisecondsF(), generate_code_.PercentOf(total)); PrintF( "----------------------------------------" "----------------------------------------\n"); PrintF("%33s %8.3f ms %9zu bytes\n", "Total", total.InMillisecondsF(), total_size_); PrintF("%33s (%.1f times slower than full code gen)\n", "", total.TimesOf(full_code_gen_)); double source_size_in_kb = static_cast(source_size_) / 1024; double normalized_time = source_size_in_kb > 0 ? total.InMillisecondsF() / source_size_in_kb : 0; double normalized_size_in_kb = source_size_in_kb > 0 ? static_cast(total_size_) / 1024 / source_size_in_kb : 0; PrintF("%33s %8.3f ms %7.3f kB allocated\n", "Average per kB source", normalized_time, normalized_size_in_kb); } void HStatistics::SaveTiming(const char* name, base::TimeDelta time, size_t size) { total_size_ += size; for (int i = 0; i < names_.length(); ++i) { if (strcmp(names_[i], name) == 0) { times_[i] += time; sizes_[i] += size; return; } } names_.Add(name); times_.Add(time); sizes_.Add(size); } HPhase::~HPhase() { if (ShouldProduceTraceOutput()) { isolate()->GetHTracer()->TraceHydrogen(name(), graph_); } #ifdef DEBUG graph_->Verify(false); // No full verify. #endif } } // namespace internal } // namespace v8