// Copyright 2013 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "hydrogen.h" #include #include "v8.h" #include "allocation-site-scopes.h" #include "codegen.h" #include "full-codegen.h" #include "hashmap.h" #include "hydrogen-bce.h" #include "hydrogen-bch.h" #include "hydrogen-canonicalize.h" #include "hydrogen-check-elimination.h" #include "hydrogen-dce.h" #include "hydrogen-dehoist.h" #include "hydrogen-environment-liveness.h" #include "hydrogen-escape-analysis.h" #include "hydrogen-infer-representation.h" #include "hydrogen-infer-types.h" #include "hydrogen-load-elimination.h" #include "hydrogen-gvn.h" #include "hydrogen-mark-deoptimize.h" #include "hydrogen-mark-unreachable.h" #include "hydrogen-minus-zero.h" #include "hydrogen-osr.h" #include "hydrogen-range-analysis.h" #include "hydrogen-redundant-phi.h" #include "hydrogen-removable-simulates.h" #include "hydrogen-representation-changes.h" #include "hydrogen-sce.h" #include "hydrogen-uint32-analysis.h" #include "lithium-allocator.h" #include "parser.h" #include "runtime.h" #include "scopeinfo.h" #include "scopes.h" #include "stub-cache.h" #include "typing.h" #if V8_TARGET_ARCH_IA32 #include "ia32/lithium-codegen-ia32.h" #elif V8_TARGET_ARCH_X64 #include "x64/lithium-codegen-x64.h" #elif V8_TARGET_ARCH_ARM #include "arm/lithium-codegen-arm.h" #elif V8_TARGET_ARCH_MIPS #include "mips/lithium-codegen-mips.h" #else #error Unsupported target architecture. #endif namespace v8 { namespace internal { 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) { } Isolate* HBasicBlock::isolate() const { return graph_->isolate(); } void HBasicBlock::MarkUnreachable() { is_reachable_ = false; } void HBasicBlock::AttachLoopInformation() { ASSERT(!IsLoopHeader()); loop_information_ = new(zone()) HLoopInformation(this, zone()); } void HBasicBlock::DetachLoopInformation() { ASSERT(IsLoopHeader()); loop_information_ = NULL; } void HBasicBlock::AddPhi(HPhi* phi) { ASSERT(!IsStartBlock()); phis_.Add(phi, zone()); phi->SetBlock(this); } void HBasicBlock::RemovePhi(HPhi* phi) { ASSERT(phi->block() == this); ASSERT(phis_.Contains(phi)); phi->Kill(); phis_.RemoveElement(phi); phi->SetBlock(NULL); } void HBasicBlock::AddInstruction(HInstruction* instr, int position) { ASSERT(!IsStartBlock() || !IsFinished()); ASSERT(!instr->IsLinked()); ASSERT(!IsFinished()); if (position != RelocInfo::kNoPosition) { instr->set_position(position); } if (first_ == NULL) { ASSERT(last_environment() != NULL); ASSERT(!last_environment()->ast_id().IsNone()); HBlockEntry* entry = new(zone()) HBlockEntry(); entry->InitializeAsFirst(this); if (position != RelocInfo::kNoPosition) { entry->set_position(position); } else { ASSERT(!FLAG_emit_opt_code_positions || !graph()->info()->IsOptimizing()); } 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) { ASSERT(HasEnvironment()); HEnvironment* environment = last_environment(); ASSERT(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, int position) { ASSERT(!IsFinished()); AddInstruction(end, position); end_ = end; for (HSuccessorIterator it(end); !it.Done(); it.Advance()) { it.Current()->RegisterPredecessor(this); } } void HBasicBlock::Goto(HBasicBlock* block, int position, FunctionState* state, bool add_simulate) { bool drop_extra = state != NULL && state->inlining_kind() == DROP_EXTRA_ON_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, int position) { HBasicBlock* target = state->function_return(); bool drop_extra = state->inlining_kind() == DROP_EXTRA_ON_RETURN; ASSERT(target->IsInlineReturnTarget()); ASSERT(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) { ASSERT(!HasEnvironment()); ASSERT(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(); ASSERT(length > 0); for (int i = 0; i < length; i++) { HBasicBlock* predecessor = predecessors_[i]; ASSERT(predecessor->end()->IsGoto()); HSimulate* simulate = HSimulate::cast(predecessor->end()->previous()); ASSERT(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; } 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) { ASSERT(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::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). ASSERT(IsLoopHeader() || first_ == NULL); HEnvironment* incoming_env = pred->last_environment(); if (IsLoopHeader()) { ASSERT(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()) { ASSERT(!IsLoopHeader()); SetInitialEnvironment(pred->last_environment()->Copy()); } predecessors_.Add(pred, zone()); } void HBasicBlock::AddDominatedBlock(HBasicBlock* block) { ASSERT(!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(); } ASSERT(first != NULL && second != NULL); } if (dominator_ != first) { ASSERT(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. ASSERT(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. ASSERT(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. ASSERT(IsFinished()); ASSERT(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) { ASSERT(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(); ASSERT(current != NULL && current->IsBlockEntry()); while (current != NULL) { ASSERT((current->next() == NULL) == current->IsControlInstruction()); ASSERT(current->block() == block); current->Verify(); current = current->next(); } // Check that successors are correctly set. HBasicBlock* first = block->end()->FirstSuccessor(); HBasicBlock* second = block->end()->SecondSuccessor(); ASSERT(second == NULL || first != NULL); // Check that the predecessor array is correct. if (first != NULL) { ASSERT(first->predecessors()->Contains(block)); if (second != NULL) { ASSERT(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); ASSERT(predecessor->end()->IsGoto() || predecessor->end()->IsDeoptimize()); ASSERT(predecessor->last_environment()->ast_id() == id); } } } // Check special property of first block to have no predecessors. ASSERT(blocks_.at(0)->predecessors()->is_empty()); if (do_full_verify) { // Check that the graph is fully connected. ReachabilityAnalyzer analyzer(entry_block_, blocks_.length(), NULL); ASSERT(analyzer.visited_count() == blocks_.length()); // Check that entry block dominator is NULL. ASSERT(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. ASSERT(i == 0); } else { // Assert that block is unreachable if dominator must not be visited. ReachabilityAnalyzer dominator_analyzer(entry_block_, blocks_.length(), block->dominator()); ASSERT(!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(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); } #define DEFINE_GET_CONSTANT(Name, name, htype, boolean_value) \ HConstant* HGraph::GetConstant##Name() { \ if (!constant_##name##_.is_set()) { \ HConstant* constant = new(zone()) HConstant( \ Unique::CreateImmovable(isolate()->factory()->name##_value()), \ Representation::Tagged(), \ htype, \ false, \ true, \ false, \ boolean_value); \ constant->InsertAfter(entry_block()->first()); \ constant_##name##_.set(constant); \ } \ return ReinsertConstantIfNecessary(constant_##name##_.get()); \ } DEFINE_GET_CONSTANT(Undefined, undefined, HType::Tagged(), false) DEFINE_GET_CONSTANT(True, true, HType::Boolean(), true) DEFINE_GET_CONSTANT(False, false, HType::Boolean(), false) DEFINE_GET_CONSTANT(Hole, the_hole, HType::Tagged(), false) DEFINE_GET_CONSTANT(Null, null, HType::Tagged(), 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(HGraphBuilder* builder) : builder_(builder), finished_(false), did_then_(false), did_else_(false), did_else_if_(false), did_and_(false), did_or_(false), captured_(false), needs_compare_(true), 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) { HEnvironment* env = builder->environment(); first_true_block_ = builder->CreateBasicBlock(env->Copy()); first_false_block_ = builder->CreateBasicBlock(env->Copy()); } HGraphBuilder::IfBuilder::IfBuilder( HGraphBuilder* builder, HIfContinuation* continuation) : builder_(builder), finished_(false), did_then_(false), did_else_(false), did_else_if_(false), did_and_(false), did_or_(false), captured_(false), needs_compare_(false), pending_merge_block_(false), first_true_block_(NULL), first_false_block_(NULL), split_edge_merge_block_(NULL), merge_at_join_blocks_(NULL), normal_merge_at_join_block_count_(0), deopt_merge_at_join_block_count_(0) { continuation->Continue(&first_true_block_, &first_false_block_); } HControlInstruction* HGraphBuilder::IfBuilder::AddCompare( HControlInstruction* compare) { ASSERT(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() { ASSERT(!needs_compare_); ASSERT(!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() { ASSERT(!needs_compare_); ASSERT(!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) { ASSERT(!did_else_if_); ASSERT(!finished_); ASSERT(!captured_); HBasicBlock* true_block = NULL; HBasicBlock* false_block = NULL; Finish(&true_block, &false_block); ASSERT(true_block != NULL); ASSERT(false_block != NULL); continuation->Capture(true_block, false_block); captured_ = true; builder_->set_current_block(NULL); End(); } void HGraphBuilder::IfBuilder::JoinContinuation(HIfContinuation* continuation) { ASSERT(!did_else_if_); ASSERT(!finished_); ASSERT(!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()) { ASSERT(continuation->IsTrueReachable()); builder_->GotoNoSimulate(true_block, continuation->true_branch()); } if (false_block != NULL && !false_block->IsFinished()) { ASSERT(continuation->IsFalseReachable()); builder_->GotoNoSimulate(false_block, continuation->false_branch()); } captured_ = true; End(); } void HGraphBuilder::IfBuilder::Then() { ASSERT(!captured_); ASSERT(!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(); ToBooleanStub::Types boolean_type = ToBooleanStub::Types(); boolean_type.Add(ToBooleanStub::BOOLEAN); 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() { ASSERT(did_then_); ASSERT(!captured_); ASSERT(!finished_); AddMergeAtJoinBlock(false); builder_->set_current_block(first_false_block_); pending_merge_block_ = true; did_else_ = true; } void HGraphBuilder::IfBuilder::Deopt(const char* reason) { ASSERT(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(); ASSERT(block == NULL || !block->IsFinished()); MergeAtJoinBlock* record = new(builder_->zone()) MergeAtJoinBlock(block, deopt, merge_at_join_blocks_); merge_at_join_blocks_ = record; if (block != NULL) { ASSERT(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() { ASSERT(!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_; } ASSERT(then_record->next_ == NULL); } void HGraphBuilder::IfBuilder::End() { if (captured_) return; Finish(); int total_merged_blocks = normal_merge_at_join_block_count_ + deopt_merge_at_join_block_count_; ASSERT(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) { builder_->PadEnvironmentForContinuation(current->block_, merge_block); builder_->GotoNoSimulate(current->block_, merge_block); } current = current->next_; } builder_->set_current_block(merge_block); } HGraphBuilder::LoopBuilder::LoopBuilder(HGraphBuilder* builder, HValue* context, LoopBuilder::Direction direction) : builder_(builder), context_(context), direction_(direction), finished_(false) { header_block_ = builder->CreateLoopHeaderBlock(); body_block_ = NULL; exit_block_ = NULL; exit_trampoline_block_ = NULL; increment_amount_ = builder_->graph()->GetConstant1(); } HGraphBuilder::LoopBuilder::LoopBuilder(HGraphBuilder* builder, HValue* context, LoopBuilder::Direction direction, HValue* increment_amount) : builder_(builder), context_(context), direction_(direction), finished_(false) { header_block_ = builder->CreateLoopHeaderBlock(); body_block_ = NULL; exit_block_ = NULL; exit_trampoline_block_ = NULL; increment_amount_ = increment_amount; } HValue* HGraphBuilder::LoopBuilder::BeginBody( HValue* initial, HValue* terminating, Token::Value token) { 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) { HValue* one = builder_->graph()->GetConstant1(); if (direction_ == kPreIncrement) { increment_ = HAdd::New(zone(), context_, phi_, one); } else { increment_ = HSub::New(zone(), context_, phi_, one); } increment_->ClearFlag(HValue::kCanOverflow); builder_->AddInstruction(increment_); return increment_; } else { return phi_; } } void HGraphBuilder::LoopBuilder::Break() { if (exit_trampoline_block_ == NULL) { // Its the first time we saw a break. 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() { ASSERT(!finished_); if (direction_ == kPostIncrement || direction_ == kPostDecrement) { if (direction_ == kPostIncrement) { increment_ = HAdd::New(zone(), context_, phi_, increment_amount_); } else { increment_ = HSub::New(zone(), context_, phi_, increment_amount_); } increment_->ClearFlag(HValue::kCanOverflow); builder_->AddInstruction(increment_); } // 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() { graph_ = new(zone()) HGraph(info_); 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) { ASSERT(current_block() != NULL); ASSERT(!FLAG_emit_opt_code_positions || position_ != RelocInfo::kNoPosition || !info_->IsOptimizing()); current_block()->AddInstruction(instr, position_); if (graph()->IsInsideNoSideEffectsScope()) { instr->SetFlag(HValue::kHasNoObservableSideEffects); } return instr; } void HGraphBuilder::FinishCurrentBlock(HControlInstruction* last) { ASSERT(!FLAG_emit_opt_code_positions || !info_->IsOptimizing() || position_ != RelocInfo::kNoPosition); current_block()->Finish(last, position_); if (last->IsReturn() || last->IsAbnormalExit()) { set_current_block(NULL); } } void HGraphBuilder::FinishExitCurrentBlock(HControlInstruction* instruction) { ASSERT(!FLAG_emit_opt_code_positions || !info_->IsOptimizing() || position_ != RelocInfo::kNoPosition); current_block()->FinishExit(instruction, 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, 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) { ASSERT(current_block() != NULL); ASSERT(!graph()->IsInsideNoSideEffectsScope()); current_block()->AddNewSimulate(id, 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::BuildCheckHeapObject(HValue* obj) { if (obj->type().IsHeapObject()) return obj; return Add(obj); } void HGraphBuilder::FinishExitWithHardDeoptimization( const char* reason, HBasicBlock* continuation) { PadEnvironmentForContinuation(current_block(), continuation); Add(reason, Deoptimizer::EAGER); if (graph()->IsInsideNoSideEffectsScope()) { GotoNoSimulate(continuation); } else { Goto(continuation); } } void HGraphBuilder::PadEnvironmentForContinuation( HBasicBlock* from, HBasicBlock* continuation) { if (continuation->last_environment() != NULL) { // When merging from a deopt block to a continuation, resolve differences in // environment by pushing constant 0 and popping extra values so that the // environments match during the join. Push 0 since it has the most specific // representation, and will not influence representation inference of the // phi. int continuation_env_length = continuation->last_environment()->length(); while (continuation_env_length != from->last_environment()->length()) { if (continuation_env_length > from->last_environment()->length()) { from->last_environment()->Push(graph()->GetConstant0()); } else { from->last_environment()->Pop(); } } } else { ASSERT(continuation->predecessors()->length() == 0); } } HValue* HGraphBuilder::BuildCheckMap(HValue* obj, Handle map) { return Add(obj, map, top_info()); } HValue* HGraphBuilder::BuildCheckString(HValue* string) { if (!string->type().IsString()) { ASSERT(!string->IsConstant() || !HConstant::cast(string)->HasStringValue()); BuildCheckHeapObject(string); return Add(string, HCheckInstanceType::IS_STRING); } return string; } HValue* HGraphBuilder::BuildWrapReceiver(HValue* object, HValue* function) { if (object->type().IsJSObject()) return object; return Add(object, function); } HValue* HGraphBuilder::BuildCheckForCapacityGrow(HValue* object, HValue* elements, ElementsKind kind, HValue* length, HValue* key, bool is_js_array, bool is_store) { 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); IfBuilder capacity_checker(this); capacity_checker.If(key, current_capacity, Token::GTE); capacity_checker.Then(); HValue* max_gap = Add(static_cast(JSObject::kMaxGap)); HValue* max_capacity = AddUncasted(current_capacity, max_gap); IfBuilder key_checker(this); key_checker.If(key, max_capacity, Token::LT); key_checker.Then(); key_checker.ElseDeopt("Key out of capacity range"); key_checker.End(); HValue* new_capacity = BuildNewElementsCapacity(key); HValue* new_elements = BuildGrowElementsCapacity(object, elements, kind, kind, length, new_capacity); environment()->Push(new_elements); capacity_checker.Else(); environment()->Push(elements); capacity_checker.End(); if (is_js_array) { HValue* new_length = AddUncasted(key, graph_->GetConstant1()); new_length->ClearFlag(HValue::kCanOverflow); Add(object, HObjectAccess::ForArrayLength(kind), new_length, INITIALIZING_STORE); } if (is_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(), kind, INITIALIZING_STORE); } 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(); } void HGraphBuilder::BuildTransitionElementsKind(HValue* object, HValue* map, ElementsKind from_kind, ElementsKind to_kind, bool is_jsarray) { ASSERT(!IsFastHoleyElementsKind(from_kind) || IsFastHoleyElementsKind(to_kind)); if (AllocationSite::GetMode(from_kind, to_kind) == TRACK_ALLOCATION_SITE) { Add(object); } if (!IsSimpleMapChangeTransition(from_kind, to_kind)) { HInstruction* elements = AddLoadElements(object); HInstruction* empty_fixed_array = Add( isolate()->factory()->empty_fixed_array()); IfBuilder if_builder(this); if_builder.IfNot(elements, empty_fixed_array); if_builder.Then(); HInstruction* elements_length = AddLoadFixedArrayLength(elements); HInstruction* array_length = is_jsarray ? Add(object, HObjectAccess::ForArrayLength(from_kind)) : elements_length; BuildGrowElementsCapacity(object, elements, from_kind, to_kind, array_length, elements_length); if_builder.End(); } Add(object, HObjectAccess::ForMap(), map, INITIALIZING_STORE); } HValue* HGraphBuilder::BuildUncheckedDictionaryElementLoadHelper( HValue* elements, HValue* key, HValue* hash, HValue* mask, int current_probe) { if (current_probe == kNumberDictionaryProbes) { return NULL; } int32_t offset = SeededNumberDictionary::GetProbeOffset(current_probe); HValue* raw_index = (current_probe == 0) ? hash : AddUncasted(hash, Add(offset)); raw_index = AddUncasted(Token::BIT_AND, raw_index, mask); int32_t entry_size = SeededNumberDictionary::kEntrySize; raw_index = AddUncasted(raw_index, Add(entry_size)); raw_index->ClearFlag(HValue::kCanOverflow); int32_t base_offset = SeededNumberDictionary::kElementsStartIndex; HValue* key_index = AddUncasted(raw_index, Add(base_offset)); key_index->ClearFlag(HValue::kCanOverflow); HValue* candidate_key = Add(elements, key_index, static_cast(NULL), FAST_SMI_ELEMENTS); IfBuilder key_compare(this); key_compare.IfNot(key, candidate_key); key_compare.Then(); { // Key at the current probe doesn't match, try at the next probe. HValue* result = BuildUncheckedDictionaryElementLoadHelper( elements, key, hash, mask, current_probe + 1); if (result == NULL) { key_compare.Deopt("probes exhausted in keyed load dictionary lookup"); result = graph()->GetConstantUndefined(); } else { Push(result); } } key_compare.Else(); { // Key at current probe matches. Details must be zero, otherwise the // dictionary element requires special handling. HValue* details_index = AddUncasted( raw_index, Add(base_offset + 2)); details_index->ClearFlag(HValue::kCanOverflow); HValue* details = Add(elements, details_index, static_cast(NULL), FAST_SMI_ELEMENTS); IfBuilder details_compare(this); details_compare.If(details, graph()->GetConstant0(), Token::NE); details_compare.ThenDeopt("keyed load dictionary element not fast case"); details_compare.Else(); { // Key matches and details are zero --> fast case. Load and return the // value. HValue* result_index = AddUncasted( raw_index, Add(base_offset + 1)); result_index->ClearFlag(HValue::kCanOverflow); Push(Add(elements, result_index, static_cast(NULL), FAST_ELEMENTS)); } details_compare.End(); } key_compare.End(); return 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* key) { HValue* elements = AddLoadElements(receiver); HValue* hash = BuildElementIndexHash(key); HValue* capacity = Add( elements, Add(NameDictionary::kCapacityIndex), static_cast(NULL), FAST_SMI_ELEMENTS); HValue* mask = AddUncasted(capacity, graph()->GetConstant1()); mask->ChangeRepresentation(Representation::Integer32()); mask->ClearFlag(HValue::kCanOverflow); return BuildUncheckedDictionaryElementLoadHelper(elements, key, hash, mask, 0); } HValue* HGraphBuilder::BuildNumberToString(HValue* object, Type* 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, static_cast(NULL), 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(Type::Smi())) { if_objectissmi.Deopt("Expected smi"); } else { // Check if the object is a heap number. IfBuilder if_objectisnumber(this); 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, HObjectAccess::ForHeapNumberValueLowestBits()); HValue* high = Add( object, 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, static_cast(NULL), FAST_ELEMENTS, ALLOW_RETURN_HOLE); // Check if key is a heap number (the number string cache contains only // SMIs and heap number, so it is sufficient to do a SMI check here). IfBuilder if_keyisnotsmi(this); if_keyisnotsmi.IfNot(key); if_keyisnotsmi.Then(); { // Check if values of key and object match. IfBuilder if_keyeqobject(this); if_keyeqobject.If( Add(key, HObjectAccess::ForHeapNumberValue()), Add(object, HObjectAccess::ForHeapNumberValue()), Token::EQ); if_keyeqobject.Then(); { // Make the key_index available. Push(key_index); } if_keyeqobject.JoinContinuation(&found); } if_keyisnotsmi.JoinContinuation(&found); } if_objectisnumber.Else(); { if (type->Is(Type::Number())) { if_objectisnumber.Deopt("Expected heap number"); } } 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, static_cast(NULL), FAST_ELEMENTS, ALLOW_RETURN_HOLE)); } if_found.Else(); { // Cache miss, fallback to runtime. Add(object); Push(Add( isolate()->factory()->empty_string(), Runtime::FunctionForId(Runtime::kNumberToStringSkipCache), 1)); } if_found.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, 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. If the result is larger than the max // supported string length, we bailout to the runtime. This is done implicitly // when converting the result back to a smi in case the max string length // equals the max smi value. Otherwise, for platforms with 32-bit smis, we do HValue* length = AddUncasted(left_length, right_length); STATIC_ASSERT(String::kMaxLength <= Smi::kMaxValue); if (String::kMaxLength != Smi::kMaxValue) { IfBuilder if_nooverflow(this); if_nooverflow.If( length, Add(String::kMaxLength), Token::LTE); if_nooverflow.Then(); if_nooverflow.ElseDeopt("String length exceeds limit"); } 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_ASCII_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. ASSERT(HAllocate::CompatibleInstanceTypes(CONS_STRING_TYPE, CONS_ASCII_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. Handle map = isolate()->factory()->cons_ascii_string_map(); AddStoreMapConstantNoWriteBarrier(result, map); } if_onebyte.Else(); { // We can safely skip the write barrier for storing the map here. Handle map = isolate()->factory()->cons_string_map(); AddStoreMapConstantNoWriteBarrier(result, map); } if_onebyte.End(); // Initialize the cons string fields. Add(result, HObjectAccess::ForStringHashField(), Add(String::kEmptyHashField), INITIALIZING_STORE); Add(result, HObjectAccess::ForStringLength(), length, INITIALIZING_STORE); Add(result, HObjectAccess::ForConsStringFirst(), left, INITIALIZING_STORE); Add(result, HObjectAccess::ForConsStringSecond(), right, INITIALIZING_STORE); // 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) { ASSERT(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::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); ASSERT_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); ASSERT_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.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* ascii_string_map = Add(isolate()->factory()->ascii_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(ascii_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 = Pop(); size = AddUncasted(size, Add(static_cast( SeqString::kHeaderSize + kObjectAlignmentMask))); size->ClearFlag(HValue::kCanOverflow); size = AddUncasted( Token::BIT_AND, size, Add(static_cast( ~kObjectAlignmentMask))); // Allocate the string object. HAllocate does not care whether we pass // STRING_TYPE or ASCII_STRING_TYPE here, so we just use STRING_TYPE here. HAllocate* result = BuildAllocate( size, HType::String(), STRING_TYPE, allocation_mode); // We can safely skip the write barrier for storing map here. AddStoreMapNoWriteBarrier(result, map); // Initialize the string fields. Add(result, HObjectAccess::ForStringHashField(), Add(String::kEmptyHashField), INITIALIZING_STORE); Add(result, HObjectAccess::ForStringLength(), length, INITIALIZING_STORE); // 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_sameencodingandsequential.Else(); { // Fallback to the runtime to add the two strings. Add(left); Add(right); Push(Add(isolate()->factory()->empty_string(), 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, bool is_store, LoadKeyedHoleMode load_mode, KeyedAccessStoreMode store_mode) { ASSERT((!IsExternalArrayElementsKind(elements_kind) && !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 && is_store)) { checked_object->ClearGVNFlag(kDependsOnElementsKind); } bool fast_smi_only_elements = IsFastSmiElementsKind(elements_kind); bool fast_elements = IsFastObjectElementsKind(elements_kind); HValue* elements = AddLoadElements(checked_object); if (is_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(), top_info()); check_cow_map->ClearGVNFlag(kDependsOnElementsKind); } HInstruction* length = NULL; if (is_js_array) { length = Add( checked_object, HObjectAccess::ForArrayLength(elements_kind)); } else { length = AddLoadFixedArrayLength(elements); } length->set_type(HType::Smi()); HValue* checked_key = NULL; if (IsExternalArrayElementsKind(elements_kind) || IsFixedTypedArrayElementsKind(elements_kind)) { HValue* backing_store; if (IsExternalArrayElementsKind(elements_kind)) { backing_store = Add( elements, HObjectAccess::ForExternalArrayExternalPointer()); } else { backing_store = elements; } 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, elements_kind, is_store); negative_checker.ElseDeopt("Negative key encountered"); negative_checker.End(); length_checker.End(); return result; } else { ASSERT(store_mode == STANDARD_STORE); checked_key = Add(key, length); return AddElementAccess( backing_store, checked_key, val, checked_object, elements_kind, is_store); } } ASSERT(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 (is_store && IsFastSmiElementsKind(elements_kind) && !val->type().IsSmi()) { val = AddUncasted(val, Representation::Smi()); } if (IsGrowStoreMode(store_mode)) { NoObservableSideEffectsScope no_effects(this); elements = BuildCheckForCapacityGrow(checked_object, elements, elements_kind, length, key, is_js_array, is_store); checked_key = key; } else { checked_key = Add(key, length); if (is_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(), top_info()); check_cow_map->ClearGVNFlag(kDependsOnElementsKind); } } } return AddElementAccess(elements, checked_key, val, checked_object, elements_kind, is_store, load_mode); } HValue* HGraphBuilder::BuildAllocateArrayFromLength( JSArrayBuilder* array_builder, HValue* length_argument) { if (length_argument->IsConstant() && HConstant::cast(length_argument)->HasSmiValue()) { int array_length = HConstant::cast(length_argument)->Integer32Value(); HValue* new_object = array_length == 0 ? array_builder->AllocateEmptyArray() : array_builder->AllocateArray(length_argument, length_argument); return new_object; } HValue* constant_zero = graph()->GetConstant0(); HConstant* max_alloc_length = Add(JSObject::kInitialMaxFastElementArray); HInstruction* checked_length = Add(length_argument, max_alloc_length); IfBuilder if_builder(this); if_builder.If(checked_length, constant_zero, Token::EQ); if_builder.Then(); const int initial_capacity = JSArray::kPreallocatedArrayElements; HConstant* initial_capacity_node = Add(initial_capacity); Push(initial_capacity_node); // capacity Push(constant_zero); // length if_builder.Else(); if (!(top_info()->IsStub()) && IsFastPackedElementsKind(array_builder->kind())) { // We'll come back later with better (holey) feedback. if_builder.Deopt("Holey array despite packed elements_kind feedback"); } else { Push(checked_length); // capacity Push(checked_length); // length } if_builder.End(); // Figure out total size HValue* length = Pop(); HValue* capacity = Pop(); return array_builder->AllocateArray(capacity, length); } HValue* HGraphBuilder::BuildAllocateElements(ElementsKind kind, HValue* capacity) { int elements_size; InstanceType instance_type; if (IsFastDoubleElementsKind(kind)) { elements_size = kDoubleSize; instance_type = FIXED_DOUBLE_ARRAY_TYPE; } else { elements_size = kPointerSize; instance_type = FIXED_ARRAY_TYPE; } HConstant* elements_size_value = Add(elements_size); HValue* mul = AddUncasted(capacity, elements_size_value); mul->ClearFlag(HValue::kCanOverflow); HConstant* header_size = Add(FixedArray::kHeaderSize); HValue* total_size = AddUncasted(mul, header_size); total_size->ClearFlag(HValue::kCanOverflow); PretenureFlag pretenure_flag = !FLAG_allocation_site_pretenuring ? isolate()->heap()->GetPretenureMode() : NOT_TENURED; return Add(total_size, HType::JSArray(), pretenure_flag, instance_type); } 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(); AddStoreMapConstant(elements, map); Add(elements, HObjectAccess::ForFixedArrayLength(), capacity, INITIALIZING_STORE); } HValue* HGraphBuilder::BuildAllocateElementsAndInitializeElementsHeader( 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* new_elements = BuildAllocateElements(kind, capacity); BuildInitializeElementsHeader(new_elements, kind, capacity); return new_elements; } HInnerAllocatedObject* HGraphBuilder::BuildJSArrayHeader(HValue* array, HValue* array_map, AllocationSiteMode mode, ElementsKind elements_kind, HValue* allocation_site_payload, HValue* length_field) { Add(array, HObjectAccess::ForMap(), array_map, INITIALIZING_STORE); HConstant* empty_fixed_array = Add(isolate()->factory()->empty_fixed_array()); HObjectAccess access = HObjectAccess::ForPropertiesPointer(); Add(array, access, empty_fixed_array, INITIALIZING_STORE); Add(array, HObjectAccess::ForArrayLength(elements_kind), length_field, INITIALIZING_STORE); if (mode == TRACK_ALLOCATION_SITE) { BuildCreateAllocationMemento( array, Add(JSArray::kSize), allocation_site_payload); } int elements_location = JSArray::kSize; if (mode == TRACK_ALLOCATION_SITE) { elements_location += AllocationMemento::kSize; } HInnerAllocatedObject* elements = Add( array, Add(elements_location)); Add(array, HObjectAccess::ForElementsPointer(), elements, INITIALIZING_STORE); return elements; } HInstruction* HGraphBuilder::AddElementAccess( HValue* elements, HValue* checked_key, HValue* val, HValue* dependency, ElementsKind elements_kind, bool is_store, LoadKeyedHoleMode load_mode) { if (is_store) { ASSERT(val != NULL); if (elements_kind == EXTERNAL_UINT8_CLAMPED_ELEMENTS || elements_kind == UINT8_CLAMPED_ELEMENTS) { val = Add(val); } return Add(elements, checked_key, val, elements_kind, elements_kind == FAST_SMI_ELEMENTS ? STORE_TO_INITIALIZED_ENTRY : INITIALIZING_STORE); } ASSERT(!is_store); ASSERT(val == NULL); HLoadKeyed* load = Add( elements, checked_key, dependency, elements_kind, load_mode); if (FLAG_opt_safe_uint32_operations && (elements_kind == EXTERNAL_UINT32_ELEMENTS || elements_kind == UINT32_ELEMENTS)) { graph()->RecordUint32Instruction(load); } return load; } HLoadNamedField* HGraphBuilder::AddLoadElements(HValue* object) { return Add(object, HObjectAccess::ForElementsPointer()); } HLoadNamedField* HGraphBuilder::AddLoadFixedArrayLength(HValue* object) { return Add(object, HObjectAccess::ForFixedArrayLength()); } 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; } void HGraphBuilder::BuildNewSpaceArrayCheck(HValue* length, ElementsKind kind) { int element_size = IsFastDoubleElementsKind(kind) ? kDoubleSize : kPointerSize; int max_size = Page::kMaxRegularHeapObjectSize / element_size; max_size -= JSArray::kSize / element_size; HConstant* max_size_constant = Add(max_size); Add(length, max_size_constant); } HValue* HGraphBuilder::BuildGrowElementsCapacity(HValue* object, HValue* elements, ElementsKind kind, ElementsKind new_kind, HValue* length, HValue* new_capacity) { BuildNewSpaceArrayCheck(new_capacity, new_kind); HValue* new_elements = BuildAllocateElementsAndInitializeElementsHeader( new_kind, new_capacity); BuildCopyElements(elements, kind, new_elements, new_kind, length, new_capacity); Add(object, HObjectAccess::ForElementsPointer(), new_elements, INITIALIZING_STORE); return new_elements; } 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. Factory* factory = isolate()->factory(); double nan_double = FixedDoubleArray::hole_nan_as_double(); HValue* hole = IsFastSmiOrObjectElementsKind(elements_kind) ? Add(factory->the_hole_value()) : Add(nan_double); // Special loop unfolding case static const int kLoopUnfoldLimit = 8; STATIC_ASSERT(JSArray::kPreallocatedArrayElements <= kLoopUnfoldLimit); 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 <= kLoopUnfoldLimit) { initial_capacity = constant_to; } } // 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; } if (initial_capacity >= 0) { for (int i = 0; i < initial_capacity; i++) { HInstruction* key = Add(i); Add(elements, key, hole, elements_kind, PREINITIALIZING_STORE); } } else { LoopBuilder builder(this, context(), LoopBuilder::kPostIncrement); HValue* key = builder.BeginBody(from, to, Token::LT); Add(elements, key, hole, elements_kind, PREINITIALIZING_STORE); builder.EndBody(); } } void HGraphBuilder::BuildCopyElements(HValue* from_elements, ElementsKind from_elements_kind, HValue* to_elements, ElementsKind to_elements_kind, HValue* length, HValue* capacity) { 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(), capacity); } LoopBuilder builder(this, context(), LoopBuilder::kPostIncrement); HValue* key = builder.BeginBody(graph()->GetConstant0(), length, Token::LT); HValue* element = Add(from_elements, key, static_cast(NULL), 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(FixedDoubleArray::hole_nan_as_double()) : graph()->GetConstantHole(); Add(to_elements, key, hole_constant, kind, PREINITIALIZING_STORE); if_hole.Else(); HStoreKeyed* store = Add(to_elements, key, element, kind, INITIALIZING_STORE); store->SetFlag(HValue::kAllowUndefinedAsNaN); if_hole.End(); } else { HStoreKeyed* store = Add(to_elements, key, element, kind, INITIALIZING_STORE); store->SetFlag(HValue::kAllowUndefinedAsNaN); } builder.EndBody(); if (!pre_fill_with_holes && length != capacity) { // Fill unused capacity with the hole. BuildFillElementsWithHole(to_elements, to_elements_kind, key, capacity); } } HValue* HGraphBuilder::BuildCloneShallowArray(HValue* boilerplate, HValue* allocation_site, AllocationSiteMode mode, ElementsKind kind, int length) { NoObservableSideEffectsScope no_effects(this); // All sizes here are multiples of kPointerSize. int size = JSArray::kSize; if (mode == TRACK_ALLOCATION_SITE) { size += AllocationMemento::kSize; } HValue* size_in_bytes = Add(size); HInstruction* object = Add(size_in_bytes, HType::JSObject(), NOT_TENURED, JS_OBJECT_TYPE); // Copy the JS array part. for (int i = 0; i < JSArray::kSize; i += kPointerSize) { if ((i != JSArray::kElementsOffset) || (length == 0)) { HObjectAccess access = HObjectAccess::ForJSArrayOffset(i); Add(object, access, Add(boilerplate, access), INITIALIZING_STORE); } } // Create an allocation site info if requested. if (mode == TRACK_ALLOCATION_SITE) { BuildCreateAllocationMemento( object, Add(JSArray::kSize), allocation_site); } if (length > 0) { HValue* boilerplate_elements = AddLoadElements(boilerplate); HValue* object_elements; if (IsFastDoubleElementsKind(kind)) { HValue* elems_size = Add(FixedDoubleArray::SizeFor(length)); object_elements = Add(elems_size, HType::JSArray(), NOT_TENURED, FIXED_DOUBLE_ARRAY_TYPE); } else { HValue* elems_size = Add(FixedArray::SizeFor(length)); object_elements = Add(elems_size, HType::JSArray(), NOT_TENURED, FIXED_ARRAY_TYPE); } Add(object, HObjectAccess::ForElementsPointer(), object_elements, INITIALIZING_STORE); // Copy the elements array header. for (int i = 0; i < FixedArrayBase::kHeaderSize; i += kPointerSize) { HObjectAccess access = HObjectAccess::ForFixedArrayHeader(i); Add(object_elements, access, Add(boilerplate_elements, access), INITIALIZING_STORE); } // Copy the elements array contents. // TODO(mstarzinger): Teach HGraphBuilder::BuildCopyElements to unfold // copying loops with constant length up to a given boundary and use this // helper here instead. for (int i = 0; i < length; i++) { HValue* key_constant = Add(i); HInstruction* value = Add(boilerplate_elements, key_constant, static_cast(NULL), kind); Add(object_elements, key_constant, value, kind, INITIALIZING_STORE); } } return object; } void HGraphBuilder::BuildCompareNil( HValue* value, Type* type, HIfContinuation* continuation) { IfBuilder if_nil(this); bool some_case_handled = false; bool some_case_missing = false; if (type->Maybe(Type::Null())) { if (some_case_handled) if_nil.Or(); if_nil.If(value, graph()->GetConstantNull()); some_case_handled = true; } else { some_case_missing = true; } if (type->Maybe(Type::Undefined())) { if (some_case_handled) if_nil.Or(); if_nil.If(value, graph()->GetConstantUndefined()); some_case_handled = true; } else { some_case_missing = true; } if (type->Maybe(Type::Undetectable())) { if (some_case_handled) if_nil.Or(); if_nil.If(value); some_case_handled = true; } else { some_case_missing = true; } if (some_case_missing) { if_nil.Then(); if_nil.Else(); if (type->NumClasses() == 1) { BuildCheckHeapObject(value); // For ICs, the map checked below is a sentinel map that gets replaced by // the monomorphic map when the code is used as a template to generate a // new IC. For optimized functions, there is no sentinel map, the map // emitted below is the actual monomorphic map. BuildCheckMap(value, type->Classes().Current()); } else { if_nil.Deopt("Too many undetectable types"); } } if_nil.CaptureContinuation(continuation); } void HGraphBuilder::BuildCreateAllocationMemento( HValue* previous_object, HValue* previous_object_size, HValue* allocation_site) { ASSERT(allocation_site != NULL); HInnerAllocatedObject* allocation_memento = Add( previous_object, previous_object_size); AddStoreMapConstant( allocation_memento, isolate()->factory()->allocation_memento_map()); Add( allocation_memento, HObjectAccess::ForAllocationMementoSite(), allocation_site, INITIALIZING_STORE); if (FLAG_allocation_site_pretenuring) { HValue* memento_create_count = Add( allocation_site, 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); HStoreNamedField* store = Add( allocation_site, HObjectAccess::ForAllocationSiteOffset( AllocationSite::kPretenureCreateCountOffset), memento_create_count, INITIALIZING_STORE); // No write barrier needed to store a smi. store->SkipWriteBarrier(); } } HInstruction* HGraphBuilder::BuildGetNativeContext(HValue* closure) { // Get the global context, then the native context HInstruction* context = Add(closure, HObjectAccess::ForFunctionContextPointer()); HInstruction* global_object = Add(context, HObjectAccess::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)); HObjectAccess access = HObjectAccess::ForJSObjectOffset( GlobalObject::kNativeContextOffset); return Add(global_object, access); } HInstruction* HGraphBuilder::BuildGetNativeContext() { // Get the global context, then the native context HValue* global_object = Add( context(), HObjectAccess::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)); return Add( global_object, HObjectAccess::ForJSObjectOffset( GlobalObject::kNativeContextOffset)); } HInstruction* HGraphBuilder::BuildGetArrayFunction() { HInstruction* native_context = BuildGetNativeContext(); HInstruction* index = Add(static_cast(Context::ARRAY_FUNCTION_INDEX)); return Add( native_context, index, static_cast(NULL), FAST_ELEMENTS); } HGraphBuilder::JSArrayBuilder::JSArrayBuilder(HGraphBuilder* builder, ElementsKind kind, HValue* allocation_site_payload, HValue* constructor_function, AllocationSiteOverrideMode override_mode) : builder_(builder), kind_(kind), allocation_site_payload_(allocation_site_payload), constructor_function_(constructor_function) { ASSERT(!allocation_site_payload->IsConstant() || HConstant::cast(allocation_site_payload)->handle( builder_->isolate())->IsAllocationSite()); mode_ = override_mode == DISABLE_ALLOCATION_SITES ? DONT_TRACK_ALLOCATION_SITE : AllocationSite::GetMode(kind); } HGraphBuilder::JSArrayBuilder::JSArrayBuilder(HGraphBuilder* builder, ElementsKind kind, HValue* constructor_function) : builder_(builder), kind_(kind), mode_(DONT_TRACK_ALLOCATION_SITE), allocation_site_payload_(NULL), constructor_function_(constructor_function) { } HValue* HGraphBuilder::JSArrayBuilder::EmitMapCode() { if (!builder()->top_info()->IsStub()) { // A constant map is fine. Handle map(builder()->isolate()->get_initial_js_array_map(kind_), builder()->isolate()); return builder()->Add(map); } if (constructor_function_ != NULL && kind_ == GetInitialFastElementsKind()) { // No need for a context lookup if the kind_ matches the initial // map, because we can just load the map in that case. HObjectAccess access = HObjectAccess::ForPrototypeOrInitialMap(); return builder()->AddLoadNamedField(constructor_function_, access); } // TODO(mvstanton): we should always have a constructor function if we // are creating a stub. HInstruction* native_context = constructor_function_ != NULL ? builder()->BuildGetNativeContext(constructor_function_) : builder()->BuildGetNativeContext(); HInstruction* index = builder()->Add( static_cast(Context::JS_ARRAY_MAPS_INDEX)); HInstruction* map_array = builder()->Add( native_context, index, static_cast(NULL), FAST_ELEMENTS); HInstruction* kind_index = builder()->Add(kind_); return builder()->Add( map_array, kind_index, static_cast(NULL), FAST_ELEMENTS); } HValue* HGraphBuilder::JSArrayBuilder::EmitInternalMapCode() { // Find the map near the constructor function HObjectAccess access = HObjectAccess::ForPrototypeOrInitialMap(); return builder()->AddLoadNamedField(constructor_function_, access); } HValue* HGraphBuilder::JSArrayBuilder::EstablishAllocationSize( HValue* length_node) { ASSERT(length_node != NULL); int base_size = JSArray::kSize; if (mode_ == TRACK_ALLOCATION_SITE) { base_size += AllocationMemento::kSize; } STATIC_ASSERT(FixedDoubleArray::kHeaderSize == FixedArray::kHeaderSize); base_size += FixedArray::kHeaderSize; HInstruction* elements_size_value = builder()->Add(elements_size()); HInstruction* mul = HMul::NewImul(builder()->zone(), builder()->context(), length_node, elements_size_value); builder()->AddInstruction(mul); HInstruction* base = builder()->Add(base_size); HInstruction* total_size = HAdd::New(builder()->zone(), builder()->context(), base, mul); total_size->ClearFlag(HValue::kCanOverflow); builder()->AddInstruction(total_size); return total_size; } HValue* HGraphBuilder::JSArrayBuilder::EstablishEmptyArrayAllocationSize() { int base_size = JSArray::kSize; if (mode_ == TRACK_ALLOCATION_SITE) { base_size += AllocationMemento::kSize; } base_size += IsFastDoubleElementsKind(kind_) ? FixedDoubleArray::SizeFor(initial_capacity()) : FixedArray::SizeFor(initial_capacity()); return builder()->Add(base_size); } HValue* HGraphBuilder::JSArrayBuilder::AllocateEmptyArray() { HValue* size_in_bytes = EstablishEmptyArrayAllocationSize(); HConstant* capacity = builder()->Add(initial_capacity()); return AllocateArray(size_in_bytes, capacity, builder()->graph()->GetConstant0()); } HValue* HGraphBuilder::JSArrayBuilder::AllocateArray(HValue* capacity, HValue* length_field, FillMode fill_mode) { HValue* size_in_bytes = EstablishAllocationSize(capacity); return AllocateArray(size_in_bytes, capacity, length_field, fill_mode); } HValue* HGraphBuilder::JSArrayBuilder::AllocateArray(HValue* size_in_bytes, HValue* capacity, HValue* length_field, FillMode fill_mode) { // 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. capacity = builder()->AddUncasted(capacity, Representation::Smi()); length_field = builder()->AddUncasted(length_field, Representation::Smi()); // Allocate (dealing with failure appropriately) HAllocate* new_object = builder()->Add(size_in_bytes, HType::JSArray(), NOT_TENURED, JS_ARRAY_TYPE); // Folded array allocation should be aligned if it has fast double elements. if (IsFastDoubleElementsKind(kind_)) { new_object->MakeDoubleAligned(); } // Fill in the fields: map, properties, length HValue* map; if (allocation_site_payload_ == NULL) { map = EmitInternalMapCode(); } else { map = EmitMapCode(); } elements_location_ = builder()->BuildJSArrayHeader(new_object, map, mode_, kind_, allocation_site_payload_, length_field); // Initialize the elements builder()->BuildInitializeElementsHeader(elements_location_, kind_, capacity); if (fill_mode == FILL_WITH_HOLE) { builder()->BuildFillElementsWithHole(elements_location_, kind_, graph()->GetConstant0(), capacity); } return new_object; } HStoreNamedField* HGraphBuilder::AddStoreMapConstant(HValue *object, Handle map) { return Add(object, HObjectAccess::ForMap(), Add(map), INITIALIZING_STORE); } HValue* HGraphBuilder::AddLoadJSBuiltin(Builtins::JavaScript builtin) { HValue* global_object = Add( context(), HObjectAccess::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)); HObjectAccess access = HObjectAccess::ForJSObjectOffset( GlobalObject::kBuiltinsOffset); HValue* builtins = Add(global_object, access); HObjectAccess function_access = HObjectAccess::ForJSObjectOffset( JSBuiltinsObject::OffsetOfFunctionWithId(builtin)); return Add(builtins, function_access); } HOptimizedGraphBuilder::HOptimizedGraphBuilder(CompilationInfo* info) : HGraphBuilder(info), function_state_(NULL), initial_function_state_(this, info, NORMAL_RETURN), ast_context_(NULL), break_scope_(NULL), inlined_count_(0), globals_(10, info->zone()), inline_bailout_(false), osr_(new(info->zone()) HOsrBuilder(this)) { // 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->zone()); if (FLAG_emit_opt_code_positions) { SetSourcePosition(info->shared_info()->start_position()); } } 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, HBasicBlock* exit_block, HBasicBlock* continue_block) { if (continue_block != NULL) { if (exit_block != NULL) Goto(exit_block, continue_block); continue_block->SetJoinId(statement->ContinueId()); 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 = osr()->HasOsrEntryAt(statement) ? osr()->BuildOsrLoopEntry(statement) : BuildLoopEntry(); return loop_entry; } void HBasicBlock::FinishExit(HControlInstruction* instruction, int position) { Finish(instruction, position); ClearEnvironment(); } HGraph::HGraph(CompilationInfo* info) : 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), zone_(info->zone()), is_recursive_(false), use_optimistic_licm_(false), depends_on_empty_array_proto_elements_(false), type_change_checksum_(0), maximum_environment_size_(0), no_side_effects_scope_count_(0), disallow_adding_new_values_(false) { if (info->IsStub()) { HydrogenCodeStub* stub = info->code_stub(); CodeStubInterfaceDescriptor* descriptor = stub->GetInterfaceDescriptor(isolate_); start_environment_ = new(zone_) HEnvironment(zone_, descriptor->environment_length()); } else { start_environment_ = new(zone_) HEnvironment(NULL, info->scope(), info->closure(), zone_); } start_environment_->set_ast_id(BailoutId::FunctionEntry()); 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; ASSERT(!OptimizingCompilerThread::IsOptimizerThread(isolate())); 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, BitVector* visited) { PostorderProcessor* result = new(zone) PostorderProcessor(NULL); return result->SetupSuccessors(zone, block, NULL, visited); } PostorderProcessor* PerformStep(Zone* zone, BitVector* visited, ZoneList* order) { PostorderProcessor* next = PerformNonBacktrackingStep(zone, visited, order); if (next != NULL) { return next; } else { return Backtrack(zone, visited, 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, BitVector* visited) { if (block == NULL || visited->Contains(block->block_id()) || block->parent_loop_header() != loop_header) { kind_ = NONE; block_ = NULL; loop_ = NULL; loop_header_ = NULL; return this; } else { block_ = block; loop_ = NULL; visited->Add(block->block_id()); 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 { ASSERT(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) { ASSERT(block_->end()->FirstSuccessor() == NULL || order->Contains(block_->end()->FirstSuccessor()) || block_->end()->FirstSuccessor()->IsLoopHeader()); ASSERT(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, BitVector* visited, 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, BitVector* visited, ZoneList* order) { PostorderProcessor* parent = Pop(zone, visited, order); while (parent != NULL) { PostorderProcessor* next = parent->PerformNonBacktrackingStep(zone, visited, order); if (next != NULL) { return next; } else { parent = parent->Pop(zone, visited, order); } } return NULL; } PostorderProcessor* PerformNonBacktrackingStep( Zone* zone, BitVector* visited, 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_, visited); } break; case SUCCESSORS_OF_LOOP_HEADER: next_block = AdvanceSuccessors(); if (next_block != NULL) { PostorderProcessor* result = Push(zone); return result->SetupSuccessors(zone, next_block, block(), visited); } 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_, visited); } 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()); BitVector visited(blocks_.length(), zone()); ZoneList reverse_result(8, zone()); HBasicBlock* start = blocks_[0]; PostorderProcessor* postorder = PostorderProcessor::CreateEntryProcessor(zone(), start, &visited); while (postorder != NULL) { postorder = postorder->PerformStep(zone(), &visited, &reverse_result); } blocks_.Rewind(0); int index = 0; for (int i = reverse_result.length() - 1; i >= 0; --i) { HBasicBlock* b = reverse_result[i]; blocks_.Add(b, zone()); b->set_block_id(index++); } } 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) : owner_(owner), compilation_info_(info), call_context_(NULL), inlining_kind_(inlining_kind), function_return_(NULL), test_context_(NULL), entry_(NULL), arguments_object_(NULL), arguments_elements_(NULL), 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); } FunctionState::~FunctionState() { delete test_context_; owner_->set_function_state(outer_); } // 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()), for_typeof_(false) { owner->set_ast_context(this); // Push. #ifdef DEBUG ASSERT(owner->environment()->frame_type() == JS_FUNCTION); original_length_ = owner->environment()->length(); #endif } AstContext::~AstContext() { owner_->set_ast_context(outer_); // Pop. } EffectContext::~EffectContext() { ASSERT(owner()->HasStackOverflow() || owner()->current_block() == NULL || (owner()->environment()->length() == original_length_ && owner()->environment()->frame_type() == JS_FUNCTION)); } ValueContext::~ValueContext() { ASSERT(owner()->HasStackOverflow() || owner()->current_block() == NULL || (owner()->environment()->length() == original_length_ + 1 && owner()->environment()->frame_type() == JS_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 (!arguments_allowed() && value->CheckFlag(HValue::kIsArguments)) { owner()->Bailout(kBadValueContextForArgumentsValue); } owner()->Push(value); } void TestContext::ReturnValue(HValue* value) { BuildBranch(value); } void EffectContext::ReturnInstruction(HInstruction* instr, BailoutId ast_id) { ASSERT(!instr->IsControlInstruction()); owner()->AddInstruction(instr); if (instr->HasObservableSideEffects()) { owner()->Add(ast_id, REMOVABLE_SIMULATE); } } void EffectContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) { ASSERT(!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) { ASSERT(!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) { ASSERT(!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) { ASSERT(!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) { ASSERT(!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); } ToBooleanStub::Types 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()->set_bailout_reason(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_for_typeof(true); Visit(expr); } void HOptimizedGraphBuilder::VisitForControl(Expression* expr, HBasicBlock* true_block, HBasicBlock* false_block) { TestContext for_test(this, expr, true_block, false_block); Visit(expr); } void HOptimizedGraphBuilder::VisitArgument(Expression* expr) { CHECK_ALIVE(VisitForValue(expr)); Push(Add(Pop())); } void HOptimizedGraphBuilder::VisitArgumentList( ZoneList* arguments) { for (int i = 0; i < arguments->length(); i++) { CHECK_ALIVE(VisitArgument(arguments->at(i))); } } void HOptimizedGraphBuilder::VisitExpressions( ZoneList* exprs) { for (int i = 0; i < exprs->length(); ++i) { CHECK_ALIVE(VisitForValue(exprs->at(i))); } } bool HOptimizedGraphBuilder::BuildGraph() { if (current_info()->function()->is_generator()) { Bailout(kFunctionIsAGenerator); return false; } Scope* scope = current_info()->scope(); if (scope->HasIllegalRedeclaration()) { Bailout(kFunctionWithIllegalRedeclaration); return false; } if (scope->calls_eval()) { Bailout(kFunctionCallsEval); return false; } 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); // Handle implicit declaration of the function name in named function // expressions before other declarations. if (scope->is_function_scope() && scope->function() != NULL) { VisitVariableDeclaration(scope->function()); } VisitDeclarations(scope->declarations()); Add(BailoutId::Declarations()); Add(HStackCheck::kFunctionEntry); VisitStatements(current_info()->function()->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()); ASSERT(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); // 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(); Run(); // Must be performed before canonicalization to ensure that Canonicalize // will not remove semantically meaningful ToInt32 operations e.g. BIT_OR with // zero. if (FLAG_opt_safe_uint32_operations) Run(); if (FLAG_use_canonicalizing) Run(); if (FLAG_use_gvn) Run(); if (FLAG_check_elimination) Run(); if (FLAG_use_range) Run(); Run(); Run(); // Eliminate redundant stack checks on backwards branches. Run(); if (FLAG_array_bounds_checks_elimination) Run(); if (FLAG_array_bounds_checks_hoisting) 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); ASSERT(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 { ASSERT(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()); } while (!arguments.is_empty()) { Add(arguments.RemoveLast()); } } template HInstruction* HOptimizedGraphBuilder::PreProcessCall(Instruction* call) { PushArgumentsFromEnvironment(call->argument_count()); return call; } void HOptimizedGraphBuilder::SetUpScope(Scope* scope) { // 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. ASSERT_EQ(scope->num_parameters() + 1, environment()->parameter_count()); HArgumentsObject* arguments_object = New(environment()->parameter_count()); for (int i = 0; i < environment()->parameter_count(); ++i) { HInstruction* parameter = Add(i); arguments_object->AddArgument(parameter, zone()); environment()->Bind(i, parameter); } AddInstruction(arguments_object); graph()->SetArgumentsObject(arguments_object); HConstant* undefined_constant = graph()->GetConstantUndefined(); // Initialize specials and locals to undefined. for (int i = environment()->parameter_count() + 1; i < environment()->length(); ++i) { environment()->Bind(i, undefined_constant); } // Handle the arguments and arguments shadow variables specially (they do // not have declarations). if (scope->arguments() != NULL) { if (!scope->arguments()->IsStackAllocated()) { return Bailout(kContextAllocatedArguments); } environment()->Bind(scope->arguments(), graph()->GetArgumentsObject()); } } 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (stmt->scope() != NULL) { return Bailout(kScopedBlock); } BreakAndContinueInfo break_info(stmt); { BreakAndContinueScope push(&break_info, this); CHECK_BAILOUT(VisitStatements(stmt->statements())); } 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); VisitForEffect(stmt->expression()); } void HOptimizedGraphBuilder::VisitEmptyStatement(EmptyStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); } void HOptimizedGraphBuilder::VisitIfStatement(IfStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(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)); if (cond_true->HasPredecessor()) { cond_true->SetJoinId(stmt->ThenId()); set_current_block(cond_true); CHECK_BAILOUT(Visit(stmt->then_statement())); cond_true = current_block(); } else { cond_true = NULL; } if (cond_false->HasPredecessor()) { cond_false->SetJoinId(stmt->ElseId()); set_current_block(cond_false); CHECK_BAILOUT(Visit(stmt->else_statement())); cond_false = current_block(); } else { cond_false = NULL; } HBasicBlock* join = CreateJoin(cond_true, cond_false, stmt->IfId()); set_current_block(join); } } HBasicBlock* HOptimizedGraphBuilder::BreakAndContinueScope::Get( BreakableStatement* stmt, BreakType type, 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(); } ASSERT(current != NULL); // Always found (unless stack is malformed). 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); int drop_extra = 0; HBasicBlock* continue_block = break_scope()->Get( stmt->target(), BreakAndContinueScope::CONTINUE, &drop_extra); Drop(drop_extra); Goto(continue_block); set_current_block(NULL); } void HOptimizedGraphBuilder::VisitBreakStatement(BreakStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); int drop_extra = 0; HBasicBlock* break_block = break_scope()->Get( stmt->target(), BreakAndContinueScope::BREAK, &drop_extra); Drop(drop_extra); Goto(break_block); set_current_block(NULL); } void HOptimizedGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(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()) { TestContext* test = TestContext::cast(context); CHECK_ALIVE(VisitForEffect(stmt->expression())); Goto(test->if_true(), state); } else if (context->IsEffect()) { CHECK_ALIVE(VisitForEffect(stmt->expression())); Goto(function_return(), state); } else { ASSERT(context->IsValue()); CHECK_ALIVE(VisitForValue(stmt->expression())); HValue* return_value = Pop(); HValue* receiver = environment()->arguments_environment()->Lookup(0); HHasInstanceTypeAndBranch* typecheck = New(return_value, FIRST_SPEC_OBJECT_TYPE, LAST_SPEC_OBJECT_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 { ASSERT(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()) { CHECK_ALIVE(VisitForEffect(stmt->expression())); Goto(function_return(), state); } else { ASSERT(context->IsValue()); CHECK_ALIVE(VisitForValue(stmt->expression())); AddLeaveInlined(Pop(), state); } } set_current_block(NULL); } void HOptimizedGraphBuilder::VisitWithStatement(WithStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kWithStatement); } void HOptimizedGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); // We only optimize switch statements with a bounded number of clauses. const int kCaseClauseLimit = 128; ZoneList* clauses = stmt->cases(); int clause_count = clauses->length(); ZoneList body_blocks(clause_count, zone()); if (clause_count > kCaseClauseLimit) { return Bailout(kSwitchStatementTooManyClauses); } CHECK_ALIVE(VisitForValue(stmt->tag())); Add(stmt->EntryId()); HValue* tag_value = Top(); Type* tag_type = stmt->tag()->bounds().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_ALIVE(VisitForValue(clause->label())); HValue* label_value = Pop(); Type* label_type = clause->label()->bounds().lower; Type* combined_type = clause->compare_type(); HControlInstruction* compare = BuildCompareInstruction( Token::EQ_STRICT, tag_value, label_value, tag_type, label_type, combined_type, stmt->tag()->position(), clause->label()->position(), 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); { 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, HBasicBlock* loop_entry, BreakAndContinueInfo* break_info) { BreakAndContinueScope push(break_info, this); Add(stmt->StackCheckId()); HStackCheck* stack_check = HStackCheck::cast(Add(HStackCheck::kBackwardsBranch)); ASSERT(loop_entry->IsLoopHeader()); loop_entry->loop_information()->set_stack_check(stack_check); CHECK_BAILOUT(Visit(stmt->body())); } void HOptimizedGraphBuilder::VisitDoWhileStatement(DoWhileStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); ASSERT(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); BreakAndContinueInfo break_info(stmt); CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info)); HBasicBlock* body_exit = JoinContinue(stmt, current_block(), break_info.continue_block()); HBasicBlock* loop_successor = NULL; if (body_exit != NULL && !stmt->cond()->ToBooleanIsTrue()) { set_current_block(body_exit); loop_successor = graph()->CreateBasicBlock(); if (stmt->cond()->ToBooleanIsFalse()) { 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); ASSERT(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); // If the condition is constant true, do not generate a branch. HBasicBlock* loop_successor = NULL; if (!stmt->cond()->ToBooleanIsTrue()) { 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); if (current_block() != NULL) { CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info)); } HBasicBlock* body_exit = JoinContinue(stmt, 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (stmt->init() != NULL) { CHECK_ALIVE(Visit(stmt->init())); } ASSERT(current_block() != NULL); HBasicBlock* loop_entry = BuildLoopEntry(stmt); HBasicBlock* loop_successor = NULL; if (stmt->cond() != 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); if (current_block() != NULL) { CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info)); } HBasicBlock* body_exit = JoinContinue(stmt, 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (!FLAG_optimize_for_in) { return Bailout(kForInStatementOptimizationIsDisabled); } if (stmt->for_in_type() != ForInStatement::FAST_FOR_IN) { return Bailout(kForInStatementIsNotFastCase); } 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. HInstruction* map = Add(enumerable); Add(stmt->PrepareId()); HInstruction* array = Add( enumerable, map, DescriptorArray::kEnumCacheBridgeCacheIndex); HInstruction* enum_length = Add(map); HInstruction* start_index = Add(0); Push(map); Push(array); Push(enum_length); Push(start_index); HInstruction* index_cache = Add( enumerable, map, DescriptorArray::kEnumCacheBridgeIndicesCacheIndex); HForInCacheArray::cast(array)->set_index_cache( HForInCacheArray::cast(index_cache)); HBasicBlock* loop_entry = BuildLoopEntry(stmt); HValue* index = environment()->ExpressionStackAt(0); HValue* limit = environment()->ExpressionStackAt(1); // 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); HValue* key = Add( environment()->ExpressionStackAt(2), // Enum cache. environment()->ExpressionStackAt(0), // Iteration index. environment()->ExpressionStackAt(0), FAST_ELEMENTS); // Check if the expected map still matches that of the enumerable. // If not just deoptimize. Add(environment()->ExpressionStackAt(4), environment()->ExpressionStackAt(3)); Bind(each_var, key); BreakAndContinueInfo break_info(stmt, 5); CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info)); HBasicBlock* body_exit = JoinContinue(stmt, current_block(), break_info.continue_block()); if (body_exit != NULL) { set_current_block(body_exit); HValue* current_index = Pop(); Push(AddUncasted(current_index, graph()->GetConstant1())); 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kForOfStatement); } void HOptimizedGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kTryCatchStatement); } void HOptimizedGraphBuilder::VisitTryFinallyStatement( TryFinallyStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kTryFinallyStatement); } void HOptimizedGraphBuilder::VisitDebuggerStatement(DebuggerStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kDebuggerStatement); } void HOptimizedGraphBuilder::VisitCaseClause(CaseClause* clause) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitFunctionLiteral(FunctionLiteral* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); Handle shared_info = expr->shared_info(); if (shared_info.is_null()) { shared_info = Compiler::BuildFunctionInfo(expr, current_info()->script()); } // We also have a stack overflow if the recursive compilation did. if (HasStackOverflow()) return; HFunctionLiteral* instr = New(shared_info, expr->pretenure()); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitNativeFunctionLiteral( NativeFunctionLiteral* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); return Bailout(kNativeFunctionLiteral); } void HOptimizedGraphBuilder::VisitConditional(Conditional* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(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()); } } } HOptimizedGraphBuilder::GlobalPropertyAccess HOptimizedGraphBuilder::LookupGlobalProperty( Variable* var, LookupResult* lookup, bool is_store) { if (var->is_this() || !current_info()->has_global_object()) { return kUseGeneric; } Handle global(current_info()->global_object()); global->Lookup(*var->name(), lookup); if (!lookup->IsNormal() || (is_store && lookup->IsReadOnly()) || lookup->holder() != *global) { return kUseGeneric; } return kUseCell; } HValue* HOptimizedGraphBuilder::BuildContextChainWalk(Variable* var) { ASSERT(var->IsContextSlot()); HValue* context = environment()->context(); int length = current_info()->scope()->ContextChainLength(var->scope()); while (length-- > 0) { context = AddLoadNamedField( context, HObjectAccess::ForContextSlot(Context::PREVIOUS_INDEX)); } return context; } void HOptimizedGraphBuilder::VisitVariableProxy(VariableProxy* expr) { if (expr->is_this()) { current_info()->set_this_has_uses(true); } ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); Variable* variable = expr->var(); switch (variable->location()) { case Variable::UNALLOCATED: { if (IsLexicalVariableMode(variable->mode())) { // TODO(rossberg): should this be an ASSERT? 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()); } LookupResult lookup(isolate()); GlobalPropertyAccess type = LookupGlobalProperty(variable, &lookup, false); if (type == kUseCell && current_info()->global_object()->IsAccessCheckNeeded()) { type = kUseGeneric; } if (type == kUseCell) { Handle global(current_info()->global_object()); Handle cell(global->GetPropertyCell(&lookup)); if (cell->type()->IsConstant()) { cell->AddDependentCompilationInfo(top_info()); Handle constant_object = cell->type()->AsConstant(); if (constant_object->IsConsString()) { constant_object = FlattenGetString(Handle::cast(constant_object)); } HConstant* constant = New(constant_object); return ast_context()->ReturnInstruction(constant, expr->id()); } else { HLoadGlobalCell* instr = New(cell, lookup.GetPropertyDetails()); return ast_context()->ReturnInstruction(instr, expr->id()); } } else { HValue* global_object = Add( context(), HObjectAccess::ForContextSlot( Context::GLOBAL_OBJECT_INDEX)); HLoadGlobalGeneric* instr = New(global_object, variable->name(), ast_context()->is_for_typeof()); return ast_context()->ReturnInstruction(instr, expr->id()); } } case Variable::PARAMETER: case Variable::LOCAL: { HValue* value = LookupAndMakeLive(variable); if (value == graph()->GetConstantHole()) { ASSERT(IsDeclaredVariableMode(variable->mode()) && variable->mode() != VAR); return Bailout(kReferenceToUninitializedVariable); } return ast_context()->ReturnValue(value); } case Variable::CONTEXT: { HValue* context = BuildContextChainWalk(variable); HLoadContextSlot* instr = new(zone()) HLoadContextSlot(context, variable); return ast_context()->ReturnInstruction(instr, expr->id()); } case Variable::LOOKUP: return Bailout(kReferenceToAVariableWhichRequiresDynamicLookup); } } void HOptimizedGraphBuilder::VisitLiteral(Literal* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); HConstant* instr = New(expr->value()); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitRegExpLiteral(RegExpLiteral* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); Handle closure = function_state()->compilation_info()->closure(); Handle literals(closure->literals()); HRegExpLiteral* instr = New(literals, expr->pattern(), expr->flags(), expr->literal_index()); return ast_context()->ReturnInstruction(instr, expr->id()); } static bool CanInlinePropertyAccess(Map* type) { return type->IsJSObjectMap() && !type->is_dictionary_map() && !type->has_named_interceptor(); } static void LookupInPrototypes(Handle map, Handle name, LookupResult* lookup) { while (map->prototype()->IsJSObject()) { Handle holder(JSObject::cast(map->prototype())); map = Handle(holder->map()); if (!CanInlinePropertyAccess(*map)) break; map->LookupDescriptor(*holder, *name, lookup); if (lookup->IsFound()) return; } lookup->NotFound(); } // Tries to find a JavaScript accessor of the given name in the prototype chain // starting at the given map. Return true iff there is one, including the // corresponding AccessorPair plus its holder (which could be null when the // accessor is found directly in the given map). static bool LookupAccessorPair(Handle map, Handle name, Handle* accessors, Handle* holder) { Isolate* isolate = map->GetIsolate(); LookupResult lookup(isolate); // Check for a JavaScript accessor directly in the map. map->LookupDescriptor(NULL, *name, &lookup); if (lookup.IsPropertyCallbacks()) { Handle callback(lookup.GetValueFromMap(*map), isolate); if (!callback->IsAccessorPair()) return false; *accessors = Handle::cast(callback); *holder = Handle(); return true; } // Everything else, e.g. a field, can't be an accessor call. if (lookup.IsFound()) return false; // Check for a JavaScript accessor somewhere in the proto chain. LookupInPrototypes(map, name, &lookup); if (lookup.IsPropertyCallbacks()) { Handle callback(lookup.GetValue(), isolate); if (!callback->IsAccessorPair()) return false; *accessors = Handle::cast(callback); *holder = Handle(lookup.holder()); return true; } // We haven't found a JavaScript accessor anywhere. return false; } static bool LookupSetter(Handle map, Handle name, Handle* setter, Handle* holder) { Handle accessors; if (LookupAccessorPair(map, name, &accessors, holder) && accessors->setter()->IsJSFunction()) { Handle func(JSFunction::cast(accessors->setter())); CallOptimization call_optimization(func); // TODO(dcarney): temporary hack unless crankshaft can handle api calls. if (call_optimization.is_simple_api_call()) return false; *setter = func; return true; } return false; } // 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()) { Handle result = JSObject::TryMigrateInstance(boilerplate); if (result.is_null()) return false; } ASSERT(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->HasFastObjectElements()) { 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()) { 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.type() != FIELD) continue; int index = descriptors->GetFieldIndex(i); if ((*max_properties)-- == 0) return false; Handle value(boilerplate->InObjectPropertyAt(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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); expr->BuildConstantProperties(isolate()); 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->literals()->get(expr->literal_index()), isolate()); Handle site; Handle boilerplate; if (!literals_cell->IsUndefined()) { // 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 usage_context(isolate(), site, false); usage_context.EnterNewScope(); literal = BuildFastLiteral(boilerplate, &usage_context); usage_context.ExitScope(site, boilerplate); } else { NoObservableSideEffectsScope no_effects(this); Handle closure_literals(closure->literals(), isolate()); Handle constant_properties = expr->constant_properties(); int literal_index = expr->literal_index(); int flags = expr->fast_elements() ? ObjectLiteral::kFastElements : ObjectLiteral::kNoFlags; flags |= expr->has_function() ? ObjectLiteral::kHasFunction : ObjectLiteral::kNoFlags; Add(Add(closure_literals)); Add(Add(literal_index)); Add(Add(constant_properties)); Add(Add(flags)); // TODO(mvstanton): Add a flag to turn off creation of any // AllocationMementos for this call: we are in crankshaft and should have // learned enough about transition behavior to stop emitting mementos. Runtime::FunctionId function_id = Runtime::kCreateObjectLiteral; literal = Add(isolate()->factory()->empty_string(), 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); expr->CalculateEmitStore(zone()); for (int i = 0; i < expr->properties()->length(); i++) { ObjectLiteral::Property* property = expr->properties()->at(i); if (property->IsCompileTimeValue()) continue; Literal* key = property->key(); Expression* value = property->value(); switch (property->kind()) { case ObjectLiteral::Property::MATERIALIZED_LITERAL: ASSERT(!CompileTimeValue::IsCompileTimeValue(value)); // Fall through. case ObjectLiteral::Property::COMPUTED: if (key->value()->IsInternalizedString()) { if (property->emit_store()) { CHECK_ALIVE(VisitForValue(value)); HValue* value = Pop(); Handle map = property->GetReceiverType(); Handle name = property->key()->AsPropertyName(); HInstruction* store; if (map.is_null()) { // If we don't know the monomorphic type, do a generic store. CHECK_ALIVE(store = BuildStoreNamedGeneric(literal, name, value)); } else { #if DEBUG Handle setter; Handle holder; ASSERT(!LookupSetter(map, name, &setter, &holder)); #endif CHECK_ALIVE(store = BuildStoreNamedMonomorphic(literal, name, value, map)); } AddInstruction(store); if (store->HasObservableSideEffects()) { Add(key->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(); } } if (expr->has_function()) { // Return the result of the transformation to fast properties // instead of the original since this operation changes the map // of the object. This makes sure that the original object won't // be used by other optimized code before it is transformed // (e.g. because of code motion). HToFastProperties* result = Add(Pop()); return ast_context()->ReturnValue(result); } else { return ast_context()->ReturnValue(Pop()); } } void HOptimizedGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); expr->BuildConstantElements(isolate()); ZoneList* subexprs = expr->values(); int length = subexprs->length(); HInstruction* literal; Handle site; Handle literals(environment()->closure()->literals(), isolate()); bool uninitialized = false; Handle literals_cell(literals->get(expr->literal_index()), isolate()); Handle boilerplate_object; if (literals_cell->IsUndefined()) { uninitialized = true; Handle raw_boilerplate = Runtime::CreateArrayLiteralBoilerplate( isolate(), literals, expr->constant_elements()); if (raw_boilerplate.is_null()) { return Bailout(kArrayBoilerplateCreationFailed); } boilerplate_object = Handle::cast(raw_boilerplate); AllocationSiteCreationContext creation_context(isolate()); site = creation_context.EnterNewScope(); if (JSObject::DeepWalk(boilerplate_object, &creation_context).is_null()) { return Bailout(kArrayBoilerplateCreationFailed); } creation_context.ExitScope(site, boilerplate_object); literals->set(expr->literal_index(), *site); if (boilerplate_object->elements()->map() == isolate()->heap()->fixed_cow_array_map()) { isolate()->counters()->cow_arrays_created_runtime()->Increment(); } } else { ASSERT(literals_cell->IsAllocationSite()); site = Handle::cast(literals_cell); boilerplate_object = Handle( JSObject::cast(site->transition_info()), isolate()); } ASSERT(!boilerplate_object.is_null()); ASSERT(site->SitePointsToLiteral()); ElementsKind boilerplate_elements_kind = boilerplate_object->GetElementsKind(); // Check whether to use fast or slow deep-copying for boilerplate. int max_properties = kMaxFastLiteralProperties; if (IsFastLiteral(boilerplate_object, kMaxFastLiteralDepth, &max_properties)) { AllocationSiteUsageContext usage_context(isolate(), site, false); usage_context.EnterNewScope(); literal = BuildFastLiteral(boilerplate_object, &usage_context); usage_context.ExitScope(site, boilerplate_object); } else { NoObservableSideEffectsScope no_effects(this); // Boilerplate already exists and constant elements are never accessed, // pass an empty fixed array to the runtime function instead. Handle constants = isolate()->factory()->empty_fixed_array(); int literal_index = expr->literal_index(); int flags = expr->depth() == 1 ? ArrayLiteral::kShallowElements : ArrayLiteral::kNoFlags; flags |= ArrayLiteral::kDisableMementos; Add(Add(literals)); Add(Add(literal_index)); Add(Add(constants)); Add(Add(flags)); // TODO(mvstanton): Consider a flag to turn off creation of any // AllocationMementos for this call: we are in crankshaft and should have // learned enough about transition behavior to stop emitting mementos. Runtime::FunctionId function_id = Runtime::kCreateArrayLiteral; literal = Add(isolate()->factory()->empty_string(), Runtime::FunctionForId(function_id), 4); // De-opt if elements kind changed from boilerplate_elements_kind. Handle map = Handle(boilerplate_object->map(), isolate()); literal = Add(literal, map, top_info()); } // The array is expected in the bailout environment during computation // of the property values and is the value of the entire expression. Push(literal); // The literal index is on the stack, too. Push(Add(expr->literal_index())); HInstruction* elements = NULL; for (int i = 0; i < length; i++) { Expression* subexpr = subexprs->at(i); // 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); 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: { HStoreKeyed* instr = Add(elements, key, value, boilerplate_elements_kind, INITIALIZING_STORE); instr->SetUninitialized(uninitialized); break; } default: UNREACHABLE(); break; } Add(expr->GetIdForElement(i)); } Drop(1); // array literal index return ast_context()->ReturnValue(Pop()); } HCheckMaps* HOptimizedGraphBuilder::AddCheckMap(HValue* object, Handle map) { BuildCheckHeapObject(object); return Add(object, map, top_info()); } HInstruction* HOptimizedGraphBuilder::BuildStoreNamedField( HValue* checked_object, Handle name, HValue* value, Handle map, LookupResult* lookup) { ASSERT(lookup->IsFound()); // If the property does not exist yet, we have to check that it wasn't made // readonly or turned into a setter by some meanwhile modifications on the // prototype chain. if (!lookup->IsProperty() && map->prototype()->IsJSReceiver()) { Object* proto = map->prototype(); // First check that the prototype chain isn't affected already. LookupResult proto_result(isolate()); proto->Lookup(*name, &proto_result); if (proto_result.IsProperty()) { // If the inherited property could induce readonly-ness, bail out. if (proto_result.IsReadOnly() || !proto_result.IsCacheable()) { Bailout(kImproperObjectOnPrototypeChainForStore); return NULL; } // We only need to check up to the preexisting property. proto = proto_result.holder(); } else { // Otherwise, find the top prototype. while (proto->GetPrototype(isolate())->IsJSObject()) { proto = proto->GetPrototype(isolate()); } ASSERT(proto->GetPrototype(isolate())->IsNull()); } ASSERT(proto->IsJSObject()); BuildCheckPrototypeMaps( Handle(JSObject::cast(map->prototype())), Handle(JSObject::cast(proto))); } HObjectAccess field_access = HObjectAccess::ForField(map, lookup, name); bool transition_to_field = lookup->IsTransitionToField(*map); HStoreNamedField *instr; if (FLAG_track_double_fields && field_access.representation().IsDouble()) { 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); PretenureFlag pretenure_flag = !FLAG_allocation_site_pretenuring ? isolate()->heap()->GetPretenureMode() : NOT_TENURED; HInstruction* heap_number = Add(heap_number_size, HType::HeapNumber(), pretenure_flag, HEAP_NUMBER_TYPE); AddStoreMapConstant(heap_number, isolate()->factory()->heap_number_map()); Add(heap_number, HObjectAccess::ForHeapNumberValue(), value, INITIALIZING_STORE); instr = New(checked_object->ActualValue(), heap_number_access, heap_number, INITIALIZING_STORE); } else { // Already holds a HeapNumber; load the box and write its value field. HInstruction* heap_number = Add(checked_object, heap_number_access); heap_number->set_type(HType::HeapNumber()); instr = New(heap_number, HObjectAccess::ForHeapNumberValue(), value, STORE_TO_INITIALIZED_ENTRY); } } else { // This is a normal store. instr = New( checked_object->ActualValue(), field_access, value, transition_to_field ? INITIALIZING_STORE : STORE_TO_INITIALIZED_ENTRY); } if (transition_to_field) { Handle transition(lookup->GetTransitionMapFromMap(*map)); HConstant* transition_constant = Add(transition); instr->SetTransition(transition_constant, top_info()); // TODO(fschneider): Record the new map type of the object in the IR to // enable elimination of redundant checks after the transition store. instr->SetGVNFlag(kChangesMaps); } return instr; } HInstruction* HOptimizedGraphBuilder::BuildStoreNamedGeneric( HValue* object, Handle name, HValue* value) { return New( object, name, value, function_strict_mode_flag()); } // Sets the lookup result and returns true if the load/store can be inlined. static bool ComputeStoreField(Handle type, Handle name, LookupResult* lookup, bool lookup_transition = true) { ASSERT(!type->is_observed()); if (!CanInlinePropertyAccess(*type)) { lookup->NotFound(); return false; } // If we directly find a field, the access can be inlined. type->LookupDescriptor(NULL, *name, lookup); if (lookup->IsField()) return true; if (!lookup_transition) return false; type->LookupTransition(NULL, *name, lookup); return lookup->IsTransitionToField(*type) && (type->unused_property_fields() > 0); } HInstruction* HOptimizedGraphBuilder::BuildStoreNamedMonomorphic( HValue* object, Handle name, HValue* value, Handle map) { // Handle a store to a known field. LookupResult lookup(isolate()); if (ComputeStoreField(map, name, &lookup)) { HCheckMaps* checked_object = AddCheckMap(object, map); return BuildStoreNamedField(checked_object, name, value, map, &lookup); } // No luck, do a generic store. return BuildStoreNamedGeneric(object, name, value); } bool HOptimizedGraphBuilder::PropertyAccessInfo::IsCompatibleForLoad( PropertyAccessInfo* info) { if (!CanInlinePropertyAccess(*map_)) return false; if (!LookupDescriptor()) return false; if (!lookup_.IsFound()) { return (!info->lookup_.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 (lookup_.IsPropertyCallbacks()) { return accessor_.is_identical_to(info->accessor_); } if (lookup_.IsConstant()) { return constant_.is_identical_to(info->constant_); } ASSERT(lookup_.IsField()); if (!info->lookup_.IsField()) return false; Representation r = access_.representation(); if (!info->access_.representation().IsCompatibleForLoad(r)) return false; if (info->access_.offset() != access_.offset()) return false; if (info->access_.IsInobject() != access_.IsInobject()) return false; info->GeneralizeRepresentation(r); return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::LookupDescriptor() { map_->LookupDescriptor(NULL, *name_, &lookup_); return LoadResult(map_); } bool HOptimizedGraphBuilder::PropertyAccessInfo::LoadResult(Handle map) { if (lookup_.IsField()) { access_ = HObjectAccess::ForField(map, &lookup_, name_); } else if (lookup_.IsPropertyCallbacks()) { Handle callback(lookup_.GetValueFromMap(*map), isolate()); if (!callback->IsAccessorPair()) return false; Object* getter = Handle::cast(callback)->getter(); if (!getter->IsJSFunction()) return false; Handle accessor = handle(JSFunction::cast(getter)); CallOptimization call_optimization(accessor); // TODO(dcarney): temporary hack unless crankshaft can handle api calls. if (call_optimization.is_simple_api_call()) return false; accessor_ = accessor; } else if (lookup_.IsConstant()) { constant_ = handle(lookup_.GetConstantFromMap(*map), isolate()); } return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::LookupInPrototypes() { Handle map = map_; 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)) { lookup_.NotFound(); return false; } map->LookupDescriptor(*holder_, *name_, &lookup_); if (lookup_.IsFound()) return LoadResult(map); } lookup_.NotFound(); return true; } bool HOptimizedGraphBuilder::PropertyAccessInfo::CanLoadMonomorphic() { if (!CanInlinePropertyAccess(*map_)) return IsStringLength(); if (IsJSObjectFieldAccessor()) return true; if (!LookupDescriptor()) return false; if (lookup_.IsFound()) return true; return LookupInPrototypes(); } bool HOptimizedGraphBuilder::PropertyAccessInfo::CanLoadAsMonomorphic( SmallMapList* types) { ASSERT(map_.is_identical_to(types->first())); if (!CanLoadMonomorphic()) return false; if (types->length() > kMaxLoadPolymorphism) return false; if (IsStringLength()) { for (int i = 1; i < types->length(); ++i) { if (types->at(i)->instance_type() >= FIRST_NONSTRING_TYPE) return false; } return true; } if (IsArrayLength()) { bool is_fast = IsFastElementsKind(map_->elements_kind()); for (int i = 1; i < types->length(); ++i) { Handle test_map = types->at(i); if (test_map->instance_type() != JS_ARRAY_TYPE) return false; if (IsFastElementsKind(test_map->elements_kind()) != is_fast) { return false; } } return true; } if (IsJSObjectFieldAccessor()) { InstanceType instance_type = map_->instance_type(); for (int i = 1; i < types->length(); ++i) { if (types->at(i)->instance_type() != instance_type) return false; } return true; } for (int i = 1; i < types->length(); ++i) { PropertyAccessInfo test_info(isolate(), types->at(i), name_); if (!test_info.IsCompatibleForLoad(this)) return false; } return true; } HInstruction* HOptimizedGraphBuilder::BuildLoadMonomorphic( PropertyAccessInfo* info, HValue* object, HInstruction* checked_object, BailoutId ast_id, BailoutId return_id, bool can_inline_accessor) { HObjectAccess access = HObjectAccess::ForMap(); // bogus default if (info->GetJSObjectFieldAccess(&access)) { return New(checked_object, access); } HValue* checked_holder = checked_object; if (info->has_holder()) { Handle prototype(JSObject::cast(info->map()->prototype())); checked_holder = BuildCheckPrototypeMaps(prototype, info->holder()); } if (!info->lookup()->IsFound()) return graph()->GetConstantUndefined(); if (info->lookup()->IsField()) { return BuildLoadNamedField(checked_holder, info->access()); } if (info->lookup()->IsPropertyCallbacks()) { Push(checked_object); if (FLAG_inline_accessors && can_inline_accessor && TryInlineGetter(info->accessor(), ast_id, return_id)) { return NULL; } Add(Pop()); return BuildCallConstantFunction(info->accessor(), 1); } ASSERT(info->lookup()->IsConstant()); return New(info->constant()); } void HOptimizedGraphBuilder::HandlePolymorphicLoadNamedField( BailoutId ast_id, BailoutId return_id, HValue* object, SmallMapList* types, Handle name) { // Something did not match; must use a polymorphic load. int count = 0; HBasicBlock* join = NULL; for (int i = 0; i < types->length() && count < kMaxLoadPolymorphism; ++i) { PropertyAccessInfo info(isolate(), types->at(i), name); if (info.CanLoadMonomorphic()) { if (count == 0) { BuildCheckHeapObject(object); join = graph()->CreateBasicBlock(); } ++count; HBasicBlock* if_true = graph()->CreateBasicBlock(); HBasicBlock* if_false = graph()->CreateBasicBlock(); HCompareMap* compare = New( object, info.map(), if_true, if_false); FinishCurrentBlock(compare); set_current_block(if_true); HInstruction* load = BuildLoadMonomorphic( &info, object, compare, ast_id, return_id, FLAG_polymorphic_inlining); if (load == NULL) { if (HasStackOverflow()) return; } else { if (!load->IsLinked()) { AddInstruction(load); } if (!ast_context()->IsEffect()) Push(load); } 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 == types->length() && FLAG_deoptimize_uncommon_cases) { // Because the deopt may be the only path in the polymorphic load, make sure // that the environment stack matches the depth on deopt that it otherwise // would have had after a successful load. if (!ast_context()->IsEffect()) Push(graph()->GetConstant0()); FinishExitWithHardDeoptimization("Unknown map in polymorphic load", join); } else { HInstruction* load = Add(object, name); if (!ast_context()->IsEffect()) Push(load); if (join != NULL) { Goto(join); } else { Add(ast_id, REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop()); return; } } ASSERT(join != NULL); join->SetJoinId(ast_id); set_current_block(join); if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop()); } bool HOptimizedGraphBuilder::TryStorePolymorphicAsMonomorphic( BailoutId assignment_id, HValue* object, HValue* value, SmallMapList* types, Handle name) { // Use monomorphic store if property lookup results in the same field index // for all maps. Requires special map check on the set of all handled maps. if (types->length() > kMaxStorePolymorphism) return false; LookupResult lookup(isolate()); int count; Representation representation = Representation::None(); HObjectAccess access = HObjectAccess::ForMap(); // initial value unused. for (count = 0; count < types->length(); ++count) { Handle map = types->at(count); // Pass false to ignore transitions. if (!ComputeStoreField(map, name, &lookup, false)) break; ASSERT(!map->is_observed()); HObjectAccess new_access = HObjectAccess::ForField(map, &lookup, name); Representation new_representation = new_access.representation(); if (count == 0) { // First time through the loop; set access and representation. access = new_access; representation = new_representation; } else if (!representation.IsCompatibleForStore(new_representation)) { // Representations did not match. break; } else if (access.offset() != new_access.offset()) { // Offsets did not match. break; } else if (access.IsInobject() != new_access.IsInobject()) { // In-objectness did not match. break; } } if (count != types->length()) return false; // Everything matched; can use monomorphic store. BuildCheckHeapObject(object); HCheckMaps* checked_object = Add(object, types); HInstruction* store; CHECK_ALIVE_OR_RETURN( store = BuildStoreNamedField( checked_object, name, value, types->at(count - 1), &lookup), true); if (!ast_context()->IsEffect()) Push(value); AddInstruction(store); Add(assignment_id); if (!ast_context()->IsEffect()) Drop(1); ast_context()->ReturnValue(value); return true; } void HOptimizedGraphBuilder::HandlePolymorphicStoreNamedField( BailoutId assignment_id, HValue* object, HValue* value, SmallMapList* types, Handle name) { if (TryStorePolymorphicAsMonomorphic( assignment_id, object, value, types, name)) { return; } // TODO(ager): We should recognize when the prototype chains for different // maps are identical. In that case we can avoid repeatedly generating the // same prototype map checks. int count = 0; HBasicBlock* join = NULL; for (int i = 0; i < types->length() && count < kMaxStorePolymorphism; ++i) { Handle map = types->at(i); LookupResult lookup(isolate()); if (ComputeStoreField(map, name, &lookup)) { if (count == 0) { BuildCheckHeapObject(object); join = graph()->CreateBasicBlock(); } ++count; HBasicBlock* if_true = graph()->CreateBasicBlock(); HBasicBlock* if_false = graph()->CreateBasicBlock(); HCompareMap* compare = New(object, map, if_true, if_false); FinishCurrentBlock(compare); set_current_block(if_true); HInstruction* instr; CHECK_ALIVE(instr = BuildStoreNamedField( compare, name, value, map, &lookup)); // Goto will add the HSimulate for the store. AddInstruction(instr); if (!ast_context()->IsEffect()) Push(value); 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 == types->length() && FLAG_deoptimize_uncommon_cases) { FinishExitWithHardDeoptimization("Unknown map in polymorphic store", join); } else { HInstruction* instr = BuildStoreNamedGeneric(object, name, value); AddInstruction(instr); if (join != NULL) { if (!ast_context()->IsEffect()) { Push(value); } Goto(join); } else { // The HSimulate for the store should not see the stored value in // effect contexts (it is not materialized at expr->id() in the // unoptimized code). if (instr->HasObservableSideEffects()) { if (ast_context()->IsEffect()) { Add(assignment_id, REMOVABLE_SIMULATE); } else { Push(value); Add(assignment_id, REMOVABLE_SIMULATE); Drop(1); } } return ast_context()->ReturnValue(value); } } ASSERT(join != NULL); join->SetJoinId(assignment_id); set_current_block(join); if (!ast_context()->IsEffect()) { ast_context()->ReturnValue(Pop()); } } static bool ComputeReceiverTypes(Expression* expr, HValue* receiver, SmallMapList** t) { SmallMapList* types = expr->GetReceiverTypes(); *t = types; bool monomorphic = expr->IsMonomorphic(); if (types != NULL && receiver->HasMonomorphicJSObjectType()) { Map* root_map = receiver->GetMonomorphicJSObjectMap()->FindRootMap(); types->FilterForPossibleTransitions(root_map); monomorphic = types->length() == 1; } return monomorphic && CanInlinePropertyAccess(*types->first()); } void HOptimizedGraphBuilder::BuildStore(Expression* expr, Property* prop, BailoutId ast_id, BailoutId return_id, bool is_uninitialized) { HValue* value = environment()->ExpressionStackAt(0); if (!prop->key()->IsPropertyName()) { // Keyed store. HValue* key = environment()->ExpressionStackAt(1); HValue* object = environment()->ExpressionStackAt(2); bool has_side_effects = false; HandleKeyedElementAccess(object, key, value, expr, true, // is_store &has_side_effects); Drop(3); Push(value); Add(return_id, REMOVABLE_SIMULATE); return ast_context()->ReturnValue(Pop()); } // Named store. HValue* object = environment()->ExpressionStackAt(1); if (is_uninitialized) { Add("Insufficient type feedback for property assignment", Deoptimizer::SOFT); } Literal* key = prop->key()->AsLiteral(); Handle name = Handle::cast(key->value()); ASSERT(!name.is_null()); HInstruction* instr = NULL; SmallMapList* types; bool monomorphic = ComputeReceiverTypes(expr, object, &types); if (monomorphic) { Handle map = types->first(); Handle setter; Handle holder; if (LookupSetter(map, name, &setter, &holder)) { AddCheckConstantFunction(holder, object, map); if (FLAG_inline_accessors && TryInlineSetter(setter, ast_id, return_id, value)) { return; } Drop(2); Add(object); Add(value); instr = BuildCallConstantFunction(setter, 2); } else { Drop(2); CHECK_ALIVE(instr = BuildStoreNamedMonomorphic(object, name, value, map)); } } else if (types != NULL && types->length() > 1) { Drop(2); return HandlePolymorphicStoreNamedField(ast_id, object, value, types, name); } else { Drop(2); instr = BuildStoreNamedGeneric(object, name, value); } if (!ast_context()->IsEffect()) Push(value); AddInstruction(instr); if (instr->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(); ASSERT(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->id(), expr->AssignmentId(), expr->IsUninitialized()); } // 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, BailoutId ast_id) { LookupResult lookup(isolate()); GlobalPropertyAccess type = LookupGlobalProperty(var, &lookup, true); if (type == kUseCell) { Handle global(current_info()->global_object()); Handle cell(global->GetPropertyCell(&lookup)); if (cell->type()->IsConstant()) { IfBuilder builder(this); HValue* constant = Add(cell->type()->AsConstant()); if (cell->type()->AsConstant()->IsNumber()) { builder.If(value, constant, Token::EQ); } else { builder.If(value, constant); } builder.Then(); builder.Else(); Add("Constant global variable assignment", Deoptimizer::EAGER); builder.End(); } HInstruction* instr = Add(value, cell, lookup.GetPropertyDetails()); if (instr->HasObservableSideEffects()) { Add(ast_id, REMOVABLE_SIMULATE); } } else { HValue* global_object = Add( context(), HObjectAccess::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)); HStoreNamedGeneric* instr = Add(global_object, var->name(), value, function_strict_mode_flag()); USE(instr); ASSERT(instr->HasObservableSideEffects()); Add(ast_id, REMOVABLE_SIMULATE); } } void HOptimizedGraphBuilder::HandleCompoundAssignment(Assignment* expr) { Expression* target = expr->target(); VariableProxy* proxy = target->AsVariableProxy(); Property* prop = target->AsProperty(); ASSERT(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 Variable::UNALLOCATED: HandleGlobalVariableAssignment(var, Top(), expr->AssignmentId()); break; case Variable::PARAMETER: case Variable::LOCAL: if (var->mode() == CONST) { return Bailout(kUnsupportedConstCompoundAssignment); } BindIfLive(var, Top()); break; case Variable::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: return ast_context()->ReturnValue(Pop()); case CONST_HARMONY: // This case is checked statically so no need to // perform checks here UNREACHABLE(); 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 Variable::LOOKUP: return Bailout(kCompoundAssignmentToLookupSlot); } return ast_context()->ReturnValue(Pop()); } else if (prop != NULL) { CHECK_ALIVE(VisitForValue(prop->obj())); HValue* object = Top(); HValue* key = NULL; if ((!prop->IsFunctionPrototype() && !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)); BuildStore(expr, prop, expr->id(), expr->AssignmentId(), expr->IsUninitialized()); } else { return Bailout(kInvalidLhsInCompoundAssignment); } } void HOptimizedGraphBuilder::VisitAssignment(Assignment* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); VariableProxy* proxy = expr->target()->AsVariableProxy(); Property* prop = expr->target()->AsProperty(); ASSERT(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_CONST) { CHECK_ALIVE(VisitForValue(expr->value())); return ast_context()->ReturnValue(Pop()); } if (var->IsStackAllocated()) { // We insert a use of the old value to detect unsupported uses of const // variables (e.g. initialization inside a loop). HValue* old_value = environment()->Lookup(var); Add(old_value); } } else if (var->mode() == CONST_HARMONY) { if (expr->op() != Token::INIT_CONST_HARMONY) { return Bailout(kNonInitializerAssignmentToConst); } } if (proxy->IsArguments()) return Bailout(kAssignmentToArguments); // Handle the assignment. switch (var->location()) { case Variable::UNALLOCATED: CHECK_ALIVE(VisitForValue(expr->value())); HandleGlobalVariableAssignment(var, Top(), expr->AssignmentId()); return ast_context()->ReturnValue(Pop()); case Variable::PARAMETER: case Variable::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 Variable::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: return ast_context()->ReturnValue(Pop()); case CONST_HARMONY: // This case is checked statically so no need to // perform checks here UNREACHABLE(); default: mode = HStoreContextSlot::kNoCheck; } } else if (expr->op() == Token::INIT_VAR || expr->op() == Token::INIT_LET || expr->op() == Token::INIT_CONST_HARMONY) { mode = HStoreContextSlot::kNoCheck; } else { ASSERT(expr->op() == Token::INIT_CONST); mode = HStoreContextSlot::kCheckIgnoreAssignment; } 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 Variable::LOOKUP: return Bailout(kAssignmentToLOOKUPVariable); } } 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) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); // We don't optimize functions with invalid left-hand sides in // assignments, count operations, or for-in. Consequently throw can // currently only occur in an effect context. ASSERT(ast_context()->IsEffect()); CHECK_ALIVE(VisitForValue(expr->exception())); HValue* value = environment()->Pop(); if (!FLAG_emit_opt_code_positions) SetSourcePosition(expr->position()); Add(value); 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()); } } HLoadNamedField* HGraphBuilder::BuildLoadNamedField(HValue* object, HObjectAccess access) { if (FLAG_track_double_fields && access.representation().IsDouble()) { // load the heap number HLoadNamedField* heap_number = Add( object, access.WithRepresentation(Representation::Tagged())); heap_number->set_type(HType::HeapNumber()); // load the double value from it return New( heap_number, HObjectAccess::ForHeapNumberValue()); } return New(object, access); } HInstruction* HGraphBuilder::AddLoadNamedField(HValue* object, HObjectAccess access) { return AddInstruction(BuildLoadNamedField(object, access)); } 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 AddLoadNamedField( AddLoadNamedField(string, HObjectAccess::ForMap()), HObjectAccess::ForMapInstanceType()); } HInstruction* HGraphBuilder::AddLoadStringLength(HValue* string) { if (string->IsConstant()) { HConstant* c_string = HConstant::cast(string); if (c_string->HasStringValue()) { return Add(c_string->StringValue()->length()); } } return AddLoadNamedField(string, HObjectAccess::ForStringLength()); } HInstruction* HOptimizedGraphBuilder::BuildLoadNamedGeneric( HValue* object, Handle name, Property* expr) { if (expr->IsUninitialized()) { Add("Insufficient type feedback for generic named load", Deoptimizer::SOFT); } return New(object, name); } HInstruction* HOptimizedGraphBuilder::BuildLoadKeyedGeneric(HValue* object, HValue* key) { return New(object, key); } LoadKeyedHoleMode HOptimizedGraphBuilder::BuildKeyedHoleMode(Handle map) { // Loads from a "stock" fast holey double arrays can elide the hole check. LoadKeyedHoleMode load_mode = NEVER_RETURN_HOLE; if (*map == isolate()->get_initial_js_array_map(FAST_HOLEY_DOUBLE_ELEMENTS) && isolate()->IsFastArrayConstructorPrototypeChainIntact()) { Handle prototype(JSObject::cast(map->prototype()), isolate()); Handle object_prototype = isolate()->initial_object_prototype(); BuildCheckPrototypeMaps(prototype, object_prototype); load_mode = ALLOW_RETURN_HOLE; graph()->MarkDependsOnEmptyArrayProtoElements(); } return load_mode; } HInstruction* HOptimizedGraphBuilder::BuildMonomorphicElementAccess( HValue* object, HValue* key, HValue* val, HValue* dependency, Handle map, bool is_store, KeyedAccessStoreMode store_mode) { HCheckMaps* checked_object = Add(object, map, top_info(), dependency); if (dependency) { checked_object->ClearGVNFlag(kDependsOnElementsKind); } if (is_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. Handle prototype(JSObject::cast(map->prototype())); Object* holder = map->prototype(); while (holder->GetPrototype(isolate())->IsJSObject()) { holder = holder->GetPrototype(isolate()); } ASSERT(holder->GetPrototype(isolate())->IsNull()); BuildCheckPrototypeMaps(prototype, Handle(JSObject::cast(holder))); } LoadKeyedHoleMode load_mode = BuildKeyedHoleMode(map); return BuildUncheckedMonomorphicElementAccess( checked_object, key, val, map->instance_type() == JS_ARRAY_TYPE, map->elements_kind(), is_store, load_mode, store_mode); } 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 (!map->IsJSObjectMap()) 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(); HInstruction* instr = BuildUncheckedMonomorphicElementAccess( checked_object, key, val, most_general_consolidated_map->instance_type() == JS_ARRAY_TYPE, consolidated_elements_kind, false, NEVER_RETURN_HOLE, STANDARD_STORE); return instr; } HValue* HOptimizedGraphBuilder::HandlePolymorphicElementAccess( HValue* object, HValue* key, HValue* val, SmallMapList* maps, bool is_store, KeyedAccessStoreMode store_mode, bool* has_side_effects) { *has_side_effects = false; BuildCheckHeapObject(object); if (!is_store) { 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); ElementsKind elements_kind = map->elements_kind(); if (IsFastElementsKind(elements_kind) && elements_kind != GetInitialFastElementsKind()) { possible_transitioned_maps.Add(map); } } // Get transition target for each map (NULL == no transition). for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); Handle transitioned_map = map->FindTransitionedMap(&possible_transitioned_maps); transition_target.Add(transitioned_map); } MapHandleList untransitionable_maps(maps->length()); HTransitionElementsKind* transition = NULL; for (int i = 0; i < maps->length(); ++i) { Handle map = maps->at(i); ASSERT(map->IsMap()); if (!transition_target.at(i).is_null()) { ASSERT(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. ASSERT(untransitionable_maps.length() >= 1); if (untransitionable_maps.length() == 1) { Handle untransitionable_map = untransitionable_maps[0]; HInstruction* instr = NULL; if (untransitionable_map->has_slow_elements_kind() || !untransitionable_map->IsJSObjectMap()) { instr = AddInstruction(is_store ? BuildStoreKeyedGeneric(object, key, val) : BuildLoadKeyedGeneric(object, key)); } else { instr = BuildMonomorphicElementAccess( object, key, val, transition, untransitionable_map, is_store, store_mode); } *has_side_effects |= instr->HasObservableSideEffects(); return is_store ? NULL : instr; } HBasicBlock* join = graph()->CreateBasicBlock(); for (int i = 0; i < untransitionable_maps.length(); ++i) { Handle map = untransitionable_maps[i]; if (!map->IsJSObjectMap()) continue; 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 (IsDictionaryElementsKind(elements_kind)) { access = is_store ? AddInstruction(BuildStoreKeyedGeneric(object, key, val)) : AddInstruction(BuildLoadKeyedGeneric(object, key)); } else { ASSERT(IsFastElementsKind(elements_kind) || IsExternalArrayElementsKind(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, is_store, 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 (!is_store) { Push(access); } NoObservableSideEffectsScope scope(this); GotoNoSimulate(join); set_current_block(other_map); } // Deopt if none of the cases matched. NoObservableSideEffectsScope scope(this); FinishExitWithHardDeoptimization("Unknown map in polymorphic element access", join); set_current_block(join); return is_store ? NULL : Pop(); } HValue* HOptimizedGraphBuilder::HandleKeyedElementAccess( HValue* obj, HValue* key, HValue* val, Expression* expr, bool is_store, bool* has_side_effects) { ASSERT(!expr->IsPropertyName()); HInstruction* instr = NULL; SmallMapList* types; bool monomorphic = ComputeReceiverTypes(expr, obj, &types); bool force_generic = false; if (is_store && (monomorphic || (types != NULL && !types->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 < types->length(); i++) { Handle current_map = types->at(i); if (current_map->DictionaryElementsInPrototypeChainOnly()) { force_generic = true; monomorphic = false; break; } } } if (monomorphic) { Handle map = types->first(); if (map->has_slow_elements_kind()) { instr = is_store ? BuildStoreKeyedGeneric(obj, key, val) : BuildLoadKeyedGeneric(obj, key); AddInstruction(instr); } else { BuildCheckHeapObject(obj); instr = BuildMonomorphicElementAccess( obj, key, val, NULL, map, is_store, expr->GetStoreMode()); } } else if (!force_generic && (types != NULL && !types->is_empty())) { return HandlePolymorphicElementAccess( obj, key, val, types, is_store, expr->GetStoreMode(), has_side_effects); } else { if (is_store) { if (expr->IsAssignment() && expr->AsAssignment()->HasNoTypeInformation()) { Add("Insufficient type feedback for keyed store", Deoptimizer::SOFT); } instr = BuildStoreKeyedGeneric(obj, key, val); } else { if (expr->AsProperty()->HasNoTypeInformation()) { Add("Insufficient type feedback for keyed load", Deoptimizer::SOFT); } instr = BuildLoadKeyedGeneric(obj, key); } AddInstruction(instr); } *has_side_effects = instr->HasObservableSideEffects(); return instr; } HInstruction* HOptimizedGraphBuilder::BuildStoreKeyedGeneric( HValue* object, HValue* key, HValue* value) { return New( object, key, value, function_strict_mode_flag()); } 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::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 (!name->IsOneByteEqualTo(STATIC_ASCII_VECTOR("length"))) return false; 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 { Push(graph()->GetArgumentsObject()); 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; } 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()); } static bool AreStringTypes(SmallMapList* types) { for (int i = 0; i < types->length(); i++) { if (types->at(i)->instance_type() >= FIRST_NONSTRING_TYPE) return false; } return true; } void HOptimizedGraphBuilder::BuildLoad(Property* expr, BailoutId ast_id) { HInstruction* instr = NULL; if (expr->IsStringAccess()) { HValue* index = Pop(); HValue* string = Pop(); HInstruction* char_code = BuildStringCharCodeAt(string, index); AddInstruction(char_code); instr = NewUncasted(char_code); } else if (expr->IsFunctionPrototype()) { HValue* function = Pop(); BuildCheckHeapObject(function); instr = New(function); } else if (expr->key()->IsPropertyName()) { Handle name = expr->key()->AsLiteral()->AsPropertyName(); HValue* object = Pop(); SmallMapList* types; ComputeReceiverTypes(expr, object, &types); ASSERT(types != NULL); if (types->length() > 0) { PropertyAccessInfo info(isolate(), types->first(), name); if (!info.CanLoadAsMonomorphic(types)) { return HandlePolymorphicLoadNamedField( ast_id, expr->LoadId(), object, types, name); } BuildCheckHeapObject(object); HInstruction* checked_object; if (AreStringTypes(types)) { checked_object = Add(object, HCheckInstanceType::IS_STRING); } else { checked_object = Add(object, types); } instr = BuildLoadMonomorphic( &info, object, checked_object, ast_id, expr->LoadId()); if (instr == NULL) return; if (instr->IsLinked()) return ast_context()->ReturnValue(instr); } else { instr = BuildLoadNamedGeneric(object, name, expr); } } else { HValue* key = Pop(); HValue* obj = Pop(); bool has_side_effects = false; HValue* load = HandleKeyedElementAccess( obj, key, NULL, expr, false, // is_store &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); } } return ast_context()->ReturnValue(load); } return ast_context()->ReturnInstruction(instr, ast_id); } void HOptimizedGraphBuilder::VisitProperty(Property* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (TryArgumentsAccess(expr)) return; CHECK_ALIVE(VisitForValue(expr->obj())); if ((!expr->IsFunctionPrototype() && !expr->key()->IsPropertyName()) || expr->IsStringAccess()) { CHECK_ALIVE(VisitForValue(expr->key())); } BuildLoad(expr, expr->id()); } HInstruction* HGraphBuilder::BuildConstantMapCheck(Handle constant, CompilationInfo* info) { HConstant* constant_value = New(constant); if (constant->map()->CanOmitMapChecks()) { constant->map()->AddDependentCompilationInfo( DependentCode::kPrototypeCheckGroup, info); return constant_value; } AddInstruction(constant_value); HCheckMaps* check = Add(constant_value, handle(constant->map()), info); check->ClearGVNFlag(kDependsOnElementsKind); return check; } HInstruction* HGraphBuilder::BuildCheckPrototypeMaps(Handle prototype, Handle holder) { while (!prototype.is_identical_to(holder)) { BuildConstantMapCheck(prototype, top_info()); prototype = handle(JSObject::cast(prototype->GetPrototype())); } HInstruction* checked_object = BuildConstantMapCheck(prototype, top_info()); if (!checked_object->IsLinked()) AddInstruction(checked_object); return checked_object; } void HOptimizedGraphBuilder::AddCheckPrototypeMaps(Handle holder, Handle receiver_map) { if (!holder.is_null()) { Handle prototype(JSObject::cast(receiver_map->prototype())); BuildCheckPrototypeMaps(prototype, holder); } } void HOptimizedGraphBuilder::AddCheckConstantFunction( Handle holder, HValue* receiver, Handle receiver_map) { // Constant functions have the nice property that the map will change if they // are overwritten. Therefore it is enough to check the map of the holder and // its prototypes. AddCheckMap(receiver, receiver_map); AddCheckPrototypeMaps(holder, receiver_map); } HInstruction* HOptimizedGraphBuilder::NewPlainFunctionCall( HValue* fun, int argument_count, bool pass_argument_count) { return New( fun, argument_count, pass_argument_count); } HInstruction* HOptimizedGraphBuilder::NewArgumentAdaptorCall( HValue* fun, HValue* context, int argument_count, HValue* expected_param_count) { CallInterfaceDescriptor* descriptor = isolate()->call_descriptor(Isolate::ArgumentAdaptorCall); HValue* arity = Add(argument_count - 1); HValue* op_vals[] = { fun, context, arity, expected_param_count }; Handle adaptor = isolate()->builtins()->ArgumentsAdaptorTrampoline(); HConstant* adaptor_value = Add(adaptor); return New( adaptor_value, argument_count, descriptor, Vector(op_vals, descriptor->environment_length())); } HInstruction* HOptimizedGraphBuilder::BuildCallConstantFunction( Handle jsfun, int argument_count) { HValue* target = Add(jsfun); // For constant functions, we try to avoid calling the // argument adaptor and instead call the function directly int formal_parameter_count = jsfun->shared()->formal_parameter_count(); bool dont_adapt_arguments = (formal_parameter_count == SharedFunctionInfo::kDontAdaptArgumentsSentinel); int arity = argument_count - 1; bool can_invoke_directly = dont_adapt_arguments || formal_parameter_count == arity; if (can_invoke_directly) { return NewPlainFunctionCall(target, argument_count, dont_adapt_arguments); } else { HValue* param_count_value = Add(formal_parameter_count); HValue* context = Add(target, HObjectAccess::ForFunctionContextPointer()); return NewArgumentAdaptorCall(target, context, argument_count, param_count_value); } UNREACHABLE(); return NULL; } HInstruction* HOptimizedGraphBuilder::NewCallNamed( Handle name, int argument_count) { CallInterfaceDescriptor* descriptor = isolate()->call_descriptor(Isolate::NamedCall); HValue* op_vals[] = { context(), Add(name) }; int arity = argument_count - 1; Handle ic = isolate()->stub_cache()->ComputeCallInitialize(arity); return New( Add(ic), argument_count, descriptor, Vector(op_vals, descriptor->environment_length())); } HInstruction* HOptimizedGraphBuilder::NewCallKeyed( HValue* key, int argument_count) { CallInterfaceDescriptor* descriptor = isolate()->call_descriptor(Isolate::KeyedCall); HValue* op_vals[] = { context(), key }; int arity = argument_count - 1; Handle ic = isolate()->stub_cache()->ComputeKeyedCallInitialize(arity); return New( Add(ic), argument_count, descriptor, Vector(op_vals, descriptor->environment_length())); } class FunctionSorter { public: FunctionSorter() : index_(0), ticks_(0), ast_length_(0), src_length_(0) { } FunctionSorter(int index, int ticks, int ast_length, int src_length) : index_(index), ticks_(ticks), ast_length_(ast_length), src_length_(src_length) { } int index() const { return index_; } int ticks() const { return ticks_; } int ast_length() const { return ast_length_; } int src_length() const { return src_length_; } private: int index_; int ticks_; int ast_length_; int src_length_; }; inline bool operator<(const FunctionSorter& lhs, const FunctionSorter& rhs) { int diff = lhs.ticks() - rhs.ticks(); if (diff != 0) return diff > 0; diff = lhs.ast_length() - rhs.ast_length(); if (diff != 0) return diff < 0; return lhs.src_length() < rhs.src_length(); } bool HOptimizedGraphBuilder::TryCallPolymorphicAsMonomorphic( Call* expr, HValue* receiver, SmallMapList* types, Handle name) { if (types->length() > kMaxCallPolymorphism) return false; PropertyAccessInfo info(isolate(), types->at(0), name); if (!info.CanLoadAsMonomorphic(types)) return false; if (!expr->ComputeTarget(info.map(), name)) return false; BuildCheckHeapObject(receiver); Add(receiver, types); AddCheckPrototypeMaps(expr->holder(), info.map()); if (FLAG_trace_inlining) { Handle caller = current_info()->closure(); SmartArrayPointer 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 (!TryInlineCall(expr)) { int argument_count = expr->arguments()->length() + 1; // Includes receiver. HInstruction* call = BuildCallConstantFunction( expr->target(), argument_count); PushArgumentsFromEnvironment(argument_count); AddInstruction(call); if (!ast_context()->IsEffect()) Push(call); Add(expr->id(), REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop()); } return true; } void HOptimizedGraphBuilder::HandlePolymorphicCallNamed( Call* expr, HValue* receiver, SmallMapList* types, Handle name) { if (TryCallPolymorphicAsMonomorphic(expr, receiver, types, name)) return; int argument_count = expr->arguments()->length() + 1; // Includes receiver. HBasicBlock* join = NULL; FunctionSorter order[kMaxCallPolymorphism]; int ordered_functions = 0; Handle initial_string_map( isolate()->native_context()->string_function()->initial_map()); Handle string_marker_map( JSObject::cast(initial_string_map->prototype())->map()); Handle initial_number_map( isolate()->native_context()->number_function()->initial_map()); Handle number_marker_map( JSObject::cast(initial_number_map->prototype())->map()); Handle heap_number_map = isolate()->factory()->heap_number_map(); bool handle_smi = false; for (int i = 0; i < types->length() && ordered_functions < kMaxCallPolymorphism; ++i) { Handle map = types->at(i); if (expr->ComputeTarget(map, name)) { if (map.is_identical_to(number_marker_map)) handle_smi = true; order[ordered_functions++] = FunctionSorter(i, expr->target()->shared()->profiler_ticks(), InliningAstSize(expr->target()), expr->target()->shared()->SourceSize()); } } std::sort(order, order + ordered_functions); HBasicBlock* number_block = NULL; for (int fn = 0; fn < ordered_functions; ++fn) { int i = order[fn].index(); Handle map = types->at(i); if (fn == 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)); Goto(empty_smi_block, number_block); set_current_block(not_smi_block); } else { BuildCheckHeapObject(receiver); } } HBasicBlock* if_true = graph()->CreateBasicBlock(); HBasicBlock* if_false = graph()->CreateBasicBlock(); HUnaryControlInstruction* compare; if (handle_smi && map.is_identical_to(number_marker_map)) { compare = New(receiver, heap_number_map, if_true, if_false); map = initial_number_map; expr->set_number_check( Handle(JSObject::cast(map->prototype()))); } else if (map.is_identical_to(string_marker_map)) { compare = New(receiver, if_true, if_false); map = initial_string_map; expr->set_string_check( Handle(JSObject::cast(map->prototype()))); } else { compare = New(receiver, map, if_true, if_false); expr->set_map_check(); } FinishCurrentBlock(compare); if (expr->check_type() == NUMBER_CHECK) { Goto(if_true, number_block); if_true = number_block; number_block->SetJoinId(expr->id()); } set_current_block(if_true); expr->ComputeTarget(map, name); AddCheckPrototypeMaps(expr->holder(), map); if (FLAG_trace_inlining && FLAG_polymorphic_inlining) { Handle caller = current_info()->closure(); SmartArrayPointer 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 (FLAG_polymorphic_inlining && 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 { HInstruction* call = BuildCallConstantFunction( expr->target(), argument_count); PushArgumentsFromEnvironment(argument_count); AddInstruction(call); 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 == types->length() && FLAG_deoptimize_uncommon_cases) { // Because the deopt may be the only path in the polymorphic call, make sure // that the environment stack matches the depth on deopt that it otherwise // would have had after a successful call. Drop(argument_count); if (!ast_context()->IsEffect()) Push(graph()->GetConstant0()); FinishExitWithHardDeoptimization("Unknown map in polymorphic call", join); } else { HInstruction* call = NewCallNamed(name, argument_count); PushArgumentsFromEnvironment(argument_count); 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. ASSERT(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) { if (FLAG_trace_inlining) { SmartArrayPointer target_name = target->shared()->DebugName()->ToCString(); SmartArrayPointer caller_name = caller->shared()->DebugName()->ToCString(); if (reason == NULL) { PrintF("Inlined %s called from %s.\n", target_name.get(), 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 builtins marked for inlining. if (target->IsBuiltin()) { return target_shared->inline_builtin() ? 0 : 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. if (!target_shared->IsInlineable()) { TraceInline(target, caller, "target not inlineable"); return kNotInlinable; } if (target_shared->dont_inline() || target_shared->dont_optimize()) { 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) { 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. if (inlined_count_ > Min(FLAG_max_inlined_nodes_cumulative, kUnlimitedMaxInlinedNodesCumulative)) { TraceInline(target, caller, "cumulative AST node limit reached"); return false; } // Parse and allocate variables. CompilationInfo target_info(target, zone()); Handle target_shared(target->shared()); if (!Parser::Parse(&target_info) || !Scope::Analyze(&target_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_info.scope()->num_heap_slots() > 0) { TraceInline(target, caller, "target has context-allocated variables"); return false; } FunctionLiteral* function = target_info.function(); // 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; } AstProperties::Flags* flags(function->flags()); if (flags->Contains(kDontInline) || 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; } if (!function->scope()->arguments()->IsStackAllocated()) { TraceInline(target, caller, "target uses non-stackallocated arguments object"); return false; } } // All declarations must be inlineable. ZoneList* decls = target_info.scope()->declarations(); int decl_count = decls->length(); for (int i = 0; i < decl_count; ++i) { if (!decls->at(i)->IsInlineable()) { 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 (!target_shared->has_deoptimization_support()) { // Note that we compile here using the same AST that we will use for // generating the optimized inline code. target_info.EnableDeoptimizationSupport(); if (!FullCodeGenerator::MakeCode(&target_info)) { TraceInline(target, caller, "could not generate deoptimization info"); return false; } if (target_shared->scope_info() == ScopeInfo::Empty(isolate())) { // The scope info might not have been set if a lazily compiled // function is inlined before being called for the first time. Handle target_scope_info = ScopeInfo::Create(target_info.scope(), zone()); target_shared->set_scope_info(*target_scope_info); } target_shared->EnableDeoptimizationSupport(*target_info.code()); Compiler::RecordFunctionCompilation(Logger::FUNCTION_TAG, &target_info, target_shared); } // ---------------------------------------------------------------- // After this point, we've made a decision to inline this function (so // TryInline should always return true). // Type-check the inlined function. ASSERT(target_shared->has_deoptimization_support()); AstTyper::Run(&target_info); // 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); HConstant* undefined = graph()->GetConstantUndefined(); HEnvironment* inner_env = environment()->CopyForInlining(target, arguments_count, function, undefined, function_state()->inlining_kind()); HConstant* context = Add(Handle(target->context())); inner_env->BindContext(context); Add(return_id); current_block()->UpdateEnvironment(inner_env); HArgumentsObject* arguments_object = NULL; // If the function uses arguments object create and bind one, also copy // current arguments values to use them for materialization. if (function->scope()->arguments() != NULL) { ASSERT(function->scope()->arguments()->IsStackAllocated()); HEnvironment* arguments_env = inner_env->arguments_environment(); int arguments_count = arguments_env->parameter_count(); arguments_object = Add(arguments_count); inner_env->Bind(function->scope()->arguments(), arguments_object); for (int i = 0; i < arguments_count; i++) { arguments_object->AddArgument(arguments_env->Lookup(i), zone()); } } HEnterInlined* enter_inlined = Add(target, arguments_count, function, function_state()->inlining_kind(), function->scope()->arguments(), arguments_object); function_state()->set_entry(enter_inlined); VisitDeclarations(target_info.scope()->declarations()); VisitStatements(function->body()); if (HasStackOverflow()) { // Bail out if the inline function did, as we cannot residualize a call // instead. TraceInline(target, caller, "inline graph construction failed"); target_shared->DisableOptimization(kInliningBailedOut); inline_bailout_ = true; delete target_state; return true; } // Update inlined nodes count. inlined_count_ += nodes_added; Handle unoptimized_code(target_shared->code()); ASSERT(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); 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()) { Goto(inlined_test_context()->if_true(), state); } else if (call_context()->IsEffect()) { Goto(function_return(), state); } else { ASSERT(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 { ASSERT(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()) { Goto(inlined_test_context()->if_false(), state); } else if (call_context()->IsEffect()) { Goto(function_return(), state); } else { ASSERT(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. ASSERT(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, bool drop_extra) { return TryInline(expr->target(), expr->arguments()->length(), NULL, expr->id(), expr->ReturnId(), drop_extra ? DROP_EXTRA_ON_RETURN : NORMAL_RETURN); } 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); } bool HOptimizedGraphBuilder::TryInlineGetter(Handle getter, BailoutId ast_id, BailoutId return_id) { return TryInline(getter, 0, NULL, ast_id, return_id, GETTER_CALL_RETURN); } bool HOptimizedGraphBuilder::TryInlineSetter(Handle setter, BailoutId id, BailoutId assignment_id, HValue* implicit_return_value) { return TryInline(setter, 1, implicit_return_value, id, assignment_id, SETTER_CALL_RETURN); } bool HOptimizedGraphBuilder::TryInlineApply(Handle function, Call* expr, int arguments_count) { return TryInline(function, arguments_count, NULL, expr->id(), expr->ReturnId(), NORMAL_RETURN); } bool HOptimizedGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr, bool drop_extra) { if (!expr->target()->shared()->HasBuiltinFunctionId()) return false; BuiltinFunctionId id = expr->target()->shared()->builtin_function_id(); switch (id) { case kMathExp: if (!FLAG_fast_math) break; // Fall through if FLAG_fast_math. case kMathRound: case kMathFloor: case kMathAbs: case kMathSqrt: case kMathLog: if (expr->arguments()->length() == 1) { HValue* argument = Pop(); Drop(1); // Receiver. HInstruction* op = NewUncasted(argument, id); if (drop_extra) Drop(1); // Optionally drop the function. ast_context()->ReturnInstruction(op, expr->id()); return true; } break; case kMathImul: if (expr->arguments()->length() == 2) { HValue* right = Pop(); HValue* left = Pop(); Drop(1); // Receiver. HInstruction* op = HMul::NewImul(zone(), context(), left, right); if (drop_extra) Drop(1); // Optionally drop the function. ast_context()->ReturnInstruction(op, expr->id()); return true; } break; default: // Not supported for inlining yet. break; } return false; } bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall( Call* expr, HValue* receiver, Handle receiver_map, CheckType check_type) { ASSERT(check_type != RECEIVER_MAP_CHECK || !receiver_map.is_null()); // Try to inline calls like Math.* as operations in the calling function. if (!expr->target()->shared()->HasBuiltinFunctionId()) return false; BuiltinFunctionId id = expr->target()->shared()->builtin_function_id(); int argument_count = expr->arguments()->length() + 1; // Plus receiver. switch (id) { case kStringCharCodeAt: case kStringCharAt: if (argument_count == 2 && check_type == STRING_CHECK) { HValue* index = Pop(); HValue* string = Pop(); ASSERT(!expr->holder().is_null()); BuildCheckPrototypeMaps(Call::GetPrototypeForPrimitiveCheck( STRING_CHECK, expr->holder()->GetIsolate()), expr->holder()); HInstruction* char_code = BuildStringCharCodeAt(string, index); if (id == kStringCharCodeAt) { ast_context()->ReturnInstruction(char_code, expr->id()); return true; } AddInstruction(char_code); HInstruction* result = NewUncasted(char_code); ast_context()->ReturnInstruction(result, expr->id()); return true; } break; case kStringFromCharCode: if (argument_count == 2 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* argument = Pop(); Drop(1); // Receiver. HInstruction* result = NewUncasted(argument); ast_context()->ReturnInstruction(result, expr->id()); return true; } break; case kMathExp: if (!FLAG_fast_math) break; // Fall through if FLAG_fast_math. case kMathRound: case kMathFloor: case kMathAbs: case kMathSqrt: case kMathLog: if (argument_count == 2 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* argument = Pop(); Drop(1); // Receiver. HInstruction* op = NewUncasted(argument, id); ast_context()->ReturnInstruction(op, expr->id()); return true; } break; case kMathPow: if (argument_count == 3 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* right = Pop(); HValue* left = Pop(); Pop(); // Pop receiver. 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. ASSERT(!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, expr->id()); return true; } break; case kMathMax: case kMathMin: if (argument_count == 3 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* right = Pop(); HValue* left = Pop(); Drop(1); // Receiver. HMathMinMax::Operation op = (id == kMathMin) ? HMathMinMax::kMathMin : HMathMinMax::kMathMax; HInstruction* result = NewUncasted(left, right, op); ast_context()->ReturnInstruction(result, expr->id()); return true; } break; case kMathImul: if (argument_count == 3 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* right = Pop(); HValue* left = Pop(); Drop(1); // Receiver. HInstruction* result = HMul::NewImul(zone(), context(), left, right); ast_context()->ReturnInstruction(result, expr->id()); return true; } break; case kArrayPop: { if (!expr->IsMonomorphic() || expr->check_type() != RECEIVER_MAP_CHECK) { return false; } if (receiver_map->instance_type() != JS_ARRAY_TYPE) return false; ElementsKind elements_kind = receiver_map->elements_kind(); if (!IsFastElementsKind(elements_kind)) return false; AddCheckConstantFunction(expr->holder(), receiver, receiver_map); Drop(expr->arguments()->length()); HValue* result; HValue* checked_object; HValue* reduced_length; HValue* receiver = Pop(); { NoObservableSideEffectsScope scope(this); checked_object = AddCheckMap(receiver, receiver_map); HValue* elements = AddLoadElements(checked_object); // Ensure that we aren't popping from a copy-on-write array. if (IsFastSmiOrObjectElementsKind(elements_kind)) { Add( elements, isolate()->factory()->fixed_array_map(), top_info()); } HValue* length = Add( checked_object, HObjectAccess::ForArrayLength(elements_kind)); reduced_length = AddUncasted(length, graph()->GetConstant1()); HValue* bounds_check = Add( graph()->GetConstant0(), length); result = AddElementAccess(elements, reduced_length, NULL, bounds_check, elements_kind, false); Factory* factory = isolate()->factory(); double nan_double = FixedDoubleArray::hole_nan_as_double(); HValue* hole = IsFastSmiOrObjectElementsKind(elements_kind) ? Add(factory->the_hole_value()) : Add(nan_double); if (IsFastSmiOrObjectElementsKind(elements_kind)) { elements_kind = FAST_HOLEY_ELEMENTS; } AddElementAccess( elements, reduced_length, hole, bounds_check, elements_kind, true); } Add( checked_object, HObjectAccess::ForArrayLength(elements_kind), reduced_length, STORE_TO_INITIALIZED_ENTRY); if (!ast_context()->IsEffect()) Push(result); Add(expr->id(), REMOVABLE_SIMULATE); if (!ast_context()->IsEffect()) Drop(1); ast_context()->ReturnValue(result); return true; } case kArrayPush: { if (!expr->IsMonomorphic() || expr->check_type() != RECEIVER_MAP_CHECK) { return false; } if (receiver_map->instance_type() != JS_ARRAY_TYPE) return false; ElementsKind elements_kind = receiver_map->elements_kind(); if (!IsFastElementsKind(elements_kind)) return false; AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* op_vals[] = { context(), // Receiver. environment()->ExpressionStackAt(expr->arguments()->length()) }; const int argc = expr->arguments()->length(); // Includes receiver. PushArgumentsFromEnvironment(argc + 1); CallInterfaceDescriptor* descriptor = isolate()->call_descriptor(Isolate::CallHandler); ArrayPushStub stub(receiver_map->elements_kind(), argc); Handle code = stub.GetCode(isolate()); HConstant* code_value = Add(code); ASSERT((sizeof(op_vals) / kPointerSize) == descriptor->environment_length()); HInstruction* call = New( code_value, argc + 1, descriptor, Vector(op_vals, descriptor->environment_length())); ast_context()->ReturnInstruction(call, expr->id()); return true; } default: // Not yet supported for inlining. break; } return false; } bool HOptimizedGraphBuilder::TryCallApply(Call* expr) { Expression* callee = expr->expression(); Property* prop = callee->AsProperty(); ASSERT(prop != NULL); if (!expr->IsMonomorphic() || expr->check_type() != RECEIVER_MAP_CHECK) { return false; } Handle function_map = expr->GetReceiverTypes()->first(); if (function_map->instance_type() != JS_FUNCTION_TYPE || !expr->target()->shared()->HasBuiltinFunctionId() || expr->target()->shared()->builtin_function_id() != kFunctionApply) { return false; } if (current_info()->scope()->arguments() == NULL) return false; 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 = LookupAndMakeLive(arg_two->var()); if (!arg_two_value->CheckFlag(HValue::kIsArguments)) return false; // Found pattern f.apply(receiver, arguments). CHECK_ALIVE_OR_RETURN(VisitForValue(prop->obj()), true); HValue* function = Top(); AddCheckConstantFunction(expr->holder(), function, function_map); CHECK_ALIVE_OR_RETURN(VisitForValue(args->at(0)), true); HValue* receiver = Pop(); Drop(1); // Pop the function. if (function_state()->outer() == NULL) { HInstruction* elements = Add(false); HInstruction* length = Add(elements); HValue* wrapped_receiver = BuildWrapReceiver(receiver, function); HInstruction* result = New(function, wrapped_receiver, length, elements); ast_context()->ReturnInstruction(result, expr->id()); return true; } 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. ASSERT_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(BuildWrapReceiver(receiver, function)); for (int i = 1; i < arguments_count; i++) { Push(arguments_values->at(i)); } Handle known_function; if (function->IsConstant() && HConstant::cast(function)->handle(isolate())->IsJSFunction()) { known_function = Handle::cast( HConstant::cast(function)->handle(isolate())); int args_count = arguments_count - 1; // Excluding receiver. if (TryInlineApply(known_function, expr, args_count)) return true; } Drop(arguments_count - 1); Push(Add(Pop())); for (int i = 1; i < arguments_count; i++) { Push(Add(arguments_values->at(i))); } HInvokeFunction* call = New(function, known_function, arguments_count); Drop(arguments_count); ast_context()->ReturnInstruction(call, expr->id()); return true; } } HValue* HOptimizedGraphBuilder::ImplicitReceiverFor(HValue* function, Handle target) { SharedFunctionInfo* shared = target->shared(); if (shared->is_classic_mode() && !shared->native()) { HValue* context = Add( function, HObjectAccess::ForJSObjectOffset(JSFunction::kContextOffset)); HValue* global_object = Add( context, HObjectAccess::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)); return Add( global_object, HObjectAccess::ForJSObjectOffset( GlobalObject::kGlobalReceiverOffset)); } return graph()->GetConstantUndefined(); } void HOptimizedGraphBuilder::VisitCall(Call* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); Expression* callee = expr->expression(); int argument_count = expr->arguments()->length() + 1; // Plus receiver. HInstruction* call = NULL; Property* prop = callee->AsProperty(); if (prop != NULL) { if (!prop->key()->IsPropertyName()) { // Keyed function call. CHECK_ALIVE(VisitForValue(prop->obj())); CHECK_ALIVE(VisitForValue(prop->key())); // Push receiver and key like the non-optimized code generator expects it. HValue* key = Pop(); HValue* receiver = Pop(); Push(key); Push(Add(receiver)); CHECK_ALIVE(VisitArgumentList(expr->arguments())); if (expr->IsMonomorphic()) { BuildCheckHeapObject(receiver); ElementsKind kind = expr->KeyedArrayCallIsHoley() ? FAST_HOLEY_ELEMENTS : FAST_ELEMENTS; Handle map(isolate()->get_initial_js_array_map(kind)); HValue* function = BuildMonomorphicElementAccess( receiver, key, NULL, NULL, map, false, STANDARD_STORE); call = New(function, argument_count); } else { call = NewCallKeyed(key, argument_count); } Drop(argument_count + 1); // 1 is the key. return ast_context()->ReturnInstruction(call, expr->id()); } // Named function call. if (TryCallApply(expr)) return; CHECK_ALIVE(VisitForValue(prop->obj())); CHECK_ALIVE(VisitExpressions(expr->arguments())); Handle name = prop->key()->AsLiteral()->AsPropertyName(); HValue* receiver = environment()->ExpressionStackAt(expr->arguments()->length()); SmallMapList* types; bool was_monomorphic = expr->IsMonomorphic(); bool monomorphic = ComputeReceiverTypes(expr, receiver, &types); if (!was_monomorphic && monomorphic) { monomorphic = expr->ComputeTarget(types->first(), name); } if (monomorphic) { Handle map = types->first(); if (TryInlineBuiltinMethodCall(expr, receiver, map, expr->check_type())) { if (FLAG_trace_inlining) { PrintF("Inlining builtin "); expr->target()->ShortPrint(); PrintF("\n"); } return; } if (CallStubCompiler::HasCustomCallGenerator(expr->target()) || expr->check_type() != RECEIVER_MAP_CHECK) { // When the target has a custom call IC generator, use the IC, // because it is likely to generate better code. Also use the IC // when a primitive receiver check is required. call = NewCallNamed(name, argument_count); PushArgumentsFromEnvironment(argument_count); } else { AddCheckConstantFunction(expr->holder(), receiver, map); if (TryInlineCall(expr)) return; call = BuildCallConstantFunction(expr->target(), argument_count); PushArgumentsFromEnvironment(argument_count); } } else if (types != NULL && types->length() > 1) { ASSERT(expr->check_type() == RECEIVER_MAP_CHECK); HandlePolymorphicCallNamed(expr, receiver, types, name); return; } else { call = NewCallNamed(name, argument_count); PushArgumentsFromEnvironment(argument_count); } } else { VariableProxy* proxy = expr->expression()->AsVariableProxy(); if (proxy != NULL && proxy->var()->is_possibly_eval(isolate())) { return Bailout(kPossibleDirectCallToEval); } bool global_call = proxy != NULL && proxy->var()->IsUnallocated(); if (global_call) { Variable* var = proxy->var(); bool known_global_function = false; // If there is a global property cell for the name at compile time and // access check is not enabled we assume that the function will not change // and generate optimized code for calling the function. LookupResult lookup(isolate()); GlobalPropertyAccess type = LookupGlobalProperty(var, &lookup, false); if (type == kUseCell && !current_info()->global_object()->IsAccessCheckNeeded()) { Handle global(current_info()->global_object()); known_global_function = expr->ComputeGlobalTarget(global, &lookup); } if (known_global_function) { // Push the global object instead of the global receiver because // code generated by the full code generator expects it. HValue* global_object = Add( context(), HObjectAccess::ForContextSlot( Context::GLOBAL_OBJECT_INDEX)); Push(global_object); CHECK_ALIVE(VisitExpressions(expr->arguments())); CHECK_ALIVE(VisitForValue(expr->expression())); HValue* function = Pop(); 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, false)) { // Nothing to drop. if (FLAG_trace_inlining) { PrintF("Inlining builtin "); expr->target()->ShortPrint(); PrintF("\n"); } return; } if (TryInlineCall(expr)) return; if (expr->target().is_identical_to(current_info()->closure())) { graph()->MarkRecursive(); } if (CallStubCompiler::HasCustomCallGenerator(expr->target())) { // We're about to install a contextual IC, which expects the global // object as receiver rather than the global proxy. HValue* global_object = Add( context(), HObjectAccess::ForContextSlot( Context::GLOBAL_OBJECT_INDEX)); const int receiver_index = argument_count - 1; environment()->SetExpressionStackAt(receiver_index, global_object); // When the target has a custom call IC generator, use the IC, // because it is likely to generate better code. call = NewCallNamed(var->name(), argument_count); PushArgumentsFromEnvironment(argument_count); } else { call = BuildCallConstantFunction(expr->target(), argument_count); PushArgumentsFromEnvironment(argument_count); } } else { HValue* receiver = Add( context(), HObjectAccess::ForContextSlot( Context::GLOBAL_OBJECT_INDEX)); Push(Add(receiver)); CHECK_ALIVE(VisitArgumentList(expr->arguments())); call = NewCallNamed(var->name(), argument_count); Drop(argument_count); } } else if (expr->IsMonomorphic()) { // The function is on the stack in the unoptimized code during // evaluation of the arguments. CHECK_ALIVE(VisitForValue(expr->expression())); HValue* function = Top(); Add(function, expr->target()); HValue* receiver = ImplicitReceiverFor(function, expr->target()); Push(receiver); CHECK_ALIVE(VisitExpressions(expr->arguments())); if (TryInlineBuiltinFunctionCall(expr, true)) { // Drop the function. if (FLAG_trace_inlining) { PrintF("Inlining builtin "); expr->target()->ShortPrint(); PrintF("\n"); } return; } if (TryInlineCall(expr, true)) { // Drop function from environment. return; } else { call = PreProcessCall(New(function, expr->target(), argument_count)); Drop(1); // The function. } } else { CHECK_ALIVE(VisitForValue(expr->expression())); HValue* function = Top(); HValue* receiver = graph()->GetConstantUndefined(); Push(Add(receiver)); CHECK_ALIVE(VisitArgumentList(expr->arguments())); call = New( function, argument_count, NORMAL_CONTEXTUAL_CALL); Drop(argument_count + 1); } } return ast_context()->ReturnInstruction(call, expr->id()); } void HOptimizedGraphBuilder::BuildInlinedCallNewArray(CallNew* expr) { NoObservableSideEffectsScope no_effects(this); int argument_count = expr->arguments()->length(); // We should at least have the constructor on the expression stack. HValue* constructor = environment()->ExpressionStackAt(argument_count); ElementsKind kind = expr->elements_kind(); Handle site = expr->allocation_site(); ASSERT(!site.is_null()); // Register on the site for deoptimization if the transition feedback changes. AllocationSite::AddDependentCompilationInfo( site, AllocationSite::TRANSITIONS, top_info()); HInstruction* site_instruction = Add(site); // In the single constant argument case, we may have to adjust elements kind // to avoid creating a packed non-empty array. if (argument_count == 1 && !IsHoleyElementsKind(kind)) { HValue* argument = environment()->Top(); if (argument->IsConstant()) { HConstant* constant_argument = HConstant::cast(argument); ASSERT(constant_argument->HasSmiValue()); int constant_array_size = constant_argument->Integer32Value(); if (constant_array_size != 0) { kind = GetHoleyElementsKind(kind); } } } // Build the array. JSArrayBuilder array_builder(this, kind, site_instruction, constructor, DISABLE_ALLOCATION_SITES); HValue* new_object; if (argument_count == 0) { new_object = array_builder.AllocateEmptyArray(); } else if (argument_count == 1) { HValue* argument = environment()->Top(); new_object = BuildAllocateArrayFromLength(&array_builder, argument); } else { HValue* length = Add(argument_count); // Smi arrays need to initialize array elements with the hole because // bailout could occur if the arguments don't fit in a smi. // // TODO(mvstanton): If all the arguments are constants in smi range, then // we could set fill_with_hole to false and save a few instructions. JSArrayBuilder::FillMode fill_mode = IsFastSmiElementsKind(kind) ? JSArrayBuilder::FILL_WITH_HOLE : JSArrayBuilder::DONT_FILL_WITH_HOLE; new_object = array_builder.AllocateArray(length, length, fill_mode); HValue* elements = array_builder.GetElementsLocation(); for (int i = 0; i < argument_count; i++) { HValue* value = environment()->ExpressionStackAt(argument_count - i - 1); HValue* constant_i = Add(i); Add(elements, constant_i, value, kind, INITIALIZING_STORE); } } Drop(argument_count + 1); // drop constructor and args. ast_context()->ReturnValue(new_object); } // Checks whether allocation using the given constructor can be inlined. static bool IsAllocationInlineable(Handle constructor) { return constructor->has_initial_map() && constructor->initial_map()->instance_type() == JS_OBJECT_TYPE && constructor->initial_map()->instance_size() < HAllocate::kMaxInlineSize && constructor->initial_map()->InitialPropertiesLength() == 0; } bool HOptimizedGraphBuilder::IsCallNewArrayInlineable(CallNew* expr) { bool inline_ok = false; Handle caller = current_info()->closure(); Handle target(isolate()->global_context()->array_function(), isolate()); int argument_count = expr->arguments()->length(); // We should have the function plus array arguments on the environment stack. ASSERT(environment()->length() >= (argument_count + 1)); Handle site = expr->allocation_site(); ASSERT(!site.is_null()); if (site->CanInlineCall()) { // We also want to avoid inlining in certain 1 argument scenarios. if (argument_count == 1) { HValue* argument = Top(); if (argument->IsConstant()) { // Do not inline if the constant length argument is not a smi or // outside the valid range for a fast array. HConstant* constant_argument = HConstant::cast(argument); if (constant_argument->HasSmiValue()) { int value = constant_argument->Integer32Value(); inline_ok = value >= 0 && value < JSObject::kInitialMaxFastElementArray; if (!inline_ok) { TraceInline(target, caller, "Length outside of valid array range"); } } } else { inline_ok = true; } } else { inline_ok = true; } } else { TraceInline(target, caller, "AllocationSite requested no inlining."); } if (inline_ok) { TraceInline(target, caller, NULL); } return inline_ok; } void HOptimizedGraphBuilder::VisitCallNew(CallNew* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (!FLAG_emit_opt_code_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 (FLAG_inline_construct && expr->IsMonomorphic() && IsAllocationInlineable(expr->target())) { Handle constructor = expr->target(); HValue* check = Add(function, constructor); // Force completion of inobject slack tracking before generating // allocation code to finalize instance size. if (constructor->shared()->IsInobjectSlackTrackingInProgress()) { constructor->shared()->CompleteInobjectSlackTracking(); } // Calculate instance size from initial map of constructor. ASSERT(constructor->has_initial_map()); Handle initial_map(constructor->initial_map()); int instance_size = initial_map->instance_size(); ASSERT(initial_map->InitialPropertiesLength() == 0); // Allocate an instance of the implicit receiver object. HValue* size_in_bytes = Add(instance_size); PretenureFlag pretenure_flag = (FLAG_pretenuring_call_new && !FLAG_allocation_site_pretenuring) ? isolate()->heap()->GetPretenureMode() : NOT_TENURED; HAllocate* receiver = Add(size_in_bytes, HType::JSObject(), pretenure_flag, JS_OBJECT_TYPE); receiver->set_known_initial_map(initial_map); // Load the initial map from the constructor. HValue* constructor_value = Add(constructor); HValue* initial_map_value = Add(constructor_value, HObjectAccess::ForJSObjectOffset( JSFunction::kPrototypeOrInitialMapOffset)); // Initialize map and fields of the newly allocated object. { NoObservableSideEffectsScope no_effects(this); ASSERT(initial_map->instance_type() == JS_OBJECT_TYPE); Add(receiver, HObjectAccess::ForJSObjectOffset(JSObject::kMapOffset), initial_map_value, INITIALIZING_STORE); HValue* empty_fixed_array = Add(factory->empty_fixed_array()); Add(receiver, HObjectAccess::ForJSObjectOffset(JSObject::kPropertiesOffset), empty_fixed_array, INITIALIZING_STORE); Add(receiver, HObjectAccess::ForJSObjectOffset(JSObject::kElementsOffset), empty_fixed_array, INITIALIZING_STORE); if (initial_map->inobject_properties() != 0) { HConstant* undefined = graph()->GetConstantUndefined(); for (int i = 0; i < initial_map->inobject_properties(); i++) { int property_offset = JSObject::kHeaderSize + i * kPointerSize; Add(receiver, HObjectAccess::ForJSObjectOffset(property_offset), undefined, PREINITIALIZING_STORE); } } } // 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; ASSERT(environment()->ExpressionStackAt(receiver_index) == function); environment()->SetExpressionStackAt(receiver_index, receiver); if (TryInlineConstruct(expr, receiver)) return; // TODO(mstarzinger): For now we remove the previous HAllocate and all // corresponding instructions and instead add HPushArgument 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(); while (instr != initial_map_value) { HInstruction* prev_instr = instr->previous(); instr->DeleteAndReplaceWith(NULL); instr = prev_instr; } initial_map_value->DeleteAndReplaceWith(NULL); receiver->DeleteAndReplaceWith(NULL); check->DeleteAndReplaceWith(NULL); environment()->SetExpressionStackAt(receiver_index, function); HInstruction* call = PreProcessCall(New(function, argument_count)); return ast_context()->ReturnInstruction(call, expr->id()); } else { // The constructor function is both an operand to the instruction and an // argument to the construct call. Handle array_function( isolate()->global_context()->array_function(), isolate()); bool use_call_new_array = expr->target().is_identical_to(array_function); if (use_call_new_array && IsCallNewArrayInlineable(expr)) { // Verify we are still calling the array function for our native context. Add(function, array_function); BuildInlinedCallNewArray(expr); return; } HBinaryCall* call; if (use_call_new_array) { Add(function, array_function); call = New(function, argument_count, expr->elements_kind()); } else { call = New(function, argument_count); } PreProcessCall(call); return ast_context()->ReturnInstruction(call, expr->id()); } } // Support for generating inlined runtime functions. // Lookup table for generators for runtime calls that are generated inline. // Elements of the table are member pointers to functions of // HOptimizedGraphBuilder. #define INLINE_FUNCTION_GENERATOR_ADDRESS(Name, argc, ressize) \ &HOptimizedGraphBuilder::Generate##Name, const HOptimizedGraphBuilder::InlineFunctionGenerator HOptimizedGraphBuilder::kInlineFunctionGenerators[] = { INLINE_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_ADDRESS) INLINE_RUNTIME_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_ADDRESS) }; #undef INLINE_FUNCTION_GENERATOR_ADDRESS 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::ForJSObjectOffset(offset), graph()->GetConstant0(), INITIALIZING_STORE); } Add( obj, HObjectAccess::ForJSArrayBufferViewBuffer(), buffer, INITIALIZING_STORE); Add( obj, HObjectAccess::ForJSArrayBufferViewByteOffset(), byte_offset, INITIALIZING_STORE); Add( obj, HObjectAccess::ForJSArrayBufferViewByteLength(), byte_length, INITIALIZING_STORE); HObjectAccess weak_first_view_access = HObjectAccess::ForJSArrayBufferWeakFirstView(); Add(obj, HObjectAccess::ForJSArrayBufferViewWeakNext(), Add(buffer, weak_first_view_access), INITIALIZING_STORE); Add( buffer, weak_first_view_access, obj, INITIALIZING_STORE); } void HOptimizedGraphBuilder::VisitDataViewInitialize( CallRuntime* expr) { ZoneList* arguments = expr->arguments(); NoObservableSideEffectsScope scope(this); ASSERT(arguments->length()== 4); CHECK_ALIVE(VisitForValue(arguments->at(0))); HValue* obj = Pop(); CHECK_ALIVE(VisitForValue(arguments->at(1))); HValue* buffer = Pop(); CHECK_ALIVE(VisitForValue(arguments->at(2))); HValue* byte_offset = Pop(); CHECK_ALIVE(VisitForValue(arguments->at(3))); HValue* byte_length = Pop(); BuildArrayBufferViewInitialization( obj, buffer, byte_offset, byte_length); } void HOptimizedGraphBuilder::VisitTypedArrayInitialize( CallRuntime* expr) { ZoneList* arguments = expr->arguments(); NoObservableSideEffectsScope scope(this); 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 kArgsLength = 5; ASSERT(arguments->length() == kArgsLength); CHECK_ALIVE(VisitForValue(arguments->at(kObjectArg))); HValue* obj = Pop(); ASSERT(arguments->at(kArrayIdArg)->node_type() == AstNode::kLiteral); Handle value = static_cast(arguments->at(kArrayIdArg))->value(); ASSERT(value->IsSmi()); int array_id = Smi::cast(*value)->value(); CHECK_ALIVE(VisitForValue(arguments->at(kBufferArg))); HValue* buffer = Pop(); HValue* byte_offset; bool is_zero_byte_offset; if (arguments->at(kByteOffsetArg)->node_type() == AstNode::kLiteral && Smi::FromInt(0) == *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; } CHECK_ALIVE(VisitForValue(arguments->at(kByteLengthArg))); HValue* byte_length = Pop(); IfBuilder byte_offset_smi(this); if (!is_zero_byte_offset) { byte_offset_smi.If(byte_offset); byte_offset_smi.Then(); } { // byte_offset is Smi. BuildArrayBufferViewInitialization( obj, buffer, byte_offset, byte_length); ExternalArrayType array_type = kExternalInt8Array; // Bogus initialization. size_t element_size = 1; // Bogus initialization. Runtime::ArrayIdToTypeAndSize(array_id, &array_type, &element_size); HInstruction* length = AddUncasted(byte_length, Add(static_cast(element_size))); Add(obj, HObjectAccess::ForJSTypedArrayLength(), length, INITIALIZING_STORE); HValue* elements = Add( Add(ExternalArray::kAlignedSize), HType::JSArray(), NOT_TENURED, static_cast(FIRST_EXTERNAL_ARRAY_TYPE + array_type)); Handle external_array_map( isolate()->heap()->MapForExternalArrayType(array_type)); AddStoreMapConstant(elements, external_array_map); HValue* backing_store = Add( buffer, 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::ForExternalArrayExternalPointer(), typed_array_start, INITIALIZING_STORE); Add( elements, HObjectAccess::ForFixedArrayLength(), length, INITIALIZING_STORE); Add( obj, HObjectAccess::ForElementsPointer(), elements, INITIALIZING_STORE); } if (!is_zero_byte_offset) { byte_offset_smi.Else(); { // byte_offset is not Smi. Push(Add(obj)); VisitArgument(arguments->at(kArrayIdArg)); Push(Add(buffer)); Push(Add(byte_offset)); Push(Add(byte_length)); Add(expr->name(), expr->function(), kArgsLength); Drop(kArgsLength); } } byte_offset_smi.End(); } void HOptimizedGraphBuilder::VisitCallRuntime(CallRuntime* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (expr->is_jsruntime()) { return Bailout(kCallToAJavaScriptRuntimeFunction); } const Runtime::Function* function = expr->function(); ASSERT(function != NULL); if (function->function_id == Runtime::kDataViewInitialize) { return VisitDataViewInitialize(expr); } if (function->function_id == Runtime::kTypedArrayInitialize) { return VisitTypedArrayInitialize(expr); } if (function->function_id == Runtime::kMaxSmi) { ASSERT(expr->arguments()->length() == 0); HConstant* max_smi = New(static_cast(Smi::kMaxValue)); return ast_context()->ReturnInstruction(max_smi, expr->id()); } if (function->intrinsic_type == Runtime::INLINE) { ASSERT(expr->name()->length() > 0); ASSERT(expr->name()->Get(0) == '_'); // Call to an inline function. int lookup_index = static_cast(function->function_id) - static_cast(Runtime::kFirstInlineFunction); ASSERT(lookup_index >= 0); ASSERT(static_cast(lookup_index) < ARRAY_SIZE(kInlineFunctionGenerators)); InlineFunctionGenerator generator = kInlineFunctionGenerators[lookup_index]; // Call the inline code generator using the pointer-to-member. (this->*generator)(expr); } else { ASSERT(function->intrinsic_type == Runtime::RUNTIME); CHECK_ALIVE(VisitArgumentList(expr->arguments())); Handle name = expr->name(); int argument_count = expr->arguments()->length(); HCallRuntime* call = New(name, function, argument_count); Drop(argument_count); return ast_context()->ReturnInstruction(call, expr->id()); } } void HOptimizedGraphBuilder::VisitUnaryOperation(UnaryOperation* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(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(); HValue* function = AddLoadJSBuiltin(Builtins::DELETE); Add(obj); Add(key); Add(Add(function_strict_mode_flag())); // TODO(olivf) InvokeFunction produces a check for the parameter count, // even though we are certain to pass the correct number of arguments here. HInstruction* instr = New(function, 3); 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; } ASSERT(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()); } HInstruction* HOptimizedGraphBuilder::BuildIncrement( bool returns_original_input, CountOperation* expr) { // The input to the count operation is on top of the expression stack. Representation rep = Representation::FromType(expr->type()); if (rep.IsNone() || rep.IsTagged()) { rep = Representation::Smi(); } if (returns_original_input) { // 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->SetFlag(HInstruction::kCannotBeTagged); instr->ClearAllSideEffects(); return instr; } void HOptimizedGraphBuilder::BuildStoreForEffect(Expression* expr, Property* prop, 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, ast_id, return_id); } void HOptimizedGraphBuilder::VisitCountOperation(CountOperation* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (!FLAG_emit_opt_code_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(kUnsupportedCountOperationWithConst); } // Argument of the count operation is a variable, not a property. ASSERT(prop == NULL); CHECK_ALIVE(VisitForValue(target)); after = BuildIncrement(returns_original_input, expr); input = returns_original_input ? Top() : Pop(); Push(after); switch (var->location()) { case Variable::UNALLOCATED: HandleGlobalVariableAssignment(var, after, expr->AssignmentId()); break; case Variable::PARAMETER: case Variable::LOCAL: BindIfLive(var, after); break; case Variable::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 so we use a // linear search of the parameter list. int count = current_info()->scope()->num_parameters(); for (int i = 0; i < count; ++i) { if (var == current_info()->scope()->parameter(i)) { return Bailout(kAssignmentToParameterInArgumentsObject); } } } 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 Variable::LOOKUP: return Bailout(kLookupVariableInCountOperation); } Drop(returns_original_input ? 2 : 1); return ast_context()->ReturnValue(expr->is_postfix() ? input : after); } // Argument of the count operation is a property. ASSERT(prop != NULL); if (returns_original_input) Push(graph()->GetConstantUndefined()); CHECK_ALIVE(VisitForValue(prop->obj())); HValue* object = Top(); HValue* key = NULL; if ((!prop->IsFunctionPrototype() && !prop->key()->IsPropertyName()) || prop->IsStringAccess()) { CHECK_ALIVE(VisitForValue(prop->key())); key = Top(); } CHECK_ALIVE(PushLoad(prop, object, key)); after = BuildIncrement(returns_original_input, 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->id(), expr->AssignmentId(), object, key, after)); return ast_context()->ReturnValue(Pop()); } environment()->SetExpressionStackAt(0, after); return BuildStore(expr, prop, 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(OS::nan_value()); } 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); if (sa != sub->right()) return false; HValue* const32 = sub->left(); if (!const32->IsConstant() || HConstant::cast(const32)->Integer32Value() != 32) { return false; } return (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, Type* expected) { if (expected->Is(Type::Smi())) { return AddUncasted(number, Representation::Smi()); } if (expected->Is(Type::Signed32())) { return AddUncasted(number, Representation::Integer32()); } return number; } HValue* HGraphBuilder::TruncateToNumber(HValue* value, Type** expected) { if (value->IsConstant()) { HConstant* constant = HConstant::cast(value); Maybe number = constant->CopyToTruncatedNumber(zone()); if (number.has_value) { *expected = Type::Number(zone()); return AddInstruction(number.value); } } // 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); Type* expected_type = *expected; // Separate the number type from the rest. Type* expected_obj = Type::Intersect(expected_type, Type::NonNumber(zone()), zone()); Type* expected_number = Type::Intersect(expected_type, Type::Number(zone()), zone()); // We expect to get a number. // (We need to check first, since Type::None->Is(Type::Any()) == true. if (expected_obj->Is(Type::None())) { ASSERT(!expected_number->Is(Type::None(zone()))); return value; } if (expected_obj->Is(Type::Undefined(zone()))) { // This is already done by HChange. *expected = Type::Union(expected_number, Type::Double(zone()), zone()); return value; } return value; } HValue* HOptimizedGraphBuilder::BuildBinaryOperation( BinaryOperation* expr, HValue* left, HValue* right) { Type* left_type = expr->left()->bounds().lower; Type* right_type = expr->right()->bounds().lower; Type* result_type = expr->bounds().lower; Maybe fixed_right_arg = expr->fixed_right_arg(); Handle allocation_site = expr->allocation_site(); PretenureFlag pretenure_flag = !FLAG_allocation_site_pretenuring ? isolate()->heap()->GetPretenureMode() : NOT_TENURED; HAllocationMode allocation_mode = FLAG_allocation_site_pretenuring ? (allocation_site.is_null() ? HAllocationMode(NOT_TENURED) : HAllocationMode(allocation_site)) : HAllocationMode(pretenure_flag); HValue* result = HGraphBuilder::BuildBinaryOperation( expr->op(), left, right, left_type, right_type, result_type, fixed_right_arg, allocation_mode); // 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()) { Push(result); Add(expr->id(), REMOVABLE_SIMULATE); Drop(1); } return result; } HValue* HGraphBuilder::BuildBinaryOperation( Token::Value op, HValue* left, HValue* right, Type* left_type, Type* right_type, Type* result_type, Maybe fixed_right_arg, HAllocationMode allocation_mode) { Representation left_rep = Representation::FromType(left_type); Representation right_rep = Representation::FromType(right_type); bool maybe_string_add = op == Token::ADD && (left_type->Maybe(Type::String()) || right_type->Maybe(Type::String())); if (left_type->Is(Type::None())) { Add("Insufficient type feedback for LHS of binary operation", Deoptimizer::SOFT); // TODO(rossberg): we should be able to get rid of non-continuous // defaults. left_type = Type::Any(zone()); } else { if (!maybe_string_add) left = TruncateToNumber(left, &left_type); left_rep = Representation::FromType(left_type); } if (right_type->Is(Type::None())) { Add("Insufficient type feedback for RHS of binary operation", Deoptimizer::SOFT); right_type = Type::Any(zone()); } else { if (!maybe_string_add) right = TruncateToNumber(right, &right_type); right_rep = Representation::FromType(right_type); } // Special case for string addition here. if (op == Token::ADD && (left_type->Is(Type::String()) || right_type->Is(Type::String()))) { // Validate type feedback for left argument. if (left_type->Is(Type::String())) { left = BuildCheckString(left); } // Validate type feedback for right argument. if (right_type->Is(Type::String())) { right = BuildCheckString(right); } // Convert left argument as necessary. if (left_type->Is(Type::Number())) { ASSERT(right_type->Is(Type::String())); left = BuildNumberToString(left, left_type); } else if (!left_type->Is(Type::String())) { ASSERT(right_type->Is(Type::String())); HValue* function = AddLoadJSBuiltin(Builtins::STRING_ADD_RIGHT); Add(left); Add(right); return AddUncasted(function, 2); } // Convert right argument as necessary. if (right_type->Is(Type::Number())) { ASSERT(left_type->Is(Type::String())); right = BuildNumberToString(right, right_type); } else if (!right_type->Is(Type::String())) { ASSERT(left_type->Is(Type::String())); HValue* function = AddLoadJSBuiltin(Builtins::STRING_ADD_LEFT); Add(left); Add(right); return AddUncasted(function, 2); } // Fast path for empty constant strings. if (left->IsConstant() && HConstant::cast(left)->HasStringValue() && HConstant::cast(left)->StringValue()->length() == 0) { return right; } if (right->IsConstant() && HConstant::cast(right)->HasStringValue() && HConstant::cast(right)->StringValue()->length() == 0) { return left; } // Register the dependent code with the allocation site. if (!allocation_mode.feedback_site().is_null()) { ASSERT(!graph()->info()->IsStub()); Handle site(allocation_mode.feedback_site()); AllocationSite::AddDependentCompilationInfo( site, AllocationSite::TENURING, top_info()); } // 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()); } if (graph()->info()->IsStub()) { left = EnforceNumberType(left, left_type); right = EnforceNumberType(right, right_type); } Representation result_rep = Representation::FromType(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* function = AddLoadJSBuiltin(BinaryOpIC::TokenToJSBuiltin(op)); Add(left); Add(right); instr = AddUncasted(function, 2); } 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.has_value) { if (right->IsConstant()) { HConstant* c_right = HConstant::cast(right); if (c_right->HasInteger32Value()) { ASSERT_EQ(fixed_right_arg.value, c_right->Integer32Value()); } } else { HConstant* fixed_right = Add( static_cast(fixed_right_arg.value)); IfBuilder if_same(this); if_same.If(right, fixed_right, Token::EQ); if_same.Then(); if_same.ElseDeopt("Unexpected RHS of binary operation"); 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(Type::Signed32()) && right_type->Is(Type::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 (FLAG_opt_safe_uint32_operations && 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->name()->IsOneByteEqualTo(STATIC_ASCII_VECTOR("_ClassOf"))) { return false; } ASSERT(call->arguments()->length() == 1); return true; } void HOptimizedGraphBuilder::VisitBinaryOperation(BinaryOperation* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(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. if (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())); ASSERT(current_block() != NULL); HValue* left_value = Top(); if (left_value->IsConstant()) { HConstant* left_constant = HConstant::cast(left_value); if ((is_logical_and && left_constant->BooleanValue()) || (!is_logical_and && !left_constant->BooleanValue())) { Drop(1); // left_value. 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(); ToBooleanStub::Types 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 { ASSERT(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. if (empty_block->HasPredecessor()) { empty_block->SetJoinId(expr->id()); } else { empty_block = NULL; } if (right_block->HasPredecessor()) { right_block->SetJoinId(expr->RightId()); set_current_block(right_block); CHECK_BAILOUT(VisitForEffect(expr->right())); right_block = current_block(); } else { right_block = NULL; } 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); if (FLAG_emit_opt_code_positions && result->IsBinaryOperation()) { HBinaryOperation::cast(result)->SetOperandPositions( zone(), expr->left()->position(), expr->right()->position()); } 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()); } static bool IsLiteralCompareBool(Isolate* isolate, HValue* left, Token::Value op, HValue* right) { return op == Token::EQ_STRICT && ((left->IsConstant() && HConstant::cast(left)->handle(isolate)->IsBoolean()) || (right->IsConstant() && HConstant::cast(right)->handle(isolate)->IsBoolean())); } void HOptimizedGraphBuilder::VisitCompareOperation(CompareOperation* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); if (!FLAG_emit_opt_code_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, isolate())) { 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(); ASSERT(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()); } Type* left_type = expr->left()->bounds().lower; Type* right_type = expr->right()->bounds().lower; Type* combined_type = expr->combined_type(); CHECK_ALIVE(VisitForValue(expr->left())); CHECK_ALIVE(VisitForValue(expr->right())); if (FLAG_emit_opt_code_positions) SetSourcePosition(expr->position()); HValue* right = Pop(); HValue* left = Pop(); Token::Value op = expr->op(); if (IsLiteralCompareBool(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 global function not // residing in new space. If it is we assume that the function will stay the // same. Handle target = Handle::null(); VariableProxy* proxy = expr->right()->AsVariableProxy(); bool global_function = (proxy != NULL) && proxy->var()->IsUnallocated(); if (global_function && current_info()->has_global_object() && !current_info()->global_object()->IsAccessCheckNeeded()) { Handle name = proxy->name(); Handle global(current_info()->global_object()); LookupResult lookup(isolate()); global->Lookup(*name, &lookup); if (lookup.IsNormal() && lookup.GetValue()->IsJSFunction()) { Handle candidate(JSFunction::cast(lookup.GetValue())); // If the function is in new space we assume it's more likely to // change and thus prefer the general IC code. if (!isolate()->heap()->InNewSpace(*candidate)) { target = candidate; } } } // If the target is not null we have found a known global function that is // assumed to stay the same for this instanceof. if (target.is_null()) { HInstanceOf* result = New(left, right); return ast_context()->ReturnInstruction(result, expr->id()); } else { Add(right, target); HInstanceOfKnownGlobal* result = New(left, target); return ast_context()->ReturnInstruction(result, expr->id()); } // Code below assumes that we don't fall through. UNREACHABLE(); } else if (op == Token::IN) { HValue* function = AddLoadJSBuiltin(Builtins::IN); Add(left); Add(right); // TODO(olivf) InvokeFunction produces a check for the parameter count, // even though we are certain to pass the correct number of arguments here. HInstruction* result = New(function, 2); return ast_context()->ReturnInstruction(result, expr->id()); } HControlInstruction* compare = BuildCompareInstruction( op, left, right, left_type, right_type, combined_type, expr->left()->position(), expr->right()->position(), 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, Type* left_type, Type* right_type, Type* combined_type, int left_position, int right_position, BailoutId bailout_id) { // Cases handled below depend on collected type feedback. They should // soft deoptimize when there is no type feedback. if (combined_type->Is(Type::None())) { Add("Insufficient type feedback for combined type " "of binary operation", Deoptimizer::SOFT); combined_type = left_type = right_type = Type::Any(zone()); } Representation left_rep = Representation::FromType(left_type); Representation right_rep = Representation::FromType(right_type); Representation combined_rep = Representation::FromType(combined_type); if (combined_type->Is(Type::Receiver())) { if (Token::IsEqualityOp(op)) { // Can we get away with map check and not instance type check? HValue* operand_to_check = left->block()->block_id() < right->block()->block_id() ? left : right; if (combined_type->IsClass()) { Handle map = combined_type->AsClass(); AddCheckMap(operand_to_check, map); HCompareObjectEqAndBranch* result = New(left, right); if (FLAG_emit_opt_code_positions) { result->set_operand_position(zone(), 0, left_position); result->set_operand_position(zone(), 1, right_position); } return result; } else { BuildCheckHeapObject(operand_to_check); Add(operand_to_check, HCheckInstanceType::IS_SPEC_OBJECT); HCompareObjectEqAndBranch* result = New(left, right); return result; } } else { Bailout(kUnsupportedNonPrimitiveCompare); return NULL; } } else if (combined_type->Is(Type::InternalizedString()) && Token::IsEqualityOp(op)) { 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(Type::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_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()) { Push(result); AddSimulate(bailout_id, REMOVABLE_SIMULATE); Drop(1); } // 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); if (FLAG_emit_opt_code_positions) { result->SetOperandPositions(zone(), left_position, right_position); } return result; } } } void HOptimizedGraphBuilder::HandleLiteralCompareNil(CompareOperation* expr, Expression* sub_expr, NilValue nil) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); ASSERT(expr->op() == Token::EQ || expr->op() == Token::EQ_STRICT); if (!FLAG_emit_opt_code_positions) SetSourcePosition(expr->position()); CHECK_ALIVE(VisitForValue(sub_expr)); HValue* value = Pop(); if (expr->op() == Token::EQ_STRICT) { HConstant* nil_constant = nil == kNullValue ? graph()->GetConstantNull() : graph()->GetConstantUndefined(); HCompareObjectEqAndBranch* instr = New(value, nil_constant); return ast_context()->ReturnControl(instr, expr->id()); } else { ASSERT_EQ(Token::EQ, expr->op()); Type* type = expr->combined_type()->Is(Type::None()) ? Type::Any(zone()) : expr->combined_type(); HIfContinuation continuation; BuildCompareNil(value, type, &continuation); return ast_context()->ReturnContinuation(&continuation, expr->id()); } } 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); InstanceType instance_type = boilerplate_object->map()->instance_type(); ASSERT(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( boilerplate_object->map()->instance_size()); PretenureFlag pretenure_flag = isolate()->heap()->GetPretenureMode(); if (FLAG_allocation_site_pretenuring) { pretenure_flag = site_context->current()->GetPretenureMode(); Handle site(site_context->current()); AllocationSite::AddDependentCompilationInfo( site, AllocationSite::TENURING, top_info()); } HInstruction* object = Add(object_size_constant, type, pretenure_flag, instance_type, site_context->current()); BuildEmitObjectHeader(boilerplate_object, object); Handle elements(boilerplate_object->elements()); int elements_size = (elements->length() > 0 && elements->map() != isolate()->heap()->fixed_cow_array_map()) ? elements->Size() : 0; HInstruction* object_elements = NULL; if (elements_size > 0) { HValue* object_elements_size = Add(elements_size); if (boilerplate_object->HasFastDoubleElements()) { object_elements = Add(object_elements_size, HType::JSObject(), pretenure_flag, FIXED_DOUBLE_ARRAY_TYPE, site_context->current()); } else { object_elements = Add(object_elements_size, HType::JSObject(), pretenure_flag, FIXED_ARRAY_TYPE, site_context->current()); } } BuildInitElementsInObjectHeader(boilerplate_object, object, object_elements); // Copy object elements if non-COW. if (object_elements != NULL) { BuildEmitElements(boilerplate_object, elements, object_elements, site_context); } // Copy in-object properties. if (boilerplate_object->map()->NumberOfFields() != 0) { BuildEmitInObjectProperties(boilerplate_object, object, site_context, pretenure_flag); } return object; } void HOptimizedGraphBuilder::BuildEmitObjectHeader( Handle boilerplate_object, HInstruction* object) { ASSERT(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()); ASSERT(*properties_field == isolate()->heap()->empty_fixed_array()); HInstruction* properties = Add(properties_field); HObjectAccess access = HObjectAccess::ForPropertiesPointer(); Add(object, access, properties, INITIALIZING_STORE); if (boilerplate_object->IsJSArray()) { Handle boilerplate_array = Handle::cast(boilerplate_object); Handle length_field = Handle(boilerplate_array->length(), isolate()); HInstruction* length = Add(length_field); ASSERT(boilerplate_array->length()->IsSmi()); Add(object, HObjectAccess::ForArrayLength( boilerplate_array->GetElementsKind()), length, INITIALIZING_STORE); } } void HOptimizedGraphBuilder::BuildInitElementsInObjectHeader( Handle boilerplate_object, HInstruction* object, HInstruction* object_elements) { ASSERT(boilerplate_object->properties()->length() == 0); if (object_elements == NULL) { Handle elements_field = Handle(boilerplate_object->elements(), isolate()); object_elements = Add(elements_field); } Add(object, HObjectAccess::ForElementsPointer(), object_elements, INITIALIZING_STORE); } void HOptimizedGraphBuilder::BuildEmitInObjectProperties( Handle boilerplate_object, HInstruction* object, AllocationSiteUsageContext* site_context, PretenureFlag pretenure_flag) { Handle descriptors( boilerplate_object->map()->instance_descriptors()); int limit = boilerplate_object->map()->NumberOfOwnDescriptors(); int copied_fields = 0; for (int i = 0; i < limit; i++) { PropertyDetails details = descriptors->GetDetails(i); if (details.type() != FIELD) continue; copied_fields++; int index = descriptors->GetFieldIndex(i); int property_offset = boilerplate_object->GetInObjectPropertyOffset(index); Handle name(descriptors->GetKey(i)); Handle value = Handle(boilerplate_object->InObjectPropertyAt(index), isolate()); // The access for the store depends on the type of the boilerplate. HObjectAccess access = boilerplate_object->IsJSArray() ? HObjectAccess::ForJSArrayOffset(property_offset) : HObjectAccess::ForJSObjectOffset(property_offset); 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, INITIALIZING_STORE); } 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); // This heap number alloc does not have a corresponding // AllocationSite. That is okay because // 1) it's a child object of another object with a valid allocation site // 2) we can just use the mode of the parent object for pretenuring HInstruction* double_box = Add(heap_number_constant, HType::HeapNumber(), pretenure_flag, HEAP_NUMBER_TYPE); AddStoreMapConstant(double_box, isolate()->factory()->heap_number_map()); Add(double_box, HObjectAccess::ForHeapNumberValue(), Add(value), INITIALIZING_STORE); value_instruction = double_box; } else if (representation.IsSmi() && value->IsUninitialized()) { value_instruction = graph()->GetConstant0(); } else { value_instruction = Add(value); } Add(object, access, value_instruction, INITIALIZING_STORE); } } int inobject_properties = boilerplate_object->map()->inobject_properties(); HInstruction* value_instruction = Add(isolate()->factory()->one_pointer_filler_map()); for (int i = copied_fields; i < inobject_properties; i++) { ASSERT(boilerplate_object->IsJSObject()); int property_offset = boilerplate_object->GetInObjectPropertyOffset(i); HObjectAccess access = HObjectAccess::ForJSObjectOffset(property_offset); Add(object, access, value_instruction, PREINITIALIZING_STORE); } } 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, static_cast(NULL), kind, ALLOW_RETURN_HOLE); HInstruction* store = Add(object_elements, key_constant, value_instruction, kind, INITIALIZING_STORE); store->SetFlag(HValue::kAllowUndefinedAsNaN); } } 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, kind, INITIALIZING_STORE); } else { HInstruction* value_instruction = Add(boilerplate_elements, key_constant, static_cast(NULL), kind, ALLOW_RETURN_HOLE); Add(object_elements, key_constant, value_instruction, kind, INITIALIZING_STORE); } } } void HOptimizedGraphBuilder::VisitThisFunction(ThisFunction* expr) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); HInstruction* instr = BuildThisFunction(); return ast_context()->ReturnInstruction(instr, expr->id()); } void HOptimizedGraphBuilder::VisitDeclarations( ZoneList* declarations) { ASSERT(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 = DeclareGlobalsEvalFlag::encode(current_info()->is_eval()) | DeclareGlobalsNativeFlag::encode(current_info()->is_native()) | DeclareGlobalsLanguageMode::encode(current_info()->language_mode()); Add(array, flags); globals_.Clear(); } } void HOptimizedGraphBuilder::VisitVariableDeclaration( VariableDeclaration* declaration) { VariableProxy* proxy = declaration->proxy(); VariableMode mode = declaration->mode(); Variable* variable = proxy->var(); bool hole_init = mode == CONST || mode == CONST_HARMONY || mode == LET; switch (variable->location()) { case Variable::UNALLOCATED: globals_.Add(variable->name(), zone()); globals_.Add(variable->binding_needs_init() ? isolate()->factory()->the_hole_value() : isolate()->factory()->undefined_value(), zone()); return; case Variable::PARAMETER: case Variable::LOCAL: if (hole_init) { HValue* value = graph()->GetConstantHole(); environment()->Bind(variable, value); } break; case Variable::CONTEXT: if (hole_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 Variable::LOOKUP: return Bailout(kUnsupportedLookupSlotInDeclaration); } } void HOptimizedGraphBuilder::VisitFunctionDeclaration( FunctionDeclaration* declaration) { VariableProxy* proxy = declaration->proxy(); Variable* variable = proxy->var(); switch (variable->location()) { case Variable::UNALLOCATED: { globals_.Add(variable->name(), zone()); Handle function = Compiler::BuildFunctionInfo( declaration->fun(), current_info()->script()); // Check for stack-overflow exception. if (function.is_null()) return SetStackOverflow(); globals_.Add(function, zone()); return; } case Variable::PARAMETER: case Variable::LOCAL: { CHECK_ALIVE(VisitForValue(declaration->fun())); HValue* value = Pop(); BindIfLive(variable, value); break; } case Variable::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 Variable::LOOKUP: return Bailout(kUnsupportedLookupSlotInDeclaration); } } void HOptimizedGraphBuilder::VisitModuleDeclaration( ModuleDeclaration* declaration) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitImportDeclaration( ImportDeclaration* declaration) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitExportDeclaration( ExportDeclaration* declaration) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitModuleLiteral(ModuleLiteral* module) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitModuleVariable(ModuleVariable* module) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitModulePath(ModulePath* module) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitModuleUrl(ModuleUrl* module) { UNREACHABLE(); } void HOptimizedGraphBuilder::VisitModuleStatement(ModuleStatement* stmt) { UNREACHABLE(); } // Generators for inline runtime functions. // Support for types. void HOptimizedGraphBuilder::GenerateIsSmi(CallRuntime* call) { ASSERT(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::GenerateIsSpecObject(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, FIRST_SPEC_OBJECT_TYPE, LAST_SPEC_OBJECT_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsFunction(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, JS_FUNCTION_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsMinusZero(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HCompareMinusZeroAndBranch* result = New(value); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateHasCachedArrayIndex(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasCachedArrayIndexAndBranch* result = New(value); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsArray(CallRuntime* call) { ASSERT(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::GenerateIsRegExp(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HHasInstanceTypeAndBranch* result = New(value, JS_REGEXP_TYPE); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsObject(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HIsObjectAndBranch* result = New(value); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsNonNegativeSmi(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionIsNonNegativeSmi); } void HOptimizedGraphBuilder::GenerateIsUndetectableObject(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HIsUndetectableAndBranch* result = New(value); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateIsStringWrapperSafeForDefaultValueOf( CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionIsStringWrapperSafeForDefaultValueOf); } // Support for construct call checks. void HOptimizedGraphBuilder::GenerateIsConstructCall(CallRuntime* call) { ASSERT(call->arguments()->length() == 0); if (function_state()->outer() != NULL) { // We are generating graph for inlined function. HValue* value = function_state()->inlining_kind() == CONSTRUCT_CALL_RETURN ? graph()->GetConstantTrue() : graph()->GetConstantFalse(); return ast_context()->ReturnValue(value); } else { return ast_context()->ReturnControl(New(), call->id()); } } // Support for arguments.length and arguments[?]. void HOptimizedGraphBuilder::GenerateArgumentsLength(CallRuntime* call) { // Our implementation of arguments (based on this stack frame or an // adapter below it) does not work for inlined functions. This runtime // function is blacklisted by AstNode::IsInlineable. ASSERT(function_state()->outer() == NULL); ASSERT(call->arguments()->length() == 0); HInstruction* elements = Add(false); HArgumentsLength* result = New(elements); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateArguments(CallRuntime* call) { // Our implementation of arguments (based on this stack frame or an // adapter below it) does not work for inlined functions. This runtime // function is blacklisted by AstNode::IsInlineable. ASSERT(function_state()->outer() == NULL); ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* index = Pop(); HInstruction* elements = Add(false); HInstruction* length = Add(elements); HInstruction* checked_index = Add(index, length); HAccessArgumentsAt* result = New( elements, length, checked_index); return ast_context()->ReturnInstruction(result, call->id()); } // Support for accessing the class and value fields of an object. void HOptimizedGraphBuilder::GenerateClassOf(CallRuntime* call) { // The special form detected by IsClassOfTest is detected before we get here // and does not cause a bailout. return Bailout(kInlinedRuntimeFunctionClassOf); } void HOptimizedGraphBuilder::GenerateValueOf(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HValueOf* result = New(value); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateDateField(CallRuntime* call) { ASSERT(call->arguments()->length() == 2); ASSERT_NE(NULL, call->arguments()->at(1)->AsLiteral()); Smi* index = Smi::cast(*(call->arguments()->at(1)->AsLiteral()->value())); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* date = Pop(); HDateField* result = New(date, index); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateOneByteSeqStringSetChar( CallRuntime* call) { ASSERT(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* string = Pop(); Add(String::ONE_BYTE_ENCODING, string, index, value); Add(call->id(), FIXED_SIMULATE); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::GenerateTwoByteSeqStringSetChar( CallRuntime* call) { ASSERT(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* string = Pop(); Add(String::TWO_BYTE_ENCODING, string, index, value); Add(call->id(), FIXED_SIMULATE); return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } void HOptimizedGraphBuilder::GenerateSetValueOf(CallRuntime* call) { ASSERT(call->arguments()->length() == 2); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* value = Pop(); HValue* object = Pop(); // Check if object is a not a smi. HBasicBlock* if_smi = graph()->CreateBasicBlock(); HBasicBlock* if_heap_object = graph()->CreateBasicBlock(); HBasicBlock* join = graph()->CreateBasicBlock(); FinishCurrentBlock(New(object, if_smi, if_heap_object)); Goto(if_smi, join); // Check if object is a JSValue. set_current_block(if_heap_object); HHasInstanceTypeAndBranch* typecheck = New(object, JS_VALUE_TYPE); HBasicBlock* if_js_value = graph()->CreateBasicBlock(); HBasicBlock* not_js_value = graph()->CreateBasicBlock(); typecheck->SetSuccessorAt(0, if_js_value); typecheck->SetSuccessorAt(1, not_js_value); FinishCurrentBlock(typecheck); Goto(not_js_value, join); // Create in-object property store to kValueOffset. set_current_block(if_js_value); Add(object, HObjectAccess::ForJSObjectOffset(JSValue::kValueOffset), value, INITIALIZING_STORE); Goto(if_js_value, join); join->SetJoinId(call->id()); set_current_block(join); return ast_context()->ReturnValue(value); } // Fast support for charCodeAt(n). void HOptimizedGraphBuilder::GenerateStringCharCodeAt(CallRuntime* call) { ASSERT(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 string.charAt(n) and string[n]. void HOptimizedGraphBuilder::GenerateStringCharFromCode(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* char_code = Pop(); HInstruction* result = NewUncasted(char_code); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for string.charAt(n) and string[n]. void HOptimizedGraphBuilder::GenerateStringCharAt(CallRuntime* call) { ASSERT(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* char_code = BuildStringCharCodeAt(string, index); AddInstruction(char_code); HInstruction* result = NewUncasted(char_code); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for object equality testing. void HOptimizedGraphBuilder::GenerateObjectEquals(CallRuntime* call) { ASSERT(call->arguments()->length() == 2); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* right = Pop(); HValue* left = Pop(); HCompareObjectEqAndBranch* result = New(left, right); return ast_context()->ReturnControl(result, call->id()); } void HOptimizedGraphBuilder::GenerateLog(CallRuntime* call) { // %_Log is ignored in optimized code. return ast_context()->ReturnValue(graph()->GetConstantUndefined()); } // Fast support for StringAdd. void HOptimizedGraphBuilder::GenerateStringAdd(CallRuntime* call) { ASSERT_EQ(2, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* right = Pop(); HValue* left = Pop(); HInstruction* result = NewUncasted(left, right); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for SubString. void HOptimizedGraphBuilder::GenerateSubString(CallRuntime* call) { ASSERT_EQ(3, call->arguments()->length()); CHECK_ALIVE(VisitArgumentList(call->arguments())); HCallStub* result = New(CodeStub::SubString, 3); Drop(3); return ast_context()->ReturnInstruction(result, call->id()); } // Fast support for StringCompare. void HOptimizedGraphBuilder::GenerateStringCompare(CallRuntime* call) { ASSERT_EQ(2, call->arguments()->length()); CHECK_ALIVE(VisitArgumentList(call->arguments())); HCallStub* result = New(CodeStub::StringCompare, 2); Drop(2); return ast_context()->ReturnInstruction(result, call->id()); } // Support for direct calls from JavaScript to native RegExp code. void HOptimizedGraphBuilder::GenerateRegExpExec(CallRuntime* call) { ASSERT_EQ(4, call->arguments()->length()); CHECK_ALIVE(VisitArgumentList(call->arguments())); HCallStub* result = New(CodeStub::RegExpExec, 4); Drop(4); return ast_context()->ReturnInstruction(result, call->id()); } // Construct a RegExp exec result with two in-object properties. void HOptimizedGraphBuilder::GenerateRegExpConstructResult(CallRuntime* call) { ASSERT_EQ(3, call->arguments()->length()); CHECK_ALIVE(VisitArgumentList(call->arguments())); HCallStub* result = New(CodeStub::RegExpConstructResult, 3); Drop(3); return ast_context()->ReturnInstruction(result, call->id()); } // Support for fast native caches. void HOptimizedGraphBuilder::GenerateGetFromCache(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionGetFromCache); } // Fast support for number to string. void HOptimizedGraphBuilder::GenerateNumberToString(CallRuntime* call) { ASSERT_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* number = Pop(); HValue* result = BuildNumberToString(number, Type::Any(zone())); return ast_context()->ReturnValue(result); } // Fast call for custom callbacks. void HOptimizedGraphBuilder::GenerateCallFunction(CallRuntime* call) { // 1 ~ The function to call is not itself an argument to the call. int arg_count = call->arguments()->length() - 1; ASSERT(arg_count >= 1); // There's always at least a receiver. for (int i = 0; i < arg_count; ++i) { CHECK_ALIVE(VisitArgument(call->arguments()->at(i))); } CHECK_ALIVE(VisitForValue(call->arguments()->last())); HValue* function = Pop(); // Branch for function proxies, or other non-functions. HHasInstanceTypeAndBranch* typecheck = New(function, JS_FUNCTION_TYPE); HBasicBlock* if_jsfunction = graph()->CreateBasicBlock(); HBasicBlock* if_nonfunction = graph()->CreateBasicBlock(); HBasicBlock* join = graph()->CreateBasicBlock(); typecheck->SetSuccessorAt(0, if_jsfunction); typecheck->SetSuccessorAt(1, if_nonfunction); FinishCurrentBlock(typecheck); set_current_block(if_jsfunction); HInstruction* invoke_result = Add(function, arg_count); Drop(arg_count); Push(invoke_result); Goto(if_jsfunction, join); set_current_block(if_nonfunction); HInstruction* call_result = Add(function, arg_count); Drop(arg_count); Push(call_result); Goto(if_nonfunction, join); set_current_block(join); join->SetJoinId(call->id()); return ast_context()->ReturnValue(Pop()); } // Fast call to math functions. void HOptimizedGraphBuilder::GenerateMathPow(CallRuntime* call) { ASSERT_EQ(2, call->arguments()->length()); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); CHECK_ALIVE(VisitForValue(call->arguments()->at(1))); HValue* right = Pop(); HValue* left = Pop(); HInstruction* result = NewUncasted(left, right); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateMathLog(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HInstruction* result = NewUncasted(value, kMathLog); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateMathSqrt(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HInstruction* result = NewUncasted(value, kMathSqrt); return ast_context()->ReturnInstruction(result, call->id()); } // Check whether two RegExps are equivalent void HOptimizedGraphBuilder::GenerateIsRegExpEquivalent(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionIsRegExpEquivalent); } void HOptimizedGraphBuilder::GenerateGetCachedArrayIndex(CallRuntime* call) { ASSERT(call->arguments()->length() == 1); CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); HValue* value = Pop(); HGetCachedArrayIndex* result = New(value); return ast_context()->ReturnInstruction(result, call->id()); } void HOptimizedGraphBuilder::GenerateFastAsciiArrayJoin(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionFastAsciiArrayJoin); } // Support for generators. void HOptimizedGraphBuilder::GenerateGeneratorNext(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionGeneratorNext); } void HOptimizedGraphBuilder::GenerateGeneratorThrow(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionGeneratorThrow); } void HOptimizedGraphBuilder::GenerateDebugBreakInOptimizedCode( CallRuntime* call) { Add(); return ast_context()->ReturnValue(graph()->GetConstant0()); } #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) { Initialize(scope->num_parameters() + 1, 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) { ASSERT(!block->IsLoopHeader()); ASSERT(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. ASSERT(phi->merged_index() == i || !phi->HasMergedIndex()); ASSERT(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. ASSERT(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) { ASSERT(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 { ASSERT(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; ASSERT(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; } void HEnvironment::Drop(int count) { for (int i = 0; i < count; ++i) { Pop(); } } 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; } HEnvironment* HEnvironment::CopyForInlining( Handle target, int arguments, FunctionLiteral* function, HConstant* undefined, InliningKind inlining_kind) const { ASSERT(frame_type() == JS_FUNCTION); // 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 (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; } void HEnvironment::PrintTo(StringStream* stream) { for (int i = 0; i < length(); i++) { if (i == 0) stream->Add("parameters\n"); if (i == parameter_count()) stream->Add("specials\n"); if (i == parameter_count() + specials_count()) stream->Add("locals\n"); if (i == parameter_count() + specials_count() + local_count()) { stream->Add("expressions\n"); } HValue* val = values_.at(i); stream->Add("%d: ", i); if (val != NULL) { val->PrintNameTo(stream); } else { stream->Add("NULL"); } stream->Add("\n"); } PrintF("\n"); } void HEnvironment::PrintToStd() { HeapStringAllocator string_allocator; StringStream trace(&string_allocator); PrintTo(&trace); PrintF("%s", trace.ToCString().get()); } void HTracer::TraceCompilation(CompilationInfo* info) { Tag tag(this, "compilation"); if (info->IsOptimizing()) { Handle name = info->function()->debug_name(); PrintStringProperty("name", name->ToCString().get()); PrintStringProperty("method", name->ToCString().get()); } else { CodeStub::Major major_key = info->code_stub()->MajorKey(); PrintStringProperty("name", CodeStub::MajorName(major_key, false)); PrintStringProperty("method", "stub"); } PrintLongProperty("date", static_cast(OS::TimeCurrentMillis())); } void HTracer::TraceLithium(const char* name, LChunk* chunk) { ASSERT(!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) { ASSERT(!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(); trace_.Add("%d ", phi->merged_index()); phi->PrintNameTo(&trace_); trace_.Add(" "); phi->PrintTo(&trace_); trace_.Add("\n"); } } { Tag HIR_tag(this, "HIR"); for (HInstructionIterator it(current); !it.Done(); it.Advance()) { HInstruction* instruction = it.Current(); int bci = FLAG_emit_opt_code_positions && instruction->has_position() ? instruction->position() : 0; int uses = instruction->UseCount(); PrintIndent(); trace_.Add("%d %d ", bci, uses); instruction->PrintNameTo(&trace_); trace_.Add(" "); instruction->PrintTo(&trace_); trace_.Add(" <|@\n"); } } 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_); trace_.Add(" [hir:"); linstr->hydrogen_value()->PrintNameTo(&trace_); trace_.Add("]"); trace_.Add(" <|@\n"); } } } } } } 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\"", DoubleRegister::AllocationIndexToString(assigned_reg)); } else { ASSERT(op->IsRegister()); trace_.Add(" \"%s\"", Register::AllocationIndexToString(assigned_reg)); } } else if (range->IsSpilled()) { LOperand* op = range->TopLevel()->GetSpillOperand(); if (op->IsDoubleStackSlot()) { trace_.Add(" \"double_stack:%d\"", op->index()); } else { ASSERT(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->shared_info().is_null()) return; source_size_ += info->shared_info()->SourceSize(); } void HStatistics::Print() { PrintF("Timing results:\n"); TimeDelta sum; for (int i = 0; i < times_.length(); ++i) { sum += times_[i]; } for (int i = 0; i < names_.length(); ++i) { PrintF("%32s", names_[i]); double ms = times_[i].InMillisecondsF(); double percent = times_[i].PercentOf(sum); PrintF(" %8.3f ms / %4.1f %% ", ms, percent); unsigned size = sizes_[i]; double size_percent = static_cast(size) * 100 / total_size_; PrintF(" %9u bytes / %4.1f %%\n", size, size_percent); } PrintF("----------------------------------------" "---------------------------------------\n"); TimeDelta total = create_graph_ + optimize_graph_ + generate_code_; PrintF("%32s %8.3f ms / %4.1f %% \n", "Create graph", create_graph_.InMillisecondsF(), create_graph_.PercentOf(total)); PrintF("%32s %8.3f ms / %4.1f %% \n", "Optimize graph", optimize_graph_.InMillisecondsF(), optimize_graph_.PercentOf(total)); PrintF("%32s %8.3f ms / %4.1f %% \n", "Generate and install code", generate_code_.InMillisecondsF(), generate_code_.PercentOf(total)); PrintF("----------------------------------------" "---------------------------------------\n"); PrintF("%32s %8.3f ms (%.1f times slower than full code gen)\n", "Total", total.InMillisecondsF(), 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 ? total_size_ / 1024 / source_size_in_kb : 0; PrintF("%32s %8.3f ms %7.3f kB allocated\n", "Average per kB source", normalized_time, normalized_size_in_kb); } void HStatistics::SaveTiming(const char* name, TimeDelta time, unsigned 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 v8::internal