cd5ea74700
Made operator* return reference to the raw type, not pointer. New method 'get()' should be used when raw pointer is needed. Also removed useless inline modifier from the SmaprtPointer methods and added const modifier to the methods that don't change smart pointer. Made ~SmartPointerBase protected to avoid accidental calls of the non-virtual base class's destructor. drive-by: fixed use after free in src/factory.cc BUG=None LOG=N R=alph@chromium.org, svenpanne@chromium.org Review URL: https://codereview.chromium.org/101763003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18275 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
10823 lines
375 KiB
C++
10823 lines
375 KiB
C++
// 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 <algorithm>
|
|
|
|
#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<HBasicBlock*> 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<HConstant>* 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<Object>::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<HBranch>(
|
|
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<HDeoptimize>(reason, Deoptimizer::EAGER);
|
|
AddMergeAtJoinBlock(true);
|
|
}
|
|
|
|
|
|
void HGraphBuilder::IfBuilder::Return(HValue* value) {
|
|
HValue* parameter_count = builder_->graph()->GetConstantMinus1();
|
|
builder_->FinishExitCurrentBlock(
|
|
builder_->New<HReturn>(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<HCompareNumericAndBranch>(
|
|
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<HConstant>(ExternalReference(counter));
|
|
HValue* old_value = Add<HLoadNamedField>(reference,
|
|
HObjectAccess::ForCounter());
|
|
HValue* new_value = AddUncasted<HAdd>(old_value, graph()->GetConstant1());
|
|
new_value->ClearFlag(HValue::kCanOverflow); // Ignore counter overflow
|
|
Add<HStoreNamedField>(reference, HObjectAccess::ForCounter(),
|
|
new_value);
|
|
}
|
|
}
|
|
|
|
|
|
void HGraphBuilder::AddSimulate(BailoutId id,
|
|
RemovableSimulate removable) {
|
|
ASSERT(current_block() != NULL);
|
|
ASSERT(!graph()->IsInsideNoSideEffectsScope());
|
|
current_block()->AddNewSimulate(id, 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<HCheckHeapObject>(obj);
|
|
}
|
|
|
|
|
|
void HGraphBuilder::FinishExitWithHardDeoptimization(
|
|
const char* reason, HBasicBlock* continuation) {
|
|
PadEnvironmentForContinuation(current_block(), continuation);
|
|
Add<HDeoptimize>(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> map) {
|
|
return Add<HCheckMaps>(obj, map, top_info());
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildCheckString(HValue* string) {
|
|
if (!string->type().IsString()) {
|
|
ASSERT(!string->IsConstant() ||
|
|
!HConstant::cast(string)->HasStringValue());
|
|
BuildCheckHeapObject(string);
|
|
return Add<HCheckInstanceType>(string, HCheckInstanceType::IS_STRING);
|
|
}
|
|
return string;
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildWrapReceiver(HValue* object, HValue* function) {
|
|
if (object->type().IsJSObject()) return object;
|
|
return Add<HWrapReceiver>(object, function);
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildCheckForCapacityGrow(HValue* object,
|
|
HValue* elements,
|
|
ElementsKind kind,
|
|
HValue* length,
|
|
HValue* key,
|
|
bool is_js_array) {
|
|
IfBuilder length_checker(this);
|
|
|
|
Token::Value token = IsHoleyElementsKind(kind) ? Token::GTE : Token::EQ;
|
|
length_checker.If<HCompareNumericAndBranch>(key, length, token);
|
|
|
|
length_checker.Then();
|
|
|
|
HValue* current_capacity = AddLoadFixedArrayLength(elements);
|
|
|
|
IfBuilder capacity_checker(this);
|
|
|
|
capacity_checker.If<HCompareNumericAndBranch>(key, current_capacity,
|
|
Token::GTE);
|
|
capacity_checker.Then();
|
|
|
|
HValue* max_gap = Add<HConstant>(static_cast<int32_t>(JSObject::kMaxGap));
|
|
HValue* max_capacity = AddUncasted<HAdd>(current_capacity, max_gap);
|
|
IfBuilder key_checker(this);
|
|
key_checker.If<HCompareNumericAndBranch>(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<HAdd>(key, graph_->GetConstant1());
|
|
new_length->ClearFlag(HValue::kCanOverflow);
|
|
|
|
Add<HStoreNamedField>(object, HObjectAccess::ForArrayLength(kind),
|
|
new_length);
|
|
}
|
|
|
|
length_checker.Else();
|
|
Add<HBoundsCheck>(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<HCompareMap>(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<HTrapAllocationMemento>(object);
|
|
}
|
|
|
|
if (!IsSimpleMapChangeTransition(from_kind, to_kind)) {
|
|
HInstruction* elements = AddLoadElements(object);
|
|
|
|
HInstruction* empty_fixed_array = Add<HConstant>(
|
|
isolate()->factory()->empty_fixed_array());
|
|
|
|
IfBuilder if_builder(this);
|
|
|
|
if_builder.IfNot<HCompareObjectEqAndBranch>(elements, empty_fixed_array);
|
|
|
|
if_builder.Then();
|
|
|
|
HInstruction* elements_length = AddLoadFixedArrayLength(elements);
|
|
|
|
HInstruction* array_length = is_jsarray
|
|
? Add<HLoadNamedField>(object, HObjectAccess::ForArrayLength(from_kind))
|
|
: elements_length;
|
|
|
|
BuildGrowElementsCapacity(object, elements, from_kind, to_kind,
|
|
array_length, elements_length);
|
|
|
|
if_builder.End();
|
|
}
|
|
|
|
Add<HStoreNamedField>(object, HObjectAccess::ForMap(), map);
|
|
}
|
|
|
|
|
|
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<HAdd>(hash, Add<HConstant>(offset));
|
|
raw_index = AddUncasted<HBitwise>(Token::BIT_AND, raw_index, mask);
|
|
int32_t entry_size = SeededNumberDictionary::kEntrySize;
|
|
raw_index = AddUncasted<HMul>(raw_index, Add<HConstant>(entry_size));
|
|
raw_index->ClearFlag(HValue::kCanOverflow);
|
|
|
|
int32_t base_offset = SeededNumberDictionary::kElementsStartIndex;
|
|
HValue* key_index = AddUncasted<HAdd>(raw_index, Add<HConstant>(base_offset));
|
|
key_index->ClearFlag(HValue::kCanOverflow);
|
|
|
|
HValue* candidate_key = Add<HLoadKeyed>(elements, key_index,
|
|
static_cast<HValue*>(NULL),
|
|
FAST_SMI_ELEMENTS);
|
|
|
|
IfBuilder key_compare(this);
|
|
key_compare.IfNot<HCompareObjectEqAndBranch>(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<HAdd>(
|
|
raw_index, Add<HConstant>(base_offset + 2));
|
|
details_index->ClearFlag(HValue::kCanOverflow);
|
|
|
|
HValue* details = Add<HLoadKeyed>(elements, details_index,
|
|
static_cast<HValue*>(NULL),
|
|
FAST_SMI_ELEMENTS);
|
|
IfBuilder details_compare(this);
|
|
details_compare.If<HCompareNumericAndBranch>(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<HAdd>(
|
|
raw_index, Add<HConstant>(base_offset + 1));
|
|
result_index->ClearFlag(HValue::kCanOverflow);
|
|
|
|
Push(Add<HLoadKeyed>(elements, result_index,
|
|
static_cast<HValue*>(NULL),
|
|
FAST_ELEMENTS));
|
|
}
|
|
details_compare.End();
|
|
}
|
|
key_compare.End();
|
|
|
|
return Pop();
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildElementIndexHash(HValue* index) {
|
|
int32_t seed_value = static_cast<uint32_t>(isolate()->heap()->HashSeed());
|
|
HValue* seed = Add<HConstant>(seed_value);
|
|
HValue* hash = AddUncasted<HBitwise>(Token::BIT_XOR, index, seed);
|
|
|
|
// hash = ~hash + (hash << 15);
|
|
HValue* shifted_hash = AddUncasted<HShl>(hash, Add<HConstant>(15));
|
|
HValue* not_hash = AddUncasted<HBitwise>(Token::BIT_XOR, hash,
|
|
graph()->GetConstantMinus1());
|
|
hash = AddUncasted<HAdd>(shifted_hash, not_hash);
|
|
|
|
// hash = hash ^ (hash >> 12);
|
|
shifted_hash = AddUncasted<HShr>(hash, Add<HConstant>(12));
|
|
hash = AddUncasted<HBitwise>(Token::BIT_XOR, hash, shifted_hash);
|
|
|
|
// hash = hash + (hash << 2);
|
|
shifted_hash = AddUncasted<HShl>(hash, Add<HConstant>(2));
|
|
hash = AddUncasted<HAdd>(hash, shifted_hash);
|
|
|
|
// hash = hash ^ (hash >> 4);
|
|
shifted_hash = AddUncasted<HShr>(hash, Add<HConstant>(4));
|
|
hash = AddUncasted<HBitwise>(Token::BIT_XOR, hash, shifted_hash);
|
|
|
|
// hash = hash * 2057;
|
|
hash = AddUncasted<HMul>(hash, Add<HConstant>(2057));
|
|
hash->ClearFlag(HValue::kCanOverflow);
|
|
|
|
// hash = hash ^ (hash >> 16);
|
|
shifted_hash = AddUncasted<HShr>(hash, Add<HConstant>(16));
|
|
return AddUncasted<HBitwise>(Token::BIT_XOR, hash, shifted_hash);
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildUncheckedDictionaryElementLoad(HValue* receiver,
|
|
HValue* key) {
|
|
HValue* elements = AddLoadElements(receiver);
|
|
|
|
HValue* hash = BuildElementIndexHash(key);
|
|
|
|
HValue* capacity = Add<HLoadKeyed>(
|
|
elements,
|
|
Add<HConstant>(NameDictionary::kCapacityIndex),
|
|
static_cast<HValue*>(NULL),
|
|
FAST_SMI_ELEMENTS);
|
|
|
|
HValue* mask = AddUncasted<HSub>(capacity, graph()->GetConstant1());
|
|
mask->ChangeRepresentation(Representation::Integer32());
|
|
mask->ClearFlag(HValue::kCanOverflow);
|
|
|
|
return BuildUncheckedDictionaryElementLoadHelper(elements, key,
|
|
hash, mask, 0);
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildNumberToString(HValue* object,
|
|
Handle<Type> type) {
|
|
NoObservableSideEffectsScope scope(this);
|
|
|
|
// Convert constant numbers at compile time.
|
|
if (object->IsConstant() && HConstant::cast(object)->HasNumberValue()) {
|
|
Handle<Object> number = HConstant::cast(object)->handle(isolate());
|
|
Handle<String> result = isolate()->factory()->NumberToString(number);
|
|
return Add<HConstant>(result);
|
|
}
|
|
|
|
// Create a joinable continuation.
|
|
HIfContinuation found(graph()->CreateBasicBlock(),
|
|
graph()->CreateBasicBlock());
|
|
|
|
// Load the number string cache.
|
|
HValue* number_string_cache =
|
|
Add<HLoadRoot>(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<HSar>(mask, graph()->GetConstant1());
|
|
mask = AddUncasted<HSub>(mask, graph()->GetConstant1());
|
|
|
|
// Check whether object is a smi.
|
|
IfBuilder if_objectissmi(this);
|
|
if_objectissmi.If<HIsSmiAndBranch>(object);
|
|
if_objectissmi.Then();
|
|
{
|
|
// Compute hash for smi similar to smi_get_hash().
|
|
HValue* hash = AddUncasted<HBitwise>(Token::BIT_AND, object, mask);
|
|
|
|
// Load the key.
|
|
HValue* key_index = AddUncasted<HShl>(hash, graph()->GetConstant1());
|
|
HValue* key = Add<HLoadKeyed>(number_string_cache, key_index,
|
|
static_cast<HValue*>(NULL),
|
|
FAST_ELEMENTS, ALLOW_RETURN_HOLE);
|
|
|
|
// Check if object == key.
|
|
IfBuilder if_objectiskey(this);
|
|
if_objectiskey.If<HCompareObjectEqAndBranch>(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<HCompareMap>(
|
|
object, isolate()->factory()->heap_number_map());
|
|
if_objectisnumber.Then();
|
|
{
|
|
// Compute hash for heap number similar to double_get_hash().
|
|
HValue* low = Add<HLoadNamedField>(
|
|
object, HObjectAccess::ForHeapNumberValueLowestBits());
|
|
HValue* high = Add<HLoadNamedField>(
|
|
object, HObjectAccess::ForHeapNumberValueHighestBits());
|
|
HValue* hash = AddUncasted<HBitwise>(Token::BIT_XOR, low, high);
|
|
hash = AddUncasted<HBitwise>(Token::BIT_AND, hash, mask);
|
|
|
|
// Load the key.
|
|
HValue* key_index = AddUncasted<HShl>(hash, graph()->GetConstant1());
|
|
HValue* key = Add<HLoadKeyed>(number_string_cache, key_index,
|
|
static_cast<HValue*>(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<HIsSmiAndBranch>(key);
|
|
if_keyisnotsmi.Then();
|
|
{
|
|
// Check if values of key and object match.
|
|
IfBuilder if_keyeqobject(this);
|
|
if_keyeqobject.If<HCompareNumericAndBranch>(
|
|
Add<HLoadNamedField>(key, HObjectAccess::ForHeapNumberValue()),
|
|
Add<HLoadNamedField>(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<HAdd>(key_index, graph()->GetConstant1());
|
|
Push(Add<HLoadKeyed>(number_string_cache, value_index,
|
|
static_cast<HValue*>(NULL),
|
|
FAST_ELEMENTS, ALLOW_RETURN_HOLE));
|
|
}
|
|
if_found.Else();
|
|
{
|
|
// Cache miss, fallback to runtime.
|
|
Add<HPushArgument>(object);
|
|
Push(Add<HCallRuntime>(
|
|
isolate()->factory()->empty_string(),
|
|
Runtime::FunctionForId(Runtime::kNumberToStringSkipCache),
|
|
1));
|
|
}
|
|
if_found.End();
|
|
|
|
return Pop();
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildSeqStringSizeFor(HValue* length,
|
|
String::Encoding encoding) {
|
|
STATIC_ASSERT((SeqString::kHeaderSize & kObjectAlignmentMask) == 0);
|
|
HValue* size = length;
|
|
if (encoding == String::TWO_BYTE_ENCODING) {
|
|
size = AddUncasted<HShl>(length, graph()->GetConstant1());
|
|
size->ClearFlag(HValue::kCanOverflow);
|
|
size->SetFlag(HValue::kUint32);
|
|
}
|
|
size = AddUncasted<HAdd>(size, Add<HConstant>(static_cast<int32_t>(
|
|
SeqString::kHeaderSize + kObjectAlignmentMask)));
|
|
size->ClearFlag(HValue::kCanOverflow);
|
|
size = AddUncasted<HBitwise>(
|
|
Token::BIT_AND, size, Add<HConstant>(static_cast<int32_t>(
|
|
~kObjectAlignmentMask)));
|
|
return size;
|
|
}
|
|
|
|
|
|
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<HAdd>(src_offset, index);
|
|
HValue* value =
|
|
AddUncasted<HSeqStringGetChar>(src_encoding, src, src_index);
|
|
HValue* dst_index = AddUncasted<HAdd>(dst_offset, index);
|
|
Add<HSeqStringSetChar>(dst_encoding, dst, dst_index, value);
|
|
}
|
|
loop.EndBody();
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildUncheckedStringAdd(HValue* left,
|
|
HValue* right,
|
|
PretenureFlag pretenure_flag) {
|
|
// Determine the string lengths.
|
|
HValue* left_length = Add<HLoadNamedField>(
|
|
left, HObjectAccess::ForStringLength());
|
|
HValue* right_length = Add<HLoadNamedField>(
|
|
right, HObjectAccess::ForStringLength());
|
|
|
|
// 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 valie. Otherwise, for platforms with 32-bit smis, we do
|
|
HValue* length = AddUncasted<HAdd>(left_length, right_length);
|
|
STATIC_ASSERT(String::kMaxLength <= Smi::kMaxValue);
|
|
if (String::kMaxLength != Smi::kMaxValue) {
|
|
IfBuilder if_nooverflow(this);
|
|
if_nooverflow.If<HCompareNumericAndBranch>(
|
|
length, Add<HConstant>(String::kMaxLength), Token::LTE);
|
|
if_nooverflow.Then();
|
|
if_nooverflow.ElseDeopt("String length exceeds limit");
|
|
}
|
|
|
|
// Determine the string instance types.
|
|
HLoadNamedField* left_instance_type = Add<HLoadNamedField>(
|
|
Add<HLoadNamedField>(left, HObjectAccess::ForMap()),
|
|
HObjectAccess::ForMapInstanceType());
|
|
HLoadNamedField* right_instance_type = Add<HLoadNamedField>(
|
|
Add<HLoadNamedField>(right, HObjectAccess::ForMap()),
|
|
HObjectAccess::ForMapInstanceType());
|
|
|
|
// Compute difference of instance types.
|
|
HValue* xored_instance_types = AddUncasted<HBitwise>(
|
|
Token::BIT_XOR, left_instance_type, right_instance_type);
|
|
|
|
// Check if we should create a cons string.
|
|
IfBuilder if_createcons(this);
|
|
if_createcons.If<HCompareNumericAndBranch>(
|
|
length, Add<HConstant>(ConsString::kMinLength), Token::GTE);
|
|
if_createcons.Then();
|
|
{
|
|
// 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.
|
|
HAllocate* string = Add<HAllocate>(Add<HConstant>(ConsString::kSize),
|
|
HType::String(), pretenure_flag,
|
|
CONS_STRING_TYPE);
|
|
|
|
// Compute the intersection of instance types.
|
|
HValue* anded_instance_types = AddUncasted<HBitwise>(
|
|
Token::BIT_AND, 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<HCompareNumericAndBranch>(
|
|
AddUncasted<HBitwise>(
|
|
Token::BIT_AND, anded_instance_types,
|
|
Add<HConstant>(static_cast<int32_t>(
|
|
kStringEncodingMask | kOneByteDataHintMask))),
|
|
graph()->GetConstant0(), Token::NE);
|
|
if_onebyte.Or();
|
|
STATIC_ASSERT(kOneByteStringTag != 0 &&
|
|
kOneByteDataHintTag != 0 &&
|
|
kOneByteDataHintTag != kOneByteStringTag);
|
|
if_onebyte.If<HCompareNumericAndBranch>(
|
|
AddUncasted<HBitwise>(
|
|
Token::BIT_AND, xored_instance_types,
|
|
Add<HConstant>(static_cast<int32_t>(
|
|
kOneByteStringTag | kOneByteDataHintTag))),
|
|
Add<HConstant>(static_cast<int32_t>(
|
|
kOneByteStringTag | kOneByteDataHintTag)), Token::EQ);
|
|
if_onebyte.Then();
|
|
{
|
|
// We can safely skip the write barrier for storing the map here.
|
|
Handle<Map> map = isolate()->factory()->cons_ascii_string_map();
|
|
AddStoreMapConstantNoWriteBarrier(string, map);
|
|
}
|
|
if_onebyte.Else();
|
|
{
|
|
// We can safely skip the write barrier for storing the map here.
|
|
Handle<Map> map = isolate()->factory()->cons_string_map();
|
|
AddStoreMapConstantNoWriteBarrier(string, map);
|
|
}
|
|
if_onebyte.End();
|
|
|
|
// Initialize the cons string fields.
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForStringHashField(),
|
|
Add<HConstant>(String::kEmptyHashField));
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForStringLength(), length);
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForConsStringFirst(), left);
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForConsStringSecond(),
|
|
right);
|
|
|
|
// Count the native string addition.
|
|
AddIncrementCounter(isolate()->counters()->string_add_native());
|
|
|
|
// Cons string is result.
|
|
Push(string);
|
|
}
|
|
if_createcons.Else();
|
|
{
|
|
// Compute union of instance types.
|
|
HValue* ored_instance_types = AddUncasted<HBitwise>(
|
|
Token::BIT_OR, 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<HCompareNumericAndBranch>(
|
|
AddUncasted<HBitwise>(
|
|
Token::BIT_AND, xored_instance_types,
|
|
Add<HConstant>(static_cast<int32_t>(kStringEncodingMask))),
|
|
graph()->GetConstant0(), Token::EQ);
|
|
if_sameencodingandsequential.And();
|
|
STATIC_ASSERT(kSeqStringTag == 0);
|
|
if_sameencodingandsequential.If<HCompareNumericAndBranch>(
|
|
AddUncasted<HBitwise>(
|
|
Token::BIT_AND, ored_instance_types,
|
|
Add<HConstant>(static_cast<int32_t>(kStringRepresentationMask))),
|
|
graph()->GetConstant0(), Token::EQ);
|
|
if_sameencodingandsequential.Then();
|
|
{
|
|
// Check if the result is a one-byte string.
|
|
IfBuilder if_onebyte(this);
|
|
STATIC_ASSERT(kOneByteStringTag != 0);
|
|
if_onebyte.If<HCompareNumericAndBranch>(
|
|
AddUncasted<HBitwise>(
|
|
Token::BIT_AND, ored_instance_types,
|
|
Add<HConstant>(static_cast<int32_t>(kStringEncodingMask))),
|
|
graph()->GetConstant0(), Token::NE);
|
|
if_onebyte.Then();
|
|
{
|
|
// Calculate the number of bytes needed for the characters in the
|
|
// string while observing object alignment.
|
|
HValue* size = BuildSeqStringSizeFor(
|
|
length, String::ONE_BYTE_ENCODING);
|
|
|
|
// Allocate the ASCII string object.
|
|
Handle<Map> map = isolate()->factory()->ascii_string_map();
|
|
HAllocate* string = Add<HAllocate>(size, HType::String(),
|
|
pretenure_flag, ASCII_STRING_TYPE);
|
|
string->set_known_initial_map(map);
|
|
|
|
// We can safely skip the write barrier for storing map here.
|
|
AddStoreMapConstantNoWriteBarrier(string, map);
|
|
|
|
// Length must be stored into the string before we copy characters to
|
|
// make debug verification code happy.
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForStringLength(),
|
|
length);
|
|
|
|
// Copy bytes from the left string.
|
|
BuildCopySeqStringChars(
|
|
left, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
|
string, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
|
left_length);
|
|
|
|
// Copy bytes from the right string.
|
|
BuildCopySeqStringChars(
|
|
right, graph()->GetConstant0(), String::ONE_BYTE_ENCODING,
|
|
string, left_length, String::ONE_BYTE_ENCODING,
|
|
right_length);
|
|
|
|
// Count the native string addition.
|
|
AddIncrementCounter(isolate()->counters()->string_add_native());
|
|
|
|
// Return the string.
|
|
Push(string);
|
|
}
|
|
if_onebyte.Else();
|
|
{
|
|
// Calculate the number of bytes needed for the characters in the
|
|
// string while observing object alignment.
|
|
HValue* size = BuildSeqStringSizeFor(
|
|
length, String::TWO_BYTE_ENCODING);
|
|
|
|
// Allocate the two-byte string object.
|
|
Handle<Map> map = isolate()->factory()->string_map();
|
|
HAllocate* string = Add<HAllocate>(size, HType::String(),
|
|
pretenure_flag, STRING_TYPE);
|
|
string->set_known_initial_map(map);
|
|
|
|
// We can safely skip the write barrier for storing map here.
|
|
AddStoreMapConstantNoWriteBarrier(string, map);
|
|
|
|
// Length must be stored into the string before we copy characters to
|
|
// make debug verification code happy.
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForStringLength(),
|
|
length);
|
|
|
|
// Copy bytes from the left string.
|
|
BuildCopySeqStringChars(
|
|
left, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
|
string, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
|
left_length);
|
|
|
|
// Copy bytes from the right string.
|
|
BuildCopySeqStringChars(
|
|
right, graph()->GetConstant0(), String::TWO_BYTE_ENCODING,
|
|
string, left_length, String::TWO_BYTE_ENCODING,
|
|
right_length);
|
|
|
|
// Return the string.
|
|
Push(string);
|
|
}
|
|
if_onebyte.End();
|
|
|
|
// Initialize the (common) string fields.
|
|
HValue* string = Pop();
|
|
Add<HStoreNamedField>(string, HObjectAccess::ForStringHashField(),
|
|
Add<HConstant>(String::kEmptyHashField));
|
|
|
|
// Count the native string addition.
|
|
AddIncrementCounter(isolate()->counters()->string_add_native());
|
|
|
|
Push(string);
|
|
}
|
|
if_sameencodingandsequential.Else();
|
|
{
|
|
// Fallback to the runtime to add the two strings.
|
|
Add<HPushArgument>(left);
|
|
Add<HPushArgument>(right);
|
|
Push(Add<HCallRuntime>(isolate()->factory()->empty_string(),
|
|
Runtime::FunctionForId(Runtime::kStringAdd),
|
|
2));
|
|
}
|
|
if_sameencodingandsequential.End();
|
|
}
|
|
if_createcons.End();
|
|
|
|
return Pop();
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildStringAdd(HValue* left,
|
|
HValue* right,
|
|
PretenureFlag pretenure_flag) {
|
|
// Determine the string lengths.
|
|
HValue* left_length = Add<HLoadNamedField>(
|
|
left, HObjectAccess::ForStringLength());
|
|
HValue* right_length = Add<HLoadNamedField>(
|
|
right, HObjectAccess::ForStringLength());
|
|
|
|
// Check if left string is empty.
|
|
IfBuilder if_leftisempty(this);
|
|
if_leftisempty.If<HCompareNumericAndBranch>(
|
|
left_length, graph()->GetConstant0(), Token::EQ);
|
|
if_leftisempty.Then();
|
|
{
|
|
// Count the native string addition.
|
|
AddIncrementCounter(isolate()->counters()->string_add_native());
|
|
|
|
// Just return the right string.
|
|
Push(right);
|
|
}
|
|
if_leftisempty.Else();
|
|
{
|
|
// Check if right string is empty.
|
|
IfBuilder if_rightisempty(this);
|
|
if_rightisempty.If<HCompareNumericAndBranch>(
|
|
right_length, graph()->GetConstant0(), Token::EQ);
|
|
if_rightisempty.Then();
|
|
{
|
|
// Count the native string addition.
|
|
AddIncrementCounter(isolate()->counters()->string_add_native());
|
|
|
|
// Just return the left string.
|
|
Push(left);
|
|
}
|
|
if_rightisempty.Else();
|
|
{
|
|
// Concatenate the two non-empty strings.
|
|
Push(BuildUncheckedStringAdd(left, right, pretenure_flag));
|
|
}
|
|
if_rightisempty.End();
|
|
}
|
|
if_leftisempty.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) || !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<HCheckMaps>(
|
|
elements, isolate()->factory()->fixed_array_map(), top_info());
|
|
check_cow_map->ClearGVNFlag(kDependsOnElementsKind);
|
|
}
|
|
HInstruction* length = NULL;
|
|
if (is_js_array) {
|
|
length = Add<HLoadNamedField>(
|
|
checked_object, HObjectAccess::ForArrayLength(elements_kind));
|
|
} else {
|
|
length = AddLoadFixedArrayLength(elements);
|
|
}
|
|
length->set_type(HType::Smi());
|
|
HValue* checked_key = NULL;
|
|
if (IsExternalArrayElementsKind(elements_kind)) {
|
|
if (store_mode == STORE_NO_TRANSITION_IGNORE_OUT_OF_BOUNDS) {
|
|
NoObservableSideEffectsScope no_effects(this);
|
|
HLoadExternalArrayPointer* external_elements =
|
|
Add<HLoadExternalArrayPointer>(elements);
|
|
IfBuilder length_checker(this);
|
|
length_checker.If<HCompareNumericAndBranch>(key, length, Token::LT);
|
|
length_checker.Then();
|
|
IfBuilder negative_checker(this);
|
|
HValue* bounds_check = negative_checker.If<HCompareNumericAndBranch>(
|
|
key, graph()->GetConstant0(), Token::GTE);
|
|
negative_checker.Then();
|
|
HInstruction* result = AddElementAccess(
|
|
external_elements, 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<HBoundsCheck>(key, length);
|
|
HLoadExternalArrayPointer* external_elements =
|
|
Add<HLoadExternalArrayPointer>(elements);
|
|
return AddElementAccess(
|
|
external_elements, 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<HForceRepresentation>(val, Representation::Smi());
|
|
}
|
|
|
|
if (IsGrowStoreMode(store_mode)) {
|
|
NoObservableSideEffectsScope no_effects(this);
|
|
elements = BuildCheckForCapacityGrow(checked_object, elements,
|
|
elements_kind, length, key,
|
|
is_js_array);
|
|
checked_key = key;
|
|
} else {
|
|
checked_key = Add<HBoundsCheck>(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<HCheckMaps>(
|
|
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<HConstant>(JSObject::kInitialMaxFastElementArray);
|
|
HInstruction* checked_length = Add<HBoundsCheck>(length_argument,
|
|
max_alloc_length);
|
|
IfBuilder if_builder(this);
|
|
if_builder.If<HCompareNumericAndBranch>(checked_length, constant_zero,
|
|
Token::EQ);
|
|
if_builder.Then();
|
|
const int initial_capacity = JSArray::kPreallocatedArrayElements;
|
|
HConstant* initial_capacity_node = Add<HConstant>(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<HConstant>(elements_size);
|
|
HValue* mul = AddUncasted<HMul>(capacity, elements_size_value);
|
|
mul->ClearFlag(HValue::kCanOverflow);
|
|
|
|
HConstant* header_size = Add<HConstant>(FixedArray::kHeaderSize);
|
|
HValue* total_size = AddUncasted<HAdd>(mul, header_size);
|
|
total_size->ClearFlag(HValue::kCanOverflow);
|
|
|
|
return Add<HAllocate>(total_size, HType::JSArray(),
|
|
isolate()->heap()->GetPretenureMode(), instance_type);
|
|
}
|
|
|
|
|
|
void HGraphBuilder::BuildInitializeElementsHeader(HValue* elements,
|
|
ElementsKind kind,
|
|
HValue* capacity) {
|
|
Factory* factory = isolate()->factory();
|
|
Handle<Map> map = IsFastDoubleElementsKind(kind)
|
|
? factory->fixed_double_array_map()
|
|
: factory->fixed_array_map();
|
|
|
|
AddStoreMapConstant(elements, map);
|
|
Add<HStoreNamedField>(elements, HObjectAccess::ForFixedArrayLength(),
|
|
capacity);
|
|
}
|
|
|
|
|
|
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<HForceRepresentation>(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<HStoreNamedField>(array, HObjectAccess::ForMap(), array_map);
|
|
|
|
HConstant* empty_fixed_array =
|
|
Add<HConstant>(isolate()->factory()->empty_fixed_array());
|
|
|
|
HObjectAccess access = HObjectAccess::ForPropertiesPointer();
|
|
Add<HStoreNamedField>(array, access, empty_fixed_array);
|
|
Add<HStoreNamedField>(array, HObjectAccess::ForArrayLength(elements_kind),
|
|
length_field);
|
|
|
|
if (mode == TRACK_ALLOCATION_SITE) {
|
|
BuildCreateAllocationMemento(
|
|
array, Add<HConstant>(JSArray::kSize), allocation_site_payload);
|
|
}
|
|
|
|
int elements_location = JSArray::kSize;
|
|
if (mode == TRACK_ALLOCATION_SITE) {
|
|
elements_location += AllocationMemento::kSize;
|
|
}
|
|
|
|
HInnerAllocatedObject* elements = Add<HInnerAllocatedObject>(
|
|
array, Add<HConstant>(elements_location));
|
|
Add<HStoreNamedField>(array, HObjectAccess::ForElementsPointer(), elements);
|
|
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_PIXEL_ELEMENTS) {
|
|
val = Add<HClampToUint8>(val);
|
|
}
|
|
return Add<HStoreKeyed>(elements, checked_key, val, elements_kind);
|
|
}
|
|
|
|
ASSERT(!is_store);
|
|
ASSERT(val == NULL);
|
|
HLoadKeyed* load = Add<HLoadKeyed>(
|
|
elements, checked_key, dependency, elements_kind, load_mode);
|
|
if (FLAG_opt_safe_uint32_operations &&
|
|
elements_kind == EXTERNAL_UNSIGNED_INT_ELEMENTS) {
|
|
graph()->RecordUint32Instruction(load);
|
|
}
|
|
return load;
|
|
}
|
|
|
|
|
|
HLoadNamedField* HGraphBuilder::AddLoadElements(HValue* object) {
|
|
return Add<HLoadNamedField>(object, HObjectAccess::ForElementsPointer());
|
|
}
|
|
|
|
|
|
HLoadNamedField* HGraphBuilder::AddLoadFixedArrayLength(HValue* object) {
|
|
return Add<HLoadNamedField>(object,
|
|
HObjectAccess::ForFixedArrayLength());
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildNewElementsCapacity(HValue* old_capacity) {
|
|
HValue* half_old_capacity = AddUncasted<HShr>(old_capacity,
|
|
graph_->GetConstant1());
|
|
|
|
HValue* new_capacity = AddUncasted<HAdd>(half_old_capacity, old_capacity);
|
|
new_capacity->ClearFlag(HValue::kCanOverflow);
|
|
|
|
HValue* min_growth = Add<HConstant>(16);
|
|
|
|
new_capacity = AddUncasted<HAdd>(new_capacity, min_growth);
|
|
new_capacity->ClearFlag(HValue::kCanOverflow);
|
|
|
|
return new_capacity;
|
|
}
|
|
|
|
|
|
void HGraphBuilder::BuildNewSpaceArrayCheck(HValue* length, ElementsKind kind) {
|
|
Heap* heap = isolate()->heap();
|
|
int element_size = IsFastDoubleElementsKind(kind) ? kDoubleSize
|
|
: kPointerSize;
|
|
int max_size = heap->MaxRegularSpaceAllocationSize() / element_size;
|
|
max_size -= JSArray::kSize / element_size;
|
|
HConstant* max_size_constant = Add<HConstant>(max_size);
|
|
Add<HBoundsCheck>(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<HStoreNamedField>(object, HObjectAccess::ForElementsPointer(),
|
|
new_elements);
|
|
|
|
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<HConstant>(factory->the_hole_value())
|
|
: Add<HConstant>(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<HConstant>(i);
|
|
Add<HStoreKeyed>(elements, key, hole, elements_kind);
|
|
}
|
|
} else {
|
|
LoopBuilder builder(this, context(), LoopBuilder::kPostIncrement);
|
|
|
|
HValue* key = builder.BeginBody(from, to, Token::LT);
|
|
|
|
Add<HStoreKeyed>(elements, key, hole, elements_kind);
|
|
|
|
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<HLoadKeyed>(from_elements, key,
|
|
static_cast<HValue*>(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<HCompareHoleAndBranch>(element);
|
|
if_hole.Then();
|
|
HConstant* hole_constant = IsFastDoubleElementsKind(to_elements_kind)
|
|
? Add<HConstant>(FixedDoubleArray::hole_nan_as_double())
|
|
: graph()->GetConstantHole();
|
|
Add<HStoreKeyed>(to_elements, key, hole_constant, kind);
|
|
if_hole.Else();
|
|
HStoreKeyed* store = Add<HStoreKeyed>(to_elements, key, element, kind);
|
|
store->SetFlag(HValue::kAllowUndefinedAsNaN);
|
|
if_hole.End();
|
|
} else {
|
|
HStoreKeyed* store = Add<HStoreKeyed>(to_elements, key, element, kind);
|
|
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<HConstant>(size);
|
|
HInstruction* object = Add<HAllocate>(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<HStoreNamedField>(object, access,
|
|
Add<HLoadNamedField>(boilerplate, access));
|
|
}
|
|
}
|
|
|
|
// Create an allocation site info if requested.
|
|
if (mode == TRACK_ALLOCATION_SITE) {
|
|
BuildCreateAllocationMemento(
|
|
object, Add<HConstant>(JSArray::kSize), allocation_site);
|
|
}
|
|
|
|
if (length > 0) {
|
|
HValue* boilerplate_elements = AddLoadElements(boilerplate);
|
|
HValue* object_elements;
|
|
if (IsFastDoubleElementsKind(kind)) {
|
|
HValue* elems_size = Add<HConstant>(FixedDoubleArray::SizeFor(length));
|
|
object_elements = Add<HAllocate>(elems_size, HType::JSArray(),
|
|
NOT_TENURED, FIXED_DOUBLE_ARRAY_TYPE);
|
|
} else {
|
|
HValue* elems_size = Add<HConstant>(FixedArray::SizeFor(length));
|
|
object_elements = Add<HAllocate>(elems_size, HType::JSArray(),
|
|
NOT_TENURED, FIXED_ARRAY_TYPE);
|
|
}
|
|
Add<HStoreNamedField>(object, HObjectAccess::ForElementsPointer(),
|
|
object_elements);
|
|
|
|
// Copy the elements array header.
|
|
for (int i = 0; i < FixedArrayBase::kHeaderSize; i += kPointerSize) {
|
|
HObjectAccess access = HObjectAccess::ForFixedArrayHeader(i);
|
|
Add<HStoreNamedField>(object_elements, access,
|
|
Add<HLoadNamedField>(boilerplate_elements, access));
|
|
}
|
|
|
|
// 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<HConstant>(i);
|
|
HInstruction* value = Add<HLoadKeyed>(boilerplate_elements, key_constant,
|
|
static_cast<HValue*>(NULL), kind);
|
|
Add<HStoreKeyed>(object_elements, key_constant, value, kind);
|
|
}
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
|
|
void HGraphBuilder::BuildCompareNil(
|
|
HValue* value,
|
|
Handle<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<HCompareObjectEqAndBranch>(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<HCompareObjectEqAndBranch>(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<HIsUndetectableAndBranch>(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<HInnerAllocatedObject>(
|
|
previous_object, previous_object_size);
|
|
AddStoreMapConstant(
|
|
allocation_memento, isolate()->factory()->allocation_memento_map());
|
|
Add<HStoreNamedField>(
|
|
allocation_memento,
|
|
HObjectAccess::ForAllocationMementoSite(),
|
|
allocation_site);
|
|
if (FLAG_allocation_site_pretenuring) {
|
|
HValue* memento_create_count = Add<HLoadNamedField>(
|
|
allocation_site, HObjectAccess::ForAllocationSiteOffset(
|
|
AllocationSite::kMementoCreateCountOffset));
|
|
memento_create_count = AddUncasted<HAdd>(
|
|
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<HStoreNamedField>(
|
|
allocation_site, HObjectAccess::ForAllocationSiteOffset(
|
|
AllocationSite::kMementoCreateCountOffset), memento_create_count);
|
|
// No write barrier needed to store a smi.
|
|
store->SkipWriteBarrier();
|
|
}
|
|
}
|
|
|
|
|
|
HInstruction* HGraphBuilder::BuildGetNativeContext() {
|
|
// Get the global context, then the native context
|
|
HInstruction* global_object = Add<HGlobalObject>();
|
|
HObjectAccess access = HObjectAccess::ForJSObjectOffset(
|
|
GlobalObject::kNativeContextOffset);
|
|
return Add<HLoadNamedField>(global_object, access);
|
|
}
|
|
|
|
|
|
HInstruction* HGraphBuilder::BuildGetArrayFunction() {
|
|
HInstruction* native_context = BuildGetNativeContext();
|
|
HInstruction* index =
|
|
Add<HConstant>(static_cast<int32_t>(Context::ARRAY_FUNCTION_INDEX));
|
|
return Add<HLoadKeyed>(
|
|
native_context, index, static_cast<HValue*>(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) {
|
|
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> map(builder()->isolate()->get_initial_js_array_map(kind_),
|
|
builder()->isolate());
|
|
return builder()->Add<HConstant>(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);
|
|
}
|
|
|
|
HInstruction* native_context = builder()->BuildGetNativeContext();
|
|
HInstruction* index = builder()->Add<HConstant>(
|
|
static_cast<int32_t>(Context::JS_ARRAY_MAPS_INDEX));
|
|
|
|
HInstruction* map_array = builder()->Add<HLoadKeyed>(
|
|
native_context, index, static_cast<HValue*>(NULL), FAST_ELEMENTS);
|
|
|
|
HInstruction* kind_index = builder()->Add<HConstant>(kind_);
|
|
|
|
return builder()->Add<HLoadKeyed>(
|
|
map_array, kind_index, static_cast<HValue*>(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<HConstant>(elements_size());
|
|
HInstruction* mul = HMul::NewImul(builder()->zone(), builder()->context(),
|
|
length_node, elements_size_value);
|
|
builder()->AddInstruction(mul);
|
|
HInstruction* base = builder()->Add<HConstant>(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<HConstant>(base_size);
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::JSArrayBuilder::AllocateEmptyArray() {
|
|
HValue* size_in_bytes = EstablishEmptyArrayAllocationSize();
|
|
HConstant* capacity = builder()->Add<HConstant>(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<HForceRepresentation>(capacity,
|
|
Representation::Smi());
|
|
length_field =
|
|
builder()->AddUncasted<HForceRepresentation>(length_field,
|
|
Representation::Smi());
|
|
// Allocate (dealing with failure appropriately)
|
|
HAllocate* new_object = builder()->Add<HAllocate>(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> map) {
|
|
return Add<HStoreNamedField>(object, HObjectAccess::ForMap(),
|
|
Add<HConstant>(map));
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::AddLoadJSBuiltin(Builtins::JavaScript builtin) {
|
|
HGlobalObject* global_object = Add<HGlobalObject>();
|
|
HObjectAccess access = HObjectAccess::ForJSObjectOffset(
|
|
GlobalObject::kBuiltinsOffset);
|
|
HValue* builtins = Add<HLoadNamedField>(global_object, access);
|
|
HObjectAccess function_access = HObjectAccess::ForJSObjectOffset(
|
|
JSBuiltinsObject::OffsetOfFunctionWithId(builtin));
|
|
return Add<HLoadNamedField>(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->isolate());
|
|
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<HBasicBlock*>* 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<HBasicBlock*>* 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<HBasicBlock*>* 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<HBasicBlock*>* 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<HBasicBlock*>* 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<HBasicBlock*> 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<HPhi*>(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<HSimulate>(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<HSimulate>(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<HSimulate>(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<HBranch>(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<HPushArgument>(Pop()));
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitArgumentList(
|
|
ZoneList<Expression*>* arguments) {
|
|
for (int i = 0; i < arguments->length(); i++) {
|
|
CHECK_ALIVE(VisitArgument(arguments->at(i)));
|
|
}
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitExpressions(
|
|
ZoneList<Expression*>* 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<HSimulate>(BailoutId::Declarations());
|
|
|
|
Add<HStackCheck>(HStackCheck::kFunctionEntry);
|
|
|
|
VisitStatements(current_info()->function()->body());
|
|
if (HasStackOverflow()) return false;
|
|
|
|
if (current_block() != NULL) {
|
|
Add<HReturn>(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<Code> unoptimized_code(current_info()->shared_info()->code());
|
|
ASSERT(unoptimized_code->kind() == Code::FUNCTION);
|
|
Handle<TypeFeedbackInfo> 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<HEnvironmentLivenessAnalysisPhase>();
|
|
}
|
|
|
|
if (!CheckConstPhiUses()) {
|
|
*bailout_reason = kUnsupportedPhiUseOfConstVariable;
|
|
return false;
|
|
}
|
|
Run<HRedundantPhiEliminationPhase>();
|
|
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<HMarkUnreachableBlocksPhase>();
|
|
|
|
if (FLAG_dead_code_elimination) Run<HDeadCodeEliminationPhase>();
|
|
if (FLAG_use_escape_analysis) Run<HEscapeAnalysisPhase>();
|
|
|
|
if (FLAG_load_elimination) Run<HLoadEliminationPhase>();
|
|
|
|
CollectPhis();
|
|
|
|
if (has_osr()) osr()->FinishOsrValues();
|
|
|
|
Run<HInferRepresentationPhase>();
|
|
|
|
// 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<HMergeRemovableSimulatesPhase>();
|
|
|
|
Run<HMarkDeoptimizeOnUndefinedPhase>();
|
|
Run<HRepresentationChangesPhase>();
|
|
|
|
Run<HInferTypesPhase>();
|
|
|
|
// 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<HUint32AnalysisPhase>();
|
|
|
|
if (FLAG_use_canonicalizing) Run<HCanonicalizePhase>();
|
|
|
|
if (FLAG_use_gvn) Run<HGlobalValueNumberingPhase>();
|
|
|
|
if (FLAG_check_elimination) Run<HCheckEliminationPhase>();
|
|
|
|
if (FLAG_use_range) Run<HRangeAnalysisPhase>();
|
|
|
|
Run<HComputeChangeUndefinedToNaN>();
|
|
Run<HComputeMinusZeroChecksPhase>();
|
|
|
|
// Eliminate redundant stack checks on backwards branches.
|
|
Run<HStackCheckEliminationPhase>();
|
|
|
|
if (FLAG_array_bounds_checks_elimination) Run<HBoundsCheckEliminationPhase>();
|
|
if (FLAG_array_bounds_checks_hoisting) Run<HBoundsCheckHoistingPhase>();
|
|
if (FLAG_array_index_dehoisting) Run<HDehoistIndexComputationsPhase>();
|
|
if (FLAG_dead_code_elimination) Run<HDeadCodeEliminationPhase>();
|
|
|
|
RestoreActualValues();
|
|
|
|
// Find unreachable code a second time, GVN and other optimizations may have
|
|
// made blocks unreachable that were previously reachable.
|
|
Run<HMarkUnreachableBlocksPhase>();
|
|
|
|
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) {
|
|
ASSERT(instruction->IsInformativeDefinition());
|
|
if (instruction->IsPurelyInformativeDefinition()) {
|
|
instruction->DeleteAndReplaceWith(instruction->RedefinedOperand());
|
|
} else {
|
|
instruction->ReplaceAllUsesWith(instruction->ActualValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template <class Instruction>
|
|
HInstruction* HOptimizedGraphBuilder::PreProcessCall(Instruction* call) {
|
|
int count = call->argument_count();
|
|
ZoneList<HValue*> arguments(count, zone());
|
|
for (int i = 0; i < count; ++i) {
|
|
arguments.Add(Pop(), zone());
|
|
}
|
|
|
|
while (!arguments.is_empty()) {
|
|
Add<HPushArgument>(arguments.RemoveLast());
|
|
}
|
|
return call;
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::SetUpScope(Scope* scope) {
|
|
// First special is HContext.
|
|
HInstruction* context = Add<HContext>();
|
|
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<HArgumentsObject>(environment()->parameter_count());
|
|
for (int i = 0; i < environment()->parameter_count(); ++i) {
|
|
HInstruction* parameter = Add<HParameter>(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<Statement*>* 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<HSimulate>(stmt->ThenId());
|
|
Visit(stmt->then_statement());
|
|
} else if (stmt->condition()->ToBooleanIsFalse()) {
|
|
Add<HSimulate>(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<HReturn>(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<HHasInstanceTypeAndBranch>(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 smi-literal smi comparisons,
|
|
// with a bounded number of clauses.
|
|
const int kCaseClauseLimit = 128;
|
|
ZoneList<CaseClause*>* clauses = stmt->cases();
|
|
int clause_count = clauses->length();
|
|
if (clause_count > kCaseClauseLimit) {
|
|
return Bailout(kSwitchStatementTooManyClauses);
|
|
}
|
|
|
|
ASSERT(stmt->switch_type() != SwitchStatement::UNKNOWN_SWITCH);
|
|
if (stmt->switch_type() == SwitchStatement::GENERIC_SWITCH) {
|
|
return Bailout(kSwitchStatementMixedOrNonLiteralSwitchLabels);
|
|
}
|
|
|
|
CHECK_ALIVE(VisitForValue(stmt->tag()));
|
|
Add<HSimulate>(stmt->EntryId());
|
|
HValue* tag_value = Pop();
|
|
HBasicBlock* first_test_block = current_block();
|
|
|
|
HUnaryControlInstruction* string_check = NULL;
|
|
HBasicBlock* not_string_block = NULL;
|
|
|
|
// Test switch's tag value if all clauses are string literals
|
|
if (stmt->switch_type() == SwitchStatement::STRING_SWITCH) {
|
|
first_test_block = graph()->CreateBasicBlock();
|
|
not_string_block = graph()->CreateBasicBlock();
|
|
string_check = New<HIsStringAndBranch>(
|
|
tag_value, first_test_block, not_string_block);
|
|
FinishCurrentBlock(string_check);
|
|
|
|
set_current_block(first_test_block);
|
|
}
|
|
|
|
// 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()) {
|
|
default_id = clause->EntryId();
|
|
continue;
|
|
}
|
|
|
|
// Generate a compare and branch.
|
|
CHECK_ALIVE(VisitForValue(clause->label()));
|
|
HValue* label_value = Pop();
|
|
|
|
HBasicBlock* next_test_block = graph()->CreateBasicBlock();
|
|
HBasicBlock* body_block = graph()->CreateBasicBlock();
|
|
|
|
HControlInstruction* compare;
|
|
|
|
if (stmt->switch_type() == SwitchStatement::SMI_SWITCH) {
|
|
if (!clause->compare_type()->Is(Type::Smi())) {
|
|
Add<HDeoptimize>("Non-smi switch type", Deoptimizer::SOFT);
|
|
}
|
|
|
|
HCompareNumericAndBranch* compare_ =
|
|
New<HCompareNumericAndBranch>(tag_value,
|
|
label_value,
|
|
Token::EQ_STRICT);
|
|
compare_->set_observed_input_representation(
|
|
Representation::Smi(), Representation::Smi());
|
|
compare = compare_;
|
|
} else {
|
|
compare = New<HStringCompareAndBranch>(tag_value,
|
|
label_value,
|
|
Token::EQ_STRICT);
|
|
}
|
|
|
|
compare->SetSuccessorAt(0, body_block);
|
|
compare->SetSuccessorAt(1, next_test_block);
|
|
FinishCurrentBlock(compare);
|
|
|
|
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();
|
|
|
|
if (not_string_block != NULL) {
|
|
BailoutId join_id = !default_id.IsNone() ? default_id : stmt->ExitId();
|
|
last_block = CreateJoin(last_block, not_string_block, join_id);
|
|
}
|
|
|
|
// 2. Loop over the clauses and the linked list of tests in lockstep,
|
|
// translating the clause bodies.
|
|
HBasicBlock* curr_test_block = first_test_block;
|
|
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) {
|
|
normal_block = last_block;
|
|
last_block = NULL; // Cleared to indicate we've handled it.
|
|
}
|
|
} else {
|
|
// If the current test block is deoptimizing due to an unhandled clause
|
|
// of the switch, the test instruction is in the next block since the
|
|
// deopt must end the current block.
|
|
if (curr_test_block->IsDeoptimizing()) {
|
|
ASSERT(curr_test_block->end()->SecondSuccessor() == NULL);
|
|
curr_test_block = curr_test_block->end()->FirstSuccessor();
|
|
}
|
|
normal_block = curr_test_block->end()->FirstSuccessor();
|
|
curr_test_block = curr_test_block->end()->SecondSuccessor();
|
|
}
|
|
|
|
// Identify a block to emit the body into.
|
|
if (normal_block == NULL) {
|
|
if (fall_through_block == NULL) {
|
|
// (a) Unreachable.
|
|
if (clause->is_default()) {
|
|
continue; // Might still be reachable clause bodies.
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
// (b) Reachable only as fall through.
|
|
set_current_block(fall_through_block);
|
|
}
|
|
} else if (fall_through_block == NULL) {
|
|
// (c) Reachable only normally.
|
|
set_current_block(normal_block);
|
|
} else {
|
|
// (d) Reachable both ways.
|
|
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<HSimulate>(stmt->StackCheckId());
|
|
HStackCheck* stack_check =
|
|
HStackCheck::cast(Add<HStackCheck>(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);
|
|
// The block for a true condition, the actual predecessor block of the
|
|
// back edge.
|
|
body_exit = graph()->CreateBasicBlock();
|
|
loop_successor = graph()->CreateBasicBlock();
|
|
CHECK_BAILOUT(VisitForControl(stmt->cond(), body_exit, loop_successor));
|
|
if (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<HForInPrepareMap>(enumerable);
|
|
Add<HSimulate>(stmt->PrepareId());
|
|
|
|
HInstruction* array = Add<HForInCacheArray>(
|
|
enumerable, map, DescriptorArray::kEnumCacheBridgeCacheIndex);
|
|
|
|
HInstruction* enum_length = Add<HMapEnumLength>(map);
|
|
|
|
HInstruction* start_index = Add<HConstant>(0);
|
|
|
|
Push(map);
|
|
Push(array);
|
|
Push(enum_length);
|
|
Push(start_index);
|
|
|
|
HInstruction* index_cache = Add<HForInCacheArray>(
|
|
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<HCompareNumericAndBranch>(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<HLoadKeyed>(
|
|
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<HCheckMapValue>(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<HAdd>(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();
|
|
}
|
|
|
|
|
|
static Handle<SharedFunctionInfo> SearchSharedFunctionInfo(
|
|
Code* unoptimized_code, FunctionLiteral* expr) {
|
|
int start_position = expr->start_position();
|
|
for (RelocIterator it(unoptimized_code); !it.done(); it.next()) {
|
|
RelocInfo* rinfo = it.rinfo();
|
|
if (rinfo->rmode() != RelocInfo::EMBEDDED_OBJECT) continue;
|
|
Object* obj = rinfo->target_object();
|
|
if (obj->IsSharedFunctionInfo()) {
|
|
SharedFunctionInfo* shared = SharedFunctionInfo::cast(obj);
|
|
if (shared->start_position() == start_position) {
|
|
return Handle<SharedFunctionInfo>(shared);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Handle<SharedFunctionInfo>();
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitFunctionLiteral(FunctionLiteral* expr) {
|
|
ASSERT(!HasStackOverflow());
|
|
ASSERT(current_block() != NULL);
|
|
ASSERT(current_block()->HasPredecessor());
|
|
Handle<SharedFunctionInfo> shared_info =
|
|
SearchSharedFunctionInfo(current_info()->shared_info()->code(), expr);
|
|
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<HFunctionLiteral>(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<GlobalObject> 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 = Add<HOuterContext>(context);
|
|
}
|
|
return context;
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitVariableProxy(VariableProxy* expr) {
|
|
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<Object> constant_value =
|
|
isolate()->factory()->GlobalConstantFor(variable->name());
|
|
if (!constant_value.is_null()) {
|
|
HConstant* instr = New<HConstant>(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<GlobalObject> global(current_info()->global_object());
|
|
Handle<PropertyCell> cell(global->GetPropertyCell(&lookup));
|
|
if (cell->type()->IsConstant()) {
|
|
cell->AddDependentCompilationInfo(top_info());
|
|
Handle<Object> constant_object = cell->type()->AsConstant();
|
|
if (constant_object->IsConsString()) {
|
|
constant_object =
|
|
FlattenGetString(Handle<String>::cast(constant_object));
|
|
}
|
|
HConstant* constant = New<HConstant>(constant_object);
|
|
return ast_context()->ReturnInstruction(constant, expr->id());
|
|
} else {
|
|
HLoadGlobalCell* instr =
|
|
New<HLoadGlobalCell>(cell, lookup.GetPropertyDetails());
|
|
return ast_context()->ReturnInstruction(instr, expr->id());
|
|
}
|
|
} else {
|
|
HGlobalObject* global_object = Add<HGlobalObject>();
|
|
HLoadGlobalGeneric* instr =
|
|
New<HLoadGlobalGeneric>(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<HConstant>(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<JSFunction> closure = function_state()->compilation_info()->closure();
|
|
Handle<FixedArray> literals(closure->literals());
|
|
HRegExpLiteral* instr = New<HRegExpLiteral>(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> map,
|
|
Handle<String> name,
|
|
LookupResult* lookup) {
|
|
while (map->prototype()->IsJSObject()) {
|
|
Handle<JSObject> holder(JSObject::cast(map->prototype()));
|
|
map = Handle<Map>(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> map,
|
|
Handle<String> name,
|
|
Handle<AccessorPair>* accessors,
|
|
Handle<JSObject>* 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<Object> callback(lookup.GetValueFromMap(*map), isolate);
|
|
if (!callback->IsAccessorPair()) return false;
|
|
*accessors = Handle<AccessorPair>::cast(callback);
|
|
*holder = Handle<JSObject>();
|
|
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<Object> callback(lookup.GetValue(), isolate);
|
|
if (!callback->IsAccessorPair()) return false;
|
|
*accessors = Handle<AccessorPair>::cast(callback);
|
|
*holder = Handle<JSObject>(lookup.holder());
|
|
return true;
|
|
}
|
|
|
|
// We haven't found a JavaScript accessor anywhere.
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool LookupSetter(Handle<Map> map,
|
|
Handle<String> name,
|
|
Handle<JSFunction>* setter,
|
|
Handle<JSObject>* holder) {
|
|
Handle<AccessorPair> accessors;
|
|
if (LookupAccessorPair(map, name, &accessors, holder) &&
|
|
accessors->setter()->IsJSFunction()) {
|
|
Handle<JSFunction> 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<JSObject> boilerplate,
|
|
int max_depth,
|
|
int* max_properties) {
|
|
if (boilerplate->map()->is_deprecated()) {
|
|
Handle<Object> 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<FixedArrayBase> elements(boilerplate->elements());
|
|
if (elements->length() > 0 &&
|
|
elements->map() != isolate->heap()->fixed_cow_array_map()) {
|
|
if (boilerplate->HasFastObjectElements()) {
|
|
Handle<FixedArray> fast_elements = Handle<FixedArray>::cast(elements);
|
|
int length = elements->length();
|
|
for (int i = 0; i < length; i++) {
|
|
if ((*max_properties)-- == 0) return false;
|
|
Handle<Object> value(fast_elements->get(i), isolate);
|
|
if (value->IsJSObject()) {
|
|
Handle<JSObject> value_object = Handle<JSObject>::cast(value);
|
|
if (!IsFastLiteral(value_object,
|
|
max_depth - 1,
|
|
max_properties)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else if (!boilerplate->HasFastDoubleElements()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Handle<FixedArray> properties(boilerplate->properties());
|
|
if (properties->length() > 0) {
|
|
return false;
|
|
} else {
|
|
Handle<DescriptorArray> 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<Object> value(boilerplate->InObjectPropertyAt(index), isolate);
|
|
if (value->IsJSObject()) {
|
|
Handle<JSObject> value_object = Handle<JSObject>::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<JSFunction> closure = function_state()->compilation_info()->closure();
|
|
HInstruction* literal;
|
|
|
|
// Check whether to use fast or slow deep-copying for boilerplate.
|
|
int max_properties = kMaxFastLiteralProperties;
|
|
Handle<Object> literals_cell(closure->literals()->get(expr->literal_index()),
|
|
isolate());
|
|
Handle<AllocationSite> site;
|
|
Handle<JSObject> boilerplate;
|
|
if (!literals_cell->IsUndefined()) {
|
|
// Retrieve the boilerplate
|
|
site = Handle<AllocationSite>::cast(literals_cell);
|
|
boilerplate = Handle<JSObject>(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<FixedArray> closure_literals(closure->literals(), isolate());
|
|
Handle<FixedArray> 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<HPushArgument>(Add<HConstant>(closure_literals));
|
|
Add<HPushArgument>(Add<HConstant>(literal_index));
|
|
Add<HPushArgument>(Add<HConstant>(constant_properties));
|
|
Add<HPushArgument>(Add<HConstant>(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<HCallRuntime>(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> map = property->GetReceiverType();
|
|
Handle<String> 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<JSFunction> setter;
|
|
Handle<JSObject> holder;
|
|
ASSERT(!LookupSetter(map, name, &setter, &holder));
|
|
#endif
|
|
CHECK_ALIVE(store = BuildStoreNamedMonomorphic(literal,
|
|
name,
|
|
value,
|
|
map));
|
|
}
|
|
AddInstruction(store);
|
|
if (store->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<HToFastProperties>(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<Expression*>* subexprs = expr->values();
|
|
int length = subexprs->length();
|
|
HInstruction* literal;
|
|
|
|
Handle<AllocationSite> site;
|
|
Handle<FixedArray> literals(environment()->closure()->literals(), isolate());
|
|
bool uninitialized = false;
|
|
Handle<Object> literals_cell(literals->get(expr->literal_index()),
|
|
isolate());
|
|
Handle<JSObject> boilerplate_object;
|
|
if (literals_cell->IsUndefined()) {
|
|
uninitialized = true;
|
|
Handle<Object> raw_boilerplate = Runtime::CreateArrayLiteralBoilerplate(
|
|
isolate(), literals, expr->constant_elements());
|
|
if (raw_boilerplate.is_null()) {
|
|
return Bailout(kArrayBoilerplateCreationFailed);
|
|
}
|
|
|
|
boilerplate_object = Handle<JSObject>::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<AllocationSite>::cast(literals_cell);
|
|
boilerplate_object = Handle<JSObject>(
|
|
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<FixedArray> 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<HPushArgument>(Add<HConstant>(literals));
|
|
Add<HPushArgument>(Add<HConstant>(literal_index));
|
|
Add<HPushArgument>(Add<HConstant>(constants));
|
|
Add<HPushArgument>(Add<HConstant>(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<HCallRuntime>(isolate()->factory()->empty_string(),
|
|
Runtime::FunctionForId(function_id),
|
|
4);
|
|
|
|
// De-opt if elements kind changed from boilerplate_elements_kind.
|
|
Handle<Map> map = Handle<Map>(boilerplate_object->map(), isolate());
|
|
literal = Add<HCheckMaps>(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<HConstant>(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<HConstant>(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<HStoreKeyed>(elements, key, value,
|
|
boilerplate_elements_kind);
|
|
instr->SetUninitialized(uninitialized);
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
Add<HSimulate>(expr->GetIdForElement(i));
|
|
}
|
|
|
|
Drop(1); // array literal index
|
|
return ast_context()->ReturnValue(Pop());
|
|
}
|
|
|
|
|
|
HCheckMaps* HOptimizedGraphBuilder::AddCheckMap(HValue* object,
|
|
Handle<Map> map) {
|
|
BuildCheckHeapObject(object);
|
|
return Add<HCheckMaps>(object, map, top_info());
|
|
}
|
|
|
|
|
|
HInstruction* HOptimizedGraphBuilder::BuildStoreNamedField(
|
|
HValue* checked_object,
|
|
Handle<String> name,
|
|
HValue* value,
|
|
Handle<Map> 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>(JSObject::cast(map->prototype())),
|
|
Handle<JSObject>(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<HConstant>(HeapNumber::kSize);
|
|
HInstruction* heap_number = Add<HAllocate>(heap_number_size,
|
|
HType::HeapNumber(), isolate()->heap()->GetPretenureMode(),
|
|
HEAP_NUMBER_TYPE);
|
|
AddStoreMapConstant(heap_number, isolate()->factory()->heap_number_map());
|
|
Add<HStoreNamedField>(heap_number, HObjectAccess::ForHeapNumberValue(),
|
|
value);
|
|
instr = New<HStoreNamedField>(checked_object->ActualValue(),
|
|
heap_number_access,
|
|
heap_number);
|
|
} else {
|
|
// Already holds a HeapNumber; load the box and write its value field.
|
|
HInstruction* heap_number = Add<HLoadNamedField>(checked_object,
|
|
heap_number_access);
|
|
heap_number->set_type(HType::HeapNumber());
|
|
instr = New<HStoreNamedField>(heap_number,
|
|
HObjectAccess::ForHeapNumberValue(),
|
|
value);
|
|
}
|
|
} else {
|
|
// This is a normal store.
|
|
instr = New<HStoreNamedField>(checked_object->ActualValue(),
|
|
field_access,
|
|
value);
|
|
}
|
|
|
|
if (transition_to_field) {
|
|
Handle<Map> transition(lookup->GetTransitionMapFromMap(*map));
|
|
HConstant* transition_constant = Add<HConstant>(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<String> name,
|
|
HValue* value) {
|
|
return New<HStoreNamedGeneric>(
|
|
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<Map> type,
|
|
Handle<String> 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<String> name,
|
|
HValue* value,
|
|
Handle<Map> 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> map) {
|
|
if (lookup_.IsField()) {
|
|
access_ = HObjectAccess::ForField(map, &lookup_, name_);
|
|
} else if (lookup_.IsPropertyCallbacks()) {
|
|
Handle<Object> callback(lookup_.GetValueFromMap(*map), isolate());
|
|
if (!callback->IsAccessorPair()) return false;
|
|
Object* getter = Handle<AccessorPair>::cast(callback)->getter();
|
|
if (!getter->IsJSFunction()) return false;
|
|
Handle<JSFunction> 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 = map_;
|
|
while (map->prototype()->IsJSObject()) {
|
|
holder_ = handle(JSObject::cast(map->prototype()));
|
|
if (holder_->map()->is_deprecated()) {
|
|
JSObject::TryMigrateInstance(holder_);
|
|
}
|
|
map = Handle<Map>(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<Map> 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<HLoadNamedField>(checked_object, access);
|
|
}
|
|
|
|
HValue* checked_holder = checked_object;
|
|
if (info->has_holder()) {
|
|
Handle<JSObject> 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<HPushArgument>(Pop());
|
|
return New<HCallConstantFunction>(info->accessor(), 1);
|
|
}
|
|
|
|
ASSERT(info->lookup()->IsConstant());
|
|
return New<HConstant>(info->constant());
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::HandlePolymorphicLoadNamedField(
|
|
BailoutId ast_id,
|
|
BailoutId return_id,
|
|
HValue* object,
|
|
SmallMapList* types,
|
|
Handle<String> 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<HCompareMap>(
|
|
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<HLoadNamedGeneric>(object, name);
|
|
if (!ast_context()->IsEffect()) Push(load);
|
|
|
|
if (join != NULL) {
|
|
Goto(join);
|
|
} else {
|
|
Add<HSimulate>(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<String> 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> 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<HCheckMaps>(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<HSimulate>(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<String> 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> 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<HCompareMap>(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<HSimulate>(assignment_id, REMOVABLE_SIMULATE);
|
|
} else {
|
|
Push(value);
|
|
Add<HSimulate>(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<HSimulate>(return_id, REMOVABLE_SIMULATE);
|
|
return ast_context()->ReturnValue(Pop());
|
|
}
|
|
|
|
// Named store.
|
|
HValue* object = environment()->ExpressionStackAt(1);
|
|
|
|
if (is_uninitialized) {
|
|
Add<HDeoptimize>("Insufficient type feedback for property assignment",
|
|
Deoptimizer::SOFT);
|
|
}
|
|
|
|
Literal* key = prop->key()->AsLiteral();
|
|
Handle<String> name = Handle<String>::cast(key->value());
|
|
ASSERT(!name.is_null());
|
|
|
|
HInstruction* instr = NULL;
|
|
|
|
SmallMapList* types;
|
|
bool monomorphic = ComputeReceiverTypes(expr, object, &types);
|
|
|
|
if (monomorphic) {
|
|
Handle<Map> map = types->first();
|
|
Handle<JSFunction> setter;
|
|
Handle<JSObject> 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<HPushArgument>(object);
|
|
Add<HPushArgument>(value);
|
|
instr = New<HCallConstantFunction>(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<HSimulate>(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<GlobalObject> global(current_info()->global_object());
|
|
Handle<PropertyCell> cell(global->GetPropertyCell(&lookup));
|
|
if (cell->type()->IsConstant()) {
|
|
IfBuilder builder(this);
|
|
HValue* constant = Add<HConstant>(cell->type()->AsConstant());
|
|
if (cell->type()->AsConstant()->IsNumber()) {
|
|
builder.If<HCompareNumericAndBranch>(value, constant, Token::EQ);
|
|
} else {
|
|
builder.If<HCompareObjectEqAndBranch>(value, constant);
|
|
}
|
|
builder.Then();
|
|
builder.Else();
|
|
Add<HDeoptimize>("Constant global variable assignment",
|
|
Deoptimizer::EAGER);
|
|
builder.End();
|
|
}
|
|
HInstruction* instr =
|
|
Add<HStoreGlobalCell>(value, cell, lookup.GetPropertyDetails());
|
|
if (instr->HasObservableSideEffects()) {
|
|
Add<HSimulate>(ast_id, REMOVABLE_SIMULATE);
|
|
}
|
|
} else {
|
|
HGlobalObject* global_object = Add<HGlobalObject>();
|
|
HStoreGlobalGeneric* instr =
|
|
Add<HStoreGlobalGeneric>(global_object, var->name(),
|
|
value, function_strict_mode_flag());
|
|
USE(instr);
|
|
ASSERT(instr->HasObservableSideEffects());
|
|
Add<HSimulate>(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<HStoreContextSlot>(
|
|
context, var->index(), mode, Top());
|
|
if (instr->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<HUseConst>(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<HStoreContextSlot>(
|
|
context, var->index(), mode, Top());
|
|
if (instr->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<HThrow>(value);
|
|
Add<HSimulate>(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<HAbnormalExit>());
|
|
}
|
|
}
|
|
|
|
|
|
HLoadNamedField* HGraphBuilder::BuildLoadNamedField(HValue* object,
|
|
HObjectAccess access) {
|
|
if (FLAG_track_double_fields && access.representation().IsDouble()) {
|
|
// load the heap number
|
|
HLoadNamedField* heap_number = Add<HLoadNamedField>(
|
|
object, access.WithRepresentation(Representation::Tagged()));
|
|
heap_number->set_type(HType::HeapNumber());
|
|
// load the double value from it
|
|
return New<HLoadNamedField>(
|
|
heap_number, HObjectAccess::ForHeapNumberValue());
|
|
}
|
|
return New<HLoadNamedField>(object, access);
|
|
}
|
|
|
|
|
|
HInstruction* HGraphBuilder::AddLoadNamedField(HValue* object,
|
|
HObjectAccess access) {
|
|
return AddInstruction(BuildLoadNamedField(object, access));
|
|
}
|
|
|
|
|
|
HInstruction* HGraphBuilder::BuildLoadStringLength(HValue* object,
|
|
HValue* checked_string) {
|
|
if (FLAG_fold_constants && object->IsConstant()) {
|
|
HConstant* constant = HConstant::cast(object);
|
|
if (constant->HasStringValue()) {
|
|
return New<HConstant>(constant->StringValue()->length());
|
|
}
|
|
}
|
|
return BuildLoadNamedField(checked_string, HObjectAccess::ForStringLength());
|
|
}
|
|
|
|
|
|
HInstruction* HOptimizedGraphBuilder::BuildLoadNamedGeneric(
|
|
HValue* object,
|
|
Handle<String> name,
|
|
Property* expr) {
|
|
if (expr->IsUninitialized()) {
|
|
Add<HDeoptimize>("Insufficient type feedback for generic named load",
|
|
Deoptimizer::SOFT);
|
|
}
|
|
return New<HLoadNamedGeneric>(object, name);
|
|
}
|
|
|
|
|
|
|
|
HInstruction* HOptimizedGraphBuilder::BuildLoadKeyedGeneric(HValue* object,
|
|
HValue* key) {
|
|
return New<HLoadKeyedGeneric>(object, key);
|
|
}
|
|
|
|
|
|
LoadKeyedHoleMode HOptimizedGraphBuilder::BuildKeyedHoleMode(Handle<Map> 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<JSObject> prototype(JSObject::cast(map->prototype()), isolate());
|
|
Handle<JSObject> 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> map,
|
|
bool is_store,
|
|
KeyedAccessStoreMode store_mode) {
|
|
HCheckMaps* checked_object = Add<HCheckMaps>(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<JSObject> 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>(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<Map> most_general_consolidated_map;
|
|
for (int i = 0; i < maps->length(); ++i) {
|
|
Handle<Map> 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<HCheckMaps>(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> 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> map = maps->at(i);
|
|
Handle<Map> 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> 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<HTransitionElementsKind>(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<Map> 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> 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<HCompareMap>(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<Map> current_map = types->at(i);
|
|
if (current_map->DictionaryElementsInPrototypeChainOnly()) {
|
|
force_generic = true;
|
|
monomorphic = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (monomorphic) {
|
|
Handle<Map> 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<HDeoptimize>("Insufficient type feedback for keyed store",
|
|
Deoptimizer::SOFT);
|
|
}
|
|
instr = BuildStoreKeyedGeneric(obj, key, val);
|
|
} else {
|
|
if (expr->AsProperty()->HasNoTypeInformation()) {
|
|
Add<HDeoptimize>("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<HStoreKeyedGeneric>(
|
|
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<HValue*>* 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<HPushArgument>(argument);
|
|
push_argument->InsertAfter(insert_after);
|
|
insert_after = push_argument;
|
|
}
|
|
|
|
HArgumentsElements* arguments_elements = New<HArgumentsElements>(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<String> name = expr->key()->AsLiteral()->AsPropertyName();
|
|
if (!name->IsOneByteEqualTo(STATIC_ASCII_VECTOR("length"))) return false;
|
|
|
|
if (function_state()->outer() == NULL) {
|
|
HInstruction* elements = Add<HArgumentsElements>(false);
|
|
result = New<HArgumentsLength>(elements);
|
|
} else {
|
|
// Number of arguments without receiver.
|
|
int argument_count = environment()->
|
|
arguments_environment()->parameter_count() - 1;
|
|
result = New<HConstant>(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<HArgumentsElements>(false);
|
|
HInstruction* length = Add<HArgumentsLength>(elements);
|
|
HInstruction* checked_key = Add<HBoundsCheck>(key, length);
|
|
result = New<HAccessArgumentsAt>(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<HConstant>(argument_count);
|
|
HInstruction* checked_key = Add<HBoundsCheck>(key, length);
|
|
result = New<HAccessArgumentsAt>(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<HStringCharFromCode>(char_code);
|
|
|
|
} else if (expr->IsFunctionPrototype()) {
|
|
HValue* function = Pop();
|
|
BuildCheckHeapObject(function);
|
|
instr = New<HLoadFunctionPrototype>(function);
|
|
|
|
} else if (expr->key()->IsPropertyName()) {
|
|
Handle<String> 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<HCheckInstanceType>(object, HCheckInstanceType::IS_STRING);
|
|
} else {
|
|
checked_object = Add<HCheckMaps>(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<HSimulate>(ast_id, REMOVABLE_SIMULATE);
|
|
} else {
|
|
Push(load);
|
|
Add<HSimulate>(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<JSObject> constant,
|
|
CompilationInfo* info) {
|
|
HConstant* constant_value = New<HConstant>(constant);
|
|
|
|
if (constant->map()->CanOmitMapChecks()) {
|
|
constant->map()->AddDependentCompilationInfo(
|
|
DependentCode::kPrototypeCheckGroup, info);
|
|
return constant_value;
|
|
}
|
|
|
|
AddInstruction(constant_value);
|
|
HCheckMaps* check =
|
|
Add<HCheckMaps>(constant_value, handle(constant->map()), info);
|
|
check->ClearGVNFlag(kDependsOnElementsKind);
|
|
return check;
|
|
}
|
|
|
|
|
|
HInstruction* HGraphBuilder::BuildCheckPrototypeMaps(Handle<JSObject> prototype,
|
|
Handle<JSObject> 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<JSObject> holder,
|
|
Handle<Map> receiver_map) {
|
|
if (!holder.is_null()) {
|
|
Handle<JSObject> prototype(JSObject::cast(receiver_map->prototype()));
|
|
BuildCheckPrototypeMaps(prototype, holder);
|
|
}
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::AddCheckConstantFunction(
|
|
Handle<JSObject> holder,
|
|
HValue* receiver,
|
|
Handle<Map> 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);
|
|
}
|
|
|
|
|
|
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<String> 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<HCheckMaps>(receiver, types);
|
|
AddCheckPrototypeMaps(expr->holder(), info.map());
|
|
if (FLAG_trace_inlining) {
|
|
Handle<JSFunction> caller = current_info()->closure();
|
|
SmartArrayPointer<char> 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.
|
|
HCallConstantFunction* call =
|
|
New<HCallConstantFunction>(expr->target(), argument_count);
|
|
PreProcessCall(call);
|
|
AddInstruction(call);
|
|
if (!ast_context()->IsEffect()) Push(call);
|
|
Add<HSimulate>(expr->id(), REMOVABLE_SIMULATE);
|
|
if (!ast_context()->IsEffect()) ast_context()->ReturnValue(Pop());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::HandlePolymorphicCallNamed(
|
|
Call* expr,
|
|
HValue* receiver,
|
|
SmallMapList* types,
|
|
Handle<String> 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<Map> initial_string_map(
|
|
isolate()->native_context()->string_function()->initial_map());
|
|
Handle<Map> string_marker_map(
|
|
JSObject::cast(initial_string_map->prototype())->map());
|
|
Handle<Map> initial_number_map(
|
|
isolate()->native_context()->number_function()->initial_map());
|
|
Handle<Map> number_marker_map(
|
|
JSObject::cast(initial_number_map->prototype())->map());
|
|
Handle<Map> 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> 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> 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<HIsSmiAndBranch>(
|
|
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<HCompareMap>(receiver, heap_number_map, if_true, if_false);
|
|
map = initial_number_map;
|
|
expr->set_number_check(
|
|
Handle<JSObject>(JSObject::cast(map->prototype())));
|
|
} else if (map.is_identical_to(string_marker_map)) {
|
|
compare = New<HIsStringAndBranch>(receiver, if_true, if_false);
|
|
map = initial_string_map;
|
|
expr->set_string_check(
|
|
Handle<JSObject>(JSObject::cast(map->prototype())));
|
|
} else {
|
|
compare = New<HCompareMap>(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<JSFunction> caller = current_info()->closure();
|
|
SmartArrayPointer<char> 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 {
|
|
HCallConstantFunction* call =
|
|
New<HCallConstantFunction>(expr->target(), argument_count);
|
|
PreProcessCall(call);
|
|
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 {
|
|
HCallNamed* call = New<HCallNamed>(name, argument_count);
|
|
PreProcessCall(call);
|
|
|
|
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<JSFunction> target,
|
|
Handle<JSFunction> caller,
|
|
const char* reason) {
|
|
if (FLAG_trace_inlining) {
|
|
SmartArrayPointer<char> target_name =
|
|
target->shared()->DebugName()->ToCString();
|
|
SmartArrayPointer<char> 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<JSFunction> target) {
|
|
if (!FLAG_use_inlining) return kNotInlinable;
|
|
|
|
// Precondition: call is monomorphic and we have found a target with the
|
|
// appropriate arity.
|
|
Handle<JSFunction> caller = current_info()->closure();
|
|
Handle<SharedFunctionInfo> 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(CallKind call_kind,
|
|
Handle<JSFunction> 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<JSFunction> 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<SharedFunctionInfo> 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<Declaration*>* 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<ScopeInfo> 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();
|
|
bool undefined_receiver = HEnvironment::UseUndefinedReceiver(
|
|
target, function, call_kind, inlining_kind);
|
|
HEnvironment* inner_env =
|
|
environment()->CopyForInlining(target,
|
|
arguments_count,
|
|
function,
|
|
undefined,
|
|
function_state()->inlining_kind(),
|
|
undefined_receiver);
|
|
|
|
HConstant* context = Add<HConstant>(Handle<Context>(target->context()));
|
|
inner_env->BindContext(context);
|
|
|
|
Add<HSimulate>(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<HArgumentsObject>(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<HEnterInlined>(target, arguments_count, function,
|
|
function_state()->inlining_kind(),
|
|
function->scope()->arguments(),
|
|
arguments_object, undefined_receiver);
|
|
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<Code> unoptimized_code(target_shared->code());
|
|
ASSERT(unoptimized_code->kind() == Code::FUNCTION);
|
|
Handle<TypeFeedbackInfo> 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) {
|
|
// The function call we are inlining is a method call if the call
|
|
// is a property call.
|
|
CallKind call_kind = (expr->expression()->AsProperty() == NULL)
|
|
? CALL_AS_FUNCTION
|
|
: CALL_AS_METHOD;
|
|
|
|
return TryInline(call_kind,
|
|
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(CALL_AS_FUNCTION,
|
|
expr->target(),
|
|
expr->arguments()->length(),
|
|
implicit_return_value,
|
|
expr->id(),
|
|
expr->ReturnId(),
|
|
CONSTRUCT_CALL_RETURN);
|
|
}
|
|
|
|
|
|
bool HOptimizedGraphBuilder::TryInlineGetter(Handle<JSFunction> getter,
|
|
BailoutId ast_id,
|
|
BailoutId return_id) {
|
|
return TryInline(CALL_AS_METHOD,
|
|
getter,
|
|
0,
|
|
NULL,
|
|
ast_id,
|
|
return_id,
|
|
GETTER_CALL_RETURN);
|
|
}
|
|
|
|
|
|
bool HOptimizedGraphBuilder::TryInlineSetter(Handle<JSFunction> setter,
|
|
BailoutId id,
|
|
BailoutId assignment_id,
|
|
HValue* implicit_return_value) {
|
|
return TryInline(CALL_AS_METHOD,
|
|
setter,
|
|
1,
|
|
implicit_return_value,
|
|
id, assignment_id,
|
|
SETTER_CALL_RETURN);
|
|
}
|
|
|
|
|
|
bool HOptimizedGraphBuilder::TryInlineApply(Handle<JSFunction> function,
|
|
Call* expr,
|
|
int arguments_count) {
|
|
return TryInline(CALL_AS_METHOD,
|
|
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<HUnaryMathOperation>(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<Map> 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<HStringCharFromCode>(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<HStringCharFromCode>(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<HUnaryMathOperation>(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<HUnaryMathOperation>(left, kMathPowHalf);
|
|
} else if (exponent == -0.5) {
|
|
HValue* one = graph()->GetConstant1();
|
|
HInstruction* sqrt = AddUncasted<HUnaryMathOperation>(
|
|
left, kMathPowHalf);
|
|
// MathPowHalf doesn't have side effects so there's no need for
|
|
// an environment simulation here.
|
|
ASSERT(!sqrt->HasObservableSideEffects());
|
|
result = NewUncasted<HDiv>(one, sqrt);
|
|
} else if (exponent == 2.0) {
|
|
result = NewUncasted<HMul>(left, left);
|
|
}
|
|
}
|
|
|
|
if (result == NULL) {
|
|
result = NewUncasted<HPower>(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<HMathMinMax>(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;
|
|
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<Map> 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<Expression*>* 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);
|
|
Drop(1);
|
|
|
|
CHECK_ALIVE_OR_RETURN(VisitForValue(args->at(0)), true);
|
|
HValue* receiver = Pop();
|
|
|
|
if (function_state()->outer() == NULL) {
|
|
HInstruction* elements = Add<HArgumentsElements>(false);
|
|
HInstruction* length = Add<HArgumentsLength>(elements);
|
|
HValue* wrapped_receiver = BuildWrapReceiver(receiver, function);
|
|
HInstruction* result = New<HApplyArguments>(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<HValue*>* 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<JSFunction> known_function;
|
|
if (function->IsConstant() &&
|
|
HConstant::cast(function)->handle(isolate())->IsJSFunction()) {
|
|
known_function = Handle<JSFunction>::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<HPushArgument>(Pop()));
|
|
for (int i = 1; i < arguments_count; i++) {
|
|
Push(Add<HPushArgument>(arguments_values->at(i)));
|
|
}
|
|
|
|
HInvokeFunction* call = New<HInvokeFunction>(function,
|
|
known_function,
|
|
arguments_count);
|
|
Drop(arguments_count);
|
|
ast_context()->ReturnInstruction(call, expr->id());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
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<HPushArgument>(receiver));
|
|
CHECK_ALIVE(VisitArgumentList(expr->arguments()));
|
|
|
|
if (expr->IsMonomorphic()) {
|
|
BuildCheckHeapObject(receiver);
|
|
ElementsKind kind = expr->KeyedArrayCallIsHoley()
|
|
? FAST_HOLEY_ELEMENTS : FAST_ELEMENTS;
|
|
|
|
Handle<Map> map(isolate()->get_initial_js_array_map(kind));
|
|
|
|
HValue* function = BuildMonomorphicElementAccess(
|
|
receiver, key, NULL, NULL, map, false, STANDARD_STORE);
|
|
|
|
call = New<HCallFunction>(function, argument_count);
|
|
} else {
|
|
call = New<HCallKeyed>(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<String> 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> 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 = PreProcessCall(New<HCallNamed>(name, argument_count));
|
|
} else {
|
|
AddCheckConstantFunction(expr->holder(), receiver, map);
|
|
|
|
if (TryInlineCall(expr)) return;
|
|
call = PreProcessCall(
|
|
New<HCallConstantFunction>(expr->target(), argument_count));
|
|
}
|
|
} else if (types != NULL && types->length() > 1) {
|
|
ASSERT(expr->check_type() == RECEIVER_MAP_CHECK);
|
|
HandlePolymorphicCallNamed(expr, receiver, types, name);
|
|
return;
|
|
|
|
} else {
|
|
call = PreProcessCall(New<HCallNamed>(name, 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<GlobalObject> 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.
|
|
HGlobalObject* global_object = Add<HGlobalObject>();
|
|
Push(global_object);
|
|
CHECK_ALIVE(VisitExpressions(expr->arguments()));
|
|
|
|
CHECK_ALIVE(VisitForValue(expr->expression()));
|
|
HValue* function = Pop();
|
|
Add<HCheckValue>(function, expr->target());
|
|
|
|
// Replace the global object with the global receiver.
|
|
HGlobalReceiver* global_receiver = Add<HGlobalReceiver>(global_object);
|
|
// Index of the receiver from the top of the expression stack.
|
|
const int receiver_index = argument_count - 1;
|
|
ASSERT(environment()->ExpressionStackAt(receiver_index)->
|
|
IsGlobalObject());
|
|
environment()->SetExpressionStackAt(receiver_index, global_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())) {
|
|
// When the target has a custom call IC generator, use the IC,
|
|
// because it is likely to generate better code.
|
|
call = PreProcessCall(New<HCallNamed>(var->name(), argument_count));
|
|
} else {
|
|
call = PreProcessCall(New<HCallKnownGlobal>(
|
|
expr->target(), argument_count));
|
|
}
|
|
} else {
|
|
HGlobalObject* receiver = Add<HGlobalObject>();
|
|
Push(Add<HPushArgument>(receiver));
|
|
CHECK_ALIVE(VisitArgumentList(expr->arguments()));
|
|
|
|
call = New<HCallGlobal>(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();
|
|
HGlobalObject* global = Add<HGlobalObject>();
|
|
HGlobalReceiver* receiver = Add<HGlobalReceiver>(global);
|
|
Push(receiver);
|
|
CHECK_ALIVE(VisitExpressions(expr->arguments()));
|
|
Add<HCheckValue>(function, expr->target());
|
|
|
|
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<HInvokeFunction>(function, expr->target(),
|
|
argument_count));
|
|
Drop(1); // The function.
|
|
}
|
|
|
|
} else {
|
|
CHECK_ALIVE(VisitForValue(expr->expression()));
|
|
HValue* function = Top();
|
|
HGlobalObject* global_object = Add<HGlobalObject>();
|
|
HGlobalReceiver* receiver = Add<HGlobalReceiver>(global_object);
|
|
Push(Add<HPushArgument>(receiver));
|
|
CHECK_ALIVE(VisitArgumentList(expr->arguments()));
|
|
|
|
call = New<HCallFunction>(function, argument_count);
|
|
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<Cell> cell = expr->allocation_info_cell();
|
|
AllocationSite* site = AllocationSite::cast(cell->value());
|
|
|
|
// Register on the site for deoptimization if the cell value changes.
|
|
site->AddDependentCompilationInfo(AllocationSite::TRANSITIONS, top_info());
|
|
HInstruction* cell_instruction = Add<HConstant>(cell);
|
|
|
|
// 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,
|
|
cell_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<HConstant>(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<HConstant>(i);
|
|
Add<HStoreKeyed>(elements, constant_i, value, kind);
|
|
}
|
|
}
|
|
|
|
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<JSFunction> 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<JSFunction> caller = current_info()->closure();
|
|
Handle<JSFunction> 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<Cell> cell = expr->allocation_info_cell();
|
|
AllocationSite* site = AllocationSite::cast(cell->value());
|
|
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<JSFunction> constructor = expr->target();
|
|
HValue* check = Add<HCheckValue>(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<Map> 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<HConstant>(instance_size);
|
|
PretenureFlag pretenure_flag =
|
|
(FLAG_pretenuring_call_new &&
|
|
isolate()->heap()->GetPretenureMode() == TENURED)
|
|
? TENURED : NOT_TENURED;
|
|
HAllocate* receiver =
|
|
Add<HAllocate>(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<HConstant>(constructor);
|
|
HValue* initial_map_value =
|
|
Add<HLoadNamedField>(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<HStoreNamedField>(receiver,
|
|
HObjectAccess::ForJSObjectOffset(JSObject::kMapOffset),
|
|
initial_map_value);
|
|
HValue* empty_fixed_array = Add<HConstant>(factory->empty_fixed_array());
|
|
Add<HStoreNamedField>(receiver,
|
|
HObjectAccess::ForJSObjectOffset(JSObject::kPropertiesOffset),
|
|
empty_fixed_array);
|
|
Add<HStoreNamedField>(receiver,
|
|
HObjectAccess::ForJSObjectOffset(JSObject::kElementsOffset),
|
|
empty_fixed_array);
|
|
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<HStoreNamedField>(receiver,
|
|
HObjectAccess::ForJSObjectOffset(property_offset),
|
|
undefined);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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<HCallNew>(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<JSFunction> array_function(
|
|
isolate()->global_context()->array_function(), isolate());
|
|
bool use_call_new_array = expr->target().is_identical_to(array_function);
|
|
Handle<Cell> cell = expr->allocation_info_cell();
|
|
if (use_call_new_array && IsCallNewArrayInlineable(expr)) {
|
|
// Verify we are still calling the array function for our native context.
|
|
Add<HCheckValue>(function, array_function);
|
|
BuildInlinedCallNewArray(expr);
|
|
return;
|
|
}
|
|
|
|
HBinaryCall* call;
|
|
if (use_call_new_array) {
|
|
Add<HCheckValue>(function, array_function);
|
|
call = New<HCallNewArray>(function, argument_count, cell,
|
|
expr->elements_kind());
|
|
} else {
|
|
call = New<HCallNew>(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 <class ViewClass>
|
|
void HGraphBuilder::BuildArrayBufferViewInitialization(
|
|
HValue* obj,
|
|
HValue* buffer,
|
|
HValue* byte_offset,
|
|
HValue* byte_length) {
|
|
|
|
for (int offset = ViewClass::kSize;
|
|
offset < ViewClass::kSizeWithInternalFields;
|
|
offset += kPointerSize) {
|
|
Add<HStoreNamedField>(obj,
|
|
HObjectAccess::ForJSObjectOffset(offset),
|
|
Add<HConstant>(static_cast<int32_t>(0)));
|
|
}
|
|
|
|
Add<HStoreNamedField>(
|
|
obj,
|
|
HObjectAccess::ForJSArrayBufferViewBuffer(), buffer);
|
|
Add<HStoreNamedField>(
|
|
obj,
|
|
HObjectAccess::ForJSArrayBufferViewByteOffset(),
|
|
byte_offset);
|
|
Add<HStoreNamedField>(
|
|
obj,
|
|
HObjectAccess::ForJSArrayBufferViewByteLength(),
|
|
byte_length);
|
|
|
|
HObjectAccess weak_first_view_access =
|
|
HObjectAccess::ForJSArrayBufferWeakFirstView();
|
|
Add<HStoreNamedField>(obj,
|
|
HObjectAccess::ForJSArrayBufferViewWeakNext(),
|
|
Add<HLoadNamedField>(buffer, weak_first_view_access));
|
|
Add<HStoreNamedField>(buffer, weak_first_view_access, obj);
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitDataViewInitialize(
|
|
CallRuntime* expr) {
|
|
ZoneList<Expression*>* 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<JSDataView>(
|
|
obj, buffer, byte_offset, byte_length);
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::VisitTypedArrayInitialize(
|
|
CallRuntime* expr) {
|
|
ZoneList<Expression*>* 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<Object> value =
|
|
static_cast<Literal*>(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<Literal*>(arguments->at(kByteOffsetArg))->value()) {
|
|
byte_offset = Add<HConstant>(static_cast<int32_t>(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<HIsSmiAndBranch>(byte_offset);
|
|
byte_offset_smi.Then();
|
|
}
|
|
|
|
{ // byte_offset is Smi.
|
|
BuildArrayBufferViewInitialization<JSTypedArray>(
|
|
obj, buffer, byte_offset, byte_length);
|
|
|
|
ExternalArrayType array_type = kExternalByteArray; // Bogus initialization.
|
|
size_t element_size = 1; // Bogus initialization.
|
|
Runtime::ArrayIdToTypeAndSize(array_id, &array_type, &element_size);
|
|
|
|
HInstruction* length = AddUncasted<HDiv>(byte_length,
|
|
Add<HConstant>(static_cast<int32_t>(element_size)));
|
|
|
|
Add<HStoreNamedField>(obj,
|
|
HObjectAccess::ForJSTypedArrayLength(),
|
|
length);
|
|
|
|
HValue* elements =
|
|
Add<HAllocate>(
|
|
Add<HConstant>(ExternalArray::kAlignedSize),
|
|
HType::JSArray(),
|
|
NOT_TENURED,
|
|
static_cast<InstanceType>(FIRST_EXTERNAL_ARRAY_TYPE + array_type));
|
|
|
|
Handle<Map> external_array_map(
|
|
isolate()->heap()->MapForExternalArrayType(array_type));
|
|
Add<HStoreNamedField>(elements,
|
|
HObjectAccess::ForMap(),
|
|
Add<HConstant>(external_array_map));
|
|
|
|
HValue* backing_store = Add<HLoadNamedField>(
|
|
buffer, HObjectAccess::ForJSArrayBufferBackingStore());
|
|
|
|
HValue* typed_array_start;
|
|
if (is_zero_byte_offset) {
|
|
typed_array_start = backing_store;
|
|
} else {
|
|
HInstruction* external_pointer =
|
|
AddUncasted<HAdd>(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<HStoreNamedField>(elements,
|
|
HObjectAccess::ForExternalArrayExternalPointer(),
|
|
typed_array_start);
|
|
Add<HStoreNamedField>(elements,
|
|
HObjectAccess::ForFixedArrayLength(),
|
|
length);
|
|
Add<HStoreNamedField>(
|
|
obj, HObjectAccess::ForElementsPointer(), elements);
|
|
}
|
|
|
|
if (!is_zero_byte_offset) {
|
|
byte_offset_smi.Else();
|
|
{ // byte_offset is not Smi.
|
|
Push(Add<HPushArgument>(obj));
|
|
VisitArgument(arguments->at(kArrayIdArg));
|
|
Push(Add<HPushArgument>(buffer));
|
|
Push(Add<HPushArgument>(byte_offset));
|
|
Push(Add<HPushArgument>(byte_length));
|
|
Add<HCallRuntime>(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<HConstant>(static_cast<int32_t>(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<int>(function->function_id) -
|
|
static_cast<int>(Runtime::kFirstInlineFunction);
|
|
ASSERT(lookup_index >= 0);
|
|
ASSERT(static_cast<size_t>(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<String> name = expr->name();
|
|
int argument_count = expr->arguments()->length();
|
|
HCallRuntime* call = New<HCallRuntime>(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<HPushArgument>(obj);
|
|
Add<HPushArgument>(key);
|
|
Add<HPushArgument>(Add<HConstant>(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<HInvokeFunction>(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<HTypeof>(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.
|
|
Handle<Type> info = expr->type();
|
|
Representation rep = Representation::FromType(info);
|
|
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<HForceRepresentation>(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<HAdd>(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<HStoreContextSlot>(context, var->index(),
|
|
mode, after);
|
|
if (instr->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<String> s = c_string->StringValue();
|
|
if (i < 0 || i >= s->length()) {
|
|
return New<HConstant>(OS::nan_value());
|
|
}
|
|
return New<HConstant>(s->Get(i));
|
|
}
|
|
}
|
|
BuildCheckHeapObject(string);
|
|
HValue* checkstring =
|
|
Add<HCheckInstanceType>(string, HCheckInstanceType::IS_STRING);
|
|
HInstruction* length = BuildLoadStringLength(string, checkstring);
|
|
AddInstruction(length);
|
|
HInstruction* checked_index = Add<HBoundsCheck>(index, length);
|
|
return New<HStringCharCodeAt>(string, checked_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,
|
|
Handle<Type> expected) {
|
|
if (expected->Is(Type::Smi())) {
|
|
return AddUncasted<HForceRepresentation>(number, Representation::Smi());
|
|
}
|
|
if (expected->Is(Type::Signed32())) {
|
|
return AddUncasted<HForceRepresentation>(number,
|
|
Representation::Integer32());
|
|
}
|
|
return number;
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::TruncateToNumber(HValue* value, Handle<Type>* expected) {
|
|
if (value->IsConstant()) {
|
|
HConstant* constant = HConstant::cast(value);
|
|
Maybe<HConstant*> number = constant->CopyToTruncatedNumber(zone());
|
|
if (number.has_value) {
|
|
*expected = handle(Type::Number(), isolate());
|
|
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);
|
|
|
|
Handle<Type> expected_type = *expected;
|
|
|
|
// Separate the number type from the rest.
|
|
Handle<Type> expected_obj = handle(Type::Intersect(
|
|
expected_type, handle(Type::NonNumber(), isolate())), isolate());
|
|
Handle<Type> expected_number = handle(Type::Intersect(
|
|
expected_type, handle(Type::Number(), isolate())), isolate());
|
|
|
|
// 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()));
|
|
return value;
|
|
}
|
|
|
|
if (expected_obj->Is(Type::Undefined())) {
|
|
// This is already done by HChange.
|
|
*expected = handle(Type::Union(
|
|
expected_number, handle(Type::Double(), isolate())), isolate());
|
|
return value;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
HValue* HOptimizedGraphBuilder::BuildBinaryOperation(
|
|
BinaryOperation* expr,
|
|
HValue* left,
|
|
HValue* right) {
|
|
Handle<Type> left_type = expr->left()->bounds().lower;
|
|
Handle<Type> right_type = expr->right()->bounds().lower;
|
|
Handle<Type> result_type = expr->bounds().lower;
|
|
Maybe<int> fixed_right_arg = expr->fixed_right_arg();
|
|
|
|
HValue* result = HGraphBuilder::BuildBinaryOperation(
|
|
expr->op(), left, right, left_type, right_type,
|
|
result_type, fixed_right_arg);
|
|
// 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<HSimulate>(expr->id(), REMOVABLE_SIMULATE);
|
|
Drop(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
HValue* HGraphBuilder::BuildBinaryOperation(
|
|
Token::Value op,
|
|
HValue* left,
|
|
HValue* right,
|
|
Handle<Type> left_type,
|
|
Handle<Type> right_type,
|
|
Handle<Type> result_type,
|
|
Maybe<int> fixed_right_arg) {
|
|
|
|
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<HDeoptimize>("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 = handle(Type::Any(), isolate());
|
|
} else {
|
|
if (!maybe_string_add) left = TruncateToNumber(left, &left_type);
|
|
left_rep = Representation::FromType(left_type);
|
|
}
|
|
|
|
if (right_type->Is(Type::None())) {
|
|
Add<HDeoptimize>("Insufficient type feedback for RHS of binary operation",
|
|
Deoptimizer::SOFT);
|
|
right_type = handle(Type::Any(), isolate());
|
|
} 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<HPushArgument>(left);
|
|
Add<HPushArgument>(right);
|
|
return AddUncasted<HInvokeFunction>(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<HPushArgument>(left);
|
|
Add<HPushArgument>(right);
|
|
return AddUncasted<HInvokeFunction>(function, 2);
|
|
}
|
|
|
|
return AddUncasted<HStringAdd>(left, right, STRING_ADD_CHECK_NONE);
|
|
}
|
|
|
|
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<HPushArgument>(left);
|
|
Add<HPushArgument>(right);
|
|
instr = AddUncasted<HInvokeFunction>(function, 2);
|
|
} else {
|
|
switch (op) {
|
|
case Token::ADD:
|
|
instr = AddUncasted<HAdd>(left, right);
|
|
break;
|
|
case Token::SUB:
|
|
instr = AddUncasted<HSub>(left, right);
|
|
break;
|
|
case Token::MUL:
|
|
instr = AddUncasted<HMul>(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<HConstant>(
|
|
static_cast<int>(fixed_right_arg.value));
|
|
IfBuilder if_same(this);
|
|
if_same.If<HCompareNumericAndBranch>(right, fixed_right, Token::EQ);
|
|
if_same.Then();
|
|
if_same.ElseDeopt("Unexpected RHS of binary operation");
|
|
right = fixed_right;
|
|
}
|
|
}
|
|
instr = AddUncasted<HMod>(left, right);
|
|
break;
|
|
}
|
|
case Token::DIV:
|
|
instr = AddUncasted<HDiv>(left, right);
|
|
break;
|
|
case Token::BIT_XOR:
|
|
case Token::BIT_AND:
|
|
instr = AddUncasted<HBitwise>(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<HRor>(operand, shift_amount);
|
|
} else {
|
|
instr = AddUncasted<HBitwise>(op, left, right);
|
|
}
|
|
break;
|
|
}
|
|
case Token::SAR:
|
|
instr = AddUncasted<HSar>(left, right);
|
|
break;
|
|
case Token::SHR:
|
|
instr = AddUncasted<HShr>(left, right);
|
|
if (FLAG_opt_safe_uint32_operations && instr->IsShr() &&
|
|
CanBeZero(right)) {
|
|
graph()->RecordUint32Instruction(instr);
|
|
}
|
|
break;
|
|
case Token::SHL:
|
|
instr = AddUncasted<HShl>(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<HBranch>(left_value, expected, eval_right, empty_block)
|
|
: New<HBranch>(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<String> check) {
|
|
CHECK_ALIVE(VisitForTypeOf(sub_expr));
|
|
SetSourcePosition(expr->position());
|
|
HValue* value = Pop();
|
|
HTypeofIsAndBranch* instr = New<HTypeofIsAndBranch>(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<String> 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<String> rhs = Handle<String>::cast(literal->value());
|
|
HClassOfTestAndBranch* instr = New<HClassOfTestAndBranch>(value, rhs);
|
|
return ast_context()->ReturnControl(instr, expr->id());
|
|
}
|
|
|
|
Handle<Type> left_type = expr->left()->bounds().lower;
|
|
Handle<Type> right_type = expr->right()->bounds().lower;
|
|
Handle<Type> combined_type = expr->combined_type();
|
|
Representation combined_rep = Representation::FromType(combined_type);
|
|
Representation left_rep = Representation::FromType(left_type);
|
|
Representation right_rep = Representation::FromType(right_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<HCompareObjectEqAndBranch>(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<JSFunction> target = Handle<JSFunction>::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<String> name = proxy->name();
|
|
Handle<GlobalObject> global(current_info()->global_object());
|
|
LookupResult lookup(isolate());
|
|
global->Lookup(*name, &lookup);
|
|
if (lookup.IsNormal() && lookup.GetValue()->IsJSFunction()) {
|
|
Handle<JSFunction> 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<HInstanceOf>(left, right);
|
|
return ast_context()->ReturnInstruction(result, expr->id());
|
|
} else {
|
|
Add<HCheckValue>(right, target);
|
|
HInstanceOfKnownGlobal* result =
|
|
New<HInstanceOfKnownGlobal>(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<HPushArgument>(left);
|
|
Add<HPushArgument>(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<HInvokeFunction>(function, 2);
|
|
return ast_context()->ReturnInstruction(result, expr->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<HDeoptimize>("Insufficient type feedback for combined type "
|
|
"of binary operation",
|
|
Deoptimizer::SOFT);
|
|
combined_type = left_type = right_type = handle(Type::Any(), isolate());
|
|
}
|
|
|
|
if (combined_type->Is(Type::Receiver())) {
|
|
switch (op) {
|
|
case Token::EQ:
|
|
case Token::EQ_STRICT: {
|
|
// Can we get away with map check and not instance type check?
|
|
if (combined_type->IsClass()) {
|
|
Handle<Map> map = combined_type->AsClass();
|
|
AddCheckMap(left, map);
|
|
AddCheckMap(right, map);
|
|
HCompareObjectEqAndBranch* result =
|
|
New<HCompareObjectEqAndBranch>(left, right);
|
|
if (FLAG_emit_opt_code_positions) {
|
|
result->set_operand_position(zone(), 0, expr->left()->position());
|
|
result->set_operand_position(zone(), 1, expr->right()->position());
|
|
}
|
|
return ast_context()->ReturnControl(result, expr->id());
|
|
} else {
|
|
BuildCheckHeapObject(left);
|
|
Add<HCheckInstanceType>(left, HCheckInstanceType::IS_SPEC_OBJECT);
|
|
BuildCheckHeapObject(right);
|
|
Add<HCheckInstanceType>(right, HCheckInstanceType::IS_SPEC_OBJECT);
|
|
HCompareObjectEqAndBranch* result =
|
|
New<HCompareObjectEqAndBranch>(left, right);
|
|
return ast_context()->ReturnControl(result, expr->id());
|
|
}
|
|
}
|
|
default:
|
|
return Bailout(kUnsupportedNonPrimitiveCompare);
|
|
}
|
|
} else if (combined_type->Is(Type::InternalizedString()) &&
|
|
Token::IsEqualityOp(op)) {
|
|
BuildCheckHeapObject(left);
|
|
Add<HCheckInstanceType>(left, HCheckInstanceType::IS_INTERNALIZED_STRING);
|
|
BuildCheckHeapObject(right);
|
|
Add<HCheckInstanceType>(right, HCheckInstanceType::IS_INTERNALIZED_STRING);
|
|
HCompareObjectEqAndBranch* result =
|
|
New<HCompareObjectEqAndBranch>(left, right);
|
|
return ast_context()->ReturnControl(result, expr->id());
|
|
} else if (combined_type->Is(Type::String())) {
|
|
BuildCheckHeapObject(left);
|
|
Add<HCheckInstanceType>(left, HCheckInstanceType::IS_STRING);
|
|
BuildCheckHeapObject(right);
|
|
Add<HCheckInstanceType>(right, HCheckInstanceType::IS_STRING);
|
|
HStringCompareAndBranch* result =
|
|
New<HStringCompareAndBranch>(left, right, op);
|
|
return ast_context()->ReturnControl(result, expr->id());
|
|
} else {
|
|
if (combined_rep.IsTagged() || combined_rep.IsNone()) {
|
|
HCompareGeneric* result = New<HCompareGeneric>(left, right, op);
|
|
result->set_observed_input_representation(1, left_rep);
|
|
result->set_observed_input_representation(2, right_rep);
|
|
return ast_context()->ReturnInstruction(result, expr->id());
|
|
} else {
|
|
HCompareNumericAndBranch* result =
|
|
New<HCompareNumericAndBranch>(left, right, op);
|
|
result->set_observed_input_representation(left_rep, right_rep);
|
|
if (FLAG_emit_opt_code_positions) {
|
|
result->SetOperandPositions(zone(),
|
|
expr->left()->position(),
|
|
expr->right()->position());
|
|
}
|
|
return ast_context()->ReturnControl(result, expr->id());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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<HCompareObjectEqAndBranch>(value, nil_constant);
|
|
return ast_context()->ReturnControl(instr, expr->id());
|
|
} else {
|
|
ASSERT_EQ(Token::EQ, expr->op());
|
|
Handle<Type> type = expr->combined_type()->Is(Type::None())
|
|
? handle(Type::Any(), isolate_)
|
|
: 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<HConstant>(
|
|
function_state()->compilation_info()->closure());
|
|
} else {
|
|
return New<HThisFunction>();
|
|
}
|
|
}
|
|
|
|
|
|
HInstruction* HOptimizedGraphBuilder::BuildFastLiteral(
|
|
Handle<JSObject> 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<HConstant>(
|
|
boilerplate_object->map()->instance_size());
|
|
|
|
// We should pull pre-tenure mode from the allocation site.
|
|
// For now, just see what it says, and remark on it if it sez
|
|
// we should pretenure. That means the rudimentary counting in the garbage
|
|
// collector is having an effect.
|
|
PretenureFlag pretenure_flag = isolate()->heap()->GetPretenureMode();
|
|
if (FLAG_allocation_site_pretenuring) {
|
|
pretenure_flag = site_context->current()->GetPretenureMode()
|
|
? TENURED
|
|
: NOT_TENURED;
|
|
}
|
|
|
|
HInstruction* object = Add<HAllocate>(object_size_constant, type,
|
|
pretenure_flag, instance_type, site_context->current());
|
|
|
|
BuildEmitObjectHeader(boilerplate_object, object);
|
|
|
|
Handle<FixedArrayBase> 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<HConstant>(elements_size);
|
|
if (boilerplate_object->HasFastDoubleElements()) {
|
|
object_elements = Add<HAllocate>(object_elements_size, HType::JSObject(),
|
|
pretenure_flag, FIXED_DOUBLE_ARRAY_TYPE, site_context->current());
|
|
} else {
|
|
object_elements = Add<HAllocate>(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<JSObject> boilerplate_object,
|
|
HInstruction* object) {
|
|
ASSERT(boilerplate_object->properties()->length() == 0);
|
|
|
|
Handle<Map> boilerplate_object_map(boilerplate_object->map());
|
|
AddStoreMapConstant(object, boilerplate_object_map);
|
|
|
|
Handle<Object> properties_field =
|
|
Handle<Object>(boilerplate_object->properties(), isolate());
|
|
ASSERT(*properties_field == isolate()->heap()->empty_fixed_array());
|
|
HInstruction* properties = Add<HConstant>(properties_field);
|
|
HObjectAccess access = HObjectAccess::ForPropertiesPointer();
|
|
Add<HStoreNamedField>(object, access, properties);
|
|
|
|
if (boilerplate_object->IsJSArray()) {
|
|
Handle<JSArray> boilerplate_array =
|
|
Handle<JSArray>::cast(boilerplate_object);
|
|
Handle<Object> length_field =
|
|
Handle<Object>(boilerplate_array->length(), isolate());
|
|
HInstruction* length = Add<HConstant>(length_field);
|
|
|
|
ASSERT(boilerplate_array->length()->IsSmi());
|
|
Add<HStoreNamedField>(object, HObjectAccess::ForArrayLength(
|
|
boilerplate_array->GetElementsKind()), length);
|
|
}
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::BuildInitElementsInObjectHeader(
|
|
Handle<JSObject> boilerplate_object,
|
|
HInstruction* object,
|
|
HInstruction* object_elements) {
|
|
ASSERT(boilerplate_object->properties()->length() == 0);
|
|
if (object_elements == NULL) {
|
|
Handle<Object> elements_field =
|
|
Handle<Object>(boilerplate_object->elements(), isolate());
|
|
object_elements = Add<HConstant>(elements_field);
|
|
}
|
|
Add<HStoreNamedField>(object, HObjectAccess::ForElementsPointer(),
|
|
object_elements);
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::BuildEmitInObjectProperties(
|
|
Handle<JSObject> boilerplate_object,
|
|
HInstruction* object,
|
|
AllocationSiteUsageContext* site_context,
|
|
PretenureFlag pretenure_flag) {
|
|
Handle<DescriptorArray> 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> name(descriptors->GetKey(i));
|
|
Handle<Object> value =
|
|
Handle<Object>(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<JSObject> value_object = Handle<JSObject>::cast(value);
|
|
Handle<AllocationSite> current_site = site_context->EnterNewScope();
|
|
HInstruction* result =
|
|
BuildFastLiteral(value_object, site_context);
|
|
site_context->ExitScope(current_site, value_object);
|
|
Add<HStoreNamedField>(object, access, result);
|
|
} else {
|
|
Representation representation = details.representation();
|
|
HInstruction* value_instruction = Add<HConstant>(value);
|
|
|
|
if (representation.IsDouble()) {
|
|
// Allocate a HeapNumber box and store the value into it.
|
|
HValue* heap_number_constant = Add<HConstant>(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<HAllocate>(heap_number_constant, HType::HeapNumber(),
|
|
pretenure_flag, HEAP_NUMBER_TYPE);
|
|
AddStoreMapConstant(double_box,
|
|
isolate()->factory()->heap_number_map());
|
|
Add<HStoreNamedField>(double_box, HObjectAccess::ForHeapNumberValue(),
|
|
value_instruction);
|
|
value_instruction = double_box;
|
|
}
|
|
|
|
Add<HStoreNamedField>(object, access, value_instruction);
|
|
}
|
|
}
|
|
|
|
int inobject_properties = boilerplate_object->map()->inobject_properties();
|
|
HInstruction* value_instruction =
|
|
Add<HConstant>(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<HStoreNamedField>(object, access, value_instruction);
|
|
}
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::BuildEmitElements(
|
|
Handle<JSObject> boilerplate_object,
|
|
Handle<FixedArrayBase> elements,
|
|
HValue* object_elements,
|
|
AllocationSiteUsageContext* site_context) {
|
|
ElementsKind kind = boilerplate_object->map()->elements_kind();
|
|
int elements_length = elements->length();
|
|
HValue* object_elements_length = Add<HConstant>(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<FixedArrayBase> elements,
|
|
ElementsKind kind,
|
|
HValue* object_elements) {
|
|
HInstruction* boilerplate_elements = Add<HConstant>(elements);
|
|
int elements_length = elements->length();
|
|
for (int i = 0; i < elements_length; i++) {
|
|
HValue* key_constant = Add<HConstant>(i);
|
|
HInstruction* value_instruction =
|
|
Add<HLoadKeyed>(boilerplate_elements, key_constant,
|
|
static_cast<HValue*>(NULL), kind,
|
|
ALLOW_RETURN_HOLE);
|
|
HInstruction* store = Add<HStoreKeyed>(object_elements, key_constant,
|
|
value_instruction, kind);
|
|
store->SetFlag(HValue::kAllowUndefinedAsNaN);
|
|
}
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::BuildEmitFixedArray(
|
|
Handle<FixedArrayBase> elements,
|
|
ElementsKind kind,
|
|
HValue* object_elements,
|
|
AllocationSiteUsageContext* site_context) {
|
|
HInstruction* boilerplate_elements = Add<HConstant>(elements);
|
|
int elements_length = elements->length();
|
|
Handle<FixedArray> fast_elements = Handle<FixedArray>::cast(elements);
|
|
for (int i = 0; i < elements_length; i++) {
|
|
Handle<Object> value(fast_elements->get(i), isolate());
|
|
HValue* key_constant = Add<HConstant>(i);
|
|
if (value->IsJSObject()) {
|
|
Handle<JSObject> value_object = Handle<JSObject>::cast(value);
|
|
Handle<AllocationSite> current_site = site_context->EnterNewScope();
|
|
HInstruction* result =
|
|
BuildFastLiteral(value_object, site_context);
|
|
site_context->ExitScope(current_site, value_object);
|
|
Add<HStoreKeyed>(object_elements, key_constant, result, kind);
|
|
} else {
|
|
HInstruction* value_instruction =
|
|
Add<HLoadKeyed>(boilerplate_elements, key_constant,
|
|
static_cast<HValue*>(NULL), kind,
|
|
ALLOW_RETURN_HOLE);
|
|
Add<HStoreKeyed>(object_elements, key_constant, value_instruction, kind);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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<Declaration*>* declarations) {
|
|
ASSERT(globals_.is_empty());
|
|
AstVisitor::VisitDeclarations(declarations);
|
|
if (!globals_.is_empty()) {
|
|
Handle<FixedArray> 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<HDeclareGlobals>(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<HStoreContextSlot>(
|
|
context, variable->index(), HStoreContextSlot::kNoCheck, value);
|
|
if (store->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<SharedFunctionInfo> 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<HStoreContextSlot>(
|
|
context, variable->index(), HStoreContextSlot::kNoCheck, value);
|
|
if (store->HasObservableSideEffects()) {
|
|
Add<HSimulate>(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<HIsSmiAndBranch>(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<HHasInstanceTypeAndBranch>(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<HHasInstanceTypeAndBranch>(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<HCompareMinusZeroAndBranch>(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<HHasCachedArrayIndexAndBranch>(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<HHasInstanceTypeAndBranch>(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<HHasInstanceTypeAndBranch>(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<HIsObjectAndBranch>(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<HIsUndetectableAndBranch>(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<HIsConstructCallAndBranch>(),
|
|
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<HArgumentsElements>(false);
|
|
HArgumentsLength* result = New<HArgumentsLength>(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<HArgumentsElements>(false);
|
|
HInstruction* length = Add<HArgumentsLength>(elements);
|
|
HInstruction* checked_index = Add<HBoundsCheck>(index, length);
|
|
HAccessArgumentsAt* result = New<HAccessArgumentsAt>(
|
|
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<HValueOf>(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<HDateField>(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<HSeqStringSetChar>(String::ONE_BYTE_ENCODING, string,
|
|
index, value);
|
|
Add<HSimulate>(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<HSeqStringSetChar>(String::TWO_BYTE_ENCODING, string,
|
|
index, value);
|
|
Add<HSimulate>(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<HIsSmiAndBranch>(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<HHasInstanceTypeAndBranch>(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<HStoreNamedField>(object,
|
|
HObjectAccess::ForJSObjectOffset(JSValue::kValueOffset), value);
|
|
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<HStringCharFromCode>(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<HStringCharFromCode>(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<HCompareObjectEqAndBranch>(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<HStringAdd>(left, right, STRING_ADD_CHECK_BOTH);
|
|
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<HCallStub>(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<HCallStub>(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<HCallStub>(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<HCallStub>(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, handle(Type::Number(), isolate()));
|
|
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<HHasInstanceTypeAndBranch>(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<HInvokeFunction>(function, arg_count);
|
|
Drop(arg_count);
|
|
Push(invoke_result);
|
|
Goto(if_jsfunction, join);
|
|
|
|
set_current_block(if_nonfunction);
|
|
HInstruction* call_result = Add<HCallFunction>(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<HPower>(left, right);
|
|
return ast_context()->ReturnInstruction(result, call->id());
|
|
}
|
|
|
|
|
|
void HOptimizedGraphBuilder::GenerateMathLog(CallRuntime* call) {
|
|
ASSERT_EQ(1, call->arguments()->length());
|
|
CHECK_ALIVE(VisitArgumentList(call->arguments()));
|
|
HCallStub* result = New<HCallStub>(CodeStub::TranscendentalCache, 1);
|
|
result->set_transcendental_type(TranscendentalCache::LOG);
|
|
Drop(1);
|
|
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<HUnaryMathOperation>(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<HGetCachedArrayIndex>(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<HDebugBreak>();
|
|
return ast_context()->ReturnValue(graph()->GetConstant0());
|
|
}
|
|
|
|
|
|
#undef CHECK_BAILOUT
|
|
#undef CHECK_ALIVE
|
|
|
|
|
|
HEnvironment::HEnvironment(HEnvironment* outer,
|
|
Scope* scope,
|
|
Handle<JSFunction> 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<JSFunction> 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<JSFunction> 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<JSFunction> target,
|
|
int arguments,
|
|
FunctionLiteral* function,
|
|
HConstant* undefined,
|
|
InliningKind inlining_kind,
|
|
bool undefined_receiver) 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);
|
|
}
|
|
// If the function we are inlining is a strict mode function or a
|
|
// builtin function, pass undefined as the receiver for function
|
|
// calls (instead of the global receiver).
|
|
if (undefined_receiver) {
|
|
inner->SetValueAt(0, undefined);
|
|
}
|
|
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<String> 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<int64_t>(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<HBasicBlock*>* 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");
|
|
const char* flags = current->IsLoopSuccessorDominator()
|
|
? "dom-loop-succ"
|
|
: "";
|
|
PrintStringProperty("flags", flags);
|
|
|
|
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<LInstruction*>* 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<LiveRange*>* 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<LiveRange*>* fixed = allocator->fixed_live_ranges();
|
|
for (int i = 0; i < fixed->length(); ++i) {
|
|
TraceLiveRange(fixed->at(i), "fixed", allocator->zone());
|
|
}
|
|
|
|
const ZoneList<LiveRange*>* 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<double>(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<double>(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
|